浏览代码

新增 NoticeBar、CountDown (#599)

* feat: 新增 NoticeBar

* feat: 新增 CountDown
yangxiaolu1993 4 年之前
父节点
当前提交
8685104fa4

+ 15 - 3
src/config.json

@@ -542,9 +542,10 @@
           "type": "component",
           "cName": "公告栏",
           "desc": "用于循环播放展示一组消息通知",
-          "sort": 5,
-          "show": false,
-          "author": "wangyue92"
+          "sort": 8,
+          "show": true,
+          "taro": false,
+          "author": "yangxiaolu"
         }
       ]
     },
@@ -716,6 +717,17 @@
           "show": true,
           "desc": "数字滚动组件,动态变化展示",
           "author": "Ymm0008"
+        },
+        {
+          "version": "3.0.0",
+          "name": "CountDown",
+          "type": "component",
+          "cName": "倒计时",
+          "desc": "倒计时",
+          "sort": 16,
+          "show": true,
+          "taro": false,
+          "author": "yangxiaolu"
         }
       ]
     },

+ 154 - 0
src/packages/__VUE/countdown/demo.vue

@@ -0,0 +1,154 @@
+<template>
+  <div class="demo">
+    <h2>基础用法</h2>
+    <nut-cell>
+      <nut-countdown :endTime="end" @on-end="onend"></nut-countdown>
+    </nut-cell>
+    <h2>显示天</h2>
+
+    <nut-cell>
+      <nut-countdown :endTime="end" showDays />
+    </nut-cell>
+
+    <h2>以服务端的时间为准</h2>
+
+    <nut-cell>
+      <nut-countdown :startTime="serverTime" :endTime="end" />
+    </nut-cell>
+
+    <h2>显示为 天时分秒</h2>
+
+    <nut-cell>
+      <nut-countdown showDays showPlainText :endTime="end" />
+    </nut-cell>
+
+    <h2>异步更新结束时间</h2>
+
+    <nut-cell>
+      <nut-countdown showPlainText :endTime="asyncEnd" />
+    </nut-cell>
+
+    <h2>控制开始和暂停的倒计时</h2>
+
+    <nut-cell>
+      <nut-countdown
+        :endTime="end"
+        :paused="paused"
+        @on-paused="onpaused"
+        @on-restart="onrestart"
+      />
+      <div style="position: absolute; right: 10px; top: 9px">
+        <nut-button type="primary" size="small" @click="toggle">{{
+          paused ? 'start' : 'stop'
+        }}</nut-button>
+      </div>
+    </nut-cell>
+
+    <h2>自定义展示</h2>
+
+    <nut-cell>
+      <span>
+        <nut-countdown v-model="resetTime" :endTime="end">
+          <div class="countdown-part-box">
+            <div class="part-item-symbol">{{ resetTime.d }}天</div>
+            <div class="part-item h">{{ resetTime.h }}</div>
+            <span class="part-item-symbol">:</span>
+            <div class="part-item m">{{ resetTime.m }}</div>
+            <span class="part-item-symbol">:</span>
+            <div class="part-item s">{{ resetTime.s }}</div>
+          </div>
+        </nut-countdown>
+      </span>
+    </nut-cell>
+
+    <h2>自定义显示</h2>
+
+    <nut-cell>
+      <span>可调用该组件提供的 restTime 方法获取 '天时分秒' 自定义显示</span>
+    </nut-cell>
+  </div>
+</template>
+
+<script lang="ts">
+import {
+  toRefs,
+  onMounted,
+  onUnmounted,
+  reactive,
+  computed,
+  CSSProperties,
+  onActivated,
+  onDeactivated,
+  ref,
+  watch,
+  h
+} from 'vue';
+import { createComponent } from '../../utils/create';
+const { createDemo } = createComponent('countdown');
+export default createDemo({
+  props: {},
+  setup() {
+    const state = reactive({
+      serverTime: Date.now() - 30 * 1000,
+      end: Date.now() + 50 * 1000,
+      asyncEnd: 0,
+      paused: false,
+      resetTime: {
+        d: '1',
+        h: '00',
+        m: '00',
+        s: '00'
+      }
+    });
+
+    const toggle = () => {
+      state.paused = !state.paused;
+    };
+    const onend = () => {
+      console.log('countdown: ended.');
+    };
+    const onpaused = (v) => {
+      console.log('paused: ', v);
+    };
+    const onrestart = (v) => {
+      console.log('restart: ', v);
+    };
+
+    setTimeout(() => {
+      state.asyncEnd = Date.now() + 30 * 1000;
+    }, 3000);
+
+    return {
+      ...toRefs(state),
+      toggle,
+      onend,
+      onpaused,
+      onrestart
+    };
+  }
+});
+</script>
+
+<style lang="scss" scoped>
+.countdown-part-box {
+  display: flex;
+  align-items: center;
+
+  .part-item {
+    flex-shrink: 0;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 20px;
+    height: 25px;
+    background: #e8220e;
+    color: #fff;
+    font-size: 14px;
+    border-radius: 6px;
+  }
+
+  .part-item-symbol {
+    margin: 0 5px;
+  }
+}
+</style>

