浏览代码

feat: 新增九宫格组件 (#544)

Drjingfubo 4 年之前
父节点
当前提交
da5eadc696

二进制
src/assets/img/bg.png


二进制
src/assets/img/grid.png


二进制
src/assets/img/refresh.png


+ 11 - 0
src/config.json

@@ -724,6 +724,17 @@
       "showDemo": true,
       "author": "zongyue3",
       "showTest": false
+    },
+    {
+      "version": "1.0.0",
+      "name": "NineGrid",
+      "type": "component",
+      "chnName": "九宫格",
+      "desc": "九宫格展示",
+      "sort": "6",
+      "showDemo": true,
+      "author": "Drjingfubo",
+      "showTest": true
     }
   ]
 }

+ 4 - 1
src/nutui.js

@@ -144,6 +144,8 @@ import Coupon from './packages/coupon/index.js';
 import './packages/coupon/coupon.scss';
 import Magic from './packages/magic/index.js';
 import './packages/magic/magic.scss';
+import NineGrid from './packages/ninegrid/index.js';
+import './packages/ninegrid/ninegrid.scss';
 
 const packages = {
   Cell,
@@ -215,7 +217,8 @@ const packages = {
   NumberKeyboard: NumberKeyboard,
   CollapseItem: CollapseItem,
   Coupon: Coupon,
-  Magic: Magic
+  Magic: Magic,
+  NineGrid: NineGrid
 };
 
 const components = {};

+ 5 - 0
src/packages/ninegrid/__test__/ninegrid.spec.js

@@ -0,0 +1,5 @@
+import { shallowMount, mount } from '@vue/test-utils';
+import NineGrid from '../ninegrid.vue';
+import Vue from 'vue';
+
+describe('NineGrid.vue', () => {});

+ 223 - 0
src/packages/ninegrid/animation.scss

@@ -0,0 +1,223 @@
+.fade-x-leave-active {
+  transition: all 0.5s ease;
+}
+.fade-x-enter,
+.fade-x-leave-to {
+  transform: translateX(100%);
+}
+
+.fade-x-enter-active {
+  animation: fade-in 0.6s;
+  -ms-animation: fade-in 0.6s;
+  -webkit-animation: fade-in 0.6s;
+}
+
+.bounce-enter-active {
+  animation: bounce-in 0.6s;
+  -ms-animation: bounce-in 0.6s;
+  -webkit-animation: bounce-in 0.6s;
+}
+
+.bounce-leave-active {
+  transition: all 0.25s ease;
+}
+
+.bounce-enter,
+.bounce-leave-to {
+  transform: scale(0);
+  // opacity: 0;
+}
+
+@keyframes fade-in {
+  0% {
+    transform: translateX(100%);
+  }
+
+  60% {
+    transform: translateX(0);
+  }
+
+  80% {
+    transform: translateX(80px);
+  }
+
+  100% {
+    transform: translateX(0);
+  }
+}
+
+@keyframes bounce-in {
+  0% {
+    transform: scale(0);
+  }
+
+  50% {
+    transform: scale(1.2);
+  }
+
+  100% {
+    transform: scale(1);
+  }
+}
+
+@keyframes shake-rotate {
+  0% {
+    transform: translate(0px, 0px) rotate(0deg);
+  }
+  2% {
+    transform: translate(0px, 0px) rotate(1.5deg);
+  }
+  4% {
+    transform: translate(0px, 0px) rotate(-7.5deg);
+  }
+  6% {
+    transform: translate(0px, 0px) rotate(-2.5deg);
+  }
+  8% {
+    transform: translate(0px, 0px) rotate(6.5deg);
+  }
+  10% {
+    transform: translate(0px, 0px) rotate(-4.5deg);
+  }
+  12% {
+    transform: translate(0px, 0px) rotate(0.5deg);
+  }
+  14% {
+    transform: translate(0px, 0px) rotate(-6.5deg);
+  }
+  16% {
+    transform: translate(0px, 0px) rotate(1.5deg);
+  }
+  18% {
+    transform: translate(0px, 0px) rotate(-2.5deg);
+  }
+  20% {
+    transform: translate(0px, 0px) rotate(1.5deg);
+  }
+  22% {
+    transform: translate(0px, 0px) rotate(1.5deg);
+  }
+  24% {
+    transform: translate(0px, 0px) rotate(2.5deg);
+  }
+  26% {
+    transform: translate(0px, 0px) rotate(6.5deg);
+  }
+  28% {
+    transform: translate(0px, 0px) rotate(2.5deg);
+  }
+  30% {
+    transform: translate(0px, 0px) rotate(1.5deg);
+  }
+  32% {
+    transform: translate(0px, 0px) rotate(-4.5deg);
+  }
+  34% {
+    transform: translate(0px, 0px) rotate(-3.5deg);
+  }
+  36% {
+    transform: translate(0px, 0px) rotate(-0.5deg);
+  }
+  38% {
+    transform: translate(0px, 0px) rotate(4.5deg);
+  }
+  40% {
+    transform: translate(0px, 0px) rotate(4.5deg);
+  }
+  42% {
+    transform: translate(0px, 0px) rotate(-0.5deg);
+  }
+  44% {
+    transform: translate(0px, 0px) rotate(1.5deg);
+  }
+  46% {
+    transform: translate(0px, 0px) rotate(-0.5deg);
+  }
+  48% {
+    transform: translate(0px, 0px) rotate(-7.5deg);
+  }
+  50% {
+    transform: translate(0px, 0px) rotate(3.5deg);
+  }
+  52% {
+    transform: translate(0px, 0px) rotate(-5.5deg);
+  }
+  54% {
+    transform: translate(0px, 0px) rotate(-6.5deg);
+  }
+  56% {
+    transform: translate(0px, 0px) rotate(-0.5deg);
+  }
+  58% {
+    transform: translate(0px, 0px) rotate(3.5deg);
+  }
+  60% {
+    transform: translate(0px, 0px) rotate(-2.5deg);
+  }
+  62% {
+    transform: translate(0px, 0px) rotate(3.5deg);
+  }
+  64% {
+    transform: translate(0px, 0px) rotate(6.5deg);
+  }
+  66% {
+    transform: translate(0px, 0px) rotate(-6.5deg);
+  }
+  68% {
+    transform: translate(0px, 0px) rotate(-2.5deg);
+  }
+  70% {
+    transform: translate(0px, 0px) rotate(-3.5deg);
+  }
+  72% {
+    transform: translate(0px, 0px) rotate(-6.5deg);
+  }
+  74% {
+    transform: translate(0px, 0px) rotate(-5.5deg);
+  }
+  76% {
+    transform: translate(0px, 0px) rotate(1.5deg);
+  }
+  78% {
+    transform: translate(0px, 0px) rotate(-1.5deg);
+  }
+  80% {
+    transform: translate(0px, 0px) rotate(3.5deg);
+  }
+  82% {
+    transform: translate(0px, 0px) rotate(1.5deg);
+  }
+  84% {
+    transform: translate(0px, 0px) rotate(-7.5deg);
+  }
+  86% {
+    transform: translate(0px, 0px) rotate(-0.5deg);
+  }
+  88% {
+    transform: translate(0px, 0px) rotate(0.5deg);
+  }
+  90% {
+    transform: translate(0px, 0px) rotate(-3.5deg);
+  }
+  92% {
+    transform: translate(0px, 0px) rotate(-4.5deg);
+  }
+  94% {
+    transform: translate(0px, 0px) rotate(2.5deg);
+  }
+  96% {
+    transform: translate(0px, 0px) rotate(2.5deg);
+  }
+  98% {
+    transform: translate(0px, 0px) rotate(-2.5deg);
+  }
+}
+
+@keyframes box-rotate {
+  0% {
+    transform: translate(0px, 0px) rotate(0deg);
+  }
+  100% {
+    transform: translate(0px, 0px) rotate(360deg);
+  }
+}

+ 104 - 0
src/packages/ninegrid/demo.vue

@@ -0,0 +1,104 @@
+<template>
+  <div class="demo-list">
+    <nut-ninegrid @toDetail="toDetail" @refresh="change" :data="dataArr"></nut-ninegrid>
+  </div>
+</template>
+<script>
+export default {
+  data() {
+    return {
+      dataArr: [
+        {
+          name: '商品名称名称',
+          price: '199',
+          pictureUrl: '//m.360buyimg.com/babel/s180x180_jfs/t1/174906/19/10256/188436/60a242afE89a800c9/801b64e5b80fde9a.jpg!q70.jpg'
+        },
+        {
+          name: '商品名称名称',
+          price: '199',
+          pictureUrl: '//m.360buyimg.com/babel/s180x180_jfs/t1/133995/36/19954/205635/60ccc436E19f8b69b/8fc0a468ac037d2e.jpg!q70.jpg'
+        },
+        {
+          name: '商品名称名称',
+          price: '199',
+          pictureUrl: '//m.360buyimg.com/babel/s180x180_jfs/t1/17279/28/13940/140479/60b984f4E723b9981/d007711aa1cdc358.jpg!q70.jpg'
+        },
+        {
+          name: '商品名称名称',
+          price: '199',
+          pictureUrl: '//m.360buyimg.com/babel/s180x180_jfs/t1/190452/2/84/116077/608627ecEef11d11e/e0a93f09eca31ddf.jpg!q70.jpg'
+        },
+        {
+          name: '商品名称名称',
+          price: '199',
+          pictureUrl: 'https://img10.360buyimg.com/n5/s54x54_jfs/t1/164065/10/8839/39628/603ee7edE9dee283f/e56acfa461919177.jpg'
+        },
+        {
+          name: '祥禾饽饽铺京东自营旗舰店',
+          price: '199',
+          pictureUrl: '//m.360buyimg.com/babel/s66x66_jfs/t1/195378/33/9432/145698/60d0400eE0520ca9f/2283995f6c6176e7.jpg!q50.jpg'
+        },
+        {
+          name: '鲜花4+1束 鲜花速递 ',
+          price: '199',
+          pictureUrl: '//m.360buyimg.com/babel/s180x180_jfs/t1/185809/36/6800/181830/60b4fdaaEa74ddfdf/7f3776e9a493ec20.jpg!q70.jpg'
+        },
+        {
+          name: '大连萨米托爱心樱桃',
+          price: '199',
+          pictureUrl: '//m.360buyimg.com/babel/s180x180_jfs/t1/191656/26/7699/116921/60c1ed9eE933be59e/5c77c8eabda19d0d.jpg!q70.jpg'
+        },
+        {
+          name: '鲜花玫瑰花速递鲜',
+          price: '199',
+          pictureUrl: '//m.360buyimg.com/babel/s180x180_jfs/t1/173012/21/12272/176325/60b4fedbE63906907/2e2a3b2d83624a1b.jpg!q70.jpg'
+        },
+        {
+          name: '贵州茅台酒',
+          price: '199',
+          pictureUrl: '//m.360buyimg.com/babel/s180x180_jfs/t1/175839/15/15604/255754/60cccb3cE897d48ed/586a5491fc09f48b.jpg!q70.jpg'
+        },
+        {
+          name: '海易鲜旗舰店',
+          price: '199',
+          pictureUrl: '//m.360buyimg.com/babel/s66x66_jfs/t1/179709/3/12102/211718/60dd8856E0e8b6b34/ca07667259929e95.jpg!q50.jpg'
+        },
+        {
+          name: '何氏蹦蹦鱼旗舰店',
+          price: '199',
+          pictureUrl: '//m.360buyimg.com/babel/s66x66_jfs/t1/195322/30/5856/460587/60b5c7a8E7d4415a5/dd6b7f65bcfb3025.jpg!q50.jpg'
+        },
+        {
+          name: '祥威五味子道地中药材养',
+          price: '199',
+          pictureUrl: '//m.360buyimg.com/babel/s180x180_jfs/t1/119866/20/1495/226548/5ebc8e7cE458b8026/a865aa78cdd824e1.jpg!q70.jpg'
+        },
+        {
+          name: '溪鲜花产业带 玫瑰花情人',
+          price: '199',
+          pictureUrl: '//m.360buyimg.com/babel/s180x180_jfs/t1/82574/39/10088/166075/5d788be1E59b39789/af30349781d00f0a.jpg!q70.jpg'
+        },
+        {
+          name: '商品名称名称',
+          price: '199',
+          pictureUrl: '//m.360buyimg.com/babel/s180x180_jfs/t1/195296/23/7502/581747/60c1e6b6Efc4f34b5/b9219c19185ebc9b.jpg!q70.jpg'
+        },
+        {
+          name: '半山农京东自营旗舰店',
+          price: '199',
+          pictureUrl: '//m.360buyimg.com/babel/s66x66_jfs/t1/191641/14/13305/169869/60efca80Eb40cfe34/bb1970d1b220be48.jpg!q50.jpg'
+        }
+      ]
+    };
+  },
+  methods: {
+    change() {
+      console.log('更新数据');
+      // this.dataArr = [{1:2}]
+    },
+    toDetail(item) {
+      console.log('跳转', item);
+    }
+  }
+};
+</script>

+ 67 - 0
src/packages/ninegrid/doc.md

@@ -0,0 +1,67 @@
+# NineGrid 九宫格
+ 
+ 
+
+## 基本用法
+
+```html
+    <nut-magic @click="click" :data="dataArr"></nut-magic>
+```
+
+```javascript
+export default {
+    data() {
+        return {
+            dataArr: [
+                {
+                    name:'商品名称名称',
+                    price:'199',
+                    pictureUrl:'//m.360buyimg.com/babel/s180x180_jfs/t1/174906/19/10256/188436/60a242afE89a800c9/801b64e5b80fde9a.jpg!q70.jpg'
+                },
+                {
+                    name:'商品名称名称',
+                    price:'199',
+                    pictureUrl:'//m.360buyimg.com/babel/s180x180_jfs/t1/133995/36/19954/205635/60ccc436E19f8b69b/8fc0a468ac037d2e.jpg!q70.jpg'
+                },
+                {
+                    name:'商品名称名称',
+                    price:'199',
+                    pictureUrl:'//m.360buyimg.com/babel/s180x180_jfs/t1/17279/28/13940/140479/60b984f4E723b9981/d007711aa1cdc358.jpg!q70.jpg'
+                },
+                ...('需要有16个数据‘)
+                
+          ]
+        };
+    },
+    methods: {
+        change() {
+            console.log('更新数据');
+        },
+        toDetail(item) {
+            console.log('跳转',item);
+        }
+    }
+}
+```
+
+
+
+## Prop
+
+| 字段 | 说明 | 类型 | 默认值
+|----- | ----- | ----- | ----- 
+| data | 传入数据,每次传入数据需有16个item,分为正反两面 | Array | []
+| data[].name | 名称 | String | ''
+| data[].pictureUrl | 商品图片 | String | ''
+| data[].price | 价格 | String | ''
+| data[].link | 跳转链接 | String | ''
+
+## 事件
+
+| 字段 | 说明 | 类型 | 返回值| 默认值
+|----- | ----- | ----- | -----  | ----- 
+| change | 魔方翻转两次触发事件(16个item展示完毕) | function | - | ----- 
+| toDetail | 点击商品跳转详情 | function | item | ----- 
+
+
+

+ 8 - 0
src/packages/ninegrid/index.js

@@ -0,0 +1,8 @@
+import NineGrid from './ninegrid.vue';
+import './ninegrid.scss';
+
+NineGrid.install = function(Vue) {
+  Vue.component(NineGrid.name, NineGrid);
+};
+
+export default NineGrid;

+ 132 - 0
src/packages/ninegrid/ninegrid.scss

@@ -0,0 +1,132 @@
+@import './animation.scss';
+.nut-ninegrid {
+  height: 357px;
+  width: 357px;
+  flex-shrink: 0;
+  background-image: url('~@/assets/img/bg.png');
+  background-size: 100% 100%;
+  overflow: auto;
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+  padding: 12px;
+  perspective: 1000;
+
+  .item {
+    position: relative;
+    width: 107px;
+    height: 107px;
+
+    .front,
+    .back {
+      position: absolute;
+      top: 0;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      border-radius: 8px;
+      padding: 9px 8px 0 8px;
+      overflow: hidden;
+      backface-visibility: hidden;
+      background-image: linear-gradient(#ffefd1, #ffc8a4);
+      transition: 1s;
+      transform-style: preserve-3d;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      > p {
+        width: 100%;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        font-family: PingFangSC-Medium;
+        text-align: center;
+        font-size: 13px;
+        height: 15px;
+        margin-bottom: 3px;
+        color: rgba(46, 45, 45, 1);
+        flex-shrink: 0;
+      }
+      .nut-price {
+        height: 40px;
+        > span {
+          font-family: PingFangSC-Medium;
+          font-size: 10px !important;
+          color: rgba(249, 18, 50, 1) !important;
+
+          &.price-big {
+            font-size: 16px !important;
+          }
+        }
+      }
+
+      > img {
+        width: 71px;
+        height: 71px;
+        flex-shrink: 0;
+      }
+    }
+
+    .front {
+      z-index: 2;
+      transform: rotateY(0deg);
+    }
+
+    .back {
+      transform: rotateY(-180deg);
+    }
+
+    &.d {
+      transform: rotate(3deg);
+    }
+
+    &.active {
+      .front {
+        transform: rotateY(180deg);
+      }
+
+      .back {
+        transform: rotateY(0deg);
+      }
+    }
+
+    &.shake {
+      animation: shake-rotate 0.5s 1;
+    }
+
+    .center {
+      border-radius: 8px;
+      overflow: hidden;
+      background-image: linear-gradient(#ff733e, #ff4e57);
+      border: 1px solid #ffa07c;
+      width: 100%;
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+
+      > img {
+        width: 15px;
+        height: 16px;
+
+        &.rotate {
+          animation-name: box-rotate;
+          animation-duration: 1s;
+        }
+      }
+
+      > span {
+        font-family: PingFangSC-Medium;
+        font-size: 16px;
+        color: rgba(255, 212, 167, 1);
+      }
+
+      > p {
+        font-family: PingFangSC-Medium;
+        font-size: 12px;
+        color: rgba(255, 212, 167, 1);
+      }
+    }
+  }
+}

+ 140 - 0
src/packages/ninegrid/ninegrid.vue

@@ -0,0 +1,140 @@
+<template>
+  <div class="nut-ninegrid">
+    <div
+      v-for="(item, index) in gridList"
+      class="item"
+      :class="[{ active: activeState && index != 4 }, { shake: shakeIndexs.includes(index + 1) }]"
+      :key="index"
+    >
+      <div v-if="index == 4" class="center" @click="refresh(true)">
+        <img :class="{ rotate: loadingDataState }" src="~@/assets/img/refresh.png" alt="refresh.png" />
+        <span>换一换</span>
+        <p>{{ countDown }}S&nbsp;后自动换</p>
+      </div>
+      <template v-else>
+        <div class="front" @click="toDetail(item.front)">
+          <p>{{ item.front.name }}</p>
+          <nut-price class="price" :price="item.front.price" :thousands="true" />
+          <img :src="item.front.pictureUrl" />
+        </div>
+        <div class="back" @click="toDetail(item.back)">
+          <p>{{ item.back.name }}</p>
+          <nut-price class="price" :price="item.back.price" :thousands="true" />
+          <img :src="item.back.pictureUrl" />
+        </div>
+      </template>
+    </div>
+  </div>
+</template>
+<script>
+export default {
+  name: 'nut-ninegrid',
+  props: {
+    data: {
+      type: [Object, Array],
+      default: () => {
+        return [];
+      }
+    }
+  },
+  data() {
+    return {
+      gridList: [], // 数据list
+      activeState: false, // 去掉中间的换一换方块
+      shakeIndexs: [], // 动画抖一抖的下标
+      loadingDataState: false, // 换一换的小图标是否旋转  并且等它旋转完才可以再次点击
+      countDown: 10, // 倒计时时间
+      number: 0
+    };
+  },
+  watch: {
+    data: {
+      handler(value) {
+        if (value) {
+          this.resResponse(value);
+        }
+      }
+    }
+  },
+  methods: {
+    resResponse(sudoku_goodsG) {
+      if (!(sudoku_goodsG.length > 15)) {
+        return;
+      }
+      let list = sudoku_goodsG;
+      let spArrs = this.spArray(8, list);
+      let front = spArrs[0];
+      let back = spArrs[1];
+      let filterData = [];
+      front.forEach((element, index) => {
+        filterData.push({
+          front: element,
+          back: back[index]
+        });
+      });
+      filterData.splice(4, 0, {});
+      this.gridList = filterData;
+    },
+    beginTimeIntervar() {
+      this.timeIntervar = setInterval(() => {
+        this.countDown--;
+        if (this.countDown == 0) {
+          this.refresh();
+        }
+        if (this.countDown == 7) {
+          this.shakeIndexs = [2, 4, 6, 8];
+        } else if (this.countDown == 3) {
+          this.shakeIndexs = [1, 3, 7, 9];
+        }
+      }, 1000);
+    },
+    refresh(manual = false) {
+      if (this.loadingDataState) return;
+      this.number += 1;
+      this.countDown = 10;
+      this.shakeIndexs = [];
+      this.activeState = !this.activeState;
+
+      // 检查点击次数
+      if (this.number % 2 === 0) {
+        console.log(123);
+        this.$emit('refresh');
+      } else {
+        this.loadingDataState = true;
+        setTimeout(() => {
+          this.loadingDataState = false;
+        }, 1000);
+      }
+    },
+    /**
+     * 跳转详情
+     * @param item skuItem
+     */
+    toDetail(item) {
+      // 跳转商详
+      this.$emit('toDetail', item);
+    },
+    /**
+     *
+     * 数组拆分函数
+     * @static
+     * @param {number} N 拆分长度
+     * @param {any[]} Q 将要拆分的数组
+     * @returns {Array}
+     * @memberof Utils
+     */
+    spArray(N, Q) {
+      let R = [],
+        F;
+      for (F = 0; F < Q.length; ) {
+        R.push(Q.slice(F, (F += N)));
+      }
+      return R;
+    }
+  },
+  mounted() {
+    this.resResponse(this.data);
+    this.beginTimeIntervar();
+  }
+};
+</script>

+ 1 - 0
types/nutui.d.ts

@@ -90,3 +90,4 @@ export declare class NumberKeyboard extends UIComponent {}
 export declare class CollapseItem extends UIComponent {}
 export declare class Coupon extends UIComponent {}
 export declare class Magic extends UIComponent {}
+export declare class NineGrid extends UIComponent {}