npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@boyzcf/vue3-time-line

v0.0.1

Published

一个基于Vue3、Vite的时间轴组件,一般用于监控视频的回放

Downloads

9

Readme

time-line

一个基于Vue3、Vite的时间轴组件,一般用于监控视频的回放

安装

npm install @boyzcf/vue3-time-line
or
pnpm install @boyzcf/vue3-time-line
or
yarn add @boyzcf/vue3-time-line

全局引入

// main.js
import { createApp } from 'vue'
import App from './App.vue'

import TimeLine from 'time-line'

const app = createApp(App)

app.use(TimeLine, { comName: 'TimeLine' })
app.mount('#app')

局部引入

// demo.vue

import { TimeLineCom } from 'time-line'

基础用法

<TimeLine @timeChange="timeChange"></TimeLine>

time2: '2021-01-15 00:00:00',
const timeChange = (time) => {
  defaultData.time = t
}

显示时间段

  <template>
    <div class="container">
      <div class="timeShow">当前时间:{{ showTime2 }}</div>
      <div class="timeLine">
        <TimeLine
          ref="Timeline2"
          :initTime="defaultData.time2"
          @timeChange="timeChange2"
          :timeSegments="defaultData.timeSegments"
          @click_timeSegments="click_timeSegments"
          @click_timeline="onClickTimeLine"
          @dragTimeChange="onDragTimeChange"
        ></TimeLine>
      </div>
    </div>
  </template>

  <script setup>
    import dayjs from 'dayjs'
    import { ref, reactive, computed, onMounted, onBeforeUnmount, watch } from 'vue'
    defineOptions({ name: 'SegmentCom' })
    const defaultData = reactive({
      // 显示时间段
      time2: '2021-01-15 00:00:00',
      timeSegments: [
        {
          name: '时间段1',
          beginTime: new Date('2021-01-13 10:00:00').getTime(),
          endTime: new Date('2021-01-14 23:00:00').getTime(),
          color: '#1a94bc',
          startRatio: 0.65,
          endRatio: 0.9
        },
        {
          name: '时间段2',
          beginTime: new Date('2021-01-15 02:00:00').getTime(),
          endTime: new Date('2021-01-15 18:00:00').getTime(),
          color: '#1a94bc',
          startRatio: 0.65,
          endRatio: 0.9
        }
      ],
      timer: null
    })
    const showTime2 = computed(() => dayjs(defaultData.time2).format('YYYY-MM-DD HH:mm:ss'))
    const Timeline2 = ref(null)

    const timeChange2 = (t) => {
      defaultData.time2 = t
    }
    const click_timeSegments = (arr, ...args) => {
      console.log('onClickTimeSegments', arr, args)
      alert('点击了:' + arr[0].name)
    }
    const onClickTimeLine = (...args) => {
      console.log('onClickTimeLine', args)
    }
    const onDragTimeChange = (...args) => {
      console.log('onDragTimeChange', args)
    }
    onMounted(() => {
      defaultData.timer = setInterval(() => {
        defaultData.time2 += 1000
        Timeline2.value.setTime(defaultData.time2)
      }, 1000)
    })

    onBeforeUnmount(() => {
      clearTimeout(defaultData.timer)
    })
    </script>

    <style lang="scss" scoped>
    .container {
      width: 1200px;
      height: 100%;
      margin: 0 auto;
      display: flex;
      position: relative;
      justify-content: center;
      flex-direction: column;

      .timeLine {
        height: 50px;
      }
    }

    .timeShow {
      margin: 10px 0;
      display: flex;
      justify-content: center;
      user-select: none;
    }
  </style>

多个时间轴

当超过一个播放窗口时可以显示多个时间轴。

<template>
  <div class="container">
    <div class="timeShow">当前时间:{{ showTime3 }}</div>
    <div class="timeline3">
      <TimeLine
        ref="Timeline3"
        :initTime="defaultData.time3"
        @timeChange="timeChange3"
        :timeSegments="defaultData.timeSegments3"
        @click_timeSegments="click_timeSegments3"
        :windowList="defaultData.windowList"
        @click_window_timeSegments="click_window_timeSegments"
      ></TimeLine>
    </div>
  </div>