+ 175 - 0
src/packages/__VUE/countdown/doc.md

@@ -0,0 +1,175 @@
+#  countdown组件
+
+### 介绍
+
+### 安装
+
+
+``` javascript
+import { createApp } from 'vue';
+// vue
+import { CountDown } from '@nutui/nutui';
+
+const app = createApp();
+app.use(CountDown);
+```
+
+### 基础用法
+
+```html
+<nut-countdown :end-time="end" ></nut-countdown>
+```
+
+``` javascript
+setup() {
+  const state = reactive({
+      end: Date.now() + 50 * 1000,
+    });
+  return {
+    ...toRefs(state)
+  };
+}
+```
+
+### 显示天
+
+```html
+<nut-countdown :end-time="end" show-days ></nut-countdown>
+```
+
+### 以服务端的时间为准
+
+```html
+<nut-countdown  :start-time="serverTime" :end-time="end" show-days ></nut-countdown>
+```
+
+``` javascript
+setup() {
+  const state = reactive({
+      serverTime: Date.now() - 30 * 1000,
+      end: Date.now() + 50 * 1000,
+    });
+  return {
+    ...toRefs(state)
+  };
+}
+```
+
+### 显示为 天时分秒
+
+```html
+<nut-countdown show-days show-plain-text  :end-time="end"></nut-countdown>
+```
+``` javascript
+setup() {
+  const state = reactive({
+      end: Date.now() + 50 * 1000,
+    });
+  return {
+    ...toRefs(state)
+  };
+}
+```
+
+### 异步更新结束时间
+
+```html
+<nut-countdown  show-days show-plain-text  :end-time="asyncEnd" ></nut-countdown>
+```
+``` javascript
+setup() {
+  const state = reactive({
+      asyncEnd: 0,
+    });
+  return {
+    ...toRefs(state)
+  };
+}
+```
+
+### 控制开始和暂停的倒计时
+
+```html
+<nut-cell>
+    <nut-countdown  :endTime="end" :paused="paused" @on-paused="onpaused" @on-restart="onrestart" />
+    <div style="position:absolute;right:10px;top:9px">
+        <nut-button type="primary" size='small' @click="toggle">{{ paused ? 'start' : 'stop' }}</nut-button>
+    </div>
+</nut-cell>
+```
+``` javascript
+setup() {
+  const state = reactive({
+      paused: false,
+      end: Date.now() + 50 * 1000,
+    });
+
+    const toggle = ()=> {
+      state.paused = !state.paused;
+    }
+    const onpaused = (v)=> {
+      console.log('paused: ', v);
+    }
+    const onrestart = (v)=> {
+      console.log('restart: ', v);
+    }
+  return {
+      toggle,
+      onpaused,
+      onrestart,
+    ...toRefs(state)
+  };
+}
+```
+### 自定义展示
+
+```html
+<nut-countdown v-model="resetTime" :endTime="end">
+    <div class="countdown-part-box">
+        <div class="part-item-symbol"> resetTime.d 天</div>
+        <div class="part-item h"> resetTime.h </div>
+        <span class="part-item-symbol">:</span>
+        <div class="part-item m"> resetTime.m </div>
+        <span class="part-item-symbol">:</span>
+        <div class="part-item s"> resetTime.s </div>
+    </div>
+</nut-countdown>
+```
+``` javascript
+setup() {
+  const state = reactive({
+      end: Date.now() + 50 * 1000,
+      resetTime: {
+        d: '1',
+        h: '00',
+        m: '00',
+        s: '00'
+      }
+    });
+  return {
+    ...toRefs(state)
+  };
+}
+```
+
+### API
+
+### Props
+
+| 字段 | 说明 | 类型 | 默认值
+| ----- | ----- | ----- | -----
+| v-model | 当前时间,自定义展示内容时生效 | Object | {}
+| start-time | 开始时间 | String, Number | Date.now()
+| end-time | 结束时间 | String, Number | Date.now()
+| show-days | 是否显示天 | Boolean | false
+| show-plain-text | 显示为纯文本 | Boolean | false
+| paused | 是否暂停 | Boolean | false
+
+
+### Event
+
+| 字段 | 说明 | 回调参数
+| ----- | ----- | ----- 
+| on-end | 倒计时结束时 | 剩余时间戳
+| on-paused | 暂停时 | 剩余时间戳
+| on-restart | 暂停时 | 剩余时间戳

