Browse Source

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

richard1015 4 years ago
parent
commit
ed25bb23e0

+ 22 - 0
src/config.json

@@ -546,6 +546,28 @@
           "show": false,
           "desc": "标签栏子组件",
           "author": "Drjingfubo"
+        },
+        {
+          "version": "3.0.0",
+          "name": "NoticeBar",
+          "type": "component",
+          "cName": "公告栏",
+          "desc": "用于循环播放展示一组消息通知",
+          "sort": 8,
+          "show": true,
+          "taro": false,
+          "author": "yangxiaolu"
+        },
+        {
+          "version": "3.0.0",
+          "name": "Elevator",
+          "type": "component",
+          "cName": "电梯楼层",
+          "desc": "用于列表快速定位以及索引的显示",
+          "sort": 5,
+          "show": true,
+          "taro": true,
+          "author": "szg2008"
         }
       ]
     },

+ 200 - 0
src/packages/__VUE/elevator/demo.vue

@@ -0,0 +1,200 @@
+<template>
+  <div class="demo">
+    <h2>基本用法</h2>
+    <div class="elevator-wrapper">
+      <nut-elevator
+        :index-list="dataList"
+        :height="260"
+        @click-item="clickItem"
+        @click-index="clickIndex"
+      ></nut-elevator>
+    </div>
+
+    <h2>自定义索引key</h2>
+    <div class="elevator-wrapper">
+      <nut-elevator
+        :index-list="dataList2"
+        :height="220"
+        :acceptKey="acceptKey"
+        @click-item="clickItem"
+        @click-index="clickIndex"
+      ></nut-elevator>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { reactive, toRefs } from 'vue';
+import { createComponent } from '../../utils/create';
+const { createDemo } = createComponent('elevator');
+export default createDemo({
+  setup() {
+    const state = reactive({
+      acceptKey: 'num',
+      dataList: [
+        {
+          title: 'A',
+          list: [
+            {
+              name: '安徽',
+              id: 1
+            }
+          ]
+        },
+        {
+          title: 'B',
+          list: [
+            {
+              name: '北京',
+              id: 2
+            }
+          ]
+        },
+        {
+          title: 'G',
+          list: [
+            {
+              name: '广西',
+              id: 3
+            },
+            {
+              name: '广东',
+              id: 4
+            }
+          ]
+        },
+        {
+          title: 'H',
+          list: [
+            {
+              name: '湖南',
+              id: 5
+            },
+            {
+              name: '湖北',
+              id: 6
+            }
+          ]
+        }
+      ],
+      dataList2: [
+        {
+          num: '一',
+          list: [
+            {
+              name: '北京',
+              id: 1
+            },
+            {
+              name: '上海',
+              id: 2
+            },
+            {
+              name: '深圳',
+              id: 3
+            },
+            {
+              name: '广州',
+              id: 4
+            },
+            {
+              name: '杭州',
+              id: 5
+            }
+          ]
+        },
+        {
+          num: '二',
+          list: [
+            {
+              name: '成都',
+              id: 6
+            },
+            {
+              name: '西安',
+              id: 7
+            },
+            {
+              name: '天津',
+              id: 8
+            },
+            {
+              name: '武汉',
+              id: 9
+            },
+            {
+              name: '长沙',
+              id: 10
+            },
+            {
+              name: '重庆',
+              id: 11
+            },
+            {
+              name: '苏州',
+              id: 12
+            },
+            {
+              name: '南京',
+              id: 13
+            }
+          ]
+        },
+        {
+          num: '三',
+          list: [
+            {
+              name: '西宁',
+              id: 14
+            },
+            {
+              name: '兰州',
+              id: 15
+            },
+            {
+              name: '石家庄',
+              id: 16
+            },
+            {
+              name: '秦皇岛',
+              id: 17
+            },
+            {
+              name: '大连',
+              id: 18
+            },
+            {
+              name: '哈尔滨',
+              id: 19
+            },
+            {
+              name: '长春',
+              id: 20
+            },
+            {
+              name: '太原',
+              id: 21
+            }
+          ]
+        }
+      ]
+    });
+
+    const clickItem = (key: string, item: any) => {
+      console.log(key, JSON.stringify(item));
+    };
+
+    const clickIndex = (key: string) => {
+      console.log(key);
+    };
+
+    return { ...toRefs(state), clickItem, clickIndex };
+  }
+});
+</script>
+
+<style lang="scss" scoped>
+.demo {
+  background-color: #fff !important;
+}
+</style>

