liujiaxin 1 hafta önce
ebeveyn
işleme
2dbc61158a
100 değiştirilmiş dosya ile 8196 ekleme ve 687 silme
  1. 0 329
      components/ThreeDSwiper.vue
  2. 148 0
      components/ThreeItemSwiper.vue
  3. 275 0
      node_modules/mescroll-uni/README.md
  4. 55 0
      node_modules/mescroll-uni/components/mescroll-down.css
  5. 47 0
      node_modules/mescroll-uni/components/mescroll-down.vue
  6. 118 0
      node_modules/mescroll-uni/components/mescroll-empty.vue
  7. 83 0
      node_modules/mescroll-uni/components/mescroll-top.vue
  8. 47 0
      node_modules/mescroll-uni/components/mescroll-up.css
  9. 39 0
      node_modules/mescroll-uni/components/mescroll-up.vue
  10. 19 0
      node_modules/mescroll-uni/mescroll-body.css
  11. 403 0
      node_modules/mescroll-uni/mescroll-body.vue
  12. 15 0
      node_modules/mescroll-uni/mescroll-i18n.js
  13. 57 0
      node_modules/mescroll-uni/mescroll-mixins.js
  14. 64 0
      node_modules/mescroll-uni/mescroll-uni-option.js
  15. 36 0
      node_modules/mescroll-uni/mescroll-uni.css
  16. 799 0
      node_modules/mescroll-uni/mescroll-uni.js
  17. 480 0
      node_modules/mescroll-uni/mescroll-uni.vue
  18. 47 0
      node_modules/mescroll-uni/mixins/mescroll-comp.js
  19. 66 0
      node_modules/mescroll-uni/mixins/mescroll-more-item.js
  20. 74 0
      node_modules/mescroll-uni/mixins/mescroll-more.js
  21. 41 0
      node_modules/mescroll-uni/package.json
  22. 109 0
      node_modules/mescroll-uni/wxs/mixins.js
  23. 92 0
      node_modules/mescroll-uni/wxs/renderjs.js
  24. 268 0
      node_modules/mescroll-uni/wxs/wxs.wxs
  25. 136 0
      node_modules/uview-plus/changelog.md
  26. 2 1
      node_modules/uview-plus/components/u-barcode/u-barcode.vue
  27. 10 7
      node_modules/uview-plus/components/u-calendar/calendar.js
  28. 1 1
      node_modules/uview-plus/components/u-calendar/header.vue
  29. 11 3
      node_modules/uview-plus/components/u-calendar/month.vue
  30. 9 0
      node_modules/uview-plus/components/u-calendar/props.js
  31. 17 7
      node_modules/uview-plus/components/u-calendar/u-calendar.vue
  32. 333 0
      node_modules/uview-plus/components/u-cascader/u-cascader.vue
  33. 34 23
      node_modules/uview-plus/components/u-cate-tab/u-cate-tab.vue
  34. 1 1
      node_modules/uview-plus/components/u-checkbox-group/u-checkbox-group.vue
  35. 5 4
      node_modules/uview-plus/components/u-checkbox/u-checkbox.vue
  36. 109 0
      node_modules/uview-plus/components/u-choose/u-choose.vue
  37. 5 3
      node_modules/uview-plus/components/u-city-locate/u-city-locate.vue
  38. 5 5
      node_modules/uview-plus/components/u-code/code.js
  39. 1095 0
      node_modules/uview-plus/components/u-color-picker/u-color-picker.vue
  40. 7 5
      node_modules/uview-plus/components/u-copy/u-copy.vue
  41. 406 0
      node_modules/uview-plus/components/u-coupon/u-coupon.vue
  42. 23 8
      node_modules/uview-plus/components/u-cropper/u-cropper.vue
  43. 5 3
      node_modules/uview-plus/components/u-datetime-picker/datetimePicker.js
  44. 0 0
      node_modules/uview-plus/components/u-datetime-picker/dayjs.esm.min.js
  45. 5 0
      node_modules/uview-plus/components/u-datetime-picker/props.js
  46. 4 3
      node_modules/uview-plus/components/u-datetime-picker/u-datetime-picker.vue
  47. 16 15
      node_modules/uview-plus/components/u-empty/u-empty.vue
  48. 432 0
      node_modules/uview-plus/components/u-goods-sku/u-goods-sku.vue
  49. 5 4
      node_modules/uview-plus/components/u-input/input.js
  50. 11 1
      node_modules/uview-plus/components/u-input/props.js
  51. 37 5
      node_modules/uview-plus/components/u-input/u-input.vue
  52. 3 2
      node_modules/uview-plus/components/u-keyboard/keyboard.js
  53. 2 1
      node_modules/uview-plus/components/u-link/link.js
  54. 2 1
      node_modules/uview-plus/components/u-loading-page/loadingPage.js
  55. 4 3
      node_modules/uview-plus/components/u-loadmore/loadmore.js
  56. 0 0
      node_modules/uview-plus/components/u-markdown/marked.esm.js
  57. 4 3
      node_modules/uview-plus/components/u-modal/modal.js
  58. 2 1
      node_modules/uview-plus/components/u-no-network/noNetwork.js
  59. 7 5
      node_modules/uview-plus/components/u-no-network/u-no-network.vue
  60. 3 1
      node_modules/uview-plus/components/u-pagination/u-pagination.vue
  61. 19 0
      node_modules/uview-plus/components/u-pdf-reader/props.js
  62. 52 0
      node_modules/uview-plus/components/u-pdf-reader/u-pdf-reader.vue
  63. 10 4
      node_modules/uview-plus/components/u-picker-data/u-picker-data.vue
  64. 6 4
      node_modules/uview-plus/components/u-picker/picker.js
  65. 6 1
      node_modules/uview-plus/components/u-picker/props.js
  66. 2 1
      node_modules/uview-plus/components/u-picker/u-picker.vue
  67. 59 0
      node_modules/uview-plus/components/u-popover/props.js
  68. 95 0
      node_modules/uview-plus/components/u-popover/u-popover.vue
  69. 6 2
      node_modules/uview-plus/components/u-popup/popup.js
  70. 21 1
      node_modules/uview-plus/components/u-popup/props.js
  71. 126 9
      node_modules/uview-plus/components/u-popup/u-popup.vue
  72. 622 0
      node_modules/uview-plus/components/u-poster/u-poster.vue
  73. 8 6
      node_modules/uview-plus/components/u-pull-refresh/u-pull-refresh.vue
  74. 18 12
      node_modules/uview-plus/components/u-qrcode/u-qrcode.vue
  75. 3 2
      node_modules/uview-plus/components/u-read-more/readMore.js
  76. 152 134
      node_modules/uview-plus/components/u-search/props.js
  77. 3 2
      node_modules/uview-plus/components/u-search/search.js
  78. 14 2
      node_modules/uview-plus/components/u-search/u-search.vue
  79. 2 1
      node_modules/uview-plus/components/u-section/section.js
  80. 463 0
      node_modules/uview-plus/components/u-short-video/u-short-video.vue
  81. 3 2
      node_modules/uview-plus/components/u-signature/u-signature.vue
  82. 5 1
      node_modules/uview-plus/components/u-slider/props.js
  83. 2 1
      node_modules/uview-plus/components/u-slider/slider.js
  84. 8 3
      node_modules/uview-plus/components/u-slider/u-slider.vue
  85. 8 1
      node_modules/uview-plus/components/u-steps-item/u-steps-item.vue
  86. 6 2
      node_modules/uview-plus/components/u-tabbar-item/props.js
  87. 2 1
      node_modules/uview-plus/components/u-tabbar-item/tabbarItem.js
  88. 44 4
      node_modules/uview-plus/components/u-tabbar-item/u-tabbar-item.vue
  89. 10 0
      node_modules/uview-plus/components/u-tabbar/props.js
  90. 3 1
      node_modules/uview-plus/components/u-tabbar/tabbar.js
  91. 7 0
      node_modules/uview-plus/components/u-tabbar/u-tabbar.vue
  92. 3 2
      node_modules/uview-plus/components/u-toolbar/toolbar.js
  93. 5 0
      node_modules/uview-plus/components/u-tooltip/props.js
  94. 2 1
      node_modules/uview-plus/components/u-tooltip/tooltip.js
  95. 42 35
      node_modules/uview-plus/components/u-tooltip/u-tooltip.vue
  96. 7 5
      node_modules/uview-plus/components/u-upload/u-upload.vue
  97. 5 2
      node_modules/uview-plus/index.js
  98. 54 0
      node_modules/uview-plus/libs/i18n/index.js
  99. 80 0
      node_modules/uview-plus/libs/i18n/locales/de.json
  100. 80 0
      node_modules/uview-plus/libs/i18n/locales/en.json

+ 0 - 329
components/ThreeDSwiper.vue

@@ -1,329 +0,0 @@
-<template>
-  <view class="three-item-swiper">
-    <!-- 轮播容器 -->
-    <view class="swiper-container" :style="{ height: containerHeight + 'px' }">
-      <view class="swiper-wrapper" 
-            :style="{ 
-              transform: `translateX(${translateX}px)`,
-              transition: isAnimating ? 'transform 0.3s ease-out' : 'none'
-            }">
-        <!-- 轮播项 -->
-        <view class="swiper-item" 
-              v-for="(item, index) in items" 
-              :key="index"
-              :style="getItemStyle(index)">
-          <image :src="item.imgUrl" mode="cover" class="item-image" :alt="item.title"></image>
-          <view class="item-title" v-if="item.title">{{ item.title }}</view>
-        </view>
-      </view>
-    </view>
-
-    <!-- 指示点 -->
-    <view class="indicators">
-      <view class="indicator"
-            v-for="(item, index) in items"
-            :key="index"
-            :class="{ active: index === currentIndex }"
-            @click="switchTo(index)"></view>
-    </view>
-  </view>
-</template>
-
-<script>
-export default {
-  props: {
-    // 轮播数据
-    items: {
-      type: Array,
-      required: true,
-      default: () => []
-    },
-    // 容器高度(px)
-    containerHeight: {
-      type: Number,
-      default: 400
-    },
-    // 自动轮播间隔(ms),0为不自动轮播
-    autoPlay: {
-      type: Number,
-      default: 3000
-    },
-    // 中间项放大比例
-    scaleRatio: {
-      type: Number,
-      default: 1.2
-    }
-  },
-  data() {
-    return {
-      currentIndex: 0,       // 当前居中索引
-      startX: 0,             // 触摸起始X
-      moveX: 0,              // 移动X
-      translateX: 0,         // 容器偏移量
-      itemWidth: 0,          // 项宽度
-      isAnimating: false,    // 是否动画中
-      isDragging: false,     // 是否拖拽中
-      timer: null            // 自动播放定时器
-    };
-  },
-  watch: {
-    items() {
-      this.currentIndex = 0;
-      this.$nextTick(() => {
-        this.calculateLayout();
-      });
-      this.resetAutoPlay();
-    },
-    currentIndex() {
-      this.updatePosition(true);
-    },
-    autoPlay() {
-      this.resetAutoPlay();
-    }
-  },
-  mounted() {
-    // 初始化布局
-    this.$nextTick(() => {
-      this.calculateLayout();
-    });
-    
-    // 监听窗口尺寸变化
-    uni.onWindowResize(() => {
-      this.$nextTick(() => {
-        this.calculateLayout();
-      });
-    });
-    
-    // 初始化自动播放
-    this.initAutoPlay();
-  },
-  beforeDestroy() {
-    this.clearTimer();
-    uni.offWindowResize();
-  },
-  methods: {
-    // 计算布局
-    calculateLayout() {
-      // 获取容器宽度
-      const query = uni.createSelectorQuery().in(this);
-      query.select('.swiper-container').boundingClientRect(data => {
-        if (data) {
-          // 计算每个项的宽度(容器的一半)
-          this.itemWidth = data.width / 2;
-          this.updatePosition(false);
-        }
-      }).exec();
-    },
-    
-    // 更新位置
-    updatePosition(animate = true) {
-      if (!this.itemWidth) return;
-      
-      this.isAnimating = animate;
-      // 计算偏移量,让当前项居中
-      this.translateX = this.itemWidth - this.currentIndex * this.itemWidth;
-    },
-    
-    // 获取每个项的样式
-    getItemStyle(index) {
-      if (!this.itemWidth) return {};
-      
-      // 计算与当前项的距离
-      const distance = Math.abs(index - this.currentIndex);
-      let scale = 1;
-      let zIndex = 1;
-      let opacity = 0.8;
-      
-      // 中间项放大
-      if (distance === 0) {
-        scale = this.scaleRatio;
-        zIndex = 10;
-        opacity = 1;
-      }
-      
-      return {
-        width: `${this.itemWidth}px`,
-        transform: `scale(${scale})`,
-        zIndex,
-        opacity,
-        transition: this.isAnimating ? 'all 0.3s ease-out' : 'none'
-      };
-    },
-    
-    // 触摸开始
-    handleTouchStart(e) {
-      if (this.items.length <= 1) return;
-      
-      this.isDragging = true;
-      this.isAnimating = false;
-      this.startX = e.touches[0].clientX;
-      this.clearTimer(); // 停止自动播放
-    },
-    
-    // 触摸移动
-    handleTouchMove(e) {
-      if (!this.isDragging || this.items.length <= 1) return;
-      
-      this.moveX = e.touches[0].clientX;
-      const diffX = this.moveX - this.startX;
-      
-      // 计算临时偏移量(增加阻力感)
-      this.translateX = (this.itemWidth - this.currentIndex * this.itemWidth) + diffX * 0.8;
-    },
-    
-    // 触摸结束
-    handleTouchEnd() {
-      if (!this.isDragging || this.items.length <= 1) return;
-      
-      this.isDragging = false;
-      const diffX = this.moveX - this.startX;
-      const threshold = this.itemWidth / 3; // 滑动阈值
-      
-      // 判断滑动方向
-      if (diffX > threshold) {
-        // 向右滑动,上一项
-        this.prev();
-      } else if (diffX < -threshold) {
-        // 向左滑动,下一项
-        this.next();
-      } else {
-        // 未达到阈值,回弹
-        this.updatePosition(true);
-      }
-      
-      this.resetAutoPlay(); // 恢复自动播放
-    },
-    
-    // 下一项
-    next() {
-      if (this.currentIndex >= this.items.length - 1) {
-        this.currentIndex = 0; // 循环到第一项
-      } else {
-        this.currentIndex++;
-      }
-    },
-    
-    // 上一项
-    prev() {
-      if (this.currentIndex <= 0) {
-        this.currentIndex = this.items.length - 1; // 循环到最后一项
-      } else {
-        this.currentIndex--;
-      }
-    },
-    
-    // 切换到指定索引
-    switchTo(index) {
-      if (index === this.currentIndex) return;
-      this.currentIndex = index;
-      this.resetAutoPlay();
-    },
-    
-    // 初始化自动播放
-    initAutoPlay() {
-      if (this.autoPlay > 0 && this.items.length > 1) {
-        this.timer = setInterval(() => {
-          this.next();
-        }, this.autoPlay);
-      }
-    },
-    
-    // 重置自动播放
-    resetAutoPlay() {
-      this.clearTimer();
-      this.initAutoPlay();
-    },
-    
-    // 清除定时器
-    clearTimer() {
-      if (this.timer) {
-        clearInterval(this.timer);
-        this.timer = null;
-      }
-    }
-  }
-};
-</script>
-
-<style scoped>
-.three-item-swiper {
-  position: relative;
-  width: 100%;
-  overflow: hidden;
-}
-
-/* 轮播容器 */
-.swiper-container {
-  position: relative;
-  width: 100%;
-  overflow: hidden;
-}
-
-/* 轮播轨道 */
-.swiper-wrapper {
-  position: absolute;
-  top: 0;
-  left: 0;
-  height: 100%;
-  display: flex;
-}
-
-/* 轮播项 */
-.swiper-item {
-  height: 100%;
-  flex-shrink: 0;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  position: relative;
-}
-
-/* 图片样式 */
-.item-image {
-  width: 100%;
-  height: 100%;
-  border-radius: 16rpx;
-  box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.2);
-}
-
-/* 标题样式 */
-.item-title {
-  position: absolute;
-  bottom: 20rpx;
-  left: 0;
-  right: 0;
-  text-align: center;
-  color: #fff;
-  font-size: 32rpx;
-  text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.5);
-  padding: 0 20rpx;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-}
-
-/* 指示点容器 */
-.indicators {
-  display: flex;
-  justify-content: center;
-  gap: 12rpx;
-  padding: 20rpx 0;
-}
-
-/* 指示点样式 */
-.indicator {
-  width: 16rpx;
-  height: 16rpx;
-  border-radius: 50%;
-  background-color: #ddd;
-  transition: all 0.3s ease;
-}
-
-/* 激活状态指示点 */
-.indicator.active {
-  width: 40rpx;
-  border-radius: 8rpx;
-  background-color: #007aff;
-}
-</style>

+ 148 - 0
components/ThreeItemSwiper.vue

@@ -0,0 +1,148 @@
+<template>
+	<view class="custom-swiper-wrapper">
+		<swiper class="custom-swiper" :current="activeIndex" @change="onSwiperChange" circular :duration="300"
+			indicator-dots="false" autoplay="false" display-multiple-items="1" :previous-margin="outerMargin"
+			:next-margin="outerMargin" indicator-color="rgba(255, 255, 255, 0.5)" indicator-active-color="#FFEB66">
+			<swiper-item v-for="(item, index) in products" :key="index">
+				<!-- 用容器包裹item,通过padding控制内部间距 -->
+				<view class="item-container">
+					<!-- <view class="swiper-item" :class="{ 'swiper-item-active': index === currentCenterIndex }">
+						<image class="item-image" :src="item.image" mode="aspectFit"></image>
+						<view class="item-text" v-if="index === currentCenterIndex">
+							<view class="text-title">{{ item.title }}</view>
+							<view class="text-desc">{{ item.desc }}</view>
+						</view>
+					</view> -->
+					<view class="swiper-item" :class="{ 'swiper-item-active': index === currentCenterIndex }">
+						<image class="item-image" :src="item.imgUrl" mode="aspectFit"></image>
+						<view class="item-text" v-if="index === currentCenterIndex">
+							<view class="text-title">{{ item.prizeLevel }}等奖</view>
+							<view class="text-desc">{{ item.productName }}</view>
+						</view>
+					</view>
+				</view>
+			</swiper-item>
+		</swiper>
+	</view>
+</template>
+
+<script>
+	import {
+		string
+	} from 'uview-plus/libs/function/test';
+
+	export default {
+		props: {
+			products: {
+				type: Array,
+				default: "",
+			}
+		},
+		data() {
+			return {
+				activeIndex: 0, // 初始激活项索引(建议从1开始,避免循环时偏移)
+				outerMargin: '120rpx', // 轮播左右外部间距
+				// swiperData: [{
+				// 		image: "/static/images/zfb.png",
+				// 		title: "二等奖",
+				// 		desc: "纳美科学高浓度小苏打牙膏",
+				// 	},
+				// 	{
+				// 		image: "/static/images/red_bg.png",
+				// 		title: "二等奖",
+				// 		desc: "纳美科学高浓度小苏打牙膏",
+				// 	},
+				// 	{
+				// 		image: "/static/images/share.png",
+				// 		title: "二等奖",
+				// 		desc: "纳美科学高浓度小苏打牙膏",
+				// 	},
+				// 	{
+				// 		image: "/static/images/integral.png",
+				// 		title: "二等奖",
+				// 		desc: "纳美科学高浓度小苏打牙膏",
+				// 	}
+				// ],
+			};
+		},
+		computed: {
+			// 计算中心项索引(解决循环轮播时的索引匹配问题)
+			currentCenterIndex() {
+				if (this.products.length === 0) return 0;
+				return this.activeIndex % this.products.length;
+			}
+		},
+		methods: {
+			onSwiperChange(e) {
+				this.activeIndex = e.detail.current;
+			},
+		},
+	};
+</script>
+
+<style scoped>
+	.custom-swiper-wrapper {
+		width: 100%;
+		overflow: hidden;
+		/* padding: 20rpx 0; */
+	}
+
+	.custom-swiper {
+		width: 100%;
+		height: 500rpx;
+		/* 固定高度,避免item被截断 */
+	}
+
+	/* 控制item之间的间距容器 */
+	.item-container {
+		padding: 0 20rpx;
+		/* 左右内边距,控制item之间的间距 */
+	}
+
+	/* 基础item样式 */
+	.swiper-item {
+		width: 100% !important;
+		/* 宽度占满容器,配合padding控制实际宽度 */
+		height: 348rpx;
+		background: #FFFFFF;
+		box-shadow: 0rpx 12rpx 19rpx 2rpx rgba(219, 73, 22, 0.6);
+		border-radius: 24rpx;
+		border: 4rpx solid #FFCA96;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		box-sizing: border-box;
+		padding: 20rpx 0;
+		transition: all 0.3s ease;
+	}
+
+	/* 中心项激活样式 */
+	.swiper-item-active {
+		transition: all 0.5s ease;
+		height: 420rpx;
+		/* 中心项高度增加 */
+		z-index: 10;
+		/* 确保中心项在最上层 */
+	}
+
+	.item-image {
+		width: 280rpx;
+		height: 280rpx;
+	}
+
+	.item-text {
+		text-align: center;
+	}
+
+	.text-title {
+		font-weight: 500;
+		font-size: 32rpx;
+		color: #222222;
+		margin: 20rpx 0 10rpx;
+	}
+
+	.text-desc {
+		font-size: 24rpx;
+		color: #757575;
+	}
+</style>

+ 275 - 0
node_modules/mescroll-uni/README.md

@@ -0,0 +1,275 @@
+## mescroll --【wxs+renderjs实现】高性能的下拉刷新上拉加载组件
+1. mescroll的uni版本 是专门用在uni-app的下拉刷新和上拉加载的组件, 支持一套代码编译到iOS、Android、H5、小程序等多个平台
+
+2. mescroll的uni版本 继承了mescroll.js的实用功能: 自动处理分页, 自动控制无数据, 空布局提示, 回到顶部按钮 ..
+
+3. mescroll的uni版本 丰富的案例, 自由灵活的api, 超详细的注释, 可让您快速自定义真正属于自己的下拉上拉组件
+
+<br/>
+
+
+## 最新文档(1.3.7版本): <a href="http://www.mescroll.com/uni.html">http://www.mescroll.com/uni.html</a>
+2021-04-13 by mescroll (文档可能会有缓存,建议打开时刷新一下)
+
+
+## 近期已更新优化的内容:
+1. 微信小程序, app, h5使用高性能wxs和renderjs, 下拉刷新更流畅丝滑, 尤其能明显解决Android小程序下拉卡顿的问题
+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)
+<br/>
+<br/>
+<a href="https://ext.dcloud.net.cn/plugin?id=343&update_log">查看更多 ... </a>
+
+
+### 更新记录:
+
+---
+#### 1.3.7版本 (2021/04/13)  
+1. 新增`mescroll-swiper-sticky.vue`的示例, 轮播吸顶菜单导航  
+2. 新增`mescroll-empty.vue`的示例, 单独使用空布局组件  
+3. 简化tabs在具体项目中的使用,并简化对应的示例  
+4. mescroll-uni 支持动态禁止滚动的属性 disableScroll (注: mescroll-body不支持)  
+-by 小瑾同学
+
+---
+#### 1.3.5版本 (2021/04/10)  
+1. 新增 `mescroll-i18n.vue` 的示例, 支持国际化的配置  
+2. down的`beforeEndDelay`不再默认配置 // (显示加载成功/失败的时长, android小程序设置此项结束下拉会卡顿, 配置后请注意测试)  
+3. mescroll-body-part.vue 和 mescroll-uni-part.vue 的示例新增参考代码: `异步加载左侧菜单(超简单)`  
+4. mescroll的极简示例新增参考代码: `先请求其他接口,再触发upCallback,无需配置auto为false(超简单)`  
+5. 修复mescroll-swiper的tabs在app或h5错位的问题  
+-by 小瑾同学
+
+---
+#### 1.3.3版本 (2020/09/15)  
+1. 新增下拉刷新成功和失败的文本配置, 可在 mescroll-uni-option.js 配置修改  
+1. 新增 me-video 视频组件, 解决APP端视频下拉悬浮错位的问题, 参考最新的 mescroll-options.vue 示例  
+2. 更新 mescroll-comp.vue 的示例, 支持 mescroll-body 子子..子组件 , 包括 mescroll-more.vue 也支持写在子子..子组件  
+3. mescroll-uni 的 down.offset 由原来的 80 调整为 150 , 避免超快速滑动列表到底部,偶尔出现无法再翻页的问题 (mescroll-body无此问题)  
+4. 修复 mescroll-more.vue 和 mescroll-swiper.vue 的示例在字节跳动小程序2.0.0以上新版编辑器无法正常运行的问题  
+5. 修复淘宝和美团的示例下拉卡顿的问题 ( 案例需重新下载 )  
+-by mescroll  
+
+---
+#### 1.3.2版本 (2020/08/05)
+1. mescroll-body新增sticky属性, 简化吸顶悬浮sticky.vue的示例  
+2. QQ小程序支持wxs,解决QQ小程序卡顿和无法隐藏加载状态的问题  
+3. mescroll.scrollTo(y)的y支持css选择器, 包括跨自定义组件的后代选择器, 支持滚动到子组件的view (参考 mescroll-options.vue)  
+-by 小瑾同学
+
+---
+#### 1.3.1版本 (2020/07/27)
+1. 修复Android小程序下拉刷新时, image 和 swiper 脱离文档流的问题  
+2. 修复H5端, 当配置down.use为false时, 返回其他页面无法滚动的问题  
+3. mescroll-comp.js支持mescroll-body写在子子子...组件中 (以前版本仅支持写在一级子组件) 
+4. 吸顶悬浮提供了sticky样式和监听滚动条实现的示例: sticky.vue 和 sticky-scroll.vue (推荐使用sticky样式实现)  
+-by 小瑾同学
+
+---
+#### 1.3.0版本 (2020/07/10)
+1. 微信小程序, app, h5使用高性能wxs和renderjs, 下拉刷新更流畅丝滑, 尤其能明显解决Android小程序下拉卡顿的问题
+2. 使用wxs和renderjs优化所有案例, 尤其是中高级案例, 建议大家重新下载最新的案例
+3. 废弃down的isBounce配置, 已通过renderjs自动判断, 无需配置mescroll-touch
+4. 废弃down的fps配置, 已通过wxs提高性能, 无需手动节流
+5. 新增 "局部区域滚动" 的案例: mescroll-body-part.vue 和 mescroll-uni-part.vue
+6. 解决swiper切换时,有时会触发下拉刷新的问题, 已避免swiper和下拉刷新相互冲突
+7. 解决钉钉小程序mescroll-uni下拉刷新有时无法触发的问题
+8. 解决上拉加载进度在部分Android手机显示不全的问题
+9. 提高 me-tabs 组件在部分Android手机的兼容性  
+-by 小瑾同学
+
+---
+#### 1.2.8版本 (2020/06/28)
+1. 解决 mescroll-uni 再某些情况下列表数据渲染不完全的问题 ( mescroll-body无此问题 )
+2. 优化 me-tabs 组件, 使用支付宝小程序可隐藏滚动条, 同时修复字节跳动小程序tab切换时渲染延迟的问题
+-by 小瑾同学
+
+---
+#### 1.2.7版本 (2020/06/24)
+1. 上拉加载结束隐藏底部加载区域,避免加载区域占位
+2. h5端的tab页默认偏移TabBar的高度,避免h5端列表被TabBar遮住 (如不想偏移,可通过配置 :bottombar="false" 取消)
+3. 新增 me-tabs 组件,tabs支持水平滑动; 优化mescroll-more和mescroll-swiper的案例, 顶部tab支持水平滑动
+
+---
+#### 1.2.6版本 (2020/06/16)
+1. mescroll-uni 和 mescroll-body 的 scrollTo 正式支持 scroll-into-view (传入的 y 为view的id即可生效)
+2. topbar 顶部是否预留状态栏的高度, 默认false; 这个版本还可支持设置状态栏背景: 如 '#ffff00', 'url(xxx)', 'linear-gradient(xx)'
+3. down.bgColor 和 up.bgColor 加载区域的背景,不仅支持色值, 而且还是支持背景图和渐变: 如 'url(xxx)', 'linear-gradient(xx)'
+4. 通过css方式适配iPhoneX, 比之前通过style方式具有更好的兼容性, 也同时消除了edge浏览器重复设置相同属性的警告
+5. 移除非必须的标签选择器,避免微信小程序提示组件内不可使用标签选择器的警告
+6. 修复当配置up的use为false时,默认的下拉刷新有时候无法自动隐藏的问题
+7. 修复当配置down的native为true时,auto失效的问题
+8. 修复空布局在某些情况下图片和文本错位的问题
+
+---
+#### 1.2.5版本 (2020/03/15)
+1. mescroll-body 的 props 支持 safearea 的配置 (需要适配iPhoneX时,配置为 true 即可, 默认 false)
+2. mescroll-uni 的 scrollTo 支持 scroll-into-view (当传入的 y 为view的id时, 即可生效)
+3. 新增 下拉加载聊天记录的案例 list-msg.vue, 类似微信QQ的聊天记录
+
+---
+#### 1.2.4版本 (2020/03/11)
+1. down和up分别新增 bgColor 的配置: 下拉区域背景颜色,默认"transparent"
+2. down和up分别新增 textColor 的配置: 下拉文本的颜色,默认"gray" (当bgColor配置了颜色,而textColor未配置时,则自动默认为白色)
+3. 调整mescroll-more-item.js, 使mescroll-more的案例支持初始化tabIndex大于0的tab页
+4. mescroll-body支持isBounce的配置, 解决H5下拉刷新失效的问题
+5. 解决mescroll-body在Android真机小程序下拉卡顿的问题 (mescroll-uni无此问题)
+
+---
+#### 1.2.3版本 (2020/02/18)
+新增3个mescroll的mixins, 极大简化了mescroll-comp, mescroll-more, mescroll-swiper的案例
+
+
+#### 1.2.2版本 (2020/02/16)
+1. 调整mescroll-more和mescroll-swiper的案例,确保各小程序平台可正确获取到mescroll对象  
+2. 修复字节跳动小程序初始化时的异常警告: <a href="http://www.mescroll.com/qa.html?v=20200216#q26">详情</a>
+
+
+#### 1.2.1版本 (2020/02/08)
+1. 新增 &lt;mescroll-body&gt; 组件, 用来填补 &lt;mescroll-uni&gt; 的不足.
+2. mescroll-body基于原生页的滚动,支持写入原生组件和fixed元素,不必固定高度,不必配置pages.json,简单性能好.
+3. mescroll-body可配置down的native:true, 可直接代理系统自带的下拉组件, 参考 mescroll-native 示例
+4. 新增mescroll-mixins.js,简化代码,兼容更多小程序平台
+5. 修复字节跳动小程序和支付宝小程序的部分异常警告
+
+#### 1.2.0版本 (2020/01/06)
+1. mescroll-uni.vue的props新增height. // 简单快捷设置mescroll的高度, 此项有值,则不使用fixed. 
+	使用场景: 当在弹窗或浮层中使用fixed固定mescroll高度比较麻烦时, 配置此项就很方便了
+	支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight
+2. mescroll-uni.vue的props新增safearea. // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用,此项值对回到顶部按钮生效)
+3. mescroll-uni.vue的props中,top和bottom不仅仅支持数字, 还支持"20rpx", "20px", "20%"格式的值
+4. 补充锁定上拉加载mescroll.lockUpScroll的方法
+5. down.fps默认值提高至80
+6. 独立出空布局的组件&lt;mescroll-empty&gt;, 以便在不使用mescroll的界面也能统一管理空布局
+```
+<template>
+		<view>
+			// 基本用法
+			<mescroll-empty v-if="list.length==0"></mescroll-empty>
+			// 所有配置项 (option同up.empty的配置一致)
+			<mescroll-empty v-if="isShowEmpty" :option="optEmpty" @emptyclick="emptyClick"></mescroll-empty>
+		</view>
+	</template>
+	
+	<script>
+		import MescrollEmpty from '@/components/mescroll-uni/components/mescroll-empty.vue';
+		export default {
+			components: {
+				MescrollEmpty
+			},
+			...
+		}
+```
+7. 为了更快速自定义回到顶部按钮, up.toTop新增以下配置项:
+```
+	toTop: {
+		zIndex: 9990, // fixed定位z-index值
+		right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+		bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+		safearea: false, // bottom的偏移量是否加上底部安全区的距离, 默认false. 需要适配iPhoneX时使用 (具体的界面如果不配置此项,则取mescroll-uni.vue的safearea值)
+		width: 72, // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+		radius: "50%", // 圆角, 默认"50%" (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+		left: null, // 到左边的距离, 默认null. 此项有值时,right不生效. (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+		duration: 300, // 回到顶部的动画时长,默认300ms (当值为0或300则使用系统自带回到顶部,更流畅; 其他值则通过step模拟,部分机型可能不够流畅,所以非特殊情况不建议修改此项)
+		src: null, // (已有配置)
+		offset: 1000 // (已有配置)
+	}
+```
+
+
+---
+#### 1.1.9版本 (2019/12/16)
+1. 解决左右滑动屏幕,某些情况下会触发上拉回调的bug
+2. mescroll-uni.vue的props新增topbar: top的偏移量是否加上状态栏高度 (当fixed为false时不生效; 使用场景:取消原生导航栏时,配置此项可自动加上状态栏高度的偏移量)
+
+---
+#### 1.1.8版本 (2019/11/01)
+1. 解决Android小程序快速上拉可能会导致不断翻页直到没有数据为止的bug
+2. 过滤某些情况下touch的警告输出
+
+---
+#### 1.1.7版本 (2019/10/15)
+1. 重点解决下拉刷新卡顿,特别是Android小程序卡顿的问题
+2. 补充之前版本遗漏的mescroll.lockUpScroll方法
+3. 支持数据不满屏时,仍可触发翻页的回调
+
+---
+#### 1.1.5版本 (2019/07/25)
+1. up新增isBounce的配置, 默认值为false. 目的是解决H5在ios下拉出现灰色背景和android端无法正常下拉的问题. 详见: <a href="http://www.mescroll.com/qa.html?v=190725#q25">http://www.mescroll.com/qa.html?v=190725#q25</a>
+2. 修复setClientHeight某些情况下会提示undefined的问题
+3. 修复在mescroll-uni-option.js配置page会导致分页异常的问题
+
+---
+#### 1.1.2版本 (2019/07/19)
+一. 修复和优化的内容:
+1. 修复列表不满屏时,无法上拉的问题
+2. 修复列表滚动到底部,有时无法上拉的问题
+3. 修复快速滚动到顶部时,滚动条的位置有时不为0的问题
+4. 修复配置page.num不生效的问题
+5. 修复小程序android端下拉卡顿的问题 (可配置down的supply,复杂的列表可适当调大值)
+6. 修复H5多mescroll的情况下回到顶部按钮错位的问题
+7. 优化和简化逻辑,去除冗余代码
+
+二. 完善获取节点信息的方式:
+1. 获取mescroll的唯一元素id: mescroll.viewId (可通过uni.createSelectorQuery进一步获取更多信息)
+2. 获取滚动内容的高度: mescroll.getScrollHeight()
+3. 获取mescroll的高度: mescroll.getClientHeight()
+4. 获取滚动条位置: mescroll.getScrollTop()
+5. 获取到底部的距离: mescroll.getScrollBottom()
+6. 获取滚动的方向: mescroll.isScrollUp // true向上滑; false向下滑
+7. 更多信息详见mescroll-options的示例
+
+---
+#### 1.1.1版本 (2019/07/16)
+1. 修复滚动到顶部,有时无法下拉的问题
+2. 修复禁止上拉时,列表底部有空白区域的问题
+
+---
+#### 1.1.0版本 (重要版本) (2019/07/01)
+一. 简化使用:
+1. **无需**在page.json中配置onReachBottomDistance<br/>
+2. **无需**在页面中注册onReachBottom 和 onPageScroll<br/>
+3. 初始化时@init**不再**是必须配置项<br/>
+4. 极大的**简化** list-mescroll-more.vue 的案例<br/>
+5. 全面**支持swiper**,详见 mescroll-swiper.vue 的案例<br/>
+6. 所有基础案例都有所简化, **建议**重新下载参考. (以前版本向下兼容)<br/> 
+7. 所有中高级案例自定义的部分,改动比较大, **必须**重新下载参考. (不兼容以前版本)
+
+二. 优化性能:
+1. 支持fixed定位,可实现局部**区域滚动**<br/>
+2. 支持swiper,scrollview**嵌套**使用<br/>
+3. 优化下拉逻辑,修复下拉刷新卡顿和抖动的问题<br/>
+4. 优化默认样式,避免某些情况出现双滚动条的问题<br/>
+5. up废弃errDistance配置, 内部已优化处理相关问题
+
+三. 新增配置:
+1. down和up新增fps节流配置.(默认40fps, 值越大每秒刷新频率越高)<br/>
+2. up新增offset配置: 距底部多远时,触发upCallback (默认80, 代替page.json的onReachBottomDistance)<br/>
+3. up新增onScroll配置: 是否监听滚动事件,默认false<br/>
+4. < mescroll-uni 新增 @scroll="scroll" 获取滚动条的位置和滚动方向, 需配置up的onScroll为true, 详见 mescroll-options.vue 的示例<br/>
+5. < mescroll-uni 新增 :fixed="true/false" 是否支持fixed定位, 默认 true. 
+<br/> 当:fixed="true", 此时 :top 和 :bottom 为 fixed 的 top 和 bottom
+<br/> 当:fixed="false", 此时 :top 和 :bottom 为 padding-top 和 padding-bottom
+
+
+---
+#### 1.0.3版本 (2019/06/13)
+1. 默认设置page高度100%,使列表不满屏的时候,仍可下拉刷新<br/>
+2. 加入-webkit-overflow-scrolling: touch, 编译到H5和APP,使iOS列表滚动更流畅<br/>
+3. mescroll-empty加入box-sizing: border-box, 修复图标和文本不居中的问题<br/>
+4. 上拉配置新增errDistance,默认110 // mescroll.endErr()的时候需往上滑动一段距离,使其能再次触发上拉加载 (已在1.1.0版本废弃)<br/>
+5. mescroll.endErr(errDistance) 新增的参数 errDistance; 可单独配置异常往上滑动的距离 (已在1.1.0版本废弃)<br/>
+6. 修改了list-mescroll-more的160行为mescroll.endErr(0);(已在1.1.0版本优化)<br/>
+7. 新增mescroll.scrollTo(y,t) 滚动到指定的位置; 本质调用的是uni.pageScrollTo
+
+---
+#### 1.0.2版本 (2019/05/28)  
+1. 组件根元素加入mescroll-uni的样式,empty新增fixed等配置项; <br/>
+2. 修复list-mescroll-more案例切换tabs,某些情况的page.num会错乱的问题

