Browse Source

feat(rate): add attr touchable

richard1015 3 years ago
parent
commit
046c4e23e9

+ 2 - 2
package.json

@@ -114,10 +114,10 @@
     "transliteration": "^2.2.0",
     "ts-jest": "^26.5.5",
     "typescript": "4.6.4",
-    "vite": "^2.9.1",
+    "vite": "^2.9.12",
     "vite-plugin-dts": "^1.0.5",
     "vite-plugin-md": "^0.11.8",
-    "vue": "3.2.24",
+    "vue": "3.2.37",
     "vue-jest": "^5.0.0-alpha.7"
   },
   "eslintConfig": {

+ 154 - 0
src/packages/__VUE/rate/common.ts

@@ -0,0 +1,154 @@
+import { computed, Ref, ref } from 'vue';
+import { createComponent } from '@/packages/utils/create';
+import { pxCheck } from '@/packages/utils/pxCheck';
+import { useTouch } from '@/packages/utils/useTouch';
+const { componentName } = createComponent('rate');
+const useComponent = (touchable: Boolean = true) => {
+  return {
+    props: {
+      count: {
+        type: [String, Number],
+        default: 5
+      },
+      modelValue: {
+        type: [String, Number],
+        default: 0
+      },
+      iconSize: {
+        type: [String, Number],
+        default: 18
+      },
+      activeColor: {
+        type: String,
+        default: ''
+      },
+      voidColor: {
+        type: String,
+        default: ''
+      },
+      uncheckedIcon: {
+        type: String,
+        default: 'star-n'
+      },
+      checkedIcon: {
+        type: String,
+        default: 'star-fill-n'
+      },
+      readonly: {
+        type: Boolean,
+        default: false
+      },
+      disabled: {
+        type: Boolean,
+        default: false
+      },
+      allowHalf: {
+        type: Boolean,
+        default: false
+      },
+      touchable: {
+        type: Boolean,
+        default: true
+      },
+      spacing: {
+        type: [String, Number],
+        default: 14
+      },
+      classPrefix: {
+        type: String,
+        default: 'nut-icon'
+      },
+      fontClassName: {
+        type: String,
+        default: 'nutui-iconfont'
+      }
+    },
+    emits: ['update:modelValue', 'change'],
+    setup(props: any, { emit }: any) {
+      const rateRefs = ref<HTMLElement[]>([]);
+      const classes = computed(() => {
+        const prefixCls = componentName;
+        return {
+          [prefixCls]: true
+        };
+      });
+      const updateVal = (value: number) => {
+        emit('update:modelValue', value);
+        emit('change', value);
+      };
+      const onClick = (e: number, index: number) => {
+        if (props.disabled || props.readonly) return;
+        let value = 0;
+        if (index === 1 && props.modelValue === index) {
+        } else {
+          value = index;
+          if (props.allowHalf && e == 2) {
+            value -= 0.5;
+          }
+        }
+        updateVal(value);
+      };
+      const getScoreByPosition = (x: number, rateRefs: Ref<HTMLElement[]>, allowHalf: boolean) => {
+        let v = 0;
+        for (let index = rateRefs.value.length - 1; index >= 0; index--) {
+          const item = rateRefs.value[index];
+          if (x > item.offsetLeft) {
+            if (allowHalf) {
+              v = index + (x > item.offsetLeft + item.clientWidth / 2 ? 1 : 0.5);
+            } else {
+              v = index + 1;
+            }
+            break;
+          }
+        }
+        return v;
+      };
+      const touch = useTouch();
+      const touchMethods = {
+        onTouchStart(event: Event) {
+          if (!props.touchable) return;
+          touch.start(event);
+        },
+        onTouchMove(event: Event) {
+          if (!props.touchable || !touchable) return;
+          touch.move(event);
+          if (touch.isHorizontal()) {
+            if (rateRefs.value) {
+              event.preventDefault();
+              updateVal(getScoreByPosition(touch.moveX.value, rateRefs, props.allowHalf));
+            }
+          }
+        }
+      };
+      const refRandomId = Math.random().toString(36).slice(-8);
+      return {
+        classes,
+        ...touchMethods,
+        onClick,
+        pxCheck,
+        rateRefs,
+        refRandomId
+      };
+    }
+  };
+};
+
+// import { useTaroRect } from '@/packages/utils/useTaroRect';
+// const getScoreByPositionTaro = async (x: number, rateRefs: Ref<HTMLElement[]>, allowHalf: boolean) => {
+//     let v = 0;
+//     for (let index = rateRefs.value.length - 1; index >= 0; index--) {
+//         const _item = rateRefs.value[index];
+//         let item = await useTaroRect(_item, Taro);
+//         if (x > (item.left)) {
+//             if (allowHalf) {
+//                 v = index + (x > item.left + item.width / 2 ? 1 : 0.5);
+//             } else {
+//                 v = index + 1;
+//             }
+//             break;
+//         }
+//     }
+//     return v;
+// };
+export const component = useComponent();
+export const taroComponent = useComponent(false);

+ 4 - 5
src/packages/__VUE/rate/demo.vue

@@ -30,10 +30,11 @@
 </template>
 
 <script lang="ts">
-import { reactive, getCurrentInstance } from 'vue';
+import { reactive } from 'vue';
 import { createComponent } from '@/packages/utils/create';
 import { useTranslate } from '@/sites/assets/util/useTranslate';
 const { createDemo, translate } = createComponent('rate');
+import { Toast } from '@/packages/nutui.vue';
 useTranslate({
   'zh-CN': {
     basic: '基本用法',
@@ -60,8 +61,6 @@ useTranslate({
 });
 export default createDemo({
   setup() {
-    let { proxy } = getCurrentInstance();
-
     const state = reactive({
       val: 3,
       val1: 3.5,
@@ -73,8 +72,8 @@ export default createDemo({
       val7: 3,
       val8: 3
     });
-    const onChange = (val) => {
-      proxy.$toast.text(val);
+    const onChange = (val: string) => {
+      Toast.text(val);
     };
     return {
       state,

+ 17 - 15
src/packages/__VUE/rate/doc.en-US.md

@@ -197,21 +197,23 @@ export default {
 
 ## Prop
 
-| Attribute       | Description                                                                      | Type    | Default          |
-|-----------------|----------------------------------------------------------------------------------|---------|------------------|
-| v-model         | The current number of stars, you can use v-model to bind data in both directions | Number  | -                |
-| count           | Total number of stars                                                            | Number  | 5                |
-| icon-size       | Star size                                                                        | Number  | 18               |
-| active-color    | Icon selection color                                                             | String  | #fa200c          |
-| void-color      | Icon unselected color                                                            | String  | #ccc             |
-| unchecked-icon  | Use icon (unchecked)[icon name](#/icon)                                          | String  | star-n           |
-| checked-icon    | Use icon (checked)[icon name](#/icon)                                            | String  | star-fill-n      |
-| font-class-name | Custom icon font base class name                                                 | String  | `nutui-iconfont` |
-| class-prefix    | Custom icon class name prefix for using custom icons                             | String  | `nut-icon`       |
-| allow-half      | half star                                                                        | Boolean | false            |
-| readonly        | read-only                                                                        | Boolean | false            |
-| disabled        | whether to disable                                                               | Boolean | false            |
-| spacing         | spacing                                                                          | Number  | 20               |
+| Attribute          | Description                                                                      | Type    | Default          |
+|--------------------|----------------------------------------------------------------------------------|---------|------------------|
+| v-model            | The current number of stars, you can use v-model to bind data in both directions | Number  | -                |
+| count              | Total number of stars                                                            | Number  | 5                |
+| icon-size          | Star size                                                                        | Number  | 18               |
+| active-color       | Icon selection color                                                             | String  | #fa200c          |
+| void-color         | Icon unselected color                                                            | String  | #ccc             |
+| unchecked-icon     | Use icon (unchecked)[icon name](#/icon)                                          | String  | star-n           |
+| checked-icon       | Use icon (checked)[icon name](#/icon)                                            | String  | star-fill-n      |
+| font-class-name    | Custom icon font base class name                                                 | String  | `nutui-iconfont` |
+| class-prefix       | Custom icon class name prefix for using custom icons                             | String  | `nut-icon`       |
+| allow-half         | half star                                                                        | Boolean | false            |
+| readonly           | read-only                                                                        | Boolean | false            |
+| disabled           | whether to disable                                                               | Boolean | false            |
+| spacing            | spacing                                                                          | Number  | 20               |
+| touchable`v3.1.22` | Whether to allow select rate by touch gesture                                    | Boolean | true             |
+
 
 ## Event
 | Event  | Description                                                | Arguments |

+ 16 - 15
src/packages/__VUE/rate/doc.md

@@ -199,21 +199,22 @@ export default {
 
 ## Prop
 
-| 字段            | 说明                                      | 类型    | 默认值           |
-|-----------------|-------------------------------------------|---------|------------------|
-| v-model         | 当前 star 数,可使用 v-model 双向绑定数据 | Number  | -                |
-| count           | star 总数                                 | Number  | 5                |
-| icon-size       | star 大小                                 | Number  | 18               |
-| active-color    | 图标选中颜色                              | String  | #fa200c          |
-| void-color      | 图标未选中颜色                            | String  | #ccc             |
-| unchecked-icon  | 使用图标(未选中)[图标名称](#/icon)    | String  | star-n           |
-| checked-icon    | 使用图标(选中)[图标名称](#/icon)       | String  | star-fill-n      |
-| font-class-name | 自定义icon 字体基础类名                   | String  | `nutui-iconfont` |
-| class-prefix    | 自定义icon 类名前缀,用于使用自定义图标   | String  | `nut-icon`       |
-| allow-half      | 是否半星                                  | Boolean | false            |
-| readonly        | 是否只读                                  | Boolean | false            |
-| disabled        | 是否禁用                                  | Boolean | false            |
-| spacing         | 间距                                      | Number  | 20               |
+| 字段                               | 说明                                      | 类型    | 默认值           |
+|------------------------------------|-------------------------------------------|---------|------------------|
+| v-model                            | 当前 star 数,可使用 v-model 双向绑定数据 | Number  | -                |
+| count                              | star 总数                                 | Number  | 5                |
+| icon-size                          | star 大小                                 | Number  | 18               |
+| active-color                       | 图标选中颜色                              | String  | #fa200c          |
+| void-color                         | 图标未选中颜色                            | String  | #ccc             |
+| unchecked-icon                     | 使用图标(未选中)[图标名称](#/icon)        | String  | star-n           |
+| checked-icon                       | 使用图标(选中)[图标名称](#/icon)          | String  | star-fill-n      |
+| font-class-name                    | 自定义icon 字体基础类名                   | String  | `nutui-iconfont` |
+| class-prefix                       | 自定义icon 类名前缀,用于使用自定义图标   | String  | `nut-icon`       |
+| allow-half                         | 是否半星                                  | Boolean | false            |
+| readonly                           | 是否只读                                  | Boolean | false            |
+| disabled                           | 是否禁用                                  | Boolean | false            |
+| spacing                            | 间距                                      | Number  | 20               |
+| touchable`v3.1.22` `小程序暂不支持` | 是否可以通过滑动手势选择评分              | Boolean | true             |
 
 ## Event
 | 字段   | 说明                       | 回调参数 |

+ 1 - 1
src/packages/__VUE/rate/index.scss

@@ -1,5 +1,5 @@
 .nut-rate {
-  display: flex;
+  display: inline-flex;
   &-item {
     display: flex;
     flex-shrink: 0;

+ 4 - 124
src/packages/__VUE/rate/index.taro.vue

@@ -1,127 +1,7 @@
-<template>
-  <view :class="classes">
-    <view class="nut-rate-item" v-for="n in count" :key="n" :style="{ marginRight: pxCheck(spacing) }">
-      <nut-icon
-        :size="iconSize"
-        class="nut-rate-item__icon"
-        @click="onClick(1, n)"
-        :class="{ 'nut-rate-item__icon--disabled': disabled || n > modelValue }"
-        :color="n <= modelValue ? activeColor : voidColor"
-        :font-class-name="fontClassName"
-        :class-prefix="classPrefix"
-        :name="n <= modelValue ? checkedIcon : uncheckedIcon"
-      />
-      <nut-icon
-        v-if="allowHalf && modelValue + 1 > n"
-        class="nut-rate-item__icon nut-rate-item__icon--half"
-        @click="onClick(2, n)"
-        :font-class-name="fontClassName"
-        :class-prefix="classPrefix"
-        :color="n <= modelValue + 1 ? activeColor : voidColor"
-        :size="iconSize"
-        :name="checkedIcon"
-      />
-      <nut-icon
-        v-else-if="allowHalf && modelValue + 1 < n"
-        class="nut-rate-item__icon nut-rate-item__icon--disabled nut-rate-item__icon--half"
-        @click="onClick(2, n)"
-        :font-class-name="fontClassName"
-        :class-prefix="classPrefix"
-        :color="voidColor"
-        :size="iconSize"
-        :name="uncheckedIcon"
-      />
-    </view>
-  </view>
-</template>
+<template src="./template.html"></template>
 <script lang="ts">
-import { computed } from 'vue';
 import { createComponent } from '@/packages/utils/create';
-import { pxCheck } from '@/packages/utils/pxCheck';
-const { componentName, create } = createComponent('rate');
-import Taro from '@tarojs/taro';
-export default create({
-  props: {
-    count: {
-      type: [String, Number],
-      default: 5
-    },
-    modelValue: {
-      type: [String, Number],
-      default: 0
-    },
-    iconSize: {
-      type: [String, Number],
-      default: 18
-    },
-    activeColor: {
-      type: String,
-      default: ''
-    },
-    voidColor: {
-      type: String,
-      default: ''
-    },
-    uncheckedIcon: {
-      type: String,
-      default: 'star-n'
-    },
-    checkedIcon: {
-      type: String,
-      default: 'star-fill-n'
-    },
-    readonly: {
-      type: Boolean,
-      default: false
-    },
-    disabled: {
-      type: Boolean,
-      default: false
-    },
-    allowHalf: {
-      type: Boolean,
-      default: false
-    },
-    spacing: {
-      type: [String, Number],
-      default: 14
-    },
-    classPrefix: {
-      type: String,
-      default: 'nut-icon'
-    },
-    fontClassName: {
-      type: String,
-      default: 'nutui-iconfont'
-    }
-  },
-  emits: ['update:modelValue', 'change'],
-  setup(props, { emit }) {
-    const classes = computed(() => {
-      const prefixCls = componentName;
-      return {
-        [prefixCls]: true
-      };
-    });
-    const onClick = (e: number, index: number) => {
-      if (props.disabled || props.readonly) return;
-      let value = 0;
-      if (index === 1 && props.modelValue === index) {
-      } else {
-        value = index;
-        if (props.allowHalf && e == 2) {
-          value -= 0.5;
-        }
-      }
-      emit('update:modelValue', value);
-      emit('change', value);
-    };
-
-    return {
-      classes,
-      onClick,
-      pxCheck
-    };
-  }
-});
+const { create } = createComponent('rate');
+import { taroComponent } from './common';
+export default create(taroComponent);
 </script>

+ 4 - 130
src/packages/__VUE/rate/index.vue

@@ -1,133 +1,7 @@
-<template>
-  <view :class="classes">
-    <view
-      class="nut-rate-item"
-      v-for="n in count"
-      :key="n"
-      @click="onClick($event, n)"
-      :style="{ marginRight: pxCheck(spacing) }"
-    >
-      <nut-icon
-        :size="iconSize"
-        class="nut-rate-item__icon"
-        :class="{ 'nut-rate-item__icon--disabled': disabled || n > modelValue }"
-        :font-class-name="fontClassName"
-        :class-prefix="classPrefix"
-        :color="n <= modelValue ? activeColor : voidColor"
-        :name="n <= modelValue ? checkedIcon : uncheckedIcon"
-      />
-      <nut-icon
-        v-if="allowHalf && modelValue + 1 > n"
-        class="nut-rate-item__icon nut-rate-item__icon--half"
-        :font-class-name="fontClassName"
-        :class-prefix="classPrefix"
-        :color="n <= modelValue + 1 ? activeColor : voidColor"
-        :size="iconSize"
-        :name="checkedIcon"
-      />
-      <nut-icon
-        v-else-if="allowHalf && modelValue + 1 < n"
-        class="nut-rate-item__icon nut-rate-item__icon--disabled nut-rate-item__icon--half"
-        :font-class-name="fontClassName"
-        :class-prefix="classPrefix"
-        :color="voidColor"
-        :size="iconSize"
-        :name="uncheckedIcon"
-      />
-    </view>
-  </view>
-</template>
+<template src="./template.html"></template>
 <script lang="ts">
-import { computed } from 'vue';
 import { createComponent } from '@/packages/utils/create';
-import { pxCheck } from '@/packages/utils/pxCheck';
-const { componentName, create } = createComponent('rate');
-export default create({
-  props: {
-    count: {
-      type: [String, Number],
-      default: 5
-    },
-    modelValue: {
-      type: [String, Number],
-      default: 0
-    },
-    iconSize: {
-      type: [String, Number],
-      default: 18
-    },
-    activeColor: {
-      type: String,
-      default: ''
-    },
-    voidColor: {
-      type: String,
-      default: ''
-    },
-    uncheckedIcon: {
-      type: String,
-      default: 'star-n'
-    },
-    checkedIcon: {
-      type: String,
-      default: 'star-fill-n'
-    },
-    readonly: {
-      type: Boolean,
-      default: false
-    },
-    disabled: {
-      type: Boolean,
-      default: false
-    },
-    allowHalf: {
-      type: Boolean,
-      default: false
-    },
-    spacing: {
-      type: [String, Number],
-      default: 14
-    },
-    classPrefix: {
-      type: String,
-      default: 'nut-icon'
-    },
-    fontClassName: {
-      type: String,
-      default: 'nutui-iconfont'
-    }
-  },
-  emits: ['update:modelValue', 'change'],
-  setup(props, { emit }) {
-    const classes = computed(() => {
-      const prefixCls = componentName;
-      return {
-        [prefixCls]: true
-      };
-    });
-    const onClick = (e: Event, index: number) => {
-      e.preventDefault();
-      e.stopPropagation();
-      if (props.disabled || props.readonly) return;
-      let value = 0;
-      if (index === 1 && props.modelValue === index) {
-      } else {
-        value = index;
-        if (props.allowHalf) {
-          if ((e?.target as Element).className.includes('__icon--half')) {
-            value -= 0.5;
-          }
-        }
-      }
-      emit('update:modelValue', value);
-      emit('change', value);
-    };
-
-    return {
-      classes,
-      onClick,
-      pxCheck
-    };
-  }
-});
+const { create } = createComponent('rate');
+import { component } from './common';
+export default create(component);
 </script>

+ 16 - 0
src/packages/__VUE/rate/template.html

@@ -0,0 +1,16 @@
+<view :class="classes" @touchstart="onTouchStart" @touchmove="onTouchMove">
+    <view class="nut-rate-item" v-for="n in Number(count)" :key="n" ref="rateRefs" :id="'rateRefs-' + refRandomId + n"
+        :style="{ marginRight: pxCheck(spacing) }">
+        <nut-icon :size="iconSize" class="nut-rate-item__icon" @click="onClick(1, n)"
+            :class="{ 'nut-rate-item__icon--disabled': disabled || n > modelValue }"
+            :color="n <= modelValue ? activeColor : voidColor" :font-class-name="fontClassName"
+            :class-prefix="classPrefix" :name="n <= modelValue ? checkedIcon : uncheckedIcon" />
+        <nut-icon v-if="allowHalf && Number(modelValue) + 1 > n" class="nut-rate-item__icon nut-rate-item__icon--half"
+            @click="onClick(2, n)" :font-class-name="fontClassName" :class-prefix="classPrefix"
+            :color="n <= Number(modelValue) + 1 ? activeColor : voidColor" :size="iconSize" :name="checkedIcon" />
+        <nut-icon v-else-if="allowHalf && Number(modelValue) + 1 < n"
+            class="nut-rate-item__icon nut-rate-item__icon--disabled nut-rate-item__icon--half" @click="onClick(2, n)"
+            :font-class-name="fontClassName" :class-prefix="classPrefix" :color="voidColor" :size="iconSize"
+            :name="uncheckedIcon" />
+    </view>
+</view>

+ 6 - 0
src/packages/utils/useTouch/index.ts

@@ -17,6 +17,8 @@ function getDirection(x: number, y: number) {
 export function useTouch() {
   const startX = ref(0);
   const startY = ref(0);
+  const moveX = ref(0);
+  const moveY = ref(0);
   const deltaX = ref(0);
   const deltaY = ref(0);
   const offsetX = ref(0);
@@ -44,6 +46,8 @@ export function useTouch() {
     const touch = event.touches[0];
     deltaX.value = touch.clientX - startX.value;
     deltaY.value = touch.clientY - startY.value;
+    moveX.value = touch.clientX;
+    moveY.value = touch.clientY;
     offsetX.value = Math.abs(deltaX.value);
     offsetY.value = Math.abs(deltaY.value);
 
@@ -58,6 +62,8 @@ export function useTouch() {
     reset,
     startX,
     startY,
+    moveX,
+    moveY,
     deltaX,
     deltaY,
     offsetX,

+ 1 - 1
src/sites/mobile-taro/vue/package.json

@@ -41,7 +41,7 @@
     "@tarojs/mini-runner": "^3.4.10",
     "@tarojs/runtime": "^3.4.10",
     "@tarojs/taro": "^3.4.10",
-    "vue": "3.2.34"
+    "vue": "3.2.37"
   },
   "devDependencies": {
     "@babel/core": "^7.8.0",