+ 91 - 0
src/packages/__VUE/elevator/doc.md

@@ -0,0 +1,91 @@
+# Elevator 电梯组件
+
+### 介绍
+
+用于列表快速定位以及索引的显示
+
+### 安装
+
+``` javascript
+import { createApp } from 'vue';
+// vue
+import { Elevator } from '@nutui/nutui';
+// taro
+import { Elevator } from '@nutui/nutui-taro';
+
+const app = createApp();
+app.use(Elevator);
+
+```
+
+### 基本用法
+
+```js
+<nut-elevator :index-list="dataList" :height="260" @click-item="clickItem" @click-index="clickIndex"></nut-elevator>
+
+setup() {
+    const state = reactive({
+      acceptKey: 'num',
+      dataList: [
+        {
+          title: 'A',
+          list: [
+            {
+              name: '安徽',
+              id: 1
+            }
+          ]
+        }
+        ...
+      ],
+      dataList2: [
+        {
+          num: '一',
+          list: [
+            {
+              name: '北京',
+              id: 1
+            }
+          ]
+        }
+        ...
+      ]
+    });
+
+    const clickItem = (key: string, item: any) => {
+      console.log(key, JSON.stringify(item));
+    };
+
+    const clickIndex = (key: string) => {
+      console.log(key);
+    };
+
+    return { ...toRefs(state), clickItem, clickIndex };
+  }
+```
+
+### 自定义索引
+
+```js
+<nut-elevator :index-list="dataList2" :height="220" :acceptKey="acceptKey" @click-item="clickItem" @click-index="clickIndex"></nut-elevator>
+
+```
+
+## API
+
+### Prop
+
+| 字段                   | 说明                                                             | 类型    | 默认值 |
+|------------------------|----------------------------------------------------------------|---------|------|
+| height                 | 电梯区域的高度                                                    | Number、String  | `200px`
+| accept-key             | 索引 key 值                                                      | String  | `title` |
+| index-list             | 索引列表                                                         | Array(item需包含 id、name属性)  | `[{id: 0, name: ''}]` |
+
+### Event
+
+| 名称  | 说明     | 回调参数    |
+|-------|----------|-------------|
+| click-item | 点击内容 | key: string, item: { id: 0, name: '' } |
+| click-index | 点击索引 | key: string |
+
+

+ 72 - 0
src/packages/__VUE/elevator/index.scss

@@ -0,0 +1,72 @@
+.nut-elevator {
+  width: 100%;
+  display: block;
+  position: relative;
+  &__list {
+    overflow: auto;
+    &__item {
+      font-size: 12px;
+      color: #333;
+      &__code {
+        position: relative;
+        height: 35px;
+        line-height: 35px;
+        font-size: 14px;
+        color: #1a1a1a;
+        padding: 0 20px;
+        font-weight: 500;
+        &::after {
+          content: ' ';
+          width: 100%;
+          height: 1px;
+          position: absolute;
+          left: 0;
+          bottom: 0;
+          background-color: #f5f5f5;
+        }
+      }
+      &__name {
+        display: flex;
+        align-items: center;
+        padding: 0 20px;
+        height: 30px;
+        line-height: 30px;
+      }
+    }
+  }
+  &__code--current {
+    position: absolute;
+    right: 60px;
+    top: 50%;
+    transform: translateY(-50%);
+    width: 45px;
+    height: 45px;
+    line-height: 45px;
+    border-radius: 50%;
+    background: $white;
+    box-shadow: 0 3px 3px 1px rgba(240, 240, 240, 1);
+    text-align: center;
+  }
+  &__bars {
+    position: absolute;
+    right: 8px;
+    top: 50%;
+    transform: translateY(-50%);
+    padding: 15px 0;
+    background-color: #eeeff2;
+    border-radius: 6px;
+    text-align: center;
+    z-index: 10;
+    &__inner {
+      &__item {
+        display: block;
+        padding: 3px;
+        font-size: 10px;
+      }
+    }
+  }
+}
+
+view {
+  display: block;
+}