+ 2 - 0
src/packages/__VUE/countdown/index.scss

@@ -0,0 +1,2 @@
+.nut-countdown {
+}

+ 0 - 0
src/packages/__VUE/countdown/index.taro.vue


+ 243 - 0
src/packages/__VUE/countdown/index.vue

@@ -0,0 +1,243 @@
+<template>
+  <view :class="classes" @click="handleClick">
+    <template v-if="slots.default">
+      <slot></slot>
+    </template>
+    <template v-else-if="showPlainText">
+      <view class="nut-cd-block">{{ plainText }}</view>
+    </template>
+    <template v-else>
+      <template v-if="resttime.d >= 0 && showDays">
+        <view class="nut-cd-block">{{ resttime.d }}</view>
+        <view class="nut-cd-dot">天</view>
+      </template>
+      <view class="nut-cd-block">{{ resttime.h }}</view
+      ><view class="nut-cd-dot">:</view
+      ><view class="nut-cd-block">{{ resttime.m }}</view
+      ><view class="nut-cd-dot">:</view
+      ><view class="nut-cd-block">{{ resttime.s }}</view>
+    </template>
+  </view>
+</template>
+<script lang="ts">
+import {
+  toRefs,
+  onMounted,
+  onUnmounted,
+  reactive,
+  computed,
+  CSSProperties,
+  onActivated,
+  onDeactivated,
+  ref,
+  watch,
+  vModelText
+} from 'vue';
+import { createComponent } from '../../utils/create';
+const { componentName, create } = createComponent('countdown');
+
+export default create({
+  props: {
+    modelValue: {
+      type: Object,
+      default: () => {
+        return {};
+      }
+    },
+    paused: {
+      default: false,
+      type: Boolean
+    },
+    showDays: {
+      default: false,
+      type: Boolean
+    },
+    showPlainText: {
+      default: false,
+      type: Boolean
+    },
+    startTime: {
+      // 可以是服务器当前时间
+      type: [Number, String],
+      validator(v) {
+        const dateStr = new Date(v).toString().toLowerCase();
+        return dateStr !== 'invalid date';
+      }
+    },
+    endTime: {
+      type: [Number, String],
+      validator(v) {
+        const dateStr = new Date(v).toString().toLowerCase();
+        return dateStr !== 'invalid date';
+      }
+    }
+  },
+  components: {},
+  emits: ['input', 'on-end', 'on-restart', 'on-paused'],
+
+  setup(props, { emit, slots }) {
+    console.log('componentName', componentName);
+
+    const state = reactive({
+      restTime: 0,
+      p: 0,
+      _curr: 0,
+      timer: null
+    });
+
+    const resttime = computed(() => {
+      const rest = restTime(state.restTime);
+      const { d, h, m, s } = rest;
+      if (!props.showDays && d > 0) {
+        rest.h = fill2(Number(rest.h) + d * 24);
+        rest.d = 0;
+      }
+      return rest;
+    });
+
+    const plainText = computed(() => {
+      const { d, h, m, s } = resttime.value;
+
+      return `${d > 0 && props.showDays ? d + '天' + h : h}小时${m}分${s}秒`;
+    });
+
+    watch(
+      () => props.value,
+      (value) => {}
+    );
+
+    watch(
+      () => state.restTime,
+      (value) => {
+        let tranTime = restTime(value);
+        emit('update:modelValue', tranTime);
+        emit('input', tranTime);
+      }
+    );
+
+    watch(
+      () => props.paused,
+      (v, ov) => {
+        if (!ov) {
+          state._curr = getTimeStamp();
+          emit('on-paused', state.restTime);
+        } else {
+          state.p += getTimeStamp() - state._curr;
+          emit('on-restart', state.restTime);
+        }
+      }
+    );
+
+    watch(
+      () => props.endTime,
+      (value) => {
+        initTimer();
+      }
+    );
+
+    watch(
+      () => props.startTime,
+      (value) => {
+        initTimer();
+      }
+    );
+
+    const classes = computed(() => {
+      const prefixCls = componentName;
+      return {
+        [prefixCls]: true
+      };
+    });
+
+    const getTimeStamp = (timeStr?: string | number) => {
+      if (!timeStr) return Date.now();
+      let t = timeStr;
+      t = t > 0 ? +t : t.toString().replace(/\-/g, '/');
+      return new Date(t).getTime();
+    };
+
+    const initTimer = () => {
+      const delay = 1000;
+      const curr = Date.now();
+      const start = getTimeStamp(props.startTime || curr);
+      const end = getTimeStamp(props.endTime || curr);
+      const diffTime = curr - start;
+
+      state.restTime = end - (start + diffTime);
+      (state.timer as any) = setInterval(() => {
+        if (!props.paused) {
+          let restTime = end - (Date.now() - state.p + diffTime);
+          state.restTime = restTime;
+          if (restTime < delay) {
+            state.restTime = 0;
+            emit('on-end');
+            clearInterval(state.timer as any);
+          }
+        } else {
+          // 暂停
+        }
+      }, delay);
+    };
+
+    const fill2 = (v: any) => {
+      v += '';
+      while (v.length < 2) {
+        v = '0' + v;
+      }
+      return v;
+    };
+    const restTime = (t: any) => {
+      const ts = t;
+      let rest = {
+        d: '-',
+        h: '--',
+        m: '--',
+        s: '--'
+      };
+      if (ts === 0) {
+        rest = {
+          d: '0',
+          h: '00',
+          m: '00',
+          s: '00'
+        };
+      }
+      if (ts) {
+        const ds = 24 * 60 * 60 * 1000;
+        const hs = 60 * 60 * 1000;
+        const ms = 60 * 1000;
+
+        const d = ts >= ds ? parseInt(ts / ds) : 0;
+        const h = ts - d * ds >= hs ? parseInt((ts - d * ds) / hs) : 0;
+        const m =
+          ts - d * ds - h * hs >= ms
+            ? parseInt((ts - d * ds - h * hs) / ms)
+            : 0;
+        const s = Math.round((ts - d * ds - h * hs - m * ms) / 1000);
+
+        if (d >= 0) rest.d = d + '';
+        if (h >= 0) rest.h = fill2(h);
+        if (m >= 0) rest.m = fill2(m);
+        if (s >= 0) rest.s = fill2(s);
+      }
+      return rest;
+    };
+
+    initTimer();
+
+    return {
+      ...toRefs(props),
+      slots,
+      classes,
+      getTimeStamp,
+      initTimer,
+      resttime,
+      plainText
+    };
+  }
+});
+</script>
+
+<style lang="scss">
+@import 'index.scss';
+</style>