</template>

<script setup>
import dayjs from 'dayjs'
import { reactive, ref, computed, onMounted, onBeforeUnmount } from 'vue'

const defaultData = reactive({
  // 多个时间轴
  time3: '2021-01-15 00:00:00',
  timeSegments3: [
    {
      name: '时间段1',
      beginTime: new Date('2021-01-13 10:00:00').getTime(),
      endTime: new Date('2021-01-14 23:00:00').getTime(),
      color: '#FA3239',
      startRatio: 0.65,
      endRatio: 0.9
    },
    {
      name: '时间段2',
      beginTime: new Date('2021-01-15 02:00:00').getTime(),
      endTime: new Date('2021-01-15 18:00:00').getTime(),
      color: '#836ABB',
      startRatio: 0.65,
      endRatio: 0.9
    }
  ],
  windowList: [
    {
      name: '窗口1',
      timeSegments: [
        {
          name: '窗口1的时间段1',
          beginTime: new Date('2021-01-13 10:00:00').getTime(),
          endTime: new Date('2021-01-14 23:00:00').getTime(),
          color: '#FA3239',
          startRatio: 0.1,
          endRatio: 0.9
        },
        {
          name: '窗口1的时间段2',
          beginTime: new Date('2021-01-12 18:00:00').getTime(),
          endTime: new Date('2021-01-13 00:00:00').getTime(),
          color: '#00AEFF',
          startRatio: 0.1,
          endRatio: 0.9
        }
      ]
    },
    {
      name: '窗口2',
      timeSegments: [
        {
          name: '窗口2的时间段1',
          beginTime: new Date('2021-01-15 02:00:00').getTime(),
          endTime: new Date('2021-01-15 18:00:00').getTime(),
          color: '#FFCC00',
          startRatio: 0.1,
          endRatio: 0.9
        }
      ]
    },
    {
      name: '窗口3'
    },
    {
      name: '窗口4'
    },
    {
      name: '窗口5'
    },
    {
      name: '窗口6'
    }
  ],
  timer: null
})

const showTime3 = computed(() => dayjs(defaultData.time3).format('YYYY-MM-DD HH:mm:ss'))
const Timeline3 = ref(null)

const timeChange3 = (t) => {
  defaultData.time3 = t
}
const click_timeSegments3 = (arr) => {
  alert('点击了:' + arr[0].name)
}
const click_window_timeSegments = (data, index, item) => {
  alert('点击了窗口时间轴的时间段:' + data[0].name)
}
onMounted(() => {
  defaultData.timer = setInterval(() => {
    defaultData.time3 += 1000
    Timeline3.value.setTime(defaultData.time3)
  }, 1000)
})

onBeforeUnmount(() => {
  clearTimeout(defaultData.timer)
})
</script>

<style lang="scss" scoped>
.container {
  width: 1200px;
  height: 100%;
  margin: 0 auto;
  display: flex;
  position: relative;
  justify-content: center;
  flex-direction: column;

  .timeline3 {
    height: 200px;
  }
}

.timeShow {
  margin: 10px 0;
  display: flex;
  justify-content: center;
  user-select: none;
}
</style>

显示自定义元素

有时候会想在时间轴上显示一些自定义的东西,比如某个时间段显示一张图片之类的,这可以通过监听某个时间点的位置来实现。

<template>
  <div class="container">
    <div class="timeShow">当前时间:{{ showTime4 }}</div>
    <div class="timeLine4">
      <TimeLine
        ref="Timeline4Ref"
        :initTime="defaultData.time4"
        :windowList="defaultData.windowList4"
        @timeChange="timeChange4"
      ></TimeLine>
    </div>
    <i class="icon el-icon-s-flag" ref="flagIcon" style="color: #e72528"></i>
    <i class="icon el-icon-bicycle" ref="carIcon" style="color: #2196f3"></i>
  </div>