+ 55 - 0
node_modules/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);
+	}
+}

+ 47 - 0
node_modules/mescroll-uni/components/mescroll-down.vue

@@ -0,0 +1,47 @@
+<!-- 下拉刷新区域 -->
+<template>
+	<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background-color':mOption.bgColor,'color':mOption.textColor}">
+		<view class="downwarp-content">
+			<view class="downwarp-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mOption.textColor, 'transform':downRotate}"></view>
+			<view class="downwarp-tip">{{downText}}</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		option: Object , // down的配置项
+		type: Number, // 下拉状态(inOffset:1, outOffset:2, showLoading:3, endDownScroll:4)
+		rate: Number // 下拉比率 (inOffset: rate<1; outOffset: rate>=1)
+	},
+	computed: {
+		// 支付宝小程序需写成计算属性,prop定义default仍报错
+		mOption(){
+			return this.option || {}
+		},
+		// 是否在加载中
+		isDownLoading(){
+			return this.type === 3
+		},
+		// 旋转的角度
+		downRotate(){
+			return 'rotate(' + 360 * this.rate + 'deg)'
+		},
+		// 文本提示
+		downText(){
+			switch (this.type){
+				case 1: return this.mOption.textInOffset;
+				case 2: return this.mOption.textOutOffset;
+				case 3: return this.mOption.textLoading;
+				case 4: return this.mOption.textLoading;
+				default: return this.mOption.textInOffset;
+			}
+		}
+	}
+};
+</script>
+
+<style>
+@import "./mescroll-down.css";
+</style>

+ 118 - 0
node_modules/mescroll-uni/components/mescroll-empty.vue

@@ -0,0 +1,118 @@
+<!--空布局:
+
+可作为独立的组件, 不使用mescroll的页面也能单独引入, 以便APP全局统一管理:
+import MescrollEmpty from '@/components/mescroll-uni/components/mescroll-empty.vue';
+<mescroll-empty v-if="isShowEmpty" :option="optEmpty" @emptyclick="emptyClick"></mescroll-empty>
+-->
+<template>
+	<view class="mescroll-empty" :class="{ 'empty-fixed': option.fixed }" :style="{ 'z-index': option.zIndex, top: option.top }">
+		<view> <image v-if="icon" class="empty-icon" :src="icon" mode="widthFix" /> </view>
+		<view v-if="tip" class="empty-tip">{{ tip }}</view>
+		<view v-if="btnText" class="empty-btn" @click="emptyClick">{{ btnText }}</view>
+	</view>
+</template>
+
+<script>
+// 引入全局配置
+import GlobalOption from './../mescroll-uni-option.js';
+// 引入国际化工具类
+import mescrollI18n from './../mescroll-i18n.js';
+export default {
+	props: {
+		// empty的配置项: 默认为GlobalOption.up.empty
+		option: {
+			type: Object,
+			default() {
+				return {};
+			}
+		}
+	},
+	// 使用computed获取配置,用于支持option的动态配置
+	computed: {
+		// 图标
+		icon() {
+			if (this.option.icon != null) { // 此处不使用短路求值, 用于支持传空串不显示图标
+				return this.option.icon
+			} else{
+				let i18nType = mescrollI18n.getType() // 国际化配置
+				if (this.option.i18n) {
+					return this.option.i18n[i18nType].icon
+				} else{
+					return GlobalOption.i18n[i18nType].up.empty.icon || GlobalOption.up.empty.icon
+				}
+			}
+		},
+		// 文本提示
+		tip() {
+			if (this.option.tip != null) { // 支持传空串不显示文本提示
+				return this.option.tip
+			} else{
+				let i18nType = mescrollI18n.getType() // 国际化配置
+				if (this.option.i18n) {
+					return this.option.i18n[i18nType].tip
+				} else{
+					return GlobalOption.i18n[i18nType].up.empty.tip || GlobalOption.up.empty.tip
+				}
+			}
+		},
+		// 按钮文本
+		btnText() {
+			if (this.option.i18n) {
+				let i18nType = mescrollI18n.getType() // 国际化配置
+				return this.option.i18n[i18nType].btnText
+			} else{
+				return this.option.btnText
+			}
+		}
+	},
+	methods: {
+		// 点击按钮
+		emptyClick() {
+			this.$emit('emptyclick');
+		}
+	}
+};
+</script>
+
+<style>
+/* 无任何数据的空布局 */
+.mescroll-empty {
+	box-sizing: border-box;
+	width: 100%;
+	padding: 100rpx 50rpx;
+	text-align: center;
+}
+
+.mescroll-empty.empty-fixed {
+	z-index: 99;
+	position: absolute; /*transform会使fixed失效,最终会降级为absolute */
+	top: 100rpx;
+	left: 0;
+}
+
+.mescroll-empty .empty-icon {
+	width: 280rpx;
+	height: 280rpx;
+}
+
+.mescroll-empty .empty-tip {
+	margin-top: 20rpx;
+	font-size: 24rpx;
+	color: gray;
+}
+
+.mescroll-empty .empty-btn {
+	display: inline-block;
+	margin-top: 40rpx;
+	min-width: 200rpx;
+	padding: 18rpx;
+	font-size: 28rpx;
+	border: 1rpx solid #e04b28;
+	border-radius: 60rpx;
+	color: #e04b28;
+}
+
+.mescroll-empty .empty-btn:active {
+	opacity: 0.75;
+}
+</style>

+ 83 - 0
node_modules/mescroll-uni/components/mescroll-top.vue

@@ -0,0 +1,83 @@
+<!-- 回到顶部的按钮 -->
+<template>
+	<image
+		v-if="mOption.src"
+		class="mescroll-totop"
+		:class="[value ? 'mescroll-totop-in' : 'mescroll-totop-out', {'mescroll-totop-safearea': mOption.safearea}]"
+		:style="{'z-index':mOption.zIndex, 'left': left, 'right': right, 'bottom':addUnit(mOption.bottom), 'width':addUnit(mOption.width), 'border-radius':addUnit(mOption.radius)}"
+		:src="mOption.src"
+		mode="widthFix"
+		@click="toTopClick"
+	/>
+</template>
+
+<script>
+export default {
+	props: {
+		// up.toTop的配置项
+		option: Object,
+		// 是否显示
+		value: false
+	},
+	computed: {
+		// 支付宝小程序需写成计算属性,prop定义default仍报错
+		mOption(){
+			return this.option || {}
+		},
+		// 优先显示左边
+		left(){
+			return this.mOption.left ? this.addUnit(this.mOption.left) : 'auto';
+		},
+		// 右边距离 (优先显示左边)
+		right() {
+			return this.mOption.left ? 'auto' : this.addUnit(this.mOption.right);
+		}
+	},
+	methods: {
+		addUnit(num){
+			if(!num) return 0;
+			if(typeof num === 'number') return num + 'rpx';
+			return num
+		},
+		toTopClick() {
+			this.$emit('input', false); // 使v-model生效
+			this.$emit('click'); // 派发点击事件
+		}
+	}
+};
+</script>
+
+<style>
+/* 回到顶部的按钮 */
+.mescroll-totop {
+	z-index: 9990;
+	position: fixed !important; /* 加上important避免编译到H5,在多mescroll中定位失效 */
+	right: 20rpx;
+	bottom: 120rpx;
+	width: 72rpx;
+	height: auto;
+	border-radius: 50%;
+	opacity: 0;
+	transition: opacity 0.5s; /* 过渡 */
+	margin-bottom: var(--window-bottom); /* css变量 */
+}
+
+/* 适配 iPhoneX */
+@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
+	.mescroll-totop-safearea {
+		margin-bottom: calc(var(--window-bottom) + constant(safe-area-inset-bottom)); /* window-bottom + 适配 iPhoneX */
+		margin-bottom: calc(var(--window-bottom) + env(safe-area-inset-bottom));
+	}
+}
+
+/* 显示 -- 淡入 */
+.mescroll-totop-in {
+	opacity: 1;
+}
+
+/* 隐藏 -- 淡出且不接收事件*/
+.mescroll-totop-out {
+	opacity: 0;
+	pointer-events: none;
+}
+</style>

+ 47 - 0
node_modules/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);
+	}
+}

+ 39 - 0
node_modules/mescroll-uni/components/mescroll-up.vue

@@ -0,0 +1,39 @@
+<!-- 上拉加载区域 -->
+<template>
+	<view class="mescroll-upwarp" :style="{'background-color':mOption.bgColor,'color':mOption.textColor}">
+		<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+		<view v-show="isUpLoading">
+			<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mOption.textColor}"></view>
+			<view class="upwarp-tip">{{ mOption.textLoading }}</view>
+		</view>
+		<!-- 无数据 -->
+		<view v-if="isUpNoMore" class="upwarp-nodata">{{ mOption.textNoMore }}</view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		option: Object, // up的配置项
+		type: Number // 上拉加载的状态:0(loading前),1(loading中),2(没有更多了)
+	},
+	computed: {
+		// 支付宝小程序需写成计算属性,prop定义default仍报错
+		mOption() {
+			return this.option || {};
+		},
+		// 加载中
+		isUpLoading() {
+			return this.type === 1;
+		},
+		// 没有更多了
+		isUpNoMore() {
+			return this.type === 2;
+		}
+	}
+};
+</script>
+
+<style>
+@import './mescroll-up.css';
+</style>

+ 19 - 0
node_modules/mescroll-uni/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);
+	}
+}

+ 403 - 0
node_modules/mescroll-uni/mescroll-body.vue

@@ -0,0 +1,403 @@
+<template>
+	<view 
+	class="mescroll-body mescroll-render-touch" 
+	:class="{'mescorll-sticky': sticky}"
+	:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}" 
+	@touchstart="wxsBiz.touchstartEvent" 
+	@touchmove="wxsBiz.touchmoveEvent" 
+	@touchend="wxsBiz.touchendEvent" 
+	@touchcancel="wxsBiz.touchendEvent"
+	:change:prop="wxsBiz.propObserver"
+	:prop="wxsProp"
+	>
+		<!-- 状态栏 -->
+		<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
+		
+		<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp">
+			<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
+			<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> -->
+			<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
+				<view class="downwarp-content">
+					<view class="downwarp-progress mescroll-wxs-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform': downRotate}"></view>
+					<view class="downwarp-tip">{{downText}}</view>
+				</view>
+			</view>
+	
+			<!-- 列表内容 -->
+			<slot></slot>
+
+			<!-- 空布局 -->
+			<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
+
+			<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
+			<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
+			<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
+				<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+				<view v-show="upLoadType===1">
+					<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
+					<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
+				</view>
+				<!-- 无数据 -->
+				<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
+			</view>
+		</view>
+		
+		<!-- 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) -->
+		<!-- #ifdef H5 -->
+		<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
+		<!-- #endif -->
+		
+		<!-- 适配iPhoneX -->
+		<view v-if="safearea" class="mescroll-safearea"></view>
+		
+		<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)-->
+		<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
+		
+		<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+		<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
+		<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
+		<!-- #endif -->
+	</view>
+</template>
+
+<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
+<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+<script src="./wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
+<!-- #endif -->
+
+<!-- app, h5使用renderjs -->
+<!-- #ifdef APP-PLUS || H5 -->
+<script module="renderBiz" lang="renderjs">
+	import renderBiz from './wxs/renderjs.js';
+	export default {
+		mixins: [renderBiz]
+	}
+</script>
+<!-- #endif -->
+
+<script>
+	// 引入mescroll-uni.js,处理核心逻辑
+	import MeScroll from './mescroll-uni.js';
+	// 引入全局配置
+	import GlobalOption from './mescroll-uni-option.js';
+	// 引入空布局组件
+	import MescrollEmpty from './components/mescroll-empty.vue';
+	// 引入国际化工具类
+	import mescrollI18n from './mescroll-i18n.js';
+	// 引入回到顶部组件
+	import MescrollTop from './components/mescroll-top.vue';
+	// 引入兼容wxs(含renderjs)写法的mixins
+	import WxsMixin from './wxs/mixins.js';
+	
+	/**
+	 * mescroll-body 基于page滚动的下拉刷新和上拉加载组件, 支持嵌套原生组件, 性能好
+	 * @property {Object} down 下拉刷新的参数配置
+	 * @property {Object} up 上拉加载的参数配置
+	 * @property {Object} i18n 国际化的参数配置
+	 * @property {String, Number} top 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+	 * @property {Boolean, String} topbar 偏移量top是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
+	 * @property {String, Number} bottom 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+	 * @property {Boolean} safearea 偏移量bottom是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
+	 * @property {Boolean} fixed 是否通过fixed固定mescroll的高度, 默认true
+	 * @property {String, Number} height 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
+	 * @property {Boolean} bottombar 底部是否偏移TabBar的高度 (仅在H5端的tab页生效)
+	 * @property {Boolean} sticky 是否支持sticky,默认false; 当值配置true时,需避免在mescroll-body标签前面加非定位的元素,否则下拉区域无法隐藏
+	 * @event {Function} init 初始化完成的回调 
+	 * @event {Function} down 下拉刷新的回调
+	 * @event {Function} up 上拉加载的回调 
+	 * @event {Function} emptyclick 点击empty配置的btnText按钮回调
+	 * @event {Function} topclick 点击回到顶部的按钮回调
+	 * @event {Function} scroll 滚动监听 (需在 up 配置 onScroll:true 才生效)
+	 * @example <mescroll-body ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback"> ... </mescroll-body>
+	 */
+	export default {
+		name: 'mescroll-body',
+		mixins: [WxsMixin],
+		components: {
+			MescrollEmpty,
+			MescrollTop
+		},
+		props: {
+			down: Object,
+			up: Object,
+			i18n: Object,
+			top: [String, Number],
+			topbar: [Boolean, String],
+			bottom: [String, Number],
+			safearea: Boolean,
+			height: [String, Number],
+			bottombar:{
+				type: Boolean,
+				default: true
+			},
+			sticky: Boolean
+		},
+		data() {
+			return {
+				mescroll: {optDown:{},optUp:{}}, // mescroll实例
+				downHight: 0, //下拉刷新: 容器高度
+				downRate: 0, // 下拉比率(inOffset: rate<1; outOffset: rate>=1)
+				downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
+				upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示)
+				isShowEmpty: false, // 是否显示空布局
+				isShowToTop: false, // 是否显示回到顶部按钮
+				windowHeight: 0, // 可使用窗口的高度
+				windowBottom: 0, // 可使用窗口的底部位置
+				statusBarHeight: 0 // 状态栏高度
+			};
+		},
+		computed: {
+			// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
+			minHeight(){
+				return this.toPx(this.height || '100%') + 'px'
+			},
+			// 下拉布局往下偏移的距离 (px)
+			numTop() {
+				return this.toPx(this.top)
+			},
+			padTop() {
+				return this.numTop + 'px';
+			},
+			// 上拉布局往上偏移 (px)
+			numBottom() {
+				return this.toPx(this.bottom);
+			},
+			padBottom() {
+				return this.numBottom + 'px';
+			},
+			// 是否为重置下拉的状态
+			isDownReset() {
+				return this.downLoadType === 3 || this.downLoadType === 4;
+			},
+			// 过渡
+			transition() {
+				return this.isDownReset ? 'transform 300ms' : '';
+			},
+			translateY() {
+				return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
+			},
+			// 是否在加载中
+			isDownLoading(){
+				return this.downLoadType === 3
+			},
+			// 旋转的角度
+			downRotate(){
+				return 'rotate(' + 360 * this.downRate + 'deg)'
+			},
+			// 文本提示
+			downText(){
+				if(!this.mescroll) return ""; // 避免头条小程序初始化时报错
+				switch (this.downLoadType){
+					case 1: return this.mescroll.optDown.textInOffset;
+					case 2: return this.mescroll.optDown.textOutOffset;
+					case 3: return this.mescroll.optDown.textLoading;
+					case 4: return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset;
+					default: return this.mescroll.optDown.textInOffset;
+				}
+			}
+		},
+		methods: {
+			//number,rpx,upx,px,% --> px的数值
+			toPx(num) {
+				if (typeof num === 'string') {
+					if (num.indexOf('px') !== -1) {
+						if (num.indexOf('rpx') !== -1) {
+							// "10rpx"
+							num = num.replace('rpx', '');
+						} else if (num.indexOf('upx') !== -1) {
+							// "10upx"
+							num = num.replace('upx', '');
+						} else {
+							// "10px"
+							return Number(num.replace('px', ''));
+						}
+					} else if (num.indexOf('%') !== -1) {
+						// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
+						let rate = Number(num.replace('%', '')) / 100;
+						return this.windowHeight * rate;
+					}
+				}
+				return num ? uni.upx2px(Number(num)) : 0;
+			},
+			// 点击空布局的按钮回调
+			emptyClick() {
+				this.$emit('emptyclick', this.mescroll);
+			},
+			// 点击回到顶部的按钮回调
+			toTopClick() {
+				this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
+				this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
+			}
+		},
+		// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
+		created() {
+			let vm = this;
+
+			let diyOption = {
+				// 下拉刷新的配置
+				down: {
+					inOffset() {
+						vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					outOffset() {
+						vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					onMoving(mescroll, rate, downHight) {
+						// 下拉过程中的回调,滑动过程一直在执行;
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						vm.downRate = rate; //下拉比率 (inOffset: rate<1; outOffset: rate>=1)
+					},
+					showLoading(mescroll, downHight) {
+						vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					beforeEndDownScroll(mescroll){
+						vm.downLoadType = 4; 
+						return mescroll.optDown.beforeEndDelay // 延时结束的时长
+					},
+					endDownScroll() {
+						vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
+						vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						if(vm.downResetTimer) {clearTimeout(vm.downResetTimer); vm.downResetTimer = null} // 移除重置倒计时
+						vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,避免下次inOffset不及时显示textInOffset
+							if(vm.downLoadType === 4) vm.downLoadType = 0
+						},300)
+					},
+					// 派发下拉刷新的回调
+					callback: function(mescroll) {
+						vm.$emit('down', mescroll);
+					}
+				},
+				// 上拉加载的配置
+				up: {
+					// 显示加载中的回调
+					showLoading() {
+						vm.upLoadType = 1;
+					},
+					// 显示无更多数据的回调
+					showNoMore() {
+						vm.upLoadType = 2;
+					},
+					// 隐藏上拉加载的回调
+					hideUpScroll(mescroll) {
+						vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
+					},
+					// 空布局
+					empty: {
+						onShow(isShow) {
+							// 显示隐藏的回调
+							vm.isShowEmpty = isShow;
+						}
+					},
+					// 回到顶部
+					toTop: {
+						onShow(isShow) {
+							// 显示隐藏的回调
+							vm.isShowToTop = isShow;
+						}
+					},
+					// 派发上拉加载的回调
+					callback: function(mescroll) {
+						vm.$emit('up', mescroll);
+					}
+				}
+			};
+			
+			let i18nType = mescrollI18n.getType() // 当前语言类型
+			let i18nOption = {type: i18nType} // 国际化配置
+			MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
+			MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
+			MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
+			MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
+			let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // 深拷贝,避免对props的影响
+			MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
+
+			// 初始化MeScroll对象
+			vm.mescroll = new MeScroll(myOption, true); // 传入true,标记body为滚动区域
+			// 挂载语言包
+			vm.mescroll.i18n = i18nOption;
+			// init回调mescroll对象
+			vm.$emit('init', vm.mescroll);
+
+			// 设置高度
+			const sys = uni.getSystemInfoSync();
+			if (sys.windowHeight) vm.windowHeight = sys.windowHeight;
+			if (sys.windowBottom) vm.windowBottom = sys.windowBottom;
+			if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
+			// 使down的bottomOffset生效
+			vm.mescroll.setBodyHeight(sys.windowHeight);
+
+			// 因为使用的是page的scroll,这里需自定义scrollTo
+			vm.mescroll.resetScrollTo((y, t) => {
+				if(typeof y === 'string'){
+					// 滚动到指定view (y为css选择器)
+					setTimeout(()=>{ // 延时确保view已渲染; 不使用$nextTick
+						let selector;
+						if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
+							selector = '#'+y // 不带#和. 则默认为id选择器
+						}else{
+							selector = y
+							// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
+							if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询)
+								selector = y.split('>>>')[1].trim()
+							}
+							// #endif
+						}
+						uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
+							if (rect) {
+								let top = rect.top
+								top += vm.mescroll.getScrollTop()
+								uni.pageScrollTo({
+									scrollTop: top,
+									duration: t
+								})
+							} else{
+								console.error(selector + ' does not exist');
+							}
+						}).exec()
+					},30)
+				} else{
+					// 滚动到指定位置 (y必须为数字)
+					uni.pageScrollTo({
+						scrollTop: y,
+						duration: t
+					})
+				}
+			});
+
+			// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
+			if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
+				vm.mescroll.optUp.toTop.safearea = vm.safearea;
+			}
+			
+			// 全局配置监听
+			uni.$on("setMescrollGlobalOption", options=>{
+				if(!options) return;
+				let i18nType = options.i18n ? options.i18n.type : null
+				if(i18nType && vm.mescroll.i18n.type != i18nType){
+					vm.mescroll.i18n.type = i18nType
+					mescrollI18n.setType(i18nType)
+					MeScroll.extend(options, vm.mescroll.i18n[i18nType])
+				}
+				if(options.down){
+					let down = MeScroll.extend({}, options.down)
+					vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
+				}
+				if(options.up){
+					let up = MeScroll.extend({}, options.up)
+					vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
+				}
+			})
+		},
+		destroyed() {
+			// 注销全局配置监听
+			uni.$off("setMescrollGlobalOption")
+		}
+	};
+</script>
+
+<style>
+	@import "./mescroll-body.css";
+	@import "./components/mescroll-down.css";
+	@import './components/mescroll-up.css';
+</style>

+ 15 - 0
node_modules/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

+ 57 - 0
node_modules/mescroll-uni/mescroll-mixins.js

@@ -0,0 +1,57 @@
+// 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;
+			this.mescrollInitByRef(); // 兼容字节跳动小程序
+		},
+		// 以ref的方式初始化mescroll对象 (兼容字节跳动小程序)
+		mescrollInitByRef() {
+			if(!this.mescroll || !this.mescroll.resetUpScroll){
+				let mescrollRef = this.$refs.mescrollRef;
+				if(mescrollRef) this.mescroll = mescrollRef.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)
+		}
+	},
+	mounted() {
+		this.mescrollInitByRef(); // 兼容字节跳动小程序, 避免未设置@init或@init此时未能取到ref的情况
+	}
+	
+}
+
+export default MescrollMixin;

+ 64 - 0
node_modules/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

+ 36 - 0
node_modules/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);
+	}
+}

+ 799 - 0
node_modules/mescroll-uni/mescroll-uni.js

@@ -0,0 +1,799 @@
+/* mescroll
+ * version 1.3.7
+ * 2021-04-13 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()
+}

+ 480 - 0
node_modules/mescroll-uni/mescroll-uni.vue

@@ -0,0 +1,480 @@
+<template>
+	<view class="mescroll-uni-warp">
+		<scroll-view :id="viewId" class="mescroll-uni" :class="{'mescroll-uni-fixed':isFixed}" :style="{'height':scrollHeight,'padding-top':padTop,'padding-bottom':padBottom,'top':fixedTop,'bottom':fixedBottom}" :scroll-top="scrollTop" :scroll-with-animation="scrollAnim" @scroll="scroll" :scroll-y='scrollable' :enable-back-to-top="true" :throttle="false">
+			<view class="mescroll-uni-content mescroll-render-touch"
+			@touchstart="wxsBiz.touchstartEvent" 
+			@touchmove="wxsBiz.touchmoveEvent" 
+			@touchend="wxsBiz.touchendEvent" 
+			@touchcancel="wxsBiz.touchendEvent"
+			:change:prop="wxsBiz.propObserver"
+			:prop="wxsProp">
+				<!-- 状态栏 -->
+				<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
+		
+				<view class="mescroll-wxs-content" :style="{'transform': translateY, 'transition': transition}" :change:prop="wxsBiz.callObserver" :prop="callProp">
+					<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
+					<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> -->
+					<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
+						<view class="downwarp-content">
+							<view class="downwarp-progress mescroll-wxs-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform': downRotate}"></view>
+							<view class="downwarp-tip">{{downText}}</view>
+						</view>
+					</view>
+
+					<!-- 列表内容 -->
+					<slot></slot>
+
+					<!-- 空布局 -->
+					<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
+
+					<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
+					<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
+					<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
+						<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+						<view v-show="upLoadType===1">
+							<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
+							<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
+						</view>
+						<!-- 无数据 -->
+						<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
+					</view>
+				</view>
+			
+				<!-- 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) -->
+				<!-- #ifdef H5 -->
+				<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
+				<!-- #endif -->
+				
+				<!-- 适配iPhoneX -->
+				<view v-if="safearea" class="mescroll-safearea"></view>
+			</view>
+		</scroll-view>
+
+		<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)-->
+		<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
+		
+		<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+		<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
+		<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
+		<!-- #endif -->
+	</view>
+</template>
+
+<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
+<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+<script src="./wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
+<!-- #endif -->
+
+<!-- app, h5使用renderjs -->
+<!-- #ifdef APP-PLUS || H5 -->
+<script module="renderBiz" lang="renderjs">
+	import renderBiz from './wxs/renderjs.js';
+	export default {
+		mixins:[renderBiz]
+	}
+</script>
+<!-- #endif -->
+
+<script>
+	// 引入mescroll-uni.js,处理核心逻辑
+	import MeScroll from './mescroll-uni.js';
+	// 引入全局配置
+	import GlobalOption from './mescroll-uni-option.js';
+	// 引入空布局组件
+	import MescrollEmpty from './components/mescroll-empty.vue';
+	// 引入国际化工具类
+	import mescrollI18n from './mescroll-i18n.js';
+	// 引入回到顶部组件
+	import MescrollTop from './components/mescroll-top.vue';
+	// 引入兼容wxs(含renderjs)写法的mixins
+	import WxsMixin from './wxs/mixins.js';
+	
+	/**
+	 * mescroll-uni 嵌在页面某个区域的下拉刷新和上拉加载组件, 如嵌在弹窗,浮层,swiper中...
+	 * @property {Object} down 下拉刷新的参数配置
+	 * @property {Object} up 上拉加载的参数配置
+	 * @property {Object} i18n 国际化的参数配置
+	 * @property {String, Number} top 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+	 * @property {Boolean, String} topbar 偏移量top是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
+	 * @property {String, Number} bottom 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+	 * @property {Boolean} safearea 偏移量bottom是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
+	 * @property {Boolean} fixed 是否通过fixed固定mescroll的高度, 默认true
+	 * @property {String, Number} height 指定mescroll的高度, 此项有值,则不使用fixed. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+	 * @property {Boolean} bottombar 底部是否偏移TabBar的高度 (仅在H5端的tab页生效)
+	 * @property {Boolean} disableScroll 是否禁止滚动, 默认false
+	 * @event {Function} init 初始化完成的回调 
+	 * @event {Function} down 下拉刷新的回调
+	 * @event {Function} up 上拉加载的回调 
+	 * @event {Function} emptyclick 点击empty配置的btnText按钮回调
+	 * @event {Function} topclick 点击回到顶部的按钮回调
+	 * @event {Function} scroll 滚动监听 (需在 up 配置 onScroll:true 才生效)
+	 * @example <mescroll-uni ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback"> ... </mescroll-uni>
+	 */
+	export default {
+		name: 'mescroll-uni',
+		mixins: [WxsMixin],
+		components: {
+			MescrollEmpty,
+			MescrollTop
+		},
+		props: {
+			down: Object,
+			up: Object,
+			i18n: Object,
+			top: [String, Number],
+			topbar: [Boolean, String],
+			bottom: [String, Number],
+			safearea: Boolean,
+			fixed: {
+				type: Boolean,
+				default: true
+			},
+			height: [String, Number],
+			bottombar:{
+				type: Boolean,
+				default: true
+			},
+			disableScroll: Boolean
+		},
+		data() {
+			return {
+				mescroll: {optDown:{},optUp:{}}, // mescroll实例
+				viewId: 'id_' + Math.random().toString(36).substr(2,16), // 随机生成mescroll的id(不能数字开头,否则找不到元素)
+				downHight: 0, //下拉刷新: 容器高度
+				downRate: 0, // 下拉比率(inOffset: rate<1; outOffset: rate>=1)
+				downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
+				upLoadType: 0, // 上拉加载状态: 0(loading前), 1loading中, 2没有更多了,显示END文本提示, 3(没有更多了,不显示END文本提示)
+				isShowEmpty: false, // 是否显示空布局
+				isShowToTop: false, // 是否显示回到顶部按钮
+				scrollTop: 0, // 滚动条的位置
+				scrollAnim: false, // 是否开启滚动动画
+				windowTop: 0, // 可使用窗口的顶部位置
+				windowBottom: 0, // 可使用窗口的底部位置
+				windowHeight: 0, // 可使用窗口的高度
+				statusBarHeight: 0 // 状态栏高度
+			}
+		},
+		computed: {
+			// 是否使用fixed定位 (当height有值,则不使用)
+			isFixed(){
+				return !this.height && this.fixed
+			},
+			// mescroll的高度
+			scrollHeight(){
+				if (this.isFixed) {
+					return "auto"
+				} else if(this.height){
+					return this.toPx(this.height) + 'px'
+				}else{
+					return "100%"
+				}
+			},
+			// 下拉布局往下偏移的距离 (px)
+			numTop() {
+				return this.toPx(this.top)
+			},
+			fixedTop() {
+				return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0
+			},
+			padTop() {
+				return !this.isFixed ? this.numTop + 'px' : 0
+			},
+			// 上拉布局往上偏移 (px)
+			numBottom() {
+				return this.toPx(this.bottom)
+			},
+			fixedBottom() {
+				return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0
+			},
+			padBottom() {
+				return !this.isFixed ? this.numBottom + 'px' : 0
+			},
+			// 是否为重置下拉的状态
+			isDownReset(){
+				return this.downLoadType===3 || this.downLoadType===4
+			},
+			// 过渡
+			transition() {
+				return this.isDownReset ? 'transform 300ms' : '';
+			},
+			translateY() {
+				return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
+			},
+			// 列表是否可滑动
+			scrollable(){
+				if(this.disableScroll) return false
+				return this.downLoadType===0 || this.isDownReset
+			},
+			// 是否在加载中
+			isDownLoading(){
+				return this.downLoadType === 3
+			},
+			// 旋转的角度
+			downRotate(){
+				return 'rotate(' + 360 * this.downRate + 'deg)'
+			},
+			// 文本提示
+			downText(){
+				if(!this.mescroll) return ""; // 避免头条小程序初始化时报错
+				switch (this.downLoadType){
+					case 1: return this.mescroll.optDown.textInOffset;
+					case 2: return this.mescroll.optDown.textOutOffset;
+					case 3: return this.mescroll.optDown.textLoading;
+					case 4: return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset;
+					default: return this.mescroll.optDown.textInOffset;
+				}
+			}
+		},
+		methods: {
+			//number,rpx,upx,px,% --> px的数值
+			toPx(num){
+				if(typeof num === "string"){
+					if (num.indexOf('px') !== -1) {
+						if(num.indexOf('rpx') !== -1) { // "10rpx"
+							num = num.replace('rpx', '');
+						} else if(num.indexOf('upx') !== -1) { // "10upx"
+							num = num.replace('upx', '');
+						} else { // "10px"
+							return Number(num.replace('px', ''))
+						}
+					}else if (num.indexOf('%') !== -1){
+						// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
+						let rate = Number(num.replace("%","")) / 100
+						return this.windowHeight * rate
+					}
+				}
+				return num ? uni.upx2px(Number(num)) : 0
+			},
+			//注册列表滚动事件,用于下拉刷新和上拉加载
+			scroll(e) {
+				this.mescroll.scroll(e.detail, () => {
+					this.$emit('scroll', this.mescroll) // 此时可直接通过 this.mescroll.scrollTop获取滚动条位置; this.mescroll.isScrollUp获取是否向上滑动
+				})
+			},
+			// 点击空布局的按钮回调
+			emptyClick() {
+				this.$emit('emptyclick', this.mescroll)
+			},
+			// 点击回到顶部的按钮回调
+			toTopClick() {
+				this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
+				this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
+			},
+			// 更新滚动区域的高度 (使内容不满屏和到底,都可继续翻页)
+			setClientHeight() {
+				if (this.mescroll.getClientHeight(true) === 0 && !this.isExec) {
+					this.isExec = true; // 避免多次获取
+					this.$nextTick(() => { // 确保dom已渲染
+						this.getClientInfo(data=>{
+							this.isExec = false;
+							if (data) {
+								this.mescroll.setClientHeight(data.height);
+							} else if (this.clientNum != 3) { // 极少部分情况,可能dom还未渲染完毕,递归获取,最多重试3次
+								this.clientNum = this.clientNum == null ? 1 : this.clientNum + 1;
+								setTimeout(() => {
+									this.setClientHeight()
+								}, this.clientNum * 100)
+							}
+						})
+					})
+				}
+			},
+			// 获取滚动区域的信息
+			getClientInfo(success){
+				let query = uni.createSelectorQuery();
+				// #ifndef MP-ALIPAY || MP-DINGTALK
+				query = query.in(this) // 支付宝小程序不支持in(this),而字节跳动小程序必须写in(this), 否则都取不到值
+				// #endif
+				let view = query.select('#' + this.viewId);
+				view.boundingClientRect(data => {
+					success(data)
+				}).exec();
+			}
+		},
+		// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
+		created() {
+			let vm = this;
+
+			let diyOption = {
+				// 下拉刷新的配置
+				down: {
+					inOffset() {
+						vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					outOffset() {
+						vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					onMoving(mescroll, rate, downHight) {
+						// 下拉过程中的回调,滑动过程一直在执行;
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						vm.downRate = rate; //下拉比率 (inOffset: rate<1; outOffset: rate>=1)
+					},
+					showLoading(mescroll, downHight) {
+						vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					beforeEndDownScroll(mescroll){
+						vm.downLoadType = 4; 
+						return mescroll.optDown.beforeEndDelay // 延时结束的时长
+					},
+					endDownScroll() {
+						vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
+						vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						vm.downResetTimer && clearTimeout(vm.downResetTimer)
+						vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,以便置空this.transition,避免iOS小程序列表渲染不完整
+							if(vm.downLoadType===4) vm.downLoadType = 0
+						},300)
+					},
+					// 派发下拉刷新的回调
+					callback: function(mescroll) {
+						vm.$emit('down', mescroll)
+					}
+				},
+				// 上拉加载的配置
+				up: {
+					// 显示加载中的回调
+					showLoading() {
+						vm.upLoadType = 1;
+					},
+					// 显示无更多数据的回调
+					showNoMore() {
+						vm.upLoadType = 2;
+					},
+					// 隐藏上拉加载的回调
+					hideUpScroll(mescroll) {
+						vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
+					},
+					// 空布局
+					empty: {
+						onShow(isShow) { // 显示隐藏的回调
+							vm.isShowEmpty = isShow;
+						}
+					},
+					// 回到顶部
+					toTop: {
+						onShow(isShow) { // 显示隐藏的回调
+							vm.isShowToTop = isShow;
+						}
+					},
+					// 派发上拉加载的回调
+					callback: function(mescroll) {
+						vm.$emit('up', mescroll);
+						// 更新容器的高度 (多mescroll的情况)
+						vm.setClientHeight()
+					}
+				}
+			}
+
+			let i18nType = mescrollI18n.getType() // 当前语言类型
+			let i18nOption = {type: i18nType} // 国际化配置
+			MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
+			MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
+			MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
+			MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
+			let myOption = JSON.parse(JSON.stringify({'down': vm.down,'up': vm.up})) // 深拷贝,避免对props的影响
+			MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
+
+			// 初始化MeScroll对象
+			vm.mescroll = new MeScroll(myOption);
+			vm.mescroll.viewId = vm.viewId; // 附带id
+			vm.mescroll.i18n = i18nOption; // 挂载语言包
+			// init回调mescroll对象
+			vm.$emit('init', vm.mescroll);
+			
+			// 设置高度
+			const sys = uni.getSystemInfoSync();
+			if(sys.windowTop) vm.windowTop = sys.windowTop;
+			if(sys.windowBottom) vm.windowBottom = sys.windowBottom;
+			if(sys.windowHeight) vm.windowHeight = sys.windowHeight;
+			if(sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
+			// 使down的bottomOffset生效
+			vm.mescroll.setBodyHeight(sys.windowHeight);
+
+			// 因为使用的是scrollview,这里需自定义scrollTo
+			vm.mescroll.resetScrollTo((y, t) => {
+				vm.scrollAnim = (t !== 0); // t为0,则不使用动画过渡
+				if(typeof y === 'string'){
+					// 小程序不支持slot里面的scroll-into-view, 统一使用计算的方式实现
+					vm.getClientInfo(function(rect){
+						let mescrollTop = rect.top // mescroll到顶部的距离
+						let selector;
+						if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
+							selector = '#'+y // 不带#和. 则默认为id选择器
+						}else{
+							selector = y
+							// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
+							if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询)
+								selector = y.split('>>>')[1].trim()
+							}
+							// #endif
+						}
+						uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
+							if (rect) {
+								let curY = vm.mescroll.getScrollTop()
+								let top = rect.top - mescrollTop
+								top += curY
+								if(!vm.isFixed) top -= vm.numTop
+								vm.scrollTop = curY;
+								vm.$nextTick(function() {
+									vm.scrollTop = top
+								})
+							} else{
+								console.error(selector + ' does not exist');
+							}
+						}).exec()
+					})
+					return;
+				}
+				let curY = vm.mescroll.getScrollTop()
+				if (t === 0 || t === 300) { // 当t使用默认配置的300时,则使用系统自带的动画过渡
+					vm.scrollTop = curY;
+					vm.$nextTick(function() {
+						vm.scrollTop = y
+					})
+				} else {
+					vm.mescroll.getStep(curY, y, step => { // 此写法可支持配置t
+						vm.scrollTop = step
+					}, t)
+				}
+			})
+			
+			// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
+			if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
+				vm.mescroll.optUp.toTop.safearea = vm.safearea;
+			}
+			
+			// 全局配置监听
+			uni.$on("setMescrollGlobalOption", options=>{
+				if(!options) return;
+				let i18nType = options.i18n ? options.i18n.type : null
+				if(i18nType && vm.mescroll.i18n.type != i18nType){
+					vm.mescroll.i18n.type = i18nType
+					mescrollI18n.setType(i18nType)
+					MeScroll.extend(options, vm.mescroll.i18n[i18nType])
+				}
+				if(options.down){
+					let down = MeScroll.extend({}, options.down)
+					vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
+				}
+				if(options.up){
+					let up = MeScroll.extend({}, options.up)
+					vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
+				}
+			})
+		},
+		mounted() {
+			// 设置容器的高度
+			this.setClientHeight()
+		},
+		destroyed() {
+			// 注销全局配置监听
+			uni.$off("setMescrollGlobalOption")
+		}
+	}
+</script>
+
+<style>
+	@import "./mescroll-uni.css";
+	@import "./components/mescroll-down.css";
+	@import './components/mescroll-up.css';
+</style>

