time-axis-plus 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +348 -0
- package/index.cjs +2 -0
- package/index.es.js +896 -0
- package/index.umd.js +2 -0
- package/package.json +23 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-present
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# vue-time-axis
|
|
2
|
+
|
|
3
|
+
基于 **Canvas 双层渲染**的 Vue 3 时间轴组件,专为**监控录像回放**场景设计:支持录像片段可视化、点击/拖动定位、播放指针跟随、跨空白续播、双进度条概览、移动端手势缩放。**零运行时依赖**、**CSS 已内联**、**组件与原生 class 双模式**。
|
|
4
|
+
|
|
5
|
+
## ✨ 特性
|
|
6
|
+
|
|
7
|
+
- **双层 Canvas 架构**:静态层(边框 / 片段 / 刻度)仅在数据或视野变化时重绘,动态层(播放指针 / 悬浮提示)高频重绘成本低。
|
|
8
|
+
- **监控回放优化**:片段自动去重叠、播放指针居中跟随、点击空白跨段自动续播(`snap` + `nextBegin`)。
|
|
9
|
+
- **双进度条**:主时间轴 + 底部全天概览条(显示片段分布、可视窗口、播放位置,可点击 / 拖动跳转)。
|
|
10
|
+
- **移动端适配**:双指捏合缩放,粗指针设备自动降级(隐藏悬浮提示、调整缩放极限)。
|
|
11
|
+
- **零运行时依赖**:原生 `Date` + `ResizeObserver` 实现,不依赖 dayjs / element-resize-detector。
|
|
12
|
+
- **CSS 内联注入**:`es` / `cjs` / `umd` 全格式自包含样式,无需单独引入 css 文件。
|
|
13
|
+
- **双模式使用**:Vue 组件 `VueTimeAxis`,或脱离 Vue 直接操作 canvas 的原生 class `timeAxis`。
|
|
14
|
+
|
|
15
|
+
## 📦 安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install vue-time-axis
|
|
19
|
+
# 或
|
|
20
|
+
pnpm add vue-time-axis
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
> **peer 依赖**:`vue ^3.2.0`(由使用方提供;组件仅使用稳定的 Options API 特性)。
|
|
24
|
+
|
|
25
|
+
## 🚀 快速开始
|
|
26
|
+
|
|
27
|
+
```vue
|
|
28
|
+
<template>
|
|
29
|
+
<VueTimeAxis
|
|
30
|
+
:start-time="startTime"
|
|
31
|
+
:segments="segments"
|
|
32
|
+
:height="60"
|
|
33
|
+
@changeTime="onChangeTime"
|
|
34
|
+
/>
|
|
35
|
+
</template>
|
|
36
|
+
|
|
37
|
+
<script>
|
|
38
|
+
import VueTimeAxis from 'vue-time-axis';
|
|
39
|
+
|
|
40
|
+
export default {
|
|
41
|
+
components: { VueTimeAxis },
|
|
42
|
+
data() {
|
|
43
|
+
// 建议用本地时区构造时间戳,避免 new Date('YYYY-MM-DD') 的 UTC 解析偏差
|
|
44
|
+
const t = (h, m = 0, s = 0) => new Date(2022, 3, 14, h, m, s).getTime();
|
|
45
|
+
return {
|
|
46
|
+
startTime: t(0),
|
|
47
|
+
segments: [
|
|
48
|
+
{ begin: t(0), end: t(0, 0, 10), style: { background: 'rgba(24,208,217,0.5)' } },
|
|
49
|
+
{ begin: t(6), end: t(7), style: { background: 'rgba(24,208,217,0.5)' } },
|
|
50
|
+
{ begin: t(6, 30), end: t(7, 50), style: { background: 'rgba(255,77,79,0.6)' } },
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
methods: {
|
|
55
|
+
onChangeTime(data) {
|
|
56
|
+
// { mode, timestamp, valid, nextBegin }
|
|
57
|
+
console.log(data.mode, data.timestamp, data.valid, data.nextBegin);
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
</script>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 🧩 引入方式
|
|
65
|
+
|
|
66
|
+
组件默认导出即 `VueTimeAxis`,同时提供具名导出 `VueTimeAxis`(组件)与 `timeAxis`(原生 class)。
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
// ① 局部注册(默认导出)
|
|
70
|
+
import VueTimeAxis from 'vue-time-axis';
|
|
71
|
+
export default { components: { VueTimeAxis } };
|
|
72
|
+
|
|
73
|
+
// ② 局部注册(具名导出,可同时拿 class)
|
|
74
|
+
import { VueTimeAxis, timeAxis } from 'vue-time-axis';
|
|
75
|
+
|
|
76
|
+
// ③ 全局注册(install 注册名即 VueTimeAxis)
|
|
77
|
+
import { createApp } from 'vue';
|
|
78
|
+
import VueTimeAxis from 'vue-time-axis';
|
|
79
|
+
createApp(App).use(VueTimeAxis).mount('#app');
|
|
80
|
+
// 之后模板中可直接用 <VueTimeAxis />
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**UMD(浏览器 `<script>`,无构建工具)**:
|
|
84
|
+
|
|
85
|
+
```html
|
|
86
|
+
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
|
87
|
+
<script src="https://unpkg.com/vue-time-axis/index.umd.js"></script>
|
|
88
|
+
<script>
|
|
89
|
+
const { VueTimeAxis, timeAxis } = window.VueTimeAxis; // 命名空间对象
|
|
90
|
+
const app = Vue.createApp({ /* ... */ });
|
|
91
|
+
app.use(VueTimeAxis); // 或 app.component('VueTimeAxis', VueTimeAxis.default)
|
|
92
|
+
</script>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
> CSS 已内联进 JS,**无需**再 `import 'vue-time-axis/style.css'`。
|
|
96
|
+
|
|
97
|
+
## 📥 Props
|
|
98
|
+
|
|
99
|
+
| 属性 | 说明 | 类型 | 默认值 |
|
|
100
|
+
|------|------|------|--------|
|
|
101
|
+
| `width` | 宽度,数字按 px,字符串原样(如 `'100%'`) | `Number \| String` | `'100%'` |
|
|
102
|
+
| `height` | 高度,数字按 px | `Number \| String` | `40` |
|
|
103
|
+
| `startTime` | 轴起始时间(画布左缘对应的毫秒时间戳) | `Number` | 当天 0 点 |
|
|
104
|
+
| `segments` | 录像片段数组 `[{ begin, end, style? }]`,内部自动去重叠 | `Array` | `[]` |
|
|
105
|
+
| `visibleHours` | 可见时长(小时),即当前视口跨度 | `Number` | `24` |
|
|
106
|
+
| `maxVisibleHours` | PC 端缩小极限(可见时长上限) | `Number` | `24` |
|
|
107
|
+
| `mobileMaxVisibleHours` | 移动端缩小极限(默认更小,避免刻度拥挤) | `Number` | `12` |
|
|
108
|
+
| `minTime` | 可视边界起始(ms),`null` 不限制 | `Number` | `null` |
|
|
109
|
+
| `maxTime` | 可视边界结束(ms),`null` 不限制 | `Number` | `null` |
|
|
110
|
+
| `snapToNearest` | 点击空白区是否吸附到最近片段起点(跨空白续播) | `Boolean` | `false` |
|
|
111
|
+
| `hoverTip` | 悬浮十字线 + 时间提示;`null` 按设备自动(PC 开 / 移动关) | `Boolean` | `null` |
|
|
112
|
+
| `centerPointer` | 播放指针居中:`updateTime` 推进时自动滚动视口使指针居中 | `Boolean` | `false` |
|
|
113
|
+
| `centerResumeDelay` | 手势让位后自动恢复居中跟随的延迟(ms) | `Number` | `3000` |
|
|
114
|
+
| `overview` | 是否显示底部全天概览条(双进度条),**支持运行时动态开关** | `Boolean` | `false` |
|
|
115
|
+
| `overviewHeight` | 概览条高度(px) | `Number` | `20` |
|
|
116
|
+
| `overviewStart` | 概览范围起始(ms),`null` 自动推导(取边界或起始日全天) | `Number` | `null` |
|
|
117
|
+
| `overviewEnd` | 概览范围结束(ms),`null` 自动推导 | `Number` | `null` |
|
|
118
|
+
| `backgroundColor` | 画布背景色 | `String` | `'#000'` |
|
|
119
|
+
| `axisStyle` | 轴样式配置,支持只传部分字段逐项覆盖(见下表) | `Object` | `{}` |
|
|
120
|
+
|
|
121
|
+
> `startTime` / `minTime` / `maxTime` / `segments` / `visibleHours` / `overviewStart` / `overviewEnd` 变化会自动重绘;`axisStyle` / `snapToNearest` / `centerPointer` 等变化即时生效。
|
|
122
|
+
|
|
123
|
+
## 📤 Events
|
|
124
|
+
|
|
125
|
+
| 事件 | 回调参数 | 触发时机 |
|
|
126
|
+
|------|----------|----------|
|
|
127
|
+
| `changeTime` | `{ mode, timestamp, valid, nextBegin }` | 用户交互改变选中时间(点击 / 拖动 / 吸附) |
|
|
128
|
+
| `playTime` | `{ timestamp, valid }` | 播放指针推进(调用 `updateTime` 时触发) |
|
|
129
|
+
|
|
130
|
+
**`changeTime` 参数详解**:
|
|
131
|
+
|
|
132
|
+
- `mode`:触发方式,`'click'`(点击)\| `'drag'`(拖动)\| `'snap'`(点击空白被吸附到下一段起点)。
|
|
133
|
+
- `timestamp`:选中的毫秒时间戳。
|
|
134
|
+
- `valid`:选中点是否落在有效录像片段内。
|
|
135
|
+
- `nextBegin`:从选中时间往后最近一段录像的**完整信息**(含 `id` / `style` 等业务字段),无则 `null`。
|
|
136
|
+
|
|
137
|
+
**`playTime` 参数详解**:
|
|
138
|
+
|
|
139
|
+
- `timestamp`:当前播放毫秒时间戳。
|
|
140
|
+
- `valid`:是否处于有效录像区。
|
|
141
|
+
- 该回调**不控制自动续播**:`valid` 为 `false`(进入空白)时由业务侧决定如何处理,需要续播可调用 `getNextSegment(timestamp)` 查询下一段。
|
|
142
|
+
|
|
143
|
+
## 🛠 Methods(通过 `ref` 调用)
|
|
144
|
+
|
|
145
|
+
```vue
|
|
146
|
+
<VueTimeAxis ref="timeline" ... />
|
|
147
|
+
<!-- this.$refs.timeline.updateTime(Date.now()) -->
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
| 方法 | 说明 |
|
|
151
|
+
|------|------|
|
|
152
|
+
| `updateTime(time)` | 更新播放指针到 `time`(ms),触发 `playTime` 回调 |
|
|
153
|
+
| `getViewport()` | 返回当前视口 `{ startTime, visibleHours }` |
|
|
154
|
+
| `setViewport(startTime?, visibleHours?)` | 设置视口,两个参数均可省略 |
|
|
155
|
+
| `scrollTo(time)` | 把指定时间滚动到画布中央 |
|
|
156
|
+
| `resumeCenter()` | 立即结束手势让位并恢复居中跟随 |
|
|
157
|
+
| `zoomIn()` | 放大一档 |
|
|
158
|
+
| `zoomOut()` | 缩小一档 |
|
|
159
|
+
| `getNextSegment(time)` | 从 `time` 往后最近的一个片段,找不到返回 `null` |
|
|
160
|
+
| `getNextSegments(time)` | 从 `time` 往后的全部片段(`begin` 升序,完整信息),可作续播播放列表 |
|
|
161
|
+
| `updateOptions(json)` | 运行时更新配置:传入 json 对象逐项合并,支持动态开关 `overview`、改样式/边界/片段/视口等 |
|
|
162
|
+
|
|
163
|
+
### 运行时动态更新配置
|
|
164
|
+
|
|
165
|
+
除了通过 props 响应式更新,也可调用 `updateOptions(json)` 主动更新任意配置(组件与原生 class 均支持)。典型场景是**动态开关概览条**:
|
|
166
|
+
|
|
167
|
+
```vue
|
|
168
|
+
<template>
|
|
169
|
+
<VueTimeAxis ref="timeline" :segments="segments" />
|
|
170
|
+
<button @click="$refs.timeline.updateOptions({ overview: true })">显示概览条</button>
|
|
171
|
+
<button @click="$refs.timeline.updateOptions({ overview: false })">隐藏概览条</button>
|
|
172
|
+
<button @click="$refs.timeline.updateOptions({ axisStyle: { tickTextColor: '#ff0' } })">换肤</button>
|
|
173
|
+
</template>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
> `overview` 也可直接用 prop 动态绑定(`:overview="show"`),组件内部 `watch` 会自动调用 `updateOptions` 完成概览层的挂载/卸载与画布高度重算。
|
|
177
|
+
> `updateOptions` 内部会自动处理:`axisStyle` 逐项合并、`segments` 规范化去重叠、`visibleHours`/`startTime` 边界钳制、`overview` 结构重建,并在最后统一重绘。
|
|
178
|
+
|
|
179
|
+
## 🎨 axisStyle 样式配置
|
|
180
|
+
|
|
181
|
+
`axisStyle` 支持只传部分字段,逐项覆盖默认值:
|
|
182
|
+
|
|
183
|
+
| 字段 | 默认值 | 说明 |
|
|
184
|
+
|------|--------|------|
|
|
185
|
+
| `borderColor` | `rgb(151, 158, 167)` | 上下边框线 |
|
|
186
|
+
| `graduationColor` | `rgba(151, 158, 167, 1)` | 刻度线 |
|
|
187
|
+
| `tickTextColor` | `#ffffff` | 刻度文字 |
|
|
188
|
+
| `hoverLineColor` | `rgb(194, 202, 215)` | 悬浮十字线 |
|
|
189
|
+
| `hoverTextColor` | `rgb(0, 255, 0)` | 悬浮时间文字 |
|
|
190
|
+
| `overviewBg` | `rgb(43, 47, 51)` | 概览条背景 |
|
|
191
|
+
| `overviewTickColor` | `rgba(151, 158, 167, 0.35)` | 概览条小时参考刻度 |
|
|
192
|
+
| `overviewWindowColor` | `rgba(64, 196, 255, 0.15)` | 概览条可视窗口填充 |
|
|
193
|
+
| `overviewWindowBorder` | `rgb(64, 196, 255)` | 概览条可视窗口描边 |
|
|
194
|
+
| `overviewPlayColor` | `rgb(255, 92, 92)` | 概览条播放位置线 |
|
|
195
|
+
|
|
196
|
+
```vue
|
|
197
|
+
<VueTimeAxis :axis-style="{ tickTextColor: '#ccc', overviewPlayColor: '#ff5c5c' }" />
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## 📊 segments 数据格式
|
|
201
|
+
|
|
202
|
+
```js
|
|
203
|
+
segments: [
|
|
204
|
+
{
|
|
205
|
+
begin: 1649865600000, // 起始毫秒时间戳(必填)
|
|
206
|
+
end: 1649869200000, // 结束毫秒时间戳(必填)
|
|
207
|
+
id: 'cam-01', // 业务字段(可选,会原样带回 nextBegin)
|
|
208
|
+
style: { background: 'rgba(24,208,217,0.5)' } // 片段样式(可选)
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**去重叠规则**(无需预先处理,组件内部自动规范化):
|
|
214
|
+
|
|
215
|
+
- **完全重叠**:舍弃后段,以前段样式为准。
|
|
216
|
+
- **部分重叠**:按重叠起始位置分割,各自保留自身样式。
|
|
217
|
+
- **邻接**(首尾相接):不合并。
|
|
218
|
+
- 回调里返回的片段(如 `nextBegin`)均为**规范化后**的结果。
|
|
219
|
+
|
|
220
|
+
未传 `style` 的片段使用默认样式 `rgba(24,208,217,0.5)`。
|
|
221
|
+
|
|
222
|
+
## ⚙️ 原生 class 用法(脱离 Vue)
|
|
223
|
+
|
|
224
|
+
`timeAxis` 是底层的纯原生 canvas 类,可在任意环境(不限 Vue)使用:
|
|
225
|
+
|
|
226
|
+
```js
|
|
227
|
+
import { timeAxis } from 'vue-time-axis';
|
|
228
|
+
|
|
229
|
+
const axis = new timeAxis({
|
|
230
|
+
container: document.getElementById('timeline'), // DOM 元素或元素 id 字符串(必填)
|
|
231
|
+
startTime: new Date(2022, 3, 14).getTime(),
|
|
232
|
+
segments: [{ begin: /* ... */, end: /* ... */, style: { background: 'rgba(24,208,217,0.5)' } }],
|
|
233
|
+
minTime: new Date(2022, 3, 14, 0, 0, 0).getTime(),
|
|
234
|
+
maxTime: new Date(2022, 3, 14, 23, 59, 59).getTime(),
|
|
235
|
+
centerPointer: true,
|
|
236
|
+
changeCallback: (data) => console.log('change', data), // 对应组件 changeTime
|
|
237
|
+
playCallback: (data) => console.log('play', data), // 对应组件 playTime
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
axis.updateTime(Date.now()); // 推进播放指针
|
|
241
|
+
axis.zoomIn(); // 放大
|
|
242
|
+
axis.updateOptions({ overview: true }); // 传入 json 动态更新配置(如开启概览条)
|
|
243
|
+
// ... 组件的全部 methods(getViewport/setViewport/scrollTo/resumeCenter/
|
|
244
|
+
// zoomIn/zoomOut/getNextSegment/getNextSegments/updateTime/updateOptions)class 同样具备
|
|
245
|
+
|
|
246
|
+
axis.destroy(); // 用完销毁,释放 canvas 与事件监听
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
class 额外可配置项:`segmentStyle`(片段默认样式)、`pointerOptions`(指针样式 `{ beginY, endY, color, width }`)、`minVisibleHours`(放大极限,默认 `0.02` 小时≈秒级刻度)、`overviewGap`(概览条与主轴间距,默认 `4`px)等。
|
|
250
|
+
|
|
251
|
+
## 📖 完整示例(监控回放)
|
|
252
|
+
|
|
253
|
+
```vue
|
|
254
|
+
<template>
|
|
255
|
+
<div>
|
|
256
|
+
<VueTimeAxis
|
|
257
|
+
ref="timeline"
|
|
258
|
+
:start-time="startTime"
|
|
259
|
+
:min-time="minTime"
|
|
260
|
+
:max-time="maxTime"
|
|
261
|
+
:segments="segments"
|
|
262
|
+
:snap-to-nearest="true"
|
|
263
|
+
:center-pointer="true"
|
|
264
|
+
:height="40"
|
|
265
|
+
@changeTime="onChangeTime"
|
|
266
|
+
@playTime="onPlayTime"
|
|
267
|
+
/>
|
|
268
|
+
<button @click="togglePlay">{{ playing ? '暂停' : '播放' }}</button>
|
|
269
|
+
<button @click="$refs.timeline.zoomIn()">放大</button>
|
|
270
|
+
<button @click="$refs.timeline.zoomOut()">缩小</button>
|
|
271
|
+
<button @click="$refs.timeline.scrollTo(playTime)">定位到播放位置</button>
|
|
272
|
+
</div>
|
|
273
|
+
</template>
|
|
274
|
+
|
|
275
|
+
<script>
|
|
276
|
+
import VueTimeAxis from 'vue-time-axis';
|
|
277
|
+
|
|
278
|
+
const DAY = '2022-04-14';
|
|
279
|
+
// 按本地时区把 'YYYY-MM-DD HH:mm:ss' 解析为时间戳
|
|
280
|
+
const toTime = (str) => {
|
|
281
|
+
const [date, time = '00:00:00'] = str.split(' ');
|
|
282
|
+
const [y, mo, d] = date.split('-').map(Number);
|
|
283
|
+
const [h, mi, s] = time.split(':').map(Number);
|
|
284
|
+
return new Date(y, mo - 1, d, h, mi, s).getTime();
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
export default {
|
|
288
|
+
components: { VueTimeAxis },
|
|
289
|
+
data() {
|
|
290
|
+
return {
|
|
291
|
+
startTime: toTime(DAY),
|
|
292
|
+
minTime: toTime(DAY),
|
|
293
|
+
maxTime: toTime(`${DAY} 23:59:59`) + 999,
|
|
294
|
+
segments: [
|
|
295
|
+
{ id: '1', begin: toTime(`${DAY} 06:00:00`), end: toTime(`${DAY} 07:00:00`), style: { background: 'rgba(24,208,217,0.5)' } },
|
|
296
|
+
{ id: '2', begin: toTime(`${DAY} 06:30:00`), end: toTime(`${DAY} 07:50:00`), style: { background: 'rgba(255,77,79,0.6)' } },
|
|
297
|
+
],
|
|
298
|
+
playing: false,
|
|
299
|
+
playTimer: null,
|
|
300
|
+
playTime: toTime(DAY),
|
|
301
|
+
};
|
|
302
|
+
},
|
|
303
|
+
beforeUnmount() {
|
|
304
|
+
this.stopPlay();
|
|
305
|
+
},
|
|
306
|
+
methods: {
|
|
307
|
+
onChangeTime(data) {
|
|
308
|
+
// 用户点击/拖动/吸附:通知播放器 seek 到 data.timestamp
|
|
309
|
+
this.playTime = data.timestamp;
|
|
310
|
+
if (this.playing) { this.stopPlay(); this.startPlay(); }
|
|
311
|
+
},
|
|
312
|
+
onPlayTime(data) {
|
|
313
|
+
// 播放进入空白(valid=false)时,可查下一段实现自动续播
|
|
314
|
+
if (!data.valid) {
|
|
315
|
+
const next = this.$refs.timeline.getNextSegment(data.timestamp);
|
|
316
|
+
if (next) this.playTime = next.begin;
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
togglePlay() { this.playing ? this.stopPlay() : this.startPlay(); },
|
|
320
|
+
startPlay() {
|
|
321
|
+
this.playing = true;
|
|
322
|
+
this.playTimer = setInterval(() => {
|
|
323
|
+
this.playTime += 1000;
|
|
324
|
+
if (this.playTime >= this.maxTime) return this.stopPlay();
|
|
325
|
+
this.$refs.timeline.updateTime(this.playTime);
|
|
326
|
+
}, 1000);
|
|
327
|
+
},
|
|
328
|
+
stopPlay() {
|
|
329
|
+
this.playing = false;
|
|
330
|
+
if (this.playTimer) { clearInterval(this.playTimer); this.playTimer = null; }
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
</script>
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## ⚠️ 注意事项
|
|
338
|
+
|
|
339
|
+
- **时间单位**:所有时间相关的 props / 参数 / 回调字段均为**毫秒时间戳**(`Number`)。
|
|
340
|
+
- **时区**:构造时间戳建议用 `new Date(y, m - 1, d, h, mi, s).getTime()`(本地时区),避免 `new Date('YYYY-MM-DD')` 被按 UTC 解析导致偏移。
|
|
341
|
+
- **CSS 已内联**:无需单独引入样式文件。
|
|
342
|
+
- **自动清理**:组件卸载时会自动 `destroy()`,释放 canvas、`ResizeObserver` 与 `window.resize` 监听,无内存泄漏。
|
|
343
|
+
- **容器尺寸**:`width` 默认 `'100%'`(撑满父容器宽度);`height` 默认 `40`(px 固定高度),若显式设为 `'100%'` 则依赖父容器有确定高度;组件通过 `ResizeObserver` 自动响应容器尺寸变化重绘。
|
|
344
|
+
- **预留字段**:源码中的 `movePlay`、`clickPlay` 为历史预留 prop,当前版本**未接线生效**,请勿依赖。
|
|
345
|
+
|
|
346
|
+
## 📄 License
|
|
347
|
+
|
|
348
|
+
MIT
|
package/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(function(){"use strict";try{if(typeof document<"u"){var e=document.createElement("style");e.appendChild(document.createTextNode(".timeline-group[data-v-b90b2c6f]{font-size:0}.timeline-group .timeline-canvas[data-v-b90b2c6f]{background-color:#2b2f33}.timeline-group canvas[data-v-b90b2c6f]{display:block;touch-action:none}")),document.head.appendChild(e)}}catch(t){console.error("vite-plugin-css-injected-by-js",t)}})();
|
|
2
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const w=require("vue"),b={borderColor:"rgb(151, 158, 167)",graduationColor:"rgba(151, 158, 167, 1)",tickTextColor:"#ffffff",hoverLineColor:"rgb(194, 202, 215)",hoverTextColor:"rgb(0, 255, 0)",overviewBg:"rgb(43, 47, 51)",overviewTickColor:"rgba(151, 158, 167, 0.35)",overviewWindowColor:"rgba(64, 196, 255, 0.15)",overviewWindowBorder:"rgb(64, 196, 255)",overviewPlayColor:"rgb(255, 92, 92)"};class T{constructor(t){if(!t)throw new Error("timeAxis: options is required");let e=typeof t.container=="string"?document.getElementById(t.container):t.container;if(!e)throw new Error("timeAxis: options.container is required(DOM 元素或 id 字符串)");let i={visibleHours:24,minVisibleHours:.02,maxVisibleHours:24,graduationMinStep:10,secondsPerStep:[1,2,6,12,60,120,240,360,720,3600,7200,14400,21600,43200,86400],segmentStyle:{background:"rgba(24,208,217,0.5)"},pointerOptions:{beginY:0,endY:30,color:"rgb(64, 196, 255)",width:2},minTime:null,maxTime:null,startTime:this.dayStartOf(Date.now()),backgroundColor:"#000",snapToNearest:!1,hoverTip:!0,centerPointer:!1,centerResumeDelay:3e3,overview:!1,overviewHeight:20,overviewGap:4,overviewStart:null,overviewEnd:null,segments:[]};this.options=Object.assign({},i,t),this.options.container=e,this.options.visibleHours=Math.min(this.options.maxVisibleHours,Math.max(this.options.minVisibleHours,this.options.visibleHours)),this.configMinVisibleHours=this.options.minVisibleHours,this.options.segments=this.normalizeSegments(this.options.segments),this.options.segments.length&&(this.currentTimestamp=this.options.segments[0].begin),this.options.segmentStyle&&this.options.segmentStyle.background||(this.options.segmentStyle={background:"rgba(24,208,217,0.5)"}),this.options.axisStyle=Object.assign({},b,this.options.axisStyle||{}),this.options.startTime=this.clampStartTime(this.options.startTime),this.options.overviewStart==null&&(this.options.overviewStart=this.options.minTime!=null?this.options.minTime:this.dayStartOf(this.options.startTime)),this.options.overviewEnd==null&&(this.options.overviewEnd=this.options.maxTime!=null?this.options.maxTime:this.options.overviewStart+24*60*60*1e3),this.isClick=!1,this.button=0,this.isPressDown=!1,this.isDragPointer=!1,this.currentChecked="",this.pinchStartDistance=0,this.hoverState=null,this.centerPaused=!1,this.lastInteractTime=0,this.initCanvas(),this.drawStatic()}initCanvas(){this.options.container.style.position="relative",this.canvas=document.createElement("canvas"),this.canvas.style.position="absolute",this.canvas.style.left="0",this.canvas.style.top="0",this.overlayCanvas=document.createElement("canvas"),this.overlayCanvas.style.position="absolute",this.overlayCanvas.style.left="0",this.overlayCanvas.style.top="0",this.options.container.appendChild(this.canvas),this.options.container.appendChild(this.overlayCanvas),this.canvasCtx=this.canvas.getContext("2d"),this.overlayCtx=this.overlayCanvas.getContext("2d"),this.options.overview&&this.mountOverview(),this.initCanvasSize(),this.canvas.style.backgroundColor=this.options.backgroundColor,this.initEvents()}initCanvasSize(){let t=window.devicePixelRatio||1,e=this.options.container.clientWidth,i=this.options.container.clientHeight,s=this.options.overview?Math.max(0,i-this.options.overviewHeight-this.options.overviewGap):i,o=(a,u,v)=>{a.width=Math.round(e*t),a.height=Math.round(v*t),a.style.width=e+"px",a.style.height=v+"px",u.setTransform(t,0,0,t,0,0)};o(this.canvas,this.canvasCtx,s),o(this.overlayCanvas,this.overlayCtx,s),this.overviewCanvas&&(o(this.overviewCanvas,this.overviewCtx,this.options.overviewHeight),this.overviewCanvas.style.top=s+this.options.overviewGap+"px"),this.canvasWidth=e,this.canvasHeight=s,this.options.minVisibleHours=Math.min(this.configMinVisibleHours,this.canvasWidth/(this.options.graduationMinStep+2)/3600)}resize(){this.initCanvasSize(),this.drawStatic()}drawStatic(){this.pxPerMs=this.canvasWidth/(this.options.visibleHours*60*60*1e3),this.clearCanvas(),this.initBorders(),this.initSegments(this.options.segments||[]),this.initGraduationsLines(this.options.startTime),this.drawOverlay()}drawOverlay(){if(this.clearOverlay(),this.options.hoverTip&&this.hoverState){let{x:t,time:e}=this.hoverState;this.drawLine(this.overlayCtx,{beginX:t,beginY:0,endX:t,endY:this.canvasHeight,color:this.options.axisStyle.hoverLineColor,width:1,center:!0}),this.overlayCtx.fillStyle=this.options.axisStyle.hoverTextColor,this.overlayCtx.textAlign="center",this.overlayCtx.font="normal normal 12px Arial,sans-serif",this.overlayCtx.fillText(this.formatDate("yyyy-MM-dd hh:mm:ss",e),t,18)}this.isClick&&this.currentTimestamp!=null&&this.drawPointer(),this.drawOverview()}initBorders(){let t=this.options.axisStyle.borderColor;this.drawLine(this.canvasCtx,{beginX:0,beginY:0,endX:this.canvasWidth,endY:0,color:t,width:1}),this.drawLine(this.canvasCtx,{beginX:0,beginY:this.canvasHeight,endX:this.canvasWidth,endY:this.canvasHeight,color:t,width:1})}drawLine(t,{beginX:e,beginY:i,endX:s,endY:o,color:a,width:u,center:v}){let h=!v&&u%2===1?.5:0;t.beginPath(),t.moveTo(e+h,i+h),t.lineTo(s+h,o+h),t.strokeStyle=a,t.lineWidth=u,t.stroke()}initSegments(t){t.forEach((e,i)=>{this.drawSegment(e,i)}),this.canvasCtx.font="normal normal 12px Arial,sans-serif",this.canvasCtx.fillStyle=this.options.axisStyle.tickTextColor}drawSegment(t,e){let i=this.options.segmentStyle.background;t.style&&t.style.background&&(i=t.style.background);let s=this.pxPerMs*(t.begin-this.options.startTime),o=(t.end-t.begin)*this.pxPerMs;this.canvasCtx.fillStyle=i,this.canvasCtx.font="normal normal 12px Arial,sans-serif",this.canvasCtx.fillRect(s,0,o,this.canvasHeight),this.canvasCtx.fillStyle=this.options.axisStyle.tickTextColor}initGraduationsLines(t){let e=this.canvasWidth,i=e/(this.options.visibleHours*60*60),s=this.options.graduationMinStep/i,o=this.options.secondsPerStep.find(l=>l>s),a=o*i,u=e/a,v=this.ms_to_next_step(t,o),h=v*this.pxPerMs,r=o<60&&i*60>=50;for(let l=0;l<=u;l++){let m=t+v+l*o*1e3,d=h+l*a,c=new Date(Math.round(m)),g=10,x=1;if(c.getHours()===0&&c.getMinutes()===0&&c.getSeconds()===0){g=24,x=2;let p=this.formatDate("MM-dd",m);this.canvasCtx.textAlign="center",this.canvasCtx.fillStyle=this.options.axisStyle.tickTextColor,this.canvasCtx.font="normal normal 12px Arial,sans-serif",this.canvasCtx.fillText(p,d,34)}else if(c.getMinutes()===0&&c.getSeconds()===0){g=19,x=2;let p=this.formatDate(o<60?"hh:mm:ss":"hh:mm",m);this.canvasCtx.fillStyle=this.options.axisStyle.tickTextColor,this.canvasCtx.textAlign="center",this.canvasCtx.font="normal normal 12px Arial,sans-serif",this.canvasCtx.fillText(p,d,29)}else if(r&&c.getSeconds()===0){g=14,x=2;let p=this.formatDate("hh:mm:ss",m);this.canvasCtx.fillStyle=this.options.axisStyle.tickTextColor,this.canvasCtx.textAlign="center",this.canvasCtx.font="normal normal 12px Arial,sans-serif",this.canvasCtx.fillText(p,d,24)}this.drawLine(this.canvasCtx,{beginX:d,beginY:0,endX:d,endY:g,color:this.options.axisStyle.graduationColor,width:x,center:!0})}}drawPointer(){let t=this.pxPerMs*(this.currentTimestamp-this.options.startTime),e=this.overlayCtx;e.beginPath(),e.moveTo(t,0),e.lineTo(t,this.options.pointerOptions.endY),e.strokeStyle=this.options.pointerOptions.color,e.lineWidth=this.options.pointerOptions.width,e.stroke(),e.beginPath();let i=12/Math.sin(Math.PI/3.5);e.moveTo(t,this.options.pointerOptions.endY-1),e.lineTo(t-i/2,40),e.lineTo(t+i/2,40),e.closePath(),e.fillStyle=this.options.pointerOptions.color,e.fill()}dayStartOf(t){let e=new Date(t);return e.setHours(0,0,0,0),e.getTime()}drawOverview(){if(!this.overviewCanvas)return;let t=this.overviewCtx,e=this.canvasWidth,i=this.options.overviewHeight;t.clearRect(0,0,e,i);let s=this.options.overviewEnd-this.options.overviewStart;if(s<=0)return;let o=e/s;t.fillStyle=this.options.axisStyle.overviewBg,t.fillRect(0,0,e,i),t.strokeStyle=this.options.axisStyle.overviewTickColor,t.lineWidth=1;let a=60*60*1e3;for(let r=this.dayStartOf(this.options.overviewStart);r<=this.options.overviewEnd;r+=a){if(r<this.options.overviewStart)continue;let l=(r-this.options.overviewStart)*o;t.beginPath(),t.moveTo(l,0),t.lineTo(l,4),t.stroke()}let u=i/2-3;(this.options.segments||[]).forEach(r=>{let l=Math.max(r.begin,this.options.overviewStart),m=Math.min(r.end,this.options.overviewEnd);m<=l||(t.fillStyle=r.style&&r.style.background||this.options.segmentStyle.background,t.fillRect((l-this.options.overviewStart)*o,u,Math.max((m-l)*o,1),6))});let v=(this.options.startTime-this.options.overviewStart)*o,h=this.options.visibleHours*60*60*1e3*o;if(t.fillStyle=this.options.axisStyle.overviewWindowColor,t.fillRect(v,0,h,i),t.strokeStyle=this.options.axisStyle.overviewWindowBorder,t.strokeRect(v+.5,.5,Math.max(h-1,0),i-1),this.isClick&&this.currentTimestamp!=null){let r=(this.currentTimestamp-this.options.overviewStart)*o;r>=0&&r<=e&&this.drawLine(t,{beginX:r,beginY:0,endX:r,endY:i,color:this.options.axisStyle.overviewPlayColor,width:1,center:!0})}}ms_to_next_step(t,e){let i=new Date(t),o=(i.getHours()*3600+i.getMinutes()*60+i.getSeconds())%e;return(o?e-o:0)*1e3-i.getMilliseconds()}clampStartTime(t){if(this.options.minTime==null&&this.options.maxTime==null)return t;let e=this.options.visibleHours*60*60*1e3;return this.options.maxTime!=null&&t+e>this.options.maxTime&&(t=this.options.maxTime-e),this.options.minTime!=null&&t<this.options.minTime&&(t=this.options.minTime),t}clampTime(t){return this.options.minTime!=null&&t<this.options.minTime&&(t=this.options.minTime),this.options.maxTime!=null&&t>this.options.maxTime&&(t=this.options.maxTime),t}initEvents(){this.overlayCanvas&&(this.contextmenuHandler=t=>{t.preventDefault()},this.mousemoveHandler=this.canvasMousemoveFunc.bind(this),this.wheelHandler=this.mousewheelFunc.bind(this),this.mousedownHandler=this.mousedownFunc.bind(this),this.mouseupHandler=this.mouseupFunc.bind(this),this.mouseoutHandler=this.mouseoutFunc.bind(this),this.touchstartHandler=this.touchStartFunc.bind(this),this.touchmoveHandler=this.touchMoveFunc.bind(this),this.touchendHandler=this.touchEndFunc.bind(this),this.overlayCanvas.addEventListener("contextmenu",this.contextmenuHandler),this.overlayCanvas.addEventListener("mousemove",this.mousemoveHandler),this.overlayCanvas.addEventListener("wheel",this.wheelHandler,{passive:!1}),this.overlayCanvas.addEventListener("mousedown",this.mousedownHandler),this.overlayCanvas.addEventListener("mouseup",this.mouseupHandler),this.overlayCanvas.addEventListener("mouseout",this.mouseoutHandler),this.overlayCanvas.addEventListener("touchstart",this.touchstartHandler,{passive:!1}),this.overlayCanvas.addEventListener("touchmove",this.touchmoveHandler,{passive:!1}),this.overlayCanvas.addEventListener("touchend",this.touchendHandler))}mountOverview(){this.overviewCanvas||(this.overviewCanvas=document.createElement("canvas"),this.overviewCanvas.style.position="absolute",this.overviewCanvas.style.left="0",this.options.container.appendChild(this.overviewCanvas),this.overviewCtx=this.overviewCanvas.getContext("2d"),this.bindOverviewEvents())}bindOverviewEvents(){this.overviewCanvas&&(this.overviewMousedownHandler=this.overviewMousedownFunc.bind(this),this.overviewMousemoveHandler=()=>{this.overviewCanvas.style.cursor="pointer"},this.overviewTouchstartHandler=this.overviewTouchFunc.bind(this),this.overviewTouchmoveHandler=this.overviewTouchFunc.bind(this),this.overviewCanvas.addEventListener("mousedown",this.overviewMousedownHandler),this.overviewCanvas.addEventListener("mousemove",this.overviewMousemoveHandler),this.overviewCanvas.addEventListener("touchstart",this.overviewTouchstartHandler,{passive:!1}),this.overviewCanvas.addEventListener("touchmove",this.overviewTouchmoveHandler,{passive:!1}))}unmountOverview(){this.overviewCanvas&&(this.overviewCanvas.removeEventListener("mousedown",this.overviewMousedownHandler),this.overviewCanvas.removeEventListener("mousemove",this.overviewMousemoveHandler),this.overviewCanvas.removeEventListener("touchstart",this.overviewTouchstartHandler),this.overviewCanvas.removeEventListener("touchmove",this.overviewTouchmoveHandler),this.overviewCanvas.remove(),this.overviewCanvas=null,this.overviewCtx=null)}destroy(){this.overlayCanvas&&(this.overlayCanvas.removeEventListener("contextmenu",this.contextmenuHandler),this.overlayCanvas.removeEventListener("mousemove",this.mousemoveHandler),this.overlayCanvas.removeEventListener("wheel",this.wheelHandler),this.overlayCanvas.removeEventListener("mousedown",this.mousedownHandler),this.overlayCanvas.removeEventListener("mouseup",this.mouseupHandler),this.overlayCanvas.removeEventListener("mouseout",this.mouseoutHandler),this.overlayCanvas.removeEventListener("touchstart",this.touchstartHandler),this.overlayCanvas.removeEventListener("touchmove",this.touchmoveHandler),this.overlayCanvas.removeEventListener("touchend",this.touchendHandler),this.overlayCanvas.remove(),this.overlayCanvas=null),this.unmountOverview(),this.canvas&&(this.canvas.remove(),this.canvas=null),document.onmousemove=null,document.onmouseup=null}canDragPointer(t){let e=t.offsetX,i=t.offsetY,s=window.devicePixelRatio||1;return this.overlayCtx.isPointInPath(e*s,i*s)}setPointer(t){this.canDragPointer(t)?(this.options.container.style.cursor="pointer",this.isDragPointer=!0,this.isMoveTriangle=!0):(!this.isPressDown||this.button)&&(this.isPressDown&&this.isMoveTriangle||(this.isDragPointer=!1,this.isMoveTriangle=!1,this.options.container.style.cursor=""))}mousedownFunc(t){t.button===0?(this.isPressDown=!0,this.button=t.button,this.canDragPointer(t)?this.currentChecked="pointer":this.currentChecked="axis",document.onmousemove=e=>{this.mousemoveFunc(e)},document.onmouseup=e=>{this.mouseupFunc(e),document.onmousemove=null,document.onmouseup=null},this.isDown=!0,this.g_mousedownCursor=this.getCursorPositionX(t)):t.button}canvasMousemoveFunc(t){if(this.isPressDown)return;let e=this.getCursorPositionX(t);this.setPointer(t),this.hoverState={x:e,time:this.options.startTime+e/this.pxPerMs},this.drawOverlay()}mousemoveFunc(t){let e=this.getCursorPositionX(t);if(this.setPointer(t),this.isMove=!0,this.markUserInteract(),this.currentChecked==="axis"){let i=e-this.g_mousedownCursor;this.options.startTime=this.clampStartTime(this.options.startTime-Math.round(i/this.pxPerMs)),this.g_mousedownCursor=e,this.hoverState={x:e,time:this.options.startTime+e/this.pxPerMs},this.drawStatic()}else this.currentChecked==="pointer"&&(this.currentTimestamp=this.clampTime(this.options.startTime+e/this.pxPerMs),this.g_mousedownCursor=e,this.hoverState={x:e,time:this.currentTimestamp},this.drawOverlay())}mouseupFunc(t){if(this.isPressDown&&this.button===0)if(this.isPressDown=!1,this.isMove)this.isMove=!1,this.currentChecked==="pointer"&&(this.returnTime=this.currentTimestamp,this.returnCheckTime("drag",this.returnTime),this.drawOverlay()),this.currentChecked="";else{this.isClick=!0;let e=this.getCursorPositionX(t),i=this.clampTime(this.options.startTime+e/this.pxPerMs);this.currentTimestamp=this.returnTime=i,this.returnCheckTime("click",this.returnTime),this.drawOverlay()}}mousewheelFunc(t){t.preventDefault(),this.markUserInteract();let e=t.deltaY<0?1:-1,i=this.getCursorPositionX(t),s=this.options.startTime+i/this.pxPerMs;e<0?this.options.visibleHours=Math.min(this.options.maxVisibleHours,this.options.visibleHours*1.25):e>0&&(this.options.visibleHours=Math.max(this.options.minVisibleHours,this.options.visibleHours/1.25));let o=this.canvasWidth/(this.options.visibleHours*60*60*1e3);this.options.startTime=this.clampStartTime(s-i/o),this.drawStatic()}mouseoutFunc(t){this.hoverState=null,this.drawOverlay()}touchStartFunc(t){if(t.preventDefault(),t.touches.length===1)this.mousedownFunc(this.normalizeTouchEvent(t.touches[0]));else if(t.touches.length===2){this.isPressDown=!1,this.isMove=!1,this.currentChecked="",document.onmousemove=null,document.onmouseup=null,this.pinchStartDistance=this.getTouchDistance(t.touches),this.pinchStartVisibleHours=this.options.visibleHours;let e=this.getTouchCenterX(t.touches)-this.overlayCanvas.getBoundingClientRect().left;this.pinchCenterPosX=e,this.pinchCenterTime=this.options.startTime+e/this.pxPerMs}}touchMoveFunc(t){if(t.preventDefault(),t.touches.length===1&&this.isPressDown)this.mousemoveFunc(this.normalizeTouchEvent(t.touches[0]));else if(t.touches.length===2&&this.pinchStartDistance){this.markUserInteract();let e=this.getTouchDistance(t.touches)/this.pinchStartDistance;this.options.visibleHours=Math.min(this.options.maxVisibleHours,Math.max(this.options.minVisibleHours,this.pinchStartVisibleHours/(e*e)));let i=this.canvasWidth/(this.options.visibleHours*60*60*1e3);this.options.startTime=this.clampStartTime(this.pinchCenterTime-this.pinchCenterPosX/i),this.drawStatic()}}touchEndFunc(t){if(this.pinchStartDistance&&t.touches.length<2){this.pinchStartDistance=0,this.isPressDown=!1,this.isMove=!1,this.currentChecked="",document.onmousemove=null,document.onmouseup=null;return}this.isPressDown&&t.changedTouches.length&&(this.mouseupFunc(this.normalizeTouchEvent(t.changedTouches[0])),document.onmousemove=null,document.onmouseup=null)}normalizeTouchEvent(t){let e=this.overlayCanvas.getBoundingClientRect();return{button:0,pageX:t.pageX,pageY:t.pageY,clientX:t.clientX,clientY:t.clientY,offsetX:t.clientX-e.left,offsetY:t.clientY-e.top}}getTouchDistance(t){let e=t[0].clientX-t[1].clientX,i=t[0].clientY-t[1].clientY;return Math.sqrt(e*e+i*i)}getTouchCenterX(t){return(t[0].clientX+t[1].clientX)/2}overviewSeek(t){let e=this.overviewCanvas.getBoundingClientRect();if(e.width<=0)return;this.markUserInteract();let i=Math.min(e.width,Math.max(0,t-e.left)),s=this.options.overviewStart+i/e.width*(this.options.overviewEnd-this.options.overviewStart);this.options.startTime=this.clampStartTime(s-this.options.visibleHours*60*60*1e3/2),this.drawStatic()}overviewMousedownFunc(t){t.button===0&&(this.overviewSeek(t.clientX),document.onmousemove=e=>{this.overviewSeek(e.clientX)},document.onmouseup=()=>{document.onmousemove=null,document.onmouseup=null})}overviewTouchFunc(t){t.preventDefault(),t.touches.length&&this.overviewSeek(t.touches[0].clientX)}getCursorPositionX(t){let e=0;return t||(t=window.event),t.pageX||t.pageY?e=t.pageX:(t.clientX||t.clientY)&&(e=t.clientX+document.body.scrollLeft+document.documentElement.scrollLeft),e-=this.overlayCanvas.getBoundingClientRect().left,e}clearCanvas(){this.canvasCtx.clearRect(0,0,this.canvasWidth,this.canvasHeight)}clearOverlay(){this.overlayCtx.clearRect(0,0,this.canvasWidth,this.canvasHeight)}returnCheckTime(t,e){var o,a,u,v;let i=Math.floor(e);if(this.options.segments.findIndex(h=>i>=h.begin&&i<=h.end)>=0)(a=(o=this.options).changeCallback)==null||a.call(o,{mode:t,timestamp:i,valid:!0,nextBegin:this.getNextSegment(i)});else{let h=i,r=!1,l=t;if(this.options.snapToNearest&&this.options.segments&&this.options.segments.length){let m=this.getNextSegment(i);m&&(h=Math.floor(m.begin),r=!0,l="snap",this.currentTimestamp=this.clampTime(h))}(v=(u=this.options).changeCallback)==null||v.call(u,{mode:l,timestamp:h,valid:r,nextBegin:this.getNextSegment(i)})}}formatDate(t,e){let i=e?new Date(e):new Date;t=t||"yyyy-MM-dd";const s={"M+":i.getMonth()+1,"d+":i.getDate(),"h+":i.getHours(),"m+":i.getMinutes(),"s+":i.getSeconds(),"q+":Math.floor((i.getMonth()+3)/3),S:i.getMilliseconds()};/(y+)/.test(t)&&(t=t.replace(RegExp.$1,(i.getFullYear()+"").substr(4-RegExp.$1.length)));for(const o in s)new RegExp("("+o+")").test(t)&&(t=t.replace(RegExp.$1,RegExp.$1.length==1?s[o]:("00"+s[o]).substr((""+s[o]).length)));return t}updateDatas(t){this.options.visibleHours=Math.min(this.options.maxVisibleHours,Math.max(this.options.minVisibleHours,t.visibleHours)),this.options.startTime=t.startTime,t.minTime!==void 0&&(this.options.minTime=t.minTime),t.maxTime!==void 0&&(this.options.maxTime=t.maxTime),t.overviewStart!=null&&(this.options.overviewStart=t.overviewStart),t.overviewEnd!=null&&(this.options.overviewEnd=t.overviewEnd),this.options.startTime=this.clampStartTime(this.options.startTime),this.options.segments=this.normalizeSegments(t.segments),!this.options.segments.some(i=>this.currentTimestamp>=i.begin&&this.currentTimestamp<=i.end)&&this.options.segments.length&&(this.currentTimestamp=this.options.segments[0].begin),this.drawStatic()}updateOptions(t){if(!t||typeof t!="object")return;const e=this.options.overview,i=["axisStyle","segments","visibleHours","startTime"];Object.keys(t).forEach(s=>{t[s]===void 0||i.includes(s)||(this.options[s]=t[s])}),t.axisStyle&&(this.options.axisStyle=Object.assign({},this.options.axisStyle,t.axisStyle)),t.segments!==void 0&&(this.options.segments=this.normalizeSegments(t.segments)),t.visibleHours!==void 0&&(this.options.visibleHours=Math.min(this.options.maxVisibleHours,Math.max(this.options.minVisibleHours,t.visibleHours))),t.startTime!==void 0&&(this.options.startTime=t.startTime),(t.startTime!==void 0||t.minTime!==void 0||t.maxTime!==void 0)&&(this.options.startTime=this.clampStartTime(this.options.startTime)),t.overview!==void 0&&t.overview!==e&&(this.options.overview?this.mountOverview():this.unmountOverview()),["overview","overviewHeight","overviewGap"].some(s=>t[s]!==void 0)&&this.initCanvasSize(),t.backgroundColor!==void 0&&this.canvas&&(this.canvas.style.backgroundColor=t.backgroundColor),t.segments!==void 0&&!this.options.segments.some(o=>this.currentTimestamp>=o.begin&&this.currentTimestamp<=o.end)&&this.options.segments.length&&(this.currentTimestamp=this.options.segments[0].begin),this.drawStatic()}updateTime(t){var s,o;let e=this.clampTime(t);this.currentTimestamp=e,this.returnTime=e,this.isClick=!0,this.options.centerPointer&&!this.isCenterPaused()?(this.options.startTime=this.clampStartTime(e-this.options.visibleHours*60*60*1e3/2),this.drawStatic()):this.drawOverlay();let i=(this.options.segments||[]).some(a=>e>=a.begin&&e<=a.end);(o=(s=this.options).playCallback)==null||o.call(s,{timestamp:Math.floor(e),valid:i})}markUserInteract(){this.centerPaused=!0,this.lastInteractTime=Date.now()}isCenterPaused(){return this.centerPaused?this.isPressDown||this.pinchStartDistance?!0:Date.now()-this.lastInteractTime>=this.options.centerResumeDelay?(this.centerPaused=!1,!1):!0:!1}getViewport(){return{startTime:this.options.startTime,visibleHours:this.options.visibleHours}}setViewport(t,e){e!=null&&(this.options.visibleHours=Math.min(this.options.maxVisibleHours,Math.max(this.options.minVisibleHours,e))),t!=null&&(this.options.startTime=this.clampStartTime(t)),this.drawStatic()}scrollTo(t){this.options.startTime=this.clampStartTime(t-this.options.visibleHours*60*60*1e3/2),this.drawStatic()}resumeCenter(){this.centerPaused=!1,this.lastInteractTime=0,this.scrollTo(this.currentTimestamp)}zoomBy(t){if(!(t>0))return;let e=this.options.startTime+this.options.visibleHours*60*60*1e3/2;this.options.visibleHours=Math.min(this.options.maxVisibleHours,Math.max(this.options.minVisibleHours,this.options.visibleHours/t)),this.options.startTime=this.clampStartTime(e-this.options.visibleHours*60*60*1e3/2),this.drawStatic()}zoomIn(){this.zoomBy(1.25)}zoomOut(){this.zoomBy(.8)}getNextSegments(t){return(this.options.segments||[]).filter(e=>e.begin>=t).sort((e,i)=>e.begin-i.begin)}getNextSegment(t){return this.getNextSegments(t)[0]||null}normalizeSegments(t){if(!Array.isArray(t))return[];let e=t.filter(s=>s&&s.end>s.begin).sort((s,o)=>s.begin-o.begin||o.end-s.end),i=[];return e.forEach(s=>{let o=i[i.length-1];if(!o||s.begin>=o.end){i.push(Object.assign({},s));return}s.end<=o.end||(o.end=s.begin,o.end>o.begin?i.push(Object.assign({},s)):i[i.length-1]=Object.assign({},s))}),i}}const y=(n,t)=>{const e=n.__vccOpts||n;for(const[i,s]of t)e[i]=s;return e},C={name:"TimeAxisPlus",props:{width:{type:[Number,String],default:"100%"},height:{type:[Number,String],default:40},startTime:{type:Number,default:()=>{const n=new Date;return n.setHours(0,0,0,0),n.getTime()}},segments:{type:Array,default:()=>[]},visibleHours:{type:Number,default:24},maxVisibleHours:{type:Number,default:24},mobileMaxVisibleHours:{type:Number,default:12},minTime:{type:Number,default:null},maxTime:{type:Number,default:null},snapToNearest:{type:Boolean,default:!1},hoverTip:{type:Boolean,default:null},centerPointer:{type:Boolean,default:!1},centerResumeDelay:{type:Number,default:3e3},overview:{type:Boolean,default:!1},overviewHeight:{type:Number,default:20},overviewStart:{type:Number,default:null},overviewEnd:{type:Number,default:null},movePlay:{type:Boolean,default:!1},clickPlay:{type:Boolean,default:!0},backgroundColor:{type:String,default:"#000"},axisStyle:{type:Object,default:()=>({})}},data(){return{axis:null,ro:null,isMobile:!1}},computed:{effectiveMaxVisibleHours(){return this.isMobile?this.mobileMaxVisibleHours:this.maxVisibleHours},effectiveHoverTip(){return this.hoverTip==null?!this.isMobile:this.hoverTip}},watch:{segments:{deep:!0,handler(){this.updateAxis()}},startTime(){this.updateAxis()},visibleHours(){this.updateAxis()},effectiveMaxVisibleHours(n){this.axis&&(this.axis.options.maxVisibleHours=n)},effectiveHoverTip(n){this.axis&&(this.axis.options.hoverTip=n)},minTime(){this.updateAxis()},maxTime(){this.updateAxis()},snapToNearest(n){this.axis&&(this.axis.options.snapToNearest=n)},centerPointer(n){this.axis&&(this.axis.options.centerPointer=n)},centerResumeDelay(n){this.axis&&(this.axis.options.centerResumeDelay=n)},axisStyle:{deep:!0,handler(n){this.axis&&(this.axis.options.axisStyle=Object.assign({},this.axis.options.axisStyle,n),this.axis.drawStatic())}},overview(n){console.log(9999887),this.axis&&this.axis.updateOptions({overview:n})},overviewStart(){this.updateAxis()},overviewEnd(){this.updateAxis()}},created(){this.isMobile=window.matchMedia("(pointer: coarse)").matches||/Android|iPhone|iPad|iPod|Mobile/i.test(navigator.userAgent)},mounted(){this.$nextTick(()=>{this.initTimeAxis()}),window.addEventListener("resize",this.resizeHandler),this.watchSize()},beforeUnmount(){window.removeEventListener("resize",this.resizeHandler),this.ro&&(this.ro.disconnect(),this.ro=null),this.axis&&(this.axis.destroy(),this.axis=null)},methods:{formatSize(n){return typeof n=="number"?`${n}px`:n},watchSize(){typeof ResizeObserver>"u"||(this.ro=new ResizeObserver(()=>{this.$nextTick(()=>{this.axis&&this.axis.resize()})}),this.ro.observe(this.$refs.TimelineGroup))},initTimeAxis(){this.axis=new T({container:this.$refs.TimelineGroup,backgroundColor:this.backgroundColor,width:this.width,height:this.height,startTime:this.startTime,visibleHours:this.visibleHours,maxVisibleHours:this.effectiveMaxVisibleHours,minTime:this.minTime,maxTime:this.maxTime,snapToNearest:this.snapToNearest,hoverTip:this.effectiveHoverTip,centerPointer:this.centerPointer,centerResumeDelay:this.centerResumeDelay,axisStyle:this.axisStyle,overview:this.overview,overviewHeight:this.overviewHeight,overviewStart:this.overviewStart,overviewEnd:this.overviewEnd,segmentStyle:{background:"rgba(24,208,217,0.5)"},segments:this.segments,changeCallback:n=>{this.$emit("changeTime",n)},playCallback:n=>{this.$emit("playTime",n)}}),console.log(this.axis)},updateAxis(){this.axis&&this.axis.updateDatas({visibleHours:this.visibleHours,startTime:this.startTime,segments:this.segments,minTime:this.minTime,maxTime:this.maxTime,overviewStart:this.overviewStart,overviewEnd:this.overviewEnd})},updateTime(n){this.axis.updateTime(n)},getViewport(){return this.axis?this.axis.getViewport():null},setViewport(n,t){this.axis&&this.axis.setViewport(n,t)},scrollTo(n){this.axis&&this.axis.scrollTo(n)},resumeCenter(){this.axis&&this.axis.resumeCenter()},zoomIn(){this.axis&&this.axis.zoomIn()},zoomOut(){this.axis&&this.axis.zoomOut()},getNextSegment(n){return this.axis?this.axis.getNextSegment(n):null},getNextSegments(n){return this.axis?this.axis.getNextSegments(n):[]},updateOptions(n){this.axis&&this.axis.updateOptions(n)},resizeHandler(){this.axis&&this.axis.resize()}}};function S(n,t,e,i,s,o){return w.openBlock(),w.createElementBlock("div",{class:"timeline-group",ref:"TimelineGroup",style:w.normalizeStyle({width:o.formatSize(e.width),height:o.formatSize(e.height)})},null,4)}const f=y(C,[["render",S],["__scopeId","data-v-b90b2c6f"]]);f.install=n=>{n.component(f.name,f)};exports.TimeAxisPlus=f;exports.default=f;exports.timeAxis=T;
|