</template>

<script setup>
// 显示自定义元素
import dayjs from 'dayjs'
import { reactive, ref, computed, onMounted, onBeforeUnmount } from 'vue'

defineOptions({ name: 'CustomCom' })
const defaultData = reactive({
  time4: '2021-01-02 00:00:00',
  windowList4: [
    {
      name: '窗口1'
    },
    {
      name: '窗口2'
    },
    {
      name: '窗口3'
    },
    {
      name: '窗口4'
    },
    {
      name: '窗口5'
    },
    {
      name: '窗口6'
    }
  ],
  timer: null
})
const showTime4 = computed(() => dayjs(defaultData.time4).format('YYYY-MM-DD HH:mm:ss'))
const Timeline4Ref = ref(null)
const flagIcon = ref(null)
const carIcon = ref(null)

const timeChange4 = (t) => {
  defaultData.time4 = t
}
onMounted(() => {
  defaultData.timer = setInterval(() => {
    defaultData.time4 += 1000
    Timeline4Ref.value.setTime(defaultData.time4)
  }, 1000)
  window.addEventListener('scroll', () => {
    Timeline4Ref.value.updateWatchTime()
  })
  Timeline4Ref.value.watchTime('2021-01-01 23:30:00', (x, y) => {
    if (x === -1 || y === -1) {
      flagIcon.value.style.display = 'none'
    } else {
      flagIcon.value.style.display = 'block'
      flagIcon.value.style.left = x + 'px'
      flagIcon.value.style.top = y + 24 + 'px'
    }
  })
  Timeline4Ref.value.watchTime(
    '2021-01-02 02:30:00',
    (x, y) => {
      if (x === -1 || y === -1) {
        carIcon.value.style.display = 'none'
      } else {
        carIcon.value.style.display = 'block'
        carIcon.value.style.left = x + 'px'
        carIcon.value.style.top = y + 'px'
      }
    },
    2
  )
})

onBeforeUnmount(() => {
  clearTimeout(defaultData.timer)
})
</script>

<style lang="scss" scoped>
.container {
  width: 1200px;
  height: 100%;
  margin: 0 auto;
  display: flex;
  position: relative;
  justify-content: center;
  flex-direction: column;

  .timeLine4 {
    height: 200px;
  }
}

.timeShow {
  margin: 10px 0;
  display: flex;
  justify-content: center;
  user-select: none;
}

.icon {
  position: fixed;
  font-size: 30px;
}
</style>

API

属性