+ 47 - 0
node_modules/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;

+ 66 - 0
node_modules/mescroll-uni/mixins/mescroll-more-item.js

@@ -0,0 +1,66 @@
+/**
+ * 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: {
+		// 以ref的方式初始化mescroll对象 (兼容字节跳动小程序)
+		mescrollInitByRef() {
+			if(!this.mescroll || !this.mescroll.resetUpScroll){
+				// 字节跳动小程序编辑器不支持一个页面存在相同的ref, 多mescroll的ref需动态生成, 格式为'mescrollRef下标'
+				let mescrollRef = this.$refs.mescrollRef || this.$refs['mescrollRef'+this.i];
+				if(mescrollRef) this.mescroll = mescrollRef.mescroll
+			}
+		},
+		// mescroll组件初始化的回调,可获取到mescroll对象 (覆盖mescroll-mixins.js的mescrollInit, 为了标记isInit)
+		mescrollInit(mescroll) {
+			this.mescroll = mescroll;
+			this.mescrollInitByRef && this.mescrollInitByRef(); // 兼容字节跳动小程序
+			// 自动加载当前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;

+ 74 - 0
node_modules/mescroll-uni/mixins/mescroll-more.js

@@ -0,0 +1,74 @@
+/**
+ * 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){
+				// 延时(比$nextTick靠谱一些),确保元素已渲染
+				setTimeout(()=>{
+					mescroll.scrollTo(mescroll.getScrollTop(),0)
+				},30)
+			}
+		}
+	}
+}
+
+export default MescrollMoreMixin;

+ 41 - 0
node_modules/mescroll-uni/package.json

@@ -0,0 +1,41 @@
+{
+  "_args": [
+    [
+      "mescroll-uni@1.3.7",
+      "C:\\Users\\Administrator\\Desktop\\项目\\直播\\liveH5-v3"
+    ]
+  ],
+  "_from": "mescroll-uni@1.3.7",
+  "_id": "mescroll-uni@1.3.7",
+  "_inBundle": false,
+  "_integrity": "sha512-1pQMtGA+iVRKhfJZZNXdBx05NnthIk6zm3hRbumswSA54eaKOMgpUDb9AQ2+rRdXmS6kLkEYSbW/fkb7/IyoAg==",
+  "_location": "/mescroll-uni",
+  "_phantomChildren": {},
+  "_requested": {
+    "type": "version",
+    "registry": true,
+    "raw": "mescroll-uni@1.3.7",
+    "name": "mescroll-uni",
+    "escapedName": "mescroll-uni",
+    "rawSpec": "1.3.7",
+    "saveSpec": null,
+    "fetchSpec": "1.3.7"
+  },
+  "_requiredBy": [
+    "/"
+  ],
+  "_resolved": "https://registry.npmjs.org/mescroll-uni/-/mescroll-uni-1.3.7.tgz",
+  "_spec": "1.3.7",
+  "_where": "C:\\Users\\Administrator\\Desktop\\项目\\直播\\liveH5-v3",
+  "author": {
+    "name": "wenju"
+  },
+  "description": "mescroll的uni-app版本:【wxs+renderjs实现】高性能的下拉刷新上拉加载组件",
+  "license": "MIT",
+  "main": "mescroll-uni.vue",
+  "name": "mescroll-uni",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "version": "1.3.7"
+}

+ 109 - 0
node_modules/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对象, 以便在其他组件中使用, 比如<me-video>中
+				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对象, 以便在其他组件中使用, 比如<me-video>中
+				this.$set(this.mescroll, 'downLoadType', this.downLoadType)
+			}, delay)
+		}
+		// 初始化wxs的数据
+		this.wxsCall({type: 'setWxsProp'})
+		// #endif
+	}
+}
+
+export default WxsMixin;

+ 92 - 0
node_modules/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 || 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;

+ 268 - 0
node_modules/mescroll-uni/wxs/wxs.wxs

@@ -0,0 +1,268 @@
+// 使用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) {
+	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
+}

+ 136 - 0
node_modules/uview-plus/changelog.md

@@ -1,3 +1,139 @@
+## 3.5.39(2025-09-07)
+fix: 修复cascader多语言问题
+
+## 3.5.38(2025-09-07)
+improvment: 完善演示图标
+
+## 3.5.37(2025-09-05)
+feat: cascader支持多语言
+
+## 3.5.36(2025-09-04)
+improvment: 补充图标
+
+## 3.5.35(2025-09-04)
+feat: cascader级联组件增加垂直头部与单列选项支持
+
+## 3.5.34(2025-09-03)
+improvement: 优化steps-item
+
+## 3.5.33(2025-09-02)
+升级uni-app至最新版
+## 3.5.32(2025-09-01)
+feat: cate-tab支持非联动跟随的单一切换模式
+
+feat: choose组件示例使用cate-tab的单一切换模式
+
+
+## 3.5.31(2025-09-01)
+feat: 新增choose通用选项选择器组件
+
+## 3.5.30(2025-08-31)
+fix: 修复缺失cascader级联选择器
+
+## 3.5.29(2025-08-30)
+feat: 新增cascader级联选择器
+
+## 3.5.28(2025-08-29)
+feat: 多语言增加泰语
+
+## 3.5.27(2025-08-29)
+feat: 新增基于tooltip的popover组件
+
+## 3.5.26(2025-08-29)
+improvment: 增强多语言兼容性
+
+## 3.5.25(2025-08-28)
+fix: 修复左侧与右侧弹出时页面打开闪现问题
+
+## 3.5.24(2025-08-28)
+feat: 优化tooltip弹窗定位
+
+feat: tooltip组件新增forcePosition支持强制精确定位
+
+## 3.5.23(2025-08-28)
+improvment: 调整pdf阅读器默认高度
+
+## 3.5.22(2025-08-28)
+feat: 新增goods-sku商品SKU选购组件
+
+## 3.5.21(2025-08-27)
+fix: 修复popup组件手势控制参数注释
+
+## 3.5.20(2025-08-27)
+improvement: 提升checkbox条件编译兼容性
+
+## 3.5.19(2025-08-27)
+feat: popup新增iOS风格手势控制上下高度及下滑关闭
+
+## 3.5.18(2025-08-26)
+feat: 新增coupon优惠券组件
+
+## 3.5.17(2025-08-26)
+fix: 分支合并冲突修复
+
+feat: picker-data组件新增cancel/close/confirm事件支持
+
+## 3.5.16(2025-08-26)
+feat: tabbar新增支持中间凸起按钮
+
+## 3.5.15(2025-08-25)
+feat: 之前的color-picker颜色选择器按计划上线
+
+## 3.5.14(2025-08-25)
+feat: 新增PDF阅读器组件
+
+## 3.5.13(2025-08-24)
+fix: 修复短视频示例https
+
+## 3.5.12(2025-08-23)
+feat: short-video短视频组件增加视频播放示例
+
+## 3.5.11(2025-08-23)
+feat: 新增short-video短视频切换组件
+
+feat: tabbar支持backgroundColor和borderColor属性
+
+feat: slider支持innerStyle样式属性
+
+## 3.5.10(2025-08-22)
+feat: 新增poster海报生成组件
+
+
+## 3.5.9(2025-08-22)
+feat: 优化qrcode组件支持toTempFilePath方法
+
+## 3.5.8(2025-08-22)
+fix: 优化rpx下calendar高度计算强制px
+
+## 3.5.7(2025-08-22)
+feat: input和search新增onlyClearableOnFocused参数支持控制是否仅聚焦时显示清除按钮
+
+## 3.5.6(2025-08-21)
+feat: picker弹窗及datetime-picker组件支持页面内模式
+
+## 3.5.5(2025-08-21)
+feat: popup弹窗及canlendar组件支持页面内插入无弹窗模式
+
+## 3.5.4(2025-08-21)
+feat: popup弹窗及canlendar组件支持页面内插入无弹窗模式
+
+## 3.5.3(2025-08-21)
+fix: 修复下拉刷新多语言key
+
+## 3.5.2(2025-08-20)
+fix: 修复非cli模式下引入的三方库报An error occurred while trying to read the map file错误
+
+fix: 去除i18n多语言多余console
+
+## 3.5.1(2025-08-20)
+feat: calendar组件月份标题自动适配多语言
+
+## 3.5.0(2025-08-20)
+feat: 组件库新增多语言支持且无侵入无依赖内置八种语言
+
+## 3.4.107(2025-08-20)
+feat: input组件支持密码显示切换
+
 ## 3.4.106(2025-08-20)
 feat: tooltip组件支持左右方向弹窗
 

+ 2 - 1
node_modules/uview-plus/components/u-barcode/u-barcode.vue

@@ -24,6 +24,7 @@
 
 <script>
 import { nextTick } from 'vue'
+import { t } from '../../libs/i18n'
 
 export default {
   name: 'u-barcode',
@@ -290,7 +291,7 @@ export default {
         })
       } catch (error) {
         console.error('生成条码失败:', error)
-        this.error = error.message || '生成条码失败'
+        this.error = error.message || t('up.barcode.error')
         this.$emit('error', error)
       }
     },

+ 10 - 7
node_modules/uview-plus/components/u-calendar/calendar.js

@@ -7,15 +7,16 @@
  * @lastTime     : 2021-08-20 16:52:43
  * @FilePath     : /u-view2.0/uview-ui/libs/config/props/calendar.js
  */
+import { t } from '../../libs/i18n'
 export default {
     // calendar 组件
     calendar: {
-        title: '日期选择',
+        title: t("up.calendar.chooseDates"),
         showTitle: true,
         showSubtitle: true,
         mode: 'single',
-        startText: '开始',
-        endText: '结束',
+        startText: t("up.common.start"),
+        endText: t("up.common.end"),
         customList: [],
         color: '#3c9cff',
         minDate: 0,
@@ -26,8 +27,8 @@ export default {
         formatter: null,
         showLunar: false,
         showMark: true,
-        confirmText: '确定',
-        confirmDisabledText: '确定',
+        confirmText: t("up.common.confirm"),
+        confirmDisabledText: t("up.common.confirm"),
         show: false,
         closeOnClickOverlay: false,
         readonly: false,
@@ -38,8 +39,10 @@ export default {
         allowSameDay: false,
 		round: 0,
 		monthNum: 3,
-        weekText: ['一', '二', '三', '四', '五', '六', '日'],
+        weekText: [t("up.week.one"), t("up.week.two"), t("up.week.three"), t("up.week.four"), t("up.week.five"), t("up.week.six"), t("up.week.seven")],
         forbidDays: [],
-        forbidDaysToast: '该日期已禁用',
+        forbidDaysToast: t("up.calendar.disabled"),
+        monthFormat: '',
+        pageInline: false
     }
 }

+ 1 - 1
node_modules/uview-plus/components/u-calendar/header.vue

@@ -51,7 +51,7 @@
 			weekText: {
 				type: Array,
 				default: () => {
-					return ['一', '二', '三', '四', '五', '六', '日']
+					return []
 				}
 			},
 		},

+ 11 - 3
node_modules/uview-plus/components/u-calendar/month.vue

@@ -2,7 +2,7 @@
 	<view class="u-calendar-month-wrapper" ref="u-calendar-month-wrapper">
 		<view v-for="(item, index) in months" :key="index" :class="[`u-calendar-month-${index}`]"
 			:ref="`u-calendar-month-${index}`" :id="`month-${index}`">
-			<text v-if="index !== 0" class="u-calendar-month__title">{{ item.year }}年{{ item.month }}月</text>
+			<text v-if="index !== 0" class="u-calendar-month__title">{{ monthTitle(item) }}</text>
 			<view class="u-calendar-month__days">
 				<view v-if="showMark" class="u-calendar-month__days__month-mark-wrapper">
 					<text class="u-calendar-month__days__month-mark-wrapper__text">{{ item.month }}</text>
@@ -38,6 +38,7 @@
 	import test from '../../libs/function/test';
 	import defProps from '../../libs/config/props';
 	import dayjs from '../u-datetime-picker/dayjs.esm.min.js';
+	import { t } from '../../libs/i18n'
 	export default {
 		name: 'u-calendar-month',
 		mixins: [mpMixin, mixin],
@@ -168,7 +169,7 @@
 					// #ifdef APP-NVUE
 					style.width = addUnit(dayWidth, 'px')
 					// #endif
-					style.height = addUnit(this.rowHeight)
+					style.height = addUnit(this.rowHeight, 'px')
 					if (index2 === 0) {
 						// 获取当前为星期几,如果为0,则为星期天,减一为每月第一天时,需要向左偏移的item个数
 						week = (week === 0 ? 7 : week) - 1
@@ -306,6 +307,13 @@
 					})
 				})
 			},
+			monthTitle(item) {
+				if (uni.getLocale() == 'zh-Hans' || uni.getLocale() == 'zh-Hant') {
+					return item.year + '年' + (item.month < 10 ? '0' + item.month : item.month) + '月'
+				} else {
+					return (item.month < 10 ? '0' + item.month : item.month) + '/' + item.year
+				}
+			},
 			isForbid(item) {
 				let date = dayjs(item.date).format("YYYY-MM-DD")
 				if (this.mode !== 'range' && this.forbidDays.includes(date)) {
@@ -415,7 +423,7 @@
 								if(this.rangePrompt) {
 									toast(this.rangePrompt)
 								} else {
-									toast(`选择天数不能超过 ${this.maxRange} 天`)
+									toast(t("up.calendar.daysExceed", { days: this.maxRange }))
 								}
 								return
 							}

+ 9 - 0
node_modules/uview-plus/components/u-calendar/props.js

@@ -156,5 +156,14 @@ export const props = defineMixin({
 			type: String,
 			default: defProps.calendar.forbidDaysToast
 		},
+        monthFormat:{
+			type: String,
+			default: defProps.calendar.monthFormat
+		},
+        // 是否页面内展示
+        pageInline:{
+			type: Boolean,
+			default: defProps.calendar.pageInline
+		}
     }
 })

+ 17 - 7
node_modules/uview-plus/components/u-calendar/u-calendar.vue

@@ -2,9 +2,10 @@
 	<u-popup
 		:show="show"
 		mode="bottom"
-		closeable
+		:closeable="!pageInline"
 		@close="close"
 		:round="round"
+		:pageInline="pageInline"
 		:closeOnClickOverlay="closeOnClickOverlay"
 	>
 		<view class="u-calendar">
@@ -17,7 +18,7 @@
 			></uHeader>
 			<scroll-view
 				:style="{
-                    height: addUnit(listHeight)
+                    height: addUnit(listHeight, 'px')
                 }"
 				scroll-y
 				@scroll="onScroll"
@@ -44,6 +45,7 @@
 					:allowSameDay="allowSameDay"
 					:forbidDays="forbidDays"
 					:forbidDaysToast="forbidDaysToast"
+					:monthFormat="monthFormat"
 					ref="month"
 					@monthSelected="monthSelected"
 					@updateMonthTop="updateMonthTop"
@@ -75,7 +77,7 @@ import dayjs from '../u-datetime-picker/dayjs.esm.min.js';
 import Calendar from '../../libs/util/calendar.js'
 import { mpMixin } from '../../libs/mixin/mpMixin.js'
 import { mixin } from '../../libs/mixin/mixin.js'
-import { addUnit, range, error, padZero } from '../../libs/function/index';
+import { addUnit, getPx, range, error, padZero } from '../../libs/function/index';
 import test from '../../libs/function/test';
 /**
  * Calendar 日历
@@ -186,9 +188,11 @@ export default {
 		subtitle() {
 			// 初始化时,this.months为空数组,所以需要特别判断处理
 			if (this.months.length) {
-				return `${this.months[this.monthIndex].year}年${
-					this.months[this.monthIndex].month
-				}月`
+				if (uni.getLocale() == 'zh-Hans' || uni.getLocale() == 'zh-Hant') {
+					return this.months[this.monthIndex].year + '年' + (this.months[this.monthIndex].month < 10 ? '0' + this.months[this.monthIndex].month : this.months[this.monthIndex].month) + '月'
+				} else {
+					return (this.months[this.monthIndex].month < 10 ? '0' + this.months[this.monthIndex].month : this.months[this.monthIndex].month) + '/' + this.months[this.monthIndex].year
+				}
 			} else {
 				return ''
 			}
@@ -246,7 +250,13 @@ export default {
 				return error('maxDate不能小于minDate时间')
 			}
 			// 滚动区域的高度
-			this.listHeight = this.rowHeight * 5 + 30
+			let bottomPadding = 0;
+			if (this.pageInline) {
+				bottomPadding = 0
+			} else {
+				bottomPadding = 30
+			}
+			this.listHeight = this.rowHeight * 5 + bottomPadding
 			this.setMonth()
 		},
 		close() {

+ 333 - 0
node_modules/uview-plus/components/u-cascader/u-cascader.vue

@@ -0,0 +1,333 @@
+<template>
+	<up-popup :show="popupShow" mode="bottom" :popup="false"
+		:mask="true" :closeable="true" :safe-area-inset-bottom="true"
+		close-icon-color="#ffffff" :z-index="uZIndex"
+		:maskCloseAble="maskCloseAble" @close="close">
+		<view class="up-p-t-30 up-p-l-20 up-m-b-10" v-if="headerDirection =='column'">
+			<up-steps v-if="popupShow" dot direction="column" v-model:current="tabsIndex">
+				<up-steps-item  v-for="(item, index) in genTabsList"
+					@click="tabsIndex = index" :title="item.name"></up-steps-item>
+			</up-steps>
+		</view>
+		<view class="up-p-t-20 up-m-b-10" v-else>
+			<up-tabs v-if="popupShow" :list="genTabsList"
+				:scrollable="true" v-model:current="tabsIndex" @change="tabsChange" ref="tabs"></up-tabs>
+		</view>
+		<view class="area-box">
+			<view class="u-flex" :class="{ 'change':isChange }"
+				:style="{transform: optionsCols == 2 && isChange ? 'translateX(-33.3333333%)' : ''}">
+				<template v-for="(levelData, levelIndex) in levelList" :key="levelIndex">
+					<view v-if="optionsCols == 2 || levelIndex == tabsIndex" class="area-item"
+						:style="{ width: optionsCols == 2 ? '33.33333%' : '750rpx'}">
+						<view class="u-padding-10 u-bg-gray" style="height: 100%;">
+							<scroll-view :scroll-y="true" style="height: 100%">
+								<up-cell-group v-if="levelIndex === 0 || selectedValueIndexs[levelIndex - 1] !== undefined">
+									<up-cell v-for="(item,index) in levelData"
+										:title="item[labelKey]" :arrow="false"
+										:index="index" :key="index"
+										@click="levelChange(levelIndex, index)">
+										<template v-slot:right-icon>
+											<up-icon v-if="selectedValueIndexs[levelIndex] === index"
+												size="17" name="checkbox-mark"></up-icon>
+										</template>
+									</up-cell>
+								</up-cell-group>
+							</scroll-view>
+						</view>
+					</view>
+				</template>
+			</view>
+		</view>
+		<!-- 添加按钮区域 -->
+		<view class="u-cascader-action up-flex up-flex-between">
+			<view class="u-padding-20 up-flex-fill">
+				<up-button @click="handleCancel" type="default">{{ t("up.common.cancel") }}</up-button>
+			</view>
+			<view class="u-padding-20 up-flex-fill">
+				<up-button @click="handleConfirm" type="primary">{{ t("up.common.confirm") }}</up-button>
+			</view>
+		</view>
+	</up-popup>
+</template>
+
+<script>
+	/**
+	 * u-cascader 通用无限级联选择器
+	 * @property {String Number} z-index 弹出时的z-index值(默认1075)
+	 * @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭Picker(默认true)
+	 * @property {Array} data 级联数据
+	 * @property {Array} default-value 默认选中的值
+	 * @property {String} valueKey 指定选项的值为选项对象中的哪个属性值
+	 * @property {String} labelKey 指定选项标签为选项对象中的哪个属性值
+	 * @property {String} childrenKey 指定选项的子选项为选项对象中的哪个属性值
+	 * @property {Boolean} autoClose 是否在选择最后一级时自动关闭并触发confirm(默认false)
+	 */
+	import { t } from '../../libs/i18n'
+	export default {
+		name: 'up-cascader',
+		props: {
+			// 通过双向绑定控制组件的弹出与收起
+			show: {
+				type: Boolean,
+				default: false
+			},
+			// 级联数据
+			data: {
+				type: Array,
+				default() {
+					return [];
+				}
+			},
+			// 默认选中的值
+			modelValue: {
+				type: Array,
+				default() {
+					return [];
+				}
+			},
+			// 指定选项的值为选项对象中的哪个属性值
+			valueKey: {
+				type: String,
+				default: 'value'
+			},
+			// 指定选项标签为选项对象中的哪个属性值
+			labelKey: {
+				type: String,
+				default: 'label'
+			},
+			// 指定选项的子选项为选项对象中的哪个属性值
+			childrenKey: {
+				type: String,
+				default: 'children'
+			},
+			// 是否允许通过点击遮罩关闭Picker
+			maskCloseAble: {
+				type: Boolean,
+				default: true
+			},
+			// 弹出的z-index值
+			zIndex: {
+				type: [String, Number],
+				default: 0
+			},
+			// 是否在选择最后一级时自动关闭并触发confirm
+			autoClose: {
+				type: Boolean,
+				default: false
+			},
+			// 选中项目的展示方向direction垂直方向适合文字长度过长
+			headerDirection: {
+				type: String,
+				default: 'row'
+			},
+			// 选项区域列数,支持1列和2列,默认为2列
+			optionsCols: {
+				type: [Number],
+				default: 2
+			}
+		},
+		data() {
+			return {
+				// 存储每一级的数据
+				levelList: [],
+				// 存储每一级选中的索引
+				selectedValueIndexs: [],
+				tabsIndex: 0,
+				popupShow: false,
+				// 新增confirmValues用于存储确认的值
+				confirmValues: []
+			}
+		},
+		watch: {
+			data: {
+				handler() {
+					this.initLevelList();
+				},
+				immediate: true
+			},
+			show() {
+				this.popupShow = this.show;
+			},
+			modelValue: {
+				handler() {
+					this.init();
+				},
+				immediate: true
+			}
+		},
+		computed: {
+			t,
+			isChange() {
+				return this.tabsIndex > 1;
+			},
+			genTabsList() {
+				let tabsList = [{
+					name: "请选择"
+				}];
+				
+				// 根据选中的值动态生成tabs
+				for (let i = 0; i < this.selectedValueIndexs.length; i++) {
+					if (this.selectedValueIndexs[i] !== undefined && this.levelList[i]) {
+						const selectedItem = this.levelList[i][this.selectedValueIndexs[i]];
+						if (selectedItem) {
+							tabsList[i] = {
+								name: selectedItem[this.labelKey]
+							};
+							// 如果还有下一级,则添加"请选择"
+							if (i === this.selectedValueIndexs.length - 1 && 
+								selectedItem[this.childrenKey] && 
+								selectedItem[this.childrenKey].length > 0) {
+								tabsList.push({
+									name: "请选择"
+								});
+							}
+						}
+					}
+				}
+				
+				return tabsList;
+			},
+			uZIndex() {
+				// 如果用户有传递z-index值,优先使用
+				return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
+			}
+		},
+		// 新增confirm事件
+		emits: ['update:modelValue', 'change', 'confirm'],
+		methods: {
+			init() {
+				// 初始化选中值
+				if (this.modelValue && this.modelValue.length > 0) {
+					this.setDefaultValue();
+				}
+			},
+			initLevelList() {
+				// 初始化第一级数据
+				if (this.data && this.data.length > 0) {
+					this.levelList = [this.data];
+					this.selectedValueIndexs = [];
+				}
+			},
+			setDefaultValue() {
+				// 根据默认值设置选中项
+				// 根据modelValue获取indexs给selectedValueIndexs
+				this.selectedValueIndexs = [];
+				let currentLevelData = this.data;
+				
+				for (let i = 0; i < this.modelValue.length; i++) {
+					const value = this.modelValue[i];
+					const index = currentLevelData.findIndex(item => item[this.valueKey] === value);
+					
+					if (index !== -1) {
+						this.selectedValueIndexs.push(index);
+						// 更新下一级的数据
+						if (currentLevelData[index][this.childrenKey]) {
+							currentLevelData = currentLevelData[index][this.childrenKey];
+						} else {
+							// 如果没有子级数据,则停止处理
+							break;
+						}
+					} else {
+						// 如果找不到匹配项,则停止处理
+						break;
+					}
+				}
+			},
+			close() {
+				this.$emit('update:show', false);
+			},
+			tabsChange(item) {
+			},
+			levelChange(levelIndex, index) {
+				// 设置当前级的选中值
+				this.$set(this.selectedValueIndexs, levelIndex, index);
+				
+				// 清除后续级别的选中值
+				for (let i = levelIndex + 1; i < this.selectedValueIndexs.length; i++) {
+					this.$set(this.selectedValueIndexs, i, undefined);
+				}
+				
+				// 获取当前选中项
+				const currentItem = this.levelList[levelIndex][index];
+				
+				// 如果有子级数据,则初始化下一级
+				if (currentItem && currentItem[this.childrenKey] && currentItem[this.childrenKey].length > 0) {
+					// 确保levelList数组足够长
+					if (this.levelList.length <= levelIndex + 1) {
+						this.levelList.push(currentItem[this.childrenKey]);
+					} else {
+						this.$set(this.levelList, levelIndex + 1, currentItem[this.childrenKey]);
+					}
+					// 切换到下一级tab
+					this.tabsIndex = levelIndex + 1;
+				} else {
+					// 没有子级数据,说明是最后一级
+					if (this.autoClose) {
+						// 如果启用自动关闭,则触发change事件并关闭
+						this.emitChange();
+					} else {
+						// 否则只触发change事件,不关闭
+						this.emitChange(false);
+					}
+				}
+			},
+			// 修改emitChange方法,增加closePopup参数
+			emitChange(closePopup = true) {
+				// 构造选中结果
+				const result = [];
+				for (let i = 0; i < this.selectedValueIndexs.length; i++) {
+					if (this.selectedValueIndexs[i] !== undefined && this.levelList[i]) {
+						result.push(this.levelList[i][this.selectedValueIndexs[i]][this.valueKey]);
+					}
+				}
+				
+				// 更新confirmValues
+				this.confirmValues = [...result];
+				
+				// 触发change事件,返回value数组
+				this.$emit('change', this.confirmValues);
+				
+				// 根据参数决定是否关闭弹窗
+				if (closePopup) {
+					this.close();
+				}
+			},
+			handleCancel() {
+				this.close();
+			},
+			handleConfirm() {
+				// 确认时触发confirm事件
+				this.$emit('update:modelValue', this.confirmValues);
+				this.$emit('confirm', this.confirmValues);
+				this.close();
+			}
+		}
+	}
+</script>
+<style lang="scss">
+	.area-box {
+		width: 100%;
+		overflow: hidden;
+		height: 800rpx;
+
+		>view {
+			width: 150%;
+			transition: transform 0.3s ease-in-out 0s;
+			transform: translateX(0);
+
+			&.change {
+				// transform: translateX(-33.3333333%);
+			}
+		}
+
+		.area-item {
+			// width: 750rpx;
+			height: 800rpx;
+		}
+	}
+	
+	// 添加按钮区域样式
+	.u-cascader-action {
+		border-top: 1px solid #eee;
+	}
+</style>

+ 34 - 23
node_modules/uview-plus/components/u-cate-tab/u-cate-tab.vue

@@ -19,26 +19,28 @@
                 	</slot>
 				</view>
 				<view class="u-cate-tab__page-view">
-					<view class="u-cate-tab__page-item" :id="'item' + index"
-						v-for="(item , index) in tabList" :key="index">
-                        <slot name="itemList" :item="item">
-                        </slot>
-						<template v-if="!$slots['itemList']">
-							<view class="item-title">
-                                <text>{{item[tabKeyName]}}</text>
-                            </view>
-                            <view class="item-container">
-                                <template v-for="(item1, index1) in item.children" :key="index1">
-                                    <slot name="pageItem" :pageItem="item1">
-                                        <view class="thumb-box" >
-                                            <image class="item-menu-image" :src="item1.icon" mode=""></image>
-                                            <view class="item-menu-name">{{item1[itemKeyName]}}</view>
-                                        </view>
-                                    </slot>
-                                </template>
-                            </view>
-						</template>
-					</view>
+					<template :key="index" v-for="(item , index) in tabList">
+						<view v-if="mode == 'follow' || ( mode == 'tab' && index == innerCurrent)"
+							class="u-cate-tab__page-item" :id="'item' + index">
+							<slot name="itemList" :item="item">
+							</slot>
+							<template v-if="!$slots['itemList']">
+								<view class="item-title">
+									<text>{{item[tabKeyName]}}</text>
+								</view>
+								<view class="item-container">
+									<template v-for="(item1, index1) in item.children" :key="index1">
+										<slot name="pageItem" :pageItem="item1">
+											<view class="thumb-box" >
+												<image class="item-menu-image" :src="item1.icon" mode=""></image>
+												<view class="item-menu-name">{{item1[itemKeyName]}}</view>
+											</view>
+										</slot>
+									</template>
+								</view>
+							</template>
+						</view>
+					</template>
 				</view>
 			</scroll-view>
 		</view>
