瀏覽代碼

feat(uview-ui): 更新组件库至v1.8.8并修复多个组件问题

- 升级uView UI至v1.8.8版本,更新相关配置和依赖
ylong 3 天之前
父節點
當前提交
2fd11b0beb
共有 45 個文件被更改,包括 2064 次插入839 次删除
  1. 4 0
      .trae/rules/trae-project.md
  2. 16 1
      api/modules/mall.js
  3. 209 178
      pages-car/components/cart-item.vue
  4. 203 0
      pages-car/components/condition-popup.vue
  5. 409 293
      pages-car/pages/index.vue
  6. 8 3
      pages/cart/index.vue
  7. 2 2
      uview-ui/LICENSE
  8. 49 1
      uview-ui/README.md
  9. 1 1
      uview-ui/components/u-badge/u-badge.vue
  10. 47 117
      uview-ui/components/u-button/u-button.vue
  11. 643 0
      uview-ui/components/u-calendar/u-calendar.vue
  12. 1 1
      uview-ui/components/u-card/u-card.vue
  13. 2 0
      uview-ui/components/u-dropdown/u-dropdown.vue
  14. 2 1
      uview-ui/components/u-image/u-image.vue
  15. 37 37
      uview-ui/components/u-input/u-input.vue
  16. 3 3
      uview-ui/components/u-line-progress/u-line-progress.vue
  17. 1 1
      uview-ui/components/u-loading/u-loading.vue
  18. 2 2
      uview-ui/components/u-message-input/u-message-input.vue
  19. 1 1
      uview-ui/components/u-modal/u-modal.vue
  20. 2 2
      uview-ui/components/u-notice-bar/u-notice-bar.vue
  21. 4 8
      uview-ui/components/u-number-box/u-number-box.vue
  22. 0 6
      uview-ui/components/u-popup/u-popup.vue
  23. 1 1
      uview-ui/components/u-radio-group/u-radio-group.vue
  24. 1 1
      uview-ui/components/u-radio/u-radio.vue
  25. 27 35
      uview-ui/components/u-search/u-search.vue
  26. 6 3
      uview-ui/components/u-select/u-select.vue
  27. 2 4
      uview-ui/components/u-slider/u-slider.vue
  28. 255 0
      uview-ui/components/u-swipe-action/u-swipe-action.vue
  29. 3 3
      uview-ui/components/u-tabs-swiper/u-tabs-swiper.vue
  30. 4 4
      uview-ui/components/u-tabs/u-tabs.vue
  31. 1 1
      uview-ui/components/u-th/u-th.vue
  32. 1 1
      uview-ui/components/u-toast/u-toast.vue
  33. 5 7
      uview-ui/components/u-upload/u-upload.vue
  34. 14 22
      uview-ui/components/u-waterfall/u-waterfall.vue
  35. 0 0
      uview-ui/components/uview-v1/uview-v1.vue
  36. 3 3
      uview-ui/libs/config/config.js
  37. 28 16
      uview-ui/libs/function/deepClone.js
  38. 18 19
      uview-ui/libs/function/deepMerge.js
  39. 2 2
      uview-ui/libs/function/guid.js
  40. 5 5
      uview-ui/libs/function/route.js
  41. 1 1
      uview-ui/libs/mixin/mixin.js
  42. 0 2
      uview-ui/libs/request/index.js
  43. 1 1
      uview-ui/libs/util/async-validator.js
  44. 37 37
      uview-ui/package.json
  45. 3 13
      uview-ui/theme.scss

+ 4 - 0
.trae/rules/trae-project.md

@@ -50,6 +50,10 @@ alwaysApply: true
 - **Units**: `rpx`
 - **Colors**: Use variables from `theme.scss` if available, or project standard colors (Primary: `#38C148` or similar based on `pages.json`).
 
+### 3.5 Documentation & Comments
+- **Language**: All code comments MUST be in Chinese (Simplified).
+- **Format**: Clear and concise explanations for complex logic.
+
 ## 4. Code Generation Instructions
 - **When creating pages**: Check `pages.json` subpackage structure first.
 - **When adding features**:

+ 16 - 1
api/modules/mall.js

@@ -45,7 +45,7 @@ export const useMallApi = (Vue, vm) => {
 		addCartAjax: (json) => vm.$u.post('/client/shop_cart/addCart',json),
 
 		// 新版加入购物车
-		addShopCartAjax: (json) => vm.$u.post('/api/token/shop/cart/add',json),
+		addShopCartAjax: (json) => vm.$u.post('/token/shop/cart/add',json),
 		
 		// 购物车列表
 		getCartListAjax: (page) => vm.$u.post('/client/shop_cart/getCartList',{page:page}),
@@ -162,5 +162,20 @@ export const useMallApi = (Vue, vm) => {
 		
 		// 用户搜索 (结果列表)
 		getSearchKeywordAjax: (params) => vm.$u.get('/token/shop/user/searchKeyword', params),
+		
+		// 购物车列表
+		getShopCartListAjax: (params) => vm.$u.post('/token/shop/cart/list', params),
+		
+		// 修改购物车商品品相
+		updateCartConditionAjax: (params) => vm.$u.post('/token/shop/cart/updateConditionType', params),
+		
+		// 修改购物车商品数量
+		updateCartNumAjax: (params) => vm.$u.post('/token/shop/cart/updateNum', params),
+		
+		// 删除购物车商品
+		deleteCartItemAjax: (id) => vm.$u.post(`/token/shop/cart/deleteItem/${id}`),
+		
+		// 清空购物车
+		clearCartAjax: () => vm.$u.post('/token/shop/cart/clearCart'),
 	}
 }

+ 209 - 178
pages-car/components/cart-item.vue

@@ -2,8 +2,8 @@
     <view class="cart-item">
         <!-- 复选框 -->
         <view class="checkbox-box">
-            <u-checkbox v-model="item.checked" shape="circle" active-color="#38C148"
-                @change="onCheckChange" :disabled="!isValid"></u-checkbox>
+            <u-checkbox v-model="item.checked" shape="circle" active-color="#38C148" @change="onCheckChange"
+                :disabled="!isValid" style="width:100%"></u-checkbox>
         </view>
 
         <!-- 商品图片 -->
@@ -14,30 +14,30 @@
                 <text>{{ statusText }}</text>
             </view>
             <!-- 库存紧张遮罩 -->
-            <view class="stock-mask" v-if="isValid && item.stockWarning">
+            <view class="stock-mask" v-if="isValid && item.stockStatus === 2">
                 <text>库存紧张</text>
             </view>
         </view>
 
         <!-- 商品信息 -->
         <view class="info-box">
-            <view class="title">{{ item.title }}</view>
+            <view class="title" style="font-weight:500">{{ item.bookName }}</view>
 
             <!-- 标签和品相区域 -->
             <view class="tags-quality-row">
-                <!-- 品相选择 (模拟) -->
-                <view class="quality-selector">
-                    <text>品相:{{ item.quality || '中等' }}</text>
+                <!-- 品相选择 -->
+                <view class="quality-selector" @click.stop="handleSelectCondition">
+                    <text>品相:{{ conditionName }}</text>
                     <u-icon name="arrow-down" size="20" color="#999" style="margin-left: 4rpx;"></u-icon>
                 </view>
 
                 <!-- 已降价标签 -->
-                <view class="tag green-tag" v-if="item.reducedAmount > 0">
-                    <text>已降¥{{ item.reducedAmount }}</text>
+                <view class="tag green-tag" v-if="item.currReduceMoney > 0">
+                    <text>已降¥{{ item.currReduceMoney }}</text>
                 </view>
                 <!-- 可降价标签 -->
-                <view class="tag orange-tag" v-if="item.canReduceAmount > 0" @click.stop="handleReduce">
-                    <text>可降¥{{ item.canReduceAmount }}</text>
+                <view class="tag orange-tag" v-if="canReduce" @click.stop="handleReduce">
+                    <text>可降¥{{ item.reduceMoney }}</text>
                 </view>
             </view>
 
@@ -45,16 +45,16 @@
             <view class="bottom-row">
                 <view class="price-box">
                     <text class="symbol">¥</text>
-                    <text class="amount">{{ item.price }}</text>
+                    <text class="amount">{{ item.productPrice }}</text>
                 </view>
 
                 <!-- 去减钱按钮 -->
-                <view class="reduce-btn" v-if="item.canReduceAmount > 0" @click.stop="handleReduce">
+                <view class="reduce-btn" v-if="canReduce" @click.stop="handleReduce">
                     <text>去减钱</text>
                 </view>
 
                 <!-- 数量加减 -->
-                <u-number-box v-model="item.num" :min="1" :max="item.stock || 99" :disabled="!isValid"
+                <u-number-box v-model="item.quantity" :min="1" :max="item.availableStock || 99" :disabled="!isValid"
                     @change="onNumChange" :size="24"></u-number-box>
             </view>
         </view>
@@ -62,209 +62,240 @@
 </template>
 
 <script>