| 参数 | 说明 | 类型 | 可选值 | 默认值 | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ------ | ------------------- | | initTime | 初始时间,中点所在的时间,默认为当天0点。可以传递数字类型的时间戳或字符串类型的时间,如:2020-12-19 18:30:00 | Number/String | — | | | timeRange | 中间刻度所允许显示的时间范围,即当前时间的限定范围,对象类型,字段见表1-1 | Object | — | | | initZoomIndex | 初始的时间分辨率,数字索引,时间分辨率数组为:['半小时', '1小时', '2小时', '6小时', '12小时', '1天', '3天', '15天', '30天', '365天', '3650天'] | Number | — | 5 | | showCenterLine | 是否显示中间的竖线 | Boolean | — | true | | centerLineStyle | 中间竖线的样式,对象类型,字段见表1-2 | Object | — | | | textColor | 日期时间文字的颜色 | String | — | rgba(151,158,167,1) | | hoverTextColor | 鼠标滑过显示的时间文字颜色 | String | — | rgb(194, 202, 215) | | lineColor | 时间刻度的颜色 | String | — | rgba(151,158,167,1) | | lineHeightRatio | 时间刻度高度占时间轴高度的比例,对象格式,字段见表1-3 | Object | — | | | showHoverTime | 鼠标滑过时是否显示实时所在的时间 | Boolean | — | true | | timeSegments | 要显示的时间颜色段,对象数组类型,对象字段见表1-4 | Array | — | [] | | backgroundColor | 时间轴背景颜色 | String | — | #262626 | | enableZoom | 是否允许鼠标滚动切换时间分辨率 | Boolean | — | true | | enableDrag | 是否允许拖动 | Boolean | — | true | | windowList | 播放窗口列表,播放窗口数量大于1的话可以配置此项,会显示和窗口对应数量的时间轴,只有一个窗口的话请直接使用基本时间轴,对象数组类型,对象字段见表1-5 | Array | — | [] | | baseTimeLineHeight | 当显示windowList时的基础时间轴高度 | Number | — | 50 | | initSelectWindowTimeLineIndex | 初始选中的窗口时间轴索引 | Number | — | -1 | | maxClickDistance(v0.1.2+) | 鼠标按下和松开的距离小于该值认为是点击事件 | Number | — | 3 | | roundWidthTimeSegments(v0.1.6+) | 绘制时间段时对计算出来的坐标进行四舍五入,可以防止相连的时间段绘制出来有间隔的问题 | Boolean | — | true | | customShowTime(v0.1.7+) | 自定义显示哪些时间,详细请阅读下方【属性详解1-1】 | Function | — | | | hoverTimeFormat(v0.1.9+) | 格式化鼠标滑过时间,函数类型,接收一个time参数,代表当前鼠标所在的时间戳,函数需要返回一个格式化后的时间字符串,默认显示的时间格式为YYYY-MM-DD HH:mm:ss | Function | — | | | showDateAtZero(v0.1.9+) | 0点处是否显示日期,时间轴上默认0点处显示的日期,需要显示成时间,那么就需要设为false,然后通过formatTime属性自定义格式化 | Boolean | — | true | | formatTime(v0.1.9+) | 格式化时间轴显示时间,默认规则是模式YYYY年月模式YYYY-MM0MM-DD、其他HH:mm,如果想自定义,比如0点还是显示时间,不显示日期,就可以通过该函数进行格式化。如果该函数返回值为空值,那么还会走内部规则。 | Function | — | | | extendZOOM(v0.1.9+) | 扩展ZOOM列表,详细请参考下方【属性详解1-2】 | Array | — | [] |

属性详解1-1 customShowTime的用法

该属性在v0.1.7+版本开始支持

当你在使用本组件的时候可能会遇到一个很常见的问题,比如容器的宽度很小,然后时间段展示的又是几天、甚至半个月的时间,那么很容易遇到时间段里面时间都挤在一起的问题,比如这个issue,其实组件内部是内置了一些判断方法,比如在3天的时间分辨率下,对应的initZoomIndex=6,对应关系可参考上方属性表格,那么就会使用3天的这个判断规则,如下:

;(date) => {
  // 每三小时显示
  return date.getHours() % 3 === 0 && date.getMinutes() === 0
}

意思是显示3的倍数的整点小时,那么当你容器宽度不够,且时间分辨率设置的比较大,那么时间就会挤在一起看不清,这时候你就可以通过customShowTime属性传入自定义的判断方法,这个方法接收两个参数:

  • date:是否要显示的时间,可以根据该时间进行判断是否要显示这个时间
  • currentZoomIndex:当前的时间分辨率,比如3天对应的就是6,对应关系可参考上方属性表格

那么如果内置的规则不满足的话,就可以自定义,比如3天的时间分辨率下我想只显示12倍数的小时,可以这么做:

<TimeLine :customShowTime="customShowTime"></TimeLine>
export default {
  methods: {
    customShowTime(date, zoomIndex) {
      if (zoomIndex === 6) {
        return date.getHours() % 12 === 0 && date.getMinutes() === 0
      }
    }
  }
}

函数返回值需要注意一下,如果要显示返回true,如果不显示返回false,如果不处理,仍旧交给内部规则,返回其他值。

属性详解1-2 extendZOOM的用法

该属性用于扩展ZOOM列表,即时间分辨率,内置了11个时间分辨率,可以参考上方表格initZoomIndex属性,如果内置的时间分辨率满足不了你,那么可以通过该属性进行扩展。

