ソースを参照

Merge branch 'next' of https://github.com/jdf2e/nutui into next

yangkaixuan 4 年 前
コミット
2c0e489889

+ 23 - 37
src/packages/pullrefresh/demo.vue

@@ -5,35 +5,18 @@
       <div class="vertical">
         <nut-pullrefresh
           @refresh="refresh"
+          @down-refresh="downRefresh"
           :useWindow="false"
           containerId="pull"
         >
           <div class="content" id="pull">
             <div class="main">
-              <div class="text-data">我是测试数据1</div>
-              <div class="text-data">我是测试数据2</div>
-              <div class="text-data">我是测试数据3</div>
-              <div class="text-data">我是测试数据4</div>
-              <div class="text-data">我是测试数据5</div>
-              <div class="text-data">我是测试数据6</div>
-              <div class="text-data">我是测试数据7</div>
-              <div class="text-data">我是测试数据8</div>
-              <div class="text-data">我是测试数据9</div>
-              <div class="text-data">我是测试数据10</div>
-              <div class="text-data">我是测试数据11</div>
-              <div class="text-data">我是测试数据12</div>
-              <div class="text-data">我是测试数据13</div>
-              <div class="text-data">我是测试数据14</div>
-              <div class="text-data">我是测试数据15</div>
-              <div class="text-data">我是测试数据16</div>
-              <div class="text-data">我是测试数据17</div>
-              <div class="text-data">我是测试数据18</div>
-              <div class="text-data">我是测试数据19</div>
-              <div class="text-data">我是测试数据20</div>
-              <div class="text-data">我是测试数据21</div>
-              <div class="text-data">我是测试数据22</div>
-              <div class="text-data">我是测试数据23</div>
-              <div class="text-data">我是测试数据24</div>
+              <div
+                class="text-data"
+                v-for="item in [1, 2, 3, 4, 5, 6, 7, 8, 9]"
+                :key="item"
+                >我是测试数据 {{ item }}</div
+              >
             </div>
           </div>
         </nut-pullrefresh>
@@ -45,21 +28,19 @@
       <div class="horizontal">
         <nut-pullrefresh
           @refresh="refresh"
+          @down-refresh="downRefresh"
           :useWindow="false"
           containerId="pullH"
           direction="horizontal"
         >
           <div class="contentH" id="pullH">
             <div class="mainH">
-              <div class="text-data">我是测试数据1</div>
-              <div class="text-data">我是测试数据2</div>
-              <div class="text-data">我是测试数据3</div>
-              <div class="text-data">我是测试数据4</div>
-              <div class="text-data">我是测试数据5</div>
-              <div class="text-data">我是测试数据6</div>
-              <div class="text-data">我是测试数据7</div>
-              <div class="text-data">我是测试数据8</div>
-              <div class="text-data">我是测试数据9</div>
+              <div
+                class="text-data"
+                v-for="item in [1, 2, 3, 4, 5, 6, 7, 8, 9]"
+                :key="item"
+                >我是测试数据 {{ item }}</div
+              >
             </div>
           </div>
         </nut-pullrefresh>
@@ -75,11 +56,19 @@ export default createDemo({
   props: {},
   setup() {
     const refresh = done => {
+      console.log('上拉加载');
       setTimeout(() => {
         done();
       }, 1000);
     };
-    return { refresh };
+
+    const downRefresh = done => {
+      console.log('下拉刷新');
+      setTimeout(() => {
+        done();
+      }, 1000);
+    };
+    return { refresh, downRefresh };
   }
 });
 </script>