+ 213 - 0
src/packages/__VUE/elevator/index.taro.vue

@@ -0,0 +1,213 @@
+<template>
+  <view :class="classes">
+    <scroll-view
+      class="nut-elevator__list scrollview"
+      :scroll-top="scrollTop"
+      :scroll-y="true"
+      ref="listview"
+      :style="{ height: isNaN(+height) ? height : `${height}px` }"
+    >
+      <view
+        :class="['nut-elevator__list__item', `elevator__item__${index}`]"
+        v-for="(item, index) in indexList"
+        :key="item[acceptKey]"
+        :ref="setListGroup"
+      >
+        <view class="nut-elevator__list__item__code">{{
+          item[acceptKey]
+        }}</view>
+        <view
+          class="nut-elevator__list__item__name"
+          v-for="subitem in item.list"
+          :key="subitem['id']"
+          @click="handleClickItem(item[acceptKey], subitem)"
+          >{{ subitem.name }}</view
+        >
+      </view>
+    </scroll-view>
+    <view class="nut-elevator__code--current" v-show="scrollStart">{{
+      indexList[currentIndex][acceptKey]
+    }}</view>
+    <view
+      class="nut-elevator__bars"
+      @touchstart="touchStart"
+      @touchmove.stop.prevent="touchMove"
+      @touchend="touchEnd"
+    >
+      <view class="nut-elevator__bars__inner">
+        <view
+          class="nut-elevator__bars__inner__item"
+          :data-index="index"
+          v-for="(item, index) in indexList"
+          :key="item[acceptKey]"
+          @click="handleClickIndex(item[acceptKey])"
+          >{{ item[acceptKey] }}</view
+        >
+      </view>
+    </view>
+  </view>
+</template>
+<script lang="ts">
+import { computed, reactive, toRefs, nextTick, ref, Ref, onMounted } from 'vue';
+import { createComponent } from '../../utils/create';
+const { componentName, create } = createComponent('elevator');
+import Taro, { eventCenter, getCurrentInstance } from '@tarojs/taro';
+interface ElevatorData {
+  name: string;
+  id: number | string;
+  [key: string]: string | number;
+}
+export default create({
+  props: {
+    height: {
+      type: [Number, String],
+      default: '200px'
+    },
+    acceptKey: {
+      type: [String],
+      default: 'title'
+    },
+    indexList: {
+      type: Array,
+      default: () => {
+        return [];
+      }
+    }
+  },
+  emits: ['click-item', 'click-index'],
+  setup(props: any, context: any) {
+    const spaceHeight = 23;
+    const listview: Ref<HTMLElement> = ref() as Ref<HTMLElement>;
+    const state = reactive({
+      anchorIndex: 0,
+      listHeight: [] as number[],
+      listGroup: [] as HTMLLIElement[],
+      touchState: {
+        y1: 0,
+        y2: 0
+      },
+      scrollStart: false,
+      currentIndex: 0,
+      query: Taro.createSelectorQuery(),
+      scrollTop: 0,
+      storageListHeight: [] as number[]
+    });
+
+    const classes = computed(() => {
+      const prefixCls = componentName;
+      return {
+        [prefixCls]: true
+      };
+    });
+
+    //重置滚动参数
+    const resetScrollState = () => {
+      state.anchorIndex = 0;
+      state.listHeight = [];
+      state.listGroup = [];
+      state.currentIndex = 0;
+      state.scrollStart = false;
+      state.touchState = {
+        y1: 0,
+        y2: 0
+      };
+    };
+
+    const getData = (el: HTMLElement): string | void => {
+      if (!el.dataset.index) {
+        return '0';
+      }
+      return el.dataset.index as string;
+    };
+
+    const setListGroup = (el: HTMLLIElement) => {
+      nextTick(() => {
+        if (!state.listGroup.includes(el) && el != null) {
+          state.listGroup.push(el);
+        }
+      });
+    };
+
+    const calculateHeight = () => {
+      let height = 0;
+      state.listHeight.push(height);
+      state.storageListHeight.push(height);
+      for (let i = 0; i < state.listGroup.length; i++) {
+        state.query.selectAll(`.elevator__item__${i}`).boundingClientRect();
+        state.query.exec((res: any) => {
+          height += res[i][0].height;
+          state.listHeight.push(height);
+          state.storageListHeight.push(height);
+        });
+      }
+    };
+
+    const scrollTo = (index: number) => {
+      if (!index && index !== 0) {
+        return;
+      }
+      if (!state.listHeight.length) {
+        state.listHeight = state.storageListHeight.slice();
+      }
+      if (index < 0) index = 0;
+      if (index > state.listHeight.length - 2)
+        index = state.listHeight.length - 2;
+      state.currentIndex = index;
+      state.scrollTop = state.listHeight[index];
+    };
+
+    const touchStart = (e: TouchEvent) => {
+      state.scrollStart = true;
+      let index = getData(e.target as HTMLElement);
+      let firstTouch = e.touches[0];
+      state.touchState.y1 = firstTouch.pageY;
+      state.anchorIndex = +index;
+      state.currentIndex = +index;
+      scrollTo(+index);
+    };
+
+    const touchMove = (e: TouchEvent) => {
+      let firstTouch = e.touches[0];
+      state.touchState.y2 = firstTouch.pageY;
+      let delta =
+        ((state.touchState.y2 - state.touchState.y1) / spaceHeight) | 0;
+      state.currentIndex = state.anchorIndex + delta;
+      scrollTo(state.currentIndex);
+    };
+
+    const touchEnd = () => {
+      resetScrollState();
+    };
+
+    const handleClickItem = (key: string, item: ElevatorData) => {
+      context.emit('click-item', key, item);
+    };
+
+    const handleClickIndex = (key: string) => {
+      context.emit('click-index', key);
+    };
+
+    onMounted(() => {
+      eventCenter.once((getCurrentInstance() as any).router.onReady, () => {
+        calculateHeight();
+      });
+    });
+
+    return {
+      classes,
+      ...toRefs(state),
+      setListGroup,
+      listview,
+      touchStart,
+      touchMove,
+      touchEnd,
+      handleClickItem,
+      handleClickIndex
+    };
+  }
+});
+</script>
+
+<style lang="scss">
+@import 'index.scss';
+</style>