+ 112 - 0
src/packages/__VUE/noticebar/demo.vue

@@ -0,0 +1,112 @@
+<template>
+  <div class="demo">
+    <h2>默认用法</h2>
+    <nut-noticebar :text="text"></nut-noticebar>
+
+    <h2>禁用滚动</h2>
+    <nut-noticebar
+      text="华为畅享9新品即将上市,活动期间0元预约可参与抽奖,赢HUAWEI WATCH等好礼,更多产品信息请持续关注!"
+      :scrollable="false"
+    ></nut-noticebar>
+
+    <h2>通告栏模式--关闭模式</h2>
+    <nut-noticebar :closeMode="true" @click="hello"
+      >华为畅享9新品即将上市,活动期间0元预约可参与抽奖,赢HUAWEI
+      WATCH等好礼,更多产品信息请持续关注!
+    </nut-noticebar>
+    <h2>通告栏模式--链接模式</h2>
+    <nut-noticebar
+      left-icon="https://img13.360buyimg.com/imagetools/jfs/t1/72082/2/3006/1197/5d130c8dE1c71bcd6/e48a3b60804c9775.png"
+    >
+      <a href="https://www.jd.com">京东商城</a>
+    </nut-noticebar>
+
+    <h2>纵向滚动</h2>
+    <div class="interstroll-list">
+      <nut-noticebar
+        direction="vertical"
+        :list="horseLamp1"
+        :speed="10"
+        :standTime="1000"
+        @click="go"
+        :closeMode="true"
+      ></nut-noticebar>
+    </div>
+
+    <h2>纵向复杂滚动动画</h2>
+    <nut-noticebar
+      direction="vertical"
+      :list="horseLamp2"
+      :speed="10"
+      :standTime="2000"
+      :complexAm="true"
+    ></nut-noticebar>
+
+    <h2>纵向自定义滚动内容</h2>
+    <nut-noticebar
+      direction="vertical"
+      :height="50"
+      :speed="10"
+      :standTime="1000"
+      :list="[]"
+      @close="go"
+    >
+      <div
+        class="custom-item"
+        :data-index="index"
+        v-for="(item, index) in horseLamp3"
+        style="height: 50px; line-height: 50px"
+        :key="index"
+        >{{ item }}</div
+      >
+    </nut-noticebar>
+
+    <h2>纵向自定义右侧图标</h2>
+    <nut-noticebar
+      direction="vertical"
+      :list="horseLamp1"
+      :speed="10"
+      :standTime="1000"
+    >
+      <template v-slot:rightIcon>
+        <nut-icon type="trolley" color="#f0250f"> </nut-icon>
+      </template>
+    </nut-noticebar>
+  </div>
+</template>
+
+<script lang="ts">
+import { onMounted, reactive, toRefs } from 'vue';
+import { createComponent } from '../../utils/create';
+const { createDemo } = createComponent('noticebar');
+export default createDemo({
+  props: {},
+  setup() {
+    const state = reactive({
+      horseLamp1: ['惊喜红包免费领', '爆款准点秒', '买超值优惠', '赢百万京豆'],
+      horseLamp2: ['惊喜红包免费领', '爆款准点秒', '买超值优惠', '赢百万京豆'],
+      horseLamp3: ['惊喜红包免费领', '爆款准点秒', '买超值优惠', '赢百万京豆'],
+      text: '华为畅享9新品即将上市,活动期间0元预约可参与抽奖,赢HUAWEI WATCH等好礼,更多产品信息请持续关注!'
+    });
+
+    const hello = () => {
+      console.log('hello world');
+    };
+    const go = (item: any) => {
+      console.log(item);
+    };
+
+    return {
+      ...toRefs(state),
+      hello,
+      go
+    };
+  }
+});
+</script>
+
+<style lang="scss" scoped>
+.demo {
+  padding-bottom: 30px !important;
+}
+</style>