@@ -92,9 +81,6 @@ export default createDemo({
 .content {
   height: 100%;
   overflow: auto;
-  .main {
-    padding: 10px 0;
-  }
 }
 
 .horizontal {

+ 117 - 29
src/packages/pullrefresh/doc.md

@@ -1,34 +1,122 @@
 #  pullrefresh组件
 
-    ### 介绍
-    
-    基于 xxxxxxx
-    
-    ### 安装
-    
-    
-    
-    ## 代码演示
-    
-    ### 基础用法1
-    
+### 介绍
 
+纵向可支持下拉刷新和上拉加载,横向可支持左滑更新、右滑加载更多
+
+### 安装
     
-    ## API
-    
-    ### Props
-    
-    | 参数         | 说明                             | 类型   | 默认值           |
-    |--------------|----------------------------------|--------|------------------|
-    | name         | 图标名称或图片链接               | String | -                |
-    | color        | 图标颜色                         | String | -                |
-    | size         | 图标大小,如 '20px' '2em' '2rem' | String | -                |
-    | class-prefix | 类名前缀,用于使用自定义图标     | String | 'nutui-iconfont' |
-    | tag          | HTML 标签                        | String | 'i'              |
-    
-    ### Events
+```javascript
+import { createApp } from 'vue';
+import { PullRefresh } from '@nutui/nutui';
+
+const app = createApp();
+app.use(PullRefresh);
+```
     
-    | 事件名 | 说明           | 回调参数     |
-    |--------|----------------|--------------|
-    | click  | 点击图标时触发 | event: Event |
-    
+## 代码演示
+
+### 纵向
+
+```html
+<div class="vertical">
+    <nut-pullrefresh
+        @refresh="refresh"
+        @down-refresh="downRefresh"
+        :useWindow="false"
+        containerId="pull"
+    >
+        <div class="content" id="pull">
+            <div class="main">
+                <div
+                    class="text-data"
+                    v-for="item in [1, 2, 3, 4, 5, 6, 7, 8, 9]"
+                    :key="item"
+                    >我是测试数据 {{ item }}</div>
+            </div>
+        </div>
+    </nut-pullrefresh>
+</div>
+```
+```javascript
+setup() {
+    const refresh = done => {
+      console.log('上拉加载')
+      setTimeout(() => {
+        done();
+      }, 1000);
+    };
+
+    const downRefresh = done => {
+      console.log('下拉刷新')
+      setTimeout(() => {
+        done();
+      }, 1000);
+    }
+    return { refresh, downRefresh };
+}
+```
+
+### 横向
+
+```html
+<div class="horizontal">
+    <nut-pullrefresh
+        @refresh="refresh"
+        @down-refresh="downRefresh"
+        :useWindow="false"
+        containerId="pullH"
+        direction="horizontal"
+    >
+        <div class="contentH" id="pullH">
+        <div class="mainH">
+            <div
+            class="text-data"
+            v-for="item in [1, 2, 3, 4, 5, 6, 7, 8, 9]"
+            :key="item"
+            >我是测试数据 {{ item }}</div
+            >
+        </div>
+        </div>
+    </nut-pullrefresh>
+</div>
+```
+```javascript
+setup() {
+    const refresh = done => {
+      console.log('上拉加载')
+      setTimeout(() => {
+        done();
+      }, 1000);
+    };
+
+    const downRefresh = done => {
+      console.log('下拉刷新')
+      setTimeout(() => {
+        done();
+      }, 1000);
+    }
+    return { refresh, downRefresh };
+}
+```
+
+## API
+
+### Props
+
+| 参数         | 说明                             | 类型   | 默认值           |
+|--------------|----------------------------------|--------|------------------|
+| useWindow | 将滚动侦听器添加到 window 否则侦听组件的父节点     | Boolean | true |
+| containerId          | 在 useWindow 属性为 false 的时候,自定义设置节点ID    | String | ''            |
+| disabled          | 组件是否激活                        | Boolean | true            |
+| direction        | 滚动的方向,'vertical' 纵向、'horizontal' 横向   | String | 'vertical'                |
+| pullingText        | '下拉'文案   | Object | {top: '下拉刷新', bottom: '上拉加载',left: '左滑刷新', right: '右滑加载'}              |
+| loosingText        | '释放'文案   | Object | {top: ’松手释放刷新', bottom: '松手释放刷新',left: '释放刷新', right: '加载更多'}              |
+| loadingText        | '加载中'文案   | Object | {top: '加载中...', bottom: '加载中...',left: '加载中...', right: '加载中...'}              |
+
+### Events
+
+| 事件名 | 说明           | 回调参数     |
+|--------|----------------|--------------|
+| refresh  | 纵向-上拉加载、横向-右滑加载更多时触发 | done() 函数,在数据更新完成以后,需要调用 done() 函数将组件状态初始化 |
+| downRefresh  | 纵向-下拉刷新、横向-左滑刷新时触发 | done() 函数,在数据更新完成以后,需要调用 done() 函数将组件状态初始化 |  

+ 0 - 67
src/packages/pullrefresh/index.css

@@ -1,67 +0,0 @@
-view {
-  display: block;
-}
-
-.nut-pullrefresh {
-  position: relative;
-  height: 100%;
-}
-
-.nut-pullrefresh .pullrefresh-top {
-  position: absolute;
-  left: 0;
-  overflow: hidden;
-  color: #969799;
-  font-size: 14px;
-  line-height: 50px;
-  text-align: center;
-}
-
-.nut-pullrefresh .pullrefresh-top.pullrefresh-top-v {
-  width: 100%;
-  height: 50px;
-  -webkit-transform: translateY(-100%);
-  transform: translateY(-100%);
-}
-
-.nut-pullrefresh .pullrefresh-top.pullrefresh-top-h {
-  width: 50px;
-  height: 100%;
-  -webkit-transform: translateX(-100%);
-  transform: translateX(-100%);
-  writing-mode: tb-rl;
-}
-
-.nut-pullrefresh .pullrefresh-content {
-  height: 100%;
-  overflow: auto;
-  background: #fff;
-}
-
-.nut-pullrefresh .pullrefresh-bottom {
-  position: absolute;
-  overflow: hidden;
-  color: #969799;
-  font-size: 14px;
-  line-height: 50px;
-  text-align: center;
-}
-
-.nut-pullrefresh .pullrefresh-bottom.pullrefresh-bottom-v {
-  left: 0;
-  bottom: 0;
-  width: 100%;
-  height: 0px;
-  -webkit-transform: translateY(100%);
-  transform: translateY(100%);
-}
-
-.nut-pullrefresh .pullrefresh-bottom.pullrefresh-bottom-h {
-  top: 0;
-  right: 0;
-  width: 50px;
-  height: 100%;
-  -webkit-transform: translateX(100%);
-  transform: translateX(100%);
-  writing-mode: tb-rl;
-}

ファイルの差分が大きいため隠しています
+ 0 - 1
src/packages/pullrefresh/index.min.css


+ 8 - 4
src/packages/pullrefresh/index.scss

@@ -13,20 +13,22 @@ view {
     overflow: hidden;
     color: #969799;
     font-size: 14px;
-    line-height: 50px;
+
     text-align: center;
     &.pullrefresh-top-v {
+      line-height: 50px;
       width: 100%;
       height: 50px;
       -webkit-transform: translateY(-100%);
       transform: translateY(-100%);
     }
     &.pullrefresh-top-h {
+      display: flex;
+      align-items: center;
       width: 50px;
       height: 100%;
       -webkit-transform: translateX(-100%);
       transform: translateX(-100%);
-      writing-mode: tb-rl;
     }
   }
 
@@ -42,7 +44,7 @@ view {
     overflow: hidden;
     color: #969799;
     font-size: 14px;
-    line-height: 50px;
+
     text-align: center;
 
     &.pullrefresh-bottom-v {
@@ -50,17 +52,19 @@ view {
       bottom: 0;
       width: 100%;
       height: 0px;
+      line-height: 50px;
       -webkit-transform: translateY(100%);
       transform: translateY(100%);
     }
     &.pullrefresh-bottom-h {
+      display: flex;
+      align-items: center;
       top: 0;
       right: 0;
       width: 50px;
       height: 100%;
       -webkit-transform: translateX(100%);
       transform: translateX(100%);
-      writing-mode: tb-rl;
     }
   }
 }

+ 97 - 40
src/packages/pullrefresh/index.vue

@@ -12,20 +12,8 @@
       :class="
         direction == 'horizontal' ? 'pullrefresh-top-h' : 'pullrefresh-top-v'
       "
+      >{{ refreshTopTem }}</view
     >
-      <template
-        v-if="status == 'loading' && (reachTop || reachLeft) && distance > 0"
-        >{{ loadingText }}</template
-      >
-      <template
-        v-if="status == 'pulling' && (reachTop || reachLeft) && distance > 0"
-        >{{ pullingText }}</template
-      >
-      <template
-        v-if="status == 'loosing' && (reachTop || reachLeft) && distance > 0"
-        >{{ loosingText }}</template
-      >
-    </view>
     <view class="pullrefresh-content" ref="pull">
       <slot></slot>
     </view>
@@ -38,26 +26,8 @@
           : 'pullrefresh-bottom-v'
       "
       :style="getBottomStyle"
+      >{{ refreshBottomTem }}</view
     >
-      <template
-        v-if="
-          status == 'loading' && (reachBottom || reachRight) && distance < 0
-        "
-        >{{ loadingText }}</template
-      >
-      <template
-        v-if="
-          status == 'pulling' && (reachBottom || reachRight) && distance < 0
-        "
-        >{{ pullingText }}</template
-      >
-      <template
-        v-if="
-          status == 'loosing' && (reachBottom || reachRight) && distance < 0
-        "
-        >{{ loosingText }}</template
-      >
-    </view>
   </view>
 </template>
 
@@ -89,20 +59,35 @@ export default create({
     },
 
     pullingText: {
-      type: String,
-      default: '下拉刷新'
+      type: Object,
+      default: {
+        top: '下拉刷新',
+        bottom: '上拉加载',
+        left: '左滑刷新',
+        right: '右滑加载'
+      }
     },
     loosingText: {
-      type: String,
-      default: '松手释放刷新'
+      type: Object,
+      default: {
+        top: '松手释放刷新',
+        bottom: '松手释放刷新',
+        left: '释放刷新',
+        right: '加载更多'
+      }
     },
     loadingText: {
-      type: String,
-      default: '加载中...'
+      type: Object,
+      default: {
+        top: '加载中...',
+        bottom: '加载中...',
+        left: '加载中...',
+        right: '加载中...'
+      }
     }
   },
   components: {},
-  emits: ['refresh'],
+  emits: ['refresh', 'downRefresh'], // refresh 上拉加载、右滑加载更多  downRefresh 下拉刷新、左滑刷新
 
   setup(props, { emit }) {
     console.log('componentName', componentName);
@@ -187,6 +172,69 @@ export default create({
       return style;
     });
 
+    /** 刷新 顶部或左侧 */
+    const refreshTopTem = computed(() => {
+      const { status, distance } = state;
+
+      const tag = direction.value == 'vertical' ? 'top' : 'left';
+      if (
+        status == 'loading' &&
+        (reachTop.value || reachLeft.value) &&
+        distance > 0
+      ) {
+        return props.loadingText[tag];
+      }
+
+      if (
+        status == 'pulling' &&
+        (reachTop.value || reachLeft.value) &&
+        distance > 0
+      ) {
+        return props.pullingText[tag];
+      }
+
+      if (
+        status == 'loosing' &&
+        (reachTop.value || reachLeft.value) &&
+        distance > 0
+      ) {
+        return props.loosingText[tag];
+      }
+
+      return '';
+    });
+    /** 刷新 底部或右侧 */
+    const refreshBottomTem = computed(() => {
+      const { status, distance } = state;
+
+      const tag = direction.value == 'vertical' ? 'bottom' : 'right';
+      if (
+        status == 'loading' &&
+        (reachBottom.value || reachRight.value) &&
+        distance < 0
+      ) {
+        return props.loadingText[tag];
+      }
+
+      if (
+        status == 'pulling' &&
+        (reachBottom.value || reachRight.value) &&
+        distance < 0
+      ) {
+        return props.pullingText[tag];
+      }
+
+      if (
+        status == 'loosing' &&
+        (reachBottom.value || reachRight.value) &&
+        distance < 0
+      ) {
+        return props.loosingText[tag];
+      }
+
+      return '';
+    });
+
     const isTouchable = () => state.status !== 'loading' && !disabled.value;
 
     const setStatus = (distance: number, isLoading?: boolean) => {
@@ -272,6 +320,8 @@ export default create({
             touch.start(event);
           }
         }
+      } else {
+        preventDefault(event);
       }
     };
 
@@ -299,6 +349,8 @@ export default create({
           preventDefault(event);
           setStatus(ease(deltaX.value));
         }
+      } else {
+        preventDefault(event);
       }
     };
     const touchEnd = () => {
@@ -313,6 +365,7 @@ export default create({
             deltaY.value > 0
           ) {
             dis = 50;
+            emit('downRefresh', refreshDone);
           }
           if (
             direction.value == 'vertical' &&
@@ -320,6 +373,7 @@ export default create({
             deltaY.value < 0
           ) {
             dis = -50;
+            emit('refresh', refreshDone);
           }
           if (
             direction.value == 'horizontal' &&
@@ -327,6 +381,7 @@ export default create({
             deltaX.value > 0
           ) {
             dis = 50;
+            emit('downRefresh', refreshDone);
           }
           if (
             direction.value == 'horizontal' &&
@@ -334,9 +389,9 @@ export default create({
             deltaX.value < 0
           ) {
             dis = -50;
+            emit('refresh', refreshDone);
           }
           setStatus(dis, true);
-          emit('refresh', refreshDone);
         } else {
           setStatus(0);
         }
@@ -353,6 +408,8 @@ export default create({
       reachRight,
       reachLeft,
       getBottomStyle,
+      refreshTopTem,
+      refreshBottomTem,
       ...toRefs(state)
     };
   }

+ 60 - 22
src/packages/range/demo.vue

@@ -2,30 +2,60 @@
   <div class="demo">
     <h2>基础用法</h2>
     <nut-cell class="cell">
-      <nut-range v-model="value" @change="onChange"></nut-range>
+      <nut-range v-model="value1" @change="onChange"></nut-range>
     </nut-cell>
-    <h2>指定选择范围</h2>
+    <h2>双滑块</h2>
+    <nut-cell class="cell">
+      <nut-range range v-model="value2" @change="onChange"></nut-range>
+    </nut-cell>
+    <h2>指定范围</h2>
     <nut-cell class="cell">
       <nut-range
-        v-model="value2"
+        v-model="value3"
         max="10"
         min="-10"
-        @change="onChange2"
+        @change="onChange"
       ></nut-range>
     </nut-cell>
     <h2>设置步长</h2>
     <nut-cell class="cell">
-      <nut-range v-model="value3" step="5" @change="onChange3"></nut-range>
+      <nut-range v-model="value4" step="5" @change="onChange"></nut-range>
+    </nut-cell>
+    <h2>隐藏范围</h2>
+    <nut-cell class="cell">
+      <nut-range hidden-range v-model="value5" @change="onChange"></nut-range>
+    </nut-cell>
+    <h2>隐藏标签</h2>
+    <nut-cell class="cell">
+      <nut-range hidden-tag v-model="value6" @change="onChange"></nut-range>
+    </nut-cell>
+    <h2>自定义样式</h2>
+    <nut-cell class="cell">
+      <nut-range
+        v-model="value7"
+        @change="onChange"
+        inactive-color="rgba(163,184,255,1)"
+        button-color="rgba(52,96,250,1)"
+        active-color="linear-gradient(315deg, rgba(73,143,242,1) 0%,rgba(73,101,242,1) 100%)"
+      ></nut-range>
+    </nut-cell>
+    <h2>自定义按钮</h2>
+    <nut-cell class="cell">
+      <nut-range v-model="value8" @change="onChange">
+        <template #button>
+          <div class="custom-button">{{ value10 }}</div>
+        </template>
+      </nut-range>
     </nut-cell>
     <h2>禁用</h2>
     <nut-cell class="cell">
-      <nut-range disabled v-model="value4"></nut-range>
+      <nut-range disabled v-model="value9"></nut-range>
     </nut-cell>
   </div>
 </template>
 
 <script lang="ts">
-import { ref } from 'vue';
+import { toRefs, reactive } from 'vue';
 import { createComponent } from '@/utils/create';
 import { Toast } from '../toast';
 
@@ -33,21 +63,22 @@ const { createDemo } = createComponent('range');
 export default createDemo({
   props: {},
   setup() {
-    const value = ref(50);
-    const value2 = ref(5);
-    const value3 = ref(50);
-    const value4 = ref(50);
+    const state = reactive({
+      value1: 40,
+      value2: [20, 80],
+      value3: 0,
+      value4: 20,
+      value5: 30,
+      value6: 40,
+      value7: 50,
+      value8: [20, 80],
+      value9: 60,
+      value10: 50
+    });
     const onChange = value => Toast.text('当前值:' + value);
-    const onChange2 = value2 => Toast.text('当前值:' + value2);
-    const onChange3 = value3 => Toast.text('当前值:' + value3);
     return {
-      value,
-      value2,
-      value3,
-      value4,
-      onChange,
-      onChange2,
-      onChange3
+      ...toRefs(state),
+      onChange
     };
   }
 });
@@ -55,8 +86,15 @@ export default createDemo({
 
 <style lang="scss" scoped>
 .cell {
-  padding: 30px 18px;
+  padding: 40px 18px;
 }
-.nut-range {
+.custom-button {
+  width: 26px;
+  color: #fff;
+  font-size: 10px;
+  line-height: 18px;
+  text-align: center;
+  background-color: #ee0a24;
+  border-radius: 100px;
 }
 </style>

+ 31 - 32
src/packages/range/doc.md

@@ -1,34 +1,33 @@
 #  range组件
 
-    ### 介绍
-    
-    基于 xxxxxxx
-    
-    ### 安装
-    
-    
-    
-    ## 代码演示
-    
-    ### 基础用法1
-    
-
-    
-    ## API
-    
-    ### Props
-    
-    | 参数         | 说明                             | 类型   | 默认值           |
-    |--------------|----------------------------------|--------|------------------|
-    | name         | 图标名称或图片链接               | String | -                |
-    | color        | 图标颜色                         | String | -                |
-    | size         | 图标大小,如 '20px' '2em' '2rem' | String | -                |
-    | class-prefix | 类名前缀,用于使用自定义图标     | String | 'nutui-iconfont' |
-    | tag          | HTML 标签                        | String | 'i'              |
-    
-    ### Events
-    
-    | 事件名 | 说明           | 回调参数     |
-    |--------|----------------|--------------|
-    | click  | 点击图标时触发 | event: Event |
-    
+  ### 介绍
+  
+  基于 xxxxxxx
+  
+  ### 安装
+  
+  
+  
+  ## 代码演示
+  
+  ### 基础用法1
+  
+  
+  ## API
+  
+  ### Props
+  
+  | 参数         | 说明                             | 类型   | 默认值           |
+  |--------------|----------------------------------|--------|------------------|
+  | name         | 图标名称或图片链接               | String | -                |
+  | color        | 图标颜色                         | String | -                |
+  | size         | 图标大小,如 '20px' '2em' '2rem' | String | -                |
+  | class-prefix | 类名前缀,用于使用自定义图标     | String | 'nutui-iconfont' |
+  | tag          | HTML 标签                        | String | 'i'              |
+  
+  ### Events
+  
+  | 事件名 | 说明           | 回调参数     |
+  |--------|----------------|--------------|
+  | click  | 点击图标时触发 | event: Event |
+  

+ 28 - 0
src/packages/range/index.scss

@@ -1,3 +1,16 @@
+.nut-range-container {
+  display: flex;
+  position: relative;
+  width: 100%;
+  height: 3px;
+  align-items: center;
+  .min,
+  .max {
+    font-size: 12px;
+    color: rgba(51, 51, 51, 1);
+    user-select: none;
+  }
+}
 .nut-range {
   display: block;
   position: relative;
@@ -59,6 +72,18 @@
       cursor: grab;
       outline: none;
     }
+
+    .number {
+      width: 100%;
+      height: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      user-select: none;
+      font-size: 12px;
+      color: rgba(51, 51, 51, 1);
+      transform: translate3d(0, -100%, 0);
+    }
   }
   &-disabled {
     cursor: not-allowed;
@@ -70,4 +95,7 @@
       cursor: not-allowed;
     }
   }
+  &-show-number {
+    margin: 0 15px;
+  }
 }

+ 113 - 26
src/packages/range/index.vue

@@ -1,24 +1,79 @@
 <template>
-  <view ref="root" :style="wrapperStyle" :class="classes" @click.stop="onClick">
-    <view class="nut-range-bar" :style="barStyle">
-      <view
-        role="slider"
-        class="nut-range-button-wrapper"
-        :tabindex="disabled ? -1 : 0"
-        :aria-valuemin="+min"
-        :aria-valuenow="curValue()"
-        :aria-valuemax="+max"
-        aria-orientation="horizontal"
-        @touchstart.stop.prevent="onTouchStart"
-        @touchmove.stop.prevent="onTouchMove"
-        @touchend.stop.prevent="onTouchEnd"
-        @touchcancel.stop.prevent="onTouchEnd"
-        @click="e => e.stopPropagation()"
-      >
-        <slot v-if="$slots.button"></slot>
-        <view class="nut-range-button" v-else></view>
+  <view class="nut-range-container">
+    <view class="min" v-if="!hiddenRange">{{ +min }}</view>
+    <view
+      ref="root"
+      :style="wrapperStyle"
+      :class="classes"
+      @click.stop="onClick"
+    >
+      <view class="nut-range-bar" :style="barStyle">
+        <template v-if="range">
+          <view
+            v-for="index of [0, 1]"
+            :key="index"
+            role="slider"
+            :class="{
+              'nut-range-button-wrapper-left': index == 0,
+              'nut-range-button-wrapper-right': index == 1
+            }"
+            :tabindex="disabled ? -1 : 0"
+            :aria-valuemin="+min"
+            :aria-valuenow="curValue(index)"
+            :aria-valuemax="+max"
+            aria-orientation="horizontal"
+            @touchstart.stop.prevent="
+              e => {
+                if (typeof index === 'number') {
+                  // 实时更新当前拖动的按钮索引
+                  buttonIndex = index;
+                }
+                onTouchStart(e);
+              }
+            "
+            @touchmove.stop.prevent="onTouchMove"
+            @touchend.stop.prevent="onTouchEnd"
+            @touchcancel.stop.prevent="onTouchEnd"
+            @click="e => e.stopPropagation()"
+          >
+            <slot v-if="$slots.button" name="button"></slot>
+            <view class="nut-range-button" v-else :style="buttonStyle">
+              <view class="number" v-if="!hiddenTag">{{
+                curValue(index)
+              }}</view>
+            </view>
+          </view>
+        </template>
+        <template v-else>
+          <view
+            role="slider"
+            class="nut-range-button-wrapper"
+            :tabindex="disabled ? -1 : 0"
+            :aria-valuemin="+min"
+            :aria-valuenow="curValue()"
+            :aria-valuemax="+max"
+            aria-orientation="horizontal"
+            @touchstart.stop.prevent="
+              e => {
+                onTouchStart(e);
+              }
+            "
+            @touchmove.stop.prevent="onTouchMove"
+            @touchend.stop.prevent="onTouchEnd"
+            @touchcancel.stop.prevent="onTouchEnd"
+            @click="e => e.stopPropagation()"
+          >
+            <slot v-if="$slots.button" name="button"></slot>
+            <view class="nut-range-button" v-else :style="buttonStyle">
+              <view class="number" v-if="!hiddenTag">{{
+                curValue(index)
+              }}</view>
+            </view>
+          </view>
+        </template>
       </view>
     </view>
+    <view class="max" v-if="!hiddenRange">{{ +max }}</view>
   </view>
 </template>
 <script lang="ts">
@@ -37,9 +92,17 @@ export default create({
       default: false
     },
     disabled: Boolean,
-    barHeight: [Number, String],
     activeColor: String,
     inactiveColor: String,
+    buttonColor: String,
+    hiddenRange: {
+      type: Boolean,
+      default: false
+    },
+    hiddenTag: {
+      type: Boolean,
+      default: false
+    },
     min: {
       type: [Number, String],
       default: 0
@@ -60,8 +123,10 @@ export default create({
   components: {},
   emits: ['change', 'drag-end', 'drag-start', 'update:modelValue'],
 
-  setup(props, { emit }) {
-    let buttonIndex: number;
+  setup(props, { emit, slots }) {
+    console.log(slots.button && slots.button());
+
+    const buttonIndex = ref(0);
     let startValue: SliderValue;
     let currentValue: SliderValue;
 
@@ -69,43 +134,61 @@ export default create({
     const dragStatus = ref<'start' | 'draging' | ''>();
     const touch = useTouch();
 
+    // 滑动范围计算
     const scope = computed(() => Number(props.max) - Number(props.min));
 
     const classes = computed(() => {
       const prefixCls = componentName;
       return {
         [prefixCls]: true,
-        [`${prefixCls}-disabled`]: props.disabled
+        [`${prefixCls}-disabled`]: props.disabled,
+        [`${prefixCls}-show-number`]: !props.hiddenRange
       };
     });
 
+    // 滑轨样式
     const wrapperStyle = computed(() => {
       return {
         background: props.inactiveColor
       };
     });
 
+    // 按钮样式
+    const buttonStyle = computed(() => {
+      return {
+        borderColor: props.buttonColor
+      };
+    });
+
+    // 判断是否是双滑块
     const isRange = (val: unknown): val is number[] =>
       !!props.range && Array.isArray(val);
 
+    // 组件核心:拖动效果主要是通过计算选中条长度百分比、开始位置偏移量来实现
     // 计算选中条的长度百分比
     const calcMainAxis = () => {
       const { modelValue, min } = props;
+      // 双滑块时,拖动滑块,通过实时变化滑动条的宽度,间接让滑块移动
+      // 如果拖动右滑块,则只会改变滑动条的宽度,开始位置偏移量不会变化
       if (isRange(modelValue)) {
         return `${((modelValue[1] - modelValue[0]) * 100) / scope.value}%`;
       }
+      // 单滑块时,通过实时变化滑动条宽度,来让滑块移动
       return `${((modelValue - Number(min)) * 100) / scope.value}%`;
     };
 
     // 计算选中条的开始位置的偏移量
     const calcOffset = () => {
       const { modelValue, min } = props;
+      // 双滑块时,如果拖动左滑块,则不仅会改变滑动条宽度,还要改变滑动条的开始位置
       if (isRange(modelValue)) {
         return `${((modelValue[0] - Number(min)) * 100) / scope.value}%`;
       }
+      // 单滑块时,开始位置永远是最左侧
       return `0%`;
     };
 
+    // 选中条样式
     const barStyle = computed<CSSProperties>(() => {
       return {
         width: calcMainAxis(),
@@ -124,7 +207,8 @@ export default create({
     const isSameValue = (newValue: SliderValue, oldValue: SliderValue) =>
       JSON.stringify(newValue) === JSON.stringify(oldValue);
 
-    // 处理两个滑块重叠之后的情况
+    // 处理两个滑块交错之后的情况
+    // 例如左滑块移动到右滑块右边,这个时候需要将两个滑块值进行交换
     const handleOverlap = (value: number[]) => {
       if (value[0] > value[1]) {
         return value.slice(0).reverse();
@@ -162,6 +246,7 @@ export default create({
         const [left, right] = modelValue;
         const middle = (left + right) / 2;
 
+        // 靠左边点击移动左按钮,靠右边点击移动右按钮
         if (value <= middle) {
           updateValue([value, right], true);
         } else {
@@ -207,8 +292,8 @@ export default create({
       const diff = (delta / total) * scope.value;
 
       if (isRange(startValue)) {
-        (currentValue as number[])[buttonIndex] =
-          startValue[buttonIndex] + diff;
+        (currentValue as number[])[buttonIndex.value] =
+          startValue[buttonIndex.value] + diff;
       } else {
         currentValue = startValue + diff;
       }
@@ -238,13 +323,15 @@ export default create({
       root,
       classes,
       wrapperStyle,
+      buttonStyle,
       onClick,
       onTouchStart,
       onTouchMove,
       onTouchEnd,
       ...toRefs(props),
       barStyle,
-      curValue
+      curValue,
+      buttonIndex
     };
   }
 });