+ 196 - 0
src/packages/__VUE/elevator/index.vue

@@ -0,0 +1,196 @@
+<template>
+  <view :class="classes">
+    <view
+      class="nut-elevator__list"
+      ref="listview"
+      :style="{ height: isNaN(+height) ? height : `${height}px` }"
+    >
+      <view
+        class="nut-elevator__list__item"
+        v-for="item in indexList"
+        :key="item[acceptKey]"
+        :ref="setListGroup"
+      >
+        <view class="nut-elevator__list__item__code">{{
+          item[acceptKey]
+        }}</view>
+        <view
+          class="nut-elevator__list__item__name"
+          v-for="subitem in item.list"
+          :key="subitem['id']"
+          @click="handleClickItem(item[acceptKey], subitem)"
+          >{{ subitem.name }}</view
+        >
+      </view>
+    </view>
+    <view class="nut-elevator__code--current" v-show="scrollStart">{{
+      indexList[currentIndex][acceptKey]
+    }}</view>
+    <view
+      class="nut-elevator__bars"
+      @touchstart="touchStart"
+      @touchmove.stop.prevent="touchMove"
+      @touchend="touchEnd"
+    >
+      <view class="nut-elevator__bars__inner">
+        <view
+          class="nut-elevator__bars__inner__item"
+          :data-index="index"
+          v-for="(item, index) in indexList"
+          :key="item[acceptKey]"
+          @click="handleClickIndex(item[acceptKey])"
+          >{{ item[acceptKey] }}</view
+        >
+      </view>
+    </view>
+  </view>
+</template>
+<script lang="ts">
+import { computed, reactive, toRefs, nextTick, ref, Ref } from 'vue';
+import { createComponent } from '../../utils/create';
+const { componentName, create } = createComponent('elevator');
+interface ElevatorData {
+  name: string;
+  id: number | string;
+  [key: string]: string | number;
+}
+export default create({
+  props: {
+    height: {
+      type: [Number, String],
+      default: '200px'
+    },
+    acceptKey: {
+      type: [String],
+      default: 'title'
+    },
+    indexList: {
+      type: Array,
+      default: () => {
+        return [];
+      }
+    }
+  },
+  emits: ['click-item', 'click-index'],
+  setup(props: any, context: any) {
+    const spaceHeight = 23;
+    const listview: Ref<any> = ref(null);
+    const state = reactive({
+      anchorIndex: 0,
+      listHeight: [] as number[],
+      listGroup: [] as HTMLLIElement[],
+      touchState: {
+        y1: 0,
+        y2: 0
+      },
+      scrollStart: false,
+      currentIndex: 0
+    });
+
+    const classes = computed(() => {
+      const prefixCls = componentName;
+      return {
+        [prefixCls]: true
+      };
+    });
+
+    //重置滚动参数
+    const resetScrollState = () => {
+      state.anchorIndex = 0;
+      state.listHeight = [];
+      state.listGroup = [];
+      state.currentIndex = 0;
+      state.scrollStart = false;
+      state.touchState = {
+        y1: 0,
+        y2: 0
+      };
+    };
+
+    const getData = (el: HTMLElement, name: string): string | void => {
+      const prefix = 'data-';
+      return el.getAttribute(prefix + name) as string;
+    };
+
+    const setListGroup = (el: HTMLLIElement) => {
+      nextTick(() => {
+        if (!state.listGroup.includes(el) && el != null) {
+          state.listGroup.push(el);
+        }
+      });
+    };
+
+    const calculateHeight = () => {
+      let height = 0;
+      state.listHeight.push(height);
+      for (let i = 0; i < state.listGroup.length; i++) {
+        let item = state.listGroup[i];
+        height += item.clientHeight;
+        state.listHeight.push(height);
+      }
+    };
+
+    const scrollTo = (index: number) => {
+      if (!index && index !== 0) {
+        return;
+      }
+      if (!state.listHeight.length) {
+        calculateHeight();
+      }
+      if (index < 0) index = 0;
+      if (index > state.listHeight.length - 2)
+        index = state.listHeight.length - 2;
+      state.currentIndex = index;
+      listview.value.scrollTo(0, state.listHeight[index]);
+    };
+
+    const touchStart = (e: TouchEvent) => {
+      state.scrollStart = true;
+      let index = getData(e.target as HTMLElement, 'index');
+      let firstTouch = e.touches[0];
+      state.touchState.y1 = firstTouch.pageY;
+      state.anchorIndex = +index;
+      state.currentIndex = +index;
+      console.log(state.currentIndex);
+      scrollTo(+index);
+    };
+
+    const touchMove = (e: TouchEvent) => {
+      let firstTouch = e.touches[0];
+      state.touchState.y2 = firstTouch.pageY;
+      let delta =
+        ((state.touchState.y2 - state.touchState.y1) / spaceHeight) | 0;
+      state.currentIndex = state.anchorIndex + delta;
+      scrollTo(state.currentIndex);
+    };
+
+    const touchEnd = () => {
+      resetScrollState();
+    };
+
+    const handleClickItem = (key: string, item: ElevatorData) => {
+      context.emit('click-item', key, item);
+    };
+
+    const handleClickIndex = (key: string) => {
+      context.emit('click-index', key);
+    };
+
+    return {
+      classes,
+      ...toRefs(state),
+      setListGroup,
+      listview,
+      touchStart,
+      touchMove,
+      touchEnd,
+      handleClickItem,
+      handleClickIndex
+    };
+  }
+});
+</script>
+
+<style lang="scss" scoped>
+@import 'index.scss';
+</style>