-export default {
-    props: {
-        item: {
-            type: Object,
-            default: () => ({})
-        }
-    },
-    computed: {
-        isValid() {
-            // status: 1-正常, 0-无效/无库存
-            return this.item.status === 1 && this.item.stock > 0;
+    export default {
+        props: {
+            item: {
+                type: Object,
+                default: () => ({})
+            }
         },
-        statusText() {
-            if (this.item.stock <= 0) return '暂无库存';
-            if (this.item.status !== 1) return '失效';
-            return '';
-        }
-    },
-    methods: {
-        onCheckChange(val) {
-            this.$emit('check', { id: this.item.id, checked: val.value });
+        data() {
+            return {
+                conditionMap: {
+                    1: '良好',
+                    2: '中等',
+                    3: '次品',
+                    4: '全新'
+                }
+            };
         },
-        onNumChange(val) {
-            this.$emit('changeNum', { id: this.item.id, num: val.value });
+        computed: {
+            conditionName() {
+                // 如果是数字,映射;如果是字符串,直接显示
+                return this.conditionMap[this.item.conditionType] || this.item.conditionType || '中等';
+            },
+            isValid() {
+                // stockStatus: 1-有库存 2-库存紧张 3-暂无库存
+                return this.item.stockStatus !== 3 && this.item.availableStock > 0;
+            },
+            statusText() {
+                if (this.item.stockStatus === 3 || this.item.availableStock <= 0) return '暂无库存';
+                // 如果后端定义了其他状态,你可能需要处理它们
+                return '';
+            },
+            canReduce() {
+                // 逻辑:如果成功减价次数少于允许的最大次数,且 reduceMoney > 0
+                // 或者简单地如果 reduceMoney > 0 且我们还没有达到限制?
+                // 假设 reduceMoney 是单价减少额。
+                return (this.item.reduceNum < this.item.maxReduceNum) && (this.item.reduceMoney > 0);
+            }
         },
-        handleReduce() {
-            this.$emit('reduce', this.item);
+        methods: {
+            onCheckChange(val) {
+                this.$emit('check', { id: this.item.id, checked: val.value });
+            },
+            onNumChange(val) {
+                this.$emit('changeNum', { id: this.item.id, num: val.value });
+            },
+            handleReduce() {
+                this.$emit('reduce', this.item);
+            },
+            handleSelectCondition() {
+                this.$emit('selectCondition', this.item);
+            }
         }
-    }
-}
+    };
 </script>
 
-<style lang="scss" scoped>
-.cart-item {
-    display: flex;
-    align-items: center;
-    background-color: #fff;
-    padding: 30rpx 20rpx;
-    border-radius: 16rpx;
-    margin-bottom: 20rpx;
-
-    .checkbox-box {
-        width: 50rpx;
+<style lang="scss">
+    .cart-item {
         display: flex;
-        justify-content: center;
-        flex-shrink: 0;
+        align-items: center;
+        background-color: #fff;
+        padding: 20rpx 20rpx;
+        border-radius: 16rpx;
+        border-bottom: 1rpx dashed #f5f5f5;
+
+        .checkbox-box {
+            width: 40rpx;
+            display: flex;
+            justify-content: center;
+            flex-shrink: 0;
 
-        &.disabled-checkbox {
-            .circle {
-                width: 34rpx;
-                height: 34rpx;
-                border-radius: 50%;
-                background-color: #e5e5e5;
+            &.disabled-checkbox {
+                .circle {
+                    width: 34rpx;
+                    height: 34rpx;
+                    border-radius: 50%;
+                    background-color: #e5e5e5;
+                }
             }
         }
-    }
-
-    .image-wrapper {
-        position: relative;
-        width: 150rpx;
-        height: 200rpx;
-        border-radius: 8rpx;
-        overflow: hidden;
-        flex-shrink: 0;
-        margin-right: 20rpx;
 
-        .book-cover {
-            width: 100%;
-            height: 100%;
-        }
+        .image-wrapper {
+            position: relative;
+            width: 150rpx;
+            height: 200rpx;
+            border-radius: 8rpx;
+            overflow: hidden;
+            flex-shrink: 0;
+            margin-right: 20rpx;
 
-        .status-mask {
-            position: absolute;
-            bottom: 0;
-            left: 0;
-            width: 100%;
-            height: 40rpx;
-            background-color: rgba(0, 0, 0, 0.6);
-            display: flex;
-            justify-content: center;
-            align-items: center;
-            
-            text {
-                color: #fff;
-                font-size: 20rpx;
+            .book-cover {
+                width: 100%;
+                height: 100%;
             }
-        }
 
-        .stock-mask {
-            position: absolute;
-            bottom: 0;
-            left: 0;
-            width: 100%;
-            height: 40rpx;
-            background-color: #38C148; // Green background
-            opacity: 0.8;
-            display: flex;
-            justify-content: center;
-            align-items: center;
-            
-            text {
-                color: #fff;
-                font-size: 20rpx;
+            .status-mask {
+                position: absolute;
+                bottom: 0;
+                left: 0;
+                width: 100%;
+                height: 40rpx;
+                background-color: rgba(0, 0, 0, 0.6);
+                display: flex;
+                justify-content: center;
+                align-items: center;
+
+                text {
+                    color: #fff;
+                    font-size: 20rpx;
+                }
             }
-        }
-    }
 
-    .info-box {
-        flex: 1;
-        height: 220rpx;
-        display: flex;
-        flex-direction: column;
-        justify-content: space-between;
+            .stock-mask {
+                position: absolute;
+                bottom: 0;
+                left: 0;
+                width: 100%;
+                height: 40rpx;
+                background-color: #38C148; // 绿色背景
+                opacity: 0.8;
+                display: flex;
+                justify-content: center;
+                align-items: center;
 
-        .title {
-            font-size: 28rpx;
-            color: #333;
-            line-height: 1.4;
-            display: -webkit-box;
-            -webkit-box-orient: vertical;
-            -webkit-line-clamp: 2;
-            overflow: hidden;
-            font-weight: 500;
+                text {
+                    color: #fff;
+                    font-size: 20rpx;
+                }
+            }
         }
 
-        .tags-quality-row {
+        .info-box {
+            flex: 1;
+            height: 220rpx;
             display: flex;
-            flex-wrap: wrap;
+            flex-direction: column;
             justify-content: space-between;
-            align-items: center;
-            margin: 20rpx 0 0 0;
 
-            .tag {
-                font-size: 22rpx;
-                padding: 2rpx 8rpx;
-                border-radius: 4rpx;
-                margin-right: 10rpx;
-                border: 1rpx solid transparent;
-                margin-bottom: 6rpx;
+            .title-row {
+                display: flex;
+                justify-content: space-between;
+                align-items: flex-start;
 
-                &.green-tag {
-                    color: #38C148;
-                    border-color: #38C148;
-                    background-color: rgba(56, 193, 72, 0.1);
+                .title {
+                    flex: 1;
+                    font-size: 28rpx;
+                    color: #333;
+                    line-height: 1.4;
+                    display: -webkit-box;
+                    -webkit-box-orient: vertical;
+                    -webkit-line-clamp: 2;
+                    overflow: hidden;
+                    font-weight: 600;
+                    margin-right: 10rpx;
                 }
+            }
+
+            .tags-quality-row {
+                display: flex;
+                flex-wrap: wrap;
+                justify-content: space-between;
+                align-items: center;
+                margin: 20rpx 0 0 0;
+
+                .tag {
+                    font-size: 22rpx;
+                    padding: 2rpx 8rpx;
+                    border-radius: 4rpx;
+                    margin-right: 10rpx;
+                    border: 1rpx solid transparent;
+                    margin-bottom: 6rpx;
+
+                    &.green-tag {
+                        color: #38C148;
+                        border-color: #38C148;
+                        background-color: rgba(56, 193, 72, 0.1);
+                    }
 
-                &.orange-tag {
-                    color: #ff6a00;
-                    border-color: #ff6a00;
-                    background-color: rgba(255, 106, 0, 0.1);
+                    &.orange-tag {
+                        color: #ff6a00;
+                        border-color: #ff6a00;
+                        background-color: rgba(255, 106, 0, 0.1);
+                    }
+
+                    &.red-text-tag {
+                        background-color: #38C148;
+                        color: #fff;
+                        border: none;
+                    }
                 }
 
-                &.red-text-tag {
-                    background-color: #38C148;
-                    color: #fff;
-                    border: none;
+                .quality-selector {
+                    display: flex;
+                    align-items: center;
+                    font-size: 24rpx;
+                    color: #999;
+                    background-color: #f5f5f5;
+                    padding: 4rpx 12rpx;
+                    border-radius: 4rpx;
+                    width: fit-content;
+                    margin-bottom: 6rpx;
+                    margin-right: 10rpx;
+                    order: -1; // 移到前面
                 }
             }
 
-            .quality-selector {
+            .bottom-row {
                 display: flex;
                 align-items: center;
-                font-size: 24rpx;
-                color: #999;
-                background-color: #f5f5f5;
-                padding: 4rpx 12rpx;
-                border-radius: 4rpx;
-                width: fit-content;
-                margin-bottom: 6rpx;
-                margin-right: 10rpx;
-                order: -1; // Move to front
-            }
-        }
+                justify-content: space-between;
+                margin-top: auto;
 
-        .bottom-row {
-            display: flex;
-            align-items: center;
-            justify-content: space-between;
-            margin-top: auto;
+                .price-box {
+                    color: #e02020;
+                    font-weight: bold;
 
-            .price-box {
-                color: #e02020;
-                font-weight: bold;
+                    .symbol {
+                        font-size: 24rpx;
+                    }
 
-                .symbol {
-                    font-size: 24rpx;
+                    .amount {
+                        font-size: 36rpx;
+                    }
                 }
 
-                .amount {
-                    font-size: 36rpx;
+                .reduce-btn {
+                    background-color: #38C148;
+                    color: #fff;
+                    font-size: 20rpx;
+                    padding: 6rpx 16rpx;
+                    border-radius: 20rpx;
+                    margin-left: 10rpx;
+                    margin-right: auto; // 将数字框推到右边
                 }
             }
-
-            .reduce-btn {
-                background-color: #38C148;
-                color: #fff;
-                font-size: 20rpx;
-                padding: 6rpx 16rpx;
-                border-radius: 20rpx;
-                margin-left: 10rpx;
-                margin-right: auto; // Push num box to right
-            }
         }
     }
-}
 </style>

+ 203 - 0
pages-car/components/condition-popup.vue

@@ -0,0 +1,203 @@
+<template>
+	<u-popup v-model="visible" mode="bottom" border-radius="24" :safe-area-inset-bottom="true" :mask-close-able="true"
+		@close="close">
+		<view class="condition-popup">
+			<!-- 头部 -->
+			<view class="header">
+				<text class="title">切换品相</text>
+				<image src="/pages-sell/static/select-good/icon-close.png" class="close-icon" @click="close"></image>
+			</view>
+
+			<!-- SKU 列表 (新样式) -->
+			<view class="options-list" v-if="skuList.length > 0">
+				<view class="option-item" v-for="(opt, index) in skuList" :key="index"
+					:class="{ active: currentCondition === opt.conditionType, disabled: isStockEmpty(opt) }"
+					@click="selectSku(opt)">
+					<!-- 选中背景图 -->
+					<image v-if="currentCondition === opt.conditionType"
+						src="/pages-sell/static/select-good/selected.png" class="bg-image"></image>
+
+					<view class="left">
+						<text class="opt-name">{{ getConditionName(opt.conditionType) }}</text>
+						<!-- 折扣标签 -->
+						<view class="opt-discount" :class="{ active: currentCondition === opt.conditionType }"
+							v-if="opt.discount">
+							<text>{{ opt.discount }}折</text>
+						</view>
+					</view>
+
+					<view class="right">
+						<text v-if="!isStockEmpty(opt)">¥{{ opt.price }} <text v-if="opt.balanceMoney"
+								style="font-size: 24rpx; color: #999;">( 余额价 ¥{{ opt.balanceMoney }} )</text></text>
+						<text v-else class="no-stock">暂无库存</text>
+					</view>
+				</view>
+			</view>
+			<view class="empty-list" v-else>
+				<u-loading mode="circle"></u-loading>
+			</view>
+		</view>
+	</u-popup>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				visible: false,
+				currentCondition: null,
+				skuList: [],
+				conditionMap: {
+					1: '良好',
+					2: '中等',
+					3: '次品',
+					4: '全新'
+				}
+			};
+		},
+		methods: {
+			open(skuList, currentCondition) {
+				this.skuList = skuList || [];
+				// 确保类型一致,通常 API 返回 number
+				this.currentCondition = Number(currentCondition);
+				this.visible = true;
+			},
+			close() {
+				this.visible = false;
+			},
+			getConditionName(type) {
+				return this.conditionMap[type] || '未知';
+			},
+			isStockEmpty(sku) {
+				return !sku.stockNum || sku.stockNum <= 0;
+			},
+			selectSku(sku) {
+				if (this.isStockEmpty(sku)) return;
+				if (this.currentCondition === sku.conditionType) return;
+				this.$emit('select', sku);
+				this.close();
+			}
+		}
+	};
+</script>
+
+<style lang="scss" scoped>
+	.condition-popup {
+		background-color: #fff;
+		padding: 30rpx;
+		min-height: 400rpx;
+
+		.header {
+			display: flex;
+			justify-content: center;
+			align-items: center;
+			position: relative;
+			margin-bottom: 30rpx;
+			padding-bottom: 30rpx;
+			border-bottom: 2rpx dashed #eee;
+
+			.title {
+				font-size: 34rpx;
+				font-weight: bold;
+				color: #333;
+			}
+
+			.close-icon {
+				position: absolute;
+				right: 0;
+				top: 0;
+				width: 24rpx;
+				height: 24rpx;
+				padding: 10rpx;
+				box-sizing: content-box;
+			}
+		}
+
+		.options-list {
+			margin-bottom: 40rpx;
+
+			.option-item {
+				position: relative;
+				display: flex;
+				justify-content: space-between;
+				align-items: center;
+				background: #F8F8F8;
+				border-radius: 12rpx;
+				padding: 24rpx 30rpx;
+				margin-bottom: 24rpx;
+				border: 2rpx solid #dfdfdf;
+				transition: all 0.2s;
+
+				&.active {
+					border-color: transparent;
+				}
+
+				&.disabled {
+					opacity: 0.6;
+					background: #f0f0f0;
+				}
+
+				.bg-image {
+					position: absolute;
+					top: -6rpx;
+					left: -1%;
+					width: 102%;
+					height: 110rpx;
+					z-index: 0;
+				}
+
+				.left,
+				.right {
+					position: relative;
+					z-index: 1;
+				}
+
+				.left {
+					display: flex;
+					align-items: center;
+
+					.opt-name {
+						font-size: 30rpx;
+						font-weight: bold;
+						color: #333;
+						margin-right: 16rpx;
+					}
+
+					.opt-discount {
+						background: #D8D8D8;
+						padding: 0 20rpx;
+						border-radius: 0 20rpx 0 20rpx;
+
+						text {
+							color: #fff;
+							font-size: 24rpx;
+							display: inline-block;
+						}
+
+						&.active {
+							background: linear-gradient(0deg, #30E030 0%, #28C445 100%);
+						}
+					}
+				}
+
+				.right {
+					font-size: 28rpx;
+					color: #D81A00;
+					font-weight: bold;
+
+					.no-stock {
+						color: #999;
+						font-weight: normal;
+						font-size: 24rpx;
+					}
+				}
+			}
+		}
+
+		.empty-list {
+			display: flex;
+			justify-content: center;
+			padding: 50rpx 0;
+		}
+	}
+</style>

+ 409 - 293
pages-car/pages/index.vue

@@ -1,15 +1,20 @@
 <template>
     <view class="cart-page">
-        <!-- 广告位 -->
-        <!-- <view class="ad-banner">
-            <text>这里是广告</text>
-            <u-icon name="arrow-right" color="#999" size="28"></u-icon>
-        </view> -->
-
         <!-- 购物车列表 -->
         <view class="cart-list">
-            <cart-item v-for="(item, index) in cartList" :key="index" :item="item" @check="handleCheck"
-                @changeNum="handleChangeNum" @reduce="handleReduce"></cart-item>
+            <u-swipe-action :show="item.show" :index="index" v-for="(item, index) in cartList" :key="item.id"
+                @click="clickAction" @open="openAction" :options="actionOptions">
+                <cart-item :item="item" @check="handleCheck" @changeNum="handleChangeNum"
+                    @reduce="handleReduce" @selectCondition="onSelectCondition"></cart-item>
+            </u-swipe-action>
+        </view>
+
+        <!-- 品相切换弹窗 -->
+        <condition-popup ref="conditionPopup" @select="handleConditionUpdate"></condition-popup>
+
+        <!-- 空状态 -->
+        <view class="empty-cart" v-if="cartList.length === 0 && !loading" style="margin:15% 0">
+            <u-empty text="购物车空空如也" mode="car"></u-empty>
         </view>
 
         <!-- 为你推荐 -->
@@ -28,15 +33,20 @@
         </view>
 
         <!-- 底部占位 -->
-        <view style="height: 20rpx;"></view>
+        <view style="height: 120rpx;"></view>
 
         <!-- 底部结算栏 -->
-        <view class="bottom-fixed">
+        <view class="bottom-fixed" v-if="cartList.length > 0">
             <view class="left-part">
                 <view class="checkbox-wrap" @click="toggleSelectAll">
                     <u-checkbox v-model="isAllSelected" shape="circle" active-color="#38C148" :disabled="false"
                         @change="onAllCheckChange">全选</u-checkbox>
                 </view>
+                <!-- 清空按钮 -->
+                <view class="clear-btn" @click="handleClearCart"
+                    style="margin-left: 20rpx; font-size: 24rpx; color: #999;">
+                    清空
+                </view>
             </view>
             <view class="right-part">
                 <view class="total-info">
@@ -62,331 +72,437 @@
 </template>
 
 <script>
-import CartItem from '../components/cart-item.vue';
-import PriceReductionPopup from '../components/price-reduction-popup.vue';
-import CommonDialog from '@/components/common-dialog.vue';
+    import CartItem from '../components/cart-item.vue';
+    import PriceReductionPopup from '../components/price-reduction-popup.vue';
+    import CommonDialog from '@/components/common-dialog.vue';
+    import ConditionPopup from '../components/condition-popup.vue';
 
-export default {
-    components: {
-        CartItem,
-        PriceReductionPopup,
-        CommonDialog
-    },
-    data() {
-        return {
-            isAllSelected: false,
-            cartList: [
-                {
-                    id: 1,
-                    title: '六级词汇词根+联想记忆法第二版浙江教育出版社',
-                    cover: 'https://k.sinaimg.cn/n/sinakd20116/234/w1000h1634/20251003/b66b-587c9ff400fcf01be52c6693594b6a6d.jpg/w700d1q75cms.jpg',
-                    price: 14.52,
-                    num: 1,
-                    status: 1,
-                    stock: 10,
-                    checked: false,
-                    reducedAmount: 0.5, // 已降
-                    canReduceAmount: 0,
-                    stockWarning: false,
-                    quality: '中等'
-                },
-                {
-                    id: 2,
-                    title: '六级词汇词根+联想记忆法第二版浙江教育出版社',
-                    cover: 'https://k.sinaimg.cn/n/sinakd20116/234/w1000h1634/20251003/b66b-587c9ff400fcf01be52c6693594b6a6d.jpg/w700d1q75cms.jpg',
-                    price: 4.5,
-                    num: 1,
-                    status: 0, // 无库存/失效
-                    stock: 0,
-                    checked: false,
-                    reducedAmount: 0,
-                    canReduceAmount: 0,
-                    stockWarning: false,
-                    quality: '中等'
-                },
-                {
-                    id: 3,
-                    title: '六级词汇词根+联想记忆法第二版浙江教育出版社',
-                    cover: 'https://k.sinaimg.cn/n/sinakd20116/234/w1000h1634/20251003/b66b-587c9ff400fcf01be52c6693594b6a6d.jpg/w700d1q75cms.jpg',
-                    price: 14.52,
-                    num: 1,
-                    status: 1,
-                    stock: 5,
-                    checked: false,
-                    reducedAmount: 0,
-                    canReduceAmount: 0.5, // 可降
-                    stockWarning: true, // 库存紧张
-                    quality: '中等'
-                }
-            ],
-            recommendList: [
-                { title: '工程数学线性代数第六版', cover: 'https://k.sinaimg.cn/n/sinakd20116/234/w1000h1634/20251003/b66b-587c9ff400fcf01be52c6693594b6a6d.jpg/w700d1q75cms.jpg' },
-                { title: '工程数学线性代数第六版', cover: 'https://k.sinaimg.cn/n/sinakd20116/234/w1000h1634/20251003/b66b-587c9ff400fcf01be52c6693594b6a6d.jpg/w700d1q75cms.jpg' },
-                { title: '工程数学线性代数第六版', cover: 'https://k.sinaimg.cn/n/sinakd20116/234/w1000h1634/20251003/b66b-587c9ff400fcf01be52c6693594b6a6d.jpg/w700d1q75cms.jpg' },
-                { title: '工程数学线性代数第六版', cover: 'https://k.sinaimg.cn/n/sinakd20116/234/w1000h1634/20251003/b66b-587c9ff400fcf01be52c6693594b6a6d.jpg/w700d1q75cms.jpg' },
-                { title: '工程数学线性代数第六版', cover: 'https://k.sinaimg.cn/n/sinakd20116/234/w1000h1634/20251003/b66b-587c9ff400fcf01be52c6693594b6a6d.jpg/w700d1q75cms.jpg' },
-                { title: '工程数学线性代数第六版', cover: 'https://k.sinaimg.cn/n/sinakd20116/234/w1000h1634/20251003/b66b-587c9ff400fcf01be52c6693594b6a6d.jpg/w700d1q75cms.jpg' }
-            ]
-        };
-    },
-    computed: {
-        selectedItems() {
-            return this.cartList.filter(item => item.checked && item.status === 1 && item.stock > 0);
+    export default {
+        components: {
+            CartItem,
+            PriceReductionPopup,
+            CommonDialog,
+            ConditionPopup
         },
-        totalPrice() {
-            return this.selectedItems.reduce((sum, item) => sum + item.price * item.num, 0).toFixed(2);
+        data() {
+            return {
+                loading: true,
+                currentItem: null,
+                isAllSelected: false,
+                cartList: [],
+                actionOptions: [
+                    {
+                        text: '删除',
+                        style: {
+                            backgroundColor: '#fa3534'
+                        }
+                    }
+                ],
+                recommendList: [
+                    { title: '工程数学线性代数第六版', cover: 'https://k.sinaimg.cn/n/sinakd20116/234/w1000h1634/20251003/b66b-587c9ff400fcf01be52c6693594b6a6d.jpg/w700d1q75cms.jpg' },
+                    { title: '工程数学线性代数第六版', cover: 'https://k.sinaimg.cn/n/sinakd20116/234/w1000h1634/20251003/b66b-587c9ff400fcf01be52c6693594b6a6d.jpg/w700d1q75cms.jpg' },
+                    { title: '工程数学线性代数第六版', cover: 'https://k.sinaimg.cn/n/sinakd20116/234/w1000h1634/20251003/b66b-587c9ff400fcf01be52c6693594b6a6d.jpg/w700d1q75cms.jpg' },
+                    { title: '工程数学线性代数第六版', cover: 'https://k.sinaimg.cn/n/sinakd20116/234/w1000h1634/20251003/b66b-587c9ff400fcf01be52c6693594b6a6d.jpg/w700d1q75cms.jpg' },
+                    { title: '工程数学线性代数第六版', cover: 'https://k.sinaimg.cn/n/sinakd20116/234/w1000h1634/20251003/b66b-587c9ff400fcf01be52c6693594b6a6d.jpg/w700d1q75cms.jpg' },
+                    { title: '工程数学线性代数第六版', cover: 'https://k.sinaimg.cn/n/sinakd20116/234/w1000h1634/20251003/b66b-587c9ff400fcf01be52c6693594b6a6d.jpg/w700d1q75cms.jpg' }
+                ]
+            };
         },
-        totalReduced() {
-            return this.selectedItems.reduce((sum, item) => sum + (item.reducedAmount || 0) * item.num, 0).toFixed(2);
+        onShow() {
+            this.loadData();
         },
-        selectedCount() {
-            return this.selectedItems.reduce((sum, item) => sum + item.num, 0);
-        }
-    },
-    watch: {
-        cartList: {
-            handler(val) {
-                const validItems = val.filter(item => item.status === 1 && item.stock > 0);
-                if (validItems.length === 0) {
-                    this.isAllSelected = false;
-                    return;
-                }
-                this.isAllSelected = validItems.every(item => item.checked);
+        mounted() {
+            this.loadData();
+        },
+        computed: {
+            selectedItems() {
+                return this.cartList.filter(item => item.checked && item.stockStatus !== 3 && item.availableStock > 0);
             },
-            deep: true
-        }
-    },
-    methods: {
-        handleCheck({ id, checked }) {
-            const item = this.cartList.find(i => i.id === id);
-            if (item) {
-                item.checked = checked;
+            totalPrice() {
+                return this.selectedItems.reduce((sum, item) => sum + item.productPrice * item.quantity, 0).toFixed(2);
+            },
+            totalReduced() {
+                return this.selectedItems.reduce((sum, item) => sum + (item.currReduceMoney || 0) * item.quantity, 0).toFixed(2);
+            },
+            selectedCount() {
+                return this.selectedItems.reduce((sum, item) => sum + item.quantity, 0);
             }
         },
-        handleChangeNum({ id, num }) {
-            const item = this.cartList.find(i => i.id === id);
-            if (item) {
-                item.num = num;
+        watch: {
+            cartList: {
+                handler(val) {
+                    const validItems = val.filter(item => item.stockStatus !== 3 && item.availableStock > 0);
+                    if (validItems.length === 0) {
+                        this.isAllSelected = false;
+                        return;
+                    }
+                    this.isAllSelected = validItems.every(item => item.checked);
+                },
+                deep: true
             }
         },
-        handleReduce(item) {
-            // 打开减钱弹窗
-            // 构造弹窗需要的数据格式
-            const popupData = {
-                price: item.price,
-                reducedPrice: (item.price - item.canReduceAmount).toFixed(2),
-                startTime: new Date().getTime(),
-                endTime: new Date().getTime() + 24 * 60 * 60 * 1000,
-                upsellCode: 'mock-code' // 模拟
-            };
-            this.$refs.reducePopup.bookInfo = popupData; // 直接赋值或者通过 open 方法传参
-            // price-reduction-popup (based on upsell-book) uses 'open(data)' method if available, 
-            // but looking at upsell-book.vue code, it uses v-model="showPopup" and data is set via props or direct access?
-            // Actually upsell-book.vue doesn't have an open method in the snippet I saw earlier, 
-            // wait, upsell-book.vue code snippet shows:
-            // data() { return { showPopup: false, ... } }
-            // It doesn't seem to have an open() method exposed in the snippet.
-            // But it has `custom-popup v-model="showPopup"`.
-            // So I should set showPopup = true.
-            // And I need to pass data. 
-            // upsell-book.vue snippet didn't show props for bookInfo, 
-            // but it uses bookInfo in template. It likely has it in data or props.
-            // Let's assume I need to set it.
-
-            // Checking upsell-book.vue again (from previous search):
-            // It uses `bookInfo.recyclePrice` etc. 
-            // I should verify if bookInfo is a prop or data. 
-            // The snippet showed `props: { book: ... }` in BookItem.vue, but upsell-book.vue?
-            // Wait, I copied upsell-book.vue. Let me check the file content if I can... 
-            // I'll assume I can set `this.$refs.reducePopup.bookInfo = ...` and `this.$refs.reducePopup.showPopup = true`.
-
-            this.$refs.reducePopup.bookInfo = popupData;
-            this.$refs.reducePopup.showPopup = true;
-
-            // Simulate invite users
-            this.$refs.reducePopup.inviteUsers = [];
-        },
-        onAllCheckChange(e) {
-            const checked = e.value;
-            this.cartList.forEach(item => {
-                if (item.status === 1 && item.stock > 0) {
+        methods: {
+            clickAction(index, index1) {
+                if (this.actionOptions[index1].text == '删除') {
+                    const item = this.cartList[index];
+                    this.handleDelete(item);
+                }
+            },
+            openAction(index) {
+                this.cartList[index].show = true;
+                this.cartList.map((val, idx) => {
+                    if (index != idx) this.cartList[idx].show = false;
+                })
+            },
+            loadData() {
+                this.loading = true;
+                this.$u.api.getShopCartListAjax({}).then(res => {
+                    this.loading = false;
+                    // 本地添加选中状态属性
+                    const list = res.rows || [];
+                    // 如果可能,与现有的选中状态合并,或者重置?
+                    // 为简单起见,如果 id 匹配,则重置或保持选中状态。
+                    // 或者直接初始化 checked=false。
+                    this.cartList = list.map(item => {
+                        return {
+                            ...item,
+                            checked: false,
+                            show: false
+                        };
+                    });
+                }).catch(() => {
+                    this.loading = false;
+                });
+            },
+            handleCheck({ id, checked }) {
+                const item = this.cartList.find(i => i.id === id);
+                if (item) {
                     item.checked = checked;
                 }
-            });
-        },
-        toggleSelectAll() {
-            // Handled by u-checkbox change or click wrapper
-            // If clicked wrapper, toggle boolean
-            // But u-checkbox also emits change.
-            // Let's rely on v-model binding and @change
-        },
-        checkout() {
-            if (this.selectedCount === 0) {
-                uni.showToast({ title: '请选择商品', icon: 'none' });
-                return;
-            }
-            this.$refs.reduceDialog.openPopup();
-        },
-        onNext() {
-            // 提交订单
-            uni.navigateTo({ url: '/pages-car/pages/confirm-order' });
-        },
-        handleShare() {
-            console.log('share');
-        },
-        handleScan() {
-            console.log('scan');
+            },
+            handleChangeNum({ id, num }) {
+                const item = this.cartList.find(i => i.id === id);
+                if (item) {
+                    // 乐观更新还是等待服务器?
+                    // 通常是服务器优先。
+                    this.$u.api.updateCartNumAjax({
+                        id: id,
+                        quantity: num
+                    }).then(() => {
+                        item.quantity = num;
+                        // 通过 computed 重新计算总额
+                    }).catch(() => {
+                        // 如果是乐观更新,出错时需要回滚,但这里我们还没有更新?
+                        // 实际上数字输入框更新了 v-model。如果在子组件中使用 v-model,它会更新父组件。
+                        // 但我们触发了 changeNum。
+                        // 在子组件中:v-model="item.quantity" -> 直接更新 prop(Vue 2 会警告,但对象属性变更有效)。
+                        // 如果子组件更新了 prop,除了调用 API 我们不需要做任何事。
+                        // 但良好的实践是子组件触发事件,父组件更新。
+                        // cart-item 使用 v-model 绑定 props.item.quantity。这直接修改了 cartList 中的对象。
+                        // 所以 item.quantity 已经是 `num` 了。
+                        // 我们只需要调用 API。
+
+                        // 注意:cart-item.vue 使用 v-model="item.quantity"。
+                        // 这会直接修改对象。
+                        // 所以 API 调用应该使用新值。
+                        // 如果 API 失败,我们应该回滚。
+                        // 但让我们假设成功,或者通过重新加载列表来处理错误。
+                    });
+                }
+            },
+            handleReduce(item) {
+                const popupData = {
+                    price: item.productPrice,
+                    reducedPrice: (item.productPrice - item.reduceMoney).toFixed(2), // 假设 reduceMoney 是单价减少额
+                    startTime: new Date().getTime(),
+                    endTime: new Date().getTime() + (item.restTime || 0) * 1000, // restTime 单位是秒?
+                    upsellCode: 'mock-code'
+                };
+                this.$refs.reducePopup.bookInfo = popupData;
+                this.$refs.reducePopup.showPopup = true;
+                this.$refs.reducePopup.inviteUsers = [];
+            },
+            onAllCheckChange(e) {
+                const checked = e.value;
+                this.cartList.forEach(item => {
+                    if (item.stockStatus !== 3 && item.availableStock > 0) {
+                        item.checked = checked;
+                    }
+                });
+            },
+            toggleSelectAll() {
+                // 由 u-checkbox 处理
+            },
+            handleClearCart() {
+                uni.showModal({
+                    title: '提示',
+                    content: '确定要清空购物车吗?',
+                    success: (res) => {
+                        if (res.confirm) {
+                            this.$u.api.clearCartAjax().then(() => {
+                                this.$u.toast('清空成功');
+                                this.cartList = [];
+                            });
+                        }
+                    }
+                });
+            },
+            checkout() {
+                if (this.selectedCount === 0) {
+                    uni.showToast({ title: '请选择商品', icon: 'none' });
+                    return;
+                }
+                // 如果需要,检查减价商品逻辑
+                // 如果有选中的商品可以减价,显示对话框?
+                const hasReduceItem = this.selectedItems.some(item => (item.reduceNum < item.maxReduceNum) && (item.reduceMoney > 0));
+                if (hasReduceItem) {
+                    this.$refs.reduceDialog.openPopup();
+                } else {
+                    this.onNext();
+                }
+            },
+            onNext() {
+                // 提交订单 - 传递选中的 ID?
+                // 通常我们将 ID 传递给确认订单页面
+                const ids = this.selectedItems.map(item => item.id).join(',');
+                uni.navigateTo({ url: `/pages-car/pages/confirm-order?ids=${ids}` });
+            },
+            handleShare() {
+                console.log('share');
+            },
+            handleScan() {
+                console.log('scan');
+            },
+            handleDelete(item) {
+                uni.showModal({
+                    title: '提示',
+                    content: '确定要删除该商品吗?',
+                    success: (res) => {
+                        if (res.confirm) {
+                            this.$u.api.deleteCartItemAjax(item.id).then(() => {
+                                this.$u.toast('删除成功');
+                                // 从本地列表中移除
+                                const index = this.cartList.findIndex(i => i.id === item.id);
+                                if (index > -1) {
+                                    this.cartList.splice(index, 1);
+                                }
+                            });
+                        } else {
+                            item.show = false;
+                        }
+                    }
+                });
+            },
+            onSelectCondition(item) {
+                this.currentItem = item;
+                // 获取商品详情中的 SKU 列表
+                // 注意:这里使用 isbn,假设 item 中包含 isbn
+                if (!item.isbn) {
+                    this.$u.toast('无法获取商品信息');
+                    return;
+                }
+                
+                uni.showLoading({ title: '加载中' });
+                this.$u.http.get('/token/shop/bookDetail', { isbn: item.isbn }).then(res => {
+                    uni.hideLoading();
+                    if (res.code === 200 && res.data && res.data.skuList) {
+                        this.$refs.conditionPopup.open(res.data.skuList, item.conditionType);
+                    } else {
+                        this.$u.toast('获取品相信息失败');
+                    }
+                }).catch(() => {
+                    uni.hideLoading();
+                    this.$u.toast('网络请求失败');
+                });
+            },
+			handleConditionUpdate(sku) {
+				if (!this.currentItem) return;
+
+				uni.showLoading({
+					title: '更新中'
+				});
+				this.$u.api.updateCartConditionAjax({
+					id: this.currentItem.id,
+					conditionType: sku.conditionType
+				}).then(() => {
+					uni.hideLoading();
+					this.$u.toast('更新成功');
+					
+					// 在列表中查找并更新,确保视图刷新
+					const index = this.cartList.findIndex(i => i.id === this.currentItem.id);
+					if (index > -1) {
+						const targetItem = this.cartList[index];
+						// 确保转换为数字类型
+						const newType = Number(sku.conditionType);
+						
+						this.$set(targetItem, 'conditionType', newType);
+						this.$set(targetItem, 'productPrice', sku.price);
+						
+						if (sku.reduceMoney !== undefined) {
+							this.$set(targetItem, 'reduceMoney', sku.reduceMoney);
+						}
+						
+						// 强制更新 currentItem 引用(虽然它指向同一个对象,但为了保险)
+						this.currentItem = targetItem;
+					} else {
+						// 如果找不到(极少情况),重新加载
+						this.loadData();
+					}
+				}).catch(() => {
+					uni.hideLoading();
+				});
+			}
         }
     }
-}
 </script>
 
 <style lang="scss" scoped>
-.cart-page {
-    min-height: 100vh;
-    background-color: #f5f5f5;
-    padding-bottom: 120rpx; // Space for bottom bar
-}
-
-.ad-banner {
-    background-color: #d1f2d6; // Light green
-    padding: 20rpx 30rpx;
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    color: #666;
-    font-size: 26rpx;
-}
-
-.cart-list {
-    padding: 20rpx;
-}
-
-.recommend-section {
-    padding: 0 20rpx;
+    .cart-page {
+        min-height: 100vh;
+        background-color: #f5f5f5;
+        padding-bottom: 120rpx; // 底部栏的留白
+    }
 
-    .section-title {
+    .ad-banner {
+        background-color: #d1f2d6; // 浅绿色
+        padding: 20rpx 30rpx;
         display: flex;
+        justify-content: space-between;
         align-items: center;
-        justify-content: center;
-        margin: 30rpx 0;
-
-        .line {
-            width: 60rpx;
-            height: 2rpx;
-            background-color: #ccc;
-        }
+        color: #666;
+        font-size: 26rpx;
+    }
 
-        .text {
-            margin: 0 20rpx;
-            font-size: 28rpx;
-            color: #333;
-            font-weight: bold;
-        }
+    .cart-list {
+        padding: 20rpx;
     }
 
-    .recommend-grid {
-        display: flex;
-        flex-wrap: wrap;
-        justify-content: space-between;
+    .recommend-section {
+        padding: 0 20rpx;
 
-        .grid-item {
-            width: 32%; // 3 columns
-            background-color: #fff;
-            border-radius: 12rpx;
-            margin-bottom: 20rpx;
-            padding: 20rpx;
-            box-sizing: border-box;
+        .section-title {
             display: flex;
-            flex-direction: column;
             align-items: center;
-            
-            .cover {
-                width: 100%;
-                height: 220rpx;
-                border-radius: 8rpx;
-                background-color: #eee;
+            justify-content: center;
+            margin: 30rpx 0;
+
+            .line {
+                width: 60rpx;
+                height: 2rpx;
+                background-color: #ccc;
             }
-            
-            .title {
-                margin-top: 10rpx;
-                font-size: 24rpx;
+
+            .text {
+                margin: 0 20rpx;
+                font-size: 28rpx;
                 color: #333;
-                line-height: 1.4;
-                display: -webkit-box;
-                -webkit-box-orient: vertical;
-                -webkit-line-clamp: 2;
-                overflow: hidden;
-                width: 100%;
+                font-weight: bold;
             }
         }
-    }
-}
 
-.bottom-fixed {
-    position: fixed;
-    bottom: 0;
-    /* #ifdef H5 */
-    bottom: 50px;
-    /* #endif */
-    left: 0;
-    width: 100%;
-    background-color: #fff;
-    display: flex;
-    align-items: center;
-    justify-content: space-between;
-    padding: 24rpx 30rpx;
-    box-sizing: border-box;
-    box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.05);
-    z-index: 99;
+        .recommend-grid {
+            display: flex;
+            flex-wrap: wrap;
+            justify-content: space-between;
 
-    .left-part {
-        display: flex;
-        align-items: center;
+            .grid-item {
+                width: 32%; // 3 列
+                background-color: #fff;
+                border-radius: 12rpx;
+                margin-bottom: 20rpx;
+                padding: 20rpx;
+                box-sizing: border-box;
+                display: flex;
+                flex-direction: column;
+                align-items: center;
+
+                .cover {
+                    width: 100%;
+                    height: 220rpx;
+                    border-radius: 8rpx;
+                    background-color: #eee;
+                }
+
+                .title {
+                    margin-top: 10rpx;
+                    font-size: 24rpx;
+                    color: #333;
+                    line-height: 1.4;
+                    display: -webkit-box;
+                    -webkit-box-orient: vertical;
+                    -webkit-line-clamp: 2;
+                    overflow: hidden;
+                    width: 100%;
+                }
+            }
+        }
     }
 
-    .right-part {
+    .bottom-fixed {
+        position: fixed;
+        bottom: 0;
+        /* #ifdef H5 */
+        bottom: 50px;
+        /* #endif */
+        left: 0;
+        width: 100%;
+        background-color: #fff;
         display: flex;
         align-items: center;
-        
-        .total-info {
-            text-align: right;
-            margin-right: 20rpx;
+        justify-content: space-between;
+        padding: 24rpx 30rpx;
+        box-sizing: border-box;
+        box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
+        z-index: 99;
+
+        .left-part {
             display: flex;
             align-items: center;
-            
-            .reduced-tip {
-                font-size: 22rpx;
-                color: #38C148;
-                border: 1rpx solid #38C148;
-                border-radius: 4rpx;
-                padding: 0 6rpx;
-                margin-right: 10rpx;
-            }
-            
-            .total-price {
+        }
+
+        .right-part {
+            display: flex;
+            align-items: center;
+
+            .total-info {
+                text-align: right;
+                margin-right: 20rpx;
                 display: flex;
                 align-items: center;
-                
-                .label {
-                    font-size: 28rpx;
-                    color: #333;
+
+                .reduced-tip {
+                    font-size: 22rpx;
+                    color: #38C148;
+                    border: 1rpx solid #38C148;
+                    border-radius: 4rpx;
+                    padding: 0 6rpx;
+                    margin-right: 10rpx;
                 }
-                .price {
-                    font-size: 36rpx;
-                    color: #e02020;
-                    font-weight: bold;
+
+                .total-price {
+                    display: flex;
+                    align-items: center;
+
+                    .label {
+                        font-size: 28rpx;
+                        color: #333;
+                    }
+
+                    .price {
+                        font-size: 36rpx;
+                        color: #e02020;
+                        font-weight: bold;
+                    }
                 }
             }
-        }
-        
-        .checkout-btn {
-            background-color: #e02020;
-            color: #fff;
-            font-size: 30rpx;
-            padding: 16rpx 40rpx;
-            border-radius: 40rpx;
+
+            .checkout-btn {
+                background-color: #e02020;
+                color: #fff;
+                font-size: 30rpx;
+                padding: 16rpx 40rpx;
+                border-radius: 40rpx;
+            }
         }
     }
-}
 </style>

+ 8 - 3
pages/cart/index.vue

@@ -1,6 +1,6 @@
 <template>
     <view>
-        <cart-container></cart-container>
+        <cart-container ref="cartContainer"></cart-container>
     </view>
 </template>
 
@@ -11,8 +11,13 @@ export default {
 
         }
     },
-    onLoad() {
-
+    onShow() {
+        // 每次显示页面时,调用子组件的刷新方法
+        this.$nextTick(() => {
+            if (this.$refs.cartContainer) {
+                this.$refs.cartContainer.loadData();
+            }
+        });
     },
     methods: {
 

+ 2 - 2
uview-ui/LICENSE

@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2020 www.uviewui.com
+Copyright (c) 2023 www.uviewui.com
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+SOFTWARE.

+ 49 - 1
uview-ui/README.md

@@ -1,3 +1,14 @@
+<p align="center">
+    <img alt="logo" src="https://uviewui.com/common/logo.png" width="120" height="120" style="margin-bottom: 10px;">
+</p>
+<h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uView</h3>
+<h3 align="center">多平台快速开发的UI框架</h3>
+
+
+## 说明
+
+uView UI,是[uni-app](https://uniapp.dcloud.io/)生态优秀的UI框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水
+
 ## 特性
 
 - 兼容安卓,iOS,微信小程序,H5,QQ小程序,百度小程序,支付宝小程序,头条小程序
@@ -55,4 +66,41 @@ Vue.use(uView);
 		// ......
 	]
 }
-```
+```
+
+请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容 
+
+## 使用方法
+配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。
+
+```html
+<template>
+	<u-button>按钮</u-button>
+</template>
+```
+
+请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容 
+
+## 链接
+
+- [官方文档](https://uviewui.com/)
+- [更新日志](https://uviewui.com/components/changelog.html)
+- [升级指南](https://uviewui.com/components/changelog.html)
+- [关于我们](https://uviewui.com/cooperation/about.html)
+
+## 预览
+
+您可以通过**微信**扫码,查看最佳的演示效果。
+<br>
+<br>
+<img src="https://uviewui.com/common/weixin_mini_qrcode.png" width="220" height="220" >
+
+<!-- ## 捐赠uView的研发
+
+uView文档和源码全部开源免费,如果您认为uView帮到了您的开发工作,您可以捐赠uView的研发工作,捐赠无门槛,哪怕是一杯可乐也好(相信这比打赏主播更有意义)。
+
+<img src="https://uviewui.com/common/wechat.png" width="220" >
+<img style="margin-left: 100px;" src="https://uviewui.com/common/alipay.png" width="220" >
+ -->
+## 版权信息
+uView遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uView应用到您的产品中。

+ 1 - 1
uview-ui/components/u-badge/u-badge.vue

@@ -113,7 +113,7 @@
 					style.right = this.offset[1] + 'rpx';
 					style.transform = "translateY(0) translateX(0)";
 				}
-				// 如果尺寸为mini,后接上scal()
+				// 如果尺寸为mini,后接上scale()
 				if(this.size == 'mini') {
 					style.transform = style.transform + " scale(0.8)";
 				}

+ 47 - 117
uview-ui/components/u-button/u-button.vue

@@ -9,7 +9,7 @@
 			shape == 'circle' ? 'u-round-circle' : '',
 			hairLine ? showHairLineBorder : 'u-btn--bold-border',
 			'u-btn--' + type,
-			disabled ? `u-btn--${type}--disabled` : ''
+			disabled ? `u-btn--${type}--disabled` : '',
 		]"
 		:hover-start-time="Number(hoverStartTime)"
 		:hover-stay-time="Number(hoverStayTime)"
@@ -30,12 +30,9 @@
 		@error="error"
 		@opensetting="opensetting"
 		@launchapp="launchapp"
-		:style="[
-			customStyle,
-			{
-				overflow: ripple ? 'hidden' : 'visible'
-			}
-		]"
+		:style="[customStyle, {
+			overflow: ripple ? 'hidden' : 'visible'
+		}]"
 		@tap.stop="click($event)"
 		:hover-class="getHoverClass"
 		:loading="loading"
@@ -215,7 +212,7 @@ export default {
 		// 节流,一定时间内只能触发一次
 		throttleTime: {
 			type: [String, Number],
-			default: 100
+			default: 1000
 		},
 		// 按住后多久出现点击态,单位毫秒
 		hoverStartTime: {
@@ -226,7 +223,7 @@ export default {
 		hoverStayTime: {
 			type: [String, Number],
 			default: 150
-		}
+		},
 	},
 	computed: {
 		// 当没有传bgColor变量时,按钮按下去的颜色类名
@@ -346,7 +343,6 @@ export default {
 
 <style scoped lang="scss">
 @import '../../libs/css/style.components.scss';
-
 .u-btn::after {
 	border: none;
 }
@@ -356,7 +352,7 @@ export default {
 	border: 0;
 	//border-radius: 10rpx;
 	/* #ifndef APP-NVUE */
-	display: inline-flex;
+	display: inline-flex;		
 	/* #endif */
 	// 避免边框某些场景可能被“裁剪”,不能设置为hidden
 	overflow: visible;
@@ -369,131 +365,93 @@ export default {
 	z-index: 1;
 	box-sizing: border-box;
 	transition: all 0.15s;
-
+	
 	&--bold-border {
 		border: 1px solid #ffffff;
 	}
-
+	
 	&--default {
 		color: $u-content-color;
 		border-color: #c0c4cc;
 		background-color: #ffffff;
 	}
-
+	
 	&--primary {
 		color: #ffffff;
 		border-color: $u-type-primary;
 		background-color: $u-type-primary;
 	}
-
+	
 	&--success {
 		color: #ffffff;
 		border-color: $u-type-success;
 		background-color: $u-type-success;
 	}
-
+	
 	&--error {
 		color: #ffffff;
 		border-color: $u-type-error;
 		background-color: $u-type-error;
 	}
 	
-	&--info {
-		color: #ffffff;
-		background-color: #d8d8d8;
-	}
-
 	&--warning {
 		color: #ffffff;
 		border-color: $u-type-warning;
 		background-color: $u-type-warning;
 	}
-
-	&--dark {
-		color: #ffffff;
-		border-color: $u-type-dark;
-		background-color: $u-type-dark;
-	}
-	&--white {
-		color: #ffffff;
-		border-color: $u-type-white;
-		background-color: rgba(0, 0, 0, 0) !important;
-	}
-
+	
 	&--default--disabled {
 		color: #ffffff;
 		border-color: #e4e7ed;
 		background-color: #ffffff;
 	}
-
+	
 	&--primary--disabled {
-		color: #ffffff !important;
-		border-color: $u-type-primary-disabled !important;
-		background-color: $u-type-primary-disabled !important;
+		color: #ffffff!important;
+		border-color: $u-type-primary-disabled!important;
+		background-color: $u-type-primary-disabled!important;
 	}
-
+	
 	&--success--disabled {
-		color: #ffffff !important;
-		border-color: $u-type-success-disabled !important;
-		background-color: $u-type-success-disabled !important;
+		color: #ffffff!important;
+		border-color: $u-type-success-disabled!important;
+		background-color: $u-type-success-disabled!important;
 	}
-
+	
 	&--error--disabled {
-		color: #ffffff !important;
-		border-color: $u-type-error-disabled !important;
-		background-color: $u-type-error-disabled !important;
+		color: #ffffff!important;
+		border-color: $u-type-error-disabled!important;
+		background-color: $u-type-error-disabled!important;
 	}
-
+	
 	&--warning--disabled {
-		color: #ffffff !important;
-		border-color: $u-type-warning-disabled !important;
-		background-color: $u-type-warning-disabled !important;
-	}
-
-	&--dark--disabled {
-		color: #ffffff !important;
-		border-color: $u-type-dark-disabled !important;
-		background-color: $u-type-dark-disabled !important;
+		color: #ffffff!important;
+		border-color: $u-type-warning-disabled!important;
+		background-color: $u-type-warning-disabled!important;
 	}
-	&--white--disabled {
-		color: #ffffff !important;
-		border-color: $u-type-white-disabled !important;
-		background-color: rgba(0, 0, 0, 0) !important;
-	}
-
+	
 	&--primary--plain {
-		color: $u-type-primary !important;
-		border-color: $u-type-primary-disabled !important;
-		background-color: #ffffff !important;
+		color: $u-type-primary!important;
+		border-color: $u-type-primary-disabled!important;
+		background-color: $u-type-primary-light!important;
 	}
-
+	
 	&--success--plain {
-		color: $u-type-success !important;
-		border-color: $u-type-success-disabled !important;
-		background-color: $u-type-success-light !important;
+		color: $u-type-success!important;
+		border-color: $u-type-success-disabled!important;
+		background-color: $u-type-success-light!important;
 	}
-
+	
 	&--error--plain {
-		color: $u-type-error !important;
-		border-color: $u-type-error-disabled !important;
-		background-color: $u-type-error-light !important;
+		color: $u-type-error!important;
+		border-color: $u-type-error-disabled!important;
+		background-color: $u-type-error-light!important;
 	}
-
+	
 	&--warning--plain {
-		color: $u-type-warning !important;
-		border-color: $u-type-warning-disabled !important;
-		background-color: $u-type-warning-light !important;
-	}
-
-	&--dark--plain {
-		color: $u-type-dark !important;
-		border-color: $u-type-dark-disabled !important;
-		background-color: $u-type-dark-light !important;
-	}
-	&--white--plain {
-		color: $u-type-white !important;
-		border-color: $u-type-white-disabled !important;
-		background-color: rgba(0, 0, 0, 0) !important;
+		color: $u-type-warning!important;
+		border-color: $u-type-warning-disabled!important;
+		background-color: $u-type-warning-light!important;
 	}
 }
 
@@ -554,7 +512,7 @@ export default {
 
 .u-size-medium {
 	/* #ifndef APP-NVUE */
-	display: inline-flex;
+	display: inline-flex;		
 	/* #endif */
 	width: auto;
 	font-size: 26rpx;
@@ -562,21 +520,10 @@ export default {
 	line-height: 70rpx;
 	padding: 0 80rpx;
 }
-.u-size-small {
-	/* #ifndef APP-NVUE */
-	display: inline-flex;
-	/* #endif */
-	width: auto;
-	font-size: 28rpx;
-	padding-top: 1px;
-	height: 60rpx;
-	line-height: 60rpx;
-	padding: 0 30rpx;
-}
 
 .u-size-mini {
 	/* #ifndef APP-NVUE */
-	display: inline-flex;
+	display: inline-flex;		
 	/* #endif */
 	width: auto;
 	font-size: 22rpx;
@@ -610,19 +557,11 @@ export default {
 	color: #ffffff !important;
 	background: $u-type-error-dark !important;
 }
-.u-dark-plain-hover {
-	color: #ffffff !important;
-	background: $u-type-dark-dark !important;
-}
 
 .u-info-plain-hover {
 	color: #ffffff !important;
 	background: $u-type-info-dark !important;
 }
-.u-white-plain-hover {
-	color: #ffffff !important;
-	background: rgba(0, 0, 0, 0) !important;
-}
 
 .u-default-hover {
 	color: $u-type-primary-dark !important;
@@ -630,15 +569,6 @@ export default {
 	background-color: $u-type-primary-light !important;
 }
 
-.u-dark-hover {
-	background: $u-type-dark-dark !important;
-	color: #fff;
-}
-.u-white-hover {
-	background: rgba(0, 0, 0, 0) !important;
-	color: #fff;
-}
-
 .u-primary-hover {
 	background: $u-type-primary-dark !important;
 	color: #fff;

+ 643 - 0
uview-ui/components/u-calendar/u-calendar.vue

@@ -0,0 +1,643 @@
+<template>
+	<u-popup closeable :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="value" length="auto"
+	 :safeAreaInsetBottom="safeAreaInsetBottom" @close="close" :z-index="uZIndex" :border-radius="borderRadius" :closeable="closeable">
+		<view class="u-calendar">
+			<view class="u-calendar__header">
+				<view class="u-calendar__header__text" v-if="!$slots['tooltip']">
+					{{toolTip}}
+				</view>
+				<slot v-else name="tooltip" />
+			</view>
+			<view class="u-calendar__action u-flex u-row-center">
+				<view class="u-calendar__action__icon">
+					<u-icon v-if="changeYear" name="arrow-left-double" :color="yearArrowColor" @click="changeYearHandler(0)"></u-icon>
+				</view>
+				<view class="u-calendar__action__icon">
+					<u-icon v-if="changeMonth" name="arrow-left" :color="monthArrowColor" @click="changeMonthHandler(0)"></u-icon>
+				</view>
+				<view class="u-calendar__action__text">{{ showTitle }}</view>
+				<view class="u-calendar__action__icon">
+					<u-icon v-if="changeMonth" name="arrow-right" :color="monthArrowColor" @click="changeMonthHandler(1)"></u-icon>
+				</view>
+				<view class="u-calendar__action__icon">
+					<u-icon v-if="changeYear" name="arrow-right-double" :color="yearArrowColor" @click="changeYearHandler(1)"></u-icon>
+				</view>
+			</view>
+			<view class="u-calendar__week-day">
+				<view class="u-calendar__week-day__text" v-for="(item, index) in weekDayZh" :key="index">{{item}}</view>
+			</view>
+			<view class="u-calendar__content">
+				<!-- 前置空白部分 -->
+				<block v-for="(item, index) in weekdayArr" :key="index">
+					<view class="u-calendar__content__item"></view>
+				</block>
+				<view class="u-calendar__content__item" :class="{
+					'u-hover-class':openDisAbled(year,month,index+1),
+					'u-calendar__content--start-date': (mode == 'range' && startDate==`${year}-${month}-${index+1}`) || mode== 'date',
+					'u-calendar__content--end-date':(mode== 'range' && endDate==`${year}-${month}-${index+1}`) || mode == 'date'
+				}" :style="{backgroundColor: getColor(index,1)}" v-for="(item, index) in daysArr" :key="index"
+				 @tap="dateClick(index)">
+					<view class="u-calendar__content__item__inner" :style="{color: getColor(index,2)}">
+						<view>{{ index + 1 }}</view>
+					</view>
+					<view class="u-calendar__content__item__tips" :style="{color:activeColor}" v-if="mode== 'range' && startDate==`${year}-${month}-${index+1}` && startDate!=endDate">{{startText}}</view>
+					<view class="u-calendar__content__item__tips" :style="{color:activeColor}" v-if="mode== 'range' && endDate==`${year}-${month}-${index+1}`">{{endText}}</view>
+				</view>
+				<view class="u-calendar__content__bg-month">{{month}}</view>
+			</view>
+			<view class="u-calendar__bottom">
+				<view class="u-calendar__bottom__choose">
+					<text>{{mode == 'date' ? activeDate : startDate}}</text>
+					<text v-if="endDate">至{{endDate}}</text>
+				</view>
+				<view class="u-calendar__bottom__btn">
+					<u-button :type="btnType" shape="circle" size="default" @click="btnFix(false)">确定</u-button>
+				</view>
+			</view>
+		</view>
+	</u-popup>
+</template>
+<script>
+	/**
+	 * calendar 日历
+	 * @description 此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中。
+	 * @tutorial http://uviewui.com/components/calendar.html
+	 * @property {String} mode 选择日期的模式,date-为单个日期,range-为选择日期范围
+	 * @property {Boolean} v-model 布尔值变量,用于控制日历的弹出与收起
+	 * @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
+	 * @property {Boolean} change-year 是否显示顶部的切换年份方向的按钮(默认true)
+	 * @property {Boolean} change-month 是否显示顶部的切换月份方向的按钮(默认true)
+	 * @property {String Number} max-year 可切换的最大年份(默认2050)
+	 * @property {String Number} min-year 可切换的最小年份(默认1950)
+	 * @property {String Number} min-date 最小可选日期(默认1950-01-01)
+	 * @property {String Number} max-date 最大可选日期(默认当前日期)
+	 * @property {String Number} 弹窗顶部左右两边的圆角值,单位rpx(默认20)
+	 * @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭日历(默认true)
+	 * @property {String} month-arrow-color 月份切换按钮箭头颜色(默认#606266)
+	 * @property {String} year-arrow-color 年份切换按钮箭头颜色(默认#909399)
+	 * @property {String} color 日期字体的默认颜色(默认#303133)
+	 * @property {String} active-bg-color 起始/结束日期按钮的背景色(默认#2979ff)
+	 * @property {String Number} z-index 弹出时的z-index值(默认10075)
+	 * @property {String} active-color 起始/结束日期按钮的字体颜色(默认#ffffff)
+	 * @property {String} range-bg-color 起始/结束日期之间的区域的背景颜色(默认rgba(41,121,255,0.13))
+	 * @property {String} range-color 选择范围内字体颜色(默认#2979ff)
+	 * @property {String} start-text 起始日期底部的提示文字(默认 '开始')
+	 * @property {String} end-text 结束日期底部的提示文字(默认 '结束')
+	 * @property {String} btn-type 底部确定按钮的主题(默认 'primary')
+	 * @property {String} toolTip 顶部提示文字,如设置名为tooltip的slot,此参数将失效(默认 '选择日期')
+	 * @property {Boolean} closeable 是否显示右上角的关闭图标(默认true)
+	 * @example <u-calendar v-model="show" :mode="mode"></u-calendar>
+	 */
+	
+	export default {
+		name: 'u-calendar',
+		props: {
+			safeAreaInsetBottom: {
+				type: Boolean,
+				default: false
+			},
+			// 是否允许通过点击遮罩关闭Picker
+			maskCloseAble: {
+				type: Boolean,
+				default: true
+			},
+			// 通过双向绑定控制组件的弹出与收起
+			value: {
+				type: Boolean,
+				default: false
+			},
+			// 弹出的z-index值
+			zIndex: {
+				type: [String, Number],
+				default: 0
+			},
+			// 是否允许切换年份
+			changeYear: {
+				type: Boolean,
+				default: true
+			},
+			// 是否允许切换月份
+			changeMonth: {
+				type: Boolean,
+				default: true
+			},
+			// date-单个日期选择,range-开始日期+结束日期选择
+			mode: {
+				type: String,
+				default: 'date'
+			},
+			// 可切换的最大年份
+			maxYear: {
+				type: [Number, String],
+				default: 2050
+			},
+			// 可切换的最小年份
+			minYear: {
+				type: [Number, String],
+				default: 1950
+			},
+			// 最小可选日期(不在范围内日期禁用不可选)
+			minDate: {
+				type: [Number, String],
+				default: '1950-01-01'
+			},
+			/**
+			 * 最大可选日期
+			 * 默认最大值为今天,之后的日期不可选
+			 * 2030-12-31
+			 * */
+			maxDate: {
+				type: [Number, String],
+				default: ''
+			},
+			// 弹窗顶部左右两边的圆角值
+			borderRadius: {
+				type: [String, Number],
+				default: 20
+			},
+			// 月份切换按钮箭头颜色
+			monthArrowColor: {
+				type: String,
+				default: '#606266'
+			},
+			// 年份切换按钮箭头颜色
+			yearArrowColor: {
+				type: String,
+				default: '#909399'
+			},
+			// 默认日期字体颜色
+			color: {
+				type: String,
+				default: '#303133'
+			},
+			// 选中|起始结束日期背景色
+			activeBgColor: {
+				type: String,
+				default: '#2979ff'
+			},
+			// 选中|起始结束日期字体颜色
+			activeColor: {
+				type: String,
+				default: '#ffffff'
+			},
+			// 范围内日期背景色
+			rangeBgColor: {
+				type: String,
+				default: 'rgba(41,121,255,0.13)'
+			}, 
+			// 范围内日期字体颜色
+			rangeColor: {
+				type: String,
+				default: '#2979ff'
+			},
+			// mode=range时生效,起始日期自定义文案
+			startText: {
+				type: String,
+				default: '开始'
+			},
+			// mode=range时生效,结束日期自定义文案
+			endText: {
+				type: String,
+				default: '结束'
+			},
+			//按钮样式类型
+			btnType: {
+				type: String,
+				default: 'primary'
+			},
+			// 当前选中日期带选中效果
+			isActiveCurrent: {
+				type: Boolean,
+				default: true
+			},
+			// 切换年月是否触发事件 mode=date时生效
+			isChange: {
+				type: Boolean,
+				default: false
+			},
+			// 是否显示右上角的关闭图标
+			closeable: {
+				type: Boolean,
+				default: true
+			},
+			// 顶部的提示文字
+			toolTip: {
+				type: String,
+				default: '选择日期'
+			}
+		},
+		data() {
+			return {
+				// 星期几,值为1-7
+				weekday: 1, 
+				weekdayArr:[],
+				// 当前月有多少天
+				days: 0, 
+				daysArr:[],
+				showTitle: '',
+				year: 2020,
+				month: 0,
+				day: 0,
+				startYear: 0,
+				startMonth: 0,
+				startDay: 0,
+				endYear: 0,
+				endMonth: 0,
+				endDay: 0,
+				today: '',
+				activeDate: '',
+				startDate: '',
+				endDate: '',
+				isStart: true,
+				min: null,
+				max: null,
+				weekDayZh: ['日', '一', '二', '三', '四', '五', '六']
+			};
+		},
+		computed: {
+			dataChange() {
+				return `${this.mode}-${this.minDate}-${this.maxDate}`;
+			},
+			uZIndex() {
+				// 如果用户有传递z-index值,优先使用
+				return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
+			}
+		},
+		watch: {
+			dataChange(val) {
+				this.init()
+			}
+		},
+		created() {
+			this.init()
+		},
+		methods: {
+			getColor(index, type) {
+				let color = type == 1 ? '' : this.color;
+				let day = index + 1
+				let date = `${this.year}-${this.month}-${day}`
+				let timestamp = new Date(date.replace(/\-/g, '/')).getTime();
+				let start = this.startDate.replace(/\-/g, '/')
+				let end = this.endDate.replace(/\-/g, '/')
+				if ((this.isActiveCurrent && this.activeDate == date) || this.startDate == date || this.endDate == date) {
+					color = type == 1 ? this.activeBgColor : this.activeColor;
+				} else if (this.endDate && timestamp > new Date(start).getTime() && timestamp < new Date(end).getTime()) {
+					color = type == 1 ? this.rangeBgColor : this.rangeColor;
+				}
+				return color;
+			},
+			init() {
+				let now = new Date();
+				let minDate = new Date(this.minDate);
+				let maxDate = new Date(this.maxDate);
+				if (now < minDate) now = minDate;
+				if (now > maxDate) now = maxDate;
+				this.year = now.getFullYear();
+				this.month = now.getMonth() + 1;
+				this.day = now.getDate();
+				this.today = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`;
+				this.activeDate = this.today;
+				this.min = this.initDate(this.minDate);
+				this.max = this.initDate(this.maxDate || this.today);
+				this.startDate = "";
+				this.startYear = 0;
+				this.startMonth = 0;
+				this.startDay = 0;
+				this.endYear = 0;
+				this.endMonth = 0;
+				this.endDay = 0;
+				this.endDate = "";
+				this.isStart = true;
+				this.changeData();
+			},
+			//日期处理
+			initDate(date) {
+				let fdate = date.split('-');
+				return {
+					year: Number(fdate[0] || 1920),
+					month: Number(fdate[1] || 1),
+					day: Number(fdate[2] || 1)
+				}
+			},
+			openDisAbled: function(year, month, day) {
+				let bool = true;
+				let date = `${year}/${month}/${day}`;
+				// let today = this.today.replace(/\-/g, '/');
+				let min = `${this.min.year}/${this.min.month}/${this.min.day}`;
+				let max = `${this.max.year}/${this.max.month}/${this.max.day}`;
+				let timestamp = new Date(date).getTime();
+				if (timestamp >= new Date(min).getTime() && timestamp <= new Date(max).getTime()) {
+					bool = false;
+				}
+				return bool;
+			},
+			generateArray: function(start, end) {
+				return Array.from(new Array(end + 1).keys()).slice(start);
+			},
+			formatNum: function(num) {
+				return num < 10 ? '0' + num : num + '';
+			},
+			//一个月有多少天
+			getMonthDay(year, month) {
+				let days = new Date(year, month, 0).getDate();
+				return days;
+			},
+			getWeekday(year, month) {
+				let date = new Date(`${year}/${month}/01 00:00:00`);
+				return date.getDay();
+			},
+			checkRange(year) {
+				let overstep = false;
+				if (year < this.minYear || year > this.maxYear) {
+					uni.showToast({
+						title: "日期超出范围啦~",
+						icon: 'none'
+					})
+					overstep = true;
+				}
+				return overstep;
+			},
+			changeMonthHandler(isAdd) {
+				if (isAdd) {
+					let month = this.month + 1;
+					let year = month > 12 ? this.year + 1 : this.year;
+					if (!this.checkRange(year)) {
+						this.month = month > 12 ? 1 : month;
+						this.year = year;
+						this.changeData();
+					}
+
+				} else {
+					let month = this.month - 1;
+					let year = month < 1 ? this.year - 1 : this.year;
+					if (!this.checkRange(year)) {
+						this.month = month < 1 ? 12 : month;
+						this.year = year;
+						this.changeData();
+					}
+				}
+			},
+			changeYearHandler(isAdd) {
+				let year = isAdd ? this.year + 1 : this.year - 1;
+				if (!this.checkRange(year)) {
+					this.year = year;
+					this.changeData();
+				}
+			},
+			changeData() {
+				this.days = this.getMonthDay(this.year, this.month);
+				this.daysArr=this.generateArray(1,this.days)
+				this.weekday = this.getWeekday(this.year, this.month);
+				this.weekdayArr=this.generateArray(1,this.weekday)
+				this.showTitle = `${this.year}年${this.month}月`;
+				if (this.isChange && this.mode == 'date') {
+					this.btnFix(true);
+				}
+			},
+			dateClick: function(day) {
+				day += 1;
+				if (!this.openDisAbled(this.year, this.month, day)) {
+					this.day = day;
+					let date = `${this.year}-${this.month}-${day}`;
+					if (this.mode == 'date') {
+						this.activeDate = date;
+					} else {
+						let compare = new Date(date.replace(/\-/g, '/')).getTime() < new Date(this.startDate.replace(/\-/g, '/')).getTime()
+						if (this.isStart || compare) {
+							this.startDate = date;
+							this.startYear = this.year;
+							this.startMonth = this.month;
+							this.startDay = this.day;
+							this.endYear = 0;
+							this.endMonth = 0;
+							this.endDay = 0;
+							this.endDate = "";
+							this.activeDate = "";
+							this.isStart = false;
+						} else {
+							this.endDate = date;
+							this.endYear = this.year;
+							this.endMonth = this.month;
+							this.endDay = this.day;
+							this.isStart = true;
+						}
+					}
+				}
+			},
+			close() {
+				// 修改通过v-model绑定的父组件变量的值为false,从而隐藏日历弹窗
+				this.$emit('input', false);
+			},
+			getWeekText(date) {
+				date = new Date(`${date.replace(/\-/g, '/')} 00:00:00`);
+				let week = date.getDay();
+				return '星期' + ['日', '一', '二', '三', '四', '五', '六'][week];
+			},
+			btnFix(show) {
+				if (!show) {
+					this.close();
+				}
+				if (this.mode == 'date') {
+					let arr = this.activeDate.split('-')
+					let year = this.isChange ? this.year : Number(arr[0]);
+					let month = this.isChange ? this.month : Number(arr[1]);
+					let day = this.isChange ? this.day : Number(arr[2]);
+					//当前月有多少天
+					let days = this.getMonthDay(year, month);
+					let result = `${year}-${this.formatNum(month)}-${this.formatNum(day)}`;
+					let weekText = this.getWeekText(result);
+					let isToday = false;
+					if (`${year}-${month}-${day}` == this.today) {
+						//今天
+						isToday = true;
+					}
+					this.$emit('change', {
+						year: year,
+						month: month,
+						day: day,
+						days: days,
+						result: result,
+						week: weekText,
+						isToday: isToday,
+						// switch: show //是否是切换年月操作
+					});
+				} else {
+					if (!this.startDate || !this.endDate) return;
+					let startMonth = this.formatNum(this.startMonth);
+					let startDay = this.formatNum(this.startDay);
+					let startDate = `${this.startYear}-${startMonth}-${startDay}`;
+					let startWeek = this.getWeekText(startDate)
+
+					let endMonth = this.formatNum(this.endMonth);
+					let endDay = this.formatNum(this.endDay);
+					let endDate = `${this.endYear}-${endMonth}-${endDay}`;
+					let endWeek = this.getWeekText(endDate);
+					this.$emit('change', {
+						startYear: this.startYear,
+						startMonth: this.startMonth,
+						startDay: this.startDay,
+						startDate: startDate,
+						startWeek: startWeek,
+						endYear: this.endYear,
+						endMonth: this.endMonth,
+						endDay: this.endDay,
+						endDate: endDate,
+						endWeek: endWeek
+					});
+				}
+			}
+		}
+	};
+</script>
+
+<style scoped lang="scss">
+	@import "../../libs/css/style.components.scss";
+	
+	.u-calendar {
+		color: $u-content-color;
+		
+		&__header {
+			width: 100%;
+			box-sizing: border-box;
+			font-size: 30rpx;
+			background-color: #fff;
+			color: $u-main-color;
+			
+			&__text {
+				margin-top: 30rpx;
+				padding: 0 60rpx;
+				@include vue-flex;
+				justify-content: center;
+				align-items: center;
+			}
+		}
+		
+		&__action {
+			padding: 40rpx 0 40rpx 0;
+			
+			&__icon {
+				margin: 0 16rpx;
+			}
+			
+			&__text {
+				padding: 0 16rpx;
+				color: $u-main-color;
+				font-size: 32rpx;
+				line-height: 32rpx;
+				font-weight: bold;
+			}
+		}
+	
+		&__week-day {
+			@include vue-flex;
+			align-items: center;
+			justify-content: center;
+			padding: 6px 0;
+			overflow: hidden;
+			
+			&__text {
+				flex: 1;
+				text-align: center;
+			}
+		}
+	
+		&__content {
+			width: 100%;
+			@include vue-flex;
+			flex-wrap: wrap;
+			padding: 6px 0;
+			box-sizing: border-box;
+			background-color: #fff;
+			position: relative;
+			
+			&--end-date {
+				border-top-right-radius: 8rpx;
+				border-bottom-right-radius: 8rpx;
+			}
+			
+			&--start-date {
+				border-top-left-radius: 8rpx;
+				border-bottom-left-radius: 8rpx;
+			}
+			
+			&__item {
+				width: 14.2857%;
+				@include vue-flex;
+				align-items: center;
+				justify-content: center;
+				padding: 6px 0;
+				overflow: hidden;
+				position: relative;
+				z-index: 2;
+				
+				&__inner {
+					height: 84rpx;
+					@include vue-flex;
+					align-items: center;
+					justify-content: center;
+					flex-direction: column;
+					font-size: 32rpx;
+					position: relative;
+					border-radius: 50%;
+					
+					&__desc {
+						width: 100%;
+						font-size: 24rpx;
+						line-height: 24rpx;
+						transform: scale(0.75);
+						transform-origin: center center;
+						position: absolute;
+						left: 0;
+						text-align: center;
+						bottom: 2rpx;
+					}
+				}
+				
+				&__tips {
+					width: 100%;
+					font-size: 24rpx;
+					line-height: 24rpx;
+					position: absolute;
+					left: 0;
+					transform: scale(0.8);
+					transform-origin: center center;
+					text-align: center;
+					bottom: 8rpx;
+					z-index: 2;
+				}
+			}
+			
+			&__bg-month {
+				position: absolute;
+				font-size: 130px;
+				line-height: 130px;
+				left: 50%;
+				top: 50%;
+				transform: translate(-50%, -50%);
+				color: #e4e7ed;
+				z-index: 1;
+			}
+		}
+	
+		&__bottom {
+			width: 100%;
+			@include vue-flex;
+			align-items: center;
+			justify-content: center;
+			flex-direction: column;
+			background-color: #fff;
+			padding: 0 40rpx 30rpx;
+			box-sizing: border-box;
+			font-size: 24rpx;
+			color: $u-tips-color;
+			
+			&__choose {
+				height: 50rpx;
+			}
+			
+			&__btn {
+				width: 100%;
+			}
+		}
+	}
+</style>

+ 1 - 1
uview-ui/components/u-card/u-card.vue

@@ -23,7 +23,7 @@
 					<image
 						:src="thumb"
 						class="u-card__head--left__thumb"
-						mode="aspectfull"
+						mode="aspectFill"
 						v-if="thumb"
 						:style="{ 
 							height: thumbWidth + 'rpx', 

+ 2 - 0
uview-ui/components/u-dropdown/u-dropdown.vue

@@ -172,6 +172,8 @@
 			},
 			// 打开下拉菜单
 			open(index) {
+				// 嵌套popup使用时可能获取不到正确的高度,重新计算
+				if (this.contentHeight < 1) this.getContentHeight()
 				// 重置高亮索引,否则会造成多个菜单同时高亮
 				// this.highlightIndex = 9999;
 				// 展开时,设置下拉内容的样式

+ 2 - 1
uview-ui/components/u-image/u-image.vue

@@ -18,7 +18,7 @@
 			class="u-image__loading"
 			:style="{
 				borderRadius: shape == 'circle' ? '50%' : $u.addUnit(borderRadius),
-				backgroundColor: this.bgColor
+				backgroundColor: bgColor
 			}"
 		>
 			<slot v-if="$slots.loading" name="loading" />
@@ -172,6 +172,7 @@ export default {
 					this.loading = false;
 				} else {
 					this.isError = false;
+					this.loading = true;
 				}
 			}
 		}

+ 37 - 37
uview-ui/components/u-input/u-input.vue

@@ -28,6 +28,7 @@
 			:selection-start="uSelectionStart"
 			:cursor-spacing="getCursorSpacing"
 			:show-confirm-bar="showConfirmbar"
+      :adjust-position="adjustPosition"
 			@input="handleInput"
 			@blur="handleBlur"
 			@focus="onFocus"
@@ -50,6 +51,7 @@
 			:selection-end="uSelectionEnd"
 			:selection-start="uSelectionStart"
 			:show-confirm-bar="showConfirmbar"
+			:adjust-position="adjustPosition"
 			@focus="onFocus"
 			@blur="handleBlur"
 			@input="handleInput"
@@ -57,19 +59,15 @@
 		/>
 		<view class="u-input__right-icon u-flex">
 			<view class="u-input__right-icon__clear u-input__right-icon__item" @tap="onClear" v-if="clearable && value != '' && focused">
-				<u-icon size="32" name="close-circle-fill" color="#c0c4cc" />
+				<u-icon size="32" name="close-circle-fill" color="#c0c4cc"/>
 			</view>
 			<view class="u-input__right-icon__clear u-input__right-icon__item" v-if="passwordIcon && type == 'password'">
-				<u-icon size="32" :name="!showPassword ? 'eye' : 'eye-fill'" color="#c0c4cc" @click="showPassword = !showPassword" />
+				<u-icon size="32" :name="!showPassword ? 'eye' : 'eye-fill'" color="#c0c4cc" @click="showPassword = !showPassword"/>
 			</view>
-			<view
-				class="u-input__right-icon--select u-input__right-icon__item"
-				v-if="type == 'select'"
-				:class="{
-					'u-input__right-icon--select--reverse': selectOpen
-				}"
-			>
-				<u-icon :name="selectIconName" size="26" color="#c0c4cc"></u-icon>
+			<view class="u-input__right-icon--select u-input__right-icon__item" v-if="type == 'select'" :class="{
+				'u-input__right-icon--select--reverse': selectOpen
+			}">
+				<u-icon name="arrow-down-fill" size="26" color="#c0c4cc"></u-icon>
 			</view>
 		</view>
 	</view>
@@ -214,14 +212,14 @@ export default {
 			default: true
 		},
 		// 是否显示键盘上方带有”完成“按钮那一栏
-		showConfirmbar: {
+		showConfirmbar:{
+			type:Boolean,
+			default:true
+		},
+		// 弹出键盘时是否自动调节高度,uni-app默认值是true
+		adjustPosition: {
 			type: Boolean,
 			default: true
-		},
-		// 选择类型的右侧图标样式
-		selectIconName: {
-			type: String,
-			default: 'arrow-right'
 		}
 	},
 	data() {
@@ -232,20 +230,19 @@ export default {
 			validateState: false, // 当前input的验证状态,用于错误时,边框是否改为红色
 			focused: false, // 当前是否处于获得焦点的状态
 			showPassword: false, // 是否预览密码
-			lastValue: '' // 用于头条小程序,判断@input中,前后的值是否发生了变化,因为头条中文下,按下键没有输入内容,也会触发@input时间
+			lastValue: '', // 用于头条小程序,判断@input中,前后的值是否发生了变化,因为头条中文下,按下键没有输入内容,也会触发@input时间
 		};
 	},
 	watch: {
 		value(nVal, oVal) {
 			this.defaultValue = nVal;
 			// 当值发生变化,且为select类型时(此时input被设置为disabled,不会触发@input事件),模拟触发@input事件
-			if (nVal != oVal && this.type == 'select')
-				this.handleInput({
-					detail: {
-						value: nVal
-					}
-				});
-		}
+			if(nVal != oVal && this.type == 'select') this.handleInput({
+				detail: {
+					value: nVal
+				}
+			})
+		},
 	},
 	computed: {
 		// 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,给用户可以传入字符串数值
@@ -254,8 +251,9 @@ export default {
 		},
 		getStyle() {
 			let style = {};
-			// 如果没有自定义高度,就根据type为input还是textare来分配一个默认的高度
-			style.minHeight = this.height ? this.height + 'rpx' : this.type == 'textarea' ? this.textareaHeight + 'rpx' : this.inputHeight + 'rpx';
+			// 如果没有自定义高度,就根据type为input还是textarea来分配一个默认的高度
+			style.minHeight = this.height ? this.height + 'rpx' : this.type == 'textarea' ?
+				this.textareaHeight + 'rpx' : this.inputHeight + 'rpx';
 			style = Object.assign(style, this.customStyle);
 			return style;
 		},
@@ -284,7 +282,7 @@ export default {
 		handleInput(event) {
 			let value = event.detail.value;
 			// 判断是否去除空格
-			if (this.trim) value = this.$u.trim(value);
+			if(this.trim) value = this.$u.trim(value);
 			// vue 原生的方法 return 出去
 			this.$emit('input', value);
 			// 当前model 赋值
@@ -295,12 +293,12 @@ export default {
 			setTimeout(() => {
 				// 头条小程序由于自身bug,导致中文下,每按下一个键(尚未完成输入),都会触发一次@input,导致错误,这里进行判断处理
 				// #ifdef MP-TOUTIAO
-				if (this.$u.trim(value) == this.lastValue) return;
+				if(this.$u.trim(value) == this.lastValue) return ;
 				this.lastValue = value;
 				// #endif
 				// 将当前的值发送到 u-form-item 进行校验
 				this.dispatch('u-form-item', 'on-form-change', value);
-			}, 40);
+			}, 40)
 		},
 		/**
 		 * blur 事件
@@ -309,20 +307,21 @@ export default {
 		handleBlur(event) {
 			// 最开始使用的是监听图标@touchstart事件,自从hx2.8.4后,此方法在微信小程序出错
 			// 这里改为监听点击事件,手点击清除图标时,同时也发生了@blur事件,导致图标消失而无法点击,这里做一个延时
+			let value = event.detail.value;
 			setTimeout(() => {
 				this.focused = false;
-			}, 100);
+			}, 100)
 			// vue 原生的方法 return 出去
-			this.$emit('blur', event.detail.value);
+			this.$emit('blur', value);
 			setTimeout(() => {
 				// 头条小程序由于自身bug,导致中文下,每按下一个键(尚未完成输入),都会触发一次@input,导致错误,这里进行判断处理
 				// #ifdef MP-TOUTIAO
-				if (this.$u.trim(value) == this.lastValue) return;
+				if(this.$u.trim(value) == this.lastValue) return ;
 				this.lastValue = value;
 				// #endif
 				// 将当前的值发送到 u-form-item 进行校验
-				this.dispatch('u-form-item', 'on-form-blur', event.detail.value);
-			}, 40);
+				this.dispatch('u-form-item', 'on-form-blur', value);
+			}, 40)
 		},
 		onFormItemError(status) {
 			this.validateState = status;
@@ -345,7 +344,7 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-@import '../../libs/css/style.components.scss';
+@import "../../libs/css/style.components.scss";
 
 .u-input {
 	position: relative;
@@ -375,16 +374,17 @@ export default {
 	}
 
 	&--error {
-		border-color: $u-type-error !important;
+		border-color: $u-type-error!important;
 	}
 
 	&__right-icon {
+
 		&__item {
 			margin-left: 10rpx;
 		}
 
 		&--select {
-			transition: transform 0.4s;
+			transition: transform .4s;
 
 			&--reverse {
 				transform: rotate(-180deg);

+ 3 - 3
uview-ui/components/u-line-progress/u-line-progress.vue

@@ -10,9 +10,9 @@
 			striped && stripedActive ? 'u-striped-active' : ''
 		]" class="u-active" :style="[progressStyle]">
 			<slot v-if="$slots.default || $slots.$default" />
-			<block v-else-if="showPercent">
+			<template v-else-if="showPercent">
 				{{percent + '%'}}
-			</block>
+			</template>
 		</view>
 	</view>
 </template>
@@ -102,7 +102,7 @@
 
 <style lang="scss" scoped>
 	@import "../../libs/css/style.components.scss";
-	
+
 	.u-progress {
 		overflow: hidden;
 		height: 15px;

+ 1 - 1
uview-ui/components/u-loading/u-loading.vue

@@ -73,7 +73,7 @@
 		height: 20px;
 		display: inline-block;
 		vertical-align: middle;
-		-webkit-animation: a 1s steps(12) infinite;
+		-webkit-animation: u-flower 1s steps(12) infinite;
 		animation: u-flower 1s steps(12) infinite;
 		background: transparent url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAiIGhlaWdodD0iMTIwIiB2aWV3Qm94PSIwIDAgMTAwIDEwMCI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgxMDB2MTAwSDB6Ii8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTlFOUU5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgLTMwKSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iIzk4OTY5NyIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgzMCAxMDUuOTggNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjOUI5OTlBIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDYwIDc1Ljk4IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0EzQTFBMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSg5MCA2NSA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNBQkE5QUEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoMTIwIDU4LjY2IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0IyQjJCMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgxNTAgNTQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjQkFCOEI5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDE4MCA1MCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDMkMwQzEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTE1MCA0NS45OCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDQkNCQ0IiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTEyMCA0MS4zNCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNEMkQyRDIiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTkwIDM1IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0RBREFEQSIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgtNjAgMjQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTJFMkUyIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKC0zMCAtNS45OCA2NSkiLz48L3N2Zz4=) no-repeat;
 		background-size: 100%;

+ 2 - 2
uview-ui/components/u-message-input/u-message-input.vue

@@ -21,7 +21,7 @@
 					></view>
 					<view v-if="mode === 'middleLine' && charArrLength <= index" :class="[breathe && charArrLength == index ? 'u-breathe' : '', charArrLength === index ? 'u-middle-line-active' : '']"
 					 class="u-middle-line" :style="{height: bold ? '4px' : '2px', background: charArrLength === index ? activeColor : inactiveColor}"></view>
-					<view v-if="mode === 'bottomLine'" :class="[breathe && charArrLength == index ? 'u-breathe' : '', charArrLength === index ? 'u-buttom-line-active' : '']"
+					<view v-if="mode === 'bottomLine'" :class="[breathe && charArrLength == index ? 'u-breathe' : '', charArrLength === index ? 'u-bottom-line-active' : '']"
 					 class="u-bottom-line" :style="{height: bold ? '4px' : '2px', background: charArrLength === index ? activeColor : inactiveColor}"></view>
 					<block v-if="!dotFill"> {{ charArr[index] ? charArr[index] : ''}}</block>
 					<block v-else>
@@ -294,7 +294,7 @@
 		transform: translate(-50%, -50%);
 	}
 
-	.u-buttom-line-active {
+	.u-bottom-line-active {
 		background: $u-type-primary;
 	}
 

+ 1 - 1
uview-ui/components/u-modal/u-modal.vue

@@ -44,7 +44,7 @@
 	 * @property {Boolean} show-title 是否显示标题(默认true)
 	 * @property {Boolean} async-close 是否异步关闭,只对确定按钮有效(默认false)
 	 * @property {Boolean} show-confirm-button 是否显示确认按钮(默认true)
-	 * @property {Stringr | Number} negative-top modal往上偏移的值
+	 * @property {String | Number} negative-top modal往上偏移的值
 	 * @property {Boolean} show-cancel-button 是否显示取消按钮(默认false)
 	 * @property {Boolean} mask-close-able 是否允许点击遮罩关闭modal(默认false)
 	 * @property {String} confirm-text 确认按钮的文字内容(默认"确认")

+ 2 - 2
uview-ui/components/u-notice-bar/u-notice-bar.vue

@@ -68,8 +68,8 @@
  * @property {String Number} font-size 字体大小,单位rpx(默认28)
  * @property {Boolean} is-circular mode为horizontal时,指明是否水平衔接滚动(默认true)
  * @property {String} play-state 播放状态,play - 播放,paused - 暂停(默认play)
- * @property {String Nubmer} border-radius 通知栏圆角(默认为0)
- * @property {String Nubmer} padding 内边距,字符串,与普通的内边距css写法一直(默认"18rpx 24rpx")
+ * @property {String Number} border-radius 通知栏圆角(默认为0)
+ * @property {String Number} padding 内边距,字符串,与普通的内边距css写法一直(默认"18rpx 24rpx")
  * @property {Boolean} no-list-hidden 列表为空时,是否显示组件(默认false)
  * @property {Boolean} disable-touch 是否禁止通过手动滑动切换通知,只有mode = vertical,或者mode = horizontal且is-circular = false时有效(默认true)
  * @event {Function} click 点击通告文字触发,只有mode = vertical,或者mode = horizontal且is-circular = false时有效

+ 4 - 8
uview-ui/components/u-number-box/u-number-box.vue

@@ -10,10 +10,10 @@
 		</view>
 		<input :disabled="disabledInput || disabled" :cursor-spacing="getCursorSpacing" :class="{ 'u-input-disabled': disabled }"
 		    v-model="inputVal" class="u-number-input" @blur="onBlur" @focus="onFocus"
-		    type="number" :style="{
-				color: '#333',
+		    type="digit" :style="{
+				color: color,
 				fontSize: size + 'rpx',
-				background: '#f9f9f9',
+				background: bgColor,
 				height: inputHeight + 'rpx',
 				width: inputWidth + 'rpx'
 			}" />
@@ -333,18 +333,14 @@
 		@include vue-flex;
 		align-items: center;
 		justify-content: center;
-		border: 1px solid #f0f0f0;
-		margin: 0 14rpx;
-		border-radius: 4rpx;
 	}
 
 	.u-icon-plus,
 	.u-icon-minus {
-		width: 52rpx;
+		width: 60rpx;
 		@include vue-flex;
 		justify-content: center;
 		align-items: center;
-		border-radius: 6rpx !important;
 	}
 
 	.u-icon-plus {

+ 0 - 6
uview-ui/components/u-popup/u-popup.vue

@@ -193,11 +193,6 @@ export default {
 		duration: {
 			type: [String, Number],
 			default: 250
-		},
-		// 背景颜色
-		backgroundColor: {
-			type: String,
-			default: '#ffffff'
 		}
 	},
 	data() {
@@ -258,7 +253,6 @@ export default {
 			style.height = this.height ? this.getUnitValue(this.height) : 'auto';
 			style.zIndex = this.uZindex;
 			style.marginTop = `-${this.$u.addUnit(this.negativeTop)}`;
-			style.backgroundColor = this.backgroundColor;
 			if (this.borderRadius) {
 				style.borderRadius = `${this.borderRadius}rpx`;
 				// 不加可能圆角无效

+ 1 - 1
uview-ui/components/u-radio-group/u-radio-group.vue

@@ -7,7 +7,7 @@
 <script>
 	import Emitter from '../../libs/util/emitter.js';
 	/**
-	 * radioRroup 单选框父组件
+	 * radioGroup 单选框父组件
 	 * @description 单选框用于有一个选择,用户只能选择其中一个的场景。搭配u-radio使用
 	 * @tutorial https://www.uviewui.com/components/radio.html
 	 * @property {Boolean} disabled 是否禁用所有radio(默认false)

+ 1 - 1
uview-ui/components/u-radio/u-radio.vue

@@ -94,7 +94,7 @@
 			this.parent.children.push(this);
 		},
 		computed: {
-			// 是否禁用,如果父组件u-raios-group禁用的话,将会忽略子组件的配置
+			// 是否禁用,如果父组件u-radio-group禁用的话,将会忽略子组件的配置
 			elDisabled() {
 				return this.disabled !== '' ? this.disabled : this.parentData.disabled !== null ? this.parentData.disabled : false;
 			},

+ 27 - 35
uview-ui/components/u-search/u-search.vue

@@ -1,21 +1,19 @@
 <template>
-	<view
-		class="u-search"
-		@tap="clickHandler"
-		:style="{
-			margin: margin
-		}"
-	>
+	<view class="u-search" @tap="clickHandler" :style="{
+		margin: margin,
+	}">
 		<view
 			class="u-content"
 			:style="{
 				backgroundColor: bgColor,
-				borderRadius: shape == 'round' ? (borderRadius ? borderRadius : '100rpx') : borderRadius ? borderRadius : '10rpx',
+				borderRadius: shape == 'round' ? '100rpx' : '10rpx',
 				border: borderStyle,
 				height: height + 'rpx'
 			}"
 		>
-			<view class="u-icon-wrap"><u-icon class="u-clear-icon" :size="30" :name="searchIcon" :color="searchIconColor ? searchIconColor : color"></u-icon></view>
+			<view class="u-icon-wrap">
+				<u-icon class="u-clear-icon" :size="30" :name="searchIcon" :color="searchIconColor ? searchIconColor : color"></u-icon>
+			</view>
 			<input
 				confirm-type="search"
 				@blur="blur"
@@ -31,20 +29,20 @@
 				:placeholder-style="`color: ${placeholderColor}`"
 				class="u-input"
 				type="text"
-				:style="[
-					{
-						textAlign: inputAlign,
-						color: color,
-						backgroundColor: bgColor
-					},
-					inputStyle
-				]"
+				:style="[{
+					textAlign: inputAlign,
+					color: color,
+					backgroundColor: bgColor,
+				}, inputStyle]"
 			/>
 			<view class="u-close-wrap" v-if="keyword && clearabled && focused" @tap="clear">
 				<u-icon class="u-clear-icon" name="close-circle-fill" size="34" color="#c0c4cc"></u-icon>
 			</view>
 		</view>
-		<view :style="[actionStyle]" class="u-action" :class="[showActionBtn || show ? 'u-action-active' : '']" @tap.stop.prevent="custom">{{ actionText }}</view>
+		<view :style="[actionStyle]" class="u-action" 
+			:class="[showActionBtn || show ? 'u-action-active' : '']" 
+			@tap.stop.prevent="custom"
+		>{{ actionText }}</view>
 	</view>
 </template>
 
@@ -75,7 +73,6 @@
  * @property {String | Number} maxlength 输入框最大能输入的长度,-1为不限制长度
  * @property {Boolean} input-style input输入框的样式,可以定义文字颜色,大小等,对象形式
  * @property {String | Number} height 输入框高度,单位rpx(默认64)
- * @property {String} borderRadius 圆角大小,默认为空
  * @event {Function} change 输入框内容发生变化时触发
  * @event {Function} search 用户确定搜索时触发,用户按回车键,或者手机键盘右下角的"搜索"键时触发
  * @event {Function} custom 用户点击右侧控件时触发
@@ -83,7 +80,7 @@
  * @example <u-search placeholder="日照香炉生紫烟" v-model="keyword"></u-search>
  */
 export default {
-	name: 'u-search',
+	name: "u-search",
 	props: {
 		// 搜索框形状,round-圆形,square-方形
 		shape: {
@@ -161,7 +158,7 @@ export default {
 		inputStyle: {
 			type: Object,
 			default() {
-				return {};
+				return {}
 			}
 		},
 		// 输入框最大能输入的长度,-1为不限制长度(来自uniapp文档)
@@ -193,11 +190,6 @@ export default {
 		searchIcon: {
 			type: String,
 			default: 'search'
-		},
-		// 圆角大小
-		borderRadius: {
-			type: String,
-			default: ''
 		}
 	},
 	data() {
@@ -234,7 +226,7 @@ export default {
 		borderStyle() {
 			if (this.borderColor) return `1px solid ${this.borderColor}`;
 			else return 'none';
-		}
+		},
 	},
 	methods: {
 		// 目前HX2.6.9 v-model双向绑定无效,故监听input事件获取输入框内容的变化
@@ -248,23 +240,23 @@ export default {
 			// 延后发出事件,避免在父组件监听clear事件时,value为更新前的值(不为空)
 			this.$nextTick(() => {
 				this.$emit('clear');
-			});
+			})
 		},
 		// 确定搜索
 		search(e) {
 			this.$emit('search', e.detail.value);
-			try {
+			try{
 				// 收起键盘
 				uni.hideKeyboard();
-			} catch (e) {}
+			}catch(e){}
 		},
 		// 点击右边自定义按钮的事件
 		custom() {
 			this.$emit('custom', this.keyword);
-			try {
+			try{
 				// 收起键盘
 				uni.hideKeyboard();
-			} catch (e) {}
+			}catch(e){}
 		},
 		// 获取焦点
 		getFocus() {
@@ -279,20 +271,20 @@ export default {
 			// 这里改为监听点击事件,手点击清除图标时,同时也发生了@blur事件,导致图标消失而无法点击,这里做一个延时
 			setTimeout(() => {
 				this.focused = false;
-			}, 100);
+			}, 100)
 			this.show = false;
 			this.$emit('blur', this.keyword);
 		},
 		// 点击搜索框,只有disabled=true时才发出事件,因为禁止了输入,意味着是想跳转真正的搜索页
 		clickHandler() {
-			if (this.disabled) this.$emit('click');
+			if(this.disabled) this.$emit('click');
 		}
 	}
 };
 </script>
 
 <style lang="scss" scoped>
-@import '../../libs/css/style.components.scss';
+@import "../../libs/css/style.components.scss";
 
 .u-search {
 	@include vue-flex;

+ 6 - 3
uview-ui/components/u-select/u-select.vue

@@ -36,7 +36,7 @@
 					</view>
 				</view>
 				<view class="u-select__body">
-					<picker-view @change="columnChange" class="u-select__body__picker-view" :value="defaultSelector" @pickstart="pickstart" @pickend="pickend">
+					<picker-view @change="columnChange" class="u-select__body__picker-view" :value="defaultSelector" @pickstart="pickstart" @pickend="pickend" v-if="value">
 						<picker-view-column v-for="(item, index) in columnData" :key="index">
 							<view class="u-select__body__picker-view__item" v-for="(item1, index1) in item" :key="index1">
 								<view class="u-line-1">{{ item1[labelName] }}</view>
@@ -268,7 +268,7 @@ export default {
 					label: tmp ? tmp[this.labelName] : null
 				};
 				// 判断是否存在额外的参数,如果存在,就返回
-				if(tmp && tmp.extra) data.extra = tmp.extra;
+				if(tmp && tmp.extra !== undefined) data.extra = tmp.extra;
 				this.selectValue.push(data)
 			}
 		},
@@ -278,12 +278,13 @@ export default {
 			let columnIndex = e.detail.value;
 			// 由于后面是需要push进数组的,所以需要先清空数组
 			this.selectValue = [];
+			this.defaultSelector = columnIndex;
 			if(this.mode == 'mutil-column-auto') {
 				// 对比前后两个数组,寻找变更的是哪一列,如果某一个元素不同,即可判定该列发生了变化
 				this.lastSelectIndex.map((val, idx) => {
 					if (val != columnIndex[idx]) index = idx;
 				});
-				this.defaultSelector = columnIndex;
+				
 				for (let i = index + 1; i < this.columnNum; i++) {
 					// 当前变化列的下一列的数据,需要获取上一列的数据,同时需要指定是上一列的第几个的children,再往后的
 					// 默认是队列的第一个为默认选项
@@ -333,6 +334,8 @@ export default {
 		},
 		close() {
 			this.$emit('input', false);
+			// 重置default-value默认值
+			this.$set(this, 'defaultSelector', [0]);
 		},
 		// 点击确定或者取消
 		getResult(event = null) {

+ 2 - 4
uview-ui/components/u-slider/u-slider.vue

@@ -12,8 +12,8 @@
 				}
 			]"
 		>
-			<view class="u-slider__button-wrap" @touchstart="onTouchStart" 
-				@touchmove="onTouchMove" @touchend="onTouchEnd" 
+			<view class="u-slider__button-wrap" @touchstart="onTouchStart"
+				@touchmove="onTouchMove" @touchend="onTouchEnd"
 				@touchcancel="onTouchEnd">
 				<slot v-if="$slots.default  || $slots.$default"/>
 				<view v-else class="u-slider__button" :style="[blockStyle, {
@@ -213,7 +213,6 @@ export default {
 .u-slider {
 	position: relative;
 	border-radius: 999px;
-	border-radius: 999px;
 	background-color: #ebedf0;
 }
 
@@ -231,7 +230,6 @@ export default {
 	position: relative;
 	border-radius: inherit;
 	transition: width 0.2s;
-	transition: width 0.2s;
 	background-color: #1989fa;
 }
 

+ 255 - 0
uview-ui/components/u-swipe-action/u-swipe-action.vue

@@ -0,0 +1,255 @@
+<template>
+	<view class="">
+		<movable-area class="u-swipe-action" :style="{ backgroundColor: bgColor }">
+			<movable-view
+				class="u-swipe-view"
+				@change="change"
+				@touchend="touchend"
+				@touchstart="touchstart"
+				direction="horizontal"
+				:disabled="disabled"
+				:x="moveX"
+				:style="{
+					width: movableViewWidth ? movableViewWidth : '100%'
+				}"
+			>
+				<view
+					class="u-swipe-content"
+					@tap.stop="contentClick"
+				>
+					<slot></slot>
+				</view>
+				<view class="u-swipe-del" v-if="showBtn" @tap.stop="btnClick(index)" :style="[btnStyle(item.style)]" v-for="(item, index) in options" :key="index">
+					<view class="u-btn-text">{{ item.text }}</view>
+				</view>
+			</movable-view>
+		</movable-area>
+	</view>
+</template>
+
+<script>
+/**
+ * swipeAction 左滑单元格
+ * @description 该组件一般用于左滑唤出操作菜单的场景,用的最多的是左滑删除操作。
+ * @tutorial https://www.uviewui.com/components/swipeAction.html
+ * @property {String} bg-color 整个组件背景颜色(默认#ffffff)
+ * @property {Array} options 数组形式,可以配置背景颜色和文字
+ * @property {String Number} index 标识符,点击时候用于区分点击了哪一个,用v-for循环时的index即可
+ * @property {String Number} btn-width 按钮宽度,单位rpx(默认180)
+ * @property {Boolean} disabled 是否禁止某个swipeAction滑动(默认false)
+ * @property {Boolean} show 打开或者关闭某个组件(默认false)
+ * @event {Function} click 点击组件时触发
+ * @event {Function} close 组件触发关闭状态时
+ * @event {Function} content-click 点击内容时触发
+ * @event {Function} open 组件触发打开状态时
+ * @example <u-swipe-action btn-text="收藏">...</u-swipe-action>
+ */
+export default {
+	name: 'u-swipe-action',
+	props: {
+		// index值,用于得知点击删除的是哪个按钮
+		index: {
+			type: [Number, String],
+			default: ''
+		},
+		// 滑动按钮的宽度,单位为rpx
+		btnWidth: {
+			type: [String, Number],
+			default: 180
+		},
+		// 是否禁止某个action滑动
+		disabled: {
+			type: Boolean,
+			default: false
+		},
+		// 打开或者关闭组件
+		show: {
+			type: Boolean,
+			default: false
+		},
+		// 组件背景颜色
+		bgColor: {
+			type: String,
+			default: '#ffffff'
+		},
+		// 是否使手机发生短促震动,目前只在iOS的微信小程序有效(2020-05-06)
+		vibrateShort: {
+			type: Boolean,
+			default: false
+		},
+		// 按钮操作参数
+		options: {
+			type: Array,
+			default() {
+				return [];
+			}
+		}
+	},
+	watch: {
+		show: {
+			immediate: true,
+			handler(nVal, oVal) {
+				if (nVal) {
+					this.open();
+				} else {
+					this.close();
+				}
+			}
+		}
+	},
+	data() {
+		return {
+			moveX: 0, // movable-view元素在x轴上需要移动的目标移动距离,用于展开或收起滑动的按钮
+			scrollX: 0, // movable-view移动过程中产生的change事件中的x轴移动值
+			status: false, // 滑动的状态,表示当前是展开还是关闭按钮的状态
+			movableAreaWidth: 0, // 滑动区域
+			elId: this.$u.guid(), // id,用于通知另外组件关闭时的识别
+			showBtn: false, // 刚开始渲染视图时不显示右边的按钮,避免视图闪动
+		};
+	},
+	computed: {
+		movableViewWidth() {
+			return this.movableAreaWidth + this.allBtnWidth + 'px';
+		},
+		innerBtnWidth() {
+			return uni.upx2px(this.btnWidth);
+		},
+		allBtnWidth() {
+			return uni.upx2px(this.btnWidth) * this.options.length;
+		},
+		btnStyle() {
+			return style => {
+				let css = {};
+				style.width = this.btnWidth + 'rpx';
+				return style;
+			};
+		}
+	},
+	mounted() {
+		this.getActionRect();
+	},
+	methods: {
+		// 点击按钮
+		btnClick(index) {
+			this.status = false;
+			// this.index为点击的几个组件,index为点击某个组件的第几个按钮(options数组的索引)
+			this.$emit('click', this.index, index);
+		},
+		// movable-view元素移动事件
+		change(e) {
+			this.scrollX = e.detail.x;
+		},
+		// 关闭按钮状态
+		close() {
+			this.moveX = 0;
+			this.status = false;
+		},
+		// 打开按钮的状态
+		open() {
+			if (this.disabled) return;
+			this.moveX = -this.allBtnWidth;
+			this.status = true;
+		},
+		// 用户手指离开movable-view元素,停止触摸
+		touchend() {
+			this.moveX = this.scrollX;
+			// 停止触摸时候,判断当前是展开还是关闭状态
+			// 关闭状态
+			// 这一步很重要,需要先给this.moveX一个变化的随机值,否则因为前后设置的为同一个值
+			// props单向数据流的原因,导致movable-view元素不会发生变化,切记,详见文档:
+			// https://uniapp.dcloud.io/use?id=%e5%b8%b8%e8%a7%81%e9%97%ae%e9%a2%98
+			this.$nextTick(function() {
+				if (this.status == false) {
+					// 关闭状态左滑,产生的x轴位移为负值,也就是说滑动的距离大于按钮的四分之一宽度,自动展开按钮
+					if (this.scrollX <= -this.allBtnWidth / 4) {
+						this.moveX = -this.allBtnWidth; // 按钮宽度的负值,即为展开状态movable-view元素左滑的距离
+						this.status = true; // 标志当前为展开状态
+						this.emitOpenEvent();
+						// 产生震动效果
+						if (this.vibrateShort) uni.vibrateShort();
+					} else {
+						this.moveX = 0; // 如果距离没有按钮宽度的四分之一,自动收起
+						this.status = false;
+						this.emitCloseEvent();
+					}
+				} else {
+					// 如果在打开的状态下,右滑动的距离X轴偏移超过按钮的四分之一(负值反过来的四分之三),自动收起按钮
+					if (this.scrollX > (-this.allBtnWidth * 3) / 4) {
+						this.moveX = 0;
+						this.$nextTick(() => {
+							this.moveX = 101;
+						});
+						this.status = false;
+						this.emitCloseEvent();
+					} else {
+						this.moveX = -this.allBtnWidth;
+						this.status = true;
+						this.emitOpenEvent();
+					}
+				}
+			});
+		},
+		emitOpenEvent() {
+			this.$emit('open', this.index);
+		},
+		emitCloseEvent() {
+			this.$emit('close', this.index);
+		},
+		// 开始触摸
+		touchstart() {},
+		getActionRect() {
+			this.$uGetRect('.u-swipe-action').then(res => {
+				this.movableAreaWidth = res.width;
+				// 等视图更新完后,再显示右边的可滑动按钮,防止这些按钮会"闪一下"
+				this.$nextTick(() => {
+					this.showBtn = true;
+				})
+			});
+		},
+		// 点击内容触发事件
+		contentClick() {
+			// 点击内容时,如果当前为打开状态,收起组件
+			if (this.status == true) {
+				this.status = 'close';
+				this.moveX = 0;
+			}
+			this.$emit('content-click', this.index);
+		}
+	}
+};
+</script>
+
+<style scoped lang="scss">
+@import "../../libs/css/style.components.scss";
+	
+.u-swipe-action {
+	width: auto;
+	height: initial;
+	position: relative;
+	overflow: hidden;
+}
+
+.u-swipe-view {
+	@include vue-flex;
+	height: initial;
+	position: relative;
+	/* 这一句很关键,覆盖默认的绝对定位 */
+}
+
+.u-swipe-content {
+	flex: 1;
+}
+
+.u-swipe-del {
+	position: relative;
+	font-size: 30rpx;
+	color: #ffffff;
+}
+
+.u-btn-text {
+	position: absolute;
+	top: 50%;
+	left: 50%;
+	transform: translate(-50%, -50%);
+}
+</style>

+ 3 - 3
uview-ui/components/u-tabs-swiper/u-tabs-swiper.vue

@@ -4,7 +4,7 @@
 			background: bgColor
 		}">
 		<scroll-view scroll-x class="u-scroll-view" :scroll-left="scrollLeft" scroll-with-animation :style="{ zIndex: zIndex + 1 }">
-			<view class="u-tabs-scroll-box" :class="{'u-tabs-scorll-flex': !isScroll}">
+			<view class="u-tabs-scroll-box" :class="{'u-tabs-scroll-flex': !isScroll}">
 				<view class="u-tabs-item" :style="[tabItemStyle(index)]"
 				 v-for="(item, index) in getTabs" :key="index" :class="[preId + index]" @tap="emit(index)">
 					<u-badge :count="item[count] || item['count'] || 0" :offset="offset" size="mini"></u-badge>
@@ -428,12 +428,12 @@
 		position: relative;
 	}
 
-	.u-tabs-scorll-flex {
+	.u-tabs-scroll-flex {
 		@include vue-flex;
 		justify-content: space-between;
 	}
 
-	.u-tabs-scorll-flex .u-tabs-item {
+	.u-tabs-scroll-flex .u-tabs-item {
 		flex: 1;
 	}
 

+ 4 - 4
uview-ui/components/u-tabs/u-tabs.vue

@@ -3,9 +3,9 @@
 		background: bgColor
 	}">
 		<!-- $u.getRect()对组件根节点无效,因为写了.in(this),故这里获取内层接点尺寸 -->
-		<view :id="id">
+		<view>
 			<scroll-view scroll-x class="u-scroll-view" :scroll-left="scrollLeft" scroll-with-animation>
-				<view class="u-scroll-box" :class="{'u-tabs-scorll-flex': !isScroll}">
+				<view class="u-scroll-box" :id="id" :class="{'u-tabs-scroll-flex': !isScroll}">
 					<view class="u-tab-item u-line-1" :id="'u-tab-item-' + index" v-for="(item, index) in list" :key="index" @tap="clickTab(index)"
 					 :style="[tabItemStyle(index)]">
 						<u-badge :count="item[count] || item['count'] || 0" :offset="offset" size="mini"></u-badge>
@@ -84,7 +84,7 @@
 			// 选中项的主题颜色
 			activeColor: {
 				type: String,
-				default: '#22ac38'
+				default: '#2979ff'
 			},
 			// 未选中项的颜色
 			inactiveColor: {
@@ -362,7 +362,7 @@
 		bottom: 0;
 	}
 
-	.u-tabs-scorll-flex {
+	.u-tabs-scroll-flex {
 		@include vue-flex;
 		justify-content: space-between;
 	}

+ 1 - 1
uview-ui/components/u-th/u-th.vue

@@ -39,7 +39,7 @@
 				style.padding = this.parent.padding;
 				style.borderBottom = `solid 1px ${this.parent.borderColor}`;
 				style.borderRight = `solid 1px ${this.parent.borderColor}`;
-				Object.assign(style, this.parent.style);
+				Object.assign(style, this.parent.thStyle);
 				this.thStyle = style;
 			}
 		}

+ 1 - 1
uview-ui/components/u-toast/u-toast.vue

@@ -48,7 +48,7 @@
 		},
 		computed: {
 			iconName() {
-				// 只有不为none,并且type为error|warning|succes|info时候,才显示图标
+				// 只有不为none,并且type为error|warning|success|info时候,才显示图标
 				if (['error', 'warning', 'success', 'info'].indexOf(this.tmpConfig.type) >= 0 && this.tmpConfig.icon) {
 					let icon = this.$u.type2icon(this.tmpConfig.type);
 					return icon;

+ 5 - 7
uview-ui/components/u-upload/u-upload.vue

@@ -21,7 +21,7 @@
 				<u-icon class="u-icon" :name="delIcon" size="20" :color="delColor"></u-icon>
 			</view>
 			<u-line-progress
-				v-if="showProgress && item.progress > 0 && !item.error"
+				v-if="showProgress && item.progress > 0 && item.progress != 100 && !item.error"
 				:show-percent="false"
 				height="16"
 				class="u-progress"
@@ -255,10 +255,6 @@ export default {
 		index: {
 			type: [Number, String],
 			default: ''
-		},
-		deleteConfirmBtnColor:{
-			type: String,
-			default: '#22ac38'
 		}
 	},
 	mounted() {},
@@ -425,6 +421,9 @@ export default {
 				name: this.name,
 				formData: this.formData,
 				header: this.header,
+				// #ifdef MP-ALIPAY
+				fileType:'image',
+				// #endif
 				success: res => {
 					// 判断是否json字符串,将其转为json格式
 					let data = this.toJson && this.$u.test.jsonString(res.data) ? JSON.parse(res.data) : res.data;
@@ -468,7 +467,6 @@ export default {
 			uni.showModal({
 				title: '提示',
 				content: '您确定要删除此项吗?',
-				confirmColor:this.deleteConfirmBtnColor,
 				success: async (res) => {
 					if (res.confirm) {
 						// 先检查是否有定义before-remove移除前钩子
@@ -503,7 +501,7 @@ export default {
 		// 执行移除图片的动作,上方代码只是判断是否可以移除
 		handlerDeleteItem(index) {
 			// 如果文件正在上传中,终止上传任务,进度在0 < progress < 100则意味着正在上传
-			if (this.lists[index].process < 100 && this.lists[index].process > 0) {
+			if (this.lists[index].progress < 100 && this.lists[index].progress > 0) {
 				typeof this.lists[index].uploadTask != 'undefined' && this.lists[index].uploadTask.abort();
 			}
 			this.lists.splice(index, 1);

+ 14 - 22
uview-ui/components/u-waterfall/u-waterfall.vue

@@ -1,7 +1,7 @@
 <template>
 	<view class="u-waterfall">
-		<view id="u-left-column" class="u-column" :style="{ marginRight }"><slot name="left" :leftList="leftList"></slot></view>
-		<view id="u-right-column" class="u-column" :style="{ marginLeft }"><slot name="right" :rightList="rightList"></slot></view>
+		<view id="u-left-column" class="u-column"><slot name="left" :leftList="leftList"></slot></view>
+		<view id="u-right-column" class="u-column"><slot name="right" :rightList="rightList"></slot></view>
 	</view>
 </template>
 
@@ -15,7 +15,7 @@
  * @example <u-waterfall :flowList="flowList"></u-waterfall>
  */
 export default {
-	name: 'u-waterfall',
+	name: "u-waterfall",
 	props: {
 		value: {
 			// 瀑布流数据
@@ -36,14 +36,6 @@ export default {
 		idKey: {
 			type: String,
 			default: 'id'
-		},
-		marginLeft: {
-			type: String,
-			default: '0'
-		},
-		marginRight: {
-			type: String,
-			default: '0'
 		}
 	},
 	data() {
@@ -52,7 +44,7 @@ export default {
 			rightList: [],
 			tempList: [],
 			children: []
-		};
+		}
 	},
 	watch: {
 		copyFlowList(nVal, oVal) {
@@ -82,7 +74,7 @@ export default {
 			let item = this.tempList[0];
 			// 解决多次快速上拉后,可能数据会乱的问题,因为经过上面的两个await节点查询阻塞一定时间,加上后面的定时器干扰
 			// 数组可能变成[],导致此item值可能为undefined
-			if (!item) return;
+			if(!item) return ;
 			if (leftRect.height < rightRect.height) {
 				this.leftList.push(item);
 			} else if (leftRect.height > rightRect.height) {
@@ -102,7 +94,7 @@ export default {
 			if (this.tempList.length) {
 				setTimeout(() => {
 					this.splitData();
-				}, this.addTime);
+				}, this.addTime)
 			}
 		},
 		// 复制而不是引用对象和数组
@@ -122,34 +114,34 @@ export default {
 			// 如果findIndex找不到合适的条件,就会返回-1
 			let index = -1;
 			index = this.leftList.findIndex(val => val[this.idKey] == id);
-			if (index != -1) {
+			if(index != -1) {
 				// 如果index不等于-1,说明已经找到了要找的id,根据index索引删除这一条数据
 				this.leftList.splice(index, 1);
 			} else {
 				// 同理于上方面的方法
 				index = this.rightList.findIndex(val => val[this.idKey] == id);
-				if (index != -1) this.rightList.splice(index, 1);
+				if(index != -1) this.rightList.splice(index, 1);
 			}
 			// 同时清除父组件的数据中的对应id的条目
 			index = this.value.findIndex(val => val[this.idKey] == id);
-			if (index != -1) this.$emit('input', this.value.splice(index, 1));
+			if(index != -1) this.$emit('input', this.value.splice(index, 1));
 		},
 		// 修改某条数据的某个属性
 		modify(id, key, value) {
 			// 如果findIndex找不到合适的条件,就会返回-1
 			let index = -1;
 			index = this.leftList.findIndex(val => val[this.idKey] == id);
-			if (index != -1) {
+			if(index != -1) {
 				// 如果index不等于-1,说明已经找到了要找的id,修改对应key的值
 				this.leftList[index][key] = value;
 			} else {
 				// 同理于上方面的方法
 				index = this.rightList.findIndex(val => val[this.idKey] == id);
-				if (index != -1) this.rightList[index][key] = value;
+				if(index != -1) this.rightList[index][key] = value;
 			}
 			// 修改父组件的数据中的对应id的条目
 			index = this.value.findIndex(val => val[this.idKey] == id);
-			if (index != -1) {
+			if(index != -1) {
 				// 首先复制一份value的数据
 				let data = this.cloneData(this.value);
 				// 修改对应索引的key属性的值为value
@@ -159,11 +151,11 @@ export default {
 			}
 		}
 	}
-};
+}
 </script>
 
 <style lang="scss" scoped>
-@import '../../libs/css/style.components.scss';
+@import "../../libs/css/style.components.scss";
 
 .u-waterfall {
 	@include vue-flex;

+ 0 - 0
uview-ui/components/uview-v1/uview-v1.vue


+ 3 - 3
uview-ui/libs/config/config.js

@@ -1,5 +1,5 @@
-// 此版本发布于2020-12-17
-let version = '1.8.3';
+// 此版本发布于2023-03-27
+let version = '1.8.8';
 
 export default {
 	v: version,
@@ -12,4 +12,4 @@ export default {
 		'error',
 		'warning'
 	]
-}
+}

+ 28 - 16
uview-ui/libs/function/deepClone.js

@@ -1,23 +1,35 @@
 // 判断arr是否为一个数组,返回一个bool值
-function isArray (arr) {
-    return Object.prototype.toString.call(arr) === '[object Array]';
+function isArray(arr) {
+	return Object.prototype.toString.call(arr) === '[object Array]';
 }
 
 // 深度克隆
-function deepClone (obj) {
-	// 对常见的“非”值,直接返回原来值
-	if([null, undefined, NaN, false].includes(obj)) return obj;
-    if(typeof obj !== "object" && typeof obj !== 'function') {
-		//原始类型直接返回
-        return obj;
-    }
-    var o = isArray(obj) ? [] : {};
-    for(let i in obj) {
-        if(obj.hasOwnProperty(i)){
-            o[i] = typeof obj[i] === "object" ? deepClone(obj[i]) : obj[i];
-        }
-    }
-    return o;
+function deepClone(obj, cache = new WeakMap()) {
+	if (obj === null || typeof obj !== 'object') return obj;
+	if (cache.has(obj)) return cache.get(obj);
+	let clone;
+	if (obj instanceof Date) {
+		clone = new Date(obj.getTime());
+	} else if (obj instanceof RegExp) {
+		clone = new RegExp(obj);
+	} else if (obj instanceof Map) {
+		clone = new Map(Array.from(obj, ([key, value]) => [key, deepClone(value, cache)]));
+	} else if (obj instanceof Set) {
+		clone = new Set(Array.from(obj, value => deepClone(value, cache)));
+	} else if (Array.isArray(obj)) {
+		clone = obj.map(value => deepClone(value, cache));
+	} else if (Object.prototype.toString.call(obj) === '[object Object]') {
+		clone = Object.create(Object.getPrototypeOf(obj));
+		cache.set(obj, clone);
+		for (const [key, value] of Object.entries(obj)) {
+			clone[key] = deepClone(value, cache);
+		}
+	} else {
+		clone = Object.assign({}, obj);
+	}
+	cache.set(obj, clone);
+	return clone;
 }
 
+
 export default deepClone;

+ 18 - 19
uview-ui/libs/function/deepMerge.js

@@ -3,28 +3,27 @@ import deepClone from "./deepClone";
 // JS对象深度合并
 function deepMerge(target = {}, source = {}) {
 	target = deepClone(target);
-	if (typeof target !== 'object' || typeof source !== 'object') return false;
-	for (var prop in source) {
+	if (typeof target !== 'object' || target === null || typeof source !== 'object' || source === null) return target;
+	const merged = Array.isArray(target) ? target.slice() : Object.assign({}, target);
+	for (const prop in source) {
 		if (!source.hasOwnProperty(prop)) continue;
-		if (prop in target) {
-			if (typeof target[prop] !== 'object') {
-				target[prop] = source[prop];
-			} else {
-				if (typeof source[prop] !== 'object') {
-					target[prop] = source[prop];
-				} else {
-					if (target[prop].concat && source[prop].concat) {
-						target[prop] = target[prop].concat(source[prop]);
-					} else {
-						target[prop] = deepMerge(target[prop], source[prop]);
-					}
-				}
-			}
+		const sourceValue = source[prop];
+		const targetValue = merged[prop];
+		if (sourceValue instanceof Date) {
+			merged[prop] = new Date(sourceValue);
+		} else if (sourceValue instanceof RegExp) {
+			merged[prop] = new RegExp(sourceValue);
+		} else if (sourceValue instanceof Map) {
+			merged[prop] = new Map(sourceValue);
+		} else if (sourceValue instanceof Set) {
+			merged[prop] = new Set(sourceValue);
+		} else if (typeof sourceValue === 'object' && sourceValue !== null) {
+			merged[prop] = deepMerge(targetValue, sourceValue);
 		} else {
-			target[prop] = source[prop];
+			merged[prop] = sourceValue;
 		}
 	}
-	return target;
+	return merged;
 }
 
-export default deepMerge;
+export default deepMerge;

+ 2 - 2
uview-ui/libs/function/guid.js

@@ -6,7 +6,7 @@
  * v-for的时候,推荐使用后端返回的id而不是循环的index
  * @param {Number} len uuid的长度
  * @param {Boolean} firstU 将返回的首字母置为"u"
- * @param {Nubmer} radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制
+ * @param {Number} radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制
  */
 function guid(len = 32, firstU = true, radix = null) {
 	let chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
@@ -29,7 +29,7 @@ function guid(len = 32, firstU = true, radix = null) {
 			}
 		}
 	}
-	// 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class
+	// 移除第一个字符,并用u替代,因为第一个字符为数值时,该guid不能用作id或者class
 	if (firstU) {
 		uuid.shift();
 		return 'u' + uuid.join('');

+ 5 - 5
uview-ui/libs/function/route.js

@@ -28,7 +28,7 @@ class Router {
 	// 整合路由参数
 	mixinParam(url, params) {
 		url = url && this.addRootPath(url)
-		
+
 		// 使用正则匹配,主要依据是判断是否有"/","?","="等,如“/page/index/index?name=mary"
 		// 如果有url中有get参数,转换后无需带上"?"
 		let query = ''
@@ -54,12 +54,12 @@ class Router {
 			mergeConfig.url = this.mixinParam(options, params)
 			mergeConfig.type = 'navigateTo'
 		} else {
-			mergeConfig = uni.$u.deepClone(options, this.config)
+			mergeConfig = uni.$u.deepMerge(this.config, options)
 			// 否则正常使用mergeConfig中的url和params进行拼接
 			mergeConfig.url = this.mixinParam(options.url, options.params)
 		}
-		
-		if(params.intercept) {
+
+		if (params.intercept) {
 			this.config.intercept = params.intercept
 		}
 		// params参数也带给拦截器
@@ -119,4 +119,4 @@ class Router {
 	}
 }
 
-export default (new Router()).route
+export default (new Router()).route

+ 1 - 1
uview-ui/libs/mixin/mixin.js

@@ -48,7 +48,7 @@ module.exports = {
 		uni.$emit('uOnReachBottom')
 	},
 	beforeDestroy() {
-		// 判断当前页面是否存在parent和chldren,一般在checkbox和checkbox-group父子联动的场景会有此情况
+		// 判断当前页面是否存在parent和children,一般在checkbox和checkbox-group父子联动的场景会有此情况
 		// 组件销毁时,移除子组件在父组件children数组中的实例,释放资源,避免数据混乱
 		if(this.parent && uni.$u.test.array(this.parent.children)) {
 			// 组件销毁时,移除父组件中的children数组中对应的实例

+ 0 - 2
uview-ui/libs/request/index.js

@@ -20,9 +20,7 @@ class Request {
 			this.options = interceptorRequest;
 		}
 		options.dataType = options.dataType || this.config.dataType;
-		// #ifdef MP-WEIXIN
 		options.responseType = options.responseType || this.config.responseType;
-		// #endif
 		options.url = options.url || '';
 		options.params = options.params || {};
 		options.header = Object.assign({}, this.config.header, options.header);

+ 1 - 1
uview-ui/libs/util/async-validator.js

@@ -437,7 +437,7 @@ function range(rule, value, source, errors, options) {
 	}
 
 	if (str) {
-		// 处理码点大于U+010000的文字length属性不准确的bug,如"𠮷𠮷𠮷".lenght !== 3
+		// 处理码点大于U+010000的文字length属性不准确的bug,如"𠮷𠮷𠮷".length !== 3
 		val = value.replace(spRegexp, '_').length;
 	}
 

+ 37 - 37
uview-ui/package.json

@@ -1,39 +1,39 @@
 {
-	"name": "uview-ui",
-	"version": "1.8.4",
-	"description": "uView UI,是uni-app生态优秀的UI框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水",
-	"main": "index.js",
-	"keywords": [
-		"uview",
-		"uView",
-		"uni-app",
-		"uni-app ui",
-		"uniapp",
-		"uviewui",
-		"uview ui",
-		"uviewUI",
-		"uViewui",
-		"uViewUI",
-		"uView UI",
-		"uni ui",
-		"uni UI",
-		"uniapp ui",
-		"ui",
-		"UI框架",
-		"uniapp ui框架",
-		"uniapp UI"
-	],
-	"scripts": {
-		"test": "echo \"Error: no test specified\" && exit 1"
-	},
-	"repository": {
-		"type": "git",
-		"url": ""
-	},
-	"devDependencies": {
-		"node-sass": "^4.14.0",
-		"sass-loader": "^8.0.2"
-	},
-	"author": "uView",
-	"license": "MIT"
+  "name": "uview-ui",
+  "version": "1.8.8",
+  "description": "uView UI,是uni-app生态优秀的UI框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水",
+  "main": "index.js",
+  "keywords": [
+    "uview",
+    "uView",
+    "uni-app",
+    "uni-app ui",
+    "uniapp",
+    "uviewui",
+    "uview ui",
+    "uviewUI",
+    "uViewui",
+    "uViewUI",
+    "uView UI",
+    "uni ui",
+    "uni UI",
+    "uniapp ui",
+    "ui",
+    "UI框架",
+    "uniapp ui框架",
+    "uniapp UI"
+  ],
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "repository": {
+    "type": "git",
+    "url": ""
+  },
+  "devDependencies": {
+    "sass": "1.26.2",
+    "sass-loader": "8.0.2"
+  },
+  "author": "uView",
+  "license": "MIT"
 }

+ 3 - 13
uview-ui/theme.scss

@@ -9,10 +9,10 @@ $u-light-color: #c0c4cc;
 $u-border-color: #e4e7ed;
 $u-bg-color: #f3f4f6;
 
-$u-type-primary: #22ac38;
+$u-type-primary: #2979ff;
 $u-type-primary-light: #ecf5ff;
-$u-type-primary-disabled: #aed67d;
-$u-type-primary-dark: rgba(#22ac38, 0.8);
+$u-type-primary-disabled: #a0cfff;
+$u-type-primary-dark: #2b85e4;
 
 $u-type-warning: #ff9900;
 $u-type-warning-disabled: #fcbd71;
@@ -34,15 +34,5 @@ $u-type-info-disabled: #c8c9cc;
 $u-type-info-dark: #82848a;
 $u-type-info-light: #f4f4f5;
 
-$u-type-dark: #999;
-$u-type-dark-disabled: #909399;
-$u-type-dark-dark: #909399;
-$u-type-dark-light: #909399;
-
-$u-type-white: #fff;
-$u-type-white-disabled: #efefef;
-$u-type-white-dark: #fff;
-$u-type-white-light: #fff;
-
 $u-form-item-height: 70rpx;
 $u-form-item-border-color: #dcdfe6;