extendZOOM为数组类型,数组的每一项为:

{
    zoom: 26, // 时间分辨率,整个时间轴表示的时间范围,单位:小时
    zoomHourGrid: 0.5, //时间分辨率对应的每格小时数,即时间轴上最小格代表多少小时
    mobileZoomHourGrid: 2, // 手机模式下时间分辨率对应的每格小时数,如果不用适配手机端,可以不用设置
}

这个数组的数据会追加到内部的ZOOM数组,对应的zoomIndex往后累加即可,内部一共有11zoom,那么你追加了一项,对应的zoomIndex为11,因为是从零开始计数。

同时你需要传递customShowTime属性来自定义控制时间显示,否则会报错,因为内置的规则只有11个。

接下来看一个案例。

只显示当天的时间, 从00:00:00到23:59:59,详细请看这个issue

首先默认的initZoomIndex5,即1天,刚好满足,不用修改,然后将enableZoom设为false,不允许修改时间分辨率;将enableDrag设为false,不允许拖拽; 然后再将initTime设为当天的12:00:00,那么刚好整个时间轴显示的就是当前的时间,到这里,似乎就可以了。但是实际上会存在一些问题,如前面的issue中所示。

问题1:0点处显示的是日期,需要改成时间

很简单,将showDateAtZero设为false0点就不会显示日期了

问题2:鼠标滑过显示的还是带日期的,这个好办,通过hoverTimeFormat属性自定义格式化规则即可:

hoverTimeFormat(time) {
    // 小于今天,大于今天的时间不显示
    if (
        dayjs(time).isBefore(dayjs().format('YYYY-MM-DD 00:00:00')) ||
        dayjs(time).isAfter(dayjs().format('YYYY-MM-DD 23:59:59'))
    ) {
        return ''
    }
    return dayjs(time).format('HH:mm:ss')
}

问题3:左右两侧的时间显示不出来

0点和24点的时间刚好是两端,因实现原理问题,无法显示,怎么办呢,其实很简单,假如时间轴表示的时间范围为25小时,那么左右两端不就会各多出半小时的时间吗,这个空间足够显示时间了,但是内部的时间分辨率没有25小时的,这时就需要扩展时间分辨率了:

extendZOOM: [
  {
    zoom: 25,
    zoomHourGrid: 0.5
  }
]

扩展了extendZOOMcustomShowTime不能少,否则会报错:

customShowTime(date, zoomIndex) {
    // 当zoomIndex等于11,也就是等于我们开展的zoom时才自己处理
    if (zoomIndex === 11) {
        // 时间是2的倍数时才会显示
        return date.getHours() % 2 === 0 && date.getMinutes() === 0
    }
}

到这里,你还会发现一个问题,24点实际上是下一天的0点,所以显示的是00:00,这样可能不符合我们的需求,这时我们可以通过formatTime来格式化时间轴上的时间显示,判断是下一天的0点,那么就改成24:00

formatTime(time) {
    // 下一天的00:00显示24:00
    if (time.isAfter(dayjs().format('YYYY-MM-DD 23:59:59'))) {
        return '24:00'
    }
    if (
        time.hour() === 0 &&
        time.minute() === 0 &&
        time.millisecond() === 0
    ) {
        return time.format('HH:mm')
    }
}

完整代码请参考:CustomZoom.vue

表1-1 timeRange对象的字段格式

| 字段名 | 说明 | 类型 | 可选值 | 默认值 | | ------ | --------------------------------------------------------------------------------------- | ------------- | ------ | ------ | | start | 允许显示的最小时间,可以传递数字类型的时间戳或字符串类型的时间,如:2020-12-19 18:30:00 | Number/String | — | | | end | 允许显示的最大时间,可以传递数字类型的时间戳或字符串类型的时间,如:2020-12-19 18:30:00 | Number/String | — | |

表1-2 centerLineStyle对象的字段格式