+ 159 - 0
src/packages/__VUE/noticebar/doc.md

@@ -0,0 +1,159 @@
+# NoticeBar 通告栏
+
+### 介绍 
+
+
+用于循环播放展示一组消息通知。
+
+
+### 安装
+
+```javascript
+
+import { createApp } from 'vue';
+// vue
+import { NoticeBar } from '@nutui/nutui';
+
+const app = createApp();
+app.use(NoticeBar);
+
+```
+
+### 代码示例
+
+### 基本用法
+
+```html
+    <nut-noticebar
+      text="华为畅享9新品即将上市,活动期间0元预约可参与抽奖,赢HUAWEI WATCH等好礼,更多产品信息请持续关注!"
+    ></nut-noticebar>
+```
+### 禁用滚动
+文字内容多于一行时,可通过scrollable参数控制是否开启滚动
+```html
+    <nut-noticebar
+      text="华为畅享9新品即将上市,活动期间0元预约可参与抽奖,赢HUAWEI WATCH等好礼,更多产品信息请持续关注!"
+      :scrollable="false"
+    ></nut-noticebar>
+```
+### 通告栏模式--关闭模式
+```html
+    <nut-noticebar
+      :closeMode="true"
+      @click="hello"
+    >华为畅享9新品即将上市,活动期间0元预约可参与抽奖,赢HUAWEI WATCH等好礼,更多产品信息请持续关注!
+    </nut-noticebar>
+```
+### 通告栏模式--链接模式
+```html
+    <nut-noticebar
+      left-icon="https://img13.360buyimg.com/imagetools/jfs/t1/72082/2/3006/1197/5d130c8dE1c71bcd6/e48a3b60804c9775.png"
+    >
+      <a href="https://www.jd.com">京东商城</a>
+    </nut-noticebar>
+```
+
+### 纵向滚动
+
+```html
+<nut-noticebar direction='vertical' :list="horseLamp1" :speed='10' :standTime='1000'  @click='go' :closeMode="true"></nut-noticebar>
+```
+```javascript
+data() {
+    return {
+        horseLamp1: ['惊喜红包免费领', '爆款准点秒', '买超值优惠', '赢百万京豆'],
+    };
+},
+
+methods:{
+    go(item){
+        console.log(item)
+    }
+}
+```
+
+
+### 复杂滚动动画
+
+```html
+<nut-noticebar direction='vertical' :list="horseLamp2" :speed='10' :standTime='2000' :complexAm='true'></nut-noticebar>
+```
+```javascript
+data() {
+    return {
+        horseLamp2: ['惊喜红包免费领', '爆款准点秒', '买超值优惠', '赢百万京豆'],
+    };
+}
+```
+
+### 自定义滚动内容
+
+```html
+<nut-noticebar direction='vertical' :height='50' :speed='10' :standTime='1000' :list="[]"  @close='go'>
+  <div class="custom-item" :data-index='index' v-for="(item,index) in horseLamp3" :key="index">{{item}}</div>
+</nut-noticebar>
+```
+```javascript
+data() {
+    return {
+        horseLamp3: ['惊喜红包免费领', '爆款准点秒', '买超值优惠', '赢百万京豆'],
+    };
+}
+```
+
+
+### 纵向自定义右侧图标
+
+```html
+<nut-noticebar direction='vertical' :list="horseLamp1" :speed='10' :standTime='1000' >
+  <template v-slot:rightIcon>
+    <nut-icon 
+      type="trolley" 
+      color="#f0250f"
+    >
+    </nut-icon>
+
+  </template>
+</nut-noticebar>
+```
+```javascript
+data() {
+    return {
+        horseLamp1: ['惊喜红包免费领', '爆款准点秒', '买超值优惠', '赢百万京豆'],
+    };
+}
+```
+
+
+
+### Prop
+
+| 字段       | 说明                                                       | 类型          | 默认值 |
+| ---------- | ---------------------------------------------------------- | ------------- | ------ |
+| direction       | 滚动的方向,可选 across、vertical                         | String        | across     |
+| text       | 提示的信息                                                 | String        | 空     |
+| closeMode  | 是否启用关闭模式                                           | Boolean       | false  |
+| leftIcon   | close为没有左边icon,其他为自定义的图片链接,没有为默认图片 | String        | 空     |
+| color      | 导航栏的文字颜色                                           | String        | 空     |
+| background | 导航栏的背景颜色                                           | String        | 空     |
+| delay      | 延时多少秒                                                 | String/Number | 1      |
+| scrollable | 是否可以滚动                                               | Boolean       | true   |
+| speed      | 滚动速率 (px/s)                                            | Number        | 50     |
+
+### Prop(direction=vertical)
+
+| 参数         | 说明                             | 类型   | 默认值           |
+|--------------|----------------------------------|--------|------------------|
+| list         | 纵向滚动数据列表               | Array | []               |
+| speed        | 滚动的速度                         | Number | 50               |
+| standTime         | 停留时间(毫秒) | Number | 1000                |
+| complexAm | 稍复杂的动画,耗能会高     | Boolean | false |
+| height          | 每一个滚动列的高度(px),注意:在使用 slot 插槽定义滚动单元时,按照实际高度修改此值                 | Number | 40              |
+| closeMode  | 是否启用右侧关闭图标,可以通过slot[name=rightIcon]自定义图标                                   | Boolean       | false  |
+
+### Event
+
+| 字段  | 说明             | 回调参数     |
+| ----- | ---------------- | ------------ |
+| click | 外层点击事件回调 | event: Event |
+| close | 关闭通知栏时触发 | event: Event |

