vue3-time-playbar 0.1.0

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
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,117 @@
1
+ # vue3-time-playbar
2
+
3
+ 一个 Vue 3 时间播放器组件,包含交互式时间轴、播放控制和刻度标记。零依赖。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install vue3-time-playbar
9
+ ```
10
+
11
+ ## 基本用法
12
+
13
+ ```vue
14
+ <template>
15
+ <GTimePlayer
16
+ v-model="currentTime"
17
+ :start-time="startTime"
18
+ :end-time="endTime"
19
+ @change="onTimeChange"
20
+ />
21
+ </template>
22
+
23
+ <script setup lang="ts">
24
+ import { ref } from 'vue'
25
+ import GTimePlayer from 'vue3-time-playbar'
26
+ import 'vue3-time-playbar/style.css'
27
+ import type { TimeChangePayload } from 'vue3-time-playbar'
28
+
29
+ const startTime = new Date('2025-01-01T00:00:00')
30
+ const endTime = new Date('2025-01-03T00:00:00')
31
+ const currentTime = ref<number>(startTime.getTime())
32
+
33
+ function onTimeChange(payload: TimeChangePayload) {
34
+ console.log('Time:', payload.time, 'Index:', payload.index)
35
+ }
36
+ </script>
37
+ ```
38
+
39
+ > **注意:** 必须导入 CSS 文件,组件才能正确渲染。
40
+
41
+ ## Props
42
+
43
+ | 属性 | 类型 | 默认值 | 说明 |
44
+ |------|------|--------|------|
45
+ | `modelValue` | `number \| string \| Date` | — | 当前时间(v-model) |
46
+ | `startTime` | `number \| string \| Date` | *必填* | 时间轴起始时间 |
47
+ | `endTime` | `number \| string \| Date` | *必填* | 时间轴结束时间 |
48
+ | `interval` | `number` | `1` | 刻度间隔(小时) |
49
+ | `playInterval` | `number` | `1000` | 基准播放间隔(毫秒),即 1x 倍速时每步的时间间隔 |
50
+ | `speed` | `number` | `1` | 默认播放倍率,值必须是 `speedOptions` 中的一项 |
51
+ | `speedOptions` | `number[]` | `[0.5, 1, 2, 3]` | 可选的播放倍率列表,显示在速度选择器中 |
52
+ | `minTickSpacing` | `number` | 自动 | 刻度之间的最小像素间距。未指定时根据 `tickLabelMode` 自动选择:`'all'` 模式为 `15`,`'major'` 模式为 `5` |
53
+ | `tickLabelMode` | `'all' \| 'major'` | `'all'` | 刻度标签显示模式。`'all'`:所有刻度均显示小时数字;`'major'`:仅在 02、08、14、20 时显示数字标签,其余刻度线仍全部保留 |
54
+ | `formatTooltip` | `(ts: number) => string` | 内置 | 自定义 tooltip 格式化函数 |
55
+
56
+ ## 事件
57
+
58
+ | 事件 | 载荷 | 说明 |
59
+ |------|------|------|
60
+ | `update:modelValue` | `number` | 时间变化时触发(v-model) |
61
+ | `change` | `{ time: number, index: number }` | 时间变化时触发,包含时间戳和刻度索引 |
62
+
63
+ ## 交互特性
64
+
65
+ - **点击定位**:点击时间轴任意位置,自动吸附到最近的刻度
66
+ - **轨道拖动**:在时间轴轨道上按住拖动可连续调整时间
67
+ - **Tooltip 拖动**:鼠标悬浮至 Tooltip 时显示拖动图标(grab),按住拖动可直接带动播放位置
68
+ - **日期分界线**:0 点刻度自动显示加长的分界线和日期标签,隐藏多余的 "00" 小时标签
69
+ - **滚轮滚动**:当时间跨度较长产生溢出时,支持滚轮横向滚动浏览
70
+
71
+ ## 插槽
72
+
73
+ | 插槽 | 参数 | 说明 |
74
+ |------|------|------|
75
+ | `prev` | — | 自定义"上一步"按钮内容 |
76
+ | `play` | `{ isPlaying: boolean }` | 自定义"播放/暂停"按钮内容 |
77
+ | `next` | — | 自定义"下一步"按钮内容 |
78
+
79
+ ## 类型导出
80
+
81
+ ```ts
82
+ import type {
83
+ TimeInput,
84
+ TickLabelMode,
85
+ FormatTooltipFn,
86
+ TimeChangePayload,
87
+ Tick,
88
+ } from 'vue3-time-playbar'
89
+ ```
90
+
91
+ ## 工具函数导出
92
+
93
+ 包中还导出了一些实用的工具函数:
94
+
95
+ ```ts
96
+ import {
97
+ toTimestamp,
98
+ getHour,
99
+ getDayOfWeek,
100
+ getWeekdayShort,
101
+ formatHH,
102
+ formatMD,
103
+ formatHHmm,
104
+ defaultTooltipFormat,
105
+ getDateKey,
106
+ formatDayLabel,
107
+ addHours,
108
+ diffHours,
109
+ getNextMidnight,
110
+ findNearestTickIndex,
111
+ findTickIndex,
112
+ } from 'vue3-time-playbar'
113
+ ```
114
+
115
+ ## 许可证
116
+
117
+ MIT
@@ -0,0 +1,32 @@
1
+ type __VLS_Props = {
2
+ isPlaying: boolean;
3
+ };
4
+ declare function __VLS_template(): {
5
+ attrs: Partial<{}>;
6
+ slots: {
7
+ prev?(_: {}): any;
8
+ play?(_: {
9
+ isPlaying: boolean;
10
+ }): any;
11
+ next?(_: {}): any;
12
+ };
13
+ refs: {};
14
+ rootEl: HTMLDivElement;
15
+ };
16
+ type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
17
+ declare const __VLS_component: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
18
+ "toggle-play": (...args: any[]) => void;
19
+ prev: (...args: any[]) => void;
20
+ next: (...args: any[]) => void;
21
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
22
+ "onToggle-play"?: ((...args: any[]) => any) | undefined;
23
+ onPrev?: ((...args: any[]) => any) | undefined;
24
+ onNext?: ((...args: any[]) => any) | undefined;
25
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>;
26
+ declare const _default: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
27
+ export default _default;
28
+ type __VLS_WithTemplateSlots<T, S> = T & {
29
+ new (): {
30
+ $slots: S;
31
+ };
32
+ };
@@ -0,0 +1,12 @@
1
+ type __VLS_Props = {
2
+ modelValue: number;
3
+ options: number[];
4
+ };
5
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
6
+ "update:modelValue": (value: number) => any;
7
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
8
+ "onUpdate:modelValue"?: ((value: number) => any) | undefined;
9
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
10
+ selectorRef: HTMLDivElement;
11
+ }, HTMLDivElement>;
12
+ export default _default;
@@ -0,0 +1,26 @@
1
+ import { TickLabelMode, FormatTooltipFn } from './types';
2
+ type __VLS_Props = {
3
+ modelValue: number | string;
4
+ startTime: number | string;
5
+ endTime: number | string;
6
+ interval: number;
7
+ minTickSpacing?: number;
8
+ tickLabelMode?: TickLabelMode;
9
+ formatTooltip?: FormatTooltipFn;
10
+ };
11
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
12
+ "update:modelValue": (value: number) => any;
13
+ change: (value: number, index: number) => any;
14
+ "playing-pause": () => any;
15
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
16
+ "onUpdate:modelValue"?: ((value: number) => any) | undefined;
17
+ onChange?: ((value: number, index: number) => any) | undefined;
18
+ "onPlaying-pause"?: (() => any) | undefined;
19
+ }>, {
20
+ tickLabelMode: TickLabelMode;
21
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
22
+ timelineRef: HTMLDivElement;
23
+ scrollRef: HTMLDivElement;
24
+ innerRef: HTMLDivElement;
25
+ }, HTMLDivElement>;
26
+ export default _default;
@@ -0,0 +1,49 @@
1
+ import { TimeInput, TickLabelMode, FormatTooltipFn, TimeChangePayload } from './types';
2
+ interface Props {
3
+ modelValue?: TimeInput;
4
+ startTime: TimeInput;
5
+ endTime: TimeInput;
6
+ interval?: number;
7
+ /** 基准播放间隔(毫秒),1x 倍速时每步的时间间隔 */
8
+ playInterval?: number;
9
+ /** 默认播放倍率,必须是 speedOptions 中的值 */
10
+ speed?: number;
11
+ /** 可选的倍率列表 */
12
+ speedOptions?: number[];
13
+ minTickSpacing?: number;
14
+ tickLabelMode?: TickLabelMode;
15
+ formatTooltip?: FormatTooltipFn;
16
+ }
17
+ declare function __VLS_template(): {
18
+ attrs: Partial<{}>;
19
+ slots: {
20
+ prev?(_: {}): any;
21
+ play?(_: {
22
+ isPlaying: boolean;
23
+ }): any;
24
+ next?(_: {}): any;
25
+ };
26
+ refs: {};
27
+ rootEl: HTMLDivElement;
28
+ };
29
+ type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
30
+ declare const __VLS_component: import('vue').DefineComponent<Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
31
+ "update:modelValue": (value: number) => any;
32
+ change: (payload: TimeChangePayload) => any;
33
+ }, string, import('vue').PublicProps, Readonly<Props> & Readonly<{
34
+ "onUpdate:modelValue"?: ((value: number) => any) | undefined;
35
+ onChange?: ((payload: TimeChangePayload) => any) | undefined;
36
+ }>, {
37
+ interval: number;
38
+ tickLabelMode: TickLabelMode;
39
+ playInterval: number;
40
+ speed: number;
41
+ speedOptions: number[];
42
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>;
43
+ declare const _default: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
44
+ export default _default;
45
+ type __VLS_WithTemplateSlots<T, S> = T & {
46
+ new (): {
47
+ $slots: S;
48
+ };
49
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * GTimePlayer 公共类型定义
3
+ */
4
+ /** 时间输入类型:支持时间戳、ISO字符串、Date */
5
+ export type TimeInput = number | string | Date;
6
+ /** 刻度文字显示密度 */
7
+ export type TickLabelMode = 'all' | 'major';
8
+ /** tooltip 格式化函数 */
9
+ export type FormatTooltipFn = (timestamp: number) => string;
10
+ /** change 事件回调参数 */
11
+ export interface TimeChangePayload {
12
+ /** 当前时间戳(ms) */
13
+ time: number;
14
+ /** 当前 tick 索引(从 0 开始) */
15
+ index: number;
16
+ }
17
+ /** 内部 Tick 数据结构 */
18
+ export interface Tick {
19
+ /** 时间戳 */
20
+ time: number;
21
+ /** 在时间轴中的百分比位置 */
22
+ percent: number;
23
+ /** 小时文字(HH) */
24
+ label: string;
25
+ /** 是否显示小时文字 */
26
+ showLabel: boolean;
27
+ /** 日期标签(M月D日 周X) */
28
+ dayLabel: string;
29
+ /** 是否显示日期标签 */
30
+ showDayLabel: boolean;
31
+ /** 是否为日分界刻度 */
32
+ isDayBoundary: boolean;
33
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * GTimePlayer 时间工具函数
3
+ * 纯原生实现,不依赖任何第三方库
4
+ */
5
+ /** 将任意时间输入(时间戳 / ISO字符串 / Date)转为毫秒时间戳 */
6
+ export declare function toTimestamp(value: number | string | Date): number;
7
+ /** 获取小时数(0-23) */
8
+ export declare function getHour(ts: number): number;
9
+ /** 获取星期几(0=周日) */
10
+ export declare function getDayOfWeek(ts: number): number;
11
+ /** 获取短星期名 */
12
+ export declare function getWeekdayShort(ts: number): string;
13
+ /** 格式化为 HH(两位小时) */
14
+ export declare function formatHH(ts: number): string;
15
+ /** 格式化为 M月D日 */
16
+ export declare function formatMD(ts: number): string;
17
+ /** 格式化为 HH:mm */
18
+ export declare function formatHHmm(ts: number): string;
19
+ /** 默认 tooltip 格式化:M月D日 周X HH:mm */
20
+ export declare function defaultTooltipFormat(ts: number): string;
21
+ /** 获取日期标识字符串 YYYYMMDD(用于比较是否同一天) */
22
+ export declare function getDateKey(ts: number): string;
23
+ /** 日期标签:M月D日 周X */
24
+ export declare function formatDayLabel(ts: number): string;
25
+ /** 加小时(返回新时间戳) */
26
+ export declare function addHours(ts: number, hours: number): number;
27
+ /** 两个时间戳之间的小时差 */
28
+ export declare function diffHours(start: number | string | Date, end: number | string | Date): number;
29
+ /** 获取下一个午夜(当天24:00 = 次日00:00)的时间戳 */
30
+ export declare function getNextMidnight(ts: number): number;
31
+ /**
32
+ * 在 ticks 数组中找到最接近 target 的 tick index
33
+ * 使用二分查找,O(log n)
34
+ */
35
+ export declare function findNearestTickIndex(tickTimes: number[], target: number): number;
36
+ /**
37
+ * 根据时间戳找到对应的 tick index(精确匹配)
38
+ * 找不到返回 -1
39
+ */
40
+ export declare function findTickIndex(tickTimes: number[], target: number): number;
package/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { default as GTimePlayer } from './core/index.vue';
2
+ export default GTimePlayer;
3
+ export { GTimePlayer };
4
+ export type { TimeInput, TickLabelMode, FormatTooltipFn, TimeChangePayload, Tick, } from './core/types';
5
+ export { toTimestamp, getHour, getDayOfWeek, getWeekdayShort, formatHH, formatMD, formatHHmm, defaultTooltipFormat, getDateKey, formatDayLabel, addHours, diffHours, getNextMidnight, findNearestTickIndex, findTickIndex, } from './core/utils';
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "vue3-time-playbar",
3
+ "version": "0.1.0",
4
+ "description": "A Vue 3 time player component with interactive timeline, playback controls and tick marks",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "keywords": [
8
+ "vue3",
9
+ "vue",
10
+ "time",
11
+ "timeline",
12
+ "playbar",
13
+ "player",
14
+ "component"
15
+ ],
16
+ "peerDependencies": {
17
+ "vue": "^3.3.0"
18
+ },
19
+ "main": "./vue3-time-playbar.umd.cjs",
20
+ "module": "./vue3-time-playbar.mjs",
21
+ "types": "./index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./index.d.ts",
25
+ "import": "./vue3-time-playbar.mjs",
26
+ "require": "./vue3-time-playbar.umd.cjs"
27
+ },
28
+ "./style.css": "./vue3-time-playbar.css"
29
+ },
30
+ "sideEffects": [
31
+ "**/*.css"
32
+ ],
33
+ "files": [
34
+ "**/*"
35
+ ]
36
+ }
@@ -0,0 +1 @@
1
+ .player-controls[data-v-55e12a8d]{display:flex;align-items:center}.player-controls .btn-group[data-v-55e12a8d]{display:flex;align-items:center;gap:6px}.player-controls .control-btn[data-v-55e12a8d]{background:#ffffff1a;border:1px solid rgba(255,255,255,.15);border-radius:50%;width:32px;height:32px;display:flex;align-items:center;justify-content:center;color:#fffc;cursor:pointer;transition:background .2s,border-color .2s,transform .15s}.player-controls .control-btn[data-v-55e12a8d]:hover{background:#fff3;border-color:#fff6;color:#fff;transform:scale(1.08)}.player-controls .control-btn[data-v-55e12a8d]:active{transform:scale(.95)}.player-controls .control-btn svg[data-v-55e12a8d]{width:14px;height:14px}.player-controls .control-btn.play-btn[data-v-55e12a8d]{width:40px;height:40px;background:#409eff;border-color:transparent;color:#fff}.player-controls .control-btn.play-btn[data-v-55e12a8d]:hover{background:#66b1ff}.player-controls .control-btn.play-btn svg[data-v-55e12a8d]{width:16px;height:16px}.time-timeline[data-v-2c40052b]{position:relative;width:100%;height:52px;cursor:pointer}.time-timeline .handle-group[data-v-2c40052b]{position:absolute;left:0;top:0;width:0;height:100%;z-index:20;pointer-events:none;transition:transform .2s ease-out;will-change:transform}.time-timeline .handle-group.dragging[data-v-2c40052b]{transition:none}.time-timeline .handle-group .floating-tooltip[data-v-2c40052b]{position:absolute;top:-33px;left:0;transform:translate(-50%);background:#409eff;color:#fff;padding:5px 10px;border-radius:50px;font-size:14px;font-weight:500;white-space:nowrap;box-shadow:0 2px 6px #0000004d;pointer-events:auto;cursor:grab}.time-timeline .handle-group .floating-tooltip[data-v-2c40052b]:active{cursor:grabbing}.time-timeline .handle-group .floating-tooltip[data-v-2c40052b]:after{content:"";position:absolute;top:100%;left:50%;transform:translate(-50%);border:4px solid transparent;border-top-color:#409eff}.time-timeline .handle-group .handle-visual[data-v-2c40052b]{position:absolute;top:10px;left:0;transform:translate(-50%,-50%);width:8px;height:8px;background:#fff;border-radius:50%;border:2px solid #409eff;box-shadow:0 0 0 3px #409eff40}.time-timeline .scroll-container[data-v-2c40052b]{width:100%;height:100%;overflow-x:auto;overflow-y:hidden;scrollbar-width:none}.time-timeline .scroll-container[data-v-2c40052b]::-webkit-scrollbar{display:none}.time-timeline .timeline-inner[data-v-2c40052b]{position:relative;min-width:100%;height:100%}.time-timeline .track-bg[data-v-2c40052b]{position:absolute;top:8px;height:4px;background:#ffffff2e;border-radius:2px}.time-timeline .track-active[data-v-2c40052b]{position:absolute;top:8px;height:4px;background:#409eff;border-radius:2px;pointer-events:none;transition:width .15s ease-out;z-index:2}.time-timeline .ticks-container[data-v-2c40052b]{position:absolute;top:0;bottom:0;pointer-events:none}.time-timeline .tick-item[data-v-2c40052b]{position:absolute;top:0;transform:translate(-50%);display:flex;flex-direction:column;align-items:center}.time-timeline .tick-item .tick-mark[data-v-2c40052b]{margin-top:14px;width:1px;height:5px;background:#ffffff59;flex-shrink:0}.time-timeline .tick-item .tick-label[data-v-2c40052b]{margin-top:2px;font-size:11px;color:#fff9;line-height:1}.time-timeline .tick-item .day-label[data-v-2c40052b]{margin-top:3px;font-size:11px;font-weight:600;color:#ffffffe6;white-space:nowrap;line-height:1}.time-timeline .tick-item.is-day-boundary .tick-mark[data-v-2c40052b]{margin-top:14px;width:2px;height:18px;background:#fffc}.speed-selector[data-v-e4832d4c]{flex-shrink:0;margin-left:16px;padding-left:16px;border-left:1px solid rgba(255,255,255,.12);position:relative}.speed-selector .speed-trigger[data-v-e4832d4c]{display:inline-flex;align-items:center;gap:4px;min-width:36px;height:28px;padding:0 8px;background:#ffffff1a;border:1px solid rgba(255,255,255,.15);border-radius:4px;color:#fffc;font-size:13px;font-weight:500;cursor:pointer;transition:background .2s,border-color .2s,color .2s}.speed-selector .speed-trigger[data-v-e4832d4c]:hover{background:#fff3;border-color:#fff6;color:#fff}.speed-selector .speed-trigger .arrow-icon[data-v-e4832d4c]{width:10px;height:10px;transition:transform .2s}.speed-selector .speed-trigger .arrow-icon.open[data-v-e4832d4c]{transform:rotate(180deg)}.speed-selector .speed-dropdown[data-v-e4832d4c]{position:absolute;left:16px;min-width:56px;background:#083082f2;border:1px solid rgba(255,255,255,.2);border-radius:4px;padding:4px 0;box-shadow:0 4px 12px #0006;z-index:30}.speed-selector .speed-dropdown.is-up[data-v-e4832d4c]{bottom:calc(100% + 6px)}.speed-selector .speed-dropdown.is-down[data-v-e4832d4c]{top:calc(100% + 6px)}.speed-selector .speed-dropdown .speed-option[data-v-e4832d4c]{padding:6px 12px;font-size:13px;color:#ffffffb3;cursor:pointer;text-align:center;transition:background .15s,color .15s}.speed-selector .speed-dropdown .speed-option[data-v-e4832d4c]:hover{background:#ffffff26;color:#fff}.speed-selector .speed-dropdown .speed-option.active[data-v-e4832d4c]{color:#409eff;font-weight:600}.dropdown-up-enter-active[data-v-e4832d4c],.dropdown-up-leave-active[data-v-e4832d4c]{transition:opacity .15s,transform .15s}.dropdown-up-enter-from[data-v-e4832d4c],.dropdown-up-leave-to[data-v-e4832d4c]{opacity:0;transform:translateY(4px)}.dropdown-down-enter-active[data-v-e4832d4c],.dropdown-down-leave-active[data-v-e4832d4c]{transition:opacity .15s,transform .15s}.dropdown-down-enter-from[data-v-e4832d4c],.dropdown-down-leave-to[data-v-e4832d4c]{opacity:0;transform:translateY(-4px)}.g-time-player[data-v-12c09d1e]{display:flex;align-items:center;width:100%;height:72px;filter:drop-shadow(rgba(0,0,0,.8) 0px 0px 2px);background:#083082aa;box-shadow:0 0 16px #0193fa99 inset;border-radius:4px;padding:0 24px;box-sizing:border-box;color:#fff;-webkit-user-select:none;user-select:none;box-shadow:0 4px 20px #00000059}.g-time-player .controls-wrapper[data-v-12c09d1e]{flex-shrink:0;margin-right:20px;border-right:1px solid rgba(255,255,255,.12);padding-right:20px}.g-time-player .timeline-wrapper[data-v-12c09d1e]{flex:1;min-width:0;height:100%;display:flex;align-items:center;overflow:visible;position:relative}
@@ -0,0 +1,590 @@
1
+ import { defineComponent as Z, openBlock as T, createElementBlock as x, createElementVNode as u, renderSlot as W, ref as $, computed as f, watch as P, onMounted as me, nextTick as re, onUnmounted as de, withDirectives as Pe, normalizeStyle as N, normalizeClass as j, withModifiers as ue, toDisplayString as z, vShow as Ee, Fragment as pe, renderList as ge, createCommentVNode as ce, createVNode as J, Transition as Ie, withCtx as Q, createSlots as Re } from "vue";
2
+ const _e = { class: "player-controls" }, Be = { class: "btn-group" }, He = ["title"], Ne = {
3
+ key: 0,
4
+ viewBox: "0 0 24 24",
5
+ fill: "currentColor",
6
+ stroke: "none"
7
+ }, Ae = {
8
+ key: 1,
9
+ viewBox: "0 0 24 24",
10
+ fill: "currentColor",
11
+ stroke: "none"
12
+ }, We = /* @__PURE__ */ Z({
13
+ __name: "PlayerControls",
14
+ props: {
15
+ isPlaying: { type: Boolean }
16
+ },
17
+ emits: ["toggle-play", "prev", "next"],
18
+ setup(e) {
19
+ return (o, n) => (T(), x("div", _e, [
20
+ u("div", Be, [
21
+ u("button", {
22
+ class: "control-btn",
23
+ onClick: n[0] || (n[0] = (t) => o.$emit("prev")),
24
+ title: "上一时刻"
25
+ }, [
26
+ W(o.$slots, "prev", {}, () => [
27
+ n[3] || (n[3] = u("svg", {
28
+ viewBox: "0 0 24 24",
29
+ fill: "none",
30
+ stroke: "currentColor",
31
+ "stroke-width": "2",
32
+ "stroke-linecap": "round",
33
+ "stroke-linejoin": "round"
34
+ }, [
35
+ u("polygon", { points: "19 20 9 12 19 4 19 20" }),
36
+ u("line", {
37
+ x1: "5",
38
+ y1: "19",
39
+ x2: "5",
40
+ y2: "5"
41
+ })
42
+ ], -1))
43
+ ], !0)
44
+ ]),
45
+ u("button", {
46
+ class: "control-btn play-btn",
47
+ onClick: n[1] || (n[1] = (t) => o.$emit("toggle-play")),
48
+ title: e.isPlaying ? "暂停" : "播放"
49
+ }, [
50
+ W(o.$slots, "play", { isPlaying: e.isPlaying }, () => [
51
+ e.isPlaying ? (T(), x("svg", Ae, [...n[5] || (n[5] = [
52
+ u("rect", {
53
+ x: "6",
54
+ y: "4",
55
+ width: "4",
56
+ height: "16",
57
+ rx: "1"
58
+ }, null, -1),
59
+ u("rect", {
60
+ x: "14",
61
+ y: "4",
62
+ width: "4",
63
+ height: "16",
64
+ rx: "1"
65
+ }, null, -1)
66
+ ])])) : (T(), x("svg", Ne, [...n[4] || (n[4] = [
67
+ u("polygon", { points: "5 3 19 12 5 21 5 3" }, null, -1)
68
+ ])]))
69
+ ], !0)
70
+ ], 8, He),
71
+ u("button", {
72
+ class: "control-btn",
73
+ onClick: n[2] || (n[2] = (t) => o.$emit("next")),
74
+ title: "下一时刻"
75
+ }, [
76
+ W(o.$slots, "next", {}, () => [
77
+ n[6] || (n[6] = u("svg", {
78
+ viewBox: "0 0 24 24",
79
+ fill: "none",
80
+ stroke: "currentColor",
81
+ "stroke-width": "2",
82
+ "stroke-linecap": "round",
83
+ "stroke-linejoin": "round"
84
+ }, [
85
+ u("polygon", { points: "5 4 15 12 5 20 5 4" }),
86
+ u("line", {
87
+ x1: "19",
88
+ y1: "5",
89
+ x2: "19",
90
+ y2: "19"
91
+ })
92
+ ], -1))
93
+ ], !0)
94
+ ])
95
+ ])
96
+ ]));
97
+ }
98
+ }), ee = (e, o) => {
99
+ const n = e.__vccOpts || e;
100
+ for (const [t, s] of o)
101
+ n[t] = s;
102
+ return n;
103
+ }, Fe = /* @__PURE__ */ ee(We, [["__scopeId", "data-v-55e12a8d"]]), Xe = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
104
+ function h(e) {
105
+ if (typeof e == "number") return e;
106
+ if (e instanceof Date) return e.getTime();
107
+ const o = Date.parse(e);
108
+ return Number.isNaN(o) ? (console.warn(`[GTimePlayer] 无法解析时间: "${e}"`), 0) : o;
109
+ }
110
+ function K(e, o = 2) {
111
+ return String(e).padStart(o, "0");
112
+ }
113
+ function Ge(e) {
114
+ return new Date(e).getHours();
115
+ }
116
+ function Oe(e) {
117
+ return new Date(e).getDay();
118
+ }
119
+ function ye(e) {
120
+ return Xe[Oe(e)] ?? "";
121
+ }
122
+ function Ue(e) {
123
+ return K(new Date(e).getHours());
124
+ }
125
+ function he(e) {
126
+ const o = new Date(e);
127
+ return `${o.getMonth() + 1}月${o.getDate()}日`;
128
+ }
129
+ function je(e) {
130
+ const o = new Date(e);
131
+ return `${K(o.getHours())}:${K(o.getMinutes())}`;
132
+ }
133
+ function ze(e) {
134
+ return `${he(e)} ${ye(e)} ${je(e)}`;
135
+ }
136
+ function fe(e) {
137
+ const o = new Date(e);
138
+ return `${o.getFullYear()}${K(o.getMonth() + 1)}${K(o.getDate())}`;
139
+ }
140
+ function Ke(e) {
141
+ return `${he(e)} ${ye(e)}`;
142
+ }
143
+ function A(e, o) {
144
+ return e + o * 36e5;
145
+ }
146
+ function ft(e, o) {
147
+ return (h(o) - h(e)) / 36e5;
148
+ }
149
+ function Ye(e) {
150
+ const o = new Date(e);
151
+ return o.setHours(24, 0, 0, 0), o.getTime();
152
+ }
153
+ function qe(e, o) {
154
+ if (e.length === 0) return -1;
155
+ if (e.length === 1) return 0;
156
+ let n = 0, t = e.length - 1;
157
+ if (o <= e[n]) return n;
158
+ if (o >= e[t]) return t;
159
+ for (; n <= t; ) {
160
+ const s = n + t >>> 1, d = e[s];
161
+ if (d === o) return s;
162
+ d < o ? n = s + 1 : t = s - 1;
163
+ }
164
+ return n >= e.length ? t : t < 0 || Math.abs(e[n] - o) < Math.abs(e[t] - o) ? n : t;
165
+ }
166
+ function Je(e, o) {
167
+ let n = 0, t = e.length - 1;
168
+ for (; n <= t; ) {
169
+ const s = n + t >>> 1, d = e[s];
170
+ if (d === o) return s;
171
+ d < o ? n = s + 1 : t = s - 1;
172
+ }
173
+ return -1;
174
+ }
175
+ const Qe = {
176
+ key: 0,
177
+ class: "tick-label"
178
+ }, Ze = {
179
+ key: 1,
180
+ class: "day-label"
181
+ }, et = 50, tt = 40, nt = /* @__PURE__ */ Z({
182
+ __name: "TimeTimeline",
183
+ props: {
184
+ modelValue: {},
185
+ startTime: {},
186
+ endTime: {},
187
+ interval: {},
188
+ minTickSpacing: {},
189
+ tickLabelMode: { default: "all" },
190
+ formatTooltip: {}
191
+ },
192
+ emits: ["update:modelValue", "change", "playing-pause"],
193
+ setup(e, { emit: o }) {
194
+ const n = [2, 8, 14, 20], t = e, s = o, d = $(null), p = $(null), F = $(null), w = $(0), v = $(0), g = $(0);
195
+ let k = !1, c = 0, E = !1, C = !1, I = null;
196
+ const X = $(!1), G = f(() => {
197
+ const a = h(t.startTime), r = h(t.endTime), i = r - a;
198
+ if (i <= 0) return [];
199
+ const b = [];
200
+ let m = a;
201
+ for (; m <= r; ) {
202
+ const M = (m - a) / i * 100, D = Ge(m), V = b.length === 0, ae = fe(m), ie = V ? null : fe(b[b.length - 1].time), q = !V && ie !== ae || V && D === 0;
203
+ let B = !1;
204
+ if (D === 0)
205
+ B = !0;
206
+ else if (V) {
207
+ const H = Ye(m);
208
+ (A(m, t.interval) > H || r < H) && (B = !0);
209
+ }
210
+ const se = D === 0 && B ? !1 : t.tickLabelMode === "all" || n.includes(D);
211
+ b.push({
212
+ time: m,
213
+ percent: M,
214
+ label: Ue(m),
215
+ showLabel: se,
216
+ dayLabel: Ke(m),
217
+ showDayLabel: B,
218
+ isDayBoundary: q
219
+ }), m = A(m, t.interval);
220
+ }
221
+ return b;
222
+ }), te = f(() => G.value.map((a) => a.time)), S = f(() => t.minTickSpacing !== void 0 ? t.minTickSpacing : t.tickLabelMode === "major" ? 5 : 15), O = f(() => {
223
+ const a = G.value.length;
224
+ return a < 2 ? w.value || 0 : (a - 1) * S.value;
225
+ }), R = f(() => O.value > w.value && w.value > 0), L = f(() => R.value ? et : tt), l = f(() => R.value ? O.value : Math.max(0, w.value - L.value * 2)), y = f(() => l.value + L.value * 2), _ = f(() => w.value && R.value ? y.value + "px" : "100%"), ke = f(() => ({
226
+ left: L.value + "px",
227
+ width: l.value + "px"
228
+ })), we = f(() => ({
229
+ left: L.value + "px",
230
+ width: l.value + "px"
231
+ })), Y = f(() => {
232
+ const a = h(t.startTime), r = h(t.endTime), i = h(t.modelValue);
233
+ return i <= a ? 0 : i >= r ? 100 : (i - a) / (r - a) * 100;
234
+ }), Te = f(() => ({
235
+ left: L.value + "px",
236
+ width: Y.value / 100 * l.value + "px"
237
+ })), xe = f(() => {
238
+ const a = h(t.modelValue);
239
+ return t.formatTooltip ? t.formatTooltip(a) : ze(a);
240
+ }), ne = f(() => L.value + Y.value / 100 * l.value - g.value), be = f(() => ne.value > -50 && ne.value < w.value + 50), $e = f(() => ({
241
+ transform: `translateX(${ne.value}px)`
242
+ }));
243
+ function Le() {
244
+ p.value && (v.value = p.value.scrollLeft, E || (g.value = p.value.scrollLeft));
245
+ }
246
+ function De(a) {
247
+ !p.value || !R.value || (p.value.scrollLeft += a.deltaY || a.deltaX);
248
+ }
249
+ function U(a = !1) {
250
+ if (!R.value || !p.value || k) return;
251
+ const r = L.value + Y.value / 100 * l.value, i = w.value, b = y.value - i, m = Math.max(0, Math.min(r - i / 2, b));
252
+ g.value = m;
253
+ const M = p.value;
254
+ if (a || Math.abs(M.scrollLeft - m) < 1) {
255
+ M.scrollLeft = m, v.value = m;
256
+ return;
257
+ }
258
+ c && cancelAnimationFrame(c);
259
+ const D = M.scrollLeft, V = m - D;
260
+ E = !0;
261
+ const ae = 200, ie = performance.now();
262
+ function q(B) {
263
+ const se = B - ie, H = Math.min(se / ae, 1), ve = 1 - (1 - H) * (1 - H);
264
+ M.scrollLeft = D + V * ve, v.value = M.scrollLeft, H < 1 ? c = requestAnimationFrame(q) : (c = 0, E = !1);
265
+ }
266
+ c = requestAnimationFrame(q);
267
+ }
268
+ P(Y, () => U()), P(w, () => U());
269
+ function le(a) {
270
+ if (!p.value) return h(t.startTime);
271
+ const r = p.value.getBoundingClientRect(), i = p.value.scrollLeft, m = a.clientX - r.left + i - L.value, M = Math.max(0, Math.min(m, l.value)), D = h(t.startTime), V = h(t.endTime);
272
+ return D + (V - D) * (M / (l.value || 1));
273
+ }
274
+ function Se(a) {
275
+ const r = te.value;
276
+ if (r.length === 0) return { time: a, index: -1 };
277
+ const i = qe(r, a);
278
+ return { time: r[i] ?? a, index: i };
279
+ }
280
+ function oe(a) {
281
+ const { time: r, index: i } = Se(a);
282
+ r !== h(t.modelValue) && (s("update:modelValue", r), s("change", r, i));
283
+ }
284
+ function Me(a) {
285
+ if (C) {
286
+ C = !1;
287
+ return;
288
+ }
289
+ oe(le(a));
290
+ }
291
+ function Ce(a) {
292
+ const r = a.clientX;
293
+ C = !1, k = !0, s("playing-pause");
294
+ const i = (m) => {
295
+ Math.abs(m.clientX - r) > 3 && (C = !0), oe(le(m));
296
+ }, b = () => {
297
+ k = !1, document.removeEventListener("mousemove", i), document.removeEventListener("mouseup", b), re(() => U());
298
+ };
299
+ document.addEventListener("mousemove", i), document.addEventListener("mouseup", b);
300
+ }
301
+ function Ve(a) {
302
+ k = !0, C = !1, X.value = !0, document.body.style.cursor = "grabbing", s("playing-pause");
303
+ const r = (b) => {
304
+ C = !0, oe(le(b));
305
+ }, i = () => {
306
+ k = !1, X.value = !1, document.body.style.cursor = "", document.removeEventListener("mousemove", r), document.removeEventListener("mouseup", i), re(() => U());
307
+ };
308
+ document.addEventListener("mousemove", r), document.addEventListener("mouseup", i);
309
+ }
310
+ return me(() => {
311
+ p.value && (w.value = p.value.clientWidth, I = new ResizeObserver((a) => {
312
+ a[0] && (w.value = a[0].contentRect.width);
313
+ }), I.observe(p.value)), re(() => U());
314
+ }), de(() => {
315
+ c && cancelAnimationFrame(c), I == null || I.disconnect();
316
+ }), (a, r) => (T(), x("div", {
317
+ class: "time-timeline",
318
+ ref_key: "timelineRef",
319
+ ref: d
320
+ }, [
321
+ Pe(u("div", {
322
+ class: j(["handle-group", { dragging: X.value }]),
323
+ style: N($e.value)
324
+ }, [
325
+ u("div", {
326
+ class: "floating-tooltip",
327
+ onMousedown: ue(Ve, ["prevent", "stop"])
328
+ }, z(xe.value), 33),
329
+ r[0] || (r[0] = u("div", { class: "handle-visual" }, null, -1))
330
+ ], 6), [
331
+ [Ee, be.value]
332
+ ]),
333
+ u("div", {
334
+ class: "scroll-container",
335
+ ref_key: "scrollRef",
336
+ ref: p,
337
+ onScroll: Le,
338
+ onWheel: ue(De, ["prevent"])
339
+ }, [
340
+ u("div", {
341
+ class: "timeline-inner",
342
+ ref_key: "innerRef",
343
+ ref: F,
344
+ style: N({ width: _.value }),
345
+ onMousedown: ue(Ce, ["prevent"]),
346
+ onClick: Me
347
+ }, [
348
+ u("div", {
349
+ class: "track-bg",
350
+ style: N(ke.value)
351
+ }, null, 4),
352
+ u("div", {
353
+ class: "track-active",
354
+ style: N(Te.value)
355
+ }, null, 4),
356
+ u("div", {
357
+ class: "ticks-container",
358
+ style: N(we.value)
359
+ }, [
360
+ (T(!0), x(pe, null, ge(G.value, (i) => (T(), x("div", {
361
+ key: i.time,
362
+ class: j(["tick-item", { "is-day-boundary": i.isDayBoundary }]),
363
+ style: N({ left: i.percent + "%" })
364
+ }, [
365
+ r[1] || (r[1] = u("div", { class: "tick-mark" }, null, -1)),
366
+ i.showLabel ? (T(), x("div", Qe, z(i.label), 1)) : ce("", !0),
367
+ i.showDayLabel ? (T(), x("div", Ze, z(i.dayLabel), 1)) : ce("", !0)
368
+ ], 6))), 128))
369
+ ], 4)
370
+ ], 36)
371
+ ], 544)
372
+ ], 512));
373
+ }
374
+ }), lt = /* @__PURE__ */ ee(nt, [["__scopeId", "data-v-2c40052b"]]), ot = { class: "speed-value" }, at = ["onClick"], it = /* @__PURE__ */ Z({
375
+ __name: "SpeedSelector",
376
+ props: {
377
+ modelValue: {},
378
+ options: {}
379
+ },
380
+ emits: ["update:modelValue"],
381
+ setup(e, { emit: o }) {
382
+ const n = o, t = $(!1), s = $(!0), d = $(null);
383
+ function p() {
384
+ if (!d.value) return;
385
+ const g = d.value.getBoundingClientRect(), k = g.top, c = window.innerHeight - g.bottom;
386
+ s.value = k >= c;
387
+ }
388
+ function F() {
389
+ t.value || p(), t.value = !t.value;
390
+ }
391
+ function w(g) {
392
+ n("update:modelValue", g), t.value = !1;
393
+ }
394
+ function v(g) {
395
+ d.value && !d.value.contains(g.target) && (t.value = !1);
396
+ }
397
+ return me(() => document.addEventListener("mousedown", v)), de(() => document.removeEventListener("mousedown", v)), (g, k) => (T(), x("div", {
398
+ class: "speed-selector",
399
+ ref_key: "selectorRef",
400
+ ref: d
401
+ }, [
402
+ u("div", {
403
+ class: "speed-trigger",
404
+ onClick: F
405
+ }, [
406
+ u("span", ot, z(e.modelValue) + "x", 1),
407
+ (T(), x("svg", {
408
+ class: j(["arrow-icon", { open: t.value }]),
409
+ viewBox: "0 0 12 12",
410
+ fill: "currentColor"
411
+ }, [...k[0] || (k[0] = [
412
+ u("path", {
413
+ d: "M2.5 4.5L6 8L9.5 4.5",
414
+ stroke: "currentColor",
415
+ "stroke-width": "1.5",
416
+ fill: "none",
417
+ "stroke-linecap": "round",
418
+ "stroke-linejoin": "round"
419
+ }, null, -1)
420
+ ])], 2))
421
+ ]),
422
+ J(Ie, {
423
+ name: s.value ? "dropdown-up" : "dropdown-down"
424
+ }, {
425
+ default: Q(() => [
426
+ t.value ? (T(), x("div", {
427
+ key: 0,
428
+ class: j(["speed-dropdown", s.value ? "is-up" : "is-down"])
429
+ }, [
430
+ (T(!0), x(pe, null, ge(e.options, (c) => (T(), x("div", {
431
+ key: c,
432
+ class: j(["speed-option", { active: c === e.modelValue }]),
433
+ onClick: (E) => w(c)
434
+ }, z(c) + "x ", 11, at))), 128))
435
+ ], 2)) : ce("", !0)
436
+ ]),
437
+ _: 1
438
+ }, 8, ["name"])
439
+ ], 512));
440
+ }
441
+ }), st = /* @__PURE__ */ ee(it, [["__scopeId", "data-v-e4832d4c"]]), rt = { class: "g-time-player" }, ut = { class: "controls-wrapper" }, ct = { class: "timeline-wrapper" }, dt = /* @__PURE__ */ Z({
442
+ __name: "index",
443
+ props: {
444
+ modelValue: {},
445
+ startTime: {},
446
+ endTime: {},
447
+ interval: { default: 1 },
448
+ playInterval: { default: 1e3 },
449
+ speed: { default: 1 },
450
+ speedOptions: { default: () => [0.5, 1, 2, 3] },
451
+ minTickSpacing: {},
452
+ tickLabelMode: { default: "all" },
453
+ formatTooltip: {}
454
+ },
455
+ emits: ["update:modelValue", "change"],
456
+ setup(e, { emit: o }) {
457
+ const n = e, t = o, s = f(() => h(n.startTime)), d = f(() => h(n.endTime)), p = f(() => s.value), F = f(() => d.value), w = f(() => {
458
+ const l = [];
459
+ let y = s.value;
460
+ for (; y <= d.value; )
461
+ l.push(y), y = A(y, n.interval);
462
+ return l;
463
+ }), v = $(h(n.modelValue ?? n.startTime)), g = $(!1), k = $(n.speed);
464
+ let c = null;
465
+ const E = f(() => n.playInterval / k.value);
466
+ P(() => n.speed, (l) => {
467
+ k.value = l;
468
+ });
469
+ function C(l) {
470
+ const y = Je(w.value, l);
471
+ return y >= 0 ? y : 0;
472
+ }
473
+ function I(l, y) {
474
+ t("update:modelValue", l), t("change", { time: l, index: C(l) });
475
+ }
476
+ function X(l, y) {
477
+ t("change", { time: l, index: y });
478
+ }
479
+ P(
480
+ () => n.modelValue,
481
+ (l) => {
482
+ l !== void 0 && !g.value && (v.value = h(l));
483
+ }
484
+ ), P(s, () => {
485
+ S(), v.value = s.value;
486
+ }), P(d, (l) => {
487
+ v.value > l && (v.value = l, S());
488
+ }), P(v, (l) => {
489
+ I(l), l >= d.value && S();
490
+ });
491
+ function G() {
492
+ g.value ? S() : te();
493
+ }
494
+ function te() {
495
+ v.value >= d.value && (v.value = s.value), g.value = !0, c && clearInterval(c), c = setInterval(O, E.value);
496
+ }
497
+ function S() {
498
+ g.value = !1, c && (clearInterval(c), c = null);
499
+ }
500
+ P(E, (l) => {
501
+ g.value && (c && clearInterval(c), c = setInterval(O, l));
502
+ });
503
+ function O() {
504
+ const l = A(v.value, n.interval);
505
+ if (l > d.value) {
506
+ v.value = d.value, S();
507
+ return;
508
+ }
509
+ v.value = l;
510
+ }
511
+ function R() {
512
+ const l = A(v.value, -n.interval);
513
+ v.value = l < s.value ? s.value : l;
514
+ }
515
+ function L() {
516
+ const l = A(v.value, n.interval);
517
+ v.value = l > d.value ? d.value : l;
518
+ }
519
+ return de(() => S()), (l, y) => (T(), x("div", rt, [
520
+ u("div", ut, [
521
+ J(Fe, {
522
+ "is-playing": g.value,
523
+ onTogglePlay: G,
524
+ onPrev: R,
525
+ onNext: L
526
+ }, Re({ _: 2 }, [
527
+ l.$slots.prev ? {
528
+ name: "prev",
529
+ fn: Q(() => [
530
+ W(l.$slots, "prev", {}, void 0, !0)
531
+ ]),
532
+ key: "0"
533
+ } : void 0,
534
+ l.$slots.play ? {
535
+ name: "play",
536
+ fn: Q(({ isPlaying: _ }) => [
537
+ W(l.$slots, "play", { isPlaying: _ }, void 0, !0)
538
+ ]),
539
+ key: "1"
540
+ } : void 0,
541
+ l.$slots.next ? {
542
+ name: "next",
543
+ fn: Q(() => [
544
+ W(l.$slots, "next", {}, void 0, !0)
545
+ ]),
546
+ key: "2"
547
+ } : void 0
548
+ ]), 1032, ["is-playing"])
549
+ ]),
550
+ u("div", ct, [
551
+ J(lt, {
552
+ modelValue: v.value,
553
+ "onUpdate:modelValue": y[0] || (y[0] = (_) => v.value = _),
554
+ "start-time": p.value,
555
+ "end-time": F.value,
556
+ interval: e.interval,
557
+ "min-tick-spacing": e.minTickSpacing,
558
+ "tick-label-mode": e.tickLabelMode,
559
+ "format-tooltip": e.formatTooltip,
560
+ onPlayingPause: S,
561
+ onChange: X
562
+ }, null, 8, ["modelValue", "start-time", "end-time", "interval", "min-tick-spacing", "tick-label-mode", "format-tooltip"])
563
+ ]),
564
+ J(st, {
565
+ modelValue: k.value,
566
+ "onUpdate:modelValue": y[1] || (y[1] = (_) => k.value = _),
567
+ options: e.speedOptions
568
+ }, null, 8, ["modelValue", "options"])
569
+ ]));
570
+ }
571
+ }), mt = /* @__PURE__ */ ee(dt, [["__scopeId", "data-v-12c09d1e"]]);
572
+ export {
573
+ mt as GTimePlayer,
574
+ A as addHours,
575
+ mt as default,
576
+ ze as defaultTooltipFormat,
577
+ ft as diffHours,
578
+ qe as findNearestTickIndex,
579
+ Je as findTickIndex,
580
+ Ke as formatDayLabel,
581
+ Ue as formatHH,
582
+ je as formatHHmm,
583
+ he as formatMD,
584
+ fe as getDateKey,
585
+ Oe as getDayOfWeek,
586
+ Ge as getHour,
587
+ Ye as getNextMidnight,
588
+ ye as getWeekdayShort,
589
+ h as toTimestamp
590
+ };
@@ -0,0 +1 @@
1
+ (function(u,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],e):(u=typeof globalThis<"u"?globalThis:u||self,e(u.Vue3TimePlaybar={},u.Vue))})(this,(function(u,e){"use strict";const ue={class:"player-controls"},me={class:"btn-group"},fe=["title"],pe={key:0,viewBox:"0 0 24 24",fill:"currentColor",stroke:"none"},ge={key:1,viewBox:"0 0 24 24",fill:"currentColor",stroke:"none"},ye=e.defineComponent({__name:"PlayerControls",props:{isPlaying:{type:Boolean}},emits:["toggle-play","prev","next"],setup(t){return(a,o)=>(e.openBlock(),e.createElementBlock("div",ue,[e.createElementVNode("div",me,[e.createElementVNode("button",{class:"control-btn",onClick:o[0]||(o[0]=n=>a.$emit("prev")),title:"上一时刻"},[e.renderSlot(a.$slots,"prev",{},()=>[o[3]||(o[3]=e.createElementVNode("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},[e.createElementVNode("polygon",{points:"19 20 9 12 19 4 19 20"}),e.createElementVNode("line",{x1:"5",y1:"19",x2:"5",y2:"5"})],-1))],!0)]),e.createElementVNode("button",{class:"control-btn play-btn",onClick:o[1]||(o[1]=n=>a.$emit("toggle-play")),title:t.isPlaying?"暂停":"播放"},[e.renderSlot(a.$slots,"play",{isPlaying:t.isPlaying},()=>[t.isPlaying?(e.openBlock(),e.createElementBlock("svg",ge,[...o[5]||(o[5]=[e.createElementVNode("rect",{x:"6",y:"4",width:"4",height:"16",rx:"1"},null,-1),e.createElementVNode("rect",{x:"14",y:"4",width:"4",height:"16",rx:"1"},null,-1)])])):(e.openBlock(),e.createElementBlock("svg",pe,[...o[4]||(o[4]=[e.createElementVNode("polygon",{points:"5 3 19 12 5 21 5 3"},null,-1)])]))],!0)],8,fe),e.createElementVNode("button",{class:"control-btn",onClick:o[2]||(o[2]=n=>a.$emit("next")),title:"下一时刻"},[e.renderSlot(a.$slots,"next",{},()=>[o[6]||(o[6]=e.createElementVNode("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},[e.createElementVNode("polygon",{points:"5 4 15 12 5 20 5 4"}),e.createElementVNode("line",{x1:"19",y1:"5",x2:"19",y2:"19"})],-1))],!0)])])]))}}),A=(t,a)=>{const o=t.__vccOpts||t;for(const[n,s]of a)o[n]=s;return o},he=A(ye,[["__scopeId","data-v-55e12a8d"]]),ke=["周日","周一","周二","周三","周四","周五","周六"];function k(t){if(typeof t=="number")return t;if(t instanceof Date)return t.getTime();const a=Date.parse(t);return Number.isNaN(a)?(console.warn(`[GTimePlayer] 无法解析时间: "${t}"`),0):a}function _(t,a=2){return String(t).padStart(a,"0")}function ee(t){return new Date(t).getHours()}function te(t){return new Date(t).getDay()}function G(t){return ke[te(t)]??""}function ne(t){return _(new Date(t).getHours())}function U(t){const a=new Date(t);return`${a.getMonth()+1}月${a.getDate()}日`}function oe(t){const a=new Date(t);return`${_(a.getHours())}:${_(a.getMinutes())}`}function le(t){return`${U(t)} ${G(t)} ${oe(t)}`}function X(t){const a=new Date(t);return`${a.getFullYear()}${_(a.getMonth()+1)}${_(a.getDate())}`}function ae(t){return`${U(t)} ${G(t)}`}function x(t,a){return t+a*36e5}function ve(t,a){return(k(a)-k(t))/36e5}function re(t){const a=new Date(t);return a.setHours(24,0,0,0),a.getTime()}function ie(t,a){if(t.length===0)return-1;if(t.length===1)return 0;let o=0,n=t.length-1;if(a<=t[o])return o;if(a>=t[n])return n;for(;o<=n;){const s=o+n>>>1,m=t[s];if(m===a)return s;m<a?o=s+1:n=s-1}return o>=t.length?n:n<0||Math.abs(t[o]-a)<Math.abs(t[n]-a)?o:n}function se(t,a){let o=0,n=t.length-1;for(;o<=n;){const s=o+n>>>1,m=t[s];if(m===a)return s;m<a?o=s+1:n=s-1}return-1}const we={key:0,class:"tick-label"},Te={key:1,class:"day-label"},Ve=50,Ee=40,Se=A(e.defineComponent({__name:"TimeTimeline",props:{modelValue:{},startTime:{},endTime:{},interval:{},minTickSpacing:{},tickLabelMode:{default:"all"},formatTooltip:{}},emits:["update:modelValue","change","playing-pause"],setup(t,{emit:a}){const o=[2,8,14,20],n=t,s=a,m=e.ref(null),g=e.ref(null),H=e.ref(null),w=e.ref(0),f=e.ref(0),y=e.ref(0);let v=!1,d=0,$=!1,N=!1,B=null;const I=e.ref(!1),R=e.computed(()=>{const r=k(n.startTime),c=k(n.endTime),i=c-r;if(i<=0)return[];const T=[];let p=r;for(;p<=c;){const b=(p-r)/i*100,E=ee(p),D=T.length===0,J=X(p),Q=D?null:X(T[T.length-1].time),O=!D&&Q!==J||D&&E===0;let M=!1;if(E===0)M=!0;else if(D){const P=re(p);(x(p,n.interval)>P||c<P)&&(M=!0)}const Z=E===0&&M?!1:n.tickLabelMode==="all"||o.includes(E);T.push({time:p,percent:b,label:ne(p),showLabel:Z,dayLabel:ae(p),showDayLabel:M,isDayBoundary:O}),p=x(p,n.interval)}return T}),j=e.computed(()=>R.value.map(r=>r.time)),S=e.computed(()=>n.minTickSpacing!==void 0?n.minTickSpacing:n.tickLabelMode==="major"?5:15),z=e.computed(()=>{const r=R.value.length;return r<2?w.value||0:(r-1)*S.value}),C=e.computed(()=>z.value>w.value&&w.value>0),V=e.computed(()=>C.value?Ve:Ee),l=e.computed(()=>C.value?z.value:Math.max(0,w.value-V.value*2)),h=e.computed(()=>l.value+V.value*2),L=e.computed(()=>w.value&&C.value?h.value+"px":"100%"),Ce=e.computed(()=>({left:V.value+"px",width:l.value+"px"})),Le=e.computed(()=>({left:V.value+"px",width:l.value+"px"})),F=e.computed(()=>{const r=k(n.startTime),c=k(n.endTime),i=k(n.modelValue);return i<=r?0:i>=c?100:(i-r)/(c-r)*100}),Me=e.computed(()=>({left:V.value+"px",width:F.value/100*l.value+"px"})),Pe=e.computed(()=>{const r=k(n.modelValue);return n.formatTooltip?n.formatTooltip(r):le(r)}),K=e.computed(()=>V.value+F.value/100*l.value-y.value),_e=e.computed(()=>K.value>-50&&K.value<w.value+50),He=e.computed(()=>({transform:`translateX(${K.value}px)`}));function Ie(){g.value&&(f.value=g.value.scrollLeft,$||(y.value=g.value.scrollLeft))}function Re(r){!g.value||!C.value||(g.value.scrollLeft+=r.deltaY||r.deltaX)}function W(r=!1){if(!C.value||!g.value||v)return;const c=V.value+F.value/100*l.value,i=w.value,T=h.value-i,p=Math.max(0,Math.min(c-i/2,T));y.value=p;const b=g.value;if(r||Math.abs(b.scrollLeft-p)<1){b.scrollLeft=p,f.value=p;return}d&&cancelAnimationFrame(d);const E=b.scrollLeft,D=p-E;$=!0;const J=200,Q=performance.now();function O(M){const Z=M-Q,P=Math.min(Z/J,1),de=1-(1-P)*(1-P);b.scrollLeft=E+D*de,f.value=b.scrollLeft,P<1?d=requestAnimationFrame(O):(d=0,$=!1)}d=requestAnimationFrame(O)}e.watch(F,()=>W()),e.watch(w,()=>W());function q(r){if(!g.value)return k(n.startTime);const c=g.value.getBoundingClientRect(),i=g.value.scrollLeft,p=r.clientX-c.left+i-V.value,b=Math.max(0,Math.min(p,l.value)),E=k(n.startTime),D=k(n.endTime);return E+(D-E)*(b/(l.value||1))}function ze(r){const c=j.value;if(c.length===0)return{time:r,index:-1};const i=ie(c,r);return{time:c[i]??r,index:i}}function Y(r){const{time:c,index:i}=ze(r);c!==k(n.modelValue)&&(s("update:modelValue",c),s("change",c,i))}function We(r){if(N){N=!1;return}Y(q(r))}function Ae(r){const c=r.clientX;N=!1,v=!0,s("playing-pause");const i=p=>{Math.abs(p.clientX-c)>3&&(N=!0),Y(q(p))},T=()=>{v=!1,document.removeEventListener("mousemove",i),document.removeEventListener("mouseup",T),e.nextTick(()=>W())};document.addEventListener("mousemove",i),document.addEventListener("mouseup",T)}function Fe(r){v=!0,N=!1,I.value=!0,document.body.style.cursor="grabbing",s("playing-pause");const c=T=>{N=!0,Y(q(T))},i=()=>{v=!1,I.value=!1,document.body.style.cursor="",document.removeEventListener("mousemove",c),document.removeEventListener("mouseup",i),e.nextTick(()=>W())};document.addEventListener("mousemove",c),document.addEventListener("mouseup",i)}return e.onMounted(()=>{g.value&&(w.value=g.value.clientWidth,B=new ResizeObserver(r=>{r[0]&&(w.value=r[0].contentRect.width)}),B.observe(g.value)),e.nextTick(()=>W())}),e.onUnmounted(()=>{d&&cancelAnimationFrame(d),B==null||B.disconnect()}),(r,c)=>(e.openBlock(),e.createElementBlock("div",{class:"time-timeline",ref_key:"timelineRef",ref:m},[e.withDirectives(e.createElementVNode("div",{class:e.normalizeClass(["handle-group",{dragging:I.value}]),style:e.normalizeStyle(He.value)},[e.createElementVNode("div",{class:"floating-tooltip",onMousedown:e.withModifiers(Fe,["prevent","stop"])},e.toDisplayString(Pe.value),33),c[0]||(c[0]=e.createElementVNode("div",{class:"handle-visual"},null,-1))],6),[[e.vShow,_e.value]]),e.createElementVNode("div",{class:"scroll-container",ref_key:"scrollRef",ref:g,onScroll:Ie,onWheel:e.withModifiers(Re,["prevent"])},[e.createElementVNode("div",{class:"timeline-inner",ref_key:"innerRef",ref:H,style:e.normalizeStyle({width:L.value}),onMousedown:e.withModifiers(Ae,["prevent"]),onClick:We},[e.createElementVNode("div",{class:"track-bg",style:e.normalizeStyle(Ce.value)},null,4),e.createElementVNode("div",{class:"track-active",style:e.normalizeStyle(Me.value)},null,4),e.createElementVNode("div",{class:"ticks-container",style:e.normalizeStyle(Le.value)},[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(R.value,i=>(e.openBlock(),e.createElementBlock("div",{key:i.time,class:e.normalizeClass(["tick-item",{"is-day-boundary":i.isDayBoundary}]),style:e.normalizeStyle({left:i.percent+"%"})},[c[1]||(c[1]=e.createElementVNode("div",{class:"tick-mark"},null,-1)),i.showLabel?(e.openBlock(),e.createElementBlock("div",we,e.toDisplayString(i.label),1)):e.createCommentVNode("",!0),i.showDayLabel?(e.openBlock(),e.createElementBlock("div",Te,e.toDisplayString(i.dayLabel),1)):e.createCommentVNode("",!0)],6))),128))],4)],36)],544)],512))}}),[["__scopeId","data-v-2c40052b"]]),be={class:"speed-value"},Ne=["onClick"],De=A(e.defineComponent({__name:"SpeedSelector",props:{modelValue:{},options:{}},emits:["update:modelValue"],setup(t,{emit:a}){const o=a,n=e.ref(!1),s=e.ref(!0),m=e.ref(null);function g(){if(!m.value)return;const y=m.value.getBoundingClientRect(),v=y.top,d=window.innerHeight-y.bottom;s.value=v>=d}function H(){n.value||g(),n.value=!n.value}function w(y){o("update:modelValue",y),n.value=!1}function f(y){m.value&&!m.value.contains(y.target)&&(n.value=!1)}return e.onMounted(()=>document.addEventListener("mousedown",f)),e.onUnmounted(()=>document.removeEventListener("mousedown",f)),(y,v)=>(e.openBlock(),e.createElementBlock("div",{class:"speed-selector",ref_key:"selectorRef",ref:m},[e.createElementVNode("div",{class:"speed-trigger",onClick:H},[e.createElementVNode("span",be,e.toDisplayString(t.modelValue)+"x",1),(e.openBlock(),e.createElementBlock("svg",{class:e.normalizeClass(["arrow-icon",{open:n.value}]),viewBox:"0 0 12 12",fill:"currentColor"},[...v[0]||(v[0]=[e.createElementVNode("path",{d:"M2.5 4.5L6 8L9.5 4.5",stroke:"currentColor","stroke-width":"1.5",fill:"none","stroke-linecap":"round","stroke-linejoin":"round"},null,-1)])],2))]),e.createVNode(e.Transition,{name:s.value?"dropdown-up":"dropdown-down"},{default:e.withCtx(()=>[n.value?(e.openBlock(),e.createElementBlock("div",{key:0,class:e.normalizeClass(["speed-dropdown",s.value?"is-up":"is-down"])},[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(t.options,d=>(e.openBlock(),e.createElementBlock("div",{key:d,class:e.normalizeClass(["speed-option",{active:d===t.modelValue}]),onClick:$=>w(d)},e.toDisplayString(d)+"x ",11,Ne))),128))],2)):e.createCommentVNode("",!0)]),_:1},8,["name"])],512))}}),[["__scopeId","data-v-e4832d4c"]]),xe={class:"g-time-player"},$e={class:"controls-wrapper"},Be={class:"timeline-wrapper"},ce=A(e.defineComponent({__name:"index",props:{modelValue:{},startTime:{},endTime:{},interval:{default:1},playInterval:{default:1e3},speed:{default:1},speedOptions:{default:()=>[.5,1,2,3]},minTickSpacing:{},tickLabelMode:{default:"all"},formatTooltip:{}},emits:["update:modelValue","change"],setup(t,{emit:a}){const o=t,n=a,s=e.computed(()=>k(o.startTime)),m=e.computed(()=>k(o.endTime)),g=e.computed(()=>s.value),H=e.computed(()=>m.value),w=e.computed(()=>{const l=[];let h=s.value;for(;h<=m.value;)l.push(h),h=x(h,o.interval);return l}),f=e.ref(k(o.modelValue??o.startTime)),y=e.ref(!1),v=e.ref(o.speed);let d=null;const $=e.computed(()=>o.playInterval/v.value);e.watch(()=>o.speed,l=>{v.value=l});function N(l){const h=se(w.value,l);return h>=0?h:0}function B(l,h){n("update:modelValue",l),n("change",{time:l,index:N(l)})}function I(l,h){n("change",{time:l,index:h})}e.watch(()=>o.modelValue,l=>{l!==void 0&&!y.value&&(f.value=k(l))}),e.watch(s,()=>{S(),f.value=s.value}),e.watch(m,l=>{f.value>l&&(f.value=l,S())}),e.watch(f,l=>{B(l),l>=m.value&&S()});function R(){y.value?S():j()}function j(){f.value>=m.value&&(f.value=s.value),y.value=!0,d&&clearInterval(d),d=setInterval(z,$.value)}function S(){y.value=!1,d&&(clearInterval(d),d=null)}e.watch($,l=>{y.value&&(d&&clearInterval(d),d=setInterval(z,l))});function z(){const l=x(f.value,o.interval);if(l>m.value){f.value=m.value,S();return}f.value=l}function C(){const l=x(f.value,-o.interval);f.value=l<s.value?s.value:l}function V(){const l=x(f.value,o.interval);f.value=l>m.value?m.value:l}return e.onUnmounted(()=>S()),(l,h)=>(e.openBlock(),e.createElementBlock("div",xe,[e.createElementVNode("div",$e,[e.createVNode(he,{"is-playing":y.value,onTogglePlay:R,onPrev:C,onNext:V},e.createSlots({_:2},[l.$slots.prev?{name:"prev",fn:e.withCtx(()=>[e.renderSlot(l.$slots,"prev",{},void 0,!0)]),key:"0"}:void 0,l.$slots.play?{name:"play",fn:e.withCtx(({isPlaying:L})=>[e.renderSlot(l.$slots,"play",{isPlaying:L},void 0,!0)]),key:"1"}:void 0,l.$slots.next?{name:"next",fn:e.withCtx(()=>[e.renderSlot(l.$slots,"next",{},void 0,!0)]),key:"2"}:void 0]),1032,["is-playing"])]),e.createElementVNode("div",Be,[e.createVNode(Se,{modelValue:f.value,"onUpdate:modelValue":h[0]||(h[0]=L=>f.value=L),"start-time":g.value,"end-time":H.value,interval:t.interval,"min-tick-spacing":t.minTickSpacing,"tick-label-mode":t.tickLabelMode,"format-tooltip":t.formatTooltip,onPlayingPause:S,onChange:I},null,8,["modelValue","start-time","end-time","interval","min-tick-spacing","tick-label-mode","format-tooltip"])]),e.createVNode(De,{modelValue:v.value,"onUpdate:modelValue":h[1]||(h[1]=L=>v.value=L),options:t.speedOptions},null,8,["modelValue","options"])]))}}),[["__scopeId","data-v-12c09d1e"]]);u.GTimePlayer=ce,u.addHours=x,u.default=ce,u.defaultTooltipFormat=le,u.diffHours=ve,u.findNearestTickIndex=ie,u.findTickIndex=se,u.formatDayLabel=ae,u.formatHH=ne,u.formatHHmm=oe,u.formatMD=U,u.getDateKey=X,u.getDayOfWeek=te,u.getHour=ee,u.getNextMidnight=re,u.getWeekdayShort=G,u.toTimestamp=k,Object.defineProperties(u,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));