| 字段名 | 说明 | 类型 | 可选值 | 默认值 | | ------ | ---------------- | ------ | ------ | ------ | | width | 线的宽度,单位px | Number | — | 2 | | color | 线的颜色 | String | — | #fff |

表1-3 lineHeightRatio对象的字段格式

| 字段名 | 说明 | 类型 | 可选值 | 默认值 | | ------ | -------------------------- | ------ | ------ | ------ | | date | 0点时的日期线段高度 | Number | — | 0.3 | | time | 显示时间的线段高度 | Number | — | 0.2 | | none | 不显示时间的线段高度 | Number | — | 0.1 | | hover | 鼠标滑过时显示的时间段高度 | Number | — | 0.3 |

表1-4 timeSegments数组的对象元素的字段格式

| 字段名 | 说明 | 类型 | 可选值 | 默认值 | | ---------- | --------------------------------------------- | ------ | ------ | ------ | | beginTime | 起始时间戳,必填 | Number | — | | | endTime | 结束时间戳,必填 | Number | — | | | color | 颜色,必填 | String | — | | | startRatio | 高度的起始比例,即top=时间轴高度*startRatio | Number | — | 0.6 | | endRatio | 高度的结束比例,即bottom=时间轴高度*endRatio | Number | — | 0.9 |

从v0.1.8+版本开始,时间段可以只传一个beginTime,绘制一根宽度为1px的线段

表1-5 windowList数组的对象元素的字段格式

| 字段名 | 说明 | 类型 | 可选值 | 默认值 | | ------------ | ----------------------------------------- | ----- | ------ | ------ | | timeSegments | 要显示的时间段,对象数组,对象字段见表1-4 | Array | — | [] |

可以添加你需要的其他任意字段

事件

| 事件 | 说明 | 回调函数参数 | | ------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | timeChange | 当前时间切换事件 | currentTime(当前时间,时间戳格式) | | dragTimeChange | 拖动时间条结束后的事件 | currentTime(当前时间,时间戳格式) | | mousedown | 鼠标按下事件 | e(事件对象) | | mouseup | 鼠标松开事件 | e(事件对象) | | click_timeSegments | 点击到了基础时间轴里的时间段时触发 | timeSegments(点击到的时间段,数组类型)、time(v0.1.10+,点击位置对应的时间戳)、 date(v0.1.10+,点击位置对应的日期时间字符串)、 x(v0.1.10+,点击位置相对时间轴左侧的距离) | | click_window_timeSegments | 点击到了窗口时间轴里的时间段时触发 | timeSegments(点击到的时间段,数组类型)、index(时间轴索引)、item(时间轴数据) | | change_window_time_line | 点击窗口时间轴进行切换选中时触发 | index(时间轴索引)、item(时间轴数据) | | click_timeline(v0.1.2+) | 时间轴的点击事件 | time(点击位置对应的时间戳)、 date(点击位置对应的日期时间字符串)、 x(点击位置相对时间轴左侧的距离) |

方法

| 方法名 | 说明 | 参数 | | --------------- | ------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | updateWatchTime | 手动更新观察的时间位置,比如页面滚动后时间轴的整体位置变化了需要调用,如果没有显示自定义元素时无需调用 | | | reRender | 重新渲染 | | | setTime | 设置当前时间 | t(数字类型的时间戳或字符串类型的时间,如:2020-12-19 18:30:00) | | setZoom | 设置分辨率 | index(分辨率索引) | | watchTime | 设置要观察的时间点,会返回该时间点的实时位置,可以根据该位置来设置一些自定义元素的位置,位置为相对于浏览器可视窗口的位置 | time(要观察的时间,数字类型的时间戳或字符串类型的时间,如:2020-12-19 18:30:00), callback(时间点位置变化时会调用,回调参数为x水平位置、y重置位置,单位px), windowTimeLineIndex(如果自定义元素是要显示到某个窗口时间轴里的话,可以通过该参数来指定第几个时间轴,数字索引,从1开始) | | onResize | 如果时间轴所在的容器尺寸变化了需要调用该方法来适应 | |