@@ -49,6 +51,10 @@
 	export default {
 		name: 'up-cate-tab',
         props: {
+			mode: {
+                type: String,
+                default: 'follow' // follo跟随联动, tab单一显示。
+            },
 			height: {
                 type: String,
                 default: '100%'
@@ -119,10 +125,13 @@
 			addUnit,
 			// 点击左边的栏目切换
 			async swichMenu(index) {
-				if(this.arr.length == 0) {
-					await this.getMenuItemTop();
+				if (this.mode == 'follow') {
+					if(this.arr.length == 0) {
+						await this.getMenuItemTop();
+					}
+					this.scrollIntoView = 'item' + index;
 				}
-				this.scrollIntoView = 'item' + index;
+
 				if (index == this.innerCurrent) return;
 				this.$nextTick(function(){
 					this.innerCurrent = index;
@@ -216,6 +225,7 @@
 			},
 			// 右边菜单滚动
 			async rightScroll(e) {
+				if (this.mode !== 'follow') return;
 				this.oldScrollTop = e.detail.scrollTop;
                 // console.log(e.detail.scrollTop)
                 // console.log(JSON.stringify(this.arr))
@@ -261,6 +271,7 @@
 	.u-cate-tab__wrap {
 		flex: 1;
 		display: flex;
+		flex-direction: row;
 		overflow: hidden;
 	}
 

+ 1 - 1
node_modules/uview-plus/components/u-checkbox-group/u-checkbox-group.vue

@@ -108,7 +108,7 @@
 				// #ifdef VUE2
 				this.$emit("input", values);
 				// #endif
-        // 放在最后更新,否则change事件传出去的values不会更新
+       			// 放在最后更新,否则change事件传出去的values不会更新
 				this.$emit('change', values)
 			},
 		}

+ 5 - 4
node_modules/uview-plus/components/u-checkbox/u-checkbox.vue

@@ -177,7 +177,7 @@
 				const style = {}
 				if (!this.usedAlone) {
 					if (this.parentData.borderBottom && this.parentData.placement === 'row') {
-						error('检测到您将borderBottom设置为true,需要同时将u-checkbox-group的placement设置为column才有效')
+						error('检测到您将borderBottom设置为true,需要同时将up-checkbox-group的placement设置为column才有效')
 					}
 					// 当父组件设置了显示下边框并且排列形式为纵向时,给内容和边框之间加上一定间隔
 					if (this.parentData.borderBottom && this.parentData.placement === 'column') {
@@ -197,13 +197,14 @@
 					// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用
 					this.updateParentData()
 					if (!this.parent) {
-						error('u-checkbox必须搭配u-checkbox-group组件使用')
+						error('up-checkbox必须搭配up-checkbox-group组件使用')
 					}
+					let value = '';
 					// #ifdef VUE2
-					const value = this.parentData.value
+					value = this.parentData.value
 					// #endif
 					// #ifdef VUE3
-					const value = this.parentData.modelValue
+					value = this.parentData.modelValue
 					// #endif
 					// 设置初始化时,是否默认选中的状态,父组件u-checkbox-group的value可能是array,所以额外判断
 					if (this.checked) {

+ 109 - 0
node_modules/uview-plus/components/u-choose/u-choose.vue

@@ -0,0 +1,109 @@
+<style scoped lang="scss">
+	.up-choose {
+		::v-deep .up-tag {
+		    font-weight: 600;
+		}
+		&:last-child {
+			margin-right: 0;
+		}
+	}
+	
+	.up-choose-wrap {
+		flex-wrap: wrap;
+	}
+	
+	.up-choose-nowrap {
+		flex-wrap: nowrap;
+		white-space: nowrap;
+	}
+</style>
+
+<template>
+	<scroll-view 
+		:scroll-x="wrap === false" 
+		:class="['up-choose', wrap ? 'up-choose-wrap' : 'up-choose-nowrap']">
+        <template :key="item.id"  v-for="(item,index) in options">
+            <view :style="{width: width, display: 'inline-block'}">
+                <slot :item="item" :index="index">
+                    <up-tag :type="index == currentIndex ? 'primary' : 'info'"
+                        size="large" :plain="index == currentIndex ? false : true"
+                        :class="currentIndex === index ? 'active': ''" :height="itemHeight"
+                        :style="{width: itemWidth, padding: itemPadding}"
+                        @click="change(index)">
+                        {{item[labelName]}}
+                    </up-tag>
+                </slot>
+            </view>
+        </template>
+	</scroll-view>
+</template>
+
+<script>
+	export default {
+	    name: 'up-choose',
+	    props: {
+			options:{
+				type: Array,
+				default: ()=>{
+					return [];
+				}
+			},
+			modelValue: {
+				type: [Number,String,Array],
+				default: false
+			},
+			type: {
+			    type: [String],
+			    default: 'radio'
+			},
+			itemWidth: {
+			    type: [String],
+			    default: 'auto'
+			},
+			itemHeight: {
+			    type: [String],
+			    default: '50px'
+			},
+            itemPadding: {
+                type: [String],
+			    default: '8px'
+            },
+			labelName: {
+			    type: String,
+			    default: 'title'
+			},
+			valueName: {
+			    type: String,
+			    default: 'value'
+			},
+			customClick: {
+			    type: Boolean,
+			    default: false
+			},
+			// 是否换行
+			wrap: {
+				type: Boolean,
+				default: true
+			}
+	    },
+	    data() {
+	        return {
+				currentIndex: ''
+	        }
+	    },
+	    created: function () {
+			this.currentIndex = this.modelValue;
+	    },
+        emits: ['update:modelValue', 'custom-click'],
+	    methods: {
+	        change(index){
+				if (this.customClick) {
+					this.$emit('custom-click', index);
+				} else {
+					this.currentIndex = index;
+					this.$emit('update:modelValue', index);
+				}
+			}
+	    }
+	}
+</script>

+ 5 - 3
node_modules/uview-plus/components/u-city-locate/u-city-locate.vue

@@ -3,7 +3,7 @@
 		<up-index-list :indexList="indexList">
 			<template #header>
 				<view class="u-current-city-wrap">
-					<view class="u-current-city-title">定位城市</view>
+					<view class="u-current-city-title">{{ t("up.cityLocate.locateCity") }}</view>
 					<view class="u-current-city-item" @tap="location">
 						<view class="u-location-city">{{locationCity}}</view>
 					</view>
@@ -40,6 +40,7 @@
 </template>
 
 <script>
+	import { t } from '../../libs/i18n'
 	export default{
 		name: 'u-city-locate',
 		props:{
@@ -96,11 +97,12 @@
 		},
 		data(){
 			return{
-				locationCity: '定位中....'
+				locationCity: t("up.cityLocate.locating") + '....'
 			}
 		},
 		emits: ['location-success', 'select-city'],
 		methods:{
+			t,
 			// 获取城市
 			selectedCity(city){
 				this.locationCity = city[this.nameKey];
@@ -123,7 +125,7 @@
 						});
 				    },
 					fail(){
-						That.locationCity = "定位失败,请点击重试"
+						That.locationCity = t("up.cityLocate.fail");
 					}
 				});
 			},

+ 5 - 5
node_modules/uview-plus/components/u-code/code.js

@@ -5,16 +5,16 @@
  * @Date         : 2021-08-20 16:44:21
  * @LastAuthor   : LQ
  * @lastTime     : 2021-08-20 16:55:27
- * @FilePath     : /u-view2.0/uview-ui/libs/config/props/code.js
+ * @FilePath     : /uview-plus/libs/config/props/code.js
  */
-
+import { t } from '../../libs/i18n'
 export default {
     // code 组件
     code: {
         seconds: 60,
-        startText: '获取验证码',
-        changeText: 'X秒重新获取',
-        endText: '重新获取',
+        startText: t("up.code.send"),
+        changeText: t("up.code.resendAfter"),
+        endText: t("up.code.resend"),
         keepRunning: false,
         uniqueKey: ''
     }

+ 1095 - 0
node_modules/uview-plus/components/u-color-picker/u-color-picker.vue

@@ -0,0 +1,1095 @@
+<template>
+	<view class="up-color-picker">
+        <view clas="up-color-picker__trigger" @click="show = true"
+            :style="{backgroundColor: value}" >
+            <slot></slot>
+        </view>
+		<up-popup :show="show" mode="bottom" round="10" @close="close" :closeOnClickOverlay="true">
+			<view class="up-color-picker__content">
+				<view class="up-color-picker__header">
+					<text class="up-color-picker__title">选择颜色</text>
+				</view>
+				
+				<!-- 纯色/渐变色切换 -->
+				<view class="up-color-picker__switch">
+					<up-subsection
+						:list="[{ name: '纯色' }, { name: '渐变' }]"
+						:current="colorTypeIndex"
+						@change="changeColorType"
+						fontSize="14"
+					></up-subsection>
+				</view>
+				
+				<!-- 渐变色选择器 -->
+				<view v-if="colorTypeIndex == 1" class="up-color-picker__gradient">
+					<!-- 渐变色控制条 -->
+					<view 
+						class="up-color-picker__gradient-track"
+                        :style="{ background: gradientStyle }"
+					>
+						<view 
+							class="up-color-picker__gradient-pointer" 
+							v-for="(item, index) in gradientColors" 
+							:key="index"
+							:style="{ left: getGradientPointerPosition(index) + 'px' }"
+                            @click="openColorPickerForGradient(index)"
+							@touchstart="onPointerTouchStart($event, index)"
+							@touchmove.stop="onPointerTouchMove"
+							@touchend.stop="onPointerTouchEnd"
+						>
+							<view class="up-color-picker__gradient-pointer-inner" :style="{ backgroundColor: item.color }"></view>
+						</view>
+					</view>
+					
+					<view class="up-color-picker__gradient-controls">
+						<up-button 
+							type="primary" 
+							size="mini" 
+							plain 
+							@click="addGradientColor"
+							class="up-color-picker__add-btn"
+						>
+							添加颜色
+						</up-button>
+					</view>
+					
+					<!-- 圆形方向选择器 -->
+					<view class="up-color-picker__gradient-direction">
+						<text>方向:</text>
+						<view class="up-color-picker__gradient__direction-circle" 
+							@touchstart="onDirectionTouchStart"
+							@touchmove="onDirectionTouchMove"
+							@touchend="onDirectionTouchEnd">
+							<view 
+								class="up-color-picker__direction-pointer"
+								:style="{ 
+									left: directionPointer.x + 'px', 
+									top: directionPointer.y + 'px' 
+								}"
+							></view>
+						</view>
+					</view>
+				</view>
+
+                <!-- 纯色选择器 -->
+				<view class="up-color-picker__solid">
+					<!-- 饱和度和明度选择区域 -->
+					<view 
+						class="up-color-picker__saturation" 
+						:style="{ backgroundColor: `hsl(${hue}, 100%, 50%)` }"
+						@touchstart="onSaturationTouchStart"
+						@touchmove="onSaturationTouchMove"
+						@touchend="onSaturationTouchEnd"
+					>
+						<view 
+							class="up-color-picker__saturation-pointer" 
+							:style="{
+								left: saturationPosition.x + 'px',
+								top: saturationPosition.y + 'px'
+							}"
+						></view>
+					</view>
+					
+					<!-- 色相选择 -->
+					<view 
+						class="up-color-picker__hue"
+						@touchstart="onHueTouchStart"
+						@touchmove="onHueTouchMove"
+						@touchend="onHueTouchEnd"
+					>
+						<view 
+							class="up-color-picker__hue-pointer" 
+							:style="{ left: huePosition + 'px' }"
+						></view>
+					</view>
+					
+					<!-- 透明度选择 -->
+					<view v-if="colorTypeIndex == 0" 
+						class="up-color-picker__alpha"
+						@touchstart="onAlphaTouchStart"
+						@touchmove="onAlphaTouchMove"
+						@touchend="onAlphaTouchEnd"
+					>
+						<view class="up-color-picker__alpha-bg"></view>
+						<view 
+							class="up-color-picker__alpha-pointer" 
+							:style="{ left: alphaPosition + 'px' }"
+						></view>
+					</view>
+				</view>
+				
+				<!-- 常用颜色 -->
+				<view v-if="commonColors && commonColors.length" class="up-color-picker__common">
+					<text class="up-color-picker__common-title">常用颜色</text>
+					<view class="up-color-picker__common-list">
+						<view 
+							v-for="(color, index) in commonColors" 
+							:key="index"
+							class="up-color-picker__common-item"
+							:style="{ backgroundColor: color }"
+							@click="selectCommonColor(color)"
+						></view>
+					</view>
+				</view>
+				
+				<!-- 颜色预览和操作按钮 -->
+				<view class="up-color-picker__footer">
+					<view class="up-color-picker__preview">
+						<view 
+							class="up-color-picker__preview-color"
+							:style="{ backgroundColor: displayColor }"
+						></view>
+						<text class="up-color-picker__preview-text">{{ displayColor }}</text>
+					</view>
+					<view class="up-color-picker__actions">
+						<up-button 
+							type="primary" 
+							size="small" 
+							@click="confirm"
+							class="up-color-picker__btn"
+						>
+							确定
+						</up-button>
+						<up-button 
+							type="info" 
+							size="small" 
+							@click="close"
+							class="up-color-picker__btn"
+						>
+							取消
+						</up-button>
+					</view>
+				</view>
+			</view>
+		</up-popup>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'up-color-picker',
+	props: {
+		// 初始颜色值
+		modelValue: {
+			type: String,
+			default: '#ff0000'
+		},
+		// 常用颜色列表
+		commonColors: {
+			type: Array,
+			default: () => [
+            ]
+		}
+	},
+	data() {
+		return {
+            show: false,
+			// 颜色类型索引:0-纯色,1-渐变色
+			colorTypeIndex: 0,
+			// 纯色相关数据
+			hue: 0,
+			saturation: 100,
+			lightness: 50,
+			alpha: 1,
+			saturationPosition: { x: 150, y: 0 },
+			huePosition: 0,
+			alphaPosition: 0, // 将在initColor中设置为最右侧
+			// 渐变色相关数据
+			gradientColors: [
+				{ color: '#ff0000', percent: 0 },
+				{ color: '#0000ff', percent: 1 }
+			],
+			gradientDirections: [
+				{ label: '从左到右', value: 'to right' },
+				{ label: '从上到下', value: 'to bottom' },
+				{ label: '从左上到右下', value: 'to bottom right' },
+				{ label: '从右上到左下', value: 'to bottom left' }
+			],
+			currentDirection: { label: '从左到右', value: 'to right' },
+			showDirectionPicker: false,
+			// 当前选中颜色
+			currentColor: '#ff0000',
+			// 渐变色控制相关
+			draggingPointerIndex: -1,
+			directionPointer: { x: 20, y: 20 }, // 默认向右(在圆的右侧)
+			// 渐变节点颜色修改相关
+			editingGradientIndex: -1,
+			// 保存纯色和渐变色的各自状态
+			solidColorState: {
+				hue: 0,
+				saturation: 100,
+				lightness: 50,
+				alpha: 1,
+				saturationPosition: { x: 150, y: 0 },
+				huePosition: 0,
+				alphaPosition: 0, // 将在initColor中设置为最右侧
+				currentColor: '#ff0000'
+			},
+			gradientColorState: {
+				gradientColors: [
+					{ color: '#ff0000', percent: 0 },
+					{ color: '#0000ff', percent: 1 }
+				],
+				currentDirection: { label: '从左到右', value: 'to right' },
+				directionPointer: { x: 100, y: 0 }
+			},
+			// 区分预览类型:solid-纯色预览,gradient-整体渐变预览,gradient-point-渐变点预览
+			previewType: 'solid'
+		}
+	},
+	computed: {
+		// 渐变色样式
+		gradientStyle() {
+			const colors = this.gradientColors.map(item => `${item.color} ${Math.round(item.percent * 100)}%`).join(', ')
+			return `linear-gradient(${this.currentDirection.value}, ${colors})`
+		},
+		// 显示的颜色预览值
+		displayColor() {
+			if (this.previewType === 'gradient-point' && this.editingGradientIndex >= 0) {
+				return this.gradientColors[this.editingGradientIndex].color;
+			}
+            console.log(this.editingGradientIndex)
+			return this.currentColor;
+		}
+	},
+	watch: {
+		show(newVal) {
+			if (newVal) {
+				this.initColor()
+			}
+		},
+        colorTypeIndex(newVal) {
+            if (newVal == 0) {
+                this.editingGradientIndex = -1
+            }
+		},
+	},
+	mounted() {
+		this.initColor()
+	},
+    emits: ['update:modelValue', 'confirm', 'close'],
+	methods: {
+		// 初始化颜色
+		initColor() {
+			if (this.modelValue) {
+				this.currentColor = this.modelValue
+				if (this.modelValue.includes('linear-gradient')) {
+					// 解析渐变色
+					this.colorTypeIndex = 1
+					this.parseGradientColor(this.modelValue)
+					this.previewType = 'gradient';
+				} else {
+					// 解析纯色
+					this.colorTypeIndex = 0
+					this.parseSolidColor(this.modelValue)
+					this.previewType = 'solid';
+				}
+			}
+            // 初始化方向指针位置
+            this.initDirectionPointer();
+            // 初始化alphaPosition为最右侧
+            this.initAlphaPosition();
+		},
+		
+        // 初始化alpha位置为最右侧
+        async initAlphaPosition() {
+            const query = uni.createSelectorQuery().in(this);
+            query.select('.up-color-picker__alpha').boundingClientRect();
+            await this.$nextTick();
+            query.exec(res => {
+                const rect = res[0];
+                if (rect) {
+                    this.alphaPosition = rect.width || 150; // 默认150像素
+                } else {
+                    this.alphaPosition = 150; // 默认值
+                }
+                
+                // 同步solidColorState中的alphaPosition
+                this.solidColorState.alphaPosition = this.alphaPosition;
+                this.updateSolidColor();
+            });
+        },
+        
+        // 初始化方向指针位置
+        initDirectionPointer() {
+            const angle = this.getDirectionAngle(this.currentDirection.value);
+            this.setDirectionPointerByAngle(angle);
+        },
+        
+        // 根据方向值获取角度
+        getDirectionAngle(direction) {
+            switch(direction) {
+                case 'to right': return 0;
+                case 'to bottom': return 90;
+                case 'to left': return 180;
+                case 'to top': return 270;
+                case 'to bottom right': return 45;
+                case 'to bottom left': return 135;
+                case 'to top left': return 225;
+                case 'to top right': return 315;
+                default: return 0;
+            }
+        },
+        
+        // 根据角度设置方向指针位置
+        setDirectionPointerByAngle(angle) {
+            const radian = angle * Math.PI / 180;
+            const radius = 20; // 圆半径
+            this.directionPointer = {
+                x: radius * Math.cos(radian) + 20, // 20是圆心位置
+                y: radius * Math.sin(radian) + 20
+            };
+        },
+		
+		// 打开颜色选择器以修改渐变节点颜色
+		openColorPickerForGradient(index) {
+			this.editingGradientIndex = index;
+			// 设置当前颜色为选中节点的颜色
+			const color = this.gradientColors[index].color;
+			this.currentColor = color;
+			// 设置预览类型为渐变点预览
+			this.previewType = 'gradient-point';
+			
+			// 解析颜色并设置选择器位置
+			if (!color.includes('linear-gradient')) {
+				this.colorTypeIndex = 0;
+				this.parseSolidColor(color);
+				// 保存当前渐变状态
+				this.gradientColorState = {
+					gradientColors: [...this.gradientColors],
+					currentDirection: {...this.currentDirection},
+					directionPointer: {...this.directionPointer}
+				};
+			}
+		},
+		
+		// 解析纯色
+		parseSolidColor(color) {
+			// 简化处理,实际项目中可以使用更复杂的颜色解析
+			this.currentColor = color
+			// 如果正在编辑渐变节点,则更新该节点颜色
+			if (this.editingGradientIndex >= 0) {
+				this.gradientColors[this.editingGradientIndex].color = color;
+				// 切换回渐变模式并恢复渐变状态
+				this.colorTypeIndex = 1;
+				Object.assign(this, this.solidColorState);
+				this.previewType = 'gradient-point';
+			} else {
+				this.previewType = 'solid';
+			}
+		},
+		
+		// 解析渐变色
+		parseGradientColor(gradient) {
+			// 简化处理,实际项目中可以使用更复杂的渐变解析
+			this.currentColor = gradient
+			// this.previewType = 'gradient';
+		},
+		
+		// 切换颜色类型
+		changeColorType(index) {
+			// 保存当前状态
+			if (this.colorTypeIndex === 0) {
+				// 保存纯色状态
+				this.solidColorState = {
+					hue: this.hue,
+					saturation: this.saturation,
+					lightness: this.lightness,
+					alpha: this.alpha,
+					saturationPosition: {...this.saturationPosition},
+					huePosition: this.huePosition,
+					alphaPosition: this.alphaPosition,
+					currentColor: this.currentColor
+				};
+			} else {
+				// 保存渐变状态
+				this.gradientColorState = {
+					gradientColors: [...this.gradientColors],
+					currentDirection: {...this.currentDirection},
+					directionPointer: {...this.directionPointer}
+				};
+			}
+			
+			this.colorTypeIndex = index;
+			
+			// 恢复目标状态
+			if (index === 0) {
+				Object.assign(this, this.solidColorState);
+				this.previewType = 'solid';
+			} else {
+				Object.assign(this, this.gradientColorState);
+				// 确保gradientColors是响应式的
+				this.gradientColors = [...this.gradientColorState.gradientColors];
+				this.previewType = 'gradient';
+			}
+		},
+		
+		// 饱和度和明度触摸开始
+		onSaturationTouchStart(e) {
+			this.updateSaturationPosition(e)
+		},
+		
+		// 饱和度和明度触摸移动
+		onSaturationTouchMove(e) {
+			this.updateSaturationPosition(e)
+		},
+		
+		// 饱和度和明度触摸结束
+		onSaturationTouchEnd(e) {
+			this.updateSaturationPosition(e)
+		},
+		
+		// 更新饱和度和明度位置
+		updateSaturationPosition(e) {
+			const touch = e.touches[0] || e.changedTouches[0]
+			const target = e.currentTarget
+			const query = uni.createSelectorQuery().in(this);
+			query.select('.up-color-picker__saturation').boundingClientRect()
+			
+			query.exec(res => {
+				const rect = res[0];
+				if (rect) {
+					let x = touch.clientX - rect.left
+					let y = touch.clientY - rect.top
+					
+					// 边界处理
+					x = Math.max(0, Math.min(x, rect.width))
+					y = Math.max(0, Math.min(y, rect.height))
+					
+					this.saturationPosition = { x, y }
+					this.updateSolidColor()
+				}
+			})
+		},
+		
+		// 色相触摸开始
+		onHueTouchStart(e) {
+			this.updateHuePosition(e)
+		},
+		
+		// 色相触摸移动
+		onHueTouchMove(e) {
+			this.updateHuePosition(e)
+		},
+		
+		// 色相触摸结束
+		onHueTouchEnd(e) {
+			this.updateHuePosition(e)
+		},
+		
+		// 更新色相位置
+		updateHuePosition(e) {
+			const touch = e.touches[0] || e.changedTouches[0]
+			const target = e.currentTarget
+			const query = uni.createSelectorQuery().in(this);
+			query.select('.up-color-picker__hue').boundingClientRect()
+			
+			query.exec(res => {
+				const rect = res[0];
+				if (rect) {
+					let x = touch.clientX - rect.left
+					x = Math.max(0, Math.min(x, rect.width))
+					this.huePosition = x
+					this.hue = Math.round((x / rect.width) * 360)
+					this.updateSolidColor()
+				}
+			})
+		},
+		
+		// 透明度触摸开始
+		onAlphaTouchStart(e) {
+			this.updateAlphaPosition(e)
+		},
+		
+		// 透明度触摸移动
+		onAlphaTouchMove(e) {
+			this.updateAlphaPosition(e)
+		},
+		
+		// 透明度触摸结束
+		onAlphaTouchEnd(e) {
+			this.updateAlphaPosition(e)
+		},
+		
+		// 更新透明度位置
+		updateAlphaPosition(e) {
+			const touch = e.touches[0] || e.changedTouches[0]
+			const target = e.currentTarget
+			const query = uni.createSelectorQuery().in(this);
+			query.select('.up-color-picker__alpha').boundingClientRect()
+			
+			query.exec(res => {
+				const rect = res[0];
+				if (rect) {
+					let x = touch.clientX - rect.left
+					x = Math.max(0, Math.min(x, rect.width))
+					this.alphaPosition = x
+					this.alpha = x / rect.width
+					this.updateSolidColor()
+				}
+			})
+		},
+		
+		// 更新纯色
+		updateSolidColor() {
+			// 使用实际元素尺寸替代硬编码的150
+			const query = uni.createSelectorQuery().in(this);
+			query.select('.up-color-picker__saturation').boundingClientRect();
+			
+			query.exec(res => {
+				const rect = res[0];
+				const size = rect ? Math.min(rect.width, rect.height) : 150; // 默认150作为后备值
+				
+				const s = (this.saturationPosition.x / size) * 100
+				const l = 100 - (this.saturationPosition.y / size) * 100
+				this.saturation = s
+				this.lightness = l
+				
+				// 使用正确的HSL转RGB算法
+				if (this.colorTypeIndex == 0) {
+					this.currentColor = this.hslToRgb(this.hue, this.saturation, this.lightness, this.alpha)
+				} else if (this.colorTypeIndex == 1) {
+					this.gradientColors[this.editingGradientIndex].color
+						= this.hslToRgb(this.hue, this.saturation, this.lightness, this.alpha)
+				}
+			});
+		},
+		
+		// 添加渐变色
+		addGradientColor() {
+			if (this.gradientColors.length < 5) {
+				this.gradientColors.push({
+					color: '#ffffff',
+					percent: 1
+				})
+			}
+		},
+		
+		// 删除渐变色
+		removeGradientColor(index) {
+			if (this.gradientColors.length > 2) {
+				this.gradientColors.splice(index, 1)
+			}
+		},
+		
+		// 获取渐变控制点位置
+		getGradientPointerPosition(index) {
+			const trackWidth = 280; // 需要与样式中的宽度一致
+			return this.gradientColors[index].percent * trackWidth;
+		},
+		
+		// 更新渐变色
+		updateGradientColor(e) {
+			const touch = e.touches[0] || e.changedTouches[0];
+			const query = uni.createSelectorQuery().in(this);
+			query.select('.up-color-picker__gradient-track').boundingClientRect();
+			
+			query.exec(res => {
+				const rect = res[0];
+				if (rect) {
+					let x = touch.clientX - rect.left;
+					// 边界处理
+					x = Math.max(0, Math.min(x, rect.width));
+					const percent = x / rect.width;
+					
+					// 如果正在拖动控制点,则更新该控制点位置
+					if (this.draggingPointerIndex >= 0) {
+						this.gradientColors[this.draggingPointerIndex].percent = percent;
+						// 保持控制点顺序
+						this.gradientColors.sort((a, b) => a.percent - b.percent);
+					} else {
+						// 否则添加新的控制点(此处可选功能)
+					}
+				}
+			});
+		},
+		
+		// 控制点触摸开始
+		onPointerTouchStart(e, index) {
+			this.draggingPointerIndex = index;
+			// 点击控制点时更新预览颜色
+			this.currentColor = this.gradientColors[index].color;
+			this.previewType = 'gradient-point';
+			this.editingGradientIndex = index; // 添加这行以正确设置编辑索引
+			// 阻止事件冒泡
+			e.stopPropagation();
+		},
+		
+		// 控制点触摸移动
+		onPointerTouchMove(e) {
+			if (this.draggingPointerIndex === -1) return;
+			
+			const touch = e.touches[0] || e.changedTouches[0];
+			const query = uni.createSelectorQuery().in(this);
+			query.select('.up-color-picker__gradient-track').boundingClientRect();
+			
+			query.exec(res => {
+				const rect = res[0];
+				if (rect) {
+					let x = touch.clientX - rect.left;
+					// 边界处理
+					x = Math.max(0, Math.min(x, rect.width));
+					let percent = x / rect.width;
+					
+					// 处理边界情况,确保能精确到达两端
+					if (x === 0) percent = 0;
+					if (x === rect.width) percent = 1;
+					
+					// 更新控制点位置
+					this.gradientColors[this.draggingPointerIndex].percent = percent;
+					// 保持控制点顺序
+					this.gradientColors.sort((a, b) => a.percent - b.percent);
+					// 更新拖拽索引以匹配排序后的索引
+					this.draggingPointerIndex = this.gradientColors.findIndex((item, index) => {
+						// 使用更精确的比较方法,处理浮点数精度问题
+						return Math.abs(item.percent - percent) < 0.0001;
+					});
+				}
+			});
+		},
+		
+		// 控制点触摸结束
+		onPointerTouchEnd() {
+			this.draggingPointerIndex = -1;
+		},
+		
+		// 方向选择器触摸开始
+		onDirectionTouchStart(e) {
+			this.updateDirection(e);
+		},
+		
+		// 方向选择器触摸移动
+		onDirectionTouchMove(e) {
+			this.updateDirection(e);
+		},
+		
+		// 方向选择器触摸结束
+		onDirectionTouchEnd(e) {
+			this.updateDirection(e);
+		},
+		
+		// 更新方向
+		updateDirection(e) {
+			const touch = e.touches[0] || e.changedTouches[0];
+			const query = uni.createSelectorQuery().in(this);
+			query.select('.up-color-picker__gradient__direction-circle').boundingClientRect();
+			
+			query.exec(res => {
+				const rect = res[0];
+				if (rect) {
+					const centerX = rect.left + rect.width / 2;
+					const centerY = rect.top + rect.height / 2;
+					const x = touch.clientX - centerX;
+					const y = touch.clientY - centerY;
+					const distance = Math.sqrt(x * x + y * y);
+					const maxDistance = rect.width / 2;
+					
+					// 限制在圆内
+					if (distance <= maxDistance) {
+						this.directionPointer = {
+							x: x + rect.width / 2,
+							y: y + rect.height / 2
+						};
+					} else {
+						// 限制在圆周上
+						const ratio = maxDistance / distance;
+						this.directionPointer = {
+							x: x * ratio + rect.width / 2,
+							y: y * ratio + rect.height / 2
+						};
+					}
+					
+					// 计算角度并更新方向
+					const angle = Math.atan2(y, x) * 180 / Math.PI;
+                    console.log(angle)
+					this.updateGradientDirection(angle);
+				}
+			});
+		},
+		
+		// 根据角度更新渐变方向
+		updateGradientDirection(angle) {
+			// 角度转换为标准方向
+			if (angle < 0) angle += 360;
+			
+			if (angle >= 315 || angle < 45) {
+				this.currentDirection = { label: '从左到右', value: 'to right' };
+			} else if (angle >= 45 && angle < 135) {
+				this.currentDirection = { label: '从上到下', value: 'to bottom' };
+			} else if (angle >= 135 && angle < 225) {
+				this.currentDirection = { label: '从右到左', value: 'to left' };
+			} else {
+				this.currentDirection = { label: '从下到上', value: 'to top' };
+			}
+		},
+		
+		// 确认方向选择
+		confirmDirection(e) {
+			this.currentDirection = this.gradientDirections[e.index]
+			this.showDirectionPicker = false
+		},
+		
+		// 选择常用颜色
+		selectCommonColor(color) {
+			this.currentColor = color
+			if (this.colorTypeIndex === 0) {
+				this.parseSolidColor(color)
+			} else {
+				// 如果是渐变模式,将常用颜色作为渐变的一个节点
+				this.gradientColors[this.editingGradientIndex].color = color
+			}
+		},
+		
+		// 确认选择
+		confirm() {
+			let color = this.currentColor
+			if (this.colorTypeIndex === 1) {
+				color = this.gradientStyle
+			}
+            this.$emit('update:modelValue', color)
+            this.show = false;
+			this.$emit('confirm', color)
+			// 重置编辑状态
+			this.editingGradientIndex = -1;
+			this.previewType = this.colorTypeIndex === 0 ? 'solid' : 'gradient';
+			this.close()
+		},
+		
+		// 关闭选择器
+		close() {
+            this.show = false;
+			this.$emit('close')
+		},
+		
+		// HSL转RGB辅助函数
+		hslToRgb(h, s, l, a = 1) {
+			h = h / 360;
+			s = s / 100;
+			l = l / 100;
+			
+			let r, g, b;
+			
+			if (s === 0) {
+				r = g = b = l; // achromatic
+			} else {
+				const hue2rgb = (p, q, t) => {
+					if (t < 0) t += 1;
+					if (t > 1) t -= 1;
+					if (t < 1/6) return p + (q - p) * 6 * t;
+					if (t < 1/2) return q;
+					if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
+					return p;
+				};
+				
+				const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+				const p = 2 * l - q;
+				r = hue2rgb(p, q, h + 1/3);
+				g = hue2rgb(p, q, h);
+				b = hue2rgb(p, q, h - 1/3);
+			}
+			
+			const round = (value) => Math.round(value * 255);
+			return `rgba(${round(r)}, ${round(g)}, ${round(b)}, ${a.toFixed(2)})`;
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.up-color-picker {
+	&__content {
+		width: 100%;
+		padding: 20px;
+		background-color: #fff;
+	}
+	
+	&__header {
+		text-align: center;
+		margin-bottom: 20px;
+	}
+	
+	&__title {
+		font-size: 18px;
+		font-weight: bold;
+		color: #333;
+	}
+	
+	&__switch {
+		margin-bottom: 20px;
+	}
+	
+	&__saturation {
+		position: relative;
+		width: 100%;
+		height: 150px;
+		border-radius: 4px;
+		margin-bottom: 15px;
+		overflow: hidden;
+		
+		&:after {
+			content: '';
+			position: absolute;
+			top: 0;
+			left: 0;
+			right: 0;
+			bottom: 0;
+			background: linear-gradient(to right, #fff, rgba(255,255,255,0));
+		}
+		
+		&:before {
+			content: '';
+			position: absolute;
+			top: 0;
+			left: 0;
+			right: 0;
+			bottom: 0;
+			background: linear-gradient(to top, #000, rgba(0,0,0,0));
+		}
+	}
+	
+	&__saturation-pointer {
+		position: absolute;
+		width: 12px;
+		height: 12px;
+		border: 2px solid #fff;
+		border-radius: 50%;
+		transform: translate(-50%, -50%);
+		box-shadow: 0 0 2px rgba(0,0,0,0.5);
+		pointer-events: none;
+	}
+	
+	&__hue,
+	&__alpha {
+		position: relative;
+		width: 100%;
+		height: 12px;
+		border-radius: 6px;
+		margin-bottom: 15px;
+		cursor: pointer;
+	}
+	
+	&__hue {
+		background: linear-gradient(to right, #f00 0%, #ff0 16.66%, #0f0 33.33%, #0ff 50%, #00f 66.66%, #f0f 83.33%, #f00 100%);
+	}
+	
+	&__alpha {
+		position: relative;
+		overflow: hidden;
+		
+		&-bg {
+			position: absolute;
+			top: 0;
+			left: 0;
+			right: 0;
+			bottom: 0;
+			background: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%),
+						linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%);
+			background-size: 10px 10px;
+			background-position: 0 0, 5px 5px;
+		}
+		
+		&:after {
+			content: '';
+			position: absolute;
+			top: 0;
+			left: 0;
+			right: 0;
+			bottom: 0;
+			background: linear-gradient(to right, rgba(255,255,255,0), rgba(255,255,255,1));
+		}
+	}
+	
+	&__hue-pointer,
+	&__alpha-pointer {
+		position: absolute;
+		width: 4px;
+		height: 16px;
+		background: #fff;
+		border: 1px solid #ccc;
+		border-radius: 2px;
+		transform: translateX(-50%);
+		top: -2px;
+		box-shadow: 0 0 2px rgba(0,0,0,0.5);
+		pointer-events: none;
+	}
+	
+	&__gradient {
+		&-bar {
+			width: 100%;
+			height: 40px;
+			border-radius: 4px;
+		}
+		
+		&-track {
+			position: relative;
+            margin-top: 20px;
+			width: 100%;
+			height: 32px;
+			border-radius: 4px;
+			margin-bottom: 15px;
+			cursor: pointer;
+		}
+		
+		&-pointer {
+			position: absolute;
+			top: -10px;
+			transform: translateX(-50%);
+			width: 0;
+			height: 0;
+			border-left: 8px solid transparent;
+			border-right: 8px solid transparent;
+			border-top: 10px solid #333;
+			z-index: 10;
+		}
+		
+		&-pointer-inner {
+			position: absolute;
+			top: -25px;
+			left: -10px;
+			width: 20px;
+			height: 20px;
+			border-radius: 50%;
+			border: 2px solid #fff;
+			box-shadow: 0 0 2px rgba(0,0,0,0.5);
+		}
+		
+		&-controls {
+			display: flex;
+            flex-direction: row;
+			flex-wrap: wrap;
+			align-items: center;
+			margin-bottom: 15px;
+		}
+		
+		&-item {
+			display: flex;
+            flex-direction: row;
+			align-items: center;
+			margin-right: 10px;
+			margin-bottom: 10px;
+		}
+		
+		&-color-preview {
+			width: 20px;
+			height: 20px;
+			border-radius: 50%;
+			margin-right: 5px;
+			border: 1px solid #eee;
+		}
+		
+		&-percent {
+			font-size: 12px;
+			color: #666;
+			margin-right: 5px;
+		}
+		
+		&-direction {
+			display: flex;
+            flex-direction: row;
+			align-items: center;
+			margin: 10px 0;
+		}
+		
+		&__direction-circle {
+			width: 40px;
+			height: 40px;
+			border-radius: 50%;
+			background: conic-gradient(
+				#ff0000 0deg, 
+				#ffff00 60deg, 
+				#00ff00 120deg, 
+				#00ffff 180deg, 
+				#0000ff 240deg, 
+				#ff00ff 300deg, 
+				#ff0000 360deg
+			);
+			position: relative;
+			margin: 10px auto;
+			cursor: pointer;
+			border: 2px solid #eee;
+		}
+		
+		&__direction-pointer {
+			position: absolute;
+			width: 6px;
+			height: 6px;
+			background: #fff;
+			border: 2px solid #333;
+			border-radius: 50%;
+			transform: translate(-50%, -50%);
+			box-shadow: 0 0 2px rgba(0,0,0,0.5);
+			z-index: 10;
+            pointer-events: none;
+		}
+	}
+	
+	&__add-btn {
+		margin-top: 10px;
+	}
+	
+	&__common {
+		margin-top: 20px;
+		
+		&-title {
+			display: block;
+			margin-bottom: 10px;
+			font-size: 14px;
+			color: #666;
+		}
+		
+		&-list {
+			display: flex;
+            flex-direction: row;
+			flex-wrap: wrap;
+		}
+		
+		&-item {
+			width: 24px;
+			height: 24px;
+			border-radius: 50%;
+			margin-right: 10px;
+			margin-bottom: 10px;
+			border: 1px solid #eee;
+			cursor: pointer;
+		}
+	}
+	
+	&__footer {
+		margin-top: 20px;
+	}
+	
+	&__preview {
+		display: flex;
+        flex-direction: row;
+		align-items: center;
+		margin-bottom: 15px;
+	}
+	
+	&__preview-color {
+		width: 40px;
+		height: 40px;
+		border-radius: 4px;
+		border: 1px solid #eee;
+		margin-right: 10px;
+	}
+	
+	&__preview-text {
+		font-size: 14px;
+		color: #333;
+		flex: 1;
+		overflow: hidden;
+		text-overflow: ellipsis;
+		white-space: nowrap;
+	}
+	
+	&__actions {
+		display: flex;
+        flex-direction: row;
+		justify-content: flex-end;
+		
+		& .up-color-picker__btn {
+			margin-left: 10px;
+		}
+	}
+}
+</style>

+ 7 - 5
node_modules/uview-plus/components/u-copy/u-copy.vue

@@ -1,9 +1,10 @@
 <template>
 	<view @click="handleClick">
-        <slot>复制</slot>
+        <slot>{{ t("up.common.copy") }}</slot>
     </view>
 </template>
 <script>
+import { t } from '../../libs/i18n'
 export default {
     name: "up-copy",
     props: {
@@ -17,16 +18,17 @@ export default {
 		},
 		notice: {
 			type: String,
-			default: '复制成功'
+			default: t("up.common.copy") + t("up.common.success")
 		}
     },
 	emits: ['success'],
     methods: {
+		t,
         handleClick() {
             let content = this.content;
 			if (!content) {
 				uni.showToast({
-				    title: '暂无',
+				    title: t("up.common.none"),
 				    icon: 'none',
 				    duration: 2000,
 				});
@@ -42,7 +44,7 @@ export default {
                 success: function() {
 					if (that.alertStyle == 'modal') {
 						uni.showModal({
-							title: '提示',
+							title: "up.common.tip",
 							content: that.notice
 						});
 					} else {
@@ -55,7 +57,7 @@ export default {
                 },
                 fail:function(){
                     uni.showToast({
-                        title: '复制失败',
+                        title: t("up.common.copy") + t("up.common.fail"),
                         icon: 'none',
                         duration:3000,
                     });

+ 406 - 0
node_modules/uview-plus/components/u-coupon/u-coupon.vue

@@ -0,0 +1,406 @@
+<template>
+	<view class="up-coupon" :class="[`up-coupon--${shape}`, `up-coupon--${type}`, `up-coupon--${size}`, {'up-coupon--disabled': disabled}]" 
+		:style="[couponStyle]" @click="handleClick">
+		<view class="up-coupon__content">
+			<!-- 左侧金额区域 -->
+			<view class="up-coupon__amount">
+				<slot name="unit" :unit="unit" :unitPosition="unitPosition" v-if="unitPosition === 'left'">
+					<text class="up-coupon__amount-unit" v-if="unitPosition === 'left'">{{ unit }}</text>
+				</slot>
+				<slot name="amount" :amount="amount">
+					<text class="up-coupon__amount-value">{{ amount }}</text>
+				</slot>
+				<slot name="unit" :unit="unit" :unitPosition="unitPosition" v-if="unitPosition === 'right'">
+					<text class="up-coupon__amount-unit" v-if="unitPosition === 'right'">{{ unit }}</text>
+				</slot>
+				<slot name="limit" :limit="limit">
+					<text class="up-coupon__amount-limit" v-if="limit">{{ limit }}</text>
+				</slot>
+			</view>
+			
+			<!-- 中间描述区域 -->
+			<view class="up-coupon__info">
+				<slot name="title" :title="title">
+					<text class="up-coupon__info-title">{{ title }}</text>
+				</slot>
+				<slot name="desc" :desc="desc">
+					<text class="up-coupon__info-desc" v-if="desc">{{ desc }}</text>
+				</slot>
+				<slot name="time" :time="time">
+					<text class="up-coupon__info-time" v-if="time">{{ time }}</text>
+				</slot>
+			</view>
+			
+			<!-- 右侧操作区域 -->
+			<view class="up-coupon__action u-padding-right-20">
+				<slot name="action" :actionText="actionText" :circle="circle">
+                    <up-tag type="error" :bgColor="type ? 'transparent' : '#eb433d'"
+                        :borderColor="type ? '#eee' : '#eb433d'" borderRadius="6px"
+                        size="medium" class="up-coupon__action-text"
+                        :shape="circle ? 'circle': 'circle'">{{ actionText }}</up-tag>
+				</slot>
+			</view>
+		</view>
+		
+		<!-- 红包绳子效果 -->
+		<view v-if="shape === 'envelope'" class="up-coupon__rope"></view>
+		
+		<!-- 默认插槽,可用于添加额外内容 -->
+		<slot></slot>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'up-coupon',
+		props: {
+			// 金额
+			amount: {
+				type: [String, Number],
+				default: ''
+			},
+			// 金额单位
+			unit: {
+				type: String,
+				default: '¥'
+			},
+			// 单位位置
+			unitPosition: {
+				type: String,
+				default: 'left'
+			},
+			// 使用限制
+			limit: {
+				type: String,
+				default: ''
+			},
+			// 标题
+			title: {
+				type: String,
+				default: '优惠券'
+			},
+			// 描述
+			desc: {
+				type: String,
+				default: ''
+			},
+			// 有效期
+			time: {
+				type: String,
+				default: ''
+			},
+			// 操作按钮文字
+			actionText: {
+				type: String,
+				default: '使用'
+			},
+			// 形状:coupon-优惠券, envelope-红包, card-卡片
+			shape: {
+				type: String,
+				default: 'coupon'
+			},
+			// 尺寸:small, medium, large
+			size: {
+				type: String,
+				default: 'medium'
+			},
+			// 是否圆形按钮
+			circle: {
+				type: Boolean,
+				default: false
+			},
+			// 是否禁用
+			disabled: {
+				type: Boolean,
+				default: false
+			},
+			// 背景颜色
+			bgColor: {
+				type: String,
+				default: ''
+			},
+			// 文字颜色
+			color: {
+				type: String,
+				default: ''
+			},
+            // 内置背景类型
+            type: {
+				type: String,
+				default: ''
+			},
+		},
+		computed: {
+			couponStyle() {
+				const style = {};
+				if (this.bgColor) style.background = this.bgColor;
+				if (this.color) style.color = this.color;
+				return style;
+			},
+			dotCount() {
+				// 根据尺寸计算锯齿数量
+				const map = {
+					small: 8,
+					medium: 10,
+					large: 12
+				};
+				return map[this.size] || 10;
+			}
+		},
+		methods: {
+			handleClick() {
+				if (this.disabled) return;
+				this.$emit('click');
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.up-coupon {
+		position: relative;
+		overflow: hidden;
+		border-radius: 8rpx;
+		background: #ffebf0;
+		color: $u-main-color;
+		
+		&--coupon {
+			border-radius: 16rpx;
+			overflow: hidden;
+			
+			&::before {
+				content: '';
+				position: absolute;
+				left: -24rpx;
+				top: 50%;
+				transform: translateY(-50%);
+				width: 48rpx;
+				height: 48rpx;
+				background-color: #fff;
+				border-radius: 50%;
+			}
+			
+			&::after {
+				content: '';
+				position: absolute;
+				right: -24rpx;
+				top: 50%;
+				transform: translateY(-50%);
+				width: 48rpx;
+				height: 48rpx;
+				background-color: #fff;
+				border-radius: 50%;
+			}
+		}
+		
+		&--envelope {
+			border-radius: 16rpx;
+			
+			&::before {
+				content: '';
+				position: absolute;
+				left: 0;
+				top: 0;
+				right: 0;
+				height: 20rpx;
+				background: repeating-linear-gradient(-45deg, #ffd000, #ffd000 10rpx, #ffa000 10rpx, #ffa000 20rpx);
+			}
+		}
+		
+		&--card {
+			border-radius: 16rpx;
+		}
+
+        width: 100%;
+		
+		&--small {
+			// width: 520rpx;
+			height: 160rpx;
+		}
+		
+		&--medium {
+			// width: 600rpx;
+			height: 180rpx;
+		}
+		
+		&--large {
+			// width: 700rpx;
+			height: 220rpx;
+		}
+		
+		&--disabled {
+			opacity: 0.5;
+		}
+		
+		&__content {
+			display: flex;
+            flex-direction: row;
+			align-items: center;
+			justify-content: space-between;
+			height: 100%;
+			padding: 0 30rpx;
+			position: relative;
+			z-index: 2;
+		}
+		
+		&__amount {
+			display: flex;
+			flex-direction: column;
+			align-items: flex-start;
+            padding-left: 10rpx;
+			padding-right: 30rpx;
+            border-right: 1px dashed #ccc;
+			
+			&-unit {
+				font-size: 24rpx;
+				font-weight: normal;
+			}
+			
+			&-value {
+				font-size: 56rpx;
+				font-weight: bold;
+                color: red;
+				line-height: 1;
+				margin: 10rpx 0;
+			}
+			
+			&-limit {
+				font-size: 24rpx;
+				opacity: 0.9;
+			}
+		}
+		
+		&__info {
+			flex: 1;
+			display: flex;
+			flex-direction: column;
+			align-items: flex-start;
+            padding-left: 30rpx;
+			
+			&-title {
+				font-size: 32rpx;
+				font-weight: bold;
+				margin-bottom: 10rpx;
+			}
+			
+			&-desc {
+				font-size: 24rpx;
+				opacity: 0.9;
+				margin-bottom: 10rpx;
+			}
+			
+			&-time {
+				font-size: 20rpx;
+				opacity: 0.8;
+			}
+		}
+		
+		&__action {
+			display: flex;
+            flex-direction: row;
+			align-items: center;
+			justify-content: center;
+		}
+		
+		&__dots {
+			position: absolute;
+			left: 0;
+			top: 0;
+			bottom: 0;
+			width: 100%;
+			display: flex;
+			flex-direction: column;
+			justify-content: space-between;
+			padding: 30rpx 0;
+			z-index: 1;
+		}
+		
+		&__dot {
+			width: 32rpx;
+			height: 32rpx;
+			background-color: #fff;
+			border-radius: 50%;
+			margin: 0 -16rpx;
+			z-index: 3;
+		}
+		
+		&__rope {
+			position: absolute;
+			top: -40rpx;
+			left: 50%;
+			transform: translateX(-50%);
+			width: 80rpx;
+			height: 80rpx;
+			background: linear-gradient(to right, #ffd000, #ffa000);
+			border-radius: 40rpx 40rpx 0 0;
+			z-index: 1;
+			
+			&::before {
+				content: '';
+				position: absolute;
+				top: 0;
+				left: -20rpx;
+				width: 20rpx;
+				height: 40rpx;
+				background: linear-gradient(to bottom, #ffd000, #ffa000);
+				border-radius: 10rpx 0 0 10rpx;
+			}
+			
+			&::after {
+				content: '';
+				position: absolute;
+				top: 0;
+				right: -20rpx;
+				width: 20rpx;
+				height: 40rpx;
+				background: linear-gradient(to bottom, #ffd000, #ffa000);
+				border-radius: 0 10rpx 10rpx 0;
+			}
+		}
+		
+		// 不同主题样式
+		&--primary {
+			background: linear-gradient(90deg, #43afff, #3b8cff);
+            color: #fff;
+            .up-coupon__amount {
+                border-right: 1px dashed #eee;
+            }
+            .up-coupon__amount-value {
+                color: #fff;
+            }
+		}
+		
+		&--success {
+			background: linear-gradient(90deg, #67dda9, #19be6b);
+            color: #fff !important;
+            .up-coupon__amount {
+                border-right: 1px dashed #eee;
+            }
+            .up-coupon__amount-value {
+                color: #fff;
+            }
+		}
+		
+		&--warning {
+			background: linear-gradient(90deg, #ff9739, #ff6a39);
+            color: #fff;
+            .up-coupon__amount {
+                border-right: 1px dashed #eee;
+            }
+            .up-coupon__amount-value {
+                color: #fff;
+            }
+		}
+		
+		&--error {
+			background: linear-gradient(90deg, #ff7070, #ff4747);
+            color: #fff;
+            .up-coupon__amount {
+                border-right: 1px dashed #eee;
+            }
+            .up-coupon__amount-value {
+                color: #fff;
+            }
+		}
+	}
+</style>

+ 23 - 8
node_modules/uview-plus/components/u-cropper/u-cropper.vue

@@ -11,16 +11,29 @@
 		<view class="oper-wrapper" :style="{display: styleDisplay}">
 			<view class="oper">
 				<view class="btn-wrapper" v-if="showOper">
-					<view @click="select"  hover-class="hover" :style="{width: btnWidth}"><text>重选</text></view>
-					<view @click="close"  hover-class="hover" :style="{width: btnWidth}"><text>关闭</text></view>
-					<view @click="rotate"  hover-class="hover" :style="{width: btnWidth, display: btnDsp}"><text>旋转</text></view>
-					<view @click="preview" hover-class="hover" :style="{width: btnWidth}"><text>预览</text></view>
-					<view @click="confirm"  hover-class="hover" :style="{width: btnWidth}"><text>确认</text></view>
+					<view @click="select"  hover-class="hover" :style="{width: btnWidth}">
+						<text>{{ t("up.common.re-select") }}</text>
+					</view>
+					<view @click="close"  hover-class="hover" :style="{width: btnWidth}">
+						<text>{{ t("up.common.close") }}</text>
+					</view>
+					<view @click="rotate"  hover-class="hover" :style="{width: btnWidth, display: btnDsp}">
+						<text>{{ t("up.common.rotate") }}</text>
+					</view>
+					<view @click="preview" hover-class="hover" :style="{width: btnWidth}">
+						<text>{{ t("up.common.preview") }}</text>
+					</view>
+					<view @click="confirm"  hover-class="hover" :style="{width: btnWidth}">
+						<text>{{ t("up.common.confirm") }}</text>
+					</view>
 				</view>
 				<view class="clr-wrapper" v-else>
 					<slider class="my-slider" @change="colorChange"
-					block-size="25" value="0" min="-100" max="100" activeColor="red" backgroundColor="green" block-color="grey" show-value></slider>
-					<view @click="prvUpload"  hover-class="hover" :style="{width: btnWidth}"><text>确认</text></view>
+					block-size="25" value="0" min="-100" max="100" activeColor="red"
+					backgroundColor="green" block-color="grey" show-value></slider>
+					<view @click="prvUpload"  hover-class="hover" :style="{width: btnWidth}">
+						<text>{{ t("up.common.confirm") }}</text>
+					</view>
 				</view>
 			</view>
 		</view>
@@ -33,6 +46,7 @@
 </template>
 
 <script>
+	import { t } from '../../libs/i18n'
 	const tabHeight = 50;
 	export default {
 		name: "u-cropper",
@@ -131,6 +145,7 @@
 			}
 		},
 		methods: {
+			t,
 			windowResize() {
 				let sysInfo = uni.getSystemInfoSync();
 				this.platform = sysInfo.platform;
@@ -215,7 +230,7 @@
 										style.left = (this.windowWidth - areaWidth)/2 + 'px';
 									} else {
 										uni.showModal({
-											title: '裁剪框的宽或高没有设置',
+											title: t("up.cropper.emptyWidhtOrHeight"),
 											showCancel: false
 										})
 										return;

+ 5 - 3
node_modules/uview-plus/components/u-datetime-picker/datetimePicker.js

@@ -7,6 +7,7 @@
  * @lastTime     : 2021-08-20 16:57:48
  * @FilePath     : /u-view2.0/uview-ui/libs/config/props/datetimePicker.js
  */
+import { t } from '../../libs/i18n'
 export default {
     // datetimePicker 组件
     datetimePicker: {
@@ -26,8 +27,8 @@ export default {
         formatter: null,
         loading: false,
         itemHeight: 44,
-        cancelText: '取消',
-        confirmText: '确认',
+        cancelText: t("up.common.cancel"),
+        confirmText: t("up.common.confirm"),
         cancelColor: '#909193',
         confirmColor: '#3c9cff',
         visibleItemCount: 5,
@@ -36,7 +37,8 @@ export default {
         inputBorder: 'surround',
         disabled: false,
         disabledColor: '',
-        placeholder: '请选择',
+        placeholder: t("up.common.pleaseChoose"),
         inputProps: {},
+        pageInline: false
     }
 }

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
node_modules/uview-plus/components/u-datetime-picker/dayjs.esm.min.js


+ 5 - 0
node_modules/uview-plus/components/u-datetime-picker/props.js

@@ -163,6 +163,11 @@ export const props = defineMixin({
         defaultIndex: {
             type: Array,
             default: () => defProps.datetimePicker.defaultIndex
+        },
+        // 是否页面内展示
+        pageInline:{
+            type: Boolean,
+            default: () => defProps.datetimePicker.pageInline
         }
     }
 })

+ 4 - 3
node_modules/uview-plus/components/u-datetime-picker/u-datetime-picker.vue

@@ -9,13 +9,13 @@
 					v-model="inputValue"
 					v-bind="inputPropsInner"
 				></up-input>
-				<div class="input-cover">
-				</div>
+				<view class="input-cover">
+				</view>
 			</slot>
         </view>
         <u-picker
             ref="picker"
-            :show="show || (hasInput && showByClickInput)"
+            :show="pageInline || show || (hasInput && showByClickInput)"
             :popupMode="popupMode"
             :closeOnClickOverlay="closeOnClickOverlay"
             :columns="columns"
@@ -29,6 +29,7 @@
             :cancelColor="cancelColor"
             :confirmColor="confirmColor"
             :toolbarRightSlot="toolbarRightSlot"
+			:pageInline="pageInline"
             @close="close"
             @cancel="cancel"
             @confirm="confirm"

+ 16 - 15
node_modules/uview-plus/components/u-empty/u-empty.vue

@@ -35,6 +35,7 @@
 	import { mpMixin } from '../../libs/mixin/mpMixin';
 	import { mixin } from '../../libs/mixin/mixin';
 	import { addUnit, addStyle, deepMerge } from '../../libs/function/index';
+	import { t } from '../../libs/i18n'
 	/**
 	 * empty 内容为空
 	 * @description 该组件用于需要加载内容,但是加载的第一页数据就为空,提示一个"没有内容"的场景, 我们精心挑选了十几个场景的图标,方便您使用。
@@ -62,21 +63,21 @@
 		data() {
 			return {
 				icons: {
-					car: '购物车为空',
-					page: '页面不存在',
-					search: '没有搜索结果',
-					address: '没有收货地址',
-					wifi: '没有WiFi',
-					order: '订单为空',
-					coupon: '没有优惠券',
-					favor: '暂无收藏',
-					permission: '无权限',
-					history: '无历史记录',
-					news: '无新闻列表',
-					message: '消息列表为空',
-					list: '列表为空',
-					data: '数据为空',
-					comment: '暂无评论',
+					car: t("up.empty.car"),
+					page: t("up.empty.page"),
+					search: t("up.empty.search"),
+					address: t("up.empty.address"),
+					wifi: t("up.empty.wifi"),
+					order: t("up.empty.order"),
+					coupon: t("up.empty.coupon"),
+					favor: t("up.empty.favor"),
+					permission: t("up.empty.permission"),
+					history: t("up.empty.history"),
+					news: t("up.empty.news"),
+					message: t("up.empty.message"),
+					list: t("up.empty.list"),
+					data: t("up.empty.data"),
+					comment: t("up.empty.comment"),
 				}
 			}
 		},

+ 432 - 0
node_modules/uview-plus/components/u-goods-sku/u-goods-sku.vue

@@ -0,0 +1,432 @@
+<template>
+    <view class="up-goods-sku"> 
+        <view @click="open">
+            <slot name="trigger"></slot>
+        </view>
+        <up-popup
+            v-model:show="show"
+            mode="bottom"
+            :closeable="pageInline ? false : closeable"
+            :pageInline="pageInline"
+            :border-radius="20"
+            @close="close"
+        >
+            <view class="up-goods-sku-container" :style="{padding: pageInline ? '0px' : ''}">
+                <view class="up-goods-sku__header">
+                    <slot name="header">
+                        <view class="up-goods-sku__header__image">
+                            <image :src="goodsInfo.image || goodsInfo.picture" mode="aspectFill"></image>
+                        </view>
+                        <view class="up-goods-sku__header__info">
+                            <view class="up-goods-sku__header__info__price">
+                                <text class="up-goods-sku__header__info__price__symbol">¥</text>
+                                <text class="up-goods-sku__header__info__price__value">{{ price }}</text>
+                            </view>
+                            <view class="up-goods-sku__header__info__stock">库存 {{ stock }} 件</view>
+                            <view class="up-goods-sku__header__info__selected">已选: {{ selectedSkuText }}</view>
+                        </view>
+                    </slot>
+                </view>
+                
+                <scroll-view class="up-goods-sku__content" scroll-y>
+                    <view v-for="(treeItem, index) in skuTree" :key="index" class="up-goods-sku__content__item">
+                        <view class="up-goods-sku__content__item__title">{{ treeItem.label }}</view>
+                        <view class="up-goods-sku__content__item__list">
+                            <view 
+                                v-for="(leafItem, leafIndex) in treeItem.children" 
+                                :key="leafIndex"
+                                class="up-goods-sku__content__item__list__item"
+                                :class="{
+                                    'up-goods-sku__content__item__list__item--active': isSelected(treeItem.name, leafItem.id),
+                                    'up-goods-sku__content__item__list__item--disabled': isDisabled(treeItem.name, leafItem.id)
+                                }"
+                                @click="onSkuClick(treeItem.name, leafItem)"
+                            >
+                                <text>{{ leafItem.name }}</text>
+                            </view>
+                        </view>
+                    </view>
+                    
+                    <view class="up-goods-sku__content__count">
+                        <view class="up-goods-sku__content__count__title">购买数量</view>
+                        <view class="up-goods-sku__content__count__control">
+                            <up-number-box 
+                                v-model="buyNum" 
+                                :min="1" 
+                                :max="maxBuyNum"
+                                :disabled="!canBuy"
+                                @change="onNumChange"
+                            ></up-number-box>
+                        </view>
+                    </view>
+                </scroll-view>
+                
+                <view class="up-goods-sku__footer">
+                    <up-button 
+                        type="primary" 
+                        :disabled="!canBuy" 
+                        @click="onConfirm"
+                    >
+                        {{ confirmText }}
+                    </up-button>
+                </view>
+            </view>
+        </up-popup>
+    </view>
+</template>
+
+<script>
+	export default {
+		name: 'up-goods-sku',
+		props: {
+			// 商品信息
+			goodsInfo: {
+				type: Object,
+				default: () => ({})
+			},
+			// SKU树形结构
+			skuTree: {
+				type: Array,
+				default: () => []
+			},
+			// SKU列表
+			skuList: {
+				type: Array,
+				default: () => []
+			},
+			// 最大购买数量
+			maxBuy: {
+				type: Number,
+				default: 999
+			},
+			// 确认按钮文字
+			confirmText: {
+				type: String,
+				default: '确定'
+			},
+            // 是否显示关闭弹窗按钮
+            closeable: {
+				type: Boolean,
+				default: true
+			},
+            // 是否页面内联模式
+            pageInline: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+                show: false,
+				// 已选择的SKU
+				selectedSku: {},
+				// 购买数量
+				buyNum: 1
+			}
+		},
+		computed: {
+			// 当前价格
+			price() {
+				const selectedSkuComb = this.getSelectedSkuComb()
+				if (selectedSkuComb) {
+					return selectedSkuComb.price || selectedSkuComb.price_fee
+				}
+				return this.goodsInfo.price || this.goodsInfo.price_fee || 0
+			},
+			// 当前库存
+			stock() {
+				const selectedSkuComb = this.getSelectedSkuComb()
+				if (selectedSkuComb) {
+					return selectedSkuComb.stock || selectedSkuComb.quantity
+				}
+				return this.goodsInfo.stock || this.goodsInfo.quantity || 0
+			},
+			// 最大购买数量
+			maxBuyNum() {
+				const stock = this.stock
+				return stock > this.maxBuy ? this.maxBuy : stock
+			},
+			// 是否可以购买
+			canBuy() {
+				const selectedSkuCount = Object.keys(this.selectedSku).length
+				const skuTreeCount = this.skuTree.length
+				return selectedSkuCount === skuTreeCount && this.buyNum > 0 && this.stock > 0
+			},
+			// 已选SKU文字描述
+			selectedSkuText() {
+				const selected = []
+				Object.keys(this.selectedSku).forEach(key => {
+					const value = this.selectedSku[key]
+					if (value) {
+						this.skuTree.forEach(treeItem => {
+							if (treeItem.name === key) {
+								treeItem.children.forEach(leafItem => {
+									if (leafItem.id === value) {
+										selected.push(leafItem.name)
+									}
+								})
+							}
+						})
+					}
+				})
+				return selected.join(', ')
+			}
+		},
+		watch: {
+		},
+        emits: ['open', 'confirm', 'close'],
+        created() {
+            if (this.pageInline) {
+                this.show = true;
+            }
+		},
+		methods: {
+			// 判断SKU是否被选中
+			isSelected(skuKey, skuValueId) {
+				return this.selectedSku[skuKey] === skuValueId
+			},
+			// 判断SKU是否禁用
+			isDisabled(skuKey, skuValueId) {
+				// 构造一个临时的已选中SKU对象
+				const tempSelected = { ...this.selectedSku, [skuKey]: skuValueId }
+				
+				// 检查是否还有未选择的SKU维度
+				const selectedCount = Object.keys(tempSelected).filter(key => tempSelected[key]).length
+				const totalSkuCount = this.skuTree.length
+				
+				// 如果所有SKU都已选择,则检查组合是否存在
+				if (selectedCount === totalSkuCount) {
+					return !this.getSkuComb(tempSelected)
+				}
+				
+				// 检查当前选择的SKU是否会导致无法组成有效组合
+				for (let i = 0; i < this.skuList.length; i++) {
+					const sku = this.skuList[i]
+					let match = true
+					
+					// 检查已选中的SKU是否匹配
+					for (const key in tempSelected) {
+						if (tempSelected[key] && sku[key] !== tempSelected[key]) {
+							match = false
+							break
+						}
+					}
+					
+					if (match) {
+						return false
+					}
+				}
+				
+				return true
+			},
+			// SKU点击事件
+			onSkuClick(skuKey, skuValue) {
+				// 如果是禁用状态,直接返回
+				if (this.isDisabled(skuKey, skuValue.id)) {
+					return
+				}
+				
+				// 如果已选中,则取消选中
+				if (this.selectedSku[skuKey] === skuValue.id) {
+					this.$set(this.selectedSku, skuKey, '')
+				} else {
+					this.$set(this.selectedSku, skuKey, skuValue.id)
+				}
+			},
+			// 数量改变事件
+			onNumChange(e) {
+				this.buyNum = e.value
+			},
+			// 获取选中的SKU组合
+			getSelectedSkuComb() {
+				return this.getSkuComb(this.selectedSku)
+			},
+			// 根据已选SKU获取组合信息
+			getSkuComb(selectedSku) {
+				const selected = { ...selectedSku }
+				
+				// 过滤掉空值
+				Object.keys(selected).forEach(key => {
+					if (!selected[key]) {
+						delete selected[key]
+					}
+				})
+				
+				// 检查是否所有SKU都已选择
+				if (Object.keys(selected).length !== this.skuTree.length) {
+					return null
+				}
+				
+				// 查找匹配的SKU组合
+				for (let i = 0; i < this.skuList.length; i++) {
+					const sku = this.skuList[i]
+					let match = true
+					
+					for (const key in selected) {
+						if (sku[key] !== selected[key]) {
+							match = false
+							break
+						}
+					}
+					
+					if (match) {
+						return sku
+					}
+				}
+				
+				return null
+			},
+			// 重置选择
+			reset() {
+				this.selectedSku = {}
+				this.buyNum = 1
+			},
+            open() {
+                this.show = true;
+                this.$emit('open')
+            },
+			// 关闭弹窗
+			close() {
+                this.false = true;
+				this.$emit('close')
+			},
+			// 确认选择
+			onConfirm() {
+				if (!this.canBuy) {
+					return
+				}
+				
+				const selectedSkuComb = this.getSelectedSkuComb()
+				this.$emit('confirm', {
+					sku: selectedSkuComb,
+					goodsInfo: this.goodsInfo,
+					num: this.buyNum,
+					selectedText: this.selectedSkuText
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.up-goods-sku {
+		background-color: #fff;
+		overflow: hidden;
+
+        .up-goods-sku-container {
+            padding: 4rpx 30rpx;
+        }
+		
+		&__header {
+			display: flex;
+            flex-direction: row;
+			padding: 30rpx 0;
+			position: relative;
+			
+			&__image {
+				width: 180rpx;
+				height: 180rpx;
+				border-radius: 10rpx;
+				overflow: hidden;
+				margin-right: 20rpx;
+				
+				image {
+					width: 100%;
+					height: 100%;
+				}
+			}
+			
+			&__info {
+				flex: 1;
+				
+				&__price {
+					display: flex;
+                    flex-direction: row;
+					align-items: baseline;
+					margin-bottom: 20rpx;
+					
+					&__symbol {
+						font-size: 24rpx;
+						color: #fa3534;
+						margin-right: 4rpx;
+					}
+					
+					&__value {
+						font-size: 36rpx;
+						color: #fa3534;
+						font-weight: bold;
+					}
+				}
+				
+				&__stock {
+					font-size: 26rpx;
+					color: #999;
+					margin-bottom: 20rpx;
+				}
+				
+				&__selected {
+					font-size: 26rpx;
+					color: #333;
+				}
+			}
+		}
+		
+		&__content {
+			max-height: 600rpx;
+			padding: 0 30rpx 30rpx 0;
+			
+			&__item {
+				margin-bottom: 30rpx;
+				
+				&__title {
+					font-size: 28rpx;
+					color: #333;
+					margin-bottom: 20rpx;
+				}
+				
+				&__list {
+					display: flex;
+                    flex-direction: row;
+					flex-wrap: wrap;
+					
+					&__item {
+						padding: 10rpx 20rpx;
+						border: 2rpx solid #eee;
+						border-radius: 10rpx;
+						margin-right: 20rpx;
+						margin-bottom: 20rpx;
+						font-size: 26rpx;
+						color: #333;
+						
+						&--active {
+							border-color: #fa3534;
+							color: #fa3534;
+						}
+						
+						&--disabled {
+							color: #ccc;
+							border-color: #eee;
+						}
+					}
+				}
+			}
+			
+			&__count {
+				display: flex;
+                flex-direction: row;
+				align-items: center;
+				justify-content: space-between;
+				margin-top: 20rpx;
+				
+				&__title {
+					font-size: 28rpx;
+					color: #333;
+				}
+			}
+		}
+		
+		&__footer {
+			padding: 20rpx 0rpx 40rpx 0;
+		}
+	}
+</style>

+ 5 - 4
node_modules/uview-plus/components/u-input/input.js

@@ -3,9 +3,9 @@
  * @Description  :
  * @version      : 1.0
  * @Date         : 2021-08-20 16:44:21
- * @LastAuthor   : LQ
- * @lastTime     : 2021-08-20 17:13:55
- * @FilePath     : /u-view2.0/uview-ui/libs/config/props/input.js
+ * @LastAuthor   : jry
+ * @lastTime     : 2025-08-20 10:21:55
+ * @FilePath     : /uview-plus/libs/config/props/input.js
  */
 export default {
 	// index 组件
@@ -44,6 +44,7 @@ export default {
 		readonly: false,
 		shape: 'square',
 		formatter: null,
-		cursorColor: ''
+		cursorColor: '',
+		passwordVisibilityToggle: true
 	}
 }

+ 11 - 1
node_modules/uview-plus/components/u-input/props.js

@@ -44,7 +44,12 @@ export const props = defineMixin({
 		// 是否显示清除控件
 		clearable: {
 			type: Boolean,
-			default: () => defProps.input.clearable
+			default: false
+		},
+		// 是否仅在聚焦时显示清除控件
+		onlyClearableOnFocused: {
+			type: Boolean,
+			default: true
 		},
 		// 是否密码类型
 		password: {
@@ -199,5 +204,10 @@ export const props = defineMixin({
 			type: String,
 			default: () => defProps.input.cursorColor
 		},
+		// 密码类型可见性切换
+		passwordVisibilityToggle: {
+			type: Boolean,
+			default: () => defProps.input.passwordVisibilityToggle
+		}
 	}
 })

+ 37 - 5
node_modules/uview-plus/components/u-input/u-input.vue

@@ -21,7 +21,7 @@
                     ref="input-native"
             	    class="u-input__content__field-wrapper__field"
             	    :style="[inputStyle]"
-            	    :type="type"
+            	    :type="showPassword && 'password' == type ? 'text' : type"
             	    :focus="focus"
             	    :cursor="cursor"
             	    :value="innerValue"
@@ -39,7 +39,7 @@
             	    :adjust-position="adjustPosition"
             	    :selection-end="selectionEnd"
             	    :selection-start="selectionStart"
-            	    :password="password || type === 'password' || false"
+            	    :password="isPassword"
                     :ignoreCompositionEvent="ignoreCompositionEvent"
             	    @input="onInput"
             	    @blur="onBlur"
@@ -61,6 +61,15 @@
                     customStyle="line-height: 12px"
                 ></up-icon>
             </view>
+            <view
+                class="u-input__content__subfix-password-shower"
+                v-if="(type == 'password' || password) && passwordVisibilityToggle"
+            >
+                <up-icon @click="showPassword = !showPassword"
+                    :name="showPassword ? 'eye-off' : 'eye-fill'"
+                    size="18"
+                ></up-icon>
+            </view>
             <view
                 class="u-input__content__subfix-icon"
                 v-if="suffixIcon || $slots.suffix"
@@ -142,7 +151,8 @@ export default {
             // value绑定值的变化是由内部还是外部引起的
             changeFromInner: false,
 			// 过滤处理方法
-			innerFormatter: value => value
+			innerFormatter: value => value,
+            showPassword: false
         };
     },
     created() {
@@ -178,10 +188,32 @@ export default {
         }
     },
     computed: {
+        // 是否密码
+        isPassword() {
+            let ret = false;
+            if(this.password) {
+                ret = true;
+            } else if (this.type == 'password') {
+                ret = true;
+            } else {
+                ret = false;
+            }
+            if (this.showPassword) {
+                ret = false;
+            }
+            return ret;
+        },
         // 是否显示清除控件
         isShowClear() {
-            const { clearable, readonly, focused, innerValue } = this;
-            return !!clearable && !readonly && !!focused && innerValue !== "";
+            const { clearable, readonly, focused, innerValue, onlyClearableOnFocused } = this;
+            if (!clearable || readonly) {
+                return false;
+            }
+            if (onlyClearableOnFocused) {
+                return !!focused && innerValue !== "";
+            } else {
+                return innerValue !== "";
+            }
         },
         // 组件的类名
         inputClass() {

+ 3 - 2
node_modules/uview-plus/components/u-keyboard/keyboard.js

@@ -7,6 +7,7 @@
  * @lastTime     : 2021-08-20 17:07:49
  * @FilePath     : /u-view2.0/uview-ui/libs/config/props/keyboard.js
  */
+import { t } from '../../libs/i18n'
 export default {
     // 键盘组件
     keyboard: {
@@ -23,8 +24,8 @@ export default {
         show: false,
         overlay: true,
         zIndex: 10075,
-        cancelText: '取消',
-        confirmText: '确定',
+        cancelText: t("up.common.cancel"),
+        confirmText: t("up.common.confirm"),
         autoChange: false
     }
 }

+ 2 - 1
node_modules/uview-plus/components/u-link/link.js

@@ -8,6 +8,7 @@
  * @FilePath     : /u-view2.0/uview-ui/libs/config/props/link.js
  */
 import config from '../../libs/config/config'
+import { t } from '../../libs/i18n'
 
 const {
     color
@@ -19,7 +20,7 @@ export default {
         fontSize: 15,
         underLine: false,
         href: '',
-        mpTips: '链接已复制,请在浏览器打开',
+        mpTips: t("up.link.copyed"),
         lineColor: '',
         text: ''
     }

+ 2 - 1
node_modules/uview-plus/components/u-loading-page/loadingPage.js

@@ -7,10 +7,11 @@
  * @lastTime     : 2021-08-20 17:00:23
  * @FilePath     : /u-view2.0/uview-ui/libs/config/props/loadingPage.js
  */
+import { t } from '../../libs/i18n'
 export default {
     // loading-page组件
     loadingPage: {
-        loadingText: '正在加载',
+        loadingText: t("up.common.loading2"),
         image: '',
         loadingMode: 'circle',
         loading: false,

+ 4 - 3
node_modules/uview-plus/components/u-loadmore/loadmore.js

@@ -7,6 +7,7 @@
  * @lastTime     : 2021-08-20 17:15:26
  * @FilePath     : /u-view2.0/uview-ui/libs/config/props/loadmore.js
  */
+import { t } from '../../libs/i18n'
 export default {
     // loadmore 组件
     loadmore: {
@@ -17,9 +18,9 @@ export default {
 		iconSize: 17,
         color: '#606266',
         loadingIcon: 'spinner',
-        loadmoreText: '加载更多',
-        loadingText: '正在加载...',
-        nomoreText: '没有更多了',
+        loadmoreText: t("up.loadmoe.loadmore"),
+        loadingText: t("up.common.loading2") + '...',
+        nomoreText: t("up.loadmoe.nomore"),
         isDot: false,
         iconColor: '#b7b7b7',
         marginTop: 10,

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
node_modules/uview-plus/components/u-markdown/marked.esm.js


+ 4 - 3
node_modules/uview-plus/components/u-modal/modal.js

@@ -7,14 +7,15 @@
  * @lastTime     : 2021-08-20 17:15:59
  * @FilePath     : /u-view2.0/uview-ui/libs/config/props/modal.js
  */
+import { t } from '../../libs/i18n'
 export default {
     // modal 组件
     modal: {
         show: false,
         title: '',
         content: '',
-        confirmText: '确认',
-        cancelText: '取消',
+        confirmText: t("up.common.confirm"),
+        cancelText: t("up.common.cancel"),
         showConfirmButton: true,
         showCancelButton: false,
         confirmColor: '#2979ff',
@@ -28,7 +29,7 @@ export default {
         confirmButtonShape: '',
         duration: 400,
         contentTextAlign: 'left',
-        asyncCloseTip: '操作中...',
+        asyncCloseTip: t("up.common.inOperatio") + '...',
         asyncCancelClose: false,
         contentStyle: {}
     }

Dosya farkı çok büyük olduğundan ihmal edildi
+ 2 - 1
node_modules/uview-plus/components/u-no-network/noNetwork.js


+ 7 - 5
node_modules/uview-plus/components/u-no-network/u-no-network.vue

@@ -22,17 +22,17 @@
 			<!-- 只有APP平台,才能跳转设置页,因为需要调用plus环境 -->
 			<!-- #ifdef APP-PLUS -->
 			<view class="u-no-network__app">
-				<text class="u-no-network__app__setting">请检查网络,或前往</text>
+				<text class="u-no-network__app__setting">{{ t("up.noNetwork.pleaseCheck") }}</text>
 				<text
 				    class="u-no-network__app__to-setting"
 				    @tap="openSettings"
-				>设置</text>
+				>{{ t("up.common.settings") }}</text>
 			</view>
 			<!-- #endif -->
 			<view class="u-no-network__retry">
 				<u-button
 				    size="mini"
-				    text="重试"
+				    :text="t('up.common.retry')"
 				    type="primary"
 					plain
 				    @click="retry"
@@ -47,6 +47,7 @@
 	import { mpMixin } from '../../libs/mixin/mpMixin';
 	import { mixin } from '../../libs/mixin/mixin';
 	import { toast, getDeviceInfo } from '../../libs/function/index';
+	import { t } from '../../libs/i18n'
 	/**
 	 * noNetwork 无网络提示
 	 * @description 该组件无需任何配置,引入即可,内部自动处理所有功能和事件。
@@ -87,6 +88,7 @@
 		},
 		emits: ["disconnected", "connected"],
 		methods: {
+			t,
 			retry() {
 				// 重新检查网络
 				uni.getNetworkType({
@@ -94,10 +96,10 @@
 						this.networkType = res.networkType
 						this.emitEvent(this.networkType)
 						if (res.networkType == 'none') {
-							toast('无网络连接')
+							toast(t("up.noNetwork.disconnect"))
 							this.isConnected = false
 						} else {
-							toast('网络已连接')
+							toast(t("up.noNetwork.connect"))
 							this.isConnected = true
 						}
 					}

+ 3 - 1
node_modules/uview-plus/components/u-pagination/u-pagination.vue

@@ -56,7 +56,7 @@
       @click="next"
     >
       <template v-if="nextText">
-        下一页
+        {{ nextText }}
       </template>
       <up-icon v-else name="arrow-right"></up-icon>
     </view>
@@ -77,6 +77,7 @@
 </template>
 
 <script>
+import { t } from '../../libs/i18n'
 export default {
   name: 'u-pagination',
   props: {
@@ -194,6 +195,7 @@ export default {
     }
   },
   methods: {
+    t,
     handleSizeChange(e) {
       const selected = e.detail.value;
       const size = this.pageSizes[selected]?.value || this.pageSizes[0].value;

+ 19 - 0
node_modules/uview-plus/components/u-pdf-reader/props.js

@@ -0,0 +1,19 @@
+export default {
+    props: {
+        // PDF文件地址
+        src: {
+            type: String,
+            default: ''
+        },
+        // 组件高度
+        height: {
+            type: String,
+            default: '500px'
+        },
+        // pdfjs资源域名
+        baseUrl: {
+            type: String,
+            default: 'https://uview-plus.jiangruyi.com/h5'
+        }
+    }
+}

+ 52 - 0
node_modules/uview-plus/components/u-pdf-reader/u-pdf-reader.vue

@@ -0,0 +1,52 @@
+<template>
+	<view class="up-pdf-reader" :style="{ height: height }">
+		<web-view :fullscreen="false"
+			:src="viewerUrl" :style="{ width: '750rpx', height: height }"
+            :webview-styles="{ width: '750rpx', height: height }"
+			frameborder="0"
+		></web-view>
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+
+	/**
+	 * pdfReader PDF阅读器
+	 * @description 基于pdf.js的PDF阅读器组件
+	 * @tutorial https://uview-plus.jiangruyi.com/components/pdfReader.html
+	 * @property {String}			src				PDF文件地址
+	 * @property {String}	        height			组件高度,默认为'700px'
+	 * @property {String}			pdfjsDomain		pdfjs资源域名,默认为'https://uview-plus.jiangruyi.com/h5'
+	 * @example <up-pdf-reader src="https://example.com/file.pdf"></up-pdf-reader>
+	 */
+	export default {
+		name: 'up-pdf-reader',
+		mixins: [props],
+        data() {
+            return {
+                baseUrlInner: 'https://uview-plus.jiangruyi.com/h5',
+                viewerUrl: ''
+            }
+        },
+        watch: {
+            baseUrl: function (val) {
+                this.baseUrl = val;
+            },
+            src: function (val) {
+                this.viewerUrl = `${this.baseUrlInner}/static/pdfjs/web/viewer.html?file=` + encodeURIComponent(val);
+            }
+        },
+        mounted() {
+            if (this.baseUrl) {
+                this.baseUrlInner = this.baseUrl;
+            }
+            this.viewerUrl = `${this.baseUrlInner}/static/pdfjs/web/viewer.html?file=` + encodeURIComponent(this.src);
+		}
+	}
+</script>
+
+<style lang="scss" scoped>	
+	.up-pdf-reader {
+	}
+</style>

+ 10 - 4
node_modules/uview-plus/components/u-picker-data/u-picker-data.vue

@@ -18,8 +18,9 @@
 			:columns="optionsInner"
 			:keyName="labelKey"
 			:defaultIndex="defaultIndex"
-			@confirm="select"
-			@cancel="cancel">
+			@confirm="confirm"
+			@cancel="cancel"
+			@close="close">
 		</up-picker>
 	</view>
 </template>
@@ -89,15 +90,19 @@ export default {
 			return [this.options];
 		}
 	},
-    emits: ['update:modelValue'],
+    emits: ['update:modelValue', 'cancel', 'close', 'confirm'],
     methods: {
         hideKeyboard() {
             uni.hideKeyboard()
         },
 		cancel() {
 			this.show = false;
+			this.$emit('cancel')
 		},
-        select(e) {
+		close() {
+			this.$emit('close')
+		},
+        confirm(e) {
 			const {
 			    columnIndex,
 			    index,
@@ -108,6 +113,7 @@ export default {
             this.$emit('update:modelValue', value[0][this.valueKey]);
 			this.defaultIndex = columnIndex;
 			this.current = value[0][this.labelKey];
+			this.$emit('confirm')
         }
     }
 }

+ 6 - 4
node_modules/uview-plus/components/u-picker/picker.js

@@ -7,6 +7,7 @@
  * @lastTime     : 2021-08-20 17:18:20
  * @FilePath     : /u-view2.0/uview-ui/libs/config/props/picker.js
  */
+import { t } from '../../libs/i18n'
 export default {
     // picker
     picker: {
@@ -17,8 +18,8 @@ export default {
         columns: [],
         loading: false,
         itemHeight: 44,
-        cancelText: '取消',
-        confirmText: '确定',
+        cancelText: t("up.common.cancel"),
+        confirmText: t("up.common.confirm"),
         cancelColor: '#909193',
         confirmColor: '',
         visibleItemCount: 5,
@@ -30,11 +31,12 @@ export default {
 		zIndex: 10076,
         disabled: false,
         disabledColor: '',
-        placeholder: '请选择',
+        placeholder: t("up.common.pleaseChoose"),
         inputProps: {},
         bgColor: '',
         round: 0,
         duration: 300,
-        overlayOpacity: 0.5
+        overlayOpacity: 0.5,
+        pageInline: false
     }
 }

+ 6 - 1
node_modules/uview-plus/components/u-picker/props.js

@@ -142,6 +142,11 @@ export const props = defineMixin({
         overlayOpacity: {
             type: [Number, String],
             default: () => defProps.picker.overlayOpacity
-        }
+        },
+        // 是否页面内展示
+        pageInline:{
+			type: Boolean,
+			default: () => defProps.picker.pageInline
+		}
     }
 })

+ 2 - 1
node_modules/uview-plus/components/u-picker/u-picker.vue

@@ -11,7 +11,7 @@
 				v-model="inputLabel"
 				v-bind="inputPropsInner">
 			</up-input>
-			<div class="input-cover"></div>
+			<view class="input-cover"></view>
 		</view>
 		<u-popup
 			:show="show || (hasInput && showByClickInput)"
@@ -20,6 +20,7 @@
 			:bgColor="bgColor"
 			:round="round"
 			:duration="duration"
+			:pageInline="pageInline"
 			:overlayOpacity="overlayOpacity"
 			@close="closeHandler"
 		>

+ 59 - 0
node_modules/uview-plus/components/u-popover/props.js

@@ -0,0 +1,59 @@
+import { defineMixin } from '../../libs/vue'
+import defProps from '../../libs/config/props.js'
+
+export const props = defineMixin({
+    props: {
+        // 显示的文字内容
+        text: {
+            type: [String, Number],
+            default: ''
+        },
+        // 文字颜色
+        color: {
+            type: String,
+            default: '#333'
+        },
+        // 背景颜色
+        bgColor: {
+            type: String,
+            default: '#f7f7f7'
+        },
+        // 弹出框背景颜色
+        popupBgColor: {
+            type: String,
+            default: '#f7f7f7'
+        },
+        // 弹出框位置
+        placement: {
+            type: String,
+            default: 'top'
+        },
+        // 触发方式
+        triggerMode: {
+            type: String,
+            default: 'click'
+        },
+        // 是否显示 (manual模式下使用)
+        show: {
+            type: Boolean,
+            default: false
+        },
+        // z-index值
+        zIndex: {
+            type: [Number, String],
+            default: 10070
+        },
+        // 强制定位
+        forcePosition: {
+            type: Object,
+            default() {
+                return {}
+            }
+        },
+        // 弹出方向
+        direction: {
+            type: String,
+            default: 'top'
+        }
+    }
+})

+ 95 - 0
node_modules/uview-plus/components/u-popover/u-popover.vue

@@ -0,0 +1,95 @@
+<template>
+	<view class="up-popover">
+		<up-tooltip
+			ref="tooltip"
+			:text="text"
+			:color="color"
+			:bg-color="bgColor"
+			:popup-bg-color="popupBgColor"
+			:placement="placement"
+			:trigger-mode="triggerMode"
+			:show="show"
+			:z-index="zIndex"
+			:force-position="forcePosition"
+			:direction="direction"
+			@open="onOpen"
+			@close="onClose"
+			@click="onClick"
+		>
+			<template #trigger>
+				<slot name="trigger"></slot>
+			</template>
+			<template #content>
+				<view class="up-popover__content">
+					<slot name="content">
+						<text>{{text}}</text>
+					</slot>
+				</view>
+			</template>
+		</up-tooltip>
+	</view>
+</template>
+
+<script>
+    import { props } from './props';
+	import { mpMixin } from '../../libs/mixin/mpMixin';
+	import { mixin } from '../../libs/mixin/mixin';
+	/**
+	 * popover 气泡弹出框
+	 * @description 基于tooltip二次封装的popover组件,用于展示更丰富的内容
+	 * @tutorial https://www.uviewui.com/components/popover.html
+	 * @property {String | Number}	text			显示的文字内容
+	 * @property {String}			color			文字颜色
+	 * @property {String}			bgColor			背景颜色
+	 * @property {String}			popupBgColor	弹出框背景颜色
+	 * @property {String}			placement		弹出框位置 (top, bottom, left, right)
+	 * @property {String}			triggerMode		触发方式 (hover, click, manual)
+	 * @property {Boolean}			show			是否显示 (manual模式下使用)
+	 * @property {Number | String}	zIndex			z-index值
+	 * @property {Object}			forcePosition	强制定位 {top, left, right, bottom}
+	 * @property {String}			direction		弹出方向 (top, bottom, left, right)
+	 * @event {Function}			open			弹出框打开时触发
+	 * @event {Function}			close			弹出框关闭时触发
+	 * @event {Function}			click			点击触发器时触发
+	 * @example <up-popover text="提示内容"><template #trigger><up-button>点击</up-button></template></up-popover>
+	 */
+	export default {
+		name: 'up-popover',
+		mixins: [mpMixin, mixin, props],
+		data() {
+			return {
+				
+			}
+		},
+		methods: {
+			onOpen() {
+				this.$emit('open');
+			},
+			onClose() {
+				this.$emit('close');
+			},
+			onClick() {
+				this.$emit('click');
+			},
+			// 打开popover
+			open() {
+				this.$refs.tooltip && this.$refs.tooltip.open();
+			},
+			// 关闭popover
+			close() {
+				this.$refs.tooltip && this.$refs.tooltip.close();
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>	
+	.up-popover {
+		
+		&__content {
+			@include flex;
+			align-items: center;
+			padding: 12px 16px;
+		}
+	}
+</style>

+ 6 - 2
node_modules/uview-plus/components/u-popup/popup.js

@@ -21,9 +21,13 @@ export default {
         safeAreaInsetBottom: true,
         safeAreaInsetTop: false,
         closeIconPos: 'top-right',
-        round: 0,
+        round: '20px',
         zoom: true,
         bgColor: '',
-        overlayOpacity: 0.5
+        overlayOpacity: 0.5,
+        pageInline: false,
+        touchable: false,
+        minHeight: '200px',
+        maxHeight: '600px'
     }
 }

+ 21 - 1
node_modules/uview-plus/components/u-popup/props.js

@@ -76,6 +76,26 @@ export const props = defineMixin({
         overlayOpacity: {
             type: [Number, String],
             default: () => defProps.popup.overlayOpacity
-        }
+        },
+        // 是否页面内展示
+        pageInline:{
+			type: Boolean,
+			default: () => defProps.popup.pageInline
+		},
+        // 是否页开启手势滑动
+        touchable:{
+			type: Boolean,
+			default: () => defProps.popup.touchable
+		},
+        // 手势滑动最小高度
+        minHeight:{
+			type: [String],
+			default: () => defProps.popup.minHeight
+		},
+        // 手势滑动最大高度
+        maxHeight:{
+			type: [String],
+			default: () => defProps.popup.maxHeight
+		}
     }
 })

+ 126 - 9
node_modules/uview-plus/components/u-popup/u-popup.vue

@@ -9,7 +9,7 @@
 				class="u-popup__trigger__cover"></view>
 		</view>
 		<u-overlay
-			:show="show"
+			:show="show && pageInline == false"
 			@click="overlayClick"
 			v-if="overlay"
 			:zIndex="zIndex"
@@ -18,9 +18,11 @@
 			:opacity="overlayOpacity"
 		></u-overlay>
 		<u-transition
-			:show="show"
+			class="u-popup__content—transition"
+			:style="contentStyleWrap"
+			:show="pageInline ? true : show"
 			:customStyle="transitionStyle"
-			:mode="position"
+			:mode="pageInline ? 'none' : position"
 			:duration="duration"
 			@afterEnter="afterEnter"
 			@click="clickHandler"
@@ -33,6 +35,18 @@
 				@touchmove.stop.prevent="noop"
 			>
 				<u-status-bar v-if="safeAreaInsetTop"></u-status-bar>
+				<!-- 增加触摸区域,用于处理手势 -->
+				<view 
+					v-if="touchable && mode === 'bottom'"
+					class="u-popup__content__touch-area"
+					@touchstart="onTouchStart"
+					@touchmove="onTouchMove"
+					@touchend="onTouchEnd"
+					@touchcancel="onTouchEnd"
+				>
+					<!-- iOS风格的横条指示器 -->
+					<view class="u-popup__content__indicator"></view>
+				</view>
 				<slot></slot>
 				<view
 					v-if="closeable"
@@ -78,9 +92,12 @@
 	 * @property {Boolean}			safeAreaInsetBottom	是否为iPhoneX留出底部安全距离 (默认 true )
 	 * @property {Boolean}			safeAreaInsetTop	是否留出顶部安全距离(状态栏高度) (默认 false )
 	 * @property {String}			closeIconPos		自定义关闭图标位置(默认 'top-right' )
-	 * @property {String | Number}	round				圆角值(默认 0)
+	 * @property {String | Number}	round				圆角值(默认 20px
 	 * @property {String }	        bgColor				背景色值(默认 '' )
 	 * @property {Boolean}			zoom				当mode=center时 是否开启缩放(默认 true )
+	 * @property {Boolean}			touchable			是否开启底部弹窗手势功能(默认 false )
+	 * @property {String}			minHeight			最小高度,单位任意,数值默认为px(默认 '200px' )
+	 * @property {String}			maxHeight			最大高度,单位任意,数值默认为px(默认 '80%' )
 	 * @property {Object}			customStyle			组件的样式,对象形式
 	 * @event {Function} open 弹出层打开
 	 * @event {Function} close 弹出层收起
@@ -91,7 +108,13 @@
 		mixins: [mpMixin, mixin, props],
 		data() {
 			return {
-				overlayDuration: this.duration + 50
+				overlayDuration: this.duration + 50,
+				// 触摸相关数据
+				touchStartY: 0,
+				touchStartHeight: 0,
+				isTouching: false,
+				// 当前弹窗高度
+				currentHeight: 'auto'
 			}
 		},
 		watch: {
@@ -107,10 +130,12 @@
 		computed: {
 			transitionStyle() {
 				const style = {
-					zIndex: this.zIndex,
-					position: 'fixed',
 					display: 'flex',
 				}
+				if (!this.pageInline) {
+					style.zIndex = this.zIndex
+					style.position = 'fixed'
+				}
 				style[this.mode] = 0
 				if (this.mode === 'left') {
 					return deepMerge(style, {
@@ -143,6 +168,23 @@
 					})
 				}
 			},
+			contentStyleWrap() {
+				const style = {}
+				
+				// 处理手势滑动时的高度变化
+				if (this.mode === 'bottom' && this.touchable) {
+					if (this.currentHeight !== 'auto') {
+						style.height = this.currentHeight
+					}
+					if (this.maxHeight) {
+						style.maxHeight = addUnit(this.maxHeight)
+					}
+					if (this.minHeight) {
+						style.minHeight = addUnit(this.minHeight)
+					}
+				}
+				return style;
+			},
 			contentStyle() {
 				const style = {}
 				// 通过设备信息的safeAreaInsets值来判断是否需要预留顶部状态栏和底部安全局的位置
@@ -169,6 +211,7 @@
 						style.borderRadius = value
 					} 
 				}
+				
 				return deepMerge(style, addStyle(this.customStyle))
 			},
 			position() {
@@ -240,8 +283,61 @@
 						this.retryComputedComponentRect(grandChild)
 					}
 				}
-			}
+			},
 			// #endif
+			
+			// 触摸开始
+			onTouchStart(e) {
+				if (!this.touchable || this.mode !== 'bottom') return;
+				this.isTouching = true;
+				this.touchStartY = e.touches[0].clientY;
+				// 保存当前高度
+				this.touchStartHeight = this.$el.querySelector('.u-popup__content—transition').offsetHeight;
+			},
+			
+			// 触摸移动
+			onTouchMove(e) {
+				if (!this.isTouching || !this.touchable || this.mode !== 'bottom') return;
+				const touchY = e.touches[0].clientY;
+				const deltaY = touchY - this.touchStartY;
+				
+				// 只处理向上滑动(减小高度)和向下滑动(增加高度)
+				if (deltaY !== 0) {
+					const newHeight = this.touchStartHeight - deltaY;
+					const minHeight = parseFloat(addUnit(this.minHeight)) || 200;
+					const maxHeight = this.maxHeight ? 
+						(this.maxHeight.toString().includes('%') ? 
+							getWindowInfo().windowHeight * (parseFloat(this.maxHeight) / 100) : 
+							parseFloat(addUnit(this.maxHeight))) : 
+						getWindowInfo().windowHeight * 0.8;
+					
+					// 限制高度在最小值和最大值之间
+					if (newHeight >= minHeight && newHeight <= maxHeight) {
+						this.currentHeight = newHeight + 'px';
+					}
+				}
+				
+				// 阻止默认滚动行为
+				e.preventDefault();
+			},
+			
+			// 触摸结束
+			onTouchEnd(e) {
+				if (!this.isTouching || !this.touchable || this.mode !== 'bottom') return;
+				this.isTouching = false;
+				
+				const touchY = e.changedTouches[0].clientY;
+				const deltaY = touchY - this.touchStartY;
+				const velocity = Math.abs(deltaY) / (e.timeStamp - e.changedTouches[0].timestamp); // 简单的速度计算
+				
+				// 快速向下滑动时关闭弹窗
+				if (deltaY > 100 || (deltaY > 30 && velocity > 0.5)) {
+					this.close();
+				} else {
+					// 恢复到自适应高度
+					// this.currentHeight = 'auto';
+				}
+			}
 		}
 	}
 </script>
@@ -302,6 +398,27 @@
 				border-bottom-left-radius: 10px;
 				border-bottom-right-radius: 10px;
 			}
+			
+			&__touch-area {
+				// position: absolute;
+				top: 0;
+				left: 0;
+				right: 0;
+				height: 42rpx;
+				z-index: 10;
+				/* #ifndef APP-NVUE */
+				display: flex;
+				/* #endif */
+				justify-content: center;
+				align-items: center;
+			}
+			
+			&__indicator {
+				width: 100px;
+				height: 5px;
+				border-radius: 100px;
+				background-color: #c0c4cc;
+			}
 
 			&__close {
 				position: absolute;
@@ -332,4 +449,4 @@
 			}
 		}
 	}
-</style>
+</style>

+ 622 - 0
node_modules/uview-plus/components/u-poster/u-poster.vue

@@ -0,0 +1,622 @@
+<template>
+	<view class="up-poster">
+		<!-- canvas用于绘制海报 -->
+		<canvas 
+			v-if="showCanvas" 
+			class="up-poster__hidden-canvas" 
+			:canvas-id="canvasId" 
+			:id="canvasId"
+			:style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }">
+		</canvas>
+		<!-- 隐藏的二维码组件,用于生成二维码图片 -->
+		<up-qrcode 
+			ref="qrCode" 
+			:val="qrCodeValue" 
+			:size="qrCodeSize" 
+			:margin="0" 
+            :loadMake="false"
+			background="#ffffff"
+			foreground="#000000"
+			:class="['up-poster__hidden-qrcode', qrCodeShow ? '' : 'up-poster__hidden-qrcode--hidden']"
+		/>
+	</view>
+</template>
+
+<script>
+/**
+ * Poster 海报组件
+ * @description 用于生成海报的组件,支持文本、图片、二维码等元素
+ * @tutorial https://ijry.github.io/uview-plus/components/poster.html
+ * 
+ * @property {Object} json 海报配置JSON数据
+ * @property {Object} json.css 海报容器样式
+ * @property {Array}  json.views 海报元素列表
+ * @property {String} json.views.type 元素类型(text/image/qrcode/view)
+ * @property {String} json.views.text 文本内容(仅text类型)
+ * @property {String} json.views.src 图片地址(仅image/qrcode类型)
+ * @property {Object} json.views.css 元素样式
+ * 
+ * @example <up-poster :json="posterJson"></up-poster>
+ */
+export default {
+	name: 'up-poster',
+	props: {
+		json: {
+			type: Object,
+			default: () => ({})
+		}
+	},
+	data() {
+		return {
+			canvasId: 'u-poster-canvas-' + Date.now(),
+			showCanvas: false,
+			canvasWidth: 0,
+			canvasHeight: 0,
+			// 二维码相关数据
+			qrCodeValue: '',
+			qrCodeSize: 200,
+			qrCodeShow: false,
+			// 存储多个二维码的数据
+			qrCodeMap: new Map()
+		}
+	},
+	computed: {
+		// 根据传入的css生成文本样式
+		getTextStyle() {
+			return (css) => {
+				const style = {};
+				if (css.color) style.color = css.color;
+				if (css.fontSize) style.fontSize = css.fontSize;
+				if (css.fontWeight) style.fontWeight = css.fontWeight;
+				if (css.lineHeight) style.lineHeight = css.lineHeight;
+				if (css.textAlign) style.textAlign = css.textAlign;
+				return style;
+			}
+		}
+	},
+	methods: {
+		/**
+		 * 导出海报图片
+		 * @description 根据json配置生成海报并导出为临时图片路径
+		 * @returns {Promise<Object>} 返回包含图片信息的对象
+		 * @author jry ijry@qq.com
+		 */
+		async exportImage() {
+			return new Promise(async(resolve, reject) => {
+                try {
+                    // 获取海报尺寸信息
+                    const posterSize = this.json.css;
+                    // 将rpx转换为px
+                    const width = this.convertRpxToPx(posterSize.width || '750rpx');
+                    const height = this.convertRpxToPx(posterSize.height || '1114rpx');
+                    
+                    // 设置canvas尺寸
+                    this.canvasWidth = width;
+                    this.canvasHeight = height;
+                    this.showCanvas = true;
+                    
+                    // 等待DOM更新
+                    await this.$nextTick();
+                    
+                    // 创建canvas上下文
+                    const ctx = uni.createCanvasContext(this.canvasId, this);
+                    
+                    // 绘制背景
+                    if (posterSize.background) {
+                        // 支持渐变背景色
+						if (posterSize.background.includes('linear-gradient') || posterSize.background.includes('radial-gradient')) {
+							this.drawGradientBackground(ctx, posterSize, 0, 0, width, height);
+						} else {
+							ctx.setFillStyle(posterSize.background);
+                            ctx.fillRect(0, 0, width, height);
+						}
+                    }
+                    
+                    // 绘制所有元素
+                    for (const item of this.json.views) {
+                        await this.drawItem(ctx, item, width, height);
+                    }
+                    
+                    // 绘制到canvas
+                    ctx.draw(false, () => {
+                        // 等待绘制完成
+                        setTimeout(() => {
+                            // 导出图片
+                            uni.canvasToTempFilePath({
+                                canvasId: this.canvasId,
+                                success: (res) => {
+                                    // 隐藏canvas
+                                    this.showCanvas = false;
+                                    // 返回图片路径
+                                    resolve({
+                                        width: width,
+                                        height: height,
+                                        path: res.tempFilePath,
+                                        // H5下添加blob格式
+                                        blob: this.dataURLToBlob(res.tempFilePath)
+                                    });
+                                },
+                                fail: (err) => {
+                                    // 隐藏canvas
+                                    this.showCanvas = false;
+                                    reject(new Error('导出图片失败: ' + JSON.stringify(err)));
+                                }
+                            }, this);
+                        }, 300);
+                    });
+                    
+                    // 超时处理
+                    setTimeout(() => {
+                        this.showCanvas = false;
+                        reject(new Error('导出图片超时'));
+                    }, 10000);
+                } catch (error) {
+                    this.showCanvas = false;
+                    reject(error);
+                }
+			});
+		},
+		
+		/**
+		 * 绘制单个元素
+		 * @description 根据元素类型绘制文本、图片、矩形或二维码到canvas
+		 * @param {Object} ctx canvas上下文
+		 * @param {Object} item 元素配置信息
+		 * @param {Number} canvasWidth canvas宽度
+		 * @param {Number} canvasHeight canvas高度
+		 * @returns {Promise} 绘制完成的Promise
+		 * @author jry ijry@qq.com
+		 */
+		async drawItem(ctx, item, canvasWidth, canvasHeight) {
+			const css = item.css || {};
+			const left = this.convertRpxToPx(css.left || '0rpx');
+			const top = this.convertRpxToPx(css.top || '0rpx');
+			const width = this.convertRpxToPx(css.width || '0rpx');
+			const height = this.convertRpxToPx(css.height || '0rpx');
+			
+			switch (item.type) {
+				case 'view':
+					// 绘制矩形背景
+					if (css.background) {
+						// 支持渐变背景色
+						if (css.background.includes('linear-gradient') || css.background.includes('radial-gradient')) {
+							this.drawGradientBackground(ctx, css, left, top, width, height);
+						} else {
+							ctx.setFillStyle(css.background);
+							// 处理圆角
+							if (css.radius) {
+								const radius = this.convertRpxToPx(css.radius);
+								this.drawRoundRect(ctx, left, top, width, height, radius, css.background);
+							} else {
+								ctx.fillRect(left, top, width, height);
+							}
+						}
+					}
+					break;
+					
+				case 'text':
+					// 设置文本样式
+					if (css.color) ctx.setFillStyle(css.color);
+					if (css.fontSize) {
+						const fontSize = this.convertRpxToPx(css.fontSize);
+						ctx.setFontSize(fontSize);
+					}
+					if (css.fontWeight) {
+						ctx.setLineWidth(css.fontWeight === 'bold' ? 2 : 1);
+					}
+					
+					// 处理文本换行
+					if (css.lineClamp) {
+						this.drawTextWithLineClamp(ctx, item.text, left, top, width, css);
+					} else {
+						// 修复:文本垂直居中对齐问题
+						const textBaseLine = css.fontSize ? this.convertRpxToPx(css.fontSize) / 2 : 10;
+						ctx.fillText(item.text, left, top + textBaseLine);
+					}
+					break;
+					
+				case 'image':
+					// 绘制图片
+					return new Promise((resolve) => {
+						uni.getImageInfo({
+							src: item.src,
+							success: (res) => {
+								// 处理圆角
+								if (css.radius) {
+									const radius = this.convertRpxToPx(css.radius);
+									this.clipRoundRect(ctx, left, top, width, height, radius);
+								}
+								ctx.drawImage(item.src, left, top, width, height);
+								// 恢复剪切区域
+								ctx.restore();
+								resolve();
+							},
+							fail: () => {
+								// 图片加载失败时绘制占位符
+								ctx.setFillStyle('#f5f5f5');
+								ctx.fillRect(left, top, width, height);
+								resolve();
+							}
+						});
+					});
+					
+				case 'qrcode':
+					// 绘制二维码
+					if (item.text) {
+						// 使用u-qrcode生成二维码图片
+						const qrCodeImageUrl = await this.generateQRCode(item.text, width, height);
+						return new Promise((resolve) => {
+							uni.getImageInfo({
+								src: qrCodeImageUrl,
+								success: (res) => {
+									ctx.drawImage(res.path, left, top, width, height);
+									resolve();
+								},
+								fail: () => {
+									// 二维码加载失败时绘制占位符
+									ctx.setFillStyle('#f5f5f5');
+									ctx.fillRect(left, top, width, height);
+									ctx.setFillStyle('#999');
+									ctx.setFontSize(12);
+									ctx.setTextAlign('center');
+									ctx.fillText('QR', left + width/2, top + height/2);
+									ctx.setTextAlign('left');
+									resolve();
+								}
+							});
+						});
+					} else {
+						// 绘制二维码占位符
+						ctx.setFillStyle('#f5f5f5');
+						ctx.fillRect(left, top, width, height);
+						ctx.setFillStyle('#999');
+						ctx.setFontSize(12);
+						ctx.setTextAlign('center');
+						ctx.fillText('QR', left + width/2, top + height/2);
+						ctx.setTextAlign('left');
+					}
+					break;
+			}
+		},
+		
+		/**
+		 * 绘制圆角矩形
+		 * @description 绘制指定位置和尺寸的圆角矩形
+		 * @param {Object} ctx canvas上下文
+		 * @param {Number} x x坐标
+		 * @param {Number} y y坐标
+		 * @param {Number} width 宽度
+		 * @param {Number} height 高度
+		 * @param {Number} radius 圆角半径
+		 * @param {String} fillColor 填充颜色
+		 * @author jry ijry@qq.com
+		 */
+		drawRoundRect(ctx, x, y, width, height, radius, fillColor) {
+			ctx.save();
+			ctx.beginPath();
+			ctx.moveTo(x + radius, y);
+			ctx.lineTo(x + width - radius, y);
+			ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
+			ctx.lineTo(x + width, y + height - radius);
+			ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
+			ctx.lineTo(x + radius, y + height);
+			ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
+			ctx.lineTo(x, y + radius);
+			ctx.quadraticCurveTo(x, y, x + radius, y);
+			ctx.closePath();
+			if (fillColor) {
+				ctx.setFillStyle(fillColor);
+				ctx.fill();
+			}
+			ctx.restore();
+		},
+		
+		/**
+		 * 裁剪圆角矩形区域
+		 * @description 在canvas上创建圆角矩形裁剪区域
+		 * @param {Object} ctx canvas上下文
+		 * @param {Number} x x坐标
+		 * @param {Number} y y坐标
+		 * @param {Number} width 宽度
+		 * @param {Number} height 高度
+		 * @param {Number} radius 圆角半径
+		 * @author jry ijry@qq.com
+		 */
+		clipRoundRect(ctx, x, y, width, height, radius) {
+			ctx.save();
+			ctx.beginPath();
+			ctx.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 1.5);
+			ctx.lineTo(x + width - radius, y);
+			ctx.arc(x + width - radius, y + radius, radius, Math.PI * 1.5, Math.PI * 2);
+			ctx.lineTo(x + width, y + height - radius);
+			ctx.arc(x + width - radius, y + height - radius, radius, 0, Math.PI * 0.5);
+			ctx.lineTo(x + radius, y + height);
+			ctx.arc(x + radius, y + height - radius, radius, Math.PI * 0.5, Math.PI);
+			ctx.closePath();
+			ctx.clip();
+		},
+		
+		/**
+		 * 绘制带行数限制的文本
+		 * @description 绘制可控制最大行数的文本,超出部分显示省略号
+		 * @param {Object} ctx canvas上下文
+		 * @param {String} text 文本内容
+		 * @param {Number} x x坐标
+		 * @param {Number} y y坐标
+		 * @param {Number} maxWidth 最大宽度
+		 * @param {Object} css 样式配置
+		 * @author jry ijry@qq.com
+		 */
+		drawTextWithLineClamp(ctx, text, x, y, maxWidth, css) {
+            const lineClamp = parseInt(css.lineClamp) || 1;
+            const lineHeight = css.lineHeight ? this.convertRpxToPx(css.lineHeight) : 20;
+            const lines = [];
+            let currentLine = '';
+            
+            for (let i = 0; i < text.length; i++) {
+                const char = text[i];
+                const testLine = currentLine + char;
+                const metrics = ctx.measureText(testLine);
+                
+                if (metrics.width > maxWidth && currentLine !== '') {
+                    lines.push(currentLine);
+                    currentLine = char;
+                    
+                    // 如果已达最大行数,添加省略号并结束
+                    if (lines.length === lineClamp) {
+                        if (metrics.width > maxWidth) {
+                            // 添加省略号
+                            let fitLine = currentLine.substring(0, currentLine.length - 1);
+                            while (ctx.measureText(fitLine + '...').width > maxWidth && fitLine.length > 0) {
+                                fitLine = fitLine.substring(0, fitLine.length - 1);
+                            }
+                            lines[lines.length - 1] = fitLine + '...';
+                        }
+                        break;
+                    }
+                } else {
+                    currentLine = testLine;
+                }
+                
+                // 处理最后一行
+                if (i === text.length - 1 && lines.length < lineClamp) {
+                    lines.push(currentLine);
+                }
+            }
+            
+            // 绘制每一行
+            for (let i = 0; i < lines.length; i++) {
+                // 修复:正确计算文本垂直位置
+                const textBaseLine = css.fontSize ? this.convertRpxToPx(css.fontSize) / 2 : 10;
+                ctx.fillText(lines[i], x, y + (i * lineHeight) + textBaseLine);
+            }
+        },
+		
+		/**
+		 * 生成二维码图片
+		 * @description 根据文本内容生成二维码图片URL
+		 * @param {String} text 二维码内容
+		 * @param {Number} width 二维码宽度
+		 * @param {Number} height 二维码高度
+		 * @returns {Promise<String>} 二维码图片URL
+		 * @author jry ijry@qq.com
+		 */
+		generateQRCode(text, width, height) {
+			return new Promise((resolve) => {
+				// 为每个二维码生成唯一标识
+				const qrCodeKey = `${text}_${width}_${height}`;
+				
+				// 检查是否已经生成过该二维码
+				if (this.qrCodeMap.has(qrCodeKey)) {
+					resolve(this.qrCodeMap.get(qrCodeKey));
+					return;
+				}
+				
+				// 使用 u-qrcode 组件生成二维码
+				try {
+					// 设置二维码参数
+					this.qrCodeValue = text;
+					this.qrCodeSize = Math.max(width, height);
+					this.qrCodeShow = true;
+					
+					// 等待DOM更新
+					this.$nextTick(() => {
+						// 获取二维码组件实例并导出图片
+						if (this.$refs.qrCode) {
+							// 延迟一点时间确保二维码渲染完成
+							setTimeout(() => {
+								// 调用 u-qrcode 的 toTempFilePath 方法导出图片
+								this.$refs.qrCode.toTempFilePath({
+									success: (res) => {
+										// 缓存二维码图片路径
+										this.qrCodeMap.set(qrCodeKey, res.tempFilePath);
+										this.qrCodeShow = false;
+										resolve(res.tempFilePath);
+									},
+									fail: (err) => {
+										console.error('二维码生成失败:', err);
+										this.qrCodeShow = false;
+									}
+								});
+							}, 300);
+						} else {
+							// 如果没有 u-qrcode 组件,返回占位符
+							this.qrCodeShow = false;
+						}
+					});
+				} catch (error) {
+					console.error('生成二维码出错:', error);
+					this.qrCodeShow = false;
+				}
+			});
+		},
+		
+		/**
+		 * 将rpx单位转换为px
+		 * @description 根据屏幕密度将rpx单位转换为px单位
+		 * @param {String|Number} rpxValue rpx值
+		 * @returns {Number} 转换后的px值
+		 * @author jry ijry@qq.com
+		 */
+		convertRpxToPx(rpxValue) {
+			if (typeof rpxValue === 'number') return rpxValue;
+			
+			// 使用uni-app自带的uni.rpx2px方法
+			if (typeof rpxValue === 'string' && rpxValue.endsWith('rpx')) {
+				const value = parseFloat(rpxValue);
+				return uni.rpx2px(value);
+			}
+			
+			return parseFloat(rpxValue) || 0;
+		},
+		
+		/**
+		 * 绘制渐变背景
+		 * @description 绘制线性渐变或径向渐变背景
+		 * @param {Object} ctx canvas上下文
+		 * @param {Object} css 样式配置
+		 * @param {Number} left 左边距
+		 * @param {Number} top 上边距
+		 * @param {Number} width 宽度
+		 * @param {Number} height 高度
+		 * @author jry ijry@qq.com
+		 */
+		drawGradientBackground(ctx, css, left, top, width, height) {
+			const background = css.background;
+			let gradient = null;
+			
+			// 处理线性渐变
+			if (background.includes('linear-gradient')) {
+				// 解析线性渐变角度和颜色
+				const angleMatch = background.match(/linear-gradient\((\d+)deg/);
+				const angle = angleMatch ? parseInt(angleMatch[1]) : 135;
+				
+				// 根据角度计算渐变起点和终点
+				let startX = left, startY = top, endX = left + width, endY = top + height;
+				
+				// 简化的角度处理(支持常见角度)
+				if (angle === 0) {
+					startX = left;
+					startY = top + height;
+					endX = left;
+					endY = top;
+				} else if (angle === 90) {
+					startX = left;
+					startY = top;
+					endX = left + width;
+					endY = top;
+				} else if (angle === 180) {
+					startX = left;
+					startY = top;
+					endX = left;
+					endY = top + height;
+				} else if (angle === 270) {
+					startX = left + width;
+					startY = top;
+					endX = left;
+					endY = top;
+				}
+				
+				gradient = ctx.createLinearGradient(startX, startY, endX, endY);
+				
+				// 解析颜色值
+				const colorMatches = background.match(/#[0-9a-fA-F]+|rgba?\([^)]+\)/g);
+				if (colorMatches && colorMatches.length >= 2) {
+					// 添加渐变色点
+					colorMatches.forEach((color, index) => {
+						const stop = index / (colorMatches.length - 1);
+						gradient.addColorStop(stop, color);
+					});
+				}
+			} 
+			// 处理径向渐变
+			else if (background.includes('radial-gradient')) {
+				// 径向渐变从中心开始
+				const centerX = left + width / 2;
+				const centerY = top + height / 2;
+				const radius = Math.min(width, height) / 2;
+				
+				gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, radius);
+				
+				// 解析颜色值
+				const colorMatches = background.match(/#[0-9a-fA-F]+|rgba?\([^)]+\)/g);
+				if (colorMatches && colorMatches.length >= 2) {
+					// 添加渐变色点
+					colorMatches.forEach((color, index) => {
+						const stop = index / (colorMatches.length - 1);
+						gradient.addColorStop(stop, color);
+					});
+				}
+			}
+			
+			if (gradient) {
+				ctx.setFillStyle(gradient);
+				// 处理圆角
+				if (css.radius) {
+					const radius = this.convertRpxToPx(css.radius);
+					this.drawRoundRect(ctx, left, top, width, height, radius, gradient);
+				} else {
+					ctx.fillRect(left, top, width, height);
+				}
+			}
+		},
+		
+		/**
+		 * 将dataURL转换为Blob
+		 * @description H5环境下将base64格式的dataURL转换为Blob对象
+		 * @param {String} dataURL base64格式的图片数据
+		 * @returns {Blob} Blob对象
+		 * @author jry ijry@qq.com
+		 */
+		dataURLToBlob(dataURL) {
+			// 检查是否为H5环境且是base64数据
+			// #ifdef H5
+			if (dataURL && dataURL.startsWith('data:image')) {
+				const parts = dataURL.split(';base64,');
+				const contentType = parts[0].split(':')[1];
+				const raw = window.atob(parts[1]);
+				const rawLength = raw.length;
+				const uInt8Array = new Uint8Array(rawLength);
+				
+				for (let i = 0; i < rawLength; ++i) {
+					uInt8Array[i] = raw.charCodeAt(i);
+				}
+				
+				return new Blob([uInt8Array], { type: contentType });
+			}
+			// #endif
+			
+			return null;
+		},
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.up-poster {
+	position: relative;
+	
+	&__canvas {
+		position: relative;
+		overflow: hidden;
+	}
+	
+	&__hidden-canvas {
+		position: fixed;
+		top: -10000px;
+		left: -10000px;
+		z-index: -1;
+	}
+	
+	&__hidden-qrcode {
+		position: fixed;
+		top: -10000px;
+		left: -10000px;
+		z-index: -1;
+		
+		&--hidden {
+			display: none;
+		}
+	}
+}
+</style>

+ 8 - 6
node_modules/uview-plus/components/u-pull-refresh/u-pull-refresh.vue

@@ -24,7 +24,7 @@
           <view class="refresh-indicator">
             <up-icon name="arrow-downward" size="26px"></up-icon>
           </view>
-          <text class="refresh-text">下拉刷新</text>
+          <text class="refresh-text">{{ t("up.pullRefresh.pull") }}</text>
         </view>
       </slot>
       
@@ -39,7 +39,7 @@
           <view class="refresh-indicator">
             <up-icon name="arrow-upward" size="26px"></up-icon>
           </view>
-          <text class="refresh-text">释放刷新</text>
+          <text class="refresh-text">{{ t("up.pullRefresh.release") }}</text>
         </view>
       </slot>
       
@@ -52,7 +52,7 @@
           <view class="refresh-indicator">
             <view class="spinner"></view>
           </view>
-          <text class="refresh-text">正在刷新...</text>
+          <text class="refresh-text">{{ t("up.pullRefresh.refreshing") }}...</text>
         </view>
       </slot>
     </view>
@@ -95,6 +95,7 @@
 </template>
 
 <script>
+import { t } from '../../libs/i18n'
 export default {
   name: 'u-pull-refresh',
   props: {
@@ -128,9 +129,9 @@ export default {
       type: Object,
       default: () => ({
         status: 'loadmore',
-        loadmoreText: '加载更多',
-        loadingText: '正在加载...',
-        nomoreText: '没有更多了'
+        // loadmoreText: '加载更多',
+        // loadingText: '正在加载...',
+        // nomoreText: '没有更多了'
       })
     },
     // 是否使用 scroll-view 包装内容
@@ -179,6 +180,7 @@ export default {
     }
   },
   methods: {
+    t,
     // 触摸开始
     onTouchStart(e) {
       if (this.isRefreshing) return

+ 18 - 12
node_modules/uview-plus/components/u-qrcode/u-qrcode.vue

@@ -260,7 +260,7 @@ export default {
                 url: this.result
             }, e)
         },
-        async longpress() {
+        async toTempFilePath({success, fail}) {
             if (this.context) {
                 this.ctx.toTempFilePath(
                     0,
@@ -272,14 +272,15 @@ export default {
                     "",
                     1,
                     res => {
-                        this.$emit('longpressCallback', res.tempFilePath)
+                        success(res)
                     }
                 );
             }
             else {
-
                 // #ifdef MP-TOUTIAO || H5
-                this.$emit('longpressCallback', this.ctx.canvas.toDataURL("image/png", 1));
+                success({
+                    tempFilePath: this.ctx.canvas.toDataURL("image/png", 1)
+                })
                 // #endif
 
                 // #ifdef APP-PLUS
@@ -287,10 +288,9 @@ export default {
                     {
                         canvasId: this.cid,
                         success :res => {
-                            this.$emit('longpressCallback', res.tempFilePath)
+                            success(res)
                         },
-                        fail: err =>{
-                        }
+                        fail: fail
                     },
                     this)
                 // #endif
@@ -301,16 +301,22 @@ export default {
                     {
                         canvas,
                         success :res => {
-                            this.$emit('longpressCallback', res.tempFilePath)
+                            success(res)
                         },
-                        fail: err =>{
-                        }
+                        fail: fail
                     },
                     this)
                 // #endif
-
             }
-
+        },
+        async longpress() {
+            this.toTempFilePath({
+                success: res => {
+                    this.$emit('longpressCallback', res.tempFilePath)
+                },
+                fail: err => {
+                }
+            })
         },
 
         /**

+ 3 - 2
node_modules/uview-plus/components/u-read-more/readMore.js

@@ -7,13 +7,14 @@
  * @lastTime     : 2021-08-20 17:18:41
  * @FilePath     : /u-view2.0/uview-ui/libs/config/props/readMore.js
  */
+import { t } from '../../libs/i18n'
 export default {
     // readMore
     readMore: {
         showHeight: 400,
         toggle: false,
-        closeText: '展开阅读全文',
-        openText: '收起',
+        closeText: t("up.readMore.expand"),
+        openText: t("up.readMore.fold"),
         color: '#2979ff',
         fontSize: 14,
         textIndent: '2em',

+ 152 - 134
node_modules/uview-plus/components/u-search/props.js

@@ -1,138 +1,156 @@
 import { defineMixin } from '../../libs/vue'
 import defProps from '../../libs/config/props.js'
+
 export const props = defineMixin({
-    props: {
-        // 搜索框形状,round-圆形,square-方形
-        shape: {
-            type: String,
-            default: () => defProps.search.shape
-        },
-        // 搜索框背景色,默认值#f2f2f2
-        bgColor: {
-            type: String,
-            default: () => defProps.search.bgColor
-        },
-        // 占位提示文字
-        placeholder: {
-            type: String,
-            default: () => defProps.search.placeholder
-        },
-        // 是否启用清除控件
-        clearabled: {
-            type: Boolean,
-            default: () => defProps.search.clearabled
-        },
-        // 是否自动聚焦
-        focus: {
-            type: Boolean,
-            default: () => defProps.search.focus
-        },
-        // 是否在搜索框右侧显示取消按钮
-        showAction: {
-            type: Boolean,
-            default: () => defProps.search.showAction
-        },
-        // 右边控件的样式
-        actionStyle: {
-            type: Object,
-            default: () => defProps.search.actionStyle
-        },
-        // 取消按钮文字
-        actionText: {
-            type: String,
-            default: () => defProps.search.actionText
-        },
-        // 输入框内容对齐方式,可选值为 left|center|right
-        inputAlign: {
-            type: String,
-            default: () => defProps.search.inputAlign
-        },
-        // input输入框的样式,可以定义文字颜色,大小等,对象形式
-        inputStyle: {
-            type: Object,
-            default: () => defProps.search.inputStyle
-        },
-        // 是否启用输入框
-        disabled: {
-            type: Boolean,
-            default: () => defProps.search.disabled
-        },
-        // 边框颜色
-        borderColor: {
-            type: String,
-            default: () => defProps.search.borderColor
-        },
-        // 搜索图标的颜色,默认同输入框字体颜色
-        searchIconColor: {
-            type: String,
-            default: () => defProps.search.searchIconColor
-        },
-        // 输入框字体颜色
-        color: {
-            type: String,
-            default: () => defProps.search.color
-        },
-        // placeholder的颜色
-        placeholderColor: {
-            type: String,
-            default: () => defProps.search.placeholderColor
-        },
-        // 左边输入框的图标,可以为uView图标名称或图片路径
-        searchIcon: {
-            type: String,
-            default: () => defProps.search.searchIcon
-        },
-        searchIconSize: {
-            type: [Number, String],
-            default: () => defProps.search.searchIconSize
-        },
-        // 组件与其他上下左右元素之间的距离,带单位的字符串形式,如"30px"、"30px 20px"等写法
-        margin: {
-            type: String,
-            default: () => defProps.search.margin
-        },
-        // 开启showAction时,是否在input获取焦点时才显示
-        animation: {
-            type: Boolean,
-            default: () => defProps.search.animation
-        },
-        // 输入框的初始化内容
-        modelValue: {
-            type: String,
-            default: () => defProps.search.value
-        },
+	props: {
+		// #ifdef VUE3
+		// 绑定的值
+		modelValue: {
+			type: [String, Number],
+			default: () => defProps.search.value
+		},
+		// #endif
+		// #ifdef VUE2
+		// 绑定的值
 		value: {
-		    type: String,
-		    default: () => defProps.search.value
-		},
-        // 输入框最大能输入的长度,-1为不限制长度(来自uniapp文档)
-        maxlength: {
-            type: [String, Number],
-            default: () => defProps.search.maxlength
-        },
-        // 搜索框高度,单位px
-        height: {
-            type: [String, Number],
-            default: () => defProps.search.height
-        },
-        // 搜索框左侧文本
-        label: {
-            type: [String, Number, null],
-            default: () => defProps.search.label
-        },
-        // 键盘弹起时,是否自动上推页面	
-        adjustPosition: {
-            type: Boolean,
-            default: () => true
-        },
-        // 键盘收起时,是否自动失去焦点	
-        autoBlur: {
-            type: Boolean,
-            default: () => false
-        },
-        iconPosition: {
-            type: String,
-            default: () => defProps.search.iconPosition
-        }
-    }
-})
+			type: [String, Number],
+			default: () => defProps.search.value
+		},
+		// #endif
+		// 搜索框形状,round-圆形,square-方形
+		shape: {
+			type: String,
+			default: () => defProps.search.shape
+		},
+		// 搜索框背景色
+		bgColor: {
+			type: String,
+			default: () => defProps.search.bgColor
+		},
+		// 占位提示文字
+		placeholder: {
+			type: String,
+			default: () => defProps.search.placeholder
+		},
+		// 是否启用清除控件
+		clearabled: {
+			type: Boolean,
+			default: () => defProps.search.clearabled
+		},
+		// 是否仅聚焦时显示清除控件
+		onlyClearableOnFocused: {
+			type: Boolean,
+			default: true
+		},
+		// 是否自动聚焦
+		focus: {
+			type: Boolean,
+			default: () => defProps.search.focus
+		},
+		// 是否在搜索框右侧显示取消按钮
+		showAction: {
+			type: Boolean,
+			default: () => defProps.search.showAction
+		},
+		// 右侧取消按钮文字
+		actionText: {
+			type: String,
+			default: () => defProps.search.actionText
+		},
+		// 搜索框左侧文本
+		label: {
+			type: [String, Number, null],
+			default: () => defProps.search.label
+		},
+		// 输入框内容对齐方式,可选值为:left|center|right
+		inputAlign: {
+			type: String,
+			default: () => defProps.search.inputAlign
+		},
+		// 是否启用输入框
+		disabled: {
+			type: Boolean,
+			default: () => defProps.search.disabled
+		},
+		// 开启showAction时,是否在input获取焦点时才显示
+		animation: {
+			type: Boolean,
+			default: () => defProps.search.animation
+		},
+		// 边框颜色,只要配置了颜色,才会有边框
+		borderColor: {
+			type: String,
+			default: () => defProps.search.borderColor
+		},
+		// 搜索图标的颜色,默认同输入框字体颜色
+		searchIconColor: {
+			type: String,
+			default: () => defProps.search.searchIconColor
+		},
+		// 搜索图标的大小
+		searchIconSize: {
+			type: [Number, String],
+			default: () => defProps.search.searchIconSize
+		},
+		// 输入框字体颜色
+		color: {
+			type: String,
+			default: () => defProps.search.color
+		},
+		// placeholder的颜色
+		placeholderColor: {
+			type: String,
+			default: () => defProps.search.placeholderColor
+		},
+		// 左边输入框的图标,可以为uView图标名称或图片路径
+		searchIcon: {
+			type: String,
+			default: () => defProps.search.searchIcon
+		},
+		// 组件与其他上下左右元素之间的距离,带单位的字符串形式,如"30px"
+		margin: {
+			type: String,
+			default: () => defProps.search.margin
+		},
+		// 应该是uView-plus版本新增的,用于控制搜索图标的插槽位置
+		iconPosition: {
+			type: String,
+			default: () => defProps.search.iconPosition
+		},
+		// 输入框最大能输入的长度,-1为不限制长度
+		maxlength: {
+			type: [String, Number],
+			default: () => defProps.search.maxlength
+		},
+		// 输入框高度,单位px
+		height: {
+			type: [String, Number],
+			default: () => defProps.search.height
+		},
+		// 键盘弹起时,是否自动上推页面
+		adjustPosition: {
+			type: Boolean,
+			default: () => defProps.search.adjustPosition
+		},
+		// 键盘收起时,是否自动失去焦点
+		autoBlur: {
+			type: Boolean,
+			default: () => defProps.search.autoBlur
+		},
+		// 输入框的样式,对象形式
+		inputStyle: {
+			type: Object,
+			default: () => defProps.search.inputStyle
+		},
+		// 右侧控件的样式,对象形式
+		actionStyle: {
+			type: Object,
+			default: () => defProps.search.actionStyle
+		},
+		// 自定义样式,对象形式
+		customStyle: {
+			type: Object,
+			default: () => defProps.search.customStyle
+		}
+	}
+})

+ 3 - 2
node_modules/uview-plus/components/u-search/search.js

@@ -7,17 +7,18 @@
  * @lastTime     : 2021-08-20 17:19:45
  * @FilePath     : /u-view2.0/uview-ui/libs/config/props/search.js
  */
+import { t } from '../../libs/i18n'
 export default {
     // search
     search: {
         shape: 'round',
         bgColor: '#f2f2f2',
-        placeholder: '请输入关键字',
+        placeholder: t("up.search.placeholder"),
         clearabled: true,
         focus: false,
         showAction: true,
         actionStyle: {},
-        actionText: '搜索',
+        actionText: t("up.common.search"),
         inputAlign: 'left',
         inputStyle: {},
         disabled: false,

+ 14 - 2
node_modules/uview-plus/components/u-search/u-search.vue

@@ -55,7 +55,7 @@
 			/>
 			<view
 			    class="u-search__content__icon u-search__content__close"
-			    v-if="keyword && clearabled && focused"
+			    v-if="isShowClear"
 			    @click="clear"
 			>
 				<up-icon
@@ -111,6 +111,7 @@
 	 * @property {String | Number}	label				搜索框左边显示内容
 	 * @property {Boolean}	        adjustPosition	    键盘弹起时,是否自动上推页面
 	 * @property {Boolean}	        autoBlur	        键盘收起时,是否自动失去焦点
+	 * @property {Boolean}	        onlyClearableOnFocused	是否仅在聚焦时显示清除控件(默认 true )
 	 * @property {Object}			customStyle			定义需要用到的外部样式
 	 *
 	 * @event {Function} change 输入框内容发生变化时触发
@@ -125,7 +126,6 @@
 		data() {
 			return {
 				keyword: '',
-				showClear: false, // 是否显示右边的清除图标
 				show: false,
 				// 标记input当前状态是否处于聚焦中,如果是,才会显示右侧的清除控件
 				focused: this.focus
@@ -165,6 +165,18 @@
 		computed: {
 			showActionBtn() {
 				return !this.animation && this.showAction
+			},
+			// 是否显示清除控件
+			isShowClear() {
+				const { clearabled, focused, keyword, onlyClearableOnFocused } = this;
+				if (!clearabled) {
+					return false;
+				}
+				if (onlyClearableOnFocused) {
+					return !!focused && keyword !== "";
+				} else {
+					return keyword !== "";
+				}
 			}
 		},
 		emits: ['clear', 'search', 'custom', 'focus', 'blur', 'click', 'clickIcon', 'update:modelValue', 'change'],

+ 2 - 1
node_modules/uview-plus/components/u-section/section.js

@@ -7,11 +7,12 @@
  * @lastTime     : 2021-08-20 17:07:33
  * @FilePath     : /u-view2.0/uview-ui/libs/config/props/section.js
  */
+import { t } from '../../libs/i18n'
 export default {
     // u-section组件
     section: {
         title: '',
-        subTitle: '更多',
+        subTitle: t("up.common.more"),
         right: true,
         fontSize: 15,
         bold: true,

+ 463 - 0
node_modules/uview-plus/components/u-short-video/u-short-video.vue

@@ -0,0 +1,463 @@
+<template>
+	<view class="u-short-video">
+		<!-- 顶部导航区域 -->
+		<view class="u-short-video__header">
+			<slot name="menu">
+				<view class="u-short-video__header__menu">
+					<up-icon name="grid" size="24"></up-icon>
+				</view>
+			</slot>
+			
+			<up-tabs 
+				:list="tabsList" 
+				:current="currentTab"
+                lineColor="#ddd"
+                :activeStyle="{
+                    color: '#ddd',
+                    fontWeight: 400,
+                    transform: 'scale(1)'
+                }"
+                :inactiveStyle="{
+                    color: '#bbb',
+                    transform: 'scale(1)'
+                }"
+				@change="handleTabChange"
+				class="u-short-video__header__tabs"
+			></up-tabs>
+			
+			<slot name="search">
+				<view class="u-short-video__header__search">
+					<up-icon name="search" size="24"></up-icon>
+				</view>
+			</slot>
+		</view>
+		
+		<!-- 视频内容区域 -->
+		<swiper 
+			:vertical="true" 
+			:autoplay="false" 
+			@change="handleSwiperChange"
+			:current="currentVideo"
+			class="u-short-video__content"
+		>
+			<swiper-item v-for="(item, index) in videoList" :key="index">
+				<view class="u-short-video__content__item">
+					<!-- 视频播放区域 -->
+					<view class="u-short-video__content__video">
+						<video 
+							:id="'video-' + index"
+							:src="item.videoUrl" 
+							:autoplay="index === currentVideo" 
+							:controls="false"
+							:show-fullscreen-btn="false"
+							:show-play-btn="false"
+							:show-center-play-btn="false"
+							:enable-progress-gesture="true"
+							:loop="true"
+							:playback-rate="item.playbackRate || 1.0"
+							@play="onVideoPlay"
+							@pause="onVideoPause"
+							@ended="onVideoEnded"
+							@timeupdate="onTimeUpdate"
+							@loadedmetadata="onLoadedMetadata"
+							style="width: 100%; height: 100%;"
+						></video>
+						
+						<!-- 倍速设置按钮 -->
+						<!-- <view class="u-short-video__content__video__speed" @click="showSpeedOptions(index)">
+							<text class="speed-text">{{ item.playbackRate || 1.0 }}x</text>
+							<up-icon name="arrow-down" size="12" color="#fff"></up-icon>
+						</view> -->
+					</view>
+					
+					<!-- 作者信息 -->
+					<view class="u-short-video__content__author">
+						<view class="u-short-video__content__author__avatar">
+							<u-avatar :src="item.author.avatar" size="50px"></u-avatar>
+						</view>
+						<view class="u-short-video__content__author__info">
+							<text class="u-short-video__content__author__name">{{ item.author.name }}</text>
+							<text class="u-short-video__content__author__desc">{{ item.author.desc }}</text>
+						</view>
+						<view class="u-short-video__content__author__follow">
+							<up-button type="primary" size="mini">关注</up-button>
+						</view>
+					</view>
+					
+					<!-- 右侧操作区域 -->
+					<view class="u-short-video__content__actions">
+						<slot name="actions" :item="item" :index="index">
+							<view class="u-short-video__content__actions__item" @click="handleLike(item, index)">
+								<up-icon color="#eee" :name="item.isLiked ? 'thumb-up-fill' : 'thumb-up'" size="32px"></up-icon>
+								<text class="u-short-video__content__actions__text">{{ item.likeCount }}</text>
+							</view>
+							<view class="u-short-video__content__actions__item" @click="handleComment(item, index)">
+								<up-icon color="#eee" name="chat" size="32px"></up-icon>
+								<text class="u-short-video__content__actions__text">{{ item.commentCount }}</text>
+							</view>
+							<view class="u-short-video__content__actions__item" @click="handleShare(item, index)">
+								<up-icon color="#eee" name="share" size="32px"></up-icon>
+								<text class="u-short-video__content__actions__text">{{ item.shareCount }}</text>
+							</view>
+							<view class="u-short-video__content__actions__item" @click="handleCollect(item, index)">
+								<up-icon color="#eee" :name="item.isCollected ? 'bookmark-fill' : 'bookmark'" size="32px"></up-icon>
+								<text class="u-short-video__content__actions__text">{{ item.collectCount }}</text>
+							</view>
+						</slot>
+					</view>
+				</view>
+			</swiper-item>
+		</swiper>
+		
+		<!-- 倍速选择弹窗 -->
+		<up-action-sheet
+			:show="showSpeedSheet"
+			:actions="speedOptions"
+			title="播放速度"
+			@close="showSpeedSheet = false"
+			@select="selectSpeed"
+		></up-action-sheet>
+		
+		<!-- 底部导航栏 -->
+		<view class="u-short-video__footer">
+			<!-- 进度条 -->
+			<view class="u-short-video__progress" style="z-index: 999;">
+				<up-slider 
+					:value="videoList[currentVideo]?.progress" 
+					:min="0" 
+					:max="100" 
+					:step="1"
+					:show-value="false"
+                    :innerStyle="{padding: 0}"
+                    activeColor="rgba(255,255,255,0.32)"
+					inactive-color="rgba(255,255,255,0.3)"
+					block-size="6px"
+                    block-color="rgba(255,255,255,0.5)"
+					height="1px"
+					@changing="onProgressChanging"
+					@change="onProgressChange"
+				></up-slider>
+			</view>
+			
+			<slot name="tabbar">
+				<up-tabbar
+                    :fixed="true"
+                    :placeholder="true"
+                    :safeAreaInsetBottom="true"
+                    borderColor="rgba(255,255,255,0.25) !important"
+                    backgroundColor="rgba(255,255,255,0.05)"
+                >
+				<up-tabbar-item
+					@click="goNext"
+					text="首页"
+					icon="home"
+				>
+				</up-tabbar-item>
+				<up-tabbar-item
+					text="放映厅"
+					icon="photo"
+				></up-tabbar-item>
+				<up-tabbar-item
+					text="直播"
+					icon="play-right"
+				></up-tabbar-item>
+				<up-tabbar-item
+					text="我的"
+					icon="account"
+				></up-tabbar-item>
+			</up-tabbar>
+			</slot>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'u-short-video',
+		props: {
+			// tabs标签列表
+			tabsList: {
+				type: Array,
+				default: () => [
+					{ name: '推荐' },
+					{ name: '关注' },
+					{ name: '朋友' },
+					{ name: '本地' }
+				]
+			},
+			// 视频列表数据
+			videoList: {
+				type: Array,
+				default: () => []
+			},
+			// 当前选中的tab索引
+			currentTab: {
+				type: Number,
+				default: 0
+			},
+			// 当前播放的视频索引
+			currentVideo: {
+				type: Number,
+				default: 0
+			}
+		},
+		data() {
+			return {
+				progressValue: 0,
+				showSpeedSheet: false,
+				currentSpeedVideoIndex: 0,
+				speedOptions: [
+					{ name: '0.5x', value: 0.5 },
+					{ name: '0.75x', value: 0.75 },
+					{ name: '1.0x', value: 1.0 },
+					{ name: '1.25x', value: 1.25 },
+					{ name: '1.5x', value: 1.5 },
+					{ name: '2.0x', value: 2.0 }
+				]
+			}
+		},
+		methods: {
+			// 处理tab切换
+			handleTabChange(index) {
+				this.$emit('tabChange', index);
+			},
+			// 处理swiper切换
+			handleSwiperChange(e) {
+				const currentIndex = e.detail.current;
+				// 暂停当前播放的视频
+				this.pauseCurrentVideo();
+				// 播放新切换到的视频
+				this.$nextTick(() => {
+					this.playVideo(currentIndex);
+				});
+				this.$emit('videoChange', currentIndex);
+			},
+			// 处理点赞
+			handleLike(item, index) {
+				this.$emit('like', { item, index });
+			},
+			// 处理评论
+			handleComment(item, index) {
+				this.$emit('comment', { item, index });
+			},
+			// 处理分享
+			handleShare(item, index) {
+				this.$emit('share', { item, index });
+			},
+			// 处理收藏
+			handleCollect(item, index) {
+				this.$emit('collect', { item, index });
+			},
+			// 进度条拖动中
+			onProgressChanging(value) {
+				// 更新当前视频的进度值
+				if (this.videoList[this.currentVideo]) {
+					this.videoList[this.currentVideo]['progressValue'] = value.detail.value
+				}
+				this.$emit('progressChanging', {
+					progress: value.detail.value,
+					index: this.currentVideo
+				});
+			},
+			// 进度条值改变
+			onProgressChange(value) {
+				// 更新当前视频的进度值
+				if (this.videoList[this.currentVideo]) {
+					this.$set(this.videoList[this.currentVideo], 'progressValue', value.detail.value);
+				}
+				this.$emit('progressChange', {
+					progress: value.detail.value,
+					index: this.currentVideo
+				});
+			},
+			// 显示倍速选项
+			showSpeedOptions(index) {
+				this.currentSpeedVideoIndex = index;
+				this.showSpeedSheet = true;
+			},
+			// 选择倍速
+			selectSpeed(action) {
+				const videoContext = uni.createVideoContext('video-' + this.currentSpeedVideoIndex, this);
+				videoContext.playbackRate(action.value);
+				
+				// 更新视频倍速数据
+				this.$set(this.videoList[this.currentSpeedVideoIndex], 'playbackRate', action.value);
+				this.showSpeedSheet = false;
+			},
+			// 播放指定索引的视频
+			playVideo(index) {
+				const videoContext = uni.createVideoContext('video-' + index, this);
+				videoContext.play();
+			},
+			// 暂停当前视频
+			pauseCurrentVideo() {
+				const videoContext = uni.createVideoContext('video-' + this.currentVideo, this);
+				videoContext.pause();
+			},
+			// 视频播放事件
+			onVideoPlay(e) {
+				this.$emit('videoPlay', { index: this.currentVideo, event: e });
+			},
+			// 视频暂停事件
+			onVideoPause(e) {
+				this.$emit('videoPause', { index: this.currentVideo, event: e });
+			},
+			// 视频结束事件
+			onVideoEnded(e) {
+				this.$emit('videoEnded', { index: this.currentVideo, event: e });
+			},
+			// 视频时间更新事件
+			onTimeUpdate(e) {
+				const progress = (e.detail.currentTime / e.detail.duration) * 100;
+				if (this.videoList[this.currentVideo]) {
+					this.$set(this.videoList[this.currentVideo], 'progress', progress);
+				}
+				this.$emit('timeUpdate', { index: this.currentVideo, event: e });
+			},
+			// 视频元数据加载完成事件
+			onLoadedMetadata(e) {
+				this.$emit('loadedMetadata', { index: this.currentVideo, event: e });
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.u-short-video {
+		width: 100%;
+		height: 100vh;
+		position: relative;
+		
+		&__header {
+			position: absolute;
+			top: 0;
+			left: 0;
+			right: 0;
+			z-index: 10;
+			display: flex;
+            flex-direction: row;
+			align-items: center;
+			justify-content: space-between;
+			padding: 10px 15px;
+			background-color: rgba(255, 255, 255, 0.05);
+            opacity: 1;
+			
+			&__menu, &__search {
+				width: 40px;
+				height: 40px;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				color: #fff;
+			}
+			
+			&__tabs {
+				flex: 1;
+				margin: 0 10px;
+			}
+		}
+		
+		&__content {
+			width: 100%;
+			height: 100%;
+			
+			&__item {
+				width: 100%;
+				height: 100%;
+				position: relative;
+			}
+			
+			&__video {
+				width: 100%;
+				height: 100%;
+				position: relative;
+				
+				&__speed {
+					position: absolute;
+					top: 15px;
+					right: 15px;
+					z-index: 10;
+					background-color: rgba(0, 0, 0, 0.3);
+					border-radius: 20px;
+					padding: 5px 10px;
+					display: flex;
+					align-items: center;
+					
+					.speed-text {
+						color: #fff;
+						font-size: 12px;
+						margin-right: 4px;
+					}
+				}
+			}
+			
+			&__author {
+				position: absolute;
+				left: 15px;
+				bottom: 100px;
+				display: flex;
+                flex-direction: row;
+				align-items: center;
+				z-index: 10;
+				
+				&__info {
+					margin-left: 10px;
+					display: flex;
+					flex-direction: column;
+					justify-content: center;
+				}
+
+                &__name {
+						color: #eee;
+						font-size: 16px;
+						font-weight: bold;
+						margin-bottom: 5px;
+					}
+					
+					&__desc {
+						color: rgba(255, 255, 255, 0.8);
+						font-size: 14px;
+					}
+				
+				&__follow {
+					margin-left: 15px;
+				}
+			}
+			
+			&__actions {
+				position: absolute;
+				right: 15px;
+				bottom: 100px;
+				display: flex;
+				flex-direction: column;
+				align-items: center;
+				z-index: 10;
+				
+				&__item {
+					display: flex;
+					flex-direction: column;
+					align-items: center;
+					margin-bottom: 20px;
+					color: #fff;
+				}
+				
+				&__text {
+					color: #fff;
+					font-size: 12px;
+					margin-top: 5px;
+				}
+			}
+		}
+		
+		&__footer {
+			position: absolute;
+			bottom: 0;
+			left: 0;
+			right: 0;
+			z-index: 10;
+		}
+		
+		&__progress {
+		}
+	}
+</style>

+ 3 - 2
node_modules/uview-plus/components/u-signature/u-signature.vue

@@ -38,7 +38,7 @@
 			<!-- 笔画设置 -->
 			<view v-if="showBrushSettings" class="u-signature__brush-settings">
 				<view class="u-signature__progress">
-					<text class="u-signature__progress-label">笔画大小:</text>
+					<text class="u-signature__progress-label">{{ t("up.signature.penSize") }}:</text>
 					<up-slider 
 						v-model="lineWidth" 
 						:min="1" 
@@ -53,7 +53,7 @@
 			<!-- 颜色设置 -->
 			<view v-if="showColorSettings" class="u-signature__color-settings">
 				<view class="u-signature__color-picker">
-                    <text class="u-signature__color-label">笔画颜色:</text>
+                    <text class="u-signature__color-label">{{ t("up.signature.penColor") }}:</text>
 					<view class="u-signature__colors">
 						<view 
 							v-for="(color, index) in presetColors" 
@@ -71,6 +71,7 @@
 </template>
 
 <script>
+	import { t } from '../../libs/i18n'
 	export default {
 		name: 'u-signature',
 		props: {

+ 5 - 1
node_modules/uview-plus/components/u-slider/props.js

@@ -86,6 +86,10 @@ export const props = defineMixin({
         height: {
             type: String,
             default: () => defProps.slider.height
-        }
+        },
+        innerStyle: {
+            type: Object,
+            default: () => defProps.slider.innerStyle
+        },
     }
 })

+ 2 - 1
node_modules/uview-plus/components/u-slider/slider.js

@@ -22,6 +22,7 @@ export default {
 		disabled:false,
         blockStyle: {},
         useNative: false,
-        height: '2px'
+        height: '2px',
+        innerStyle: {}
     }
 }

+ 8 - 3
node_modules/uview-plus/components/u-slider/u-slider.vue

@@ -7,9 +7,7 @@
 			<view ref="u-slider-inner" class="u-slider-inner" @click="onClick"
 				@onTouchStart="onTouchStart2($event, 1)" @touchmove="onTouchMove2($event, 1)"
 				@touchend="onTouchEnd2($event, 1)" @touchcancel="onTouchEnd2($event, 1)"
-				:class="[disabled ? 'u-slider--disabled' : '']" :style="{
-					height: (isRange && showValue) ? (getPx(blockSize) + 24) + 'px' : (getPx(blockSize)) + 'px',
-				}"
+				:class="[disabled ? 'u-slider--disabled' : '']" :style="innerStyleCpu"
 			>
 				<view ref="u-slider__base"
 					class="u-slider__base"
@@ -177,6 +175,13 @@
 		},
 		created() {
 		},
+		computed: {
+			innerStyleCpu() {
+				let style = this.innerStyle;
+				style.height = (this.isRange && this.showValue) ? (getPx(this.blockSize) + 24) + 'px' : (getPx(this.blockSize)) + 'px';
+				return style;
+			}
+		},
 		async mounted() {
 			// 获取滑块条的尺寸信息
 			if (!this.useNative) {

+ 8 - 1
node_modules/uview-plus/components/u-steps-item/u-steps-item.vue

@@ -39,7 +39,8 @@
 				<view class="u-steps-item__content__title">
 					<slot name="title">
 					</slot>
-					<up-text v-if="!$slots['title']" :text="title" :type="parentData.current == index ? 'main' : 'content'" lineHeight="20px"
+					<up-text v-if="!$slots['title']" :text="title" lineHeight="20px"
+						:type="parentData.current == index ? 'main' : 'content'"
 						:size="parentData.current == index ? 14 : 13"></up-text>
 				</view>
 				<view class="u-steps-item__content__desc">
@@ -313,6 +314,12 @@
 			@include flex;
 			flex: 1;
 
+			&__title {
+				// #ifdef H5
+				cursor: pointer;
+				// #endif
+			}
+
 			&--row {
 				flex-direction: column;
 				align-items: center;

+ 6 - 2
node_modules/uview-plus/components/u-tabbar-item/props.js

@@ -7,7 +7,7 @@ export const props = defineMixin({
             type: [String, Number, null],
             default: () => defProps.tabbarItem.name
         },
-        // uView内置图标或者绝对路径的图片
+        // uview-plus内置图标或者绝对路径的图片
         icon: {
             icon: String,
             default: () => defProps.tabbarItem.icon
@@ -31,7 +31,11 @@ export const props = defineMixin({
         badgeStyle: {
             type: [Object, String],
             default: () => defProps.tabbarItem.badgeStyle
+        },
+        // 模式,默认普通模式,midButton中间按钮模式
+        mode: {
+            type: String,
+            default: () => defProps.tabbarItem.mode
         }
-
     }
 })

+ 2 - 1
node_modules/uview-plus/components/u-tabbar-item/tabbarItem.js

@@ -15,6 +15,7 @@ export default {
         badge: null,
         dot: false,
         text: '',
-        badgeStyle: 'top: 6px;right:2px;'
+        badgeStyle: 'top: 6px;right:2px;',
+        mode: ''
     }
 }

+ 44 - 4
node_modules/uview-plus/components/u-tabbar-item/u-tabbar-item.vue

@@ -2,14 +2,20 @@
 	<view
 	    class="u-tabbar-item"
 	    :style="[addStyle(customStyle)]"
+	    :class="[isMidButton ? 'u-tabbar-item--mid-button' : '']"
 	    @tap="clickHandler"
 	>
-		<view class="u-tabbar-item__icon">
+		<view 
+			class="u-tabbar-item__icon"
+			:class="[isMidButton ? 'u-tabbar-item__icon--mid-button' : '']"
+		>
+			<view class="u-tabbar-item--mid-button-cover" v-if="isMidButton">
+			</view>
 			<up-icon
 			    v-if="icon"
 			    :name="icon"
 			    :color="isActive? parentData.activeColor : parentData.inactiveColor"
-			    :size="20"
+			    :size="isMidButton ? 26 : 20"
 			></up-icon>
 			<template v-else>
 				<slot
@@ -78,6 +84,12 @@
 		options: {
 		    virtualHost: true //将自定义节点设置成虚拟的,更加接近Vue组件的表现。我们不希望自定义组件的这个节点本身可以设置样式、响应 flex 布局等
 		},
+		computed: {
+			// 计算是否为中间按钮
+			isMidButton() {
+				return this.mode === 'midButton';
+			}
+		},
 		created() {
 			this.init()
 		},
@@ -88,7 +100,7 @@
 				// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用
 				this.updateParentData()
 				if (!this.parent) {
-					error('u-tabbar-item必须搭配u-tabbar组件使用')
+					error('up-tabbar-item必须搭配up-tabbar组件使用')
 				}
 				// 本子组件在u-tabbar的children数组中的索引
 				const index = this.parent.children.indexOf(this)
@@ -146,6 +158,34 @@
 			color: $u-content-color;
 		}
 	}
+	
+	// 中间按钮样式
+	.u-tabbar-item--mid-button {
+		/* #ifndef APP-NVUE */
+		transform: translateY(-10px);
+		/* #endif */
+	}
+	
+	.u-tabbar-item--mid-button-cover {
+		background-color: #fff;
+		position: absolute;
+		top: 22px;
+		left: -10px;
+		// right: -10px;
+		width: 90px;
+		bottom: 0;
+	}
+	
+	.u-tabbar-item__icon--mid-button {
+		width: 70px;
+		height: 70px;
+		border-radius: 100px;
+		background-color: #ffffff;
+		box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+		display: flex;
+		align-items: center;
+		justify-content: center;
+	}
 
 	/* #ifdef MP */
 	// 由于小程序都使用shadow DOM形式实现,需要给影子宿主设置flex: 1才能让其撑开
@@ -154,4 +194,4 @@
 		width: 100%;
 	}
 	/* #endif */
-</style>
+</style>

+ 10 - 0
node_modules/uview-plus/components/u-tabbar/props.js

@@ -18,6 +18,11 @@ export const props = defineMixin({
             type: Boolean,
             default: () => defProps.tabbar.border
         },
+        // 上方边框颜色
+        borderColor: {
+            type: Boolean,
+            default: () => defProps.tabbar.borderColor
+        },
         // 元素层级z-index
         zIndex: {
             type: [String, Number],
@@ -42,6 +47,11 @@ export const props = defineMixin({
         placeholder: {
             type: Boolean,
             default: () => defProps.tabbar.placeholder
+        },
+        // 背景色
+        backgroundColor: {
+            type: String,
+            default: () => defProps.tabbar.backgroundColor
         }
     }
 })

+ 3 - 1
node_modules/uview-plus/components/u-tabbar/tabbar.js

@@ -17,6 +17,8 @@ export default {
         activeColor: '#1989fa',
         inactiveColor: '#7d7e80',
         fixed: true,
-        placeholder: true
+        placeholder: true,
+        borderColor: '',
+        backgroundColor: ''
     }
 }

+ 7 - 0
node_modules/uview-plus/components/u-tabbar/u-tabbar.vue

@@ -42,6 +42,7 @@
 	 * @property {String}			inactiveColor		未选中标签的颜色(默认 '#7d7e80' )
 	 * @property {Boolean}			fixed				是否固定在底部(默认 true )
 	 * @property {Boolean}			placeholder			fixed定位固定在底部时,是否生成一个等高元素防止塌陷(默认 true )
+	 * @property {String}			backgroundColor		背景色(默认 '#ffffff' )
 	 * @property {Object}			customStyle			定义需要用到的外部样式
 	 * 
 	 * @example <u-tabbar :value="value2" :placeholder="false" @change="name => value2 = name" :fixed="false" :safeAreaInsetBottom="false"><u-tabbar-item text="首页" icon="home" dot ></u-tabbar-item></u-tabbar>
@@ -59,6 +60,12 @@
 				const style = {
 					zIndex: this.zIndex
 				}
+				if (this.borderColor) {
+					style.borderColor = this.borderColor
+				}
+				if (this.backgroundColor) {
+					style.backgroundColor = this.backgroundColor
+				}
 				// 合并来自父组件的customStyle样式
 				return deepMerge(style, addStyle(this.customStyle))
 			},

+ 3 - 2
node_modules/uview-plus/components/u-toolbar/toolbar.js

@@ -7,12 +7,13 @@
  * @lastTime     : 2021-08-20 17:24:55
  * @FilePath     : /u-view2.0/uview-ui/libs/config/props/toolbar.js
  */
+import { t } from '../../libs/i18n'
 export default {
     // toolbar 组件
     toolbar: {
         show: true,
-        cancelText: '取消',
-        confirmText: '确认',
+        cancelText: t('up.common.cancel'),
+        confirmText: t('up.common.confirm'),
         cancelColor: '#909193',
         confirmColor: '',
         title: ''

+ 5 - 0
node_modules/uview-plus/components/u-tooltip/props.js

@@ -67,5 +67,10 @@ export const props = defineMixin({
             type: String,
             default: () => defProps.tooltip.triggerMode
         },
+        // 强制定位
+        forcePosition: {
+            type: Object,
+            default: () => defProps.tooltip.forcePosition
+        }
     }
 })

+ 2 - 1
node_modules/uview-plus/components/u-tooltip/tooltip.js

@@ -22,6 +22,7 @@ export default {
         overlay: true,
         showToast: true,
         popupBgColor: '',
-        triggerMode: 'longpress'
+        triggerMode: 'longpress',
+        forcePosition: {}
     }
 }

+ 42 - 35
node_modules/uview-plus/components/u-tooltip/u-tooltip.vue

@@ -29,9 +29,9 @@
 				duration="300"
 				:customStyle="{
 					position: 'absolute', 
-					top: addUnit(tooltipTop),
+					top: addUnit(tooltipTop, 'px'),
 					zIndex: zIndex,
-					...tooltipStyle
+					...tooltipStyleCpu
 				}"
 			>
 				<view
@@ -145,7 +145,7 @@
 					left: 0
 				},
 				// 文本的位置信息
-				textInfo: {
+				triggerInfo: {
 					width: 0,
 					left: 0
 				},
@@ -155,13 +155,14 @@
 				screenGap: 12,
 				// 三角形指示器的宽高,由于对元素进行了角度旋转,精确计算指示器位置时,需要用到其尺寸信息
 				indicatorWidth: 14,
-				tooltipStyle: {}
+				tooltipStyle: {},
+				calcReacted: false
 			}
 		},
 		watch: {
 			async propsChange() {
 				await this.getElRect()
-				this.getTooltipStyle();
+				// this.getTooltipStyle();
 			}
 		},
 		computed: {
@@ -170,29 +171,19 @@
 			propsChange() {
 				return [this.text, this.buttons]
 			},
-			
-		},
-		mounted() {
-			this.init()
-		},
-		emits: ["click"],
-		methods: {
-			addStyle,
-			addUnit,
-			async init() {
-				await this.getElRect()
-				this.getTooltipStyle();
-			},
 			// 计算气泡和指示器的位置信息
-			getTooltipStyle() {
+			tooltipStyleCpu() {
+				if (!this.calcReacted) {
+					return {}
+				}
 				const style = {},
 					sysInfo = getWindowInfo()
 				if (this.direction === 'left') {
 					// 右侧显示逻辑
 					style.transform = ``
 					// 垂直居中对齐
-					style.top = '-' + addUnit((this.tooltipInfo.height - this.indicatorWidth) / 2, 'px')
-					style.right = addUnit(this.textInfo.width + this.indicatorWidth, 'px')
+					style.top = '-' + addUnit((this.triggerInfo.height - this.tooltipInfo.height) / 2, 'px')
+					style.right = addUnit(this.triggerInfo.width + this.indicatorWidth, 'px')
 					this.indicatorStyle = {}
 					this.indicatorStyle.right = '-4px'
 					this.indicatorStyle.top = addUnit((this.tooltipInfo.height - this.indicatorWidth) / 2, 'px')
@@ -200,27 +191,27 @@
 					// 右侧显示逻辑
 					style.transform = ``
 					// 垂直居中对齐
-					style.top = addUnit((this.textInfo.height - this.tooltipInfo.height) / 2, 'px')
-					style.left = addUnit(this.textInfo.width + this.indicatorWidth, 'px')
+					style.top = '-' + addUnit((this.triggerInfo.height - this.tooltipInfo.height) / 2, 'px')
+					style.left = addUnit(this.triggerInfo.width + this.indicatorWidth, 'px')
 					this.indicatorStyle = {}
 					this.indicatorStyle.left = '-4px'
-					this.indicatorStyle.top = addUnit((this.textInfo.height - this.indicatorWidth) / 2, 'px')
+					this.indicatorStyle.top = addUnit((this.triggerInfo.height - this.indicatorWidth) / 2, 'px')
 				} else if (this.direction === 'top' || this.direction === 'bottom') { 
 					style.transform = `translateY(${this.direction === 'top' ? '-100%' : '100%'})`
-					if (this.tooltipInfo.width / 2 > this.textInfo.left + this.textInfo.width / 2 - this.screenGap) {
+					if (this.tooltipInfo.width / 2 > this.triggerInfo.left + this.triggerInfo.width / 2 - this.screenGap) {
 						this.indicatorStyle = {}
-						style.left = `-${addUnit(this.textInfo.left - this.screenGap)}`
-						this.indicatorStyle.left = addUnit(this.textInfo.width / 2 - getPx(style.left) - this.indicatorWidth /
+						style.left = `-${addUnit(this.triggerInfo.left - this.screenGap)}`
+						this.indicatorStyle.left = addUnit(this.triggerInfo.width / 2 - getPx(style.left) - this.indicatorWidth /
 							2, 'px')
-					} else if (this.tooltipInfo.width / 2 > sysInfo.windowWidth - this.textInfo.right + this.textInfo.width / 2 -
+					} else if (this.tooltipInfo.width / 2 > sysInfo.windowWidth - this.triggerInfo.right + this.triggerInfo.width / 2 -
 						this.screenGap) {
 						this.indicatorStyle = {}
-						style.right = `-${addUnit(sysInfo.windowWidth - this.textInfo.right - this.screenGap)}`
-						this.indicatorStyle.right = addUnit(this.textInfo.width / 2 - getPx(style.right) - this
+						style.right = `-${addUnit(sysInfo.windowWidth - this.triggerInfo.right - this.screenGap)}`
+						this.indicatorStyle.right = addUnit(this.triggerInfo.width / 2 - getPx(style.right) - this
 							.indicatorWidth / 2)
 					} else {
-						const left = Math.abs(this.textInfo.width / 2 - this.tooltipInfo.width / 2)
-						style.left = this.textInfo.width > this.tooltipInfo.width ? addUnit(left) : -addUnit(left)
+						const left = Math.abs(this.triggerInfo.width / 2 - this.tooltipInfo.width / 2)
+						style.left = this.triggerInfo.width > this.tooltipInfo.width ? addUnit(left) : -addUnit(left)
 						this.indicatorStyle = {}
 					}
 					if (this.direction === 'top') {
@@ -231,8 +222,21 @@
 						this.indicatorStyle.top = '-4px'
 					}
 				}
-				this.tooltipStyle = style
-				return style
+				let styleMerge = {...style, ...this.forcePosition}
+				this.tooltipStyle = styleMerge
+				return styleMerge
+			}
+		},
+		mounted() {
+			this.init()
+		},
+		emits: ["click"],
+		methods: {
+			addStyle,
+			addUnit,
+			async init() {
+				await this.getElRect()
+				// this.getTooltipStyle();
 			},
 			// 点击触发事件
 			async clickHander() {
@@ -292,7 +296,10 @@
 						this.tooltipInfo = await this.queryRect(this.tooltipId)
 						// 获取气泡尺寸之后,将其隐藏,为了让下次切换气泡显示与隐藏时,有淡入淡出的效果
 						this.showTooltip = false
-						this.textInfo = await this.queryRect(this.textId)
+						this.triggerInfo = await this.queryRect(this.textId)
+						sleep(500).then(() => {
+							this.calcReacted = true
+						})
 						resolve()
 					})
 				})

+ 7 - 5
node_modules/uview-plus/components/u-upload/u-upload.vue

@@ -65,7 +65,7 @@
 						    :name="item.isVideo || (item.type && item.type === 'video') ? 'movie' : 'folder'"
 						></up-icon>
 						<text class="u-upload__wrap__preview__other__text">
-							{{item.isVideo || (item.type && item.type === 'video') ? item.name || '视频' : item.name || '文件'}}
+							{{item.isVideo || (item.type && item.type === 'video') ? item.name || t("up.common.video") : item.name || t("up.common.file")}}
 						</text>
 					</view>
 					<view
@@ -194,6 +194,7 @@
 	import { mixin } from '../../libs/mixin/mixin';
 	import { addStyle, addUnit, toast } from '../../libs/function/index';
 	import test from '../../libs/function/test';
+	import { t } from '../../libs/i18n'
 	/**
 	 * upload 上传
 	 * @description 该组件用于上传图片场景
@@ -270,6 +271,7 @@
 		emits: ['error', 'beforeRead', 'oversize', 'afterRead', 'delete', 'clickPreview', 'update:fileList', 'afterAutoUpload'],
 		// #endif
 		methods: {
+			t,
 			addUnit,
 			addStyle,
 			videoErrorCallback() {},
@@ -422,7 +424,7 @@
 					file.size > maxSize;
 				if (oversize) {
 					uni.showToast({
-						title: '超过大小限制'
+						title: t("up.upload.sizeExceed")
 					})
 					this.$emit('oversize', Object.assign({
 						file
@@ -438,7 +440,7 @@
 						this.fileList.push({
 							...item,
 							status: 'uploading',
-							message: '上传中',
+							message: t("up.upload.uploading"),
 							progress: 0
 						});
 					});
@@ -639,7 +641,7 @@
                     urls: urls,
                     current: current,
 					fail() {
-						toast('预览图片失败')
+						toast(t("up.upload.previewImageFail"))
 					},
 				});
 			},
@@ -673,7 +675,7 @@
 					sources: sources,
 					current: current,
 					fail() {
-						toast('预览视频失败')
+						toast(t("up.upload.previewVideoFail"))
 					},
 				});
 				// #endif

+ 5 - 2
node_modules/uview-plus/index.js

@@ -41,9 +41,12 @@ import http from './libs/function/http.js'
 // fontUtil
 import fontUtil from './components/u-icon/util.js';
 
+// i18n
+import i18n, { t } from './libs/i18n/index.js'
+
 // 导出
 let themeType = ['primary', 'success', 'error', 'warning', 'info'];
-export { route, http, debounce, throttle, calc, digit, platform, themeType, mixin, mpMixin, props, color, test, zIndex, fontUtil }
+export { route, http, debounce, throttle, calc, digit, platform, themeType, mixin, mpMixin, props, color, test, zIndex, fontUtil, i18n ,t}
 export * from './libs/function/index.js'
 export * from './libs/function/colorGradient.js'
 
@@ -79,7 +82,7 @@ const $u = {
 	calc,
     mixin,
     mpMixin,
-    props,
+    // props,
     ...index,
     color,
     platform

+ 54 - 0
node_modules/uview-plus/libs/i18n/index.js

@@ -0,0 +1,54 @@
+import zhHans from './locales/zh-Hans.json'
+import zhHant from './locales/zh-Hant.json'
+import en from './locales/en.json'
+import es from './locales/es.json'
+import fr from './locales/fr.json'
+import de from './locales/de.json'
+import ko from './locales/ko.json'
+import ja from './locales/ja.json'
+import ru from './locales/ru.json'
+
+let settings = {
+    lang: uni.getLocale(),
+    locales: {
+        en,
+        es,
+        fr,
+        de,
+        ko,
+        ja,
+        ru,
+        'zh-Hant': zhHant,
+        'zh-Hans': zhHans
+    }
+};
+
+uni.onLocaleChange((locale) => {
+    settings.lang = locale;
+})
+
+/**
+ * 多语言方法
+ */
+export function t(value, params = {}) {
+    // console.log(settings.locales[settings.lang])
+    if (value) {
+        let lang = settings.lang
+        if (!settings.locales[settings.lang]) {
+            lang = 'zh-Hans'
+        }
+        let result = settings.locales[lang][value] || value;
+        // 替换{xxx}格式的变量
+        Object.keys(params).forEach(key => {
+            const reg = new RegExp(`{${key}}`, 'g');
+            result = result.replace(reg, params[key]);
+        });
+        return result;
+    } else {
+        return value;
+    }
+}
+
+export default {
+    settings: settings
+}

+ 80 - 0
node_modules/uview-plus/libs/i18n/locales/de.json

@@ -0,0 +1,80 @@
+{
+    "up.common.cancel": "Abbrechen",
+    "up.common.confirm": "Bestätigen",
+    "up.common.start": "Start",
+    "up.common.end": "Ende",
+    "up.common.stop": "Stopp",
+    "up.common.copy": "Kopieren",
+    "up.common.none": "Keine",
+    "up.common.tip": "Hinweis",
+    "up.common.success": "Erfolg",
+    "up.common.fail": "Fehlgeschlagen",
+    "up.common.close": "Schließen",
+    "up.common.preview": "Vorschau",
+    "up.common.re-select": "Erneut auswählen",
+    "up.common.rotate": "Drehen",
+    "up.common.pleaseChoose": "Bitte wählen",
+    "up.common.loading": "Laden",
+    "up.common.loading2": "Wird geladen",
+    "up.common.inOperation": "In Bearbeitung",
+    "up.common.settings": "Einstellungen",
+    "up.common.retry": "Wiederholen",
+    "up.common.search": "Suchen",
+    "up.common.more": "Mehr",
+    "up.common.video": "Video",
+    "up.common.file": "Datei",
+    "up.week.one": "Mo",
+    "up.week.two": "Di",
+    "up.week.three": "Mi",
+    "up.week.four": "Do",
+    "up.week.five": "Fr",
+    "up.week.six": "Sa",
+    "up.week.seven": "So",
+    "up.barcode.error": "Barcode-Generierung fehlgeschlagen",
+    "up.calendar.chooseDates": "Datumsauswahl",
+    "up.calendar.disabled": "Dieses Datum ist deaktiviert",
+    "up.calendar.daysExceed": "Die Anzahl der ausgewählten Tage darf {days} Tage nicht überschreiten",
+    "up.cityLocate.locateCity": "Stadt lokalisieren",
+    "up.cityLocate.fail": "Lokalisierung fehlgeschlagen, bitte klicken Sie zum Wiederholen.",
+    "up.cityLocate.locating": "Lokalisierung läuft",
+    "up.code.send": "Bestätigungscode erhalten",
+    "up.code.resendAfter": "Erneut senden in X Sekunden",
+    "up.code.resend": "Erneut senden",
+    "up.cropper.emptyWidhtOrHeight": "Breite oder Höhe des Zuschneidebereichs nicht festgelegt",
+    "up.empty.car": "Warenkorb ist leer",
+    "up.empty.page": "Seite existiert nicht",
+    "up.empty.search": "Keine Suchergebnisse",
+    "up.empty.address": "Keine Lieferadresse",
+    "up.empty.wifi": "Kein WLAN",
+    "up.empty.order": "Bestellungen sind leer",
+    "up.empty.coupon": "Keine Gutscheine",
+    "up.empty.favor": "Keine Favoriten",
+    "up.empty.permission": "Keine Berechtigung",
+    "up.empty.history": "Kein Verlauf",
+    "up.empty.news": "Keine Nachrichtenliste",
+    "up.empty.message": "Nachrichtenliste ist leer",
+    "up.empty.list": "Liste ist leer",
+    "up.empty.data": "Daten sind leer",
+    "up.empty.comment": "Keine Kommentare",
+    "up.link.copyed": "Link kopiert, bitte im Browser öffnen",
+    "up.loadmoe.loadmore": "Mehr laden",
+    "up.loadmoe.nomore": "Keine weiteren Daten",
+    "up.noNetwork.text": "Ups, Netzwerksignal verloren",
+    "up.noNetwork.pleaseCheck": "Bitte überprüfen Sie das Netzwerk oder gehen Sie zu",
+    "up.noNetwork.connect": "Netzwerk verbunden",
+    "up.noNetwork.disconnect": "Keine Netzwerkverbindung",
+    "up.pagination.previous": "Vorherige Seite",
+    "up.pagination.next": "Nächste Seite",
+    "up.pullRefresh.pull": "Zum Aktualisieren nach unten ziehen",
+    "up.pullRefresh.release": "Loslassen zum Aktualisieren",
+    "up.pullRefresh.refreshing": "Aktualisierung läuft",
+    "up.readMore.expand": "Erweitern zum vollständigen Lesen",
+    "up.readMore.fold": "Einklappen",
+    "up.search.placeholder": "Bitte Schlüsselwort eingeben",
+    "up.signature.penSize": "Strichstärke",
+    "up.signature.penColor": "Strichfarbe",
+    "up.upload.sizeExceed": "Größenbegrenzung überschritten",
+    "up.upload.uploading": "Upload läuft",
+    "up.upload.previewImageFail": "Bildvorschau fehlgeschlagen",
+    "up.upload.previewVideoFail": "Videovorschau fehlgeschlagen"
+}

+ 80 - 0
node_modules/uview-plus/libs/i18n/locales/en.json

@@ -0,0 +1,80 @@
+{
+    "up.common.cancel": "Cancel",
+    "up.common.confirm": "Confirm",
+    "up.common.start": "Start",
+    "up.common.end": "End",
+    "up.common.stop": "Stop",
+    "up.common.copy": "Copy",
+    "up.common.none": "None",
+    "up.common.tip": "Tip",
+    "up.common.success": "Success",
+    "up.common.fail": "Fail",
+    "up.common.close": "Close",
+    "up.common.preview": "Preview",
+    "up.common.re-select": "Re-select",
+    "up.common.rotate": "Rotate",
+    "up.common.pleaseChoose": "Please choose",
+    "up.common.loading": "Loading",
+    "up.common.loading2": "Loading",
+    "up.common.inOperation": "In operation",
+    "up.common.settings": "Settings",
+    "up.common.retry": "Retry",
+    "up.common.search": "Search",
+    "up.common.more": "More",
+    "up.common.video": "Video",
+    "up.common.file": "File",
+    "up.week.one": "Mon",
+    "up.week.two": "Tue",
+    "up.week.three": "Wed",
+    "up.week.four": "Thu",
+    "up.week.five": "Fri",
+    "up.week.six": "Sat",
+    "up.week.seven": "Sun",
+    "up.barcode.error": "Failed to generate barcode",
+    "up.calendar.chooseDates": "Date selection",
+    "up.calendar.disabled": "This date is disabled",
+    "up.calendar.daysExceed": "The number of selected days cannot exceed {days} days",
+    "up.cityLocate.locateCity": "Locate city",
+    "up.cityLocate.fail": "Location failed, please click to retry.",
+    "up.cityLocate.locating": "Locating",
+    "up.code.send": "Get verification code",
+    "up.code.resendAfter": "Resend after X seconds",
+    "up.code.resend": "Resend",
+    "up.cropper.emptyWidhtOrHeight": "The width or height of the cropping box is not set",
+    "up.empty.car": "Shopping cart is empty",
+    "up.empty.page": "Page not found",
+    "up.empty.search": "No search results",
+    "up.empty.address": "No shipping address",
+    "up.empty.wifi": "No WiFi",
+    "up.empty.order": "Order is empty",
+    "up.empty.coupon": "No coupons",
+    "up.empty.favor": "No favorites",
+    "up.empty.permission": "No permission",
+    "up.empty.history": "No history",
+    "up.empty.news": "No news list",
+    "up.empty.message": "Message list is empty",
+    "up.empty.list": "List is empty",
+    "up.empty.data": "Data is empty",
+    "up.empty.comment": "No comments",
+    "up.link.copyed": "Link copied, please open in browser",
+    "up.loadmoe.loadmore": "Load more",
+    "up.loadmoe.nomore": "No more",
+    "up.noNetwork.text": "Oops, network signal lost",
+    "up.noNetwork.pleaseCheck": "Please check the network, or go to",
+    "up.noNetwork.connect": "Network connected",
+    "up.noNetwork.disconnect": "No network connection",
+    "up.pagination.previous": "Previous",
+    "up.pagination.next": "Next",
+    "up.pullRefresh.pull": "Pull to refresh",
+    "up.pullRefresh.release": "Release to refresh",
+    "up.pullRefresh.refreshing": "Refreshing",
+    "up.readMore.expand": "Expand to read more",
+    "up.readMore.fold": "Collapse",
+    "up.search.placeholder": "Please enter keywords",
+    "up.signature.penSize": "Stroke size",
+    "up.signature.penColor": "Stroke color",
+    "up.upload.sizeExceed": "Size limit exceeded",
+    "up.upload.uploading": "Uploading",
+    "up.upload.previewImageFail": "Failed to preview image",
+    "up.upload.previewVideoFail": "Failed to preview video"
+}

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor