diff --git a/App.vue b/App.vue
index 2daafce2751534639762df463d08d749ab40d515..ad67aa36db3de3ab1c6d4c14d76f2acd5b44ee85 100644
--- a/App.vue
+++ b/App.vue
@@ -1,34 +1,37 @@
-
-
-
diff --git a/api/login.js b/api/login.js
index 54fb62cd5b1533b462ca88f1a755e06d198d0091..68ed518432b78df4ada320ee74bad487f28bdb60 100644
--- a/api/login.js
+++ b/api/login.js
@@ -1,12 +1,10 @@
import request from '@/utils/request'
// 登录方法
-export function login(username, password, code, uuid) {
+export function login(username, password) {
const data = {
username,
- password,
- code,
- uuid
+ password
}
return request({
'url': '/login',
@@ -14,20 +12,30 @@ export function login(username, password, code, uuid) {
isToken: false
},
'method': 'post',
- 'data': data
+ 'params': data
})
}
-
-// 注册方法
-export function register(data) {
- return request({
- url: '/register',
- headers: {
- isToken: false
- },
- method: 'post',
- data: data
- })
+// 微信登录
+export function wxLoginAPI(data) {
+ return request({
+ 'url': '/wxlogin',
+ headers: {
+ isToken: false
+ },
+ 'method': 'post',
+ 'params': data
+ })
+}
+// 注册方法
+export function register(data) {
+ return request({
+ url: '/register',
+ headers: {
+ isToken: false
+ },
+ method: 'post',
+ data: data
+ })
}
// 获取用户详细信息
@@ -56,4 +64,4 @@ export function getCodeImg() {
method: 'get',
timeout: 20000
})
-}
+}
diff --git a/api/system/dict/data.js b/api/system/dict/data.js
new file mode 100644
index 0000000000000000000000000000000000000000..6c9eb79b4d98af0a82361826dd078abfa8e02c98
--- /dev/null
+++ b/api/system/dict/data.js
@@ -0,0 +1,52 @@
+import request from '@/utils/request'
+
+// 查询字典数据列表
+export function listData(query) {
+ return request({
+ url: '/system/dict/data/list',
+ method: 'get',
+ params: query
+ })
+}
+
+// 查询字典数据详细
+export function getData(dictCode) {
+ return request({
+ url: '/system/dict/data/' + dictCode,
+ method: 'get'
+ })
+}
+
+// 根据字典类型查询字典数据信息
+export function getDicts(dictType) {
+ return request({
+ url: '/system/dict/data/type/' + dictType,
+ method: 'get'
+ })
+}
+
+// 新增字典数据
+export function addData(data) {
+ return request({
+ url: '/system/dict/data',
+ method: 'post',
+ data: data
+ })
+}
+
+// 修改字典数据
+export function updateData(data) {
+ return request({
+ url: '/system/dict/data',
+ method: 'put',
+ data: data
+ })
+}
+
+// 删除字典数据
+export function delData(dictCode) {
+ return request({
+ url: '/system/dict/data/' + dictCode,
+ method: 'delete'
+ })
+}
diff --git a/api/system/dict/type.js b/api/system/dict/type.js
new file mode 100644
index 0000000000000000000000000000000000000000..a0254baa713dd9e34b41d451c08d5d01909a8bb3
--- /dev/null
+++ b/api/system/dict/type.js
@@ -0,0 +1,60 @@
+import request from '@/utils/request'
+
+// 查询字典类型列表
+export function listType(query) {
+ return request({
+ url: '/system/dict/type/list',
+ method: 'get',
+ params: query
+ })
+}
+
+// 查询字典类型详细
+export function getType(dictId) {
+ return request({
+ url: '/system/dict/type/' + dictId,
+ method: 'get'
+ })
+}
+
+// 新增字典类型
+export function addType(data) {
+ return request({
+ url: '/system/dict/type',
+ method: 'post',
+ data: data
+ })
+}
+
+// 修改字典类型
+export function updateType(data) {
+ return request({
+ url: '/system/dict/type',
+ method: 'put',
+ data: data
+ })
+}
+
+// 删除字典类型
+export function delType(dictId) {
+ return request({
+ url: '/system/dict/type/' + dictId,
+ method: 'delete'
+ })
+}
+
+// 刷新字典缓存
+export function refreshCache() {
+ return request({
+ url: '/system/dict/type/refreshCache',
+ method: 'delete'
+ })
+}
+
+// 获取字典选择框列表
+export function optionselect() {
+ return request({
+ url: '/system/dict/type/optionselect',
+ method: 'get'
+ })
+}
diff --git a/api/tcm/case.js b/api/tcm/case.js
new file mode 100644
index 0000000000000000000000000000000000000000..0cf7c5b129268cab6ac7002a4dfbbb8794c084fa
--- /dev/null
+++ b/api/tcm/case.js
@@ -0,0 +1,85 @@
+import request from '@/utils/request'
+
+
+
+// 查询医案列表
+export function getCaseList(query) {
+ return request({
+ url: '/tcm/case/list',
+ method: 'get',
+ params: query
+ })
+}
+
+
+// 查询中药列表
+export function getMedicineList(query) {
+ return request({
+ url: '/tcm/search/shortcut',
+ method: 'get',
+ params: query
+ })
+}
+
+// 查询方剂详情
+export function getComposeDetail(query) {
+ return request({
+ url: '/tcm/search/compose/detail',
+ method: 'get',
+ params: query
+ })
+}
+
+// 新增医案
+export function addCase(query) {
+ return request({
+ url: '/tcm/case/create',
+ method: 'post',
+ data: query
+ })
+}
+
+// 编辑医案
+export function updateCase(query) {
+ return request({
+ url: '/tcm/case/edit/'+query.id,
+ method: 'put',
+ data: query
+ })
+}
+
+// 获取病案详情
+export function getCaseDetail(query) {
+ return request({
+ url: '/tcm/case/details',
+ method: 'get',
+ params: query
+ })
+}
+
+// 获取上一个下一个
+export function getCaseNextDetail(query) {
+ return request({
+ url: '/tcm/case/offset',
+ method: 'get',
+ params: query
+ })
+}
+
+// 删除病案
+export function deleteCase(query) {
+ return request({
+ url: '/tcm/case/delete',
+ method: 'post',
+ data: query
+ })
+}
+
+// 删除文件
+export function deleteFile(query) {
+ return request({
+ url: '/tcm/common/files/remove',
+ method: 'post',
+ data: query
+ })
+}
\ No newline at end of file
diff --git a/api/tcm/patient.js b/api/tcm/patient.js
new file mode 100644
index 0000000000000000000000000000000000000000..4471fd13eb6108cf3bccb0a3820d47c98916b46e
--- /dev/null
+++ b/api/tcm/patient.js
@@ -0,0 +1,49 @@
+import request from '@/utils/request'
+
+
+
+// 查询就诊人列表
+export function getPatientList(query) {
+ return request({
+ url: '/tcm/patient/list',
+ method: 'get',
+ params: query
+ })
+}
+
+
+// 查询就诊人详情
+export function getPatientDetails(id) {
+ return request({
+ url: '/tcm/patient/details',
+ method: 'get',
+ params:{"patientId":id}
+ })
+}
+
+// 创建就诊人
+export function createPatient(patientObj) {
+ return request({
+ url: '/tcm/patient/create',
+ method: 'post',
+ data:patientObj
+ })
+}
+
+// 修改就诊人
+export function editPatient(patientObj) {
+ return request({
+ url: '/tcm/patient/edit',
+ method: 'put',
+ data:patientObj
+ })
+}
+
+// 删除就诊人
+export function deletePatients(ids) {
+ return request({
+ url: '/tcm/patient/delete',
+ method: 'post',
+ data:[ids]
+ })
+}
\ No newline at end of file
diff --git a/components/swiper-list-item/swiper-list-item.vue b/components/swiper-list-item/swiper-list-item.vue
new file mode 100644
index 0000000000000000000000000000000000000000..cebe6276e65defdf8fbab0f74dcaa8bda4369cfa
--- /dev/null
+++ b/components/swiper-list-item/swiper-list-item.vue
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+ {{item.title}}
+ {{item.detail}}
+
+
+
+
+
+
+
+
+
diff --git a/config.js b/config.js
index 333f63ea1e643e93a8ee551a38040caebad2b3fc..dbc7d4eba4615b24ce16be5fdee6d1ad7ed07625 100644
--- a/config.js
+++ b/config.js
@@ -1,15 +1,16 @@
// 应用全局配置
-module.exports = {
- baseUrl: 'https://vue.ruoyi.vip/prod-api',
- // baseUrl: 'http://localhost:8080',
+export default {
+ // baseUrl: 'https://vue.ruoyi.vip/prod-api',
+ // baseUrl: 'http://8.156.79.168:8000',
+ baseUrl: 'https://huaici.wecarry.cn',
// 应用信息
appInfo: {
// 应用名称
- name: "ruoyi-app",
+ name: "怀慈医录",
// 应用版本
version: "1.2.0",
// 应用logo
- logo: "/static/logo.png",
+ logo: "/static/images/logo.png",
// 官方网站
site_url: "http://ruoyi.vip",
// 政策协议
diff --git a/index.html b/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..2511ed7c39df30402294d3cb8213a2cba4a88d04
--- /dev/null
+++ b/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+ RuoYi-App
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/js_sdk/jason-alioss-upload/base64.js b/js_sdk/jason-alioss-upload/base64.js
new file mode 100644
index 0000000000000000000000000000000000000000..b7d5d8b92491e00d5d2c8795f49bcd18a1ece48e
--- /dev/null
+++ b/js_sdk/jason-alioss-upload/base64.js
@@ -0,0 +1,131 @@
+export const Base64 = {
+
+ // private property
+ _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
+
+ // public method for encoding
+ encode: function(input) {
+ var output = "";
+ var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
+ var i = 0;
+
+ input = Base64._utf8_encode(input);
+
+ while (i < input.length) {
+
+ chr1 = input.charCodeAt(i++);
+ chr2 = input.charCodeAt(i++);
+ chr3 = input.charCodeAt(i++);
+
+ enc1 = chr1 >> 2;
+ enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
+ enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
+ enc4 = chr3 & 63;
+
+ if (isNaN(chr2)) {
+ enc3 = enc4 = 64;
+ } else if (isNaN(chr3)) {
+ enc4 = 64;
+ }
+
+ output = output +
+ this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
+ this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
+
+ }
+
+ return output;
+ },
+
+ // public method for decoding
+ decode: function(input) {
+ var output = "";
+ var chr1, chr2, chr3;
+ var enc1, enc2, enc3, enc4;
+ var i = 0;
+
+ input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
+
+ while (i < input.length) {
+
+ enc1 = this._keyStr.indexOf(input.charAt(i++));
+ enc2 = this._keyStr.indexOf(input.charAt(i++));
+ enc3 = this._keyStr.indexOf(input.charAt(i++));
+ enc4 = this._keyStr.indexOf(input.charAt(i++));
+
+ chr1 = (enc1 << 2) | (enc2 >> 4);
+ chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
+ chr3 = ((enc3 & 3) << 6) | enc4;
+
+ output = output + String.fromCharCode(chr1);
+
+ if (enc3 != 64) {
+ output = output + String.fromCharCode(chr2);
+ }
+ if (enc4 != 64) {
+ output = output + String.fromCharCode(chr3);
+ }
+
+ }
+
+ output = Base64._utf8_decode(output);
+
+ return output;
+
+ },
+
+ // private method for UTF-8 encoding
+ _utf8_encode: function(string) {
+ string = string.replace(/\r\n/g, "\n");
+ var utftext = "";
+
+ for (var n = 0; n < string.length; n++) {
+
+ var c = string.charCodeAt(n);
+
+ if (c < 128) {
+ utftext += String.fromCharCode(c);
+ } else if ((c > 127) && (c < 2048)) {
+ utftext += String.fromCharCode((c >> 6) | 192);
+ utftext += String.fromCharCode((c & 63) | 128);
+ } else {
+ utftext += String.fromCharCode((c >> 12) | 224);
+ utftext += String.fromCharCode(((c >> 6) & 63) | 128);
+ utftext += String.fromCharCode((c & 63) | 128);
+ }
+
+ }
+
+ return utftext;
+ },
+
+ // private method for UTF-8 decoding
+ _utf8_decode: function(utftext) {
+ var string = "";
+ var i = 0;
+ var c = c1 = c2 = 0;
+
+ while (i < utftext.length) {
+
+ c = utftext.charCodeAt(i);
+
+ if (c < 128) {
+ string += String.fromCharCode(c);
+ i++;
+ } else if ((c > 191) && (c < 224)) {
+ c2 = utftext.charCodeAt(i + 1);
+ string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
+ i += 2;
+ } else {
+ c2 = utftext.charCodeAt(i + 1);
+ c3 = utftext.charCodeAt(i + 2);
+ string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
+ i += 3;
+ }
+
+ }
+
+ return string;
+ }
+
+}
\ No newline at end of file
diff --git a/js_sdk/jason-alioss-upload/crypto.js b/js_sdk/jason-alioss-upload/crypto.js
new file mode 100644
index 0000000000000000000000000000000000000000..edfc6dee8fec8522fc77d4a9a081619318ee06f9
--- /dev/null
+++ b/js_sdk/jason-alioss-upload/crypto.js
@@ -0,0 +1,166 @@
+const base64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+// Crypto utilities
+export const util = {
+
+ // Bit-wise rotate left
+ rotl: function (n, b) {
+ return (n << b) | (n >>> (32 - b));
+ },
+
+ // Bit-wise rotate right
+ rotr: function (n, b) {
+ return (n << (32 - b)) | (n >>> b);
+ },
+
+ // Swap big-endian to little-endian and vice versa
+ endian: function (n) {
+
+ // If number given, swap endian
+ if (n.constructor == Number) {
+ return util.rotl(n, 8) & 0x00FF00FF |
+ util.rotl(n, 24) & 0xFF00FF00;
+ }
+
+ // Else, assume array and swap all items
+ for (var i = 0; i < n.length; i++)
+ n[i] = util.endian(n[i]);
+ return n;
+
+ },
+
+ // Generate an array of any length of random bytes
+ randomBytes: function (n) {
+ for (var bytes = []; n > 0; n--)
+ bytes.push(Math.floor(Math.random() * 256));
+ return bytes;
+ },
+
+ // Convert a string to a byte array
+ stringToBytes: function (str) {
+ var bytes = [];
+ for (var i = 0; i < str.length; i++)
+ bytes.push(str.charCodeAt(i));
+ return bytes;
+ },
+
+ // Convert a byte array to a string
+ bytesToString: function (bytes) {
+ var str = [];
+ for (var i = 0; i < bytes.length; i++)
+ str.push(String.fromCharCode(bytes[i]));
+ return str.join("");
+ },
+
+ // Convert a string to big-endian 32-bit words
+ stringToWords: function (str) {
+ var words = [];
+ for (var c = 0, b = 0; c < str.length; c++, b += 8)
+ words[b >>> 5] |= str.charCodeAt(c) << (24 - b % 32);
+ return words;
+ },
+
+ // Convert a byte array to big-endian 32-bits words
+ bytesToWords: function (bytes) {
+ var words = [];
+ for (var i = 0, b = 0; i < bytes.length; i++, b += 8)
+ words[b >>> 5] |= bytes[i] << (24 - b % 32);
+ return words;
+ },
+
+ // Convert big-endian 32-bit words to a byte array
+ wordsToBytes: function (words) {
+ var bytes = [];
+ for (var b = 0; b < words.length * 32; b += 8)
+ bytes.push((words[b >>> 5] >>> (24 - b % 32)) & 0xFF);
+ return bytes;
+ },
+
+ // Convert a byte array to a hex string
+ bytesToHex: function (bytes) {
+ var hex = [];
+ for (var i = 0; i < bytes.length; i++) {
+ hex.push((bytes[i] >>> 4).toString(16));
+ hex.push((bytes[i] & 0xF).toString(16));
+ }
+ return hex.join("");
+ },
+
+ // Convert a hex string to a byte array
+ hexToBytes: function (hex) {
+ var bytes = [];
+ for (var c = 0; c < hex.length; c += 2)
+ bytes.push(parseInt(hex.substr(c, 2), 16));
+ return bytes;
+ },
+
+ // Convert a byte array to a base-64 string
+ bytesToBase64: function (bytes) {
+
+ // Use browser-native function if it exists
+ if (typeof btoa == "function") return btoa(util.bytesToString(bytes));
+
+ var base64 = [],
+ overflow;
+
+ for (var i = 0; i < bytes.length; i++) {
+ switch (i % 3) {
+ case 0:
+ base64.push(base64map.charAt(bytes[i] >>> 2));
+ overflow = (bytes[i] & 0x3) << 4;
+ break;
+ case 1:
+ base64.push(base64map.charAt(overflow | (bytes[i] >>> 4)));
+ overflow = (bytes[i] & 0xF) << 2;
+ break;
+ case 2:
+ base64.push(base64map.charAt(overflow | (bytes[i] >>> 6)));
+ base64.push(base64map.charAt(bytes[i] & 0x3F));
+ overflow = -1;
+ }
+ }
+
+ // Encode overflow bits, if there are any
+ if (overflow != undefined && overflow != -1)
+ base64.push(base64map.charAt(overflow));
+
+ // Add padding
+ while (base64.length % 4 != 0) base64.push("=");
+
+ return base64.join("");
+
+ },
+
+ // Convert a base-64 string to a byte array
+ base64ToBytes: function (base64) {
+
+ // Use browser-native function if it exists
+ if (typeof atob == "function") return util.stringToBytes(atob(base64));
+
+ // Remove non-base-64 characters
+ base64 = base64.replace(/[^A-Z0-9+\/]/ig, "");
+
+ var bytes = [];
+
+ for (var i = 0; i < base64.length; i++) {
+ switch (i % 4) {
+ case 1:
+ bytes.push((base64map.indexOf(base64.charAt(i - 1)) << 2) |
+ (base64map.indexOf(base64.charAt(i)) >>> 4));
+ break;
+ case 2:
+ bytes.push(((base64map.indexOf(base64.charAt(i - 1)) & 0xF) << 4) |
+ (base64map.indexOf(base64.charAt(i)) >>> 2));
+ break;
+ case 3:
+ bytes.push(((base64map.indexOf(base64.charAt(i - 1)) & 0x3) << 6) |
+ (base64map.indexOf(base64.charAt(i))));
+ break;
+ }
+ }
+
+ return bytes;
+
+ }
+
+}
diff --git a/js_sdk/jason-alioss-upload/hmac.js b/js_sdk/jason-alioss-upload/hmac.js
new file mode 100644
index 0000000000000000000000000000000000000000..df53729c4202a4e66b65cb17455e7f161b93b614
--- /dev/null
+++ b/js_sdk/jason-alioss-upload/hmac.js
@@ -0,0 +1,25 @@
+import { util } from './crypto.js'
+
+export function HMAC(hasher, message, key, options) {
+ const _blocksize = 16
+ // Allow arbitrary length keys
+ key = key.length > _blocksize * 4 ?
+ hasher(key, { asBytes: true }) :
+ util.stringToBytes(key);
+
+ // XOR keys with pad constants
+ var okey = key,
+ ikey = key.slice(0);
+ for (var i = 0; i < _blocksize * 4; i++) {
+ okey[i] ^= 0x5C;
+ ikey[i] ^= 0x36;
+ }
+
+ var hmacbytes = hasher(util.bytesToString(okey) +
+ hasher(util.bytesToString(ikey) + message, { asString: true }),
+ { asBytes: true });
+ return options && options.asBytes ? hmacbytes :
+ options && options.asString ? util.bytesToString(hmacbytes) :
+ util.bytesToHex(hmacbytes);
+
+}
\ No newline at end of file
diff --git a/js_sdk/jason-alioss-upload/oss.js b/js_sdk/jason-alioss-upload/oss.js
new file mode 100644
index 0000000000000000000000000000000000000000..5cf4763ad43d6de239064309490bdc004a41f4cc
--- /dev/null
+++ b/js_sdk/jason-alioss-upload/oss.js
@@ -0,0 +1,71 @@
+import { Base64 } from './base64.js'
+import { util } from './crypto.js'
+import { HMAC } from './hmac.js'
+import { SHA1 } from './sha1.js'
+
+// 下面这3个信息必填
+const url = 'https://wecarry-prod-bucket.oss-cn-chengdu.aliyuncs.com/'
+const OSSAccessKeyId = 'LTAI5tH1bQWvvgnxzbR9Nt1Z'
+const OssAccesskeySercet= '6oycHYtFCQruh0cx7W41bwh3NsP5pX';
+
+
+const policyText = {
+ "expiration": "2034-01-01T12:00:00.000Z", // 设置Policy的有效期,格式为UTC时间。如果Policy失效,将无法上传文件。
+ "conditions": [
+ ["content-length-range", 0, 1048576000] // 限制上传文件的大小,单位为字节,此处限制文件大小为1 GB。如果上传的文件大小超过此限制,文件将无法上传到OSS。如果未设置该限制,则默认文件大小最大为5 GB。
+ ]
+}
+
+const policy = Base64.encode(JSON.stringify(policyText))
+const bytes = HMAC(SHA1, policy, OssAccesskeySercet, { asBytes: true })
+const signature = util.bytesToBase64(bytes)
+
+// 生成文件名随机字符串
+function random_string(len) {
+ const strLeng = len || 32;
+ const chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
+ const maxPos = chars.length;
+ let pwd = '';
+ for (let i = 0; i < strLeng; i++) {
+ pwd += chars.charAt(Math.floor(Math.random() * maxPos));
+ }
+ return pwd;
+}
+
+// 获取文件后缀
+function get_suffix(filename) {
+ const pos = filename.lastIndexOf('.')
+ console.log(filename,pos,filename.substring(pos))
+ let suffix = ''
+ if (pos != -1) {
+ suffix = filename.substring(pos)
+ }
+ return suffix;
+}
+
+// dir格式:'img/'
+export const ossUpload = (filePath, name, dir) => {
+ const key = dir + random_string(10) + get_suffix(name)
+ console.log(key,name,url)
+ return new Promise((resolve, reject) => {
+ uni.uploadFile({
+ url,
+ filePath,
+ name: 'file',
+ formData: {
+ name,
+ key,
+ policy,
+ OSSAccessKeyId,
+ success_action_status: '200',
+ signature
+ },
+ success: () => {
+ resolve({success: true, data: key})
+ },
+ fail: () => {
+ reject({success: false, data: '上传失败'})
+ }
+ })
+ })
+}
\ No newline at end of file
diff --git a/js_sdk/jason-alioss-upload/package.json b/js_sdk/jason-alioss-upload/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..d3a3c9986bb98bf29f48f0accfea8f817524bdf3
--- /dev/null
+++ b/js_sdk/jason-alioss-upload/package.json
@@ -0,0 +1,20 @@
+{
+ "id": "jason-alioss-upload",
+ "name": "阿里云存储OSS前端直接上传(全端通用) - 前端JASON",
+ "displayName": "阿里云存储OSS前端直接上传(全端通用) - 前端JASON",
+ "version": "1.0.0",
+ "description": "阿里云oss存储前端直传,不需要后端鉴权及签名;h5、小程序、app全端通用;支持vue2、vue3",
+ "keywords": [
+ "对象存储",
+ "oss",
+ "文件上传",
+ "阿里云",
+ "oss上传"
+ ],
+ "dcloudext": {
+ "category": [
+ "JS SDK",
+ "通用 SDK"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/js_sdk/jason-alioss-upload/sha1.js b/js_sdk/jason-alioss-upload/sha1.js
new file mode 100644
index 0000000000000000000000000000000000000000..63724c139f1a20ef162ab330a5b7352f250871d2
--- /dev/null
+++ b/js_sdk/jason-alioss-upload/sha1.js
@@ -0,0 +1,64 @@
+import { util } from './crypto.js'
+
+// The core
+function _sha1(message) {
+
+ var m = util.stringToWords(message),
+ l = message.length * 8,
+ w = [],
+ H0 = 1732584193,
+ H1 = -271733879,
+ H2 = -1732584194,
+ H3 = 271733878,
+ H4 = -1009589776;
+
+ // Padding
+ m[l >> 5] |= 0x80 << (24 - l % 32);
+ m[((l + 64 >>> 9) << 4) + 15] = l;
+
+ for (var i = 0; i < m.length; i += 16) {
+
+ var a = H0,
+ b = H1,
+ c = H2,
+ d = H3,
+ e = H4;
+
+ for (var j = 0; j < 80; j++) {
+
+ if (j < 16) w[j] = m[i + j];
+ else {
+ var n = w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16];
+ w[j] = (n << 1) | (n >>> 31);
+ }
+
+ var t = ((H0 << 5) | (H0 >>> 27)) + H4 + (w[j] >>> 0) + (
+ j < 20 ? (H1 & H2 | ~H1 & H3) + 1518500249 :
+ j < 40 ? (H1 ^ H2 ^ H3) + 1859775393 :
+ j < 60 ? (H1 & H2 | H1 & H3 | H2 & H3) - 1894007588 :
+ (H1 ^ H2 ^ H3) - 899497514);
+
+ H4 = H3;
+ H3 = H2;
+ H2 = (H1 << 30) | (H1 >>> 2);
+ H1 = H0;
+ H0 = t;
+
+ }
+
+ H0 += a;
+ H1 += b;
+ H2 += c;
+ H3 += d;
+ H4 += e;
+ }
+ return [H0, H1, H2, H3, H4];
+};
+
+// Public API
+export function SHA1(message, options) {
+ var digestbytes = util.wordsToBytes(_sha1(message));
+ return options && options.asBytes ? digestbytes :
+ options && options.asString ? util.bytesToString(digestbytes) :
+ util.bytesToHex(digestbytes);
+};
\ No newline at end of file
diff --git a/main.js b/main.js
index e742834c3c866d1b43b584519933e0c7524e0f52..76aa9bd3e58f3fb89b911c5200c26e740a63cb51 100644
--- a/main.js
+++ b/main.js
@@ -1,17 +1,16 @@
-import Vue from 'vue'
-import App from './App'
-import store from './store' // store
-import plugins from './plugins' // plugins
-import './permission' // permission
-Vue.use(plugins)
-
-Vue.config.productionTip = false
-Vue.prototype.$store = store
-
-App.mpType = 'app'
-
-const app = new Vue({
- ...App
-})
-
-app.$mount()
+import { createSSRApp } from 'vue'
+import App from './App'
+import store from './store' // store
+import { install } from './plugins' // plugins
+import './permission' // permission
+import { useDict } from '@/utils/dict'
+
+export function createApp() {
+ const app = createSSRApp(App)
+ app.use(store)
+ app.config.globalProperties.useDict = useDict
+ install(app)
+ return {
+ app
+ }
+}
diff --git a/manifest.json b/manifest.json
index 03ee107e6808e9100df8f05115435cd007da0820..7aad53226bf9ef7a426f92bd8b1511a2bc89ad4f 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,8 +1,8 @@
{
- "name" : "若依移动端",
- "appid" : "__UNI__25A9D80",
- "description" : "",
- "versionName" : "1.2.0",
+ "name" : "怀慈医录",
+ "appid" : "__UNI__C3C5599",
+ "description" : "记录中医医案",
+ "versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
"app-plus" : {
@@ -35,13 +35,15 @@
""
]
},
- "ios" : {},
+ "ios" : {
+ "dSYMs" : false
+ },
"sdkConfigs" : {}
}
},
"quickapp" : {},
"mp-weixin" : {
- "appid" : "wxccd7e2a0911b3397",
+ "appid" : "wxe63c6aa88812b004",
"setting" : {
"urlCheck" : false,
"es6" : false,
@@ -51,9 +53,12 @@
"optimization" : {
"subPackages" : true
},
- "usingComponents" : true
+ "usingComponents" : true,
+ "uniStatistics" : {
+ "enable" : true
+ }
},
- "vueVersion" : "2",
+ "vueVersion" : "3",
"h5" : {
"template" : "static/index.html",
"devServer" : {
@@ -65,5 +70,6 @@
"mode" : "hash",
"base" : "./"
}
- }
+ },
+ "locale" : "zh-Hans"
}
diff --git a/pages.json b/pages.json
index 955146f1ee3ca86b5607ec98698a551f83e096e5..39d6734a38e0b8a942106ebd3b37690ce44c6408 100644
--- a/pages.json
+++ b/pages.json
@@ -1,102 +1,116 @@
{
- "pages": [{
- "path": "pages/login",
- "style": {
- "navigationBarTitleText": "登录"
- }
- }, {
- "path": "pages/register",
- "style": {
- "navigationBarTitleText": "注册"
- }
- }, {
- "path": "pages/index",
- "style": {
- "navigationBarTitleText": "若依移动端框架",
- "navigationStyle": "custom"
- }
- }, {
- "path": "pages/work/index",
- "style": {
- "navigationBarTitleText": "工作台"
- }
- }, {
- "path": "pages/mine/index",
- "style": {
- "navigationBarTitleText": "我的"
- }
- }, {
- "path": "pages/mine/avatar/index",
- "style": {
- "navigationBarTitleText": "修改头像"
- }
- }, {
- "path": "pages/mine/info/index",
- "style": {
- "navigationBarTitleText": "个人信息"
- }
- }, {
- "path": "pages/mine/info/edit",
- "style": {
- "navigationBarTitleText": "编辑资料"
- }
- }, {
- "path": "pages/mine/pwd/index",
- "style": {
- "navigationBarTitleText": "修改密码"
- }
- }, {
- "path": "pages/mine/setting/index",
- "style": {
- "navigationBarTitleText": "应用设置"
- }
- }, {
- "path": "pages/mine/help/index",
- "style": {
- "navigationBarTitleText": "常见问题"
- }
- }, {
- "path": "pages/mine/about/index",
- "style": {
- "navigationBarTitleText": "关于我们"
- }
- }, {
- "path": "pages/common/webview/index",
- "style": {
- "navigationBarTitleText": "浏览网页"
- }
- }, {
- "path": "pages/common/textview/index",
- "style": {
- "navigationBarTitleText": "浏览文本"
- }
- }],
- "tabBar": {
- "color": "#000000",
- "selectedColor": "#000000",
- "borderStyle": "white",
- "backgroundColor": "#ffffff",
- "list": [{
- "pagePath": "pages/index",
- "iconPath": "static/images/tabbar/home.png",
- "selectedIconPath": "static/images/tabbar/home_.png",
- "text": "首页"
- }, {
- "pagePath": "pages/work/index",
- "iconPath": "static/images/tabbar/work.png",
- "selectedIconPath": "static/images/tabbar/work_.png",
- "text": "工作台"
- }, {
- "pagePath": "pages/mine/index",
- "iconPath": "static/images/tabbar/mine.png",
- "selectedIconPath": "static/images/tabbar/mine_.png",
- "text": "我的"
- }
- ]
- },
- "globalStyle": {
- "navigationBarTextStyle": "black",
- "navigationBarTitleText": "RuoYi",
- "navigationBarBackgroundColor": "#FFFFFF"
- }
-}
+ "pages": [
+ {
+ "path": "pages/login",
+ "style": {
+ "navigationBarTitleText": "登录",
+ "enablePullDownRefresh": false
+ }
+ }, {
+ "path": "pages/register",
+ "style": {
+ "navigationBarTitleText": "注册",
+ "enablePullDownRefresh": false
+ }
+ },
+ {
+ "path": "pages/index",
+ "style": {
+ "navigationBarTitleText": "怀慈医录",
+ "enablePullDownRefresh": false
+ // "navigationStyle": "custom"
+ }
+ }, {
+ "path": "pages/mine/index",
+ "style": {
+ "navigationBarTitleText": "我的",
+ "enablePullDownRefresh": false
+ }
+ }, {
+ "path": "pages/common/webview/index",
+ "style": {
+ "navigationBarTitleText": "浏览网页"
+ }
+ }, {
+ "path": "pages/common/textview/index",
+ "style": {
+ "navigationBarTitleText": "浏览文本"
+ }
+ }, {
+ "path": "pages/common/agreements/services",
+ "style": {
+ "navigationBarTitleText": "用户协议"
+ }
+ }, {
+ "path": "pages/common/agreements/privacy",
+ "style": {
+ "navigationBarTitleText": "隐私协议"
+ }
+ },
+
+ {
+ "path": "pages/patients/edit",
+ "style": {
+ "navigationBarTitleText": "编辑就诊人",
+ "enablePullDownRefresh": false
+ }
+ },
+ {
+ "path": "pages/tcmcases/addCase",
+ "style": {
+ "navigationBarTitleText": "添加病案",
+ "enablePullDownRefresh": false
+ }
+ },
+ {
+ "path": "pages/tcmcases/detail",
+ "style": {
+ "navigationBarTitleText": "病案详情",
+ "enablePullDownRefresh": false
+ }
+ },
+ {
+ "path": "pages/tcmcases/prescript",
+ "style": {
+ "navigationBarTitleText": "编辑处方",
+ "enablePullDownRefresh": false
+ }
+ },
+ {
+ "path": "pages/patients/add",
+ "style": {
+ "navigationBarTitleText": "添加就诊人",
+ "enablePullDownRefresh": false
+ }
+ }, {
+ "path": "pages/patients/select",
+ "style": {
+ "navigationBarTitleText": "选择就诊人",
+ "enablePullDownRefresh": false
+ }
+ }
+ ],
+ "tabBar": {
+ "color": "#000000",
+ "selectedColor": "#2563eb",
+ "borderStyle": "white",
+ "backgroundColor": "#ffffff",
+ "custom": true,
+ "list": [{
+ "pagePath": "pages/index",
+ "iconPath": "static/images/tabbar/home.png",
+ "selectedIconPath": "static/images/tabbar/home_.png",
+ "text": "首页"
+ }, {
+ "pagePath": "pages/mine/index",
+ "iconPath": "static/images/tabbar/mine.png",
+ "selectedIconPath": "static/images/tabbar/mine_.png",
+ "text": "我的"
+ }]
+ },
+ "globalStyle": {
+ "navigationBarTextStyle": "black",
+ "navigationBarTitleText": "RuoYi",
+ "navigationBarBackgroundColor": "#FFFFFF"
+ }
+}
\ No newline at end of file
diff --git a/pages/common/agreements/privacy.vue b/pages/common/agreements/privacy.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ef638749b8f4cfa93ac75e9bfd27f6b6c76fd01b
--- /dev/null
+++ b/pages/common/agreements/privacy.vue
@@ -0,0 +1,161 @@
+
+
+
+
+
+
+
+ 用户隐私协议
+
+ 欢迎使用我们的服务!我们深知个人信息对您的重要性,并会尽全力保护您的个人信息安全可靠。我们致力于维持您对我们的信任,恪守以下原则,保护您的个人信息:权责一致原则、目的明确原则、选择同意原则、最少够用原则、确保安全原则、主体参与原则、公开透明原则等。同时,我们承诺,我们将按业界成熟的安全标准,采取相应的安全保护措施来保护您的个人信息。
+
+ 一、我们如何收集和使用您的个人信息
+ 个人信息是指以电子或者其他方式记录的能够单独或者与其他信息结合识别特定自然人身份或者反映特定自然人活动情况的各种信息。
+ 我们仅会出于本政策所述的以下目的,收集和使用您的个人信息:
+ 1. 账号注册:当您注册账号时,我们可能会收集您的手机号码,用于创建账号和登录验证。
+ 2. 服务功能:在您使用服务过程中,为向您提供更便捷的服务,我们可能会收集您的设备信息、位置信息、日志信息等。
+ 3. 安全保障:为提高您使用我们服务的安全性,保护您或其他用户的人身财产安全免遭侵害,我们会收集必要的设备信息、日志信息等。
+
+ 二、我们如何使用Cookie和同类技术
+ 为使您获得更轻松的访问体验,您使用我们的服务时,我们可能会通过小型数据文件识别您的身份,这么做可帮您省去重复输入注册信息的步骤。您有权接受或拒绝Cookie,如果浏览器自动接收Cookie,您可以根据自己的需要修改浏览器的设置以拒绝Cookie。
+
+ 三、我们如何共享、转让、公开披露您的个人信息
+ 1. 共享:我们不会与任何公司、组织和个人共享您的个人信息,但以下情况除外:
+ (1)在获取明确同意的情况下共享;
+ (2)根据法律法规规定,或按政府主管部门的强制性要求;
+ (3)与授权合作伙伴共享:仅为实现本隐私政策中声明的目的,我们的某些服务将由授权合作伙伴提供。
+ 2. 转让:我们不会将您的个人信息转让给任何公司、组织和个人,但以下情况除外:
+ (1)在获取明确同意的情况下转让;
+ (2)在涉及合并、收购或破产清算时,如涉及到个人信息转让,我们会要求新的持有您个人信息的公司、组织继续受此隐私政策的约束。
+ 3. 公开披露:我们仅会在以下情况下,公开披露您的个人信息:
+ (1)获得您明确同意后;
+ (2)基于法律的披露:在法律、法律程序、诉讼或政府主管部门强制性要求的情况下。
+
+ 四、我们如何保护您的个人信息
+ 1. 我们已使用符合业界标准的安全防护措施保护您提供的个人信息,防止数据遭到未经授权访问、公开披露、使用、修改、损坏或丢失。
+ 2. 我们会采取一切合理可行的措施,确保未收集无关的个人信息。
+ 3. 互联网并非绝对安全的环境,我们强烈建议您不要使用非我们推荐的通信方式发送个人信息。
+
+ 五、您的权利
+ 按照中国相关的法律、法规、标准,以及其他国家、地区的通行做法,我们保障您对自己的个人信息行使以下权利:
+ 1. 访问您的个人信息:您有权访问您的个人信息,法律法规规定的例外情况除外。
+ 2. 更正您的个人信息:当您发现我们处理的关于您的个人信息有错误时,您有权要求我们做出更正。
+ 3. 删除您的个人信息:在以下情形中,您可以向我们提出删除个人信息的请求。
+ 4. 改变您授权同意的范围:每个业务功能需要一些基本的个人信息才能得以完成。对于额外收集的个人信息的收集和使用,您可以随时给予或收回您的授权同意。
+
+ 六、本政策如何更新
+ 我们的隐私政策可能变更。未经您明确同意,我们不会削减您按照本隐私政策所应享有的权利。我们会在本页面上发布对本政策所做的任何变更。
+ 对于重大变更,我们还会提供更为显著的通知(包括我们会通过公示的方式进行通知甚至向您提供弹窗提示)。
+
+ 七、如何联系我们
+ 如果您对本隐私政策有任何疑问、意见或建议,可以通过以下方式与我们联系:
+ 邮箱:mym1227@sina.com
+
+
+
+
+
+
+
diff --git a/pages/common/agreements/services.vue b/pages/common/agreements/services.vue
new file mode 100644
index 0000000000000000000000000000000000000000..5f4700881204ccd2be364a5f2c5126bb263de3db
--- /dev/null
+++ b/pages/common/agreements/services.vue
@@ -0,0 +1,167 @@
+
+
+
+
+
+
+
+
+ 服务条款确认与接纳
+ 欢迎使用我们的服务!
+ 怀慈医录 拥有产品及相关软件的所有权和运作权,享有对其产品上的一切活动的监督、提示、检查、纠正及处罚等权利。用户通过注册登录程序阅读本服务条款,首次点击"登录"按钮完成注册,即表示用户与 怀慈医录 已达成协议,自愿接受本服务条款的所有内容。如果用户不同意服务条款的条件,则不能获得使用 怀慈医录 服务以及注册成为 怀慈医录 用户的权利。
+
+
+
+
+ 服务内容
+ 我们为您提供包括但不限于信息展示、交易处理、客户服务等功能。服务的具体内容由我们根据实际情况提供,我们保留变更、中断或终止部分服务的权利。
+
+
+
+ 用户注册
+ 您确认,在您完成注册程序时,您应当是具备完全民事权利能力和完全民事行为能力的自然人、法人或其他组织。
+
+ 重要提示:您有义务提供真实、准确、完整和反映当前情况的资料。如资料有任何变动,必须及时更新。
+
+
+
+
+ 使用规则
+ 您同意遵守中华人民共和国相关法律法规,不得利用本服务从事违法违规活动,包括但不限于:
+ 1. 发布、传播违法和不良信息;
+ 2. 侵犯他人知识产权或其他合法权益;
+ 3. 用户须对在 怀慈医录 的注册信息的真实性、合法性、有效性承担全部责任,用户不得冒充他人;不得利用他人的名义发布任何信息;不得恶意使用注册帐户导致其他用户误认;否则 RuoYi 有权立即停止提供服务,收回其账号并由用户独自承担由此而产生的一切法律责任;
+ 4. 进行任何可能对互联网正常运转造成不利影响的行为;
+ 5. 用户不得使用 怀慈医录 服务发送或传播敏感信息和违反国家法律制度的信息,包括但不限于下列信息:
+ 反对宪法所确定的基本原则的;
+ 危害国家安全,泄露国家秘密,颠覆国家政权,破坏国家统一的;
+ 损害国家荣誉和利益的;
+ 煽动民族仇恨、民族歧视,破坏民族团结的;
+ 破坏国家宗教政策,宣扬邪教和封建迷信的;
+ 散布谣言,扰乱社会秩序,破坏社会稳定的;
+ 散布淫秽、色情、赌博、暴力、凶杀、恐怖或者教唆犯罪的;
+ 侮辱或者诽谤他人,侵害他人合法权益的;
+ 含有法律、行政法规禁止的其他内容的。
+
+
+
+ 隐私保护
+ 怀慈医录 非常重视对用户隐私权的保护,承诺不会在未获得用户许可的情况下擅自将用户的个人资料信息出租或出售给任何第三方,但以下情况除外:
+ 1. 您同意让第三方共享资料;
+ 2. 您同意公开你的个人资料,享受为您提供的产品和服务;
+ 3. 本站需要听从法庭传票、法律命令或遵循法律程序;
+ 4. 本站发现您违反了本站服务条款或本站其它使用规定。
+
+
+
+ 免责声明
+ 您理解并同意,因网络环境特殊性,我们不担保服务不会中断,对服务的及时性、安全性、准确性也都不作担保。
+
+ 重要提示:因不可抗力、计算机病毒或黑客攻击等造成的服务中断或不能满足用户要求而造成的损失,我们不承担任何责任。
+
+
+
+
+ 协议修改
+ 我们有权随时修改本协议的任何条款。一旦本协议的内容发生变动,我们将通过适当方式向用户提示修改内容。
+
+
+
+ 法律管辖
+ 本协议的订立、执行和解释及争议的解决均应适用中华人民共和国法律。
+
+
+
+ 其他规定
+ 如本协议中的任何条款因任何原因被认定为无效或不可执行,则该条款应被撤销,但其余条款仍具有法律效力。
+
+
+
+
+
+
+
+
diff --git a/pages/home.vue b/pages/home.vue
new file mode 100644
index 0000000000000000000000000000000000000000..bbb5ea77927060e44069126814eaa8e02cddb024
--- /dev/null
+++ b/pages/home.vue
@@ -0,0 +1,307 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 怀慈医录
+ 随时随地记录病案,让记录中医病案更简单
+
+
+
+
+
+
+
+ 病案管理
+ 快速记录望闻问切等诊治信息,数据永久保存,随时可查
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/index.vue b/pages/index.vue
index 791e8489dbfd69f56e7f29b2fd1b2b8c34110366..b95f4694176db0387235a4154c21710e59b6c623 100644
--- a/pages/index.vue
+++ b/pages/index.vue
@@ -1,36 +1,190 @@
-
-
-
-
- Hello RuoYi
-
-
-
-
-
+
+
+
+
+ {{ item }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.text }}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pages/login.vue b/pages/login.vue
index 7ca13c33361665d1071e6fb64f9891952c3ccc9f..da26889a97b5aa2b32db40c870803cb7c5f0c7e2 100644
--- a/pages/login.vue
+++ b/pages/login.vue
@@ -1,9 +1,10 @@
-
-
+
+
- 若依移动端登录
+ 怀慈医录
+ 随时随地记录,让中医病案管理更简单
@@ -13,111 +14,171 @@
-
-
-
-
-
-
-
-
-
-
-
-
- 没有账号?
- 立即注册
-
- 登录即代表同意
- 《用户协议》
- 《隐私协议》
-
-
+
+
+
+
+
+
+
-
diff --git a/pages/mine/about/index.vue b/pages/mine/about/index.vue
index baffb0dc3c122b2aa492ad34af403b16eded3d72..9e5521bef11eb6ecc042b2112649b466e2ed466c 100644
--- a/pages/mine/about/index.vue
+++ b/pages/mine/about/index.vue
@@ -1,4 +1,4 @@
-
+>
diff --git a/pages/mine/avatar/index.vue b/pages/mine/avatar/index.vue
index 75848b0f74d3aa084f05fce6ce7d1ac084b1ba51..7e32c8b6c185fdaef6cb0eefa87c7763564553f9 100644
--- a/pages/mine/avatar/index.vue
+++ b/pages/mine/avatar/index.vue
@@ -1,4 +1,4 @@
-
+
diff --git a/pages/mine/help/index.vue b/pages/mine/help/index.vue
index 52f6f3ff43d48134aaf73fc2fe3321c4c7eeeee2..790dd122384d2dccd30521bf00448687a778c8ad 100644
--- a/pages/mine/help/index.vue
+++ b/pages/mine/help/index.vue
@@ -1,4 +1,4 @@
-
+>
diff --git a/pages/mine/index.vue b/pages/mine/index.vue
index 0ba4a598b277a02fa05a2d59815642f754c5f3c4..84badc8acf4f3a2827337b26ee15ced73c24a21f 100644
--- a/pages/mine/index.vue
+++ b/pages/mine/index.vue
@@ -1,189 +1,469 @@
-
-
-
-
+page {
+ background-color: #f5f6f7;
+}
+
+.mine-container {
+ width: 100%;
+ height: 100%;
+
+
+ .header-section {
+ padding: 15px 15px 45px 15px;
+ background-color: #3C6DF8;
+ color: white;
+
+ .login-tip {
+ font-size: 18px;
+ margin-left: 10px;
+ }
+
+ .cu-avatar {
+ border: 2px solid #eaeaea;
+
+ .icon {
+ font-size: 40px;
+ }
+ }
+
+ .user-info {
+ margin-left: 15px;
+
+ .u_title {
+ font-size: 16px;
+ line-height: 30px;
+ }
+
+ .u_body {
+ font-size: 14px;
+ line-height: 30px;
+ }
+ }
+ }
+
+ .content-section {
+ position: relative;
+ padding: 20px 15px;
+ background-color: #f5f6f7;
+ min-height: calc(100vh - 200px);
+
+ .menu-card {
+ background: #ffffff;
+ border-radius: 16px;
+ margin-bottom: 20px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+ overflow: hidden;
+ transition: all 0.3s ease;
+
+ &:hover {
+ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
+ transform: translateY(-2px);
+ }
+
+ .menu-item {
+ position: relative;
+ transition: all 0.2s ease;
+
+ &:active {
+ background-color: #f8f9fa;
+ }
+
+ .menu-item-content {
+ display: flex;
+ align-items: center;
+ padding: 20px;
+ min-height: 80px;
+
+ .menu-icon-wrapper {
+ width: 48px;
+ height: 48px;
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 16px;
+ transition: all 0.3s ease;
+
+ .iconfont {
+ font-size: 24px;
+ color: #ffffff;
+ }
+
+ &.feedback-icon {
+ background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
+ }
+
+ &.logout-icon {
+ background: linear-gradient(135deg, #ff6b6b 0%, #ee5a24 100%);
+ }
+ }
+
+ .menu-text {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+
+ .menu-title {
+ font-size: 16px;
+ font-weight: 500;
+ color: #333;
+ margin-bottom: 4px;
+ line-height: 1.4;
+ text-align: left;
+ }
+
+ .menu-desc {
+ font-size: 13px;
+ color: #999;
+ line-height: 1.3;
+ text-align: left;
+ }
+ }
+
+ .menu-arrow {
+ .iconfont {
+ font-size: 16px;
+ color: #ccc;
+ transition: all 0.2s ease;
+ }
+ }
+
+ .feedback-btn {
+ background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
+ color: #ffffff;
+ border: none;
+ border-radius: 20px;
+ padding: 8px 16px;
+ font-size: 13px;
+ font-weight: 500;
+ transition: all 0.3s ease;
+
+ &::after {
+ border: none;
+ }
+
+ &:active {
+ transform: scale(0.95);
+ opacity: 0.8;
+ }
+ }
+ }
+
+ /* Ripple 水波纹效果 */
+ .ripple {
+ position: relative;
+ overflow: hidden;
+ }
+
+ .ripple::after {
+ content: '';
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ width: 0;
+ height: 0;
+ background: rgba(0, 0, 0, 0.08);
+ transform: translate(-50%, -50%);
+ border-radius: 50%;
+ animation: ripple-anim 0.4s ease-out;
+ pointer-events: none;
+ }
+
+ @keyframes ripple-anim {
+ from {
+ width: 0;
+ height: 0;
+ opacity: 0.5;
+ }
+
+ to {
+ width: 800rpx;
+ height: 800rpx;
+ opacity: 0;
+ }
+ }
+
+ /* 让整行按钮可点击且无按钮默认样式 */
+ .menu-item .menu-item-content.feedback-full-btn {
+ width: 100%;
+ text-align: left;
+ background: transparent;
+ border: none;
+ border-radius: 0;
+ box-shadow: none;
+ margin: 0;
+ padding: 20px;
+ }
+
+ .menu-item .menu-item-content.feedback-full-btn::after {
+ border: none;
+ }
+ }
+
+ &.logout-card {
+ .logout-item {
+ .menu-item-content {
+ .menu-text {
+ .menu-title {
+ color: #ff6b6b;
+ font-weight: 600;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+:deep(button::after) {
+ border: none;
+}
+
+/* 自定义tabbar样式 */
+.custom-tabbar {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 100rpx;
+ background-color: #ffffff;
+ display: flex;
+ border-top: 1rpx solid #e5e5e5;
+ z-index: 999;
+ padding-bottom: constant(safe-area-inset-bottom);
+ padding-bottom: env(safe-area-inset-bottom);
+ box-sizing: content-box;
+}
+
+.tabbar-item {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ padding-top: 10rpx;
+}
+
+.tabbar-icon {
+ width: 40rpx;
+ height: 40rpx;
+ margin-bottom: 4rpx;
+}
+
+.tabbar-text {
+ font-size: 20rpx;
+ line-height: 1;
+}
+
\ No newline at end of file
diff --git a/pages/mine/info/edit.vue b/pages/mine/info/edit.vue
index 2b4240bf78043c6324205d61168de268d864e9b9..de50b22d2ddbadc63607ea0c34163f3ee77726bd 100644
--- a/pages/mine/info/edit.vue
+++ b/pages/mine/info/edit.vue
@@ -1,4 +1,4 @@
-
+>
diff --git a/pages/mine/info/index.vue b/pages/mine/info/index.vue
index cdbce114cf4452bff9aaaa190ac6e554d08d88ba..490b4f06ed6f16695b117a1e7c00d88317e32cc5 100644
--- a/pages/mine/info/index.vue
+++ b/pages/mine/info/index.vue
@@ -1,4 +1,4 @@
-
+
diff --git a/pages/mine/pwd/index.vue b/pages/mine/pwd/index.vue
index 766416f3ff207dead80e384fdc9d8c7779ad513d..c30c9b743c57201632a4aba5d0814bcee36d9663 100644
--- a/pages/mine/pwd/index.vue
+++ b/pages/mine/pwd/index.vue
@@ -1,4 +1,4 @@
-
+>
diff --git a/pages/mine/setting/index.vue b/pages/mine/setting/index.vue
index 6f3120016e8ae888082f558c4f091a81fb1ad62b..6dc598ca014a089bd7324c180e40f68d79280b59 100644
--- a/pages/mine/setting/index.vue
+++ b/pages/mine/setting/index.vue
@@ -30,31 +30,32 @@
-
diff --git a/pages/patients/add.vue b/pages/patients/add.vue
new file mode 100644
index 0000000000000000000000000000000000000000..be58118f03832f5016528ae0afe3444a5f5dc640
--- /dev/null
+++ b/pages/patients/add.vue
@@ -0,0 +1,315 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ age }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/patients/edit.vue b/pages/patients/edit.vue
new file mode 100644
index 0000000000000000000000000000000000000000..491e032fc6cc678598ba2327e74209b223734fe3
--- /dev/null
+++ b/pages/patients/edit.vue
@@ -0,0 +1,380 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ age }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ patient.updateTime }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pages/patients/index.vue b/pages/patients/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ba5240a847c3baac92294be2549e7a91d6b14a00
--- /dev/null
+++ b/pages/patients/index.vue
@@ -0,0 +1,468 @@
+
+
+
+
+
+
+
+
+
+
+ {{ patient.patientName.charAt(0) }}
+
+
+
+
+ {{ patient.patientName }}
+
+
+ |
+ {{ calculateAge(patient.birth) }}岁
+
+
+
+
+
+ 既往史:{{ patient.pastHistory||'无' }}
+
+
+
+
+
+
+ +
+
+
+
+ 编辑
+ 删除
+ 取消
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pages/patients/select.vue b/pages/patients/select.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2674dc047daf25d4ff78e77007f2d30949caa146
--- /dev/null
+++ b/pages/patients/select.vue
@@ -0,0 +1,381 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ patient.patientName.charAt(0) }}
+
+
+
+
+ {{ patient.patientName }}
+
+
+
+ |
+ {{ calculateAge(patient.birth) }}岁
+
+
+
+
+
+
+
+
+ 新增
+ 返回
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pages/register.vue b/pages/register.vue
index 52760fe45d0a8eec1bda772120c8125647cb6eed..a791c87a7393105b6b67a193cfa25eb6449d6ab6 100644
--- a/pages/register.vue
+++ b/pages/register.vue
@@ -35,80 +35,79 @@
-
diff --git a/pages/tcmcases/addCase.vue b/pages/tcmcases/addCase.vue
new file mode 100644
index 0000000000000000000000000000000000000000..34014eb790719a9a6055c96955e5468081da6672
--- /dev/null
+++ b/pages/tcmcases/addCase.vue
@@ -0,0 +1,736 @@
+
+
+
+
+
+
+
+
+
+ {{ tcmCase.patientName }}
+ {{ tcmCase.sex == 0 ? '男' : '女' }} | {{ age }}
+
+
+ 既往史:{{ tcmCase.pastHistory ||'无' }}
+
+
+
+ 请选择就诊人
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ *处方
+
+ 上传图片
+ 手工输入
+
+
+
+
+
+
+ {{ tcmCase.materials || '未录入处方' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pages/tcmcases/detail.vue b/pages/tcmcases/detail.vue
new file mode 100644
index 0000000000000000000000000000000000000000..6ede040af6033ae25a4b9515a5f78db5735208a9
--- /dev/null
+++ b/pages/tcmcases/detail.vue
@@ -0,0 +1,698 @@
+
+
+
+
+
+
+
+
+ {{ tcmCase.patientName }}
+ {{ tcmCase.sex == 0 ? '男' : '女' }} | {{ age }}
+
+
+
+
+ 既往史:{{ tcmCase.pastHistory ||'无'}}
+
+
+
+
+
+
+
+ {{ tcmCase.createTime }}
+
+
+
+
+
+
+
+ {{ tcmCase.chiefComplaint || '无' }}
+
+
+
+
+ {{ tcmCase.threeChecks }}
+ 无
+
+
+
+
+
+
+
+
+ {{ tcmCase.what || '无' }}
+
+
+
+
+ {{ tcmCase.why || '无' }}
+
+
+
+
+ {{ tcmCase.how || '无' }}
+
+
+
+
+
+
+
+ *处方
+
+
+
+
+
+
+
+ {{ tcmCase.materials || '请选择处方' }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ tcmCase.remark || '无' }}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pages/tcmcases/index.vue b/pages/tcmcases/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..9f3719676737e1e1bf5a08df8c46973165486c14
--- /dev/null
+++ b/pages/tcmcases/index.vue
@@ -0,0 +1,592 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ record.patientName.charAt(0) }}
+
+
+ {{ record.patientName }}
+
+
+
+ |
+ {{ record.age }}岁
+
+ {{ formatTimeString(record.createTime) }}
+
+
+ 诊断: {{ record.what }}
+
+
+
+
+
+ +
+
+
+
+ 编辑
+ 删除
+ 取消
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pages/tcmcases/prescript.vue b/pages/tcmcases/prescript.vue
new file mode 100644
index 0000000000000000000000000000000000000000..f951f4234c7f4efe07c35164876c7ff38b8aadcd
--- /dev/null
+++ b/pages/tcmcases/prescript.vue
@@ -0,0 +1,389 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 总共剂
+
+
+ 一剂天
+
+
+
+ 何时服
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/work/index.vue b/pages/work/index.vue
index fed7f1dcbddf81ea5621fd97f1be68ff1e0beacb..04c6161a734e57582646289fc7004e10d1ae941b 100644
--- a/pages/work/index.vue
+++ b/pages/work/index.vue
@@ -74,35 +74,24 @@
-
diff --git a/permission.js b/permission.js
index a41a5e6c5b680245fc810df8be197518f09cd615..61fc7bc8f790b051c762ec83a381f47da909a7b5 100644
--- a/permission.js
+++ b/permission.js
@@ -5,7 +5,8 @@ const loginPage = "/pages/login"
// 页面白名单
const whiteList = [
- '/pages/login', '/pages/register', '/pages/common/webview/index'
+ '/pages/home','/pages/login', '/pages/register', '/pages/common/webview/index','/pages/common/agreements/privacy',
+ '/pages/common/agreements/services'
]
// 检查地址白名单
diff --git a/plugins/auth.js b/plugins/auth.js
index 6bcce3f7b1302bab16e5e0cc949135a5b579f198..b47b9df28957ce0f906b91da8626e588db090cf5 100644
--- a/plugins/auth.js
+++ b/plugins/auth.js
@@ -1,8 +1,8 @@
-import store from '@/store'
+import { useUserStore } from '@/store'
function authPermission(permission) {
const all_permission = "*:*:*"
- const permissions = store.getters && store.getters.permissions
+ const permissions = useUserStore().permissions
if (permission && permission.length > 0) {
return permissions.some(v => {
return all_permission === v || v === permission
@@ -14,7 +14,7 @@ function authPermission(permission) {
function authRole(role) {
const super_admin = "admin"
- const roles = store.getters && store.getters.roles
+ const roles = useUserStore().roles
if (role && role.length > 0) {
return roles.some(v => {
return super_admin === v || v === role
diff --git a/plugins/index.js b/plugins/index.js
index 22bb75cc7c4dea655dd648dd8baa1c31af6be201..015b5684b9d3e8d18c58e7e7dcc44b25bbbb327b 100644
--- a/plugins/index.js
+++ b/plugins/index.js
@@ -2,13 +2,11 @@ import tab from './tab'
import auth from './auth'
import modal from './modal'
-export default {
- install(Vue) {
- // 页签操作
- Vue.prototype.$tab = tab
- // 认证对象
- Vue.prototype.$auth = auth
- // 模态框对象
- Vue.prototype.$modal = modal
- }
+export function install(app) {
+ // 页签操作
+ app.config.globalProperties.$tab = tab
+ // 认证对象
+ app.config.globalProperties.$auth = auth
+ // 模态框对象
+ app.config.globalProperties.$modal = modal
}
diff --git a/static/images/boy.png b/static/images/boy.png
new file mode 100644
index 0000000000000000000000000000000000000000..6c4de505d6ca241a63390409f5ea7f5008658268
Binary files /dev/null and b/static/images/boy.png differ
diff --git a/static/images/copy.png b/static/images/copy.png
new file mode 100644
index 0000000000000000000000000000000000000000..5b6e3894b749e97af91206321158a66258489995
Binary files /dev/null and b/static/images/copy.png differ
diff --git a/static/images/girl.png b/static/images/girl.png
new file mode 100644
index 0000000000000000000000000000000000000000..e1f6f13edfd5320a4c2052181f204fd95602f3d8
Binary files /dev/null and b/static/images/girl.png differ
diff --git a/static/images/logo.png b/static/images/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..1cad10832f52625204db6c44685b7f9e30d79405
Binary files /dev/null and b/static/images/logo.png differ
diff --git a/static/images/man.png b/static/images/man.png
new file mode 100644
index 0000000000000000000000000000000000000000..c28c906ddf2a6ac438db99f07422d482508b30af
Binary files /dev/null and b/static/images/man.png differ
diff --git a/static/images/upload-video.png b/static/images/upload-video.png
new file mode 100644
index 0000000000000000000000000000000000000000..7c7c882d85fd61447901d78678e94c38e6291d8f
Binary files /dev/null and b/static/images/upload-video.png differ
diff --git a/static/images/woman.png b/static/images/woman.png
new file mode 100644
index 0000000000000000000000000000000000000000..2de1f3ce9e04c4655458d43229ed6cdb9a605540
Binary files /dev/null and b/static/images/woman.png differ
diff --git a/static/scss/global.scss b/static/scss/global.scss
index bc1b857c4c434dabc0b5760b31ca2119ed63a6fc..a332f442fa7b467f5a29496ec754081da681a9a2 100644
--- a/static/scss/global.scss
+++ b/static/scss/global.scss
@@ -1,24 +1,24 @@
-.text-center {
- text-align: center;
-}
-
-.font-13 {
- font-size: 13px;
-}
-
-.font-12 {
- font-size: 12px;
-}
-
-.font-11 {
- font-size: 11px;
-}
-
-.text-grey1 {
- color: #888;
-}
-.text-grey2 {
- color: #aaa;
+.text-center {
+ text-align: center;
+}
+
+.font-13 {
+ font-size: 13px;
+}
+
+.font-12 {
+ font-size: 12px;
+}
+
+.font-11 {
+ font-size: 11px;
+}
+
+.text-grey1 {
+ color: #888;
+}
+.text-grey2 {
+ color: #aaa;
}
.list-cell-arrow::before {
@@ -88,3 +88,134 @@
}
}
}
+ .search-container {
+ display: flex;
+ align-items: center;
+ background: white;
+ border-radius: 50rpx;
+ padding: 10rpx 24rpx;
+ border: 1rpx solid #eee;
+ // box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
+ margin-bottom: 24rpx;
+ .search-input {
+ flex: 1;
+ font-size: 28rpx;
+ height: 60rpx;
+ padding: 0 15rpx;
+ }
+
+ .placeholder-style {
+ color: #bbb;
+ }
+
+ .search-btn {
+ background: #007AFF;
+ color: white;
+ border-radius: 16rpx;
+ font-size: 28rpx;
+ padding: 8rpx 30rpx;
+ line-height: 1.5;
+ height: 60rpx;
+ }
+ }
+ /* 添加按钮 - 医患风格 */
+ .add-button {
+ position: fixed;
+ bottom: 280rpx;
+ right: 40rpx;
+ width: 120rpx;
+ height: 120rpx;
+ background: linear-gradient(135deg, #3b82f6, #1d4ed8);
+ border-radius: 50%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ box-shadow: 0 12rpx 40rpx rgba(59, 130, 246, 0.4);
+ z-index: 100;
+ transition: all 0.3s ease;
+
+ &::before {
+ content: '';
+ position: absolute;
+ inset: -4rpx;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #3b82f6, #1d4ed8);
+ z-index: -1;
+ opacity: 0.2;
+ }
+
+ &:active {
+ transform: scale(0.95);
+ box-shadow: 0 8rpx 24rpx rgba(59, 130, 246, 0.4);
+ }
+
+ .plus {
+ color: #fff;
+ font-size: 48rpx;
+ font-weight: 300;
+ text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
+ }
+ }
+
+
+
+
+.submit-area {
+ padding: 40rpx 30rpx;
+ background-color: #fff;
+ // position: fixed;
+ // bottom: 0;
+ // left: 0;
+ // right: 0;
+ width: 100%;
+ border-top: 1rpx solid #E7EAF2;
+ box-shadow: 0 -8rpx 32rpx rgba(0, 0, 0, 0.08);
+ backdrop-filter: blur(10rpx);
+ // z-index:99;
+ .submit-btn {
+ background-color: #3C6DF8;
+ color: #fff;
+ border-radius: 8rpx;
+ height: 80rpx;
+ line-height: 80rpx;
+ font-size: 30rpx;
+ border: none;
+ }
+}
+
+.info-list{
+ height:100%;
+ overflow:hidden;
+ .mescroll-uni-warp{
+ height:100%;
+ overflow:hidden;
+ }
+}
+
+
+/* 搜索区域 - 医患风格 */
+.search-container {
+ background: #fff;
+ margin: 20rpx 32rpx;
+ padding: 16rpx 32rpx;
+ border-radius: 16rpx;
+ box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
+ display: flex;
+ align-items: center;
+ border: 1rpx solid #e2e8f0;
+
+ .uni-icons {
+ margin-right: 16rpx;
+ color: #94a3b8;
+ }
+
+ .search-input {
+ flex: 1;
+ font-size: 30rpx;
+ color: #1e293b;
+ font-weight: 500;
+ background: transparent;
+ border: none;
+ outline: none;
+ }
+}
diff --git a/static/scss/index.scss b/static/scss/index.scss
index 1db0787a8390aa2da209d9160b20054d53d33b7e..480b269f21ac95ee1362416978616aa175596f97 100644
--- a/static/scss/index.scss
+++ b/static/scss/index.scss
@@ -1,6 +1,8 @@
// global
-@import "./global.scss";
-// color-ui
+@import "./global.scss";
+// ly
+@import "./ly.scss";
+// color-ui
@import "@/static/scss/colorui.css";
// iconfont
@import "@/static/font/iconfont.css";
\ No newline at end of file
diff --git a/static/scss/ly.scss b/static/scss/ly.scss
new file mode 100644
index 0000000000000000000000000000000000000000..d16c40ff4767d076ac15d03fd9bf0a2a80f95561
--- /dev/null
+++ b/static/scss/ly.scss
@@ -0,0 +1,100 @@
+.ly-flex {
+ display: flex;
+ }
+
+ .ly-justify-around {
+ justify-content: space-around;
+ }
+
+ .ly-justify-center {
+ justify-content: center;
+ }
+
+ .ly-justify-between {
+ justify-content: space-between;
+ }
+
+ .ly-justify-start {
+ justify-content: flex-start;
+ }
+
+ .ly-justify-end {
+ justify-content: flex-end;
+ }
+
+ .ly-items-start {
+ align-items: flex-start;
+ }
+
+ .ly-items-center {
+ align-items: center;
+ }
+
+ .ly-items-end {
+ align-items: flex-end;
+ }
+
+ .ly-flex-col {
+ flex-direction: column;
+ }
+
+ .ly-flex-col-reverse {
+ flex-direction: column-reverse;
+ }
+
+ .ly-flex-row {
+ flex-direction: row;
+ }
+
+ .ly-flex-wrap {
+ flex-wrap: wrap;
+ }
+
+ .ly-flex-wrap-reverse {
+ flex-wrap: wrap-reverse;
+ }
+
+ .ly-flex-no-wrap {
+ flex-wrap: nowrap;
+ }
+
+ .ly-flex-1 {
+ flex: 1;
+ }
+
+ .ly-text-center {
+ text-align: center;
+ }
+
+ .ly-w-full{
+ width: 100%;
+ }
+
+ .ly-h-full{
+ height: 100%;
+ }
+
+ .ly-overflow-hidden{
+ overflow: hidden;
+ }
+
+ .ly-overflow-auto{
+ overflow: auto;
+ }
+
+ .ly-overflow-y-auto{
+ overflow-y: auto;
+ }
+ .ly-overflow-x-auto{
+ overflow-x: auto;
+ }
+
+ .ly-overflow-y-hidden{
+ overflow-y: hidden;
+ }
+ .ly-overflow-x-hidden{
+ overflow-x: hidden;
+ }
+ .ly-overflow-y-scroll{
+ overflow-y: scroll;
+ }
\ No newline at end of file
diff --git a/store/getters.js b/store/getters.js
deleted file mode 100644
index f0eece37749340429f22c1682ae95bb7c9cac190..0000000000000000000000000000000000000000
--- a/store/getters.js
+++ /dev/null
@@ -1,8 +0,0 @@
-const getters = {
- token: state => state.user.token,
- avatar: state => state.user.avatar,
- name: state => state.user.name,
- roles: state => state.user.roles,
- permissions: state => state.user.permissions
-}
-export default getters
diff --git a/store/index.js b/store/index.js
index cb58786115e7132935bdb7962748d5efacb1ab1b..3f0bcc91cf3326b5364c7cde0019e163f91227a5 100644
--- a/store/index.js
+++ b/store/index.js
@@ -1,15 +1,9 @@
-import Vue from 'vue'
-import Vuex from 'vuex'
-import user from '@/store/modules/user'
-import getters from './getters'
-
-Vue.use(Vuex)
-
-const store = new Vuex.Store({
- modules: {
- user
- },
- getters
-})
-
-export default store
+import { createPinia } from 'pinia'
+import { useUserStore } from './modules/user'
+import { useConfigStore } from './modules/config'
+
+const pinia = createPinia()
+
+export default pinia
+
+export { useUserStore, useConfigStore }
diff --git a/store/modules/config.js b/store/modules/config.js
new file mode 100644
index 0000000000000000000000000000000000000000..cb3a3610d1ac0398fe7582effe6b4b1918e608ae
--- /dev/null
+++ b/store/modules/config.js
@@ -0,0 +1,13 @@
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+
+export const useConfigStore = defineStore('config', () => {
+ const config = ref()
+ const setConfig = (val) => {
+ config.value = val
+ }
+ return {
+ config,
+ setConfig
+ }
+})
\ No newline at end of file
diff --git a/store/modules/dict.js b/store/modules/dict.js
new file mode 100644
index 0000000000000000000000000000000000000000..8650b0aa57041c533fef384679d454b9e08c8ac5
--- /dev/null
+++ b/store/modules/dict.js
@@ -0,0 +1,56 @@
+import { defineStore } from "pinia"
+
+const useDictStore = defineStore("dict", {
+ state: () => ({
+ dict: new Array(),
+ }),
+ actions: {
+ // 获取字典
+ getDict(_key) {
+ if (_key == null && _key == "") {
+ return null;
+ }
+ try {
+ for (let i = 0; i < this.dict.length; i++) {
+ if (this.dict[i].key == _key) {
+ return this.dict[i].value;
+ }
+ }
+ } catch (e) {
+ return null;
+ }
+ },
+ // 设置字典
+ setDict(_key, value) {
+ if (_key !== null && _key !== "") {
+ this.dict.push({
+ key: _key,
+ value: value,
+ });
+ }
+ },
+ // 删除字典
+ removeDict(_key) {
+ var bln = false;
+ try {
+ for (let i = 0; i < this.dict.length; i++) {
+ if (this.dict[i].key == _key) {
+ this.dict.splice(i, 1);
+ return true;
+ }
+ }
+ } catch (e) {
+ bln = false;
+ }
+ return bln;
+ },
+ // 清空字典
+ cleanDict() {
+ this.dict = new Array();
+ },
+ // 初始字典
+ initDict() {},
+ }
+})
+
+export default useDictStore
diff --git a/store/modules/user.js b/store/modules/user.js
index 36e6ee3f1468b8ead9e6ddad0dea91e360d97d4b..2f744461a1f82b7b3d32a68d7239ee7e958b5693 100644
--- a/store/modules/user.js
+++ b/store/modules/user.js
@@ -1,103 +1,152 @@
+import {
+ defineStore
+} from 'pinia'
+import {
+ ref
+} from 'vue'
import config from '@/config'
import storage from '@/utils/storage'
import constant from '@/utils/constant'
-import { isHttp, isEmpty } from "@/utils/validate"
-import { login, logout, getInfo } from '@/api/login'
-import { getToken, setToken, removeToken } from '@/utils/auth'
+import {
+ isHttp,
+ isEmpty
+} from "@/utils/validate"
+import {
+ getInfo,
+ login,
+ logout,
+ wxLoginAPI
+} from '@/api/login'
+import {
+ getToken,
+ removeToken,
+ setToken
+} from '@/utils/auth'
import defAva from '@/static/images/profile.jpg'
const baseUrl = config.baseUrl
-const user = {
- state: {
- token: getToken(),
- name: storage.get(constant.name),
- avatar: storage.get(constant.avatar),
- roles: storage.get(constant.roles),
- permissions: storage.get(constant.permissions)
- },
+export const useUserStore = defineStore('user', () => {
+ const token = ref(getToken())
+ const id = ref(storage.get(constant.id))
+ const username = ref(storage.get(constant.username))
+ const nickname = ref(storage.get(constant.nickname))
+ const avatar = ref(storage.get(constant.avatar))
+ const roles = ref(storage.get(constant.roles))
+ const permissions = ref(storage.get(constant.permissions))
- mutations: {
- SET_TOKEN: (state, token) => {
- state.token = token
- },
- SET_NAME: (state, name) => {
- state.name = name
- storage.set(constant.name, name)
- },
- SET_AVATAR: (state, avatar) => {
- state.avatar = avatar
- storage.set(constant.avatar, avatar)
- },
- SET_ROLES: (state, roles) => {
- state.roles = roles
- storage.set(constant.roles, roles)
- },
- SET_PERMISSIONS: (state, permissions) => {
- state.permissions = permissions
- storage.set(constant.permissions, permissions)
- }
- },
+ const SET_TOKEN = (val) => {
+ token.value = val
+ }
+ const SET_ID = (val) => {
+ id.value = val
+ storage.set(constant.id, val)
+ }
+ const SET_NICKNAME = (val) => {
+ nickname.value = val
+ storage.set(constant.nickname, val)
+ }
- actions: {
- // 登录
- Login({ commit }, userInfo) {
- const username = userInfo.username.trim()
- const password = userInfo.password
- const code = userInfo.code
- const uuid = userInfo.uuid
- return new Promise((resolve, reject) => {
- login(username, password, code, uuid).then(res => {
- setToken(res.token)
- commit('SET_TOKEN', res.token)
- resolve()
- }).catch(error => {
- reject(error)
- })
- })
- },
+ const SET_USERNAME = (val) => {
+ username.value = val
+ storage.set(constant.username, val)
+ }
- // 获取用户信息
- GetInfo({ commit, state }) {
- return new Promise((resolve, reject) => {
- getInfo().then(res => {
- const user = res.user
- let avatar = user.avatar || ""
- if (!isHttp(avatar)) {
- avatar = (isEmpty(avatar)) ? defAva : baseUrl + avatar
- }
- const username = (isEmpty(user) || isEmpty(user.userName)) ? "" : user.userName
- if (res.roles && res.roles.length > 0) {
- commit('SET_ROLES', res.roles)
- commit('SET_PERMISSIONS', res.permissions)
- } else {
- commit('SET_ROLES', ['ROLE_DEFAULT'])
- }
- commit('SET_NAME', username)
- commit('SET_AVATAR', avatar)
- resolve(res)
- }).catch(error => {
- reject(error)
- })
- })
- },
+ const SET_AVATAR = (val) => {
+ avatar.value = val
+ storage.set(constant.avatar, val)
+ }
+ const SET_ROLES = (val) => {
+ roles.value = val
+ storage.set(constant.roles, val)
+ }
+ const SET_PERMISSIONS = (val) => {
+ permissions.value = val
+ storage.set(constant.permissions, val)
+ }
- // 退出系统
- LogOut({ commit, state }) {
- return new Promise((resolve, reject) => {
- logout(state.token).then(() => {
- commit('SET_TOKEN', '')
- commit('SET_ROLES', [])
- commit('SET_PERMISSIONS', [])
- removeToken()
- storage.clean()
- resolve()
- }).catch(error => {
- reject(error)
- })
- })
- }
- }
-}
+ // 登录
+ const loginAction = (userInfo) => {
+ const username = userInfo.username.trim()
+ const password = userInfo.password
+ return new Promise((resolve, reject) => {
+ login(username, password).then(res => {
+ setToken(res.data)
+ SET_TOKEN(res.data)
+ resolve()
+ }).catch(error => {
+ reject(error)
+ })
+ })
+ }
+ // 示例代码,需要根据你的实际API结构调整
+ const wxLogin=async (userInfo)=>{
+ const response = await wxLoginAPI(userInfo) // 你的微信登录API
+ const {
+ data
+ } = response
+ console.log(data)
+ if (data) {
+ setToken(data)
+ SET_TOKEN(data)
+ }
+ return data
+ }
+ // 获取用户信息
+ const getInfoAction = () => {
+ return new Promise((resolve, reject) => {
+ getInfo().then(res => {
+ const user = res.data
+ let avatar = user.avatar || ""
+ if (!isHttp(avatar)) {
+ avatar = (isEmpty(avatar)) ? defAva : baseUrl + avatar
+ }
+ const userid = (isEmpty(user) || isEmpty(user.userId)) ? "" : user.userId
+ const username = (isEmpty(user) || isEmpty(user.userName)) ? "" : user
+ .userName
+ const nickname = (isEmpty(user) || isEmpty(user.nickName)) ? "" : user
+ .nickName
+ if (res.roles && res.roles.length > 0) {
+ SET_ROLES(res.roles)
+ SET_PERMISSIONS(res.permissions)
+ } else {
+ SET_ROLES(['ROLE_DEFAULT'])
+ }
+ SET_ID(userid)
+ SET_USERNAME(username)
+ SET_NICKNAME(nickname)
+ SET_AVATAR(avatar)
+ resolve(res)
+ }).catch(error => {
+ reject(error)
+ })
+ })
+ }
-export default user
+ // 退出系统
+ const logOutAction = () => {
+ return new Promise((resolve, reject) => {
+ SET_TOKEN('')
+ SET_ROLES([])
+ SET_PERMISSIONS([])
+ removeToken()
+ storage.clean()
+ resolve()
+ })
+ }
+
+ return {
+ token,
+ id,
+ nickname,
+ username,
+ avatar,
+ roles,
+ permissions,
+ SET_AVATAR,
+ login: loginAction,
+ getInfo: getInfoAction,
+ logOut: logOutAction,
+ wxLogin,
+ }
+})
\ No newline at end of file
diff --git a/uni.scss b/uni.scss
index 5b30ca3170b5da87486bb04375c1844ab40ca2de..d4d2090e583233d098db2911b695b24905d7535e 100644
--- a/uni.scss
+++ b/uni.scss
@@ -1,9 +1,11 @@
+@import '@/uni_modules/uni-scss/variables.scss';
+
/**
* uni-app内置的常用样式变量
*/
-
+ $uni-primary: #3C6DF8;
/* 行为相关颜色 */
-$uni-color-primary: #007aff;
+$uni-color-primary: #3C6DF8;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
diff --git a/uni_modules/mescroll-uni/changelog.md b/uni_modules/mescroll-uni/changelog.md
new file mode 100644
index 0000000000000000000000000000000000000000..b2981f3aaa9b05d7df657e64cab58813a0a28aa6
--- /dev/null
+++ b/uni_modules/mescroll-uni/changelog.md
@@ -0,0 +1,8 @@
+## 1.3.8(2023-03-26)
+1. 新增useMescroll的hook, 支持vue3 script setup的写法
+2. 新增vue3 script setup的示例 ( 根据vue2的示例,全部重写了一遍 )
+3. mescroll-body 和 mescroll-uni 无需再写 ref="mescrollRef"
+4. 解决mescroll-uni在页面渲染之后,无法动态设置height的问题
+5. 解决renderjs在h5返回有时候无法正常滑动的问题
+6. 修复小程序编辑器提示 Cannot read property 'nv_optDown' of undefined 的错误
+-by 小瑾同学
diff --git a/uni_modules/mescroll-uni/components/mescroll-body/mescroll-body.css b/uni_modules/mescroll-uni/components/mescroll-body/mescroll-body.css
new file mode 100644
index 0000000000000000000000000000000000000000..1107710cdc5f49a5e557d2778bd0277667ca919a
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-body/mescroll-body.css
@@ -0,0 +1,19 @@
+.mescroll-body {
+ position: relative; /* 下拉刷新区域相对自身定位 */
+ height: auto; /* 不可固定高度,否则overflow:hidden导致无法滑动; 同时使设置的最小高生效,实现列表不满屏仍可下拉*/
+ overflow: hidden; /* 当有元素写在mescroll-body标签前面时,可遮住下拉刷新区域 */
+ box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */
+}
+
+/* 使sticky生效: 父元素不能overflow:hidden或者overflow:auto属性 */
+.mescroll-body.mescorll-sticky{
+ overflow: unset !important
+}
+
+/* 适配 iPhoneX */
+@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
+ .mescroll-safearea {
+ padding-bottom: constant(safe-area-inset-bottom);
+ padding-bottom: env(safe-area-inset-bottom);
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/mescroll-uni/components/mescroll-body/mescroll-body.vue b/uni_modules/mescroll-uni/components/mescroll-body/mescroll-body.vue
new file mode 100644
index 0000000000000000000000000000000000000000..76d77282310cb4f32bd0f638710535c5cc242001
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-body/mescroll-body.vue
@@ -0,0 +1,400 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{downText}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ mescroll.optUp.textLoading }}
+
+
+ {{ mescroll.optUp.textNoMore }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/beibei/components/mescroll-down.css b/uni_modules/mescroll-uni/components/mescroll-diy/beibei/components/mescroll-down.css
new file mode 100644
index 0000000000000000000000000000000000000000..dcefe2dafe61e6e9e382eb85ef84abf51324e250
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/beibei/components/mescroll-down.css
@@ -0,0 +1,47 @@
+/*下拉刷新--标语*/
+.mescroll-downwarp .downwarp-slogan{
+ display: block;
+ width: 420rpx;
+ height: 168rpx;
+ margin: auto;
+}
+/*下拉刷新--向下进度动画*/
+.mescroll-downwarp .downwarp-progress{
+ display: inline-block;
+ width: 40rpx;
+ height: 40rpx;
+ border: none;
+ margin: auto;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-image: url(https://www.mescroll.com/img/beibei/mescroll-progress.png);
+ transition: all 300ms;
+}
+/*下拉刷新--进度条*/
+.mescroll-downwarp .downwarp-loading{
+ display: inline-block;
+ width: 32rpx;
+ height: 32rpx;
+ border-radius: 50%;
+ border: 2rpx solid #FF8095;
+ border-bottom-color: transparent;
+}
+/*下拉刷新--吉祥物*/
+.mescroll-downwarp .downwarp-mascot{
+ position: absolute;
+ right: 16rpx;
+ bottom: 0;
+ width: 100rpx;
+ height: 100rpx;
+ background-size: contain;
+ background-repeat: no-repeat;
+ animation: animMascot .6s steps(1,end) infinite;
+}
+@keyframes animMascot {
+ 0% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb1.png)}
+ 25% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb2.png)}
+ 50% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb3.png)}
+ 75% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb4.png)}
+ 100% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb1.png)}
+}
\ No newline at end of file
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/beibei/components/mescroll-down.vue b/uni_modules/mescroll-uni/components/mescroll-diy/beibei/components/mescroll-down.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ee1d32181748a75771a3d43a010f3d87ee3dcad0
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/beibei/components/mescroll-down.vue
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-body.vue b/uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-body.vue
new file mode 100644
index 0000000000000000000000000000000000000000..9f572a9e7536724c70c7e7fe313fac2118991fcf
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-body.vue
@@ -0,0 +1,360 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ mescroll.optUp.textLoading }}
+
+
+ {{ mescroll.optUp.textNoMore }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-uni-option.js b/uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-uni-option.js
new file mode 100644
index 0000000000000000000000000000000000000000..fbac778830e0295d5595528c1a37faa684d0383a
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-uni-option.js
@@ -0,0 +1,49 @@
+// mescroll-uni和mescroll-body 的全局配置
+const GlobalOption = {
+ down: {
+ // 其他down的配置参数也可以写,这里只展示了常用的配置:
+ offset: uni.upx2px(140), // 在列表顶部,下拉大于140upx,松手即可触发下拉刷新的回调
+ native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
+ },
+ up: {
+ // 其他up的配置参数也可以写,这里只展示了常用的配置:
+ offset: 150, // 距底部多远时,触发upCallback
+ toTop: {
+ // 回到顶部按钮,需配置src才显示
+ src: "https://www.mescroll.com/img/mescroll-totop.png", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
+ offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px
+ right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+ bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+ width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+ },
+ empty: {
+ use: true, // 是否显示空布局
+ icon: "https://www.mescroll.com/img/mescroll-empty.png" // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
+ }
+ },
+ // 国际化配置
+ i18n: {
+ // 中文
+ zh: {
+ up: {
+ textLoading: '加载中 ...', // 加载中的提示文本
+ textNoMore: '-- END --', // 没有更多数据的提示文本
+ empty: {
+ tip: '~ 暂无相关数据 ~' // 空提示
+ }
+ }
+ },
+ // 英文
+ en: {
+ up: {
+ textLoading: 'loading ...',
+ textNoMore: '-- END --',
+ empty: {
+ tip: '~ absolutely empty ~'
+ }
+ }
+ }
+ }
+}
+
+export default GlobalOption
\ No newline at end of file
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-uni.vue b/uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-uni.vue
new file mode 100644
index 0000000000000000000000000000000000000000..15a2289178bcefe8608af446bcfaf32ec043be84
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-uni.vue
@@ -0,0 +1,434 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ mescroll.optUp.textLoading }}
+
+
+ {{ mescroll.optUp.textNoMore }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/components/mescroll-down.css b/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/components/mescroll-down.css
new file mode 100644
index 0000000000000000000000000000000000000000..dc31f882e2a1a0e068c8f2e054dd845266f8a4da
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/components/mescroll-down.css
@@ -0,0 +1,83 @@
+/*下拉刷新--内容区,定位于区域底部*/
+.mescroll-downwarp .downwarp-content{
+ height: 150rpx;
+ padding-bottom: 0;
+}
+/*下拉刷新--标语*/
+.mescroll-downwarp .downwarp-slogan{
+ padding: 32rpx 0 0 36rpx;
+ font-size: 26rpx;
+ color: gray;
+}
+/*下拉刷新--进度提示*/
+.mescroll-downwarp .downwarp-text{
+ margin-top: 4rpx;
+ font-size: 22rpx;
+ color: darkgray;
+}
+/*下拉刷新--向下进度动画*/
+.mescroll-downwarp .downwarp-progress{
+ position: absolute;
+ top: 0;
+ left: 50%;
+ width: auto;
+ height: auto;
+ border: none;
+ border-radius: 0;
+ margin-right: 0;
+ margin-left: -180rpx;
+ display: flex;
+ align-items: middle;
+}
+/*下拉刷新--人*/
+.mescroll-downwarp .downwarp-man{
+ width: 105rpx;
+ height: 146rpx;
+ transform-origin: left 60%;
+}
+/*下拉刷新--盒子*/
+.mescroll-downwarp .downwarp-box{
+ width: 36rpx;
+ height: 30rpx;
+ margin-top: 60rpx;
+ margin-left: -40rpx;
+ transform-origin: right -100%;
+}
+/*下拉刷新--进度条*/
+.mescroll-downwarp .downwarp-loading{
+ position: absolute;
+ top: 0;
+ left: 50%;
+ margin-left: -176rpx;
+ width: 100rpx;
+ height: 140rpx;
+ background-size: contain;
+ background-repeat: no-repeat;
+ animation: animManRun .3s steps(1,start) infinite;
+}
+
+@keyframes animManRun {
+ 0% {background-image: url(https://www.mescroll.com/img/jingdong/mescroll-progress2.png)}
+ 40% {background-image: url(https://www.mescroll.com/img/jingdong/mescroll-progress3.png)}
+ 70% {background-image: url(https://www.mescroll.com/img/jingdong/mescroll-progress4.png)}
+ 100% {background-image: url(https://www.mescroll.com/img/jingdong/mescroll-progress2.png)}
+}
+
+.mescroll-downwarp .downwarp-loading:before{
+ position: absolute;
+ top: 30rpx;
+ left: 50%;
+ margin-left: -120rpx;
+ content: '';
+ width: 80rpx;
+ height: 140rpx;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-image: url(https://www.mescroll.com/img/jingdong/mescroll-progress5.png);
+}
+
+/*下拉刷新--资源预加载,避免下拉的时候闪白屏*/
+.mescroll-downwarp .downwarp-load-preload {
+ animation: animManRun 1s steps(1, end) 1s;
+ pointer-events: none;
+}
\ No newline at end of file
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/components/mescroll-down.vue b/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/components/mescroll-down.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2855f104bd96489ba4f392fc22676946f994154b
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/components/mescroll-down.vue
@@ -0,0 +1,64 @@
+
+
+
+
+ 让购物更便捷
+ {{downText}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/components/mescroll-up.css b/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/components/mescroll-up.css
new file mode 100644
index 0000000000000000000000000000000000000000..78565d1cfe268202dff8267409858915f4c6dfb1
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/components/mescroll-up.css
@@ -0,0 +1,16 @@
+/*上拉加载--进度条*/
+.mescroll-upwarp .upwarp-progress{
+ display: inline-block;
+ width: 44rpx;
+ height: 44rpx;
+ border: none;
+ background-image: url(https://www.mescroll.com/img/jingdong/mescroll-loading2.png);
+ background-size: contain;
+}
+.mescroll-upwarp .upwarp-loading{
+ display: inline-block;
+ width: 44rpx;
+ height: 44rpx;
+ background-image: url(https://www.mescroll.com/img/jingdong/mescroll-loading1.png);
+ background-size: contain;
+}
\ No newline at end of file
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/components/mescroll-up.vue b/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/components/mescroll-up.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2777969f7291bfc4f5b6ccc9743937cec6905d22
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/components/mescroll-up.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+ {{ mOption.textLoading }}
+
+
+ {{ mOption.textNoMore }}
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/mescroll-body.vue b/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/mescroll-body.vue
new file mode 100644
index 0000000000000000000000000000000000000000..01cb7190412f0ced13befad3493874ba2edb0369
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/mescroll-body.vue
@@ -0,0 +1,389 @@
+
+
+
+
+
+
+
+
+
+
+
+ 让购物更便捷
+ {{downText}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ mescroll.optUp.textLoading }}
+
+
+ {{ mescroll.optUp.textNoMore }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/mescroll-uni-option.js b/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/mescroll-uni-option.js
new file mode 100644
index 0000000000000000000000000000000000000000..0349f989fd7cdd638d8f4c81105cbf139dd63053
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/mescroll-uni-option.js
@@ -0,0 +1,63 @@
+// mescroll-uni和mescroll-body 的全局配置
+const GlobalOption = {
+ down: {
+ // 其他down的配置参数也可以写,这里只展示了常用的配置:
+ offset: 100, // 在列表顶部,下拉大于100px,松手即可触发下拉刷新的回调
+ native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
+ },
+ up: {
+ // 其他up的配置参数也可以写,这里只展示了常用的配置:
+ offset: 150, // 距底部多远时,触发upCallback
+ toTop: {
+ // 回到顶部按钮,需配置src才显示
+ src: "https://www.mescroll.com/img/mescroll-totop.png", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
+ offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px
+ right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+ bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+ width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+ },
+ empty: {
+ use: true, // 是否显示空布局
+ icon: "https://www.mescroll.com/img/mescroll-empty.png" // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
+ }
+ },
+ // 国际化配置
+ i18n: {
+ // 中文
+ zh: {
+ down: {
+ textInOffset: '下拉更新...', // 下拉的距离在offset范围内的提示文本
+ textOutOffset: '松手更新...', // 下拉的距离大于offset范围的提示文本
+ textLoading: '更新中...', // 加载中的提示文本
+ textSuccess: '加载成功', // 加载成功的文本
+ textErr: '加载失败', // 加载失败的文本
+ },
+ up: {
+ textLoading: '加载中 ...', // 加载中的提示文本
+ textNoMore: '没有了哦~', // 没有更多数据的提示文本
+ empty: {
+ tip: '~ 暂无相关数据 ~' // 空提示
+ }
+ }
+ },
+ // 英文
+ en: {
+ down: {
+ textInOffset: 'drop down refresh',
+ textOutOffset: 'release updates',
+ textLoading: 'loading ...',
+ textSuccess: 'loaded successfully',
+ textErr: 'loading failed'
+ },
+ up: {
+ textLoading: 'loading ...',
+ textNoMore: '-- END --',
+ empty: {
+ tip: '~ absolutely empty ~'
+ }
+ }
+ }
+ }
+}
+
+export default GlobalOption
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/mescroll-uni.vue b/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/mescroll-uni.vue
new file mode 100644
index 0000000000000000000000000000000000000000..e09adf61ea71c688778d1d794f8afa39624b736e
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/mescroll-uni.vue
@@ -0,0 +1,467 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 让购物更便捷
+ {{downText}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ mescroll.optUp.textLoading }}
+
+
+ {{ mescroll.optUp.textNoMore }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/wxs/wxs.wxs b/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/wxs/wxs.wxs
new file mode 100644
index 0000000000000000000000000000000000000000..93fea59619add32cb04cf23acb2017f766d35129
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/jingdong/wxs/wxs.wxs
@@ -0,0 +1,273 @@
+// 使用wxs处理交互动画, 提高性能, 同时避免小程序bounce对下拉刷新的影响
+// https://uniapp.dcloud.io/frame?id=wxs
+// https://developers.weixin.qq.com/miniprogram/dev/framework/view/interactive-animation.html
+
+// 模拟mescroll实例, 与mescroll.js的写法尽量保持一致
+var me = {}
+
+// ------ 自定义下拉刷新动画 start ------
+
+/* 下拉过程中的回调,滑动过程一直在执行 (rate<1为inOffset; rate>1为outOffset) */
+me.onMoving = function (ins, rate, downHight){
+ ins.requestAnimationFrame(function () {
+ ins.selectComponent('.mescroll-wxs-content').setStyle({
+ 'will-change': 'transform', // 可解决下拉过程中, image和swiper脱离文档流的问题
+ 'transform': 'translateY(' + downHight + 'px)',
+ 'transition': ''
+ })
+ // 缩放动画
+ var downRotate = rate<1 ? 'scale('+rate+','+rate+')' : '';
+ // 人 mescroll-wxs-man
+ var man = ins.selectComponent('.mescroll-wxs-man')
+ man && man.setStyle({transform: downRotate})
+ // 盒子 mescroll-wxs-box
+ var box = ins.selectComponent('.mescroll-wxs-box')
+ box && box.setStyle({transform: downRotate})
+ })
+}
+
+/* 显示下拉刷新进度 */
+me.showLoading = function (ins){
+ me.downHight = me.optDown.offset
+ ins.requestAnimationFrame(function () {
+ ins.selectComponent('.mescroll-wxs-content').setStyle({
+ 'will-change': 'auto',
+ 'transform': 'translateY(' + me.downHight + 'px)',
+ 'transition': 'transform 300ms'
+ })
+ })
+}
+
+/* 结束下拉 */
+me.endDownScroll = function (ins){
+ me.downHight = 0;
+ me.isDownScrolling = false;
+ ins.requestAnimationFrame(function () {
+ ins.selectComponent('.mescroll-wxs-content').setStyle({
+ 'will-change': 'auto',
+ 'transform': 'translateY(0)', // 不可以写空串,否则scroll-view渲染不完整 (延时350ms会调clearTransform置空)
+ 'transition': 'transform 300ms'
+ })
+ })
+}
+
+/* 结束下拉动画执行完毕后, 清除transform和transition, 避免对列表内容样式造成影响, 如: h5的list-msg示例下拉进度条漏出来等 */
+me.clearTransform = function (ins){
+ ins.requestAnimationFrame(function () {
+ ins.selectComponent('.mescroll-wxs-content').setStyle({
+ 'will-change': '',
+ 'transform': '',
+ 'transition': ''
+ })
+ })
+}
+
+// ------ 自定义下拉刷新动画 end ------
+
+/**
+ * 监听逻辑层数据的变化 (实时更新数据)
+ */
+function propObserver(wxsProp) {
+ me.optDown = wxsProp.optDown
+ me.scrollTop = wxsProp.scrollTop
+ me.bodyHeight = wxsProp.bodyHeight
+ me.isDownScrolling = wxsProp.isDownScrolling
+ me.isUpScrolling = wxsProp.isUpScrolling
+ me.isUpBoth = wxsProp.isUpBoth
+ me.isScrollBody = wxsProp.isScrollBody
+ me.startTop = wxsProp.scrollTop // 及时更新touchstart触发的startTop, 避免scroll-view快速惯性滚动到顶部取值不准确
+}
+
+/**
+ * 监听逻辑层数据的变化 (调用wxs的方法)
+ */
+function callObserver(callProp, oldValue, ins) {
+ if (me.disabled()) return;
+ if(callProp.callType){
+ // 逻辑层(App Service)的style已失效,需在视图层(Webview)设置style
+ if(callProp.callType === 'showLoading'){
+ me.showLoading(ins)
+ }else if(callProp.callType === 'endDownScroll'){
+ me.endDownScroll(ins)
+ }else if(callProp.callType === 'clearTransform'){
+ me.clearTransform(ins)
+ }
+ }
+}
+
+/**
+ * touch事件
+ */
+function touchstartEvent(e, ins) {
+ me.downHight = 0; // 下拉的距离
+ me.startPoint = me.getPoint(e); // 记录起点
+ me.startTop = me.getScrollTop(); // 记录此时的滚动条位置
+ me.startAngle = 0; // 初始角度
+ me.lastPoint = me.startPoint; // 重置上次move的点
+ me.maxTouchmoveY = me.getBodyHeight() - me.optDown.bottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况)
+ me.inTouchend = false; // 标记不是touchend
+
+ me.callMethod(ins, {type: 'setWxsProp'}) // 同步更新wxsProp的数据 (小程序是异步的,可能touchmove先执行,才到propObserver; h5和app是同步)
+}
+
+function touchmoveEvent(e, ins) {
+ var isPrevent = true // false表示不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault (对小程序生效, h5和app无效)
+
+ if (me.disabled()) return isPrevent;
+
+ var scrollTop = me.getScrollTop(); // 当前滚动条的距离
+ var curPoint = me.getPoint(e); // 当前点
+
+ var moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
+
+ // 向下拉 && 在顶部
+ // mescroll-body,直接判定在顶部即可
+ // scroll-view在滚动时不会触发touchmove,当触顶/底/左/右时,才会触发touchmove
+ // scroll-view滚动到顶部时,scrollTop不一定为0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等
+ if (moveY > 0 && (
+ (me.isScrollBody && scrollTop <= 0)
+ ||
+ (!me.isScrollBody && (scrollTop <= 0 || (scrollTop <= me.optDown.startTop && scrollTop === me.startTop)) )
+ )) {
+ // 可下拉的条件
+ if (!me.inTouchend && !me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling &&
+ me.isUpBoth))) {
+
+ // 下拉的角度是否在配置的范围内
+ if(!me.startAngle) me.startAngle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90]
+ if (me.startAngle < me.optDown.minAngle) return isPrevent; // 如果小于配置的角度,则不往下执行下拉刷新
+
+ // 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发
+ if (me.maxTouchmoveY > 0 && curPoint.y >= me.maxTouchmoveY) {
+ me.inTouchend = true; // 标记执行touchend
+ touchendEvent(e, ins); // 提前触发touchend
+ return isPrevent;
+ }
+
+ isPrevent = false // 小程序是return false
+
+ var diff = curPoint.y - me.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上)
+
+ // 下拉距离 < 指定距离
+ if (me.downHight < me.optDown.offset) {
+ if (me.movetype !== 1) {
+ me.movetype = 1; // 加入标记,保证只执行一次
+ // me.optDown.inOffset && me.optDown.inOffset(me); // 进入指定距离范围内那一刻的回调,只执行一次
+ me.callMethod(ins, {type: 'setLoadType', downLoadType: 1})
+ me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
+ }
+ me.downHight += diff * me.optDown.inOffsetRate; // 越往下,高度变化越小
+
+ // 指定距离 <= 下拉距离
+ } else {
+ if (me.movetype !== 2) {
+ me.movetype = 2; // 加入标记,保证只执行一次
+ // me.optDown.outOffset && me.optDown.outOffset(me); // 下拉超过指定距离那一刻的回调,只执行一次
+ me.callMethod(ins, {type: 'setLoadType', downLoadType: 2})
+ me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
+ }
+ if (diff > 0) { // 向下拉
+ me.downHight += diff * me.optDown.outOffsetRate; // 越往下,高度变化越小
+ } else { // 向上收
+ me.downHight += diff; // 向上收回高度,则向上滑多少收多少高度
+ }
+ }
+
+ me.downHight = Math.round(me.downHight) // 取整
+ var rate = me.downHight / me.optDown.offset; // 下拉区域当前高度与指定距离的比值
+ // me.optDown.onMoving && me.optDown.onMoving(me, rate, me.downHight); // 下拉过程中的回调,一直在执行
+ me.onMoving(ins, rate, me.downHight)
+ }
+ }
+
+ me.lastPoint = curPoint; // 记录本次移动的点
+
+ return isPrevent // false表示不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault (对小程序生效, h5和app无效)
+}
+
+function touchendEvent(e, ins) {
+ // 如果下拉区域高度已改变,则需重置回来
+ if (me.isMoveDown) {
+ if (me.downHight >= me.optDown.offset) {
+ // 符合触发刷新的条件
+ me.downHight = me.optDown.offset; // 更新下拉区域高度
+ // me.triggerDownScroll();
+ me.callMethod(ins, {type: 'triggerDownScroll'})
+ } else {
+ // 不符合的话 则重置
+ me.downHight = 0;
+ // me.optDown.endDownScroll && me.optDown.endDownScroll(me);
+ me.callMethod(ins, {type: 'endDownScroll'})
+ }
+ me.movetype = 0;
+ me.isMoveDown = false;
+ } else if (!me.isScrollBody && me.getScrollTop() === me.startTop) { // scroll-view到顶/左/右/底的滑动事件
+ var isScrollUp = me.getPoint(e).y - me.startPoint.y < 0; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
+ // 上滑
+ if (isScrollUp) {
+ // 需检查滑动的角度
+ var angle = me.getAngle(me.getPoint(e), me.startPoint); // 两点之间的角度,区间 [0,90]
+ if (angle > 80) {
+ // 检查并触发上拉
+ // me.triggerUpScroll(true);
+ me.callMethod(ins, {type: 'triggerUpScroll'})
+ }
+ }
+ }
+ me.callMethod(ins, {type: 'setWxsProp'}) // 同步更新wxsProp的数据 (小程序是异步的,可能touchmove先执行,才到propObserver; h5和app是同步)
+}
+
+/*是否禁用*/
+me.disabled = function(){
+ return !me.optDown || !me.optDown.use || me.optDown.native
+}
+
+/* 根据点击滑动事件获取第一个手指的坐标 */
+me.getPoint = function(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}
+ } else if (e.changedTouches && e.changedTouches[0]) {
+ return {x: e.changedTouches[0].pageX,y: e.changedTouches[0].pageY}
+ } else {
+ return {x: e.clientX,y: e.clientY}
+ }
+}
+
+/* 计算两点之间的角度: 区间 [0,90]*/
+me.getAngle = function (p1, p2) {
+ var x = Math.abs(p1.x - p2.x);
+ var y = Math.abs(p1.y - p2.y);
+ var z = Math.sqrt(x * x + y * y);
+ var angle = 0;
+ if (z !== 0) {
+ angle = Math.asin(y / z) / Math.PI * 180;
+ }
+ return angle
+}
+
+/* 获取滚动条的位置 */
+me.getScrollTop = function() {
+ return me.scrollTop || 0
+}
+
+/* 获取body的高度 */
+me.getBodyHeight = function() {
+ return me.bodyHeight || 0;
+}
+
+/* 调用逻辑层的方法 */
+me.callMethod = function(ins, param) {
+ if(ins) ins.callMethod('wxsCall', param)
+}
+
+/* 导出模块 */
+module.exports = {
+ propObserver: propObserver,
+ callObserver: callObserver,
+ touchstartEvent: touchstartEvent,
+ touchmoveEvent: touchmoveEvent,
+ touchendEvent: touchendEvent
+}
\ No newline at end of file
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/meituan/components/mescroll-down.css b/uni_modules/mescroll-uni/components/mescroll-diy/meituan/components/mescroll-down.css
new file mode 100644
index 0000000000000000000000000000000000000000..eabfa30873709172e42e4dde7b945ee94938b3b3
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/meituan/components/mescroll-down.css
@@ -0,0 +1,71 @@
+/*下拉刷新--向下进度动画*/
+.mescroll-downwarp .downwarp-load {
+ display: inline-block;
+ width: 50px;
+ margin: auto;
+ border-radius: 0;
+ background-size: 100% 100%;
+ background-image: url(https://www.mescroll.com/img/meituan/mescroll-progress0.png);
+}
+
+/*下拉刷新--资源预加载,避免下拉的时候闪白屏*/
+.mescroll-downwarp .downwarp-load-preload {
+ animation: animProgress 1s steps(1, end), animLoading 1s steps(1, end) 1s;
+ pointer-events: none;
+}
+
+/*下拉刷新--进度条*/
+.mescroll-downwarp .downwarp-load-start {
+ display: inline-block;
+ width: 50px;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+ animation: animProgress 0.3s steps(1, end), animLoading 0.3s steps(1, end) 0.3s infinite;
+}
+@keyframes animProgress {
+ 0% {
+ background-image: url(https://www.mescroll.com/img/meituan/mescroll-progress0.png);
+ }
+ 16% {
+ background-image: url(https://www.mescroll.com/img/meituan/mescroll-progress1.png);
+ }
+ 32% {
+ background-image: url(https://www.mescroll.com/img/meituan/mescroll-progress2.png);
+ }
+ 48% {
+ background-image: url(https://www.mescroll.com/img/meituan/mescroll-progress3.png);
+ }
+ 64% {
+ background-image: url(https://www.mescroll.com/img/meituan/mescroll-progress4.png);
+ }
+ 80% {
+ background-image: url(https://www.mescroll.com/img/meituan/mescroll-progress5.png);
+ }
+ 100% {
+ background-image: url(https://www.mescroll.com/img/meituan/mescroll-progress0.png);
+ }
+}
+@keyframes animLoading {
+ 0% {
+ background-image: url(https://www.mescroll.com/img/meituan/mescroll-loading1.png);
+ }
+ 16% {
+ background-image: url(https://www.mescroll.com/img/meituan/mescroll-loading2.png);
+ }
+ 32% {
+ background-image: url(https://www.mescroll.com/img/meituan/mescroll-loading3.png);
+ }
+ 48% {
+ background-image: url(https://www.mescroll.com/img/meituan/mescroll-loading4.png);
+ }
+ 64% {
+ background-image: url(https://www.mescroll.com/img/meituan/mescroll-loading5.png);
+ }
+ 80% {
+ background-image: url(https://www.mescroll.com/img/meituan/mescroll-loading6.png);
+ }
+ 100% {
+ background-image: url(https://www.mescroll.com/img/meituan/mescroll-loading1.png);
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/meituan/components/mescroll-down.vue b/uni_modules/mescroll-uni/components/mescroll-diy/meituan/components/mescroll-down.vue
new file mode 100644
index 0000000000000000000000000000000000000000..fdf4f5f3ee909587181f7686167d6621347ef81f
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/meituan/components/mescroll-down.vue
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/meituan/mescroll-body.vue b/uni_modules/mescroll-uni/components/mescroll-diy/meituan/mescroll-body.vue
new file mode 100644
index 0000000000000000000000000000000000000000..afe9e61880db2896a3678d60d5b4f9fbc23acc41
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/meituan/mescroll-body.vue
@@ -0,0 +1,358 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ mescroll.optUp.textLoading }}
+
+
+ {{ mescroll.optUp.textNoMore }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/meituan/mescroll-uni.vue b/uni_modules/mescroll-uni/components/mescroll-diy/meituan/mescroll-uni.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ac1fcda3defc81f8013721f0b27acb7d7ba1cc3e
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/meituan/mescroll-uni.vue
@@ -0,0 +1,436 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ mescroll.optUp.textLoading }}
+
+
+ {{ mescroll.optUp.textNoMore }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/meituan/wxs/wxs.wxs b/uni_modules/mescroll-uni/components/mescroll-diy/meituan/wxs/wxs.wxs
new file mode 100644
index 0000000000000000000000000000000000000000..afc1a488e880f933254a39d1752065effcbff3ae
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/meituan/wxs/wxs.wxs
@@ -0,0 +1,269 @@
+// 使用wxs处理交互动画, 提高性能, 同时避免小程序bounce对下拉刷新的影响
+// https://uniapp.dcloud.io/frame?id=wxs
+// https://developers.weixin.qq.com/miniprogram/dev/framework/view/interactive-animation.html
+
+// 模拟mescroll实例, 与mescroll.js的写法尽量保持一致
+var me = {}
+
+// ------ 自定义下拉刷新动画 start ------
+
+/* 下拉过程中的回调,滑动过程一直在执行 (rate<1为inOffset; rate>1为outOffset) */
+me.onMoving = function (ins, rate, downHight){
+ ins.requestAnimationFrame(function () {
+ ins.selectComponent('.mescroll-wxs-content').setStyle({
+ 'will-change': 'transform', // 可解决下拉过程中, image和swiper脱离文档流的问题
+ 'transform': 'translateY(' + downHight + 'px)',
+ 'transition': ''
+ })
+ // 下拉刷新进度的高度,模拟下拉时小人被拉出的感觉
+ var progress = ins.selectComponent('.mescroll-wxs-progress')
+ var downLoadHeight = rate < 1 ? downHight/2+"px" : '50px' // rate<1为inOffset; rate>1为outOffset
+ progress && progress.setStyle({height: downLoadHeight})
+ })
+}
+
+/* 显示下拉刷新进度 */
+me.showLoading = function (ins){
+ me.downHight = me.optDown.offset
+ ins.requestAnimationFrame(function () {
+ ins.selectComponent('.mescroll-wxs-content').setStyle({
+ 'will-change': 'auto',
+ 'transform': 'translateY(' + me.downHight + 'px)',
+ 'transition': 'transform 300ms'
+ })
+ })
+}
+
+/* 结束下拉 */
+me.endDownScroll = function (ins){
+ me.downHight = 0;
+ me.isDownScrolling = false;
+ ins.requestAnimationFrame(function () {
+ ins.selectComponent('.mescroll-wxs-content').setStyle({
+ 'will-change': 'auto',
+ 'transform': 'translateY(0)', // 不可以写空串,否则scroll-view渲染不完整 (延时350ms会调clearTransform置空)
+ 'transition': 'transform 300ms'
+ })
+ })
+}
+
+/* 结束下拉动画执行完毕后, 清除transform和transition, 避免对列表内容样式造成影响, 如: h5的list-msg示例下拉进度条漏出来等 */
+me.clearTransform = function (ins){
+ ins.requestAnimationFrame(function () {
+ ins.selectComponent('.mescroll-wxs-content').setStyle({
+ 'will-change': '',
+ 'transform': '',
+ 'transition': ''
+ })
+ })
+}
+
+// ------ 自定义下拉刷新动画 end ------
+
+/**
+ * 监听逻辑层数据的变化 (实时更新数据)
+ */
+function propObserver(wxsProp) {
+ me.optDown = wxsProp.optDown
+ me.scrollTop = wxsProp.scrollTop
+ me.bodyHeight = wxsProp.bodyHeight
+ me.isDownScrolling = wxsProp.isDownScrolling
+ me.isUpScrolling = wxsProp.isUpScrolling
+ me.isUpBoth = wxsProp.isUpBoth
+ me.isScrollBody = wxsProp.isScrollBody
+ me.startTop = wxsProp.scrollTop // 及时更新touchstart触发的startTop, 避免scroll-view快速惯性滚动到顶部取值不准确
+}
+
+/**
+ * 监听逻辑层数据的变化 (调用wxs的方法)
+ */
+function callObserver(callProp, oldValue, ins) {
+ if (me.disabled()) return;
+ if(callProp.callType){
+ // 逻辑层(App Service)的style已失效,需在视图层(Webview)设置style
+ if(callProp.callType === 'showLoading'){
+ me.showLoading(ins)
+ }else if(callProp.callType === 'endDownScroll'){
+ me.endDownScroll(ins)
+ }else if(callProp.callType === 'clearTransform'){
+ me.clearTransform(ins)
+ }
+ }
+}
+
+/**
+ * touch事件
+ */
+function touchstartEvent(e, ins) {
+ me.downHight = 0; // 下拉的距离
+ me.startPoint = me.getPoint(e); // 记录起点
+ me.startTop = me.getScrollTop(); // 记录此时的滚动条位置
+ me.startAngle = 0; // 初始角度
+ me.lastPoint = me.startPoint; // 重置上次move的点
+ me.maxTouchmoveY = me.getBodyHeight() - me.optDown.bottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况)
+ me.inTouchend = false; // 标记不是touchend
+
+ me.callMethod(ins, {type: 'setWxsProp'}) // 同步更新wxsProp的数据 (小程序是异步的,可能touchmove先执行,才到propObserver; h5和app是同步)
+}
+
+function touchmoveEvent(e, ins) {
+ var isPrevent = true // false表示不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault (对小程序生效, h5和app无效)
+
+ if (me.disabled()) return isPrevent;
+
+ var scrollTop = me.getScrollTop(); // 当前滚动条的距离
+ var curPoint = me.getPoint(e); // 当前点
+
+ var moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
+
+ // 向下拉 && 在顶部
+ // mescroll-body,直接判定在顶部即可
+ // scroll-view在滚动时不会触发touchmove,当触顶/底/左/右时,才会触发touchmove
+ // scroll-view滚动到顶部时,scrollTop不一定为0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等
+ if (moveY > 0 && (
+ (me.isScrollBody && scrollTop <= 0)
+ ||
+ (!me.isScrollBody && (scrollTop <= 0 || (scrollTop <= me.optDown.startTop && scrollTop === me.startTop)) )
+ )) {
+ // 可下拉的条件
+ if (!me.inTouchend && !me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling &&
+ me.isUpBoth))) {
+
+ // 下拉的角度是否在配置的范围内
+ if(!me.startAngle) me.startAngle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90]
+ if (me.startAngle < me.optDown.minAngle) return isPrevent; // 如果小于配置的角度,则不往下执行下拉刷新
+
+ // 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发
+ if (me.maxTouchmoveY > 0 && curPoint.y >= me.maxTouchmoveY) {
+ me.inTouchend = true; // 标记执行touchend
+ touchendEvent(e, ins); // 提前触发touchend
+ return isPrevent;
+ }
+
+ isPrevent = false // 小程序是return false
+
+ var diff = curPoint.y - me.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上)
+
+ // 下拉距离 < 指定距离
+ if (me.downHight < me.optDown.offset) {
+ if (me.movetype !== 1) {
+ me.movetype = 1; // 加入标记,保证只执行一次
+ // me.optDown.inOffset && me.optDown.inOffset(me); // 进入指定距离范围内那一刻的回调,只执行一次
+ me.callMethod(ins, {type: 'setLoadType', downLoadType: 1})
+ me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
+ }
+ me.downHight += diff * me.optDown.inOffsetRate; // 越往下,高度变化越小
+
+ // 指定距离 <= 下拉距离
+ } else {
+ if (me.movetype !== 2) {
+ me.movetype = 2; // 加入标记,保证只执行一次
+ // me.optDown.outOffset && me.optDown.outOffset(me); // 下拉超过指定距离那一刻的回调,只执行一次
+ me.callMethod(ins, {type: 'setLoadType', downLoadType: 2})
+ me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
+ }
+ if (diff > 0) { // 向下拉
+ me.downHight += diff * me.optDown.outOffsetRate; // 越往下,高度变化越小
+ } else { // 向上收
+ me.downHight += diff; // 向上收回高度,则向上滑多少收多少高度
+ }
+ }
+
+ me.downHight = Math.round(me.downHight) // 取整
+ var rate = me.downHight / me.optDown.offset; // 下拉区域当前高度与指定距离的比值
+ // me.optDown.onMoving && me.optDown.onMoving(me, rate, me.downHight); // 下拉过程中的回调,一直在执行
+ me.onMoving(ins, rate, me.downHight)
+ }
+ }
+
+ me.lastPoint = curPoint; // 记录本次移动的点
+
+ return isPrevent // false表示不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault (对小程序生效, h5和app无效)
+}
+
+function touchendEvent(e, ins) {
+ // 如果下拉区域高度已改变,则需重置回来
+ if (me.isMoveDown) {
+ if (me.downHight >= me.optDown.offset) {
+ // 符合触发刷新的条件
+ me.downHight = me.optDown.offset; // 更新下拉区域高度
+ // me.triggerDownScroll();
+ me.callMethod(ins, {type: 'triggerDownScroll'})
+ } else {
+ // 不符合的话 则重置
+ me.downHight = 0;
+ // me.optDown.endDownScroll && me.optDown.endDownScroll(me);
+ me.callMethod(ins, {type: 'endDownScroll'})
+ }
+ me.movetype = 0;
+ me.isMoveDown = false;
+ } else if (!me.isScrollBody && me.getScrollTop() === me.startTop) { // scroll-view到顶/左/右/底的滑动事件
+ var isScrollUp = me.getPoint(e).y - me.startPoint.y < 0; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
+ // 上滑
+ if (isScrollUp) {
+ // 需检查滑动的角度
+ var angle = me.getAngle(me.getPoint(e), me.startPoint); // 两点之间的角度,区间 [0,90]
+ if (angle > 80) {
+ // 检查并触发上拉
+ // me.triggerUpScroll(true);
+ me.callMethod(ins, {type: 'triggerUpScroll'})
+ }
+ }
+ }
+ me.callMethod(ins, {type: 'setWxsProp'}) // 同步更新wxsProp的数据 (小程序是异步的,可能touchmove先执行,才到propObserver; h5和app是同步)
+}
+
+/*是否禁用*/
+me.disabled = function(){
+ return !me.optDown || !me.optDown.use || me.optDown.native
+}
+
+/* 根据点击滑动事件获取第一个手指的坐标 */
+me.getPoint = function(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}
+ } else if (e.changedTouches && e.changedTouches[0]) {
+ return {x: e.changedTouches[0].pageX,y: e.changedTouches[0].pageY}
+ } else {
+ return {x: e.clientX,y: e.clientY}
+ }
+}
+
+/* 计算两点之间的角度: 区间 [0,90]*/
+me.getAngle = function (p1, p2) {
+ var x = Math.abs(p1.x - p2.x);
+ var y = Math.abs(p1.y - p2.y);
+ var z = Math.sqrt(x * x + y * y);
+ var angle = 0;
+ if (z !== 0) {
+ angle = Math.asin(y / z) / Math.PI * 180;
+ }
+ return angle
+}
+
+/* 获取滚动条的位置 */
+me.getScrollTop = function() {
+ return me.scrollTop || 0
+}
+
+/* 获取body的高度 */
+me.getBodyHeight = function() {
+ return me.bodyHeight || 0;
+}
+
+/* 调用逻辑层的方法 */
+me.callMethod = function(ins, param) {
+ if(ins) ins.callMethod('wxsCall', param)
+}
+
+/* 导出模块 */
+module.exports = {
+ propObserver: propObserver,
+ callObserver: callObserver,
+ touchstartEvent: touchstartEvent,
+ touchmoveEvent: touchmoveEvent,
+ touchendEvent: touchendEvent
+}
\ No newline at end of file
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/meitun/components/mescroll-down.css b/uni_modules/mescroll-uni/components/mescroll-diy/meitun/components/mescroll-down.css
new file mode 100644
index 0000000000000000000000000000000000000000..e6fae0c4801dcdb1794424e7ab7e6a75f208047a
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/meitun/components/mescroll-down.css
@@ -0,0 +1,47 @@
+/*下拉刷新--内容区,定位于区域底部*/
+.mescroll-downwarp .downwarp-content {
+ padding-bottom: 0;
+}
+/*下拉刷新--进度*/
+.mescroll-downwarp .downwarp-progress {
+ display: inline-block;
+ width: 240rpx;
+ height: 140rpx;
+ border: none;
+ margin: auto;
+ border-radius: 0;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+}
+/*下拉刷新--进度-慢*/
+.mescroll-downwarp .downwarp-progress-slow {
+ animation: animProgress 2s steps(1, end) infinite;
+}
+/*下拉刷新--进度-快*/
+.mescroll-downwarp .downwarp-progress-fast {
+ animation: animProgress 0.6s steps(1, end) infinite;
+}
+@keyframes animProgress {
+ 0% {
+ background-image: url(https://www.mescroll.com/img/meitun/mescroll-progress1.png);
+ }
+ 16% {
+ background-image: url(https://www.mescroll.com/img/meitun/mescroll-progress2.png);
+ }
+ 32% {
+ background-image: url(https://www.mescroll.com/img/meitun/mescroll-progress3.png);
+ }
+ 48% {
+ background-image: url(https://www.mescroll.com/img/meitun/mescroll-progress4.png);
+ }
+ 64% {
+ background-image: url(https://www.mescroll.com/img/meitun/mescroll-progress5.png);
+ }
+ 80% {
+ background-image: url(https://www.mescroll.com/img/meitun/mescroll-progress6.png);
+ }
+ 100% {
+ background-image: url(https://www.mescroll.com/img/meitun/mescroll-progress1.png);
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/meitun/components/mescroll-down.vue b/uni_modules/mescroll-uni/components/mescroll-diy/meitun/components/mescroll-down.vue
new file mode 100644
index 0000000000000000000000000000000000000000..cd94ab895788281307bb5e2522f9e30cd119125c
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/meitun/components/mescroll-down.vue
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/meitun/mescroll-body.vue b/uni_modules/mescroll-uni/components/mescroll-diy/meitun/mescroll-body.vue
new file mode 100644
index 0000000000000000000000000000000000000000..40d50a13bddd19068bf73094197fb347ace73acb
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/meitun/mescroll-body.vue
@@ -0,0 +1,353 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ mescroll.optUp.textLoading }}
+
+
+ {{ mescroll.optUp.textNoMore }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/meitun/mescroll-uni.vue b/uni_modules/mescroll-uni/components/mescroll-diy/meitun/mescroll-uni.vue
new file mode 100644
index 0000000000000000000000000000000000000000..4b0b1e311e41ce37135a81619b894b74e547632b
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/meitun/mescroll-uni.vue
@@ -0,0 +1,430 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ mescroll.optUp.textLoading }}
+
+
+ {{ mescroll.optUp.textNoMore }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/taobao/components/mescroll-down.css b/uni_modules/mescroll-uni/components/mescroll-diy/taobao/components/mescroll-down.css
new file mode 100644
index 0000000000000000000000000000000000000000..d8bd0e7e8f202a180c9229212741741881501be2
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/taobao/components/mescroll-down.css
@@ -0,0 +1,89 @@
+/*下拉刷新*/
+.mescroll-downwarp{
+ background-color: #2B202D;
+ background-image: url(https://www.mescroll.com/img/taobao/mescroll-bg.png);
+ background-size: cover;
+ background-position: 50% 55%;
+}
+
+/*下拉刷新--内容区,定位于区域底部*/
+.mescroll-downwarp .downwarp-content{
+ height: 100rpx;
+ background-image: url(https://www.mescroll.com/img/taobao/mescroll-bg-down.png);
+ background-size: 100% 100%;
+}
+/*下拉刷新--旋转进度条*/
+.mescroll-downwarp .downwarp-progress{
+ position: relative;
+ width: 56rpx;
+ height: 56rpx;
+ border: none;
+}
+/*下拉刷新--旋转进度条-箭头*/
+.mescroll-downwarp .downwarp-progress:after{
+ position: absolute;
+ top: 10rpx;
+ left: 10rpx;
+ content: "";
+ display: inline-block;
+ width: 36rpx;
+ height: 36rpx;
+ background-image: url(https://www.mescroll.com/img/taobao/mescroll-arrow.png);
+ background-size: contain;
+ background-position: center;
+}
+/*下拉刷新--旋转进度条-左右遮罩*/
+.mescroll-downwarp .downwarp-progress .progress-arc{
+ display: inline-block;
+ width: 28rpx;
+ height: 56rpx;
+ overflow: hidden;
+}
+/*下拉刷新--旋转进度条-左半圆*/
+.mescroll-downwarp .progress-left-arc{
+ width: 52rpx;
+ height: 52rpx;
+ border-radius: 50%;
+ border: 1px solid white;
+ border-left-color: transparent;
+ border-bottom-color: transparent;
+ transform: rotate(45deg);
+}
+/*下拉刷新--旋转进度条-右半圆*/
+.mescroll-downwarp .progress-right-arc{
+ width: 52rpx;
+ height: 52rpx;
+ margin-left: -28rpx;
+ border-radius: 50%;
+ border: 1px solid white;
+ border-top-color: transparent;
+ border-right-color: transparent;
+ transform: rotate(45deg);
+}
+
+/*下拉刷新--旋转进度条-旋转中*/
+.mescroll-downwarp .mescroll-rotate.downwarp-progress{
+ width: 52rpx;
+ height: 52rpx;
+ border: 1px solid white;
+}
+.mescroll-downwarp .mescroll-rotate.downwarp-progress .progress-arc,
+.mescroll-downwarp .mescroll-rotate.downwarp-progress:after{
+ display: none;
+}
+
+/*下拉刷新--提示*/
+.mescroll-downwarp .downwarp-tip{
+ min-width: 180rpx;
+ color: white;
+}
+
+/*下拉区域--淘宝二楼显示月亮的动画*/
+.moon-show{
+ transition: none;
+ animation: moonShow 2s linear;
+}
+@keyframes moonShow {
+ 50% {height: 200%; }
+ 100% {height: 50%; }
+}
\ No newline at end of file
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/taobao/components/mescroll-down.vue b/uni_modules/mescroll-uni/components/mescroll-diy/taobao/components/mescroll-down.vue
new file mode 100644
index 0000000000000000000000000000000000000000..fc872c5c58c36d358c4b5307ac5d12a763a98c02
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/taobao/components/mescroll-down.vue
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{downText}}
+
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/taobao/mescroll-body.vue b/uni_modules/mescroll-uni/components/mescroll-diy/taobao/mescroll-body.vue
new file mode 100644
index 0000000000000000000000000000000000000000..1dffc6e2a2663f1a57c0af47c607715cd389c857
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/taobao/mescroll-body.vue
@@ -0,0 +1,493 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{downText}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ mescroll.optUp.textLoading }}
+
+
+ {{ mescroll.optUp.textNoMore }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/taobao/mescroll-uni-option.js b/uni_modules/mescroll-uni/components/mescroll-diy/taobao/mescroll-uni-option.js
new file mode 100644
index 0000000000000000000000000000000000000000..f13859765405c22c0fc3215765d7fb0b469dce3e
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/taobao/mescroll-uni-option.js
@@ -0,0 +1,64 @@
+// mescroll-uni和mescroll-body 的全局配置
+const GlobalOption = {
+ down: {
+ // 其他down的配置参数也可以写,这里只展示了常用的配置:
+ offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
+ outOffsetRate: 0.5, //超过指定距离范围外时,改变下拉区域高度比例;小于1,越往下拉高度变化越小;
+ native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
+ },
+ up: {
+ // 其他up的配置参数也可以写,这里只展示了常用的配置:
+ offset: 150, // 距底部多远时,触发upCallback
+ toTop: {
+ // 回到顶部按钮,需配置src才显示
+ src: "https://www.mescroll.com/img/mescroll-totop.png", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
+ offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px
+ right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+ bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+ width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+ },
+ empty: {
+ use: true, // 是否显示空布局
+ icon: "https://www.mescroll.com/img/mescroll-empty.png" // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
+ }
+ },
+ // 国际化配置
+ i18n: {
+ // 中文
+ zh: {
+ down: {
+ textInOffset: '下拉即可刷新', // 下拉的距离在offset范围内的提示文本
+ textOutOffset: '释放即可刷新', // 下拉的距离大于offset范围的提示文本
+ textLoading: '加载中...', // 加载中的提示文本
+ textSuccess: '加载成功', // 加载成功的文本
+ textErr: '加载失败', // 加载失败的文本
+ },
+ up: {
+ textLoading: '正在寻找您心水的宝贝', // 加载中的提示文本
+ textNoMore: '没有了哦~', // 没有更多数据的提示文本
+ empty: {
+ tip: '~ 暂无相关数据 ~' // 空提示
+ }
+ }
+ },
+ // 英文
+ en: {
+ down: {
+ textInOffset: 'drop down refresh',
+ textOutOffset: 'release updates',
+ textLoading: 'loading ...',
+ textSuccess: 'loaded successfully',
+ textErr: 'loading failed'
+ },
+ up: {
+ textLoading: 'loading ...',
+ textNoMore: '-- END --',
+ empty: {
+ tip: '~ absolutely empty ~'
+ }
+ }
+ }
+ }
+}
+
+export default GlobalOption
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/taobao/mescroll-uni.vue b/uni_modules/mescroll-uni/components/mescroll-diy/taobao/mescroll-uni.vue
new file mode 100644
index 0000000000000000000000000000000000000000..dbeacf074aac1b35cd6f492d3f5f7ae463ba7e07
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/taobao/mescroll-uni.vue
@@ -0,0 +1,572 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{downText}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ mescroll.optUp.textLoading }}
+
+
+ {{ mescroll.optUp.textNoMore }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/taobao/wxs/wxs.wxs b/uni_modules/mescroll-uni/components/mescroll-diy/taobao/wxs/wxs.wxs
new file mode 100644
index 0000000000000000000000000000000000000000..b77f813ffb7acc7eb4e344e8d98950d57a11f9b5
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/taobao/wxs/wxs.wxs
@@ -0,0 +1,318 @@
+// 使用wxs处理交互动画, 提高性能, 同时避免小程序bounce对下拉刷新的影响
+// https://uniapp.dcloud.io/frame?id=wxs
+// https://developers.weixin.qq.com/miniprogram/dev/framework/view/interactive-animation.html
+
+// 模拟mescroll实例, 与mescroll.js的写法尽量保持一致
+var me = {}
+
+// ------ 自定义下拉刷新动画 start ------
+
+/* 下拉过程中的回调,滑动过程一直在执行 (rate<1为inOffset; rate>1为outOffset)*/
+me.onMoving = function (ins, rate, downHight){
+ var isLockCallback = (rate >= 2); //大于2倍offset,自定义下拉刷新,进入淘宝二楼
+
+ ins.requestAnimationFrame(function () {
+ ins.selectComponent('.mescroll-wxs-content').setStyle({
+ 'will-change': 'transform', // 可解决下拉过程中, image和swiper脱离文档流的问题
+ 'transform': 'translateY(' + downHight + 'px)',
+ 'transition': ''
+ })
+ // 显示淘宝二楼,则不再更新进度条
+ if(isLockCallback) return;
+ var leftAct = ins.selectComponent('.wxs-left-arc');
+ if(!leftAct) return;
+ var rightAct = ins.selectComponent('.wxs-right-arc');
+ if(!rightAct) return;
+ // 左半圆
+ var rotateLeft = ''
+ if (rate<1) {
+ var progressLeft = 360 * rate
+ if (progressLeft<180) {
+ rotateLeft = 'rotate(45deg)';
+ } else{
+ rotateLeft = 'rotate(' + (progressLeft-180+45) + 'deg)';
+ }
+ }else{
+ rotateLeft = 'rotate(225deg)';
+ }
+ leftAct.setStyle({
+ transform: rotateLeft
+ })
+ // 右半圆
+ var rotateRight = ""
+ var progressRight = 360 * rate;
+ if (progressRight<180) {
+ rotateRight = 'rotate(' + (progressRight+45) + 'deg)';
+ } else{
+ rotateRight = 'rotate(225deg)';
+ }
+ rightAct.setStyle({
+ transform: rotateRight
+ })
+ })
+
+ // 回调wxsCallLock
+ if(me.isLockCallback != isLockCallback){
+ me.isLockCallback=isLockCallback // 避免多次触发
+ ins.callMethod('wxsCallLock', {rate:rate, isLockCallback:isLockCallback})
+ }
+}
+
+/* 显示下拉刷新进度 */
+me.showLoading = function (ins){
+ me.downHight = me.optDown.offset
+ ins.requestAnimationFrame(function () {
+ ins.selectComponent('.mescroll-wxs-content').setStyle({
+ 'will-change': 'auto',
+ 'transform': 'translateY(' + me.downHight + 'px)',
+ 'transition': 'transform 300ms'
+ })
+ })
+}
+
+/* 结束下拉 */
+me.endDownScroll = function (ins){
+ me.downHight = 0;
+ me.isDownScrolling = false;
+ ins.requestAnimationFrame(function () {
+ ins.selectComponent('.mescroll-wxs-content').setStyle({
+ 'will-change': 'auto',
+ 'transform': 'translateY(0)', // 不可以写空串,否则scroll-view渲染不完整 (延时350ms会调clearTransform置空)
+ 'transition': 'transform 300ms'
+ })
+ })
+}
+
+/* 结束下拉动画执行完毕后, 清除transform和transition, 避免对列表内容样式造成影响, 如: h5的list-msg示例下拉进度条漏出来等 */
+me.clearTransform = function (ins){
+ ins.requestAnimationFrame(function () {
+ ins.selectComponent('.mescroll-wxs-content').setStyle({
+ 'will-change': '',
+ 'transform': '',
+ 'transition': ''
+ })
+ })
+}
+
+/* 显示淘宝二楼 */
+me.showErlou = function (ins){
+ ins.requestAnimationFrame(function () {
+ var downHight = 2 * me.getBodyHeight(); // 往下拉
+ ins.selectComponent('.mescroll-wxs-content').setStyle({
+ transform: 'translateY(' + downHight + 'px)',
+ transition: 'transform 300ms'
+ })
+ })
+}
+
+// ------ 自定义下拉刷新动画 end ------
+
+/**
+ * 监听逻辑层数据的变化 (实时更新数据)
+ */
+function propObserver(wxsProp) {
+ me.optDown = wxsProp.optDown
+ me.scrollTop = wxsProp.scrollTop
+ me.bodyHeight = wxsProp.bodyHeight
+ me.isDownScrolling = wxsProp.isDownScrolling
+ me.isUpScrolling = wxsProp.isUpScrolling
+ me.isUpBoth = wxsProp.isUpBoth
+ me.isScrollBody = wxsProp.isScrollBody
+ me.startTop = wxsProp.scrollTop // 及时更新touchstart触发的startTop, 避免scroll-view快速惯性滚动到顶部取值不准确
+}
+
+/**
+ * 监听逻辑层数据的变化 (调用wxs的方法)
+ */
+function callObserver(callProp, oldValue, ins) {
+ if (me.disabled()) return;
+ if(callProp.callType){
+ // 逻辑层(App Service)的style已失效,需在视图层(Webview)设置style
+ if(callProp.callType === 'showLoading'){
+ me.showLoading(ins)
+ }else if(callProp.callType === 'endDownScroll'){
+ me.endDownScroll(ins)
+ }else if(callProp.callType === 'clearTransform'){
+ me.clearTransform(ins)
+ }else if(callProp.callType === 'showErlou'){
+ me.showErlou(ins)
+ }
+ }
+}
+
+/**
+ * touch事件
+ */
+function touchstartEvent(e, ins) {
+ me.downHight = 0; // 下拉的距离
+ me.startPoint = me.getPoint(e); // 记录起点
+ me.startTop = me.getScrollTop(); // 记录此时的滚动条位置
+ me.startAngle = 0; // 初始角度
+ me.lastPoint = me.startPoint; // 重置上次move的点
+ me.maxTouchmoveY = me.getBodyHeight() - me.optDown.bottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况)
+ me.inTouchend = false; // 标记不是touchend
+
+ me.callMethod(ins, {type: 'setWxsProp'}) // 同步更新wxsProp的数据 (小程序是异步的,可能touchmove先执行,才到propObserver; h5和app是同步)
+}
+
+function touchmoveEvent(e, ins) {
+ var isPrevent = true // false表示不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault (对小程序生效, h5和app无效)
+
+ if (me.disabled()) return isPrevent;
+
+ var scrollTop = me.getScrollTop(); // 当前滚动条的距离
+ var curPoint = me.getPoint(e); // 当前点
+
+ var moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
+
+ // 向下拉 && 在顶部
+ // mescroll-body,直接判定在顶部即可
+ // scroll-view在滚动时不会触发touchmove,当触顶/底/左/右时,才会触发touchmove
+ // scroll-view滚动到顶部时,scrollTop不一定为0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等
+ if (moveY > 0 && (
+ (me.isScrollBody && scrollTop <= 0)
+ ||
+ (!me.isScrollBody && (scrollTop <= 0 || (scrollTop <= me.optDown.startTop && scrollTop === me.startTop)) )
+ )) {
+ // 可下拉的条件
+ if (!me.inTouchend && !me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling &&
+ me.isUpBoth))) {
+
+ // 下拉的角度是否在配置的范围内
+ if(!me.startAngle) me.startAngle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90]
+ if (me.startAngle < me.optDown.minAngle) return isPrevent; // 如果小于配置的角度,则不往下执行下拉刷新
+
+ // 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发
+ if (me.maxTouchmoveY > 0 && curPoint.y >= me.maxTouchmoveY) {
+ me.inTouchend = true; // 标记执行touchend
+ touchendEvent(e, ins); // 提前触发touchend
+ return isPrevent;
+ }
+
+ isPrevent = false // 小程序是return false
+
+ var diff = curPoint.y - me.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上)
+
+ // 下拉距离 < 指定距离
+ if (me.downHight < me.optDown.offset) {
+ if (me.movetype !== 1) {
+ me.movetype = 1; // 加入标记,保证只执行一次
+ // me.optDown.inOffset && me.optDown.inOffset(me); // 进入指定距离范围内那一刻的回调,只执行一次
+ me.callMethod(ins, {type: 'setLoadType', downLoadType: 1})
+ me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
+ }
+ me.downHight += diff * me.optDown.inOffsetRate; // 越往下,高度变化越小
+
+ // 指定距离 <= 下拉距离
+ } else {
+ if (me.movetype !== 2) {
+ me.movetype = 2; // 加入标记,保证只执行一次
+ // me.optDown.outOffset && me.optDown.outOffset(me); // 下拉超过指定距离那一刻的回调,只执行一次
+ me.callMethod(ins, {type: 'setLoadType', downLoadType: 2})
+ me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
+ }
+ if (diff > 0) { // 向下拉
+ me.downHight += diff * me.optDown.outOffsetRate; // 越往下,高度变化越小
+ } else { // 向上收
+ me.downHight += diff; // 向上收回高度,则向上滑多少收多少高度
+ }
+ }
+
+ me.downHight = Math.round(me.downHight) // 取整
+ var rate = me.downHight / me.optDown.offset; // 下拉区域当前高度与指定距离的比值
+ // me.optDown.onMoving && me.optDown.onMoving(me, rate, me.downHight); // 下拉过程中的回调,一直在执行
+ me.onMoving(ins, rate, me.downHight)
+ }
+ }
+
+ me.lastPoint = curPoint; // 记录本次移动的点
+
+ return isPrevent // false表示不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault (对小程序生效, h5和app无效)
+}
+
+function touchendEvent(e, ins) {
+ // 如果下拉区域高度已改变,则需重置回来
+ if (me.isMoveDown) {
+ if (me.downHight >= me.optDown.offset) {
+ // 符合触发刷新的条件
+ me.downHight = me.optDown.offset; // 更新下拉区域高度
+ // me.triggerDownScroll();
+ me.callMethod(ins, {type: 'triggerDownScroll'})
+ } else {
+ // 不符合的话 则重置
+ me.downHight = 0;
+ // me.optDown.endDownScroll && me.optDown.endDownScroll(me);
+ me.callMethod(ins, {type: 'endDownScroll'})
+ }
+ me.movetype = 0;
+ me.isMoveDown = false;
+ } else if (!me.isScrollBody && me.getScrollTop() === me.startTop) { // scroll-view到顶/左/右/底的滑动事件
+ var isScrollUp = me.getPoint(e).y - me.startPoint.y < 0; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
+ // 上滑
+ if (isScrollUp) {
+ // 需检查滑动的角度
+ var angle = me.getAngle(me.getPoint(e), me.startPoint); // 两点之间的角度,区间 [0,90]
+ if (angle > 80) {
+ // 检查并触发上拉
+ // me.triggerUpScroll(true);
+ me.callMethod(ins, {type: 'triggerUpScroll'})
+ }
+ }
+ }
+ me.callMethod(ins, {type: 'setWxsProp'}) // 同步更新wxsProp的数据 (小程序是异步的,可能touchmove先执行,才到propObserver; h5和app是同步)
+}
+
+/*是否禁用*/
+me.disabled = function(){
+ return !me.optDown || !me.optDown.use || me.optDown.native
+}
+
+/* 根据点击滑动事件获取第一个手指的坐标 */
+me.getPoint = function(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}
+ } else if (e.changedTouches && e.changedTouches[0]) {
+ return {x: e.changedTouches[0].pageX,y: e.changedTouches[0].pageY}
+ } else {
+ return {x: e.clientX,y: e.clientY}
+ }
+}
+
+/* 计算两点之间的角度: 区间 [0,90]*/
+me.getAngle = function (p1, p2) {
+ var x = Math.abs(p1.x - p2.x);
+ var y = Math.abs(p1.y - p2.y);
+ var z = Math.sqrt(x * x + y * y);
+ var angle = 0;
+ if (z !== 0) {
+ angle = Math.asin(y / z) / Math.PI * 180;
+ }
+ return angle
+}
+
+/* 获取滚动条的位置 */
+me.getScrollTop = function() {
+ return me.scrollTop || 0
+}
+
+/* 获取body的高度 */
+me.getBodyHeight = function() {
+ return me.bodyHeight || 0;
+}
+
+/* 调用逻辑层的方法 */
+me.callMethod = function(ins, param) {
+ if(ins) ins.callMethod('wxsCall', param)
+}
+
+/* 导出模块 */
+module.exports = {
+ propObserver: propObserver,
+ callObserver: callObserver,
+ touchstartEvent: touchstartEvent,
+ touchmoveEvent: touchmoveEvent,
+ touchendEvent: touchendEvent
+}
\ No newline at end of file
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-down.css b/uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-down.css
new file mode 100644
index 0000000000000000000000000000000000000000..b62ed40363fde316db22f759f23ec0ab036911a7
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-down.css
@@ -0,0 +1,44 @@
+/*下拉刷新--上下箭头*/
+.mescroll-downwarp .downwarp-arrow {
+ display: inline-block;
+ width: 20px;
+ height: 20px;
+ margin: 10px;
+ background-image: url(https://www.mescroll.com/img/xinlang/mescroll-arrow.png);
+ background-size: contain;
+ vertical-align: middle;
+ transition: all 300ms;
+}
+
+/*下拉刷新--旋转进度条*/
+.mescroll-downwarp .downwarp-progress{
+ width: 36px;
+ height: 36px;
+ border: none;
+ margin: auto;
+ background-size: contain;
+ animation: progressRotate 0.6s steps(6, start) infinite;
+}
+@keyframes progressRotate {
+ 0% {
+ background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png);
+ }
+ 16% {
+ background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress2.png);
+ }
+ 32% {
+ background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress3.png);
+ }
+ 48% {
+ background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress4.png);
+ }
+ 64% {
+ background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress5.png);
+ }
+ 80% {
+ background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress6.png);
+ }
+ 100% {
+ background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png);
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-down.vue b/uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-down.vue
new file mode 100644
index 0000000000000000000000000000000000000000..745d90320af6638a2c0bfe117d4a34b2c49311ef
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-down.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+ {{ downText }}
+
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-up.css b/uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-up.css
new file mode 100644
index 0000000000000000000000000000000000000000..abf51f1dbd2b76a3665431217fb59ec6f1cac832
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-up.css
@@ -0,0 +1,32 @@
+/*上拉加载--旋转进度条*/
+.mescroll-upwarp .upwarp-progress {
+ width: 36px;
+ height: 36px;
+ border: none;
+ margin: auto;
+ background-size: contain;
+ animation: progressRotate 0.6s steps(6, start) infinite;
+}
+@keyframes progressRotate {
+ 0% {
+ background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png);
+ }
+ 16% {
+ background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress2.png);
+ }
+ 32% {
+ background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress3.png);
+ }
+ 48% {
+ background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress4.png);
+ }
+ 64% {
+ background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress5.png);
+ }
+ 80% {
+ background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress6.png);
+ }
+ 100% {
+ background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png);
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-up.vue b/uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-up.vue
new file mode 100644
index 0000000000000000000000000000000000000000..31bd42a1c6a4f639167fc6e4b2d7685b95caf256
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-up.vue
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+ {{ mOption.textLoading }}
+
+
+ {{ mOption.textNoMore }}
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-body.vue b/uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-body.vue
new file mode 100644
index 0000000000000000000000000000000000000000..cb2376a3685d219d28a654a7574e8275b550d56b
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-body.vue
@@ -0,0 +1,380 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ downText }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ mescroll.optUp.textLoading }}
+
+
+ {{ mescroll.optUp.textNoMore }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-uni-option.js b/uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-uni-option.js
new file mode 100644
index 0000000000000000000000000000000000000000..55d331ec182b4fa2b75285e0cae175c990fdbc2a
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-uni-option.js
@@ -0,0 +1,64 @@
+// 全局配置
+// mescroll-body 和 mescroll-uni 通用
+const GlobalOption = {
+ down: {
+ // 其他down的配置参数也可以写,这里只展示了常用的配置:
+ offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
+ native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
+ },
+ up: {
+ // 其他up的配置参数也可以写,这里只展示了常用的配置:
+ offset: 150, // 距底部多远时,触发upCallback,仅mescroll-uni生效 ( mescroll-body配置的是pages.json的 onReachBottomDistance )
+ toTop: {
+ // 回到顶部按钮,需配置src才显示
+ src: "https://www.mescroll.com/img/mescroll-totop.png", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
+ offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px
+ right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+ bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+ width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+ },
+ empty: {
+ use: true, // 是否显示空布局
+ icon: "https://www.mescroll.com/img/mescroll-empty.png" // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
+ }
+ },
+ // 国际化配置
+ i18n: {
+ // 中文
+ zh: {
+ down: {
+ textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
+ textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
+ textLoading: '加载中 ...', // 加载中的提示文本
+ textSuccess: '加载成功', // 加载成功的文本
+ textErr: '加载失败', // 加载失败的文本
+ },
+ up: {
+ textLoading: '加载中 ...', // 加载中的提示文本
+ textNoMore: '-- END --', // 没有更多数据的提示文本
+ empty: {
+ tip: '~ 空空如也 ~' // 空提示
+ }
+ }
+ },
+ // 英文
+ en: {
+ down: {
+ textInOffset: 'drop down refresh',
+ textOutOffset: 'release updates',
+ textLoading: 'loading ...',
+ textSuccess: 'loaded successfully',
+ textErr: 'loading failed'
+ },
+ up: {
+ textLoading: 'loading ...',
+ textNoMore: '-- END --',
+ empty: {
+ tip: '~ absolutely empty ~'
+ }
+ }
+ }
+ }
+}
+
+export default GlobalOption
diff --git a/uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-uni.vue b/uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-uni.vue
new file mode 100644
index 0000000000000000000000000000000000000000..8c8b6de43ec095bb6743fc0fc4fdd0052d98b9b0
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-uni.vue
@@ -0,0 +1,459 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ downText }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ mescroll.optUp.textLoading }}
+
+
+ {{ mescroll.optUp.textNoMore }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-empty/mescroll-empty.vue b/uni_modules/mescroll-uni/components/mescroll-empty/mescroll-empty.vue
new file mode 100644
index 0000000000000000000000000000000000000000..32b406965818aea72326c58af2ff1d1b3137d0d9
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-empty/mescroll-empty.vue
@@ -0,0 +1,116 @@
+
+
+
+
+ {{ tip }}
+ {{ btnText }}
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-down.css b/uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-down.css
new file mode 100644
index 0000000000000000000000000000000000000000..72bf106c746db53eab38a2210a17620bc35a8175
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-down.css
@@ -0,0 +1,55 @@
+/* 下拉刷新区域 */
+.mescroll-downwarp {
+ position: absolute;
+ top: -100%;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ text-align: center;
+}
+
+/* 下拉刷新--内容区,定位于区域底部 */
+.mescroll-downwarp .downwarp-content {
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ width: 100%;
+ min-height: 60rpx;
+ padding: 20rpx 0;
+ text-align: center;
+}
+
+/* 下拉刷新--提示文本 */
+.mescroll-downwarp .downwarp-tip {
+ display: inline-block;
+ font-size: 28rpx;
+ vertical-align: middle;
+ margin-left: 16rpx;
+ /* color: gray; 已在style设置color,此处删去*/
+}
+
+/* 下拉刷新--旋转进度条 */
+.mescroll-downwarp .downwarp-progress {
+ display: inline-block;
+ width: 32rpx;
+ height: 32rpx;
+ border-radius: 50%;
+ border: 2rpx solid gray;
+ border-bottom-color: transparent !important; /*已在style设置border-color,此处需加 !important*/
+ vertical-align: middle;
+}
+
+/* 旋转动画 */
+.mescroll-downwarp .mescroll-rotate {
+ animation: mescrollDownRotate 0.6s linear infinite;
+}
+
+@keyframes mescrollDownRotate {
+ 0% {
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-down.vue b/uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-down.vue
new file mode 100644
index 0000000000000000000000000000000000000000..9fd1567fa1e858625eb76ab9c37efd49e84d2ccd
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-down.vue
@@ -0,0 +1,47 @@
+
+
+
+
+
+ {{downText}}
+
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-top.vue b/uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-top.vue
new file mode 100644
index 0000000000000000000000000000000000000000..a7c7e3a676ffeb42dc697251c1355a1452718440
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-top.vue
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-up.css b/uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-up.css
new file mode 100644
index 0000000000000000000000000000000000000000..cbf48cd23134de3bc62b28f3d473e2cdfae5fe3a
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-up.css
@@ -0,0 +1,47 @@
+/* 上拉加载区域 */
+.mescroll-upwarp {
+ box-sizing: border-box;
+ min-height: 110rpx;
+ padding: 30rpx 0;
+ text-align: center;
+ clear: both;
+}
+
+/*提示文本 */
+.mescroll-upwarp .upwarp-tip,
+.mescroll-upwarp .upwarp-nodata {
+ display: inline-block;
+ font-size: 28rpx;
+ vertical-align: middle;
+ /* color: gray; 已在style设置color,此处删去*/
+}
+
+.mescroll-upwarp .upwarp-tip {
+ margin-left: 16rpx;
+}
+
+/*旋转进度条 */
+.mescroll-upwarp .upwarp-progress {
+ display: inline-block;
+ width: 32rpx;
+ height: 32rpx;
+ border-radius: 50%;
+ border: 2rpx solid gray;
+ border-bottom-color: transparent !important; /*已在style设置border-color,此处需加 !important*/
+ vertical-align: middle;
+}
+
+/* 旋转动画 */
+.mescroll-upwarp .mescroll-rotate {
+ animation: mescrollUpRotate 0.6s linear infinite;
+}
+
+@keyframes mescrollUpRotate {
+ 0% {
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-up.vue b/uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-up.vue
new file mode 100644
index 0000000000000000000000000000000000000000..11c2e1fb126936f50274a96db34680eb81a0f800
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-up.vue
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+ {{ mOption.textLoading }}
+
+
+ {{ mOption.textNoMore }}
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-i18n.js b/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-i18n.js
new file mode 100644
index 0000000000000000000000000000000000000000..2b6a50f0536a0adb90e62c6aa1be9a8693eb3e42
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-i18n.js
@@ -0,0 +1,15 @@
+// 国际化工具类
+const mescrollI18n = {
+ // 默认语言
+ def: "zh",
+ // 获取当前语言类型
+ getType(){
+ return uni.getStorageSync("mescroll-i18n") || this.def
+ },
+ // 设置当前语言类型
+ setType(type){
+ uni.setStorageSync("mescroll-i18n", type)
+ }
+}
+
+export default mescrollI18n
diff --git a/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js b/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js
new file mode 100644
index 0000000000000000000000000000000000000000..10f68c09c740dba6269d8585709c6ad0f4e6cf7b
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js
@@ -0,0 +1,46 @@
+// mescroll-body 和 mescroll-uni 通用
+const MescrollMixin = {
+ data() {
+ return {
+ mescroll: null //mescroll实例对象
+ }
+ },
+ // 注册系统自带的下拉刷新 (配置down.native为true时生效, 还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
+ onPullDownRefresh(){
+ this.mescroll && this.mescroll.onPullDownRefresh();
+ },
+ // 注册列表滚动事件,用于判定在顶部可下拉刷新,在指定位置可显示隐藏回到顶部按钮 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
+ onPageScroll(e) {
+ this.mescroll && this.mescroll.onPageScroll(e);
+ },
+ // 注册滚动到底部的事件,用于上拉加载 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
+ onReachBottom() {
+ this.mescroll && this.mescroll.onReachBottom();
+ },
+ methods: {
+ // mescroll组件初始化的回调,可获取到mescroll对象
+ mescrollInit(mescroll) {
+ this.mescroll = mescroll;
+ },
+ // 下拉刷新的回调 (mixin默认resetUpScroll)
+ downCallback() {
+ if(this.mescroll.optUp.use){
+ this.mescroll.resetUpScroll()
+ }else{
+ setTimeout(()=>{
+ this.mescroll.endSuccess();
+ }, 500)
+ }
+ },
+ // 上拉加载的回调
+ upCallback() {
+ // mixin默认延时500自动结束加载
+ setTimeout(()=>{
+ this.mescroll.endErr();
+ }, 500)
+ }
+ }
+
+}
+
+export default MescrollMixin;
diff --git a/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni-option.js b/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni-option.js
new file mode 100644
index 0000000000000000000000000000000000000000..55d331ec182b4fa2b75285e0cae175c990fdbc2a
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni-option.js
@@ -0,0 +1,64 @@
+// 全局配置
+// mescroll-body 和 mescroll-uni 通用
+const GlobalOption = {
+ down: {
+ // 其他down的配置参数也可以写,这里只展示了常用的配置:
+ offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
+ native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
+ },
+ up: {
+ // 其他up的配置参数也可以写,这里只展示了常用的配置:
+ offset: 150, // 距底部多远时,触发upCallback,仅mescroll-uni生效 ( mescroll-body配置的是pages.json的 onReachBottomDistance )
+ toTop: {
+ // 回到顶部按钮,需配置src才显示
+ src: "https://www.mescroll.com/img/mescroll-totop.png", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
+ offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px
+ right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+ bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+ width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+ },
+ empty: {
+ use: true, // 是否显示空布局
+ icon: "https://www.mescroll.com/img/mescroll-empty.png" // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
+ }
+ },
+ // 国际化配置
+ i18n: {
+ // 中文
+ zh: {
+ down: {
+ textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
+ textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
+ textLoading: '加载中 ...', // 加载中的提示文本
+ textSuccess: '加载成功', // 加载成功的文本
+ textErr: '加载失败', // 加载失败的文本
+ },
+ up: {
+ textLoading: '加载中 ...', // 加载中的提示文本
+ textNoMore: '-- END --', // 没有更多数据的提示文本
+ empty: {
+ tip: '~ 空空如也 ~' // 空提示
+ }
+ }
+ },
+ // 英文
+ en: {
+ down: {
+ textInOffset: 'drop down refresh',
+ textOutOffset: 'release updates',
+ textLoading: 'loading ...',
+ textSuccess: 'loaded successfully',
+ textErr: 'loading failed'
+ },
+ up: {
+ textLoading: 'loading ...',
+ textNoMore: '-- END --',
+ empty: {
+ tip: '~ absolutely empty ~'
+ }
+ }
+ }
+ }
+}
+
+export default GlobalOption
diff --git a/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.css b/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.css
new file mode 100644
index 0000000000000000000000000000000000000000..39438cdf426f9fd86ae6ea61ef8ec081b22f6e09
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.css
@@ -0,0 +1,36 @@
+.mescroll-uni-warp{
+ height: 100%;
+}
+
+.mescroll-uni-content{
+ height: 100%;
+}
+
+.mescroll-uni {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ min-height: 200rpx;
+ overflow-y: auto;
+ box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */
+}
+
+/* 定位的方式固定高度 */
+.mescroll-uni-fixed{
+ z-index: 1;
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ width: auto; /* 使right生效 */
+ height: auto; /* 使bottom生效 */
+}
+
+/* 适配 iPhoneX */
+@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
+ .mescroll-safearea {
+ padding-bottom: constant(safe-area-inset-bottom);
+ padding-bottom: env(safe-area-inset-bottom);
+ }
+}
diff --git a/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.js b/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.js
new file mode 100644
index 0000000000000000000000000000000000000000..3bfdac1766dd74fcf792ca664ae3b1fc18651163
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.js
@@ -0,0 +1,799 @@
+/* mescroll
+ * version 1.3.7
+ * 2021-04-12 wenju
+ * https://www.mescroll.com
+ */
+
+export default function MeScroll(options, isScrollBody) {
+ let me = this;
+ me.version = '1.3.7'; // mescroll版本号
+ me.options = options || {}; // 配置
+ me.isScrollBody = isScrollBody || false; // 滚动区域是否为原生页面滚动; 默认为scroll-view
+
+ me.isDownScrolling = false; // 是否在执行下拉刷新的回调
+ me.isUpScrolling = false; // 是否在执行上拉加载的回调
+ let hasDownCallback = me.options.down && me.options.down.callback; // 是否配置了down的callback
+
+ // 初始化下拉刷新
+ me.initDownScroll();
+ // 初始化上拉加载,则初始化
+ me.initUpScroll();
+
+ // 自动加载
+ setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
+ // 自动触发下拉刷新 (只有配置了down的callback才自动触发下拉刷新)
+ if ((me.optDown.use || me.optDown.native) && me.optDown.auto && hasDownCallback) {
+ if (me.optDown.autoShowLoading) {
+ me.triggerDownScroll(); // 显示下拉进度,执行下拉回调
+ } else {
+ me.optDown.callback && me.optDown.callback(me); // 不显示下拉进度,直接执行下拉回调
+ }
+ }
+ // 自动触发上拉加载
+ if(!me.isUpAutoLoad){ // 部分小程序(头条小程序)emit是异步, 会导致isUpAutoLoad判断有误, 先延时确保先执行down的callback,再执行up的callback
+ setTimeout(function(){
+ me.optUp.use && me.optUp.auto && !me.isUpAutoLoad && me.triggerUpScroll();
+ },100)
+ }
+ }, 30); // 需让me.optDown.inited和me.optUp.inited先执行
+}
+
+/* 配置参数:下拉刷新 */
+MeScroll.prototype.extendDownScroll = function(optDown) {
+ // 下拉刷新的配置
+ MeScroll.extend(optDown, {
+ use: true, // 是否启用下拉刷新; 默认true
+ auto: true, // 是否在初始化完毕之后自动执行下拉刷新的回调; 默认true
+ native: false, // 是否使用系统自带的下拉刷新; 默认false; 仅mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
+ autoShowLoading: false, // 如果设置auto=true(在初始化完毕之后自动执行下拉刷新的回调),那么是否显示下拉刷新的进度; 默认false
+ isLock: false, // 是否锁定下拉刷新,默认false;
+ offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
+ startTop: 100, // scroll-view快速滚动到顶部时,此时的scroll-top可能大于0, 此值用于控制最大的误差
+ inOffsetRate: 1, // 在列表顶部,下拉的距离小于offset时,改变下拉区域高度比例;值小于1且越接近0,高度变化越小,表现为越往下越难拉
+ outOffsetRate: 0.2, // 在列表顶部,下拉的距离大于offset时,改变下拉区域高度比例;值小于1且越接近0,高度变化越小,表现为越往下越难拉
+ bottomOffset: 20, // 当手指touchmove位置在距离body底部20px范围内的时候结束上拉刷新,避免Webview嵌套导致touchend事件不执行
+ minAngle: 45, // 向下滑动最少偏移的角度,取值区间 [0,90];默认45度,即向下滑动的角度大于45度则触发下拉;而小于45度,将不触发下拉,避免与左右滑动的轮播等组件冲突;
+ textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
+ textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
+ textLoading: '加载中 ...', // 加载中的提示文本
+ textSuccess: '加载成功', // 加载成功的文本
+ textErr: '加载失败', // 加载失败的文本
+ beforeEndDelay: 0, // 延时结束的时长 (显示加载成功/失败的时长, android小程序设置此项结束下拉会卡顿, 配置后请注意测试)
+ bgColor: "transparent", // 背景颜色 (建议在pages.json中再设置一下backgroundColorTop)
+ textColor: "gray", // 文本颜色 (当bgColor配置了颜色,而textColor未配置时,则textColor会默认为白色)
+ inited: null, // 下拉刷新初始化完毕的回调
+ inOffset: null, // 下拉的距离进入offset范围内那一刻的回调
+ outOffset: null, // 下拉的距离大于offset那一刻的回调
+ onMoving: null, // 下拉过程中的回调,滑动过程一直在执行; rate下拉区域当前高度与指定距离的比值(inOffset: rate<1; outOffset: rate>=1); downHight当前下拉区域的高度
+ beforeLoading: null, // 准备触发下拉刷新的回调: 如果return true,将不触发showLoading和callback回调; 常用来完全自定义下拉刷新, 参考案例【淘宝 v6.8.0】
+ showLoading: null, // 显示下拉刷新进度的回调
+ afterLoading: null, // 显示下拉刷新进度的回调之后,马上要执行的代码 (如: 在wxs中使用)
+ beforeEndDownScroll: null, // 准备结束下拉的回调. 返回结束下拉的延时执行时间,默认0ms; 常用于结束下拉之前再显示另外一小段动画,才去隐藏下拉刷新的场景, 参考案例【dotJump】
+ endDownScroll: null, // 结束下拉刷新的回调
+ afterEndDownScroll: null, // 结束下拉刷新的回调,马上要执行的代码 (如: 在wxs中使用)
+ callback: function(mescroll) {
+ // 下拉刷新的回调;默认重置上拉加载列表为第一页
+ mescroll.resetUpScroll();
+ }
+ })
+}
+
+/* 配置参数:上拉加载 */
+MeScroll.prototype.extendUpScroll = function(optUp) {
+ // 上拉加载的配置
+ MeScroll.extend(optUp, {
+ use: true, // 是否启用上拉加载; 默认true
+ auto: true, // 是否在初始化完毕之后自动执行上拉加载的回调; 默认true
+ isLock: false, // 是否锁定上拉加载,默认false;
+ isBoth: true, // 上拉加载时,如果滑动到列表顶部是否可以同时触发下拉刷新;默认true,两者可同时触发;
+ callback: null, // 上拉加载的回调;function(page,mescroll){ }
+ page: {
+ num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
+ size: 10, // 每页数据的数量
+ time: null // 加载第一页数据服务器返回的时间; 防止用户翻页时,后台新增了数据从而导致下一页数据重复;
+ },
+ noMoreSize: 5, // 如果列表已无数据,可设置列表的总数量要大于等于5条才显示无更多数据;避免列表数据过少(比如只有一条数据),显示无更多数据会不好看
+ offset: 150, // 距底部多远时,触发upCallback,仅mescroll-uni生效 ( mescroll-body配置的是pages.json的 onReachBottomDistance )
+ textLoading: '加载中 ...', // 加载中的提示文本
+ textNoMore: '-- END --', // 没有更多数据的提示文本
+ bgColor: "transparent", // 背景颜色 (建议在pages.json中再设置一下backgroundColorBottom)
+ textColor: "gray", // 文本颜色 (当bgColor配置了颜色,而textColor未配置时,则textColor会默认为白色)
+ inited: null, // 初始化完毕的回调
+ showLoading: null, // 显示加载中的回调
+ showNoMore: null, // 显示无更多数据的回调
+ hideUpScroll: null, // 隐藏上拉加载的回调
+ errDistance: 60, // endErr的时候需往上滑动一段距离,使其往下滑动时再次触发onReachBottom,仅mescroll-body生效
+ toTop: {
+ // 回到顶部按钮,需配置src才显示
+ src: null, // 图片路径,默认null (绝对路径或网络图)
+ offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000
+ duration: 300, // 回到顶部的动画时长,默认300ms (当值为0或300则使用系统自带回到顶部,更流畅; 其他值则通过step模拟,部分机型可能不够流畅,所以非特殊情况不建议修改此项)
+ btnClick: null, // 点击按钮的回调
+ onShow: null, // 是否显示的回调
+ zIndex: 9990, // fixed定位z-index值
+ left: null, // 到左边的距离, 默认null. 此项有值时,right不生效. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
+ right: 20, // 到右边的距离, 默认20 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
+ bottom: 120, // 到底部的距离, 默认120 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
+ safearea: false, // bottom的偏移量是否加上底部安全区的距离, 默认false, 需要适配iPhoneX时使用 (具体的界面如果不配置此项,则取本vue的safearea值)
+ width: 72, // 回到顶部图标的宽度, 默认72 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
+ radius: "50%" // 圆角, 默认"50%" (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
+ },
+ empty: {
+ use: true, // 是否显示空布局
+ icon: null, // 图标路径
+ tip: '~ 暂无相关数据 ~', // 提示
+ btnText: '', // 按钮
+ btnClick: null, // 点击按钮的回调
+ onShow: null, // 是否显示的回调
+ fixed: false, // 是否使用fixed定位,默认false; 配置fixed为true,以下的top和zIndex才生效 (transform会使fixed失效,最终会降级为absolute)
+ top: "100rpx", // fixed定位的top值 (完整的单位值,如 "10%"; "100rpx")
+ zIndex: 99 // fixed定位z-index值
+ },
+ onScroll: false // 是否监听滚动事件
+ })
+}
+
+/* 配置参数 */
+MeScroll.extend = function(userOption, defaultOption) {
+ if (!userOption) return defaultOption;
+ for (let key in defaultOption) {
+ if (userOption[key] == null) {
+ let def = defaultOption[key];
+ if (def != null && typeof def === 'object') {
+ userOption[key] = MeScroll.extend({}, def); // 深度匹配
+ } else {
+ userOption[key] = def;
+ }
+ } else if (typeof userOption[key] === 'object') {
+ MeScroll.extend(userOption[key], defaultOption[key]); // 深度匹配
+ }
+ }
+ return userOption;
+}
+
+/* 简单判断是否配置了颜色 (非透明,非白色) */
+MeScroll.prototype.hasColor = function(color) {
+ if(!color) return false;
+ let c = color.toLowerCase();
+ return c != "#fff" && c != "#ffffff" && c != "transparent" && c != "white"
+}
+
+/* -------初始化下拉刷新------- */
+MeScroll.prototype.initDownScroll = function() {
+ let me = this;
+ // 配置参数
+ me.optDown = me.options.down || {};
+ if(!me.optDown.textColor && me.hasColor(me.optDown.bgColor)) me.optDown.textColor = "#fff"; // 当bgColor有值且textColor未设置,则textColor默认白色
+ me.extendDownScroll(me.optDown);
+
+ // 如果是mescroll-body且配置了native,则禁止自定义的下拉刷新
+ if(me.isScrollBody && me.optDown.native){
+ me.optDown.use = false
+ }else{
+ me.optDown.native = false // 仅mescroll-body支持,mescroll-uni不支持
+ }
+
+ me.downHight = 0; // 下拉区域的高度
+
+ // 在页面中加入下拉布局
+ if (me.optDown.use && me.optDown.inited) {
+ // 初始化完毕的回调
+ setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
+ me.optDown.inited(me);
+ }, 0)
+ }
+}
+
+/* 列表touchstart事件 */
+MeScroll.prototype.touchstartEvent = function(e) {
+ if (!this.optDown.use) return;
+
+ this.startPoint = this.getPoint(e); // 记录起点
+ this.startTop = this.getScrollTop(); // 记录此时的滚动条位置
+ this.startAngle = 0; // 初始角度
+ this.lastPoint = this.startPoint; // 重置上次move的点
+ this.maxTouchmoveY = this.getBodyHeight() - this.optDown.bottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况)
+ this.inTouchend = false; // 标记不是touchend
+}
+
+/* 列表touchmove事件 */
+MeScroll.prototype.touchmoveEvent = function(e) {
+ if (!this.optDown.use) return;
+ let me = this;
+
+ let scrollTop = me.getScrollTop(); // 当前滚动条的距离
+ let curPoint = me.getPoint(e); // 当前点
+
+ let moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
+
+ // 向下拉 && 在顶部
+ // mescroll-body,直接判定在顶部即可
+ // scroll-view在滚动时不会触发touchmove,当触顶/底/左/右时,才会触发touchmove
+ // scroll-view滚动到顶部时,scrollTop不一定为0,也有可能大于0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等
+ if (moveY > 0 && (
+ (me.isScrollBody && scrollTop <= 0)
+ ||
+ (!me.isScrollBody && (scrollTop <= 0 || (scrollTop <= me.optDown.startTop && scrollTop === me.startTop)) )
+ )) {
+ // 可下拉的条件
+ if (!me.inTouchend && !me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling &&
+ me.optUp.isBoth))) {
+
+ // 下拉的初始角度是否在配置的范围内
+ if(!me.startAngle) me.startAngle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90]
+ if (me.startAngle < me.optDown.minAngle) return; // 如果小于配置的角度,则不往下执行下拉刷新
+
+ // 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发
+ if (me.maxTouchmoveY > 0 && curPoint.y >= me.maxTouchmoveY) {
+ me.inTouchend = true; // 标记执行touchend
+ me.touchendEvent(); // 提前触发touchend
+ return;
+ }
+
+ me.preventDefault(e); // 阻止默认事件
+
+ let diff = curPoint.y - me.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上)
+
+ // 下拉距离 < 指定距离
+ if (me.downHight < me.optDown.offset) {
+ if (me.movetype !== 1) {
+ me.movetype = 1; // 加入标记,保证只执行一次
+ me.isDownEndSuccess = null; // 重置是否加载成功的状态 (wxs执行的是wxs.wxs)
+ me.optDown.inOffset && me.optDown.inOffset(me); // 进入指定距离范围内那一刻的回调,只执行一次
+ me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
+ }
+ me.downHight += diff * me.optDown.inOffsetRate; // 越往下,高度变化越小
+
+ // 指定距离 <= 下拉距离
+ } else {
+ if (me.movetype !== 2) {
+ me.movetype = 2; // 加入标记,保证只执行一次
+ me.optDown.outOffset && me.optDown.outOffset(me); // 下拉超过指定距离那一刻的回调,只执行一次
+ me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
+ }
+ if (diff > 0) { // 向下拉
+ me.downHight += diff * me.optDown.outOffsetRate; // 越往下,高度变化越小
+ } else { // 向上收
+ me.downHight += diff; // 向上收回高度,则向上滑多少收多少高度
+ }
+ }
+
+ me.downHight = Math.round(me.downHight) // 取整
+ let rate = me.downHight / me.optDown.offset; // 下拉区域当前高度与指定距离的比值
+ me.optDown.onMoving && me.optDown.onMoving(me, rate, me.downHight); // 下拉过程中的回调,一直在执行
+ }
+ }
+
+ me.lastPoint = curPoint; // 记录本次移动的点
+}
+
+/* 列表touchend事件 */
+MeScroll.prototype.touchendEvent = function(e) {
+ if (!this.optDown.use) return;
+ // 如果下拉区域高度已改变,则需重置回来
+ if (this.isMoveDown) {
+ if (this.downHight >= this.optDown.offset) {
+ // 符合触发刷新的条件
+ this.triggerDownScroll();
+ } else {
+ // 不符合的话 则重置
+ this.downHight = 0;
+ this.endDownScrollCall(this);
+ }
+ this.movetype = 0;
+ this.isMoveDown = false;
+ } else if (!this.isScrollBody && this.getScrollTop() === this.startTop) { // scroll-view到顶/左/右/底的滑动事件
+ let isScrollUp = this.getPoint(e).y - this.startPoint.y < 0; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
+ // 上滑
+ if (isScrollUp) {
+ // 需检查滑动的角度
+ let angle = this.getAngle(this.getPoint(e), this.startPoint); // 两点之间的角度,区间 [0,90]
+ if (angle > 80) {
+ // 检查并触发上拉
+ this.triggerUpScroll(true);
+ }
+ }
+ }
+}
+
+/* 根据点击滑动事件获取第一个手指的坐标 */
+MeScroll.prototype.getPoint = function(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
+ }
+ } else if (e.changedTouches && e.changedTouches[0]) {
+ return {
+ x: e.changedTouches[0].pageX,
+ y: e.changedTouches[0].pageY
+ }
+ } else {
+ return {
+ x: e.clientX,
+ y: e.clientY
+ }
+ }
+}
+
+/* 计算两点之间的角度: 区间 [0,90]*/
+MeScroll.prototype.getAngle = function(p1, p2) {
+ let x = Math.abs(p1.x - p2.x);
+ let y = Math.abs(p1.y - p2.y);
+ let z = Math.sqrt(x * x + y * y);
+ let angle = 0;
+ if (z !== 0) {
+ angle = Math.asin(y / z) / Math.PI * 180;
+ }
+ return angle
+}
+
+/* 触发下拉刷新 */
+MeScroll.prototype.triggerDownScroll = function() {
+ if (this.optDown.beforeLoading && this.optDown.beforeLoading(this)) {
+ //return true则处于完全自定义状态
+ } else {
+ this.showDownScroll(); // 下拉刷新中...
+ !this.optDown.native && this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据
+ }
+}
+
+/* 显示下拉进度布局 */
+MeScroll.prototype.showDownScroll = function() {
+ this.isDownScrolling = true; // 标记下拉中
+ if (this.optDown.native) {
+ uni.startPullDownRefresh(); // 系统自带的下拉刷新
+ this.showDownLoadingCall(0); // 仍触发showLoading,因为上拉加载用到
+ } else{
+ this.downHight = this.optDown.offset; // 更新下拉区域高度
+ this.showDownLoadingCall(this.downHight); // 下拉刷新中...
+ }
+}
+
+MeScroll.prototype.showDownLoadingCall = function(downHight) {
+ this.optDown.showLoading && this.optDown.showLoading(this, downHight); // 下拉刷新中...
+ this.optDown.afterLoading && this.optDown.afterLoading(this, downHight); // 下拉刷新中...触发之后马上要执行的代码
+}
+
+/* 显示系统自带的下拉刷新时需要处理的业务 */
+MeScroll.prototype.onPullDownRefresh = function() {
+ this.isDownScrolling = true; // 标记下拉中
+ this.showDownLoadingCall(0); // 仍触发showLoading,因为上拉加载用到
+ this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据
+}
+
+/* 结束下拉刷新 */
+MeScroll.prototype.endDownScroll = function() {
+ if (this.optDown.native) { // 结束原生下拉刷新
+ this.isDownScrolling = false;
+ this.endDownScrollCall(this);
+ uni.stopPullDownRefresh();
+ return
+ }
+ let me = this;
+ // 结束下拉刷新的方法
+ let endScroll = function() {
+ me.downHight = 0;
+ me.isDownScrolling = false;
+ me.endDownScrollCall(me);
+ if(!me.isScrollBody){
+ me.setScrollHeight(0) // scroll-view重置滚动区域,使数据不满屏时仍可检查触发翻页
+ me.scrollTo(0,0) // scroll-view需重置滚动条到顶部,避免startTop大于0时,对下拉刷新的影响
+ }
+ }
+ // 结束下拉刷新时的回调
+ let delay = 0;
+ if (me.optDown.beforeEndDownScroll) {
+ delay = me.optDown.beforeEndDownScroll(me); // 结束下拉刷新的延时,单位ms
+ if(me.isDownEndSuccess == null) delay = 0; // 没有执行加载中,则不延时
+ }
+ if (typeof delay === 'number' && delay > 0) {
+ setTimeout(endScroll, delay);
+ } else {
+ endScroll();
+ }
+}
+
+MeScroll.prototype.endDownScrollCall = function() {
+ this.optDown.endDownScroll && this.optDown.endDownScroll(this);
+ this.optDown.afterEndDownScroll && this.optDown.afterEndDownScroll(this);
+}
+
+/* 锁定下拉刷新:isLock=ture,null锁定;isLock=false解锁 */
+MeScroll.prototype.lockDownScroll = function(isLock) {
+ if (isLock == null) isLock = true;
+ this.optDown.isLock = isLock;
+}
+
+/* 锁定上拉加载:isLock=ture,null锁定;isLock=false解锁 */
+MeScroll.prototype.lockUpScroll = function(isLock) {
+ if (isLock == null) isLock = true;
+ this.optUp.isLock = isLock;
+}
+
+/* -------初始化上拉加载------- */
+MeScroll.prototype.initUpScroll = function() {
+ let me = this;
+ // 配置参数
+ me.optUp = me.options.up || {use: false}
+ if(!me.optUp.textColor && me.hasColor(me.optUp.bgColor)) me.optUp.textColor = "#fff"; // 当bgColor有值且textColor未设置,则textColor默认白色
+ me.extendUpScroll(me.optUp);
+
+ if (me.optUp.use === false) return; // 配置不使用上拉加载时,则不初始化上拉布局
+ me.optUp.hasNext = true; // 如果使用上拉,则默认有下一页
+ me.startNum = me.optUp.page.num + 1; // 记录page开始的页码
+
+ // 初始化完毕的回调
+ if (me.optUp.inited) {
+ setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
+ me.optUp.inited(me);
+ }, 0)
+ }
+}
+
+/*滚动到底部的事件 (仅mescroll-body生效)*/
+MeScroll.prototype.onReachBottom = function() {
+ if (this.isScrollBody && !this.isUpScrolling) { // 只能支持下拉刷新的时候同时可以触发上拉加载,否则滚动到底部就需要上滑一点才能触发onReachBottom
+ if (!this.optUp.isLock && this.optUp.hasNext) {
+ this.triggerUpScroll();
+ }
+ }
+}
+
+/*列表滚动事件 (仅mescroll-body生效)*/
+MeScroll.prototype.onPageScroll = function(e) {
+ if (!this.isScrollBody) return;
+
+ // 更新滚动条的位置 (主要用于判断下拉刷新时,滚动条是否在顶部)
+ this.setScrollTop(e.scrollTop);
+
+ // 顶部按钮的显示隐藏
+ if (e.scrollTop >= this.optUp.toTop.offset) {
+ this.showTopBtn();
+ } else {
+ this.hideTopBtn();
+ }
+}
+
+/*列表滚动事件*/
+MeScroll.prototype.scroll = function(e, onScroll) {
+ // 更新滚动条的位置
+ this.setScrollTop(e.scrollTop);
+ // 更新滚动内容高度
+ this.setScrollHeight(e.scrollHeight);
+
+ // 向上滑还是向下滑动
+ if (this.preScrollY == null) this.preScrollY = 0;
+ this.isScrollUp = e.scrollTop - this.preScrollY > 0;
+ this.preScrollY = e.scrollTop;
+
+ // 上滑 && 检查并触发上拉
+ this.isScrollUp && this.triggerUpScroll(true);
+
+ // 顶部按钮的显示隐藏
+ if (e.scrollTop >= this.optUp.toTop.offset) {
+ this.showTopBtn();
+ } else {
+ this.hideTopBtn();
+ }
+
+ // 滑动监听
+ this.optUp.onScroll && onScroll && onScroll()
+}
+
+/* 触发上拉加载 */
+MeScroll.prototype.triggerUpScroll = function(isCheck) {
+ if (!this.isUpScrolling && this.optUp.use && this.optUp.callback) {
+ // 是否校验在底部; 默认不校验
+ if (isCheck === true) {
+ let canUp = false;
+ // 还有下一页 && 没有锁定 && 不在下拉中
+ if (this.optUp.hasNext && !this.optUp.isLock && !this.isDownScrolling) {
+ if (this.getScrollBottom() <= this.optUp.offset) { // 到底部
+ canUp = true; // 标记可上拉
+ }
+ }
+ if (canUp === false) return;
+ }
+ this.showUpScroll(); // 上拉加载中...
+ this.optUp.page.num++; // 预先加一页,如果失败则减回
+ this.isUpAutoLoad = true; // 标记上拉已经自动执行过,避免初始化时多次触发上拉回调
+ this.num = this.optUp.page.num; // 把最新的页数赋值在mescroll上,避免对page的影响
+ this.size = this.optUp.page.size; // 把最新的页码赋值在mescroll上,避免对page的影响
+ this.time = this.optUp.page.time; // 把最新的页码赋值在mescroll上,避免对page的影响
+ this.optUp.callback(this); // 执行回调,联网加载数据
+ }
+}
+
+/* 显示上拉加载中 */
+MeScroll.prototype.showUpScroll = function() {
+ this.isUpScrolling = true; // 标记上拉加载中
+ this.optUp.showLoading && this.optUp.showLoading(this); // 回调
+}
+
+/* 显示上拉无更多数据 */
+MeScroll.prototype.showNoMore = function() {
+ this.optUp.hasNext = false; // 标记无更多数据
+ this.optUp.showNoMore && this.optUp.showNoMore(this); // 回调
+}
+
+/* 隐藏上拉区域**/
+MeScroll.prototype.hideUpScroll = function() {
+ this.optUp.hideUpScroll && this.optUp.hideUpScroll(this); // 回调
+}
+
+/* 结束上拉加载 */
+MeScroll.prototype.endUpScroll = function(isShowNoMore) {
+ if (isShowNoMore != null) { // isShowNoMore=null,不处理下拉状态,下拉刷新的时候调用
+ if (isShowNoMore) {
+ this.showNoMore(); // isShowNoMore=true,显示无更多数据
+ } else {
+ this.hideUpScroll(); // isShowNoMore=false,隐藏上拉加载
+ }
+ }
+ this.isUpScrolling = false; // 标记结束上拉加载
+}
+
+/* 重置上拉加载列表为第一页
+ *isShowLoading 是否显示进度布局;
+ * 1.默认null,不传参,则显示上拉加载的进度布局
+ * 2.传参true, 则显示下拉刷新的进度布局
+ * 3.传参false,则不显示上拉和下拉的进度 (常用于静默更新列表数据)
+ */
+MeScroll.prototype.resetUpScroll = function(isShowLoading) {
+ if (this.optUp && this.optUp.use) {
+ let page = this.optUp.page;
+ this.prePageNum = page.num; // 缓存重置前的页码,加载失败可退回
+ this.prePageTime = page.time; // 缓存重置前的时间,加载失败可退回
+ page.num = this.startNum; // 重置为第一页
+ page.time = null; // 重置时间为空
+ if (!this.isDownScrolling && isShowLoading !== false) { // 如果不是下拉刷新触发的resetUpScroll并且不配置列表静默更新,则显示进度;
+ if (isShowLoading == null) {
+ this.removeEmpty(); // 移除空布局
+ this.showUpScroll(); // 不传参,默认显示上拉加载的进度布局
+ } else {
+ this.showDownScroll(); // 传true,显示下拉刷新的进度布局,不清空列表
+ }
+ }
+ this.isUpAutoLoad = true; // 标记上拉已经自动执行过,避免初始化时多次触发上拉回调
+ this.num = page.num; // 把最新的页数赋值在mescroll上,避免对page的影响
+ this.size = page.size; // 把最新的页码赋值在mescroll上,避免对page的影响
+ this.time = page.time; // 把最新的页码赋值在mescroll上,避免对page的影响
+ this.optUp.callback && this.optUp.callback(this); // 执行上拉回调
+ }
+}
+
+/* 设置page.num的值 */
+MeScroll.prototype.setPageNum = function(num) {
+ this.optUp.page.num = num - 1;
+}
+
+/* 设置page.size的值 */
+MeScroll.prototype.setPageSize = function(size) {
+ this.optUp.page.size = size;
+}
+
+/* 联网回调成功,结束下拉刷新和上拉加载
+ * dataSize: 当前页的数据量(必传)
+ * totalPage: 总页数(必传)
+ * systime: 服务器时间 (可空)
+ */
+MeScroll.prototype.endByPage = function(dataSize, totalPage, systime) {
+ let hasNext;
+ if (this.optUp.use && totalPage != null) hasNext = this.optUp.page.num < totalPage; // 是否还有下一页
+ this.endSuccess(dataSize, hasNext, systime);
+}
+
+/* 联网回调成功,结束下拉刷新和上拉加载
+ * dataSize: 当前页的数据量(必传)
+ * totalSize: 列表所有数据总数量(必传)
+ * systime: 服务器时间 (可空)
+ */
+MeScroll.prototype.endBySize = function(dataSize, totalSize, systime) {
+ let hasNext;
+ if (this.optUp.use && totalSize != null) {
+ let loadSize = (this.optUp.page.num - 1) * this.optUp.page.size + dataSize; // 已加载的数据总数
+ hasNext = loadSize < totalSize; // 是否还有下一页
+ }
+ this.endSuccess(dataSize, hasNext, systime);
+}
+
+/* 联网回调成功,结束下拉刷新和上拉加载
+ * dataSize: 当前页的数据个数(不是所有页的数据总和),用于上拉加载判断是否还有下一页.如果不传,则会判断还有下一页
+ * hasNext: 是否还有下一页,布尔类型;用来解决这个小问题:比如列表共有20条数据,每页加载10条,共2页.如果只根据dataSize判断,则需翻到第三页才会知道无更多数据,如果传了hasNext,则翻到第二页即可显示无更多数据.
+ * systime: 服务器时间(可空);用来解决这个小问题:当准备翻下一页时,数据库新增了几条记录,此时翻下一页,前面的几条数据会和上一页的重复;这里传入了systime,那么upCallback的page.time就会有值,把page.time传给服务器,让后台过滤新加入的那几条记录
+ */
+MeScroll.prototype.endSuccess = function(dataSize, hasNext, systime) {
+ let me = this;
+ // 结束下拉刷新
+ if (me.isDownScrolling) {
+ me.isDownEndSuccess = true
+ me.endDownScroll();
+ }
+
+ // 结束上拉加载
+ if (me.optUp.use) {
+ let isShowNoMore; // 是否已无更多数据
+ if (dataSize != null) {
+ let pageNum = me.optUp.page.num; // 当前页码
+ let pageSize = me.optUp.page.size; // 每页长度
+ // 如果是第一页
+ if (pageNum === 1) {
+ if (systime) me.optUp.page.time = systime; // 设置加载列表数据第一页的时间
+ }
+ if (dataSize < pageSize || hasNext === false) {
+ // 返回的数据不满一页时,则说明已无更多数据
+ me.optUp.hasNext = false;
+ if (dataSize === 0 && pageNum === 1) {
+ // 如果第一页无任何数据且配置了空布局
+ isShowNoMore = false;
+ me.showEmpty();
+ } else {
+ // 总列表数少于配置的数量,则不显示无更多数据
+ let allDataSize = (pageNum - 1) * pageSize + dataSize;
+ if (allDataSize < me.optUp.noMoreSize) {
+ isShowNoMore = false;
+ } else {
+ isShowNoMore = true;
+ }
+ me.removeEmpty(); // 移除空布局
+ }
+ } else {
+ // 还有下一页
+ isShowNoMore = false;
+ me.optUp.hasNext = true;
+ me.removeEmpty(); // 移除空布局
+ }
+ }
+
+ // 隐藏上拉
+ me.endUpScroll(isShowNoMore);
+ }
+}
+
+/* 回调失败,结束下拉刷新和上拉加载 */
+MeScroll.prototype.endErr = function(errDistance) {
+ // 结束下拉,回调失败重置回原来的页码和时间
+ if (this.isDownScrolling) {
+ this.isDownEndSuccess = false
+ let page = this.optUp.page;
+ if (page && this.prePageNum) {
+ page.num = this.prePageNum;
+ page.time = this.prePageTime;
+ }
+ this.endDownScroll();
+ }
+ // 结束上拉,回调失败重置回原来的页码
+ if (this.isUpScrolling) {
+ this.optUp.page.num--;
+ this.endUpScroll(false);
+ // 如果是mescroll-body,则需往回滚一定距离
+ if(this.isScrollBody && errDistance !== 0){ // 不处理0
+ if(!errDistance) errDistance = this.optUp.errDistance; // 不传,则取默认
+ this.scrollTo(this.getScrollTop() - errDistance, 0) // 往上回滚的距离
+ }
+ }
+}
+
+/* 显示空布局 */
+MeScroll.prototype.showEmpty = function() {
+ this.optUp.empty.use && this.optUp.empty.onShow && this.optUp.empty.onShow(true)
+}
+
+/* 移除空布局 */
+MeScroll.prototype.removeEmpty = function() {
+ this.optUp.empty.use && this.optUp.empty.onShow && this.optUp.empty.onShow(false)
+}
+
+/* 显示回到顶部的按钮 */
+MeScroll.prototype.showTopBtn = function() {
+ if (!this.topBtnShow) {
+ this.topBtnShow = true;
+ this.optUp.toTop.onShow && this.optUp.toTop.onShow(true);
+ }
+}
+
+/* 隐藏回到顶部的按钮 */
+MeScroll.prototype.hideTopBtn = function() {
+ if (this.topBtnShow) {
+ this.topBtnShow = false;
+ this.optUp.toTop.onShow && this.optUp.toTop.onShow(false);
+ }
+}
+
+/* 获取滚动条的位置 */
+MeScroll.prototype.getScrollTop = function() {
+ return this.scrollTop || 0
+}
+
+/* 记录滚动条的位置 */
+MeScroll.prototype.setScrollTop = function(y) {
+ this.scrollTop = y;
+}
+
+/* 滚动到指定位置 */
+MeScroll.prototype.scrollTo = function(y, t) {
+ this.myScrollTo && this.myScrollTo(y, t) // scrollview需自定义回到顶部方法
+}
+
+/* 自定义scrollTo */
+MeScroll.prototype.resetScrollTo = function(myScrollTo) {
+ this.myScrollTo = myScrollTo
+}
+
+/* 滚动条到底部的距离 */
+MeScroll.prototype.getScrollBottom = function() {
+ return this.getScrollHeight() - this.getClientHeight() - this.getScrollTop()
+}
+
+/* 计步器
+ star: 开始值
+ end: 结束值
+ callback(step,timer): 回调step值,计步器timer,可自行通过window.clearInterval(timer)结束计步器;
+ t: 计步时长,传0则直接回调end值;不传则默认300ms
+ rate: 周期;不传则默认30ms计步一次
+ * */
+MeScroll.prototype.getStep = function(star, end, callback, t, rate) {
+ let diff = end - star; // 差值
+ if (t === 0 || diff === 0) {
+ callback && callback(end);
+ return;
+ }
+ t = t || 300; // 时长 300ms
+ rate = rate || 30; // 周期 30ms
+ let count = t / rate; // 次数
+ let step = diff / count; // 步长
+ let i = 0; // 计数
+ let timer = setInterval(function() {
+ if (i < count - 1) {
+ star += step;
+ callback && callback(star, timer);
+ i++;
+ } else {
+ callback && callback(end, timer); // 最后一次直接设置end,避免计算误差
+ clearInterval(timer);
+ }
+ }, rate);
+}
+
+/* 滚动容器的高度 */
+MeScroll.prototype.getClientHeight = function(isReal) {
+ let h = this.clientHeight || 0
+ if (h === 0 && isReal !== true) { // 未获取到容器的高度,可临时取body的高度 (可能会有误差)
+ h = this.getBodyHeight()
+ }
+ return h
+}
+MeScroll.prototype.setClientHeight = function(h) {
+ this.clientHeight = h;
+}
+
+/* 滚动内容的高度 */
+MeScroll.prototype.getScrollHeight = function() {
+ return this.scrollHeight || 0;
+}
+MeScroll.prototype.setScrollHeight = function(h) {
+ this.scrollHeight = h;
+}
+
+/* body的高度 */
+MeScroll.prototype.getBodyHeight = function() {
+ return this.bodyHeight || 0;
+}
+MeScroll.prototype.setBodyHeight = function(h) {
+ this.bodyHeight = h;
+}
+
+/* 阻止浏览器默认滚动事件 */
+MeScroll.prototype.preventDefault = function(e) {
+ // 小程序不支持e.preventDefault, 已在wxs中禁止
+ // app的bounce只能通过配置pages.json的style.app-plus.bounce为"none"来禁止, 或使用renderjs禁止
+ // cancelable:是否可以被禁用; defaultPrevented:是否已经被禁用
+ if (e && e.cancelable && !e.defaultPrevented) e.preventDefault()
+}
\ No newline at end of file
diff --git a/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.vue b/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.vue
new file mode 100644
index 0000000000000000000000000000000000000000..cb5d0265607315ee148a7c19e4ddecd797074274
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.vue
@@ -0,0 +1,480 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{downText}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ mescroll.optUp.textLoading }}
+
+
+ {{ mescroll.optUp.textNoMore }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-comp.js b/uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-comp.js
new file mode 100644
index 0000000000000000000000000000000000000000..abe7cd7f27a11bdb465cd629e8493836bb98a98a
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-comp.js
@@ -0,0 +1,47 @@
+/**
+ * mescroll-body写在子组件时,需通过mescroll的mixins补充子组件缺少的生命周期
+ */
+const MescrollCompMixin = {
+ // 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件 (一级)
+ onPageScroll(e) {
+ this.handlePageScroll(e)
+ },
+ onReachBottom() {
+ this.handleReachBottom()
+ },
+ // 当down的native: true时, 还需传递此方法进到子组件
+ onPullDownRefresh(){
+ this.handlePullDownRefresh()
+ },
+ data() {
+ return {
+ mescroll: { // mescroll-body写在子子子...组件的情况 (多级)
+ onPageScroll: e=>{
+ this.handlePageScroll(e)
+ },
+ onReachBottom: ()=>{
+ this.handleReachBottom()
+ },
+ onPullDownRefresh: ()=>{
+ this.handlePullDownRefresh()
+ }
+ }
+ }
+ },
+ methods:{
+ handlePageScroll(e){
+ let item = this.$refs["mescrollItem"];
+ if(item && item.mescroll) item.mescroll.onPageScroll(e);
+ },
+ handleReachBottom(){
+ let item = this.$refs["mescrollItem"];
+ if(item && item.mescroll) item.mescroll.onReachBottom();
+ },
+ handlePullDownRefresh(){
+ let item = this.$refs["mescrollItem"];
+ if(item && item.mescroll) item.mescroll.onPullDownRefresh();
+ }
+ }
+}
+
+export default MescrollCompMixin;
diff --git a/uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-more-item.js b/uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-more-item.js
new file mode 100644
index 0000000000000000000000000000000000000000..96ca9bffa5767bae3a494fd2d2636e3009162c11
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-more-item.js
@@ -0,0 +1,57 @@
+/**
+ * mescroll-more-item的mixins, 仅在多个 mescroll-body 写在子组件时使用 (参考 mescroll-more 案例)
+ */
+const MescrollMoreItemMixin = {
+ // 支付宝小程序不支持props的mixin,需写在具体的页面中
+ // #ifndef MP-ALIPAY || MP-DINGTALK
+ props:{
+ i: Number, // 每个tab页的专属下标
+ index: { // 当前tab的下标
+ type: Number,
+ default(){
+ return 0
+ }
+ }
+ },
+ // #endif
+ data() {
+ return {
+ downOption:{
+ auto:false // 不自动加载
+ },
+ upOption:{
+ auto:false // 不自动加载
+ },
+ isInit: false // 当前tab是否已初始化
+ }
+ },
+ watch:{
+ // 监听下标的变化
+ index(val){
+ if (this.i === val && !this.isInit) this.mescrollTrigger()
+ }
+ },
+ methods: {
+ // mescroll组件初始化的回调,可获取到mescroll对象 (覆盖mescroll-mixins.js的mescrollInit, 为了标记isInit)
+ mescrollInit(mescroll) {
+ this.mescroll = mescroll;
+ // 自动加载当前tab的数据
+ if(this.i === this.index){
+ this.mescrollTrigger()
+ }
+ },
+ // 主动触发加载
+ mescrollTrigger(){
+ this.isInit = true; // 标记为true
+ if (this.mescroll) {
+ if (this.mescroll.optDown.use) {
+ this.mescroll.triggerDownScroll();
+ } else{
+ this.mescroll.triggerUpScroll();
+ }
+ }
+ }
+ }
+}
+
+export default MescrollMoreItemMixin;
diff --git a/uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-more.js b/uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-more.js
new file mode 100644
index 0000000000000000000000000000000000000000..16b47d89479ff957c98fa39affdc350706fc40b8
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-more.js
@@ -0,0 +1,77 @@
+/**
+ * mescroll-body写在子组件时, 需通过mescroll的mixins补充子组件缺少的生命周期
+ */
+const MescrollMoreMixin = {
+ data() {
+ return {
+ tabIndex: 0, // 当前tab下标
+ mescroll: { // mescroll-body写在子子子...组件的情况 (多级)
+ onPageScroll: e=>{
+ this.handlePageScroll(e)
+ },
+ onReachBottom: ()=>{
+ this.handleReachBottom()
+ },
+ onPullDownRefresh: ()=>{
+ this.handlePullDownRefresh()
+ }
+ }
+ }
+ },
+ // 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件
+ onPageScroll(e) {
+ this.handlePageScroll(e)
+ },
+ onReachBottom() {
+ this.handleReachBottom()
+ },
+ // 当down的native: true时, 还需传递此方法进到子组件
+ onPullDownRefresh(){
+ this.handlePullDownRefresh()
+ },
+ methods:{
+ handlePageScroll(e){
+ let mescroll = this.getMescroll(this.tabIndex);
+ mescroll && mescroll.onPageScroll(e);
+ },
+ handleReachBottom(){
+ let mescroll = this.getMescroll(this.tabIndex);
+ mescroll && mescroll.onReachBottom();
+ },
+ handlePullDownRefresh(){
+ let mescroll = this.getMescroll(this.tabIndex);
+ mescroll && mescroll.onPullDownRefresh();
+ },
+ // 根据下标获取对应子组件的mescroll
+ getMescroll(i){
+ if(!this.mescrollItems) this.mescrollItems = [];
+ if(!this.mescrollItems[i]) {
+ // v-for中的refs
+ let vForItem = this.$refs["mescrollItem"];
+ if(vForItem){
+ this.mescrollItems[i] = vForItem[i]
+ }else{
+ // 普通的refs,不可重复
+ this.mescrollItems[i] = this.$refs["mescrollItem"+i];
+ }
+ }
+ let item = this.mescrollItems[i]
+ return item ? item.mescroll : null
+ },
+ // 切换tab,恢复滚动条位置
+ tabChange(i){
+ let mescroll = this.getMescroll(i);
+ if(mescroll){
+ // 恢复上次滚动条的位置
+ let y = mescroll.getScrollTop()
+ mescroll.scrollTo(y, 0)
+ // 再次恢复上次滚动条的位置, 确保元素已渲染
+ setTimeout(()=>{
+ mescroll.scrollTo(y, 0)
+ },30)
+ }
+ }
+ }
+}
+
+export default MescrollMoreMixin;
diff --git a/uni_modules/mescroll-uni/components/mescroll-uni/wxs/mixins.js b/uni_modules/mescroll-uni/components/mescroll-uni/wxs/mixins.js
new file mode 100644
index 0000000000000000000000000000000000000000..34ffa3c2607337e9592d11c73dd558f23c174e71
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-uni/wxs/mixins.js
@@ -0,0 +1,109 @@
+// 定义在wxs (含renderjs) 逻辑层的数据和方法, 与视图层相互通信
+const WxsMixin = {
+ data() {
+ return {
+ // 传入wxs视图层的数据 (响应式)
+ wxsProp: {
+ optDown:{}, // 下拉刷新的配置
+ scrollTop:0, // 滚动条的距离
+ bodyHeight:0, // body的高度
+ isDownScrolling:false, // 是否正在下拉刷新中
+ isUpScrolling:false, // 是否正在上拉加载中
+ isScrollBody:true, // 是否为mescroll-body滚动
+ isUpBoth:true, // 上拉加载时,是否同时可以下拉刷新
+ t: 0 // 数据更新的标记 (只有数据更新了,才会触发wxs的Observer)
+ },
+
+ // 标记调用wxs视图层的方法
+ callProp: {
+ callType: '', // 方法名
+ t: 0 // 数据更新的标记 (只有数据更新了,才会触发wxs的Observer)
+ },
+
+ // 不用wxs的平台使用此处的wxsBiz对象,抹平wxs的写法 (微信小程序和APP使用的wxsBiz对象是./wxs/wxs.wxs)
+ // #ifndef MP-WEIXIN || MP-QQ || APP-PLUS || H5
+ wxsBiz: {
+ //注册列表touchstart事件,用于下拉刷新
+ touchstartEvent: e=> {
+ this.mescroll.touchstartEvent(e);
+ },
+ //注册列表touchmove事件,用于下拉刷新
+ touchmoveEvent: e=> {
+ this.mescroll.touchmoveEvent(e);
+ },
+ //注册列表touchend事件,用于下拉刷新
+ touchendEvent: e=> {
+ this.mescroll.touchendEvent(e);
+ },
+ propObserver(){}, // 抹平wxs的写法
+ callObserver(){} // 抹平wxs的写法
+ },
+ // #endif
+
+ // 不用renderjs的平台使用此处的renderBiz对象,抹平renderjs的写法 (app 和 h5 使用的renderBiz对象是./wxs/renderjs.js)
+ // #ifndef APP-PLUS || H5
+ renderBiz: {
+ propObserver(){} // 抹平renderjs的写法
+ }
+ // #endif
+ }
+ },
+ methods: {
+ // wxs视图层调用逻辑层的回调
+ wxsCall(msg){
+ if(msg.type === 'setWxsProp'){
+ // 更新wxsProp数据 (值改变才触发更新)
+ this.wxsProp = {
+ optDown: this.mescroll.optDown,
+ scrollTop: this.mescroll.getScrollTop(),
+ bodyHeight: this.mescroll.getBodyHeight(),
+ isDownScrolling: this.mescroll.isDownScrolling,
+ isUpScrolling: this.mescroll.isUpScrolling,
+ isUpBoth: this.mescroll.optUp.isBoth,
+ isScrollBody:this.mescroll.isScrollBody,
+ t: Date.now()
+ }
+ }else if(msg.type === 'setLoadType'){
+ // 设置inOffset,outOffset的状态
+ this.downLoadType = msg.downLoadType
+ // 状态挂载到mescroll对象, 以便在其他组件中使用, 比如中
+ this.$set(this.mescroll, 'downLoadType', this.downLoadType)
+ // 重置是否加载成功的状态
+ this.$set(this.mescroll, 'isDownEndSuccess', null)
+ }else if(msg.type === 'triggerDownScroll'){
+ // 主动触发下拉刷新
+ this.mescroll.triggerDownScroll();
+ }else if(msg.type === 'endDownScroll'){
+ // 结束下拉刷新
+ this.mescroll.endDownScroll();
+ }else if(msg.type === 'triggerUpScroll'){
+ // 主动触发上拉加载
+ this.mescroll.triggerUpScroll(true);
+ }
+ }
+ },
+ mounted() {
+ // #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5
+ // 配置主动触发wxs显示加载进度的回调
+ this.mescroll.optDown.afterLoading = ()=>{
+ this.callProp = {callType: "showLoading", t: Date.now()} // 触发wxs的方法 (值改变才触发更新)
+ }
+ // 配置主动触发wxs隐藏加载进度的回调
+ this.mescroll.optDown.afterEndDownScroll = ()=>{
+ this.callProp = {callType: "endDownScroll", t: Date.now()} // 触发wxs的方法 (值改变才触发更新)
+ let delay = 300 + (this.mescroll.optDown.beforeEndDelay || 0)
+ setTimeout(()=>{
+ if(this.downLoadType === 4 || this.downLoadType === 0){
+ this.callProp = {callType: "clearTransform", t: Date.now()} // 触发wxs的方法 (值改变才触发更新)
+ }
+ // 状态挂载到mescroll对象, 以便在其他组件中使用, 比如中
+ this.$set(this.mescroll, 'downLoadType', this.downLoadType)
+ }, delay)
+ }
+ // 初始化wxs的数据
+ this.wxsCall({type: 'setWxsProp'})
+ // #endif
+ }
+}
+
+export default WxsMixin;
diff --git a/uni_modules/mescroll-uni/components/mescroll-uni/wxs/renderjs.js b/uni_modules/mescroll-uni/components/mescroll-uni/wxs/renderjs.js
new file mode 100644
index 0000000000000000000000000000000000000000..6cd130193fdf75935cf1e843eb4c2731e6a586a2
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-uni/wxs/renderjs.js
@@ -0,0 +1,92 @@
+// 使用renderjs直接操作window对象,实现动态控制app和h5的bounce
+// bounce: iOS橡皮筋,Android半月弧,h5浏览器下拉背景等效果 (下拉刷新时禁止)
+// https://uniapp.dcloud.io/frame?id=renderjs
+
+// 与wxs的me实例一致
+var me = {}
+
+// 初始化window对象的touch事件 (仅初始化一次)
+if(window && !window.$mescrollRenderInit){
+ window.$mescrollRenderInit = true
+
+
+ window.addEventListener('touchstart', function(e){
+ if (me.disabled()) return;
+ me.startPoint = me.getPoint(e); // 记录起点
+ }, {passive: true})
+
+
+ window.addEventListener('touchmove', function(e){
+ if (me.disabled()) return;
+ if (me.getScrollTop() > 0) return; // 需在顶部下拉,才禁止bounce
+
+ var curPoint = me.getPoint(e); // 当前点
+ var moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
+ // 向下拉
+ if (moveY > 0) {
+ // 可下拉的条件
+ if (!me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling && me.isUpBoth))) {
+
+ // 只有touch在mescroll的view上面,才禁止bounce
+ var el = e.target;
+ var isMescrollTouch = false;
+ while (el && el.tagName && el.tagName !== 'UNI-PAGE-BODY' && el.tagName != "BODY") {
+ var cls = el.classList;
+ if (cls && cls.contains('mescroll-render-touch')) {
+ isMescrollTouch = true
+ break;
+ }
+ el = el.parentNode; // 继续检查其父元素
+ }
+ // 禁止bounce (不会对swiper和iOS侧滑返回造成影响)
+ if (isMescrollTouch && e.cancelable && !e.defaultPrevented) e.preventDefault();
+ }
+ }
+ }, {passive: false})
+}
+
+/* 获取滚动条的位置 */
+me.getScrollTop = function() {
+ return me.scrollTop || document.documentElement.scrollTop || document.body.scrollTop || 0
+}
+
+/* 是否禁用下拉刷新 */
+me.disabled = function(){
+ return !me.optDown || !me.optDown.use || me.optDown.native
+}
+
+/* 根据点击滑动事件获取第一个手指的坐标 */
+me.getPoint = function(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}
+ } else if (e.changedTouches && e.changedTouches[0]) {
+ return {x: e.changedTouches[0].pageX,y: e.changedTouches[0].pageY}
+ } else {
+ return {x: e.clientX,y: e.clientY}
+ }
+}
+
+/**
+ * 监听逻辑层数据的变化 (实时更新数据)
+ */
+function propObserver(wxsProp) {
+ me.optDown = wxsProp.optDown
+ me.scrollTop = wxsProp.scrollTop
+ me.isDownScrolling = wxsProp.isDownScrolling
+ me.isUpScrolling = wxsProp.isUpScrolling
+ me.isUpBoth = wxsProp.isUpBoth
+}
+
+/* 导出模块 */
+const renderBiz = {
+ data() {
+ return {
+ propObserver: propObserver,
+ }
+ }
+}
+
+export default renderBiz;
\ No newline at end of file
diff --git a/uni_modules/mescroll-uni/components/mescroll-uni/wxs/wxs.wxs b/uni_modules/mescroll-uni/components/mescroll-uni/wxs/wxs.wxs
new file mode 100644
index 0000000000000000000000000000000000000000..4af4201ba3300e7d5d0a24d8b1be8121d1749c43
--- /dev/null
+++ b/uni_modules/mescroll-uni/components/mescroll-uni/wxs/wxs.wxs
@@ -0,0 +1,269 @@
+// 使用wxs处理交互动画, 提高性能, 同时避免小程序bounce对下拉刷新的影响
+// https://uniapp.dcloud.io/frame?id=wxs
+// https://developers.weixin.qq.com/miniprogram/dev/framework/view/interactive-animation.html
+
+// 模拟mescroll实例, 与mescroll.js的写法尽量保持一致
+var me = {}
+
+// ------ 自定义下拉刷新动画 start ------
+
+/* 下拉过程中的回调,滑动过程一直在执行 (rate<1为inOffset; rate>1为outOffset) */
+me.onMoving = function (ins, rate, downHight){
+ ins.requestAnimationFrame(function () {
+ ins.selectComponent('.mescroll-wxs-content').setStyle({
+ 'will-change': 'transform', // 可解决下拉过程中, image和swiper脱离文档流的问题
+ 'transform': 'translateY(' + downHight + 'px)',
+ 'transition': ''
+ })
+ // 环形进度条
+ var progress = ins.selectComponent('.mescroll-wxs-progress')
+ progress && progress.setStyle({transform: 'rotate(' + 360 * rate + 'deg)'})
+ })
+}
+
+/* 显示下拉刷新进度 */
+me.showLoading = function (ins){
+ me.downHight = me.optDown.offset
+ ins.requestAnimationFrame(function () {
+ ins.selectComponent('.mescroll-wxs-content').setStyle({
+ 'will-change': 'auto',
+ 'transform': 'translateY(' + me.downHight + 'px)',
+ 'transition': 'transform 300ms'
+ })
+ })
+}
+
+/* 结束下拉 */
+me.endDownScroll = function (ins){
+ me.downHight = 0;
+ me.isDownScrolling = false;
+ ins.requestAnimationFrame(function () {
+ ins.selectComponent('.mescroll-wxs-content').setStyle({
+ 'will-change': 'auto',
+ 'transform': 'translateY(0)', // 不可以写空串,否则scroll-view渲染不完整 (延时350ms会调clearTransform置空)
+ 'transition': 'transform 300ms'
+ })
+ })
+}
+
+/* 结束下拉动画执行完毕后, 清除transform和transition, 避免对列表内容样式造成影响, 如: h5的list-msg示例下拉进度条漏出来等 */
+me.clearTransform = function (ins){
+ ins.requestAnimationFrame(function () {
+ ins.selectComponent('.mescroll-wxs-content').setStyle({
+ 'will-change': '',
+ 'transform': '',
+ 'transition': ''
+ })
+ })
+}
+
+// ------ 自定义下拉刷新动画 end ------
+
+/**
+ * 监听逻辑层数据的变化 (实时更新数据)
+ */
+function propObserver(wxsProp) {
+ if(!wxsProp) return
+ me.optDown = wxsProp.optDown
+ me.scrollTop = wxsProp.scrollTop
+ me.bodyHeight = wxsProp.bodyHeight
+ me.isDownScrolling = wxsProp.isDownScrolling
+ me.isUpScrolling = wxsProp.isUpScrolling
+ me.isUpBoth = wxsProp.isUpBoth
+ me.isScrollBody = wxsProp.isScrollBody
+ me.startTop = wxsProp.scrollTop // 及时更新touchstart触发的startTop, 避免scroll-view快速惯性滚动到顶部取值不准确
+}
+
+/**
+ * 监听逻辑层数据的变化 (调用wxs的方法)
+ */
+function callObserver(callProp, oldValue, ins) {
+ if (me.disabled()) return;
+ if(callProp.callType){
+ // 逻辑层(App Service)的style已失效,需在视图层(Webview)设置style
+ if(callProp.callType === 'showLoading'){
+ me.showLoading(ins)
+ }else if(callProp.callType === 'endDownScroll'){
+ me.endDownScroll(ins)
+ }else if(callProp.callType === 'clearTransform'){
+ me.clearTransform(ins)
+ }
+ }
+}
+
+/**
+ * touch事件
+ */
+function touchstartEvent(e, ins) {
+ me.downHight = 0; // 下拉的距离
+ me.startPoint = me.getPoint(e); // 记录起点
+ me.startTop = me.getScrollTop(); // 记录此时的滚动条位置
+ me.startAngle = 0; // 初始角度
+ me.lastPoint = me.startPoint; // 重置上次move的点
+ me.maxTouchmoveY = me.getBodyHeight() - me.optDown.bottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况)
+ me.inTouchend = false; // 标记不是touchend
+
+ me.callMethod(ins, {type: 'setWxsProp'}) // 同步更新wxsProp的数据 (小程序是异步的,可能touchmove先执行,才到propObserver; h5和app是同步)
+}
+
+function touchmoveEvent(e, ins) {
+ var isPrevent = true // false表示不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault (对小程序生效, h5和app无效)
+
+ if (me.disabled()) return isPrevent;
+
+ var scrollTop = me.getScrollTop(); // 当前滚动条的距离
+ var curPoint = me.getPoint(e); // 当前点
+
+ var moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
+
+ // 向下拉 && 在顶部
+ // mescroll-body,直接判定在顶部即可
+ // scroll-view在滚动时不会触发touchmove,当触顶/底/左/右时,才会触发touchmove
+ // scroll-view滚动到顶部时,scrollTop不一定为0,也有可能大于0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等
+ if (moveY > 0 && (
+ (me.isScrollBody && scrollTop <= 0)
+ ||
+ (!me.isScrollBody && (scrollTop <= 0 || (scrollTop <= me.optDown.startTop && scrollTop === me.startTop)) )
+ )) {
+ // 可下拉的条件
+ if (!me.inTouchend && !me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling &&
+ me.isUpBoth))) {
+
+ // 下拉的角度是否在配置的范围内
+ if(!me.startAngle) me.startAngle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90]
+ if (me.startAngle < me.optDown.minAngle) return isPrevent; // 如果小于配置的角度,则不往下执行下拉刷新
+
+ // 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发
+ if (me.maxTouchmoveY > 0 && curPoint.y >= me.maxTouchmoveY) {
+ me.inTouchend = true; // 标记执行touchend
+ touchendEvent(e, ins); // 提前触发touchend
+ return isPrevent;
+ }
+
+ isPrevent = false // 小程序是return false
+
+ var diff = curPoint.y - me.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上)
+
+ // 下拉距离 < 指定距离
+ if (me.downHight < me.optDown.offset) {
+ if (me.movetype !== 1) {
+ me.movetype = 1; // 加入标记,保证只执行一次
+ // me.optDown.inOffset && me.optDown.inOffset(me); // 进入指定距离范围内那一刻的回调,只执行一次
+ me.callMethod(ins, {type: 'setLoadType', downLoadType: 1})
+ me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
+ }
+ me.downHight += diff * me.optDown.inOffsetRate; // 越往下,高度变化越小
+
+ // 指定距离 <= 下拉距离
+ } else {
+ if (me.movetype !== 2) {
+ me.movetype = 2; // 加入标记,保证只执行一次
+ // me.optDown.outOffset && me.optDown.outOffset(me); // 下拉超过指定距离那一刻的回调,只执行一次
+ me.callMethod(ins, {type: 'setLoadType', downLoadType: 2})
+ me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
+ }
+ if (diff > 0) { // 向下拉
+ me.downHight += diff * me.optDown.outOffsetRate; // 越往下,高度变化越小
+ } else { // 向上收
+ me.downHight += diff; // 向上收回高度,则向上滑多少收多少高度
+ }
+ }
+
+ me.downHight = Math.round(me.downHight) // 取整
+ var rate = me.downHight / me.optDown.offset; // 下拉区域当前高度与指定距离的比值
+ // me.optDown.onMoving && me.optDown.onMoving(me, rate, me.downHight); // 下拉过程中的回调,一直在执行
+ me.onMoving(ins, rate, me.downHight)
+ }
+ }
+
+ me.lastPoint = curPoint; // 记录本次移动的点
+
+ return isPrevent // false表示不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault (对小程序生效, h5和app无效)
+}
+
+function touchendEvent(e, ins) {
+ // 如果下拉区域高度已改变,则需重置回来
+ if (me.isMoveDown) {
+ if (me.downHight >= me.optDown.offset) {
+ // 符合触发刷新的条件
+ me.downHight = me.optDown.offset; // 更新下拉区域高度
+ // me.triggerDownScroll();
+ me.callMethod(ins, {type: 'triggerDownScroll'})
+ } else {
+ // 不符合的话 则重置
+ me.downHight = 0;
+ // me.optDown.endDownScroll && me.optDown.endDownScroll(me);
+ me.callMethod(ins, {type: 'endDownScroll'})
+ }
+ me.movetype = 0;
+ me.isMoveDown = false;
+ } else if (!me.isScrollBody && me.getScrollTop() === me.startTop) { // scroll-view到顶/左/右/底的滑动事件
+ var isScrollUp = me.getPoint(e).y - me.startPoint.y < 0; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
+ // 上滑
+ if (isScrollUp) {
+ // 需检查滑动的角度
+ var angle = me.getAngle(me.getPoint(e), me.startPoint); // 两点之间的角度,区间 [0,90]
+ if (angle > 80) {
+ // 检查并触发上拉
+ // me.triggerUpScroll(true);
+ me.callMethod(ins, {type: 'triggerUpScroll'})
+ }
+ }
+ }
+ me.callMethod(ins, {type: 'setWxsProp'}) // 同步更新wxsProp的数据 (小程序是异步的,可能touchmove先执行,才到propObserver; h5和app是同步)
+}
+
+/* 是否禁用下拉刷新 */
+me.disabled = function(){
+ return !me.optDown || !me.optDown.use || me.optDown.native
+}
+
+/* 根据点击滑动事件获取第一个手指的坐标 */
+me.getPoint = function(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}
+ } else if (e.changedTouches && e.changedTouches[0]) {
+ return {x: e.changedTouches[0].pageX,y: e.changedTouches[0].pageY}
+ } else {
+ return {x: e.clientX,y: e.clientY}
+ }
+}
+
+/* 计算两点之间的角度: 区间 [0,90]*/
+me.getAngle = function (p1, p2) {
+ var x = Math.abs(p1.x - p2.x);
+ var y = Math.abs(p1.y - p2.y);
+ var z = Math.sqrt(x * x + y * y);
+ var angle = 0;
+ if (z !== 0) {
+ angle = Math.asin(y / z) / Math.PI * 180;
+ }
+ return angle
+}
+
+/* 获取滚动条的位置 */
+me.getScrollTop = function() {
+ return me.scrollTop || 0
+}
+
+/* 获取body的高度 */
+me.getBodyHeight = function() {
+ return me.bodyHeight || 0;
+}
+
+/* 调用逻辑层的方法 */
+me.callMethod = function(ins, param) {
+ if(ins) ins.callMethod('wxsCall', param)
+}
+
+/* 导出模块 */
+module.exports = {
+ propObserver: propObserver,
+ callObserver: callObserver,
+ touchstartEvent: touchstartEvent,
+ touchmoveEvent: touchmoveEvent,
+ touchendEvent: touchendEvent
+}
\ No newline at end of file
diff --git a/uni_modules/mescroll-uni/hooks/useMescroll.js b/uni_modules/mescroll-uni/hooks/useMescroll.js
new file mode 100644
index 0000000000000000000000000000000000000000..ef91cd13eb9e26e2ca63746c0a67598350db5ea5
--- /dev/null
+++ b/uni_modules/mescroll-uni/hooks/useMescroll.js
@@ -0,0 +1,66 @@
+// 小程序无法在hook中使用页面级别生命周期,需单独传入: https://ask.dcloud.net.cn/question/161173
+// import { onPageScroll, onReachBottom, onPullDownRefresh} from '@dcloudio/uni-app';
+
+/**
+ * 初始化mescroll, 相当于vue2的mescroll-mixins.js文件 (mescroll-body 和 mescroll-uni 通用)
+ * mescroll-body需传入onPageScroll, onReachBottom
+ * mescroll-uni无需传onPageScroll, onReachBottom
+ * 当down.native为true时,需传入onPullDownRefresh
+ */
+function useMescroll(onPageScroll, onReachBottom, onPullDownRefresh){
+ // mescroll实例对象
+ let mescroll = null;
+
+ // mescroll组件初始化的回调,可获取到mescroll对象
+ const mescrollInit = (e)=> {
+ mescroll = e;
+ }
+
+ // 获取mescroll对象, mescrollInit执行之后会有值, 生命周期created中会有值
+ const getMescroll = ()=>{
+ return mescroll
+ }
+
+ // 下拉刷新的回调 (mixin默认resetUpScroll)
+ const downCallback = ()=> {
+ if(mescroll.optUp.use){
+ mescroll.resetUpScroll()
+ }else{
+ setTimeout(()=>{
+ mescroll.endSuccess();
+ }, 500)
+ }
+ }
+
+ // 上拉加载的回调
+ const upCallback = ()=> {
+ // mixin默认延时500自动结束加载
+ setTimeout(()=>{
+ mescroll.endErr();
+ }, 500)
+ }
+
+ // 注册系统自带的下拉刷新 (配置down.native为true时生效, 还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
+ onPullDownRefresh && onPullDownRefresh(() => {
+ mescroll && mescroll.onPullDownRefresh();
+ })
+
+ // 注册列表滚动事件,用于判定在顶部可下拉刷新,在指定位置可显示隐藏回到顶部按钮 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
+ onPageScroll && onPageScroll(e=>{
+ mescroll && mescroll.onPageScroll(e);
+ })
+
+ // 注册滚动到底部的事件,用于上拉加载 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
+ onReachBottom && onReachBottom(()=>{
+ mescroll && mescroll.onReachBottom();
+ })
+
+ return {
+ getMescroll,
+ mescrollInit,
+ downCallback,
+ upCallback
+ }
+}
+
+export default useMescroll
\ No newline at end of file
diff --git a/uni_modules/mescroll-uni/hooks/useMescrollComp.js b/uni_modules/mescroll-uni/hooks/useMescrollComp.js
new file mode 100644
index 0000000000000000000000000000000000000000..3b83847e172fdff30119ed5124d8007cd674f78c
--- /dev/null
+++ b/uni_modules/mescroll-uni/hooks/useMescrollComp.js
@@ -0,0 +1,56 @@
+import { ref } from 'vue';
+
+// 小程序无法在hook中使用页面级别生命周期,需单独传入: https://ask.dcloud.net.cn/question/161173
+// import { onPageScroll, onReachBottom, onPullDownRefresh} from '@dcloudio/uni-app';
+
+/**
+ * mescroll-body写在子组件时,需通过useMescrollComp补充子组件缺少的生命周期, 相当于vue2的mescroll-comp.js文件
+ * 必须传入onPageScroll, onReachBottom
+ * 当down.native为true时,需传入onPullDownRefresh
+ */
+function useMescrollComp(onPageScroll, onReachBottom, onPullDownRefresh){
+ // 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件
+ onPageScroll(e=>{
+ handlePageScroll(e)
+ })
+
+ onReachBottom(()=>{
+ handleReachBottom()
+ })
+
+ // 当down的native: true时, 还需传递此方法进到子组件
+ onPullDownRefresh && onPullDownRefresh(()=>{
+ handlePullDownRefresh()
+ })
+
+ const mescrollItem = ref(null)
+
+ const handlePageScroll = (e)=>{
+ const mescroll = getMescroll()
+ mescroll && mescroll.onPageScroll(e);
+ }
+
+ const handleReachBottom = ()=>{
+ const mescroll = getMescroll()
+ mescroll && mescroll.onReachBottom();
+ }
+
+ const handlePullDownRefresh = ()=>{
+ const mescroll = getMescroll()
+ mescroll && mescroll.onPullDownRefresh();
+ }
+
+ const getMescroll = ()=>{
+ if(mescrollItem.value && mescrollItem.value.getMescroll){
+ return mescrollItem.value.getMescroll()
+ }
+ return null
+ }
+
+ return {
+ mescrollItem,
+ getMescroll
+ }
+}
+
+export default useMescrollComp
\ No newline at end of file
diff --git a/uni_modules/mescroll-uni/hooks/useMescrollMore.js b/uni_modules/mescroll-uni/hooks/useMescrollMore.js
new file mode 100644
index 0000000000000000000000000000000000000000..f9a3cd0e5f2a26d7486ff90e3346da87cb777d80
--- /dev/null
+++ b/uni_modules/mescroll-uni/hooks/useMescrollMore.js
@@ -0,0 +1,69 @@
+import { ref } from 'vue';
+
+// 小程序无法在hook中使用页面级别生命周期,需单独传入: https://ask.dcloud.net.cn/question/161173
+// import { onPageScroll, onReachBottom, onPullDownRefresh} from '@dcloudio/uni-app';
+
+/** mescroll-more示例写在子组件时,需通过useMescrollMore补充子组件缺少的生命周期, 相当于vue2的mescroll-more.js文件 */
+function useMescrollMore(mescrollItems, onPageScroll, onReachBottom, onPullDownRefresh){
+ // 当前tab下标
+ const tabIndex = ref(0)
+
+ // 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件
+ onPageScroll && onPageScroll(e=>{
+ handlePageScroll(e)
+ })
+
+ onReachBottom && onReachBottom(()=>{
+ handleReachBottom()
+ })
+
+ // 当down的native: true时, 还需传递此方法进到子组件
+ onPullDownRefresh && onPullDownRefresh(()=>{
+ handlePullDownRefresh()
+ })
+
+ const handlePageScroll = (e)=>{
+ let mescroll = getMescroll(tabIndex.value);
+ mescroll && mescroll.onPageScroll(e);
+ }
+ const handleReachBottom = ()=>{
+ let mescroll = getMescroll(tabIndex.value);
+ mescroll && mescroll.onReachBottom();
+ }
+
+ const handlePullDownRefresh = ()=>{
+ let mescroll = getMescroll(tabIndex.value);
+ mescroll && mescroll.onPullDownRefresh();
+ }
+
+ // 根据下标获取对应子组件的mescroll
+ const getMescroll = (i)=>{
+ if (mescrollItems && mescrollItems[i]) {
+ return mescrollItems[i].value.getMescroll()
+ } else{
+ return null
+ }
+ }
+
+ // 切换tab,恢复滚动条位置
+ const scrollToLastY = ()=>{
+ let mescroll = getMescroll(tabIndex.value);
+ if(mescroll){
+ // 恢复上次滚动条的位置
+ let y = mescroll.getScrollTop()
+ mescroll.scrollTo(y, 0)
+ // 再次恢复上次滚动条的位置, 确保元素已渲染
+ setTimeout(()=>{
+ mescroll.scrollTo(y, 0)
+ },20)
+ }
+ }
+
+ return {
+ tabIndex,
+ getMescroll,
+ scrollToLastY
+ }
+}
+
+export default useMescrollMore
\ No newline at end of file
diff --git a/uni_modules/mescroll-uni/package.json b/uni_modules/mescroll-uni/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..ced70f93a770fb8f42f2fa13414063f453dfa070
--- /dev/null
+++ b/uni_modules/mescroll-uni/package.json
@@ -0,0 +1,77 @@
+{
+ "id": "mescroll-uni",
+ "displayName": "【wxs+renderjs实现】高性能的下拉刷新上拉加载组件,支持vue3 setup",
+ "version": "1.3.8",
+ "description": "mescroll - 支持uni-app的下拉刷新和上拉加载的组件,支持原生页面和局部区域滚动,支持vue3 script setup的写法",
+ "keywords": [
+ "下拉刷新",
+ "上拉加载",
+ "翻页分页",
+ "wxs",
+ "setup"
+],
+ "repository": "https://github.com/mescroll/mescroll",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+"dcloudext": {
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "无",
+ "permissions": "无"
+ },
+ "npmurl": "https://www.npmjs.com/package/mescroll-uni",
+ "type": "component-vue"
+ },
+ "uni_modules": {
+ "dependencies": [],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "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"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/mescroll-uni/readme.md b/uni_modules/mescroll-uni/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..4bce178994f51881c4a44b62ec83125a51f8c974
--- /dev/null
+++ b/uni_modules/mescroll-uni/readme.md
@@ -0,0 +1,45 @@
+## mescroll --【wxs+renderjs实现】高性能的下拉刷新上拉加载组件
+1. mescroll的uni版本 是专门用在uni-app的下拉刷新和上拉加载的组件
+
+2. mescroll的uni版本 继承了mescroll.js的实用功能: 自动处理分页, 自动控制无数据, 空布局提示, 回到顶部按钮 ..
+
+3. mescroll的uni版本 丰富的案例, 自由灵活的api, 超详细的注释, 可让您快速自定义真正属于自己的下拉上拉组件
+
+
+
+
+## 最新文档(1.3.8版本): https://www.mescroll.com/uni.html
+2023-03-26 by 小瑾同学 (文档可能会有缓存,建议打开时刷新一下)
+
+
+## 1.3.5版本已调整为[uni_modules](https://uniapp.dcloud.io/uni_modules)
+uni_modules版本的mescroll-body 和 mescroll-empty 支持 [easycom规范](https://uniapp.dcloud.io/collocation/pages?id=easycom)
+所以 main.js 无需再为mescroll-body注册全局组件
+所以个别页面要单独使用 mescroll-empty , 也无需手动注册
+#### 1.3.5以前的用户升级为uni_modules版本:
+```
+1. 删除原来的 @/components/mescroll-uni 组件
+2. 删除 main.js 注册的 mescroll 组件
+3. 从插件市场导入最新mescroll组件 (1.3.5+uni_modules版本)
+4. 全局搜索 '@/components/mescroll-uni/' 替换为 '@/uni_modules/mescroll-uni/components/mescroll-uni/'
+5. mescroll-empty遵循easycom规范, 若某些页面单独使用 'mescroll-empty.vue', 可删除手动导入的代码
+```
+
+## 近期已更新优化的内容:
+1. 新增vue3 script setup的示例
+2. 新增`入门极简`示例, 国际化`mescroll-i18n.vue`示例, 轮播吸顶菜单`mescroll-swiper-sticky.vue`示例
+3. 新增 "局部区域滚动" 的案例: mescroll-body-part.vue 和 mescroll-uni-part.vue
+4. 新增 me-video 视频组件, 解决APP端视频下拉悬浮错位的问题, 参考 mescroll-options.vue 示例
+5. 新增 me-tabs 组件,tabs支持水平滑动; 优化mescroll-more和mescroll-swiper的案例, 顶部tab支持水平滑动
+6. 吸顶悬浮提供了原生sticky和监听滚动条实现的示例: sticky.vue 和 sticky-scroll.vue (推荐使用sticky样式实现)
+7. mescroll.scrollTo(y)的y支持css选择器, 包括跨自定义组件的后代选择器, 支持滚动到子组件的view (参考 mescroll-options.vue)
+8. topbar 顶部是否预留状态栏的高度, 默认false; 还可支持设置状态栏背景: 如 '#ffff00', 'url(xxx) 0 0/100% 100%', 'linear-gradient(xx)'
+9. down.bgColor 和 up.bgColor 加载区域的背景,不仅支持色值, 而且还是支持背景图和渐变: 如 'url(xxx) 0 0/100% 100%', 'linear-gradient(xx)'
+10. topbar,bgColor支持一行代码定义background: [https://www.runoob.com/cssref/css3-pr-background.html](https://www.runoob.com/cssref/css3-pr-background.html)
+
+
+查看更多 ...
+
+
+
+#### mescroll不支持nvue,也暂无支持的计划哈,so sorry~
\ No newline at end of file
diff --git a/utils/common.js b/utils/common.js
index 2f0a6688720ea9837ebcfa182a502589a8a55e70..4d35438fbdbddd0ac2b03cfbae066e00737f0aef 100644
--- a/utils/common.js
+++ b/utils/common.js
@@ -1,32 +1,41 @@
+export const mediaPreviewUrl='https://oss.wecarry.cn/'
+
+
/**
* 显示消息提示框
* @param content 提示的标题
-*/
-export function toast(content) {
- uni.showToast({
- icon: 'none',
- title: content
- })
-}
-
+*/
+export function toast(content) {
+ uni.showToast({
+ icon: 'none',
+ title: content
+ })
+}
+
+export function toastWithIcon(content, icon) {
+ uni.showToast({
+ icon: icon,
+ title: content
+ })
+}
/**
* 显示模态弹窗
* @param content 提示的标题
-*/
-export function showConfirm(content) {
- return new Promise((resolve, reject) => {
- uni.showModal({
- title: '提示',
- content: content,
- cancelText: '取消',
- confirmText: '确定',
- success: function(res) {
- resolve(res)
- }
- })
- })
-}
-
+*/
+export function showConfirm(content) {
+ return new Promise((resolve, reject) => {
+ uni.showModal({
+ title: '提示',
+ content: content,
+ cancelText: '取消',
+ confirmText: '确定',
+ success: function(res) {
+ resolve(res)
+ }
+ })
+ })
+}
+
/**
* 参数处理
* @param params 参数
@@ -36,7 +45,7 @@ export function tansParams(params) {
for (const propName of Object.keys(params)) {
const value = params[propName]
var part = encodeURIComponent(propName) + "="
- if (value !== null && value !== "" && typeof (value) !== "undefined") {
+ if (value !== null && typeof (value) !== "undefined") {
if (typeof value === 'object') {
for (const key of Object.keys(value)) {
if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') {
@@ -51,4 +60,185 @@ export function tansParams(params) {
}
}
return result
-}
\ No newline at end of file
+}
+
+/**
+ * 展示性别
+ * @param {Object} sex
+ */
+export function displaySex(sex){
+ if(sex=='0'){
+ return '男';
+ }
+ else if(sex=='1'){
+ return '女';
+ }
+ else{
+ return '未知';
+ }
+
+}
+
+/**
+ * 计算年龄,小于三岁时,展示为小数点岁,例如一岁六个月等于1.5岁,大于三岁保留整数
+ * @param {String|Date} birthDate 出出日期
+ * @returns {Number} 年龄,小于3岁返回小数,大于等于3岁返回整数
+ */
+/**
+ * 计算年龄,小于三岁时,展示为小数点岁,例如一岁六个月等于1.5岁,大于三岁保留整数
+ * @param {String|Date} birthDate 出生日期
+ * @returns {Number} 年龄,小于3岁返回小数,大于等于3岁返回整数
+ */
+export function calculateAge(birthDate) {
+ // 参数验证
+ if (!birthDate) {
+ console.warn('calculateAge: birthDate is required');
+ return 0;
+ }
+
+ const today = new Date();
+ const birth = new Date(birthDate);
+
+ // 验证日期有效性
+ if (isNaN(birth.getTime())) {
+ console.warn('calculateAge: invalid birthDate format');
+ return 0;
+ }
+
+ // 检查出生日期不能晚于今天
+ if (birth > today) {
+ console.warn('calculateAge: birthDate cannot be later than today');
+ return 0;
+ }
+
+ // 计算精确的年龄差(毫秒)
+ const diffTime = Math.abs(today - birth);
+ const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
+
+ // 如果小于1岁(365天)
+ if (diffDays < 365) {
+ // 直接计算年份小数
+ const ageInYears = diffDays / 365;
+ // 保留一位小数
+ return Math.round(ageInYears * 10) / 10;
+ }
+
+ // 计算年、月、日差值(用于1岁以上的计算)
+ let years = today.getFullYear() - birth.getFullYear();
+ let months = today.getMonth() - birth.getMonth();
+ let days = today.getDate() - birth.getDate();
+
+ // 调整月份和年份
+ if (days < 0) {
+ months--;
+ // 获取上个月的天数
+ const lastMonth = new Date(today.getFullYear(), today.getMonth(), 0);
+ days += lastMonth.getDate();
+ }
+
+ if (months < 0) {
+ years--;
+ months += 12;
+ }
+
+ // 计算总月数
+ const totalMonths = years * 12 + months;
+
+ // 小于3岁(36个月)时,返回小数年龄
+ if (totalMonths < 36) {
+ // 使用总月数加上天数占比来计算更准确的小数年龄
+ const totalDays = totalMonths * 30 + days; // 近似计算总天数
+ const ageInYears = totalDays / 365;
+ // 保留一位小数
+ return Math.round(ageInYears * 10) / 10;
+ }
+
+ // 大于等于3岁时,返回整数年龄
+ return years;
+}
+/**
+ * 格式化显示日期
+ * @param {Object} dateString
+ */
+export function formatHumanDate(dateString) {
+ formatHumanDatetime(dateString,false)
+}
+
+/**
+ * 格式化显示日期
+ * @param {Object} dateString
+ */
+export function formatHumanDatetime(dateString,includeTime) {
+ const date = new Date(dateString);
+ const now = new Date();
+
+ // 检查是否是无效日期
+ if (isNaN(date.getTime())) {
+ return '';
+ }
+
+ // 计算时间差(毫秒)
+ const diffTime = now - date;
+ const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
+
+ // 格式化时间部分(HH:mm)
+ const hours = date.getHours().toString().padStart(2, '0');
+ const minutes = date.getMinutes().toString().padStart(2, '0');
+ const timeStr = `${hours}:${minutes}`;
+
+ // 判断并返回相应格式
+ if (diffDays === 0) {
+ // 今天
+ return `今天 ${timeStr}`;
+ } else if (diffDays === 1) {
+ // 昨天
+ return `昨天 ${timeStr}`;
+ } else {
+ // 其他日期显示年月日
+ const year = date.getFullYear();
+ const month = (date.getMonth() + 1).toString().padStart(2, '0');
+ const day = date.getDate().toString().padStart(2, '0');
+ return includeTime? `${year}-${month}-${day} ${timeStr}`:`${year}-${month}-${day}`;
+ }
+}
+
+// 日期格式化
+export function parseTime(time, pattern) {
+ if (arguments.length === 0 || !time) {
+ return null
+ }
+ const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
+ let date
+ if (typeof time === 'object') {
+ date = time
+ } else {
+ if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
+ time = parseInt(time)
+ } else if (typeof time === 'string') {
+ time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), '');
+ }
+ if ((typeof time === 'number') && (time.toString().length === 10)) {
+ time = time * 1000
+ }
+ date = new Date(time)
+ }
+ const formatObj = {
+ y: date.getFullYear(),
+ m: date.getMonth() + 1,
+ d: date.getDate(),
+ h: date.getHours(),
+ i: date.getMinutes(),
+ s: date.getSeconds(),
+ a: date.getDay()
+ }
+ const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
+ let value = formatObj[key]
+ // Note: getDay() returns 0 on Sunday
+ if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
+ if (result.length > 0 && value < 10) {
+ value = '0' + value
+ }
+ return value || 0
+ })
+ return time_str
+}
diff --git a/utils/constant.js b/utils/constant.js
index 891d5b838ac011c455f3648b412d5776ce5b2409..3dcf349bdd0ac15ac209e795ecebba56f60e2397 100644
--- a/utils/constant.js
+++ b/utils/constant.js
@@ -1,8 +1,10 @@
const constant = {
- avatar: 'vuex_avatar',
- name: 'vuex_name',
- roles: 'vuex_roles',
- permissions: 'vuex_permissions'
+ avatar: 'user_avatar',
+ id: 'user_id',
+ username: 'user_name',
+ nickname: 'nickname',
+ roles: 'user_roles',
+ permissions: 'user_permissions'
}
export default constant
diff --git a/utils/dict.js b/utils/dict.js
new file mode 100644
index 0000000000000000000000000000000000000000..4cb91b20be0ae3c8add0d91ea4432a3464ca438b
--- /dev/null
+++ b/utils/dict.js
@@ -0,0 +1,25 @@
+import useDictStore from "@/store/modules/dict"
+import { getDicts } from "@/api/system/dict/data"
+import { ref, toRefs } from "vue"
+
+/**
+ * 获取字典数据
+ */
+export function useDict(...args) {
+ const res = ref({})
+ return (() => {
+ args.forEach((dictType, index) => {
+ res.value[dictType] = []
+ const dicts = useDictStore().getDict(dictType)
+ if (dicts) {
+ res.value[dictType] = dicts
+ } else {
+ getDicts(dictType).then((resp) => {
+ res.value[dictType] = resp.data.map((p) => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass }))
+ useDictStore().setDict(dictType, res.value[dictType])
+ })
+ }
+ })
+ return toRefs(res.value)
+ })()
+}
\ No newline at end of file
diff --git a/utils/errorCode.js b/utils/errorCode.js
index b72d02646ed5f48a004f8fd481aaf7a8775d6790..0b8606430c0b0d2aae59697e5bb155b62b2cc4c7 100644
--- a/utils/errorCode.js
+++ b/utils/errorCode.js
@@ -1,4 +1,5 @@
export default {
+ '200': '操作成功',
'401': '认证失败,无法访问系统资源',
'403': '当前操作没有权限',
'404': '访问资源不存在',
diff --git a/utils/request.js b/utils/request.js
index 80c07384e975fdf00af1e916e941285700162c25..900941734598400eeaadb6825362b46b3e99e580 100644
--- a/utils/request.js
+++ b/utils/request.js
@@ -1,73 +1,83 @@
-import store from '@/store'
import config from '@/config'
-import { getToken } from '@/utils/auth'
+import {
+ getToken
+} from '@/utils/auth'
import errorCode from '@/utils/errorCode'
-import { toast, showConfirm, tansParams } from '@/utils/common'
+import {
+ useUserStore
+} from '@/store/modules/user'
+import {
+ toast,
+ showConfirm,
+ tansParams
+} from '@/utils/common'
let timeout = 10000
const baseUrl = config.baseUrl
const request = config => {
- // 是否需要设置 token
- const isToken = (config.headers || {}).isToken === false
- config.header = config.header || {}
- if (getToken() && !isToken) {
- config.header['Authorization'] = 'Bearer ' + getToken()
- }
- // get请求映射params参数
- if (config.params) {
- let url = config.url + '?' + tansParams(config.params)
- url = url.slice(0, -1)
- config.url = url
- }
- return new Promise((resolve, reject) => {
- uni.request({
- method: config.method || 'get',
- timeout: config.timeout || timeout,
- url: config.baseUrl || baseUrl + config.url,
- data: config.data,
- header: config.header,
- dataType: 'json'
- }).then(response => {
- let [error, res] = response
- if (error) {
- toast('后端接口连接异常')
- reject('后端接口连接异常')
- return
- }
- const code = res.data.code || 200
- const msg = errorCode[code] || res.data.msg || errorCode['default']
- if (code === 401) {
- showConfirm('登录状态已过期,您可以继续留在该页面,或者重新登录?').then(res => {
- if (res.confirm) {
- store.dispatch('LogOut').then(res => {
- uni.reLaunch({ url: '/pages/login' })
- })
- }
- })
- reject('无效的会话,或者会话已过期,请重新登录。')
- } else if (code === 500) {
- toast(msg)
- reject('500')
- } else if (code !== 200) {
- toast(msg)
- reject(code)
- }
- resolve(res.data)
- })
- .catch(error => {
- let { message } = error
- if (message === 'Network Error') {
- message = '后端接口连接异常'
- } else if (message.includes('timeout')) {
- message = '系统接口请求超时'
- } else if (message.includes('Request failed with status code')) {
- message = '系统接口' + message.substr(message.length - 3) + '异常'
- }
- toast(message)
- reject(error)
- })
- })
+ // 是否需要设置 token
+ const isToken = (config.headers || {}).isToken === false
+ config.header = config.header || {}
+ if (getToken() && !isToken) {
+ config.header['Authorization'] = 'Bearer ' + getToken()
+ }
+ // get请求映射params参数
+ if (config.params) {
+ let url = config.url + '?' + tansParams(config.params)
+ url = url.slice(0, -1)
+ config.url = url
+ }
+ return new Promise((resolve, reject) => {
+ uni.request({
+ method: config.method || 'get',
+ timeout: config.timeout || timeout,
+ url: config.baseUrl || baseUrl + config.url,
+ data: config.data,
+ header: config.header,
+ dataType: 'json'
+ }).then(response => {
+ const res = response
+ const statusCode = res.statusCode || 200
+ const code = res.data?.code;
+ const msg = errorCode[code] || res.data.msg || errorCode['default']
+ if (statusCode === 401) {
+ uni.showToast({
+ title: '登录状态已过期,请重新登录!',
+ icon: 'none'
+ })
+ setTimeout(() => {
+ useUserStore().logOut().then(res => {
+ uni.reLaunch({
+ url: '/pages/login'
+ })
+ })
+ },1000)
+ reject('登录状态无效或者已过期,请重新登录。')
+ } else if (code === 500) {
+ toast(msg)
+ reject('500')
+ } else if (code !== 0) {
+ toast(msg)
+ reject(code)
+ }
+ resolve(res.data)
+ })
+ .catch(error => {
+ let {
+ message
+ } = error
+ if (message === 'Network Error') {
+ message = '后端接口连接异常'
+ } else if (message.includes('timeout')) {
+ message = '系统接口请求超时'
+ } else if (message.includes('Request failed with status code')) {
+ message = '系统接口' + message.substr(message.length - 3) + '异常'
+ }
+ toast(message)
+ reject(error)
+ })
+ })
}
-export default request
+export default request
\ No newline at end of file
diff --git a/utils/storage.js b/utils/storage.js
index 462ca62141e16d1f99eb08e0746158a406337671..cb803e400eb29419a791e7828fba90cf842bef59 100644
--- a/utils/storage.js
+++ b/utils/storage.js
@@ -4,7 +4,7 @@ import constant from './constant'
let storageKey = 'storage_data'
// 存储节点变量名
-let storageNodeKeys = [constant.avatar, constant.name, constant.roles, constant.permissions]
+let storageNodeKeys = [constant.avatar, constant.id, constant.name, constant.roles, constant.permissions]
const storage = {
set: function(key, value) {
diff --git a/utils/upload.js b/utils/upload.js
index 4d9d431909985567e9d0a02281208bc589e8bb86..003ddc2bfb2274cb68790b7f26f4f2514b5d3a98 100644
--- a/utils/upload.js
+++ b/utils/upload.js
@@ -1,4 +1,4 @@
-import store from '@/store'
+import { useUserStore } from '@/store'
import config from '@/config'
import { getToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode'
@@ -7,7 +7,7 @@ import { toast, showConfirm, tansParams } from '@/utils/common'
let timeout = 10000
const baseUrl = config.baseUrl
-const upload = config => {
+export default function upload(config) {
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false
config.header = config.header || {}
@@ -21,50 +21,50 @@ const upload = config => {
config.url = url
}
return new Promise((resolve, reject) => {
- uni.uploadFile({
- timeout: config.timeout || timeout,
- url: baseUrl + config.url,
- filePath: config.filePath,
- name: config.name || 'file',
- header: config.header,
- formData: config.formData,
- success: (res) => {
- let result = JSON.parse(res.data)
- const code = result.code || 200
- const msg = errorCode[code] || result.msg || errorCode['default']
- if (code === 200) {
- resolve(result)
- } else if (code == 401) {
- showConfirm("登录状态已过期,您可以继续留在该页面,或者重新登录?").then(res => {
- if (res.confirm) {
- store.dispatch('LogOut').then(res => {
- uni.reLaunch({ url: '/pages/login/login' })
- })
- }
- })
- reject('无效的会话,或者会话已过期,请重新登录。')
- } else if (code === 500) {
- toast(msg)
- reject('500')
- } else if (code !== 200) {
- toast(msg)
- reject(code)
- }
- },
- fail: (error) => {
- let { message } = error
- if (message == 'Network Error') {
- message = '后端接口连接异常'
- } else if (message.includes('timeout')) {
- message = '系统接口请求超时'
- } else if (message.includes('Request failed with status code')) {
- message = '系统接口' + message.substr(message.length - 3) + '异常'
- }
- toast(message)
- reject(error)
+ uni.uploadFile({
+ timeout: config.timeout || timeout,
+ url: baseUrl + config.url,
+ filePath: config.filePath,
+ name: config.name || 'file',
+ header: config.header,
+ formData: config.formData,
+ success: (res) => {
+ let result = JSON.parse(res.data)
+ const code = result.code || 200
+ const msg = errorCode[code] || result.msg || errorCode['default']
+ if (code === 200) {
+ resolve(result)
+ } else if (code == 401) {
+ showConfirm("登录状态已过期,您可以继续留在该页面,或者重新登录?").then(res => {
+ if (res.confirm) {
+ useUserStore().logOut().then(res => {
+ uni.reLaunch({ url: '/pages/login/login' })
+ })
+ }
+ })
+ reject('无效的会话,或者会话已过期,请重新登录。')
+ } else if (code === 500) {
+ toast(msg)
+ reject('500')
+ } else if (code !== 200) {
+ toast(msg)
+ reject(code)
}
- })
+ },
+ fail: (error) => {
+ let { message } = error
+ if (message == 'Network Error') {
+ message = '后端接口连接异常'
+ } else if (message.includes('timeout')) {
+ message = '系统接口请求超时'
+ } else if (message.includes('Request failed with status code')) {
+ message = '系统接口' + message.substr(message.length - 3) + '异常'
+ }
+ toast(message)
+ reject(error)
+ }
+ })
})
}
-export default upload
+