+ 2 - 1
src/sites/mobile-taro/vue/src/app.config.ts

@@ -44,7 +44,8 @@ export default {
         'pages/navbar/index',
         'pages/tabbar/index',
         'pages/tab/index',
-        'pages/fixednav/index'
+        'pages/fixednav/index',
+        'pages/elevator/index'
       ]
     },
     {

+ 4 - 0
src/sites/mobile-taro/vue/src/nav/pages/elevator/index.config.ts

@@ -0,0 +1,4 @@
+export default {
+  navigationBarTitleText: 'Elevator',
+  disableScroll: true
+};

+ 154 - 0
src/sites/mobile-taro/vue/src/nav/pages/elevator/index.vue

@@ -0,0 +1,154 @@
+<template>
+  <div class="demo elevator-demo">
+    <h2>基本用法</h2>
+    <div class="elevator-wrapper">
+      <nut-elevator
+        :index-list="dataList"
+        height="600px"
+        @click-item="clickItem"
+        @click-index="clickIndex"
+      ></nut-elevator>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { reactive, toRefs, defineComponent } from 'vue';
+export default defineComponent({
+  setup() {
+    const state = reactive({
+      dataList: [
+        {
+          title: 'A',
+          list: [
+            {
+              name: '安徽',
+              id: 1
+            }
+          ]
+        },
+        {
+          title: 'B',
+          list: [
+            {
+              name: '北京',
+              id: 2
+            }
+          ]
+        },
+        {
+          title: 'C',
+          list: [
+            {
+              name: '重庆',
+              id: 3
+            }
+          ]
+        },
+        {
+          title: 'F',
+          list: [
+            {
+              name: '福建',
+              id: 4
+            }
+          ]
+        },
+        {
+          title: 'G',
+          list: [
+            {
+              name: '广西',
+              id: 5
+            },
+            {
+              name: '广东',
+              id: 6
+            },
+            {
+              name: '甘肃',
+              id: 7
+            },
+            {
+              name: '贵州',
+              id: 8
+            }
+          ]
+        },
+        {
+          title: 'H',
+          list: [
+            {
+              name: '湖南',
+              id: 9
+            },
+            {
+              name: '湖北',
+              id: 10
+            },
+            {
+              name: '海南',
+              id: 11
+            },
+            {
+              name: '河北',
+              id: 12
+            },
+            {
+              name: '河南',
+              id: 13
+            },
+            {
+              name: '黑龙江',
+              id: 14
+            }
+          ]
+        },
+        {
+          title: 'J',
+          list: [
+            {
+              name: '吉林',
+              id: 15
+            },
+            {
+              name: '江苏',
+              id: 16
+            },
+            {
+              name: '江西',
+              id: 17
+            }
+          ]
+        },
+        {
+          title: 'L',
+          list: [
+            {
+              name: '辽宁',
+              id: 18
+            }
+          ]
+        }
+      ]
+    });
+
+    const clickItem = (key: string, item: any) => {
+      console.log(key, JSON.stringify(item));
+    };
+
+    const clickIndex = (key: string) => {
+      console.log(key);
+    };
+
+    return { ...toRefs(state), clickItem, clickIndex };
+  }
+});
+</script>
+
+<style lang="scss">
+.elevator-demo {
+  background-color: #fff !important;
+  overflow-y: hidden !important;
+}
+</style>