文件差异内容过多而无法显示
+ 119 - 0
src/packages/__VUE/noticebar/index.scss


+ 0 - 0
src/packages/__VUE/noticebar/index.taro.vue


+ 410 - 0
src/packages/__VUE/noticebar/index.vue

@@ -0,0 +1,410 @@
+<template>
+  <view :class="classes">
+    <view
+      v-show="showNoticeBar"
+      class="nut-noticebar-page"
+      :class="{ withicon: closeMode, close: closeMode, wrapable: wrapable }"
+      :style="barStyle"
+      @click="handleClick"
+      v-if="direction == 'across'"
+    >
+      <view
+        class="left-icon"
+        v-if="iconShow"
+        :style="{ 'background-image': `url(${iconBg})` }"
+      >
+        <nut-icon
+          name="notice"
+          size="16"
+          :color="color"
+          v-if="!iconBg"
+        ></nut-icon>
+      </view>
+      <view ref="wrap" class="wrap">
+        <view
+          ref="content"
+          class="content"
+          :class="[
+            animationClass,
+            { 'nut-ellipsis': !scrollable && !wrapable }
+          ]"
+          :style="contentStyle"
+          @animationend="onAnimationEnd"
+          @webkitAnimationEnd="onAnimationEnd"
+        >
+          <slot>{{ text }}</slot>
+        </view>
+      </view>
+      <view v-if="closeMode" class="right-icon" @click.stop="onClickIcon">
+        <nut-icon name="close" size="11" :color="color"></nut-icon>
+      </view>
+    </view>
+
+    <view
+      class="nut-noticebar-vertical"
+      v-if="scrollList.length > 0 && direction == 'vertical'"
+      :style="barStyle"
+    >
+      <template v-if="slots.default">
+        <view class="horseLamp_list" :style="horseLampStyle">
+          <ScrollItem
+            v-for="(item, index) in scrollList"
+            v-bind:key="index"
+            :style="{ height: height + 'px', 'line-height': height + 'px' }"
+            :item="item"
+          ></ScrollItem>
+        </view>
+      </template>
+
+      <template v-else>
+        <ul class="horseLamp_list" :style="horseLampStyle">
+          <li
+            class="horseLamp_list_item"
+            v-for="(item, index) in scrollList"
+            :key="index"
+            :style="{ height: height }"
+            @click="go(item)"
+          >
+            {{ item }}
+          </li>
+        </ul>
+      </template>
+
+      <view class="go" @click="!slots.rightIcon && handleClickIcon()">
+        <template v-if="slots.rightIcon">
+          <slot name="rightIcon"></slot>
+        </template>
+        <template v-else-if="closeMode">
+          <nut-icon type="cross" :color="color" size="11px"></nut-icon>
+        </template>
+      </view>
+    </view>
+  </view>
+</template>
+<script lang="ts">
+import {
+  toRefs,
+  onMounted,
+  onUnmounted,
+  reactive,
+  computed,
+  CSSProperties,
+  onActivated,
+  onDeactivated,
+  ref,
+  watch,
+  h
+} from 'vue';
+import { createComponent } from '../../utils/create';
+const { componentName, create } = createComponent('noticebar');
+
+export default create({
+  props: {
+    // 滚动方向  across 横向 vertical 纵向
+    direction: {
+      type: String,
+      default: 'across'
+    },
+    list: {
+      type: Array,
+      default: () => {
+        return [];
+      }
+    },
+    standTime: {
+      type: Number,
+      default: 1000
+    },
+    complexAm: {
+      type: Boolean,
+      default: false
+    },
+    height: {
+      type: Number,
+      default: 40
+    },
+    text: {
+      type: String,
+      default: ''
+    },
+    closeMode: {
+      type: Boolean,
+      default: false
+    },
+    wrapable: {
+      type: Boolean,
+      default: false
+    },
+    leftIcon: { type: String, default: '' },
+    color: {
+      type: String,
+      default: '#F9911B'
+    },
+    background: {
+      type: String,
+      default: 'rgba(254,250,216,1)'
+    },
+    delay: {
+      type: [String, Number],
+      default: 1
+    },
+    scrollable: {
+      type: Boolean,
+      default: true
+    },
+    speed: {
+      type: Number,
+      default: 50
+    }
+  },
+  components: {
+    ScrollItem: function (props) {
+      props.item.props.style = props.style;
+      return h(props.item);
+    }
+  },
+  emits: ['click', 'close'],
+
+  setup(props, { emit, slots }) {
+    console.log('componentName', componentName);
+
+    const wrap = ref<null | HTMLElement>(null);
+    const content = ref<null | HTMLElement>(null);
+
+    const state = reactive({
+      wrapWidth: 0,
+      firstRound: true,
+      duration: 0,
+      offsetWidth: 0,
+      showNoticeBar: true,
+      animationClass: '',
+
+      animate: false,
+      scrollList: [],
+      distance: 0,
+      timer: null,
+      keepAlive: false
+    });
+
+    const classes = computed(() => {
+      const prefixCls = componentName;
+      return {
+        [prefixCls]: true
+      };
+    });
+
+    const iconShow = computed(() => {
+      if (props.leftIcon == 'close') {
+        return false;
+      } else {
+        return true;
+      }
+    });
+
+    const barStyle = computed(() => {
+      let style: {
+        [props: string]: any;
+      } = {
+        color: props.color,
+        background: props.background
+      };
+
+      if (props.direction == 'vertical') {
+        style.height = `${props.height}px`;
+      }
+      return style;
+    });
+
+    const contentStyle = computed(() => {
+      return {
+        paddingLeft: state.firstRound ? 0 : state.wrapWidth + 'px',
+        animationDelay: (state.firstRound ? props.delay : 0) + 's',
+        animationDuration: state.duration + 's'
+      };
+    });
+    const iconBg = computed(() => {
+      let iconBg = null;
+      if (props.leftIcon) {
+        iconBg = props.leftIcon;
+      }
+      return iconBg;
+    });
+    const horseLampStyle = computed(() => {
+      let styles = {};
+      if (props.complexAm) {
+        styles = {
+          transform: `translateY(${state.distance}px)`
+        };
+      } else {
+        if (state.animate) {
+          styles = {
+            transition: `all ${~~(props.height / props.speed / 4)}s`,
+            'margin-top': `-${props.height}px`
+          };
+        }
+      }
+      return styles;
+    });
+
+    watch(
+      () => props.text,
+      (value) => {
+        initScrollWrap(value);
+      }
+    );
+
+    watch(
+      () => props.list,
+      (value) => {
+        state.scrollList = [].concat(value as any);
+      }
+    );
+
+    const initScrollWrap = (value: string) => {
+      if (state.showNoticeBar == false) {
+        return;
+      }
+      setTimeout(() => {
+        if (!wrap.value || !content.value) {
+          return;
+        }
+        const wrapWidth = wrap.value.getBoundingClientRect().width;
+
+        const offsetWidth = content.value.getBoundingClientRect().width;
+
+        if (props.scrollable && offsetWidth > wrapWidth) {
+          state.wrapWidth = wrapWidth;
+          state.offsetWidth = offsetWidth;
+
+          state.duration = offsetWidth / props.speed;
+          state.animationClass = 'play';
+        } else {
+          state.animationClass = '';
+        }
+      });
+    };
+    const handleClick = (event: Event) => {
+      emit('click', event);
+    };
+
+    const onClickIcon = (event: Event) => {
+      state.showNoticeBar = !props.closeMode;
+      emit('close', event);
+    };
+
+    const onAnimationEnd = () => {
+      state.firstRound = false;
+
+      setTimeout(() => {
+        state.duration = (state.offsetWidth + state.wrapWidth) / props.speed;
+        state.animationClass = 'play-infinite';
+      }, 0);
+    };
+
+    /**
+     * 利益点滚动方式一
+     */
+    const startRollEasy = () => {
+      showhorseLamp();
+      (state.timer as any) = setInterval(
+        showhorseLamp,
+        ~~(props.height / props.speed / 4) * 1000 + props.standTime
+      );
+    };
+    const showhorseLamp = () => {
+      state.animate = true;
+      setTimeout(() => {
+        state.scrollList.push(state.scrollList[0]);
+        state.scrollList.shift();
+        state.animate = false;
+      }, ~~(props.height / props.speed / 4) * 1000);
+    };
+
+    const startRoll = () => {
+      (state.timer as any) = setInterval(() => {
+        let chunk = 100;
+        for (let i = 0; i < chunk; i++) {
+          scroll(i, i < chunk - 1 ? false : true);
+        }
+      }, props.standTime + 100 * props.speed);
+    };
+    const scroll = (n: number, last: boolean) => {
+      setTimeout(() => {
+        state.distance -= props.height / 100;
+        if (last) {
+          state.scrollList.push(state.scrollList[0]);
+          state.scrollList.shift();
+          state.distance = 0;
+        }
+      }, n * props.speed);
+    };
+    /**
+     * 点击滚动单元
+     */
+    const go = (item: any) => {
+      emit('click', item);
+    };
+
+    const handleClickIcon = () => {
+      emit('close', state.scrollList[0]);
+    };
+
+    onMounted(() => {
+      console.log(props.direction);
+      if (props.direction == 'vertical') {
+        if (slots.default) {
+          state.scrollList = [].concat(slots.default()[0].children as any);
+        } else {
+          state.scrollList = [].concat(props.list as any);
+        }
+
+        console.log(state.scrollList);
+
+        setTimeout(() => {
+          props.complexAm ? startRoll() : startRollEasy();
+        }, props.standTime);
+      } else {
+        initScrollWrap(props.text);
+      }
+    });
+
+    onActivated(() => {
+      if (state.keepAlive) {
+        state.keepAlive = false;
+      }
+    });
+
+    onDeactivated(() => {
+      state.keepAlive = true;
+      clearInterval(state.timer as any);
+    });
+
+    onUnmounted(() => {
+      clearInterval(state.timer as any);
+    });
+
+    return {
+      ...toRefs(props),
+      ...toRefs(state),
+      classes,
+      iconShow,
+      barStyle,
+      contentStyle,
+      iconBg,
+      horseLampStyle,
+      wrap,
+      content,
+      handleClick,
+      onClickIcon,
+      onAnimationEnd,
+      go,
+      handleClickIcon,
+      slots
+    };
+  }
+});
+</script>
+
+<style lang="scss">
+@import 'index.scss';
+</style>