From cc241d92c82bd5d88fd946ccabad28a6dc7b1497 Mon Sep 17 00:00:00 2001
From: vaebe <18137693952@163.com>
Date: Thu, 13 Nov 2025 20:41:31 +0800
Subject: [PATCH 01/10] =?UTF-8?q?feat:=20=E5=9F=BA=E6=9C=AC=E9=80=BB?=
=?UTF-8?q?=E8=BE=91=E5=AE=8C=E6=88=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../ccui/ui/shared/hooks/use-namespace.ts | 3 +
packages/ccui/ui/timeline/index.ts | 24 +++
.../ccui/ui/timeline/src/timeline-item.tsx | 103 ++++++++++
.../ccui/ui/timeline/src/timeline-types.ts | 79 ++++++++
packages/ccui/ui/timeline/src/timeline.scss | 157 ++++++++++++++
packages/ccui/ui/timeline/src/timeline.tsx | 28 +++
.../ccui/ui/timeline/test/timeline.test.ts | 141 +++++++++++++
packages/docs/components/timeline/index.md | 191 ++++++++++++++++++
8 files changed, 726 insertions(+)
create mode 100644 packages/ccui/ui/timeline/index.ts
create mode 100644 packages/ccui/ui/timeline/src/timeline-item.tsx
create mode 100644 packages/ccui/ui/timeline/src/timeline-types.ts
create mode 100644 packages/ccui/ui/timeline/src/timeline.scss
create mode 100644 packages/ccui/ui/timeline/src/timeline.tsx
create mode 100644 packages/ccui/ui/timeline/test/timeline.test.ts
create mode 100644 packages/docs/components/timeline/index.md
diff --git a/packages/ccui/ui/shared/hooks/use-namespace.ts b/packages/ccui/ui/shared/hooks/use-namespace.ts
index 33391d46..2e20e447 100644
--- a/packages/ccui/ui/shared/hooks/use-namespace.ts
+++ b/packages/ccui/ui/shared/hooks/use-namespace.ts
@@ -3,6 +3,7 @@ export interface UseNamespace {
e: (el: string) => string
m: (mo: string) => string
em: (el: string, mo: string) => string
+ is: (name: string) => string
}
function createBem(
@@ -35,10 +36,12 @@ export function useNamespace(block: string, needDot = false): UseNamespace {
modifier ? createBem(namespace, '', modifier) : ''
const em = (element: string, modifier: string) =>
element && modifier ? createBem(namespace, element, modifier) : ''
+ const is = (name: string) => `is-${name}`
return {
b,
e,
m,
em,
+ is,
}
}
diff --git a/packages/ccui/ui/timeline/index.ts b/packages/ccui/ui/timeline/index.ts
new file mode 100644
index 00000000..61fe68bb
--- /dev/null
+++ b/packages/ccui/ui/timeline/index.ts
@@ -0,0 +1,24 @@
+import type { App } from 'vue'
+import Timeline from './src/timeline'
+import TimelineItem from './src/timeline-item'
+
+Timeline.install = function (app: App): void {
+ app.component(Timeline.name || 'CTimeline', Timeline)
+ app.component(TimelineItem.name || 'CTimelineItem', TimelineItem)
+}
+
+TimelineItem.install = function (app: App): void {
+ app.component(TimelineItem.name || 'CTimelineItem', TimelineItem)
+}
+
+export { Timeline, TimelineItem }
+
+export default {
+ title: 'Timeline 时间线',
+ category: '数据展示',
+ status: '100%',
+ install(app: App): void {
+ app.component(Timeline.name || 'CTimeline', Timeline)
+ app.component(TimelineItem.name || 'CTimelineItem', TimelineItem)
+ },
+}
diff --git a/packages/ccui/ui/timeline/src/timeline-item.tsx b/packages/ccui/ui/timeline/src/timeline-item.tsx
new file mode 100644
index 00000000..de4028f6
--- /dev/null
+++ b/packages/ccui/ui/timeline/src/timeline-item.tsx
@@ -0,0 +1,103 @@
+import type { TimelineItemProps } from './timeline-types'
+import { computed, defineComponent, h } from 'vue'
+import { useNamespace } from '../../shared/hooks/use-namespace'
+import { timelineItemProps } from './timeline-types'
+
+export default defineComponent({
+ name: 'CTimelineItem',
+ props: timelineItemProps,
+ emits: [],
+ setup(props: TimelineItemProps, { slots }) {
+ const ns = useNamespace('timeline-item')
+
+ // 计算节点的样式类名
+ const nodeClasses = computed(() => {
+ return [
+ ns.e('node'),
+ ns.em('node', props.size),
+ props.type && ns.em('node', props.type),
+ props.hollow && ns.is('hollow'),
+ ].filter(Boolean)
+ })
+
+ // 计算时间戳的样式类名
+ const timestampClasses = computed(() => {
+ return [
+ ns.e('timestamp'),
+ ns.is(props.placement),
+ ]
+ })
+
+ // 渲染图标
+ const renderIcon = () => {
+ if (props.icon) {
+ if (typeof props.icon === 'string') {
+ return
+ }
+ else {
+ // 如果是组件,使用 h 函数渲染
+ return h(props.icon, { class: ns.e('icon') })
+ }
+ }
+ return null
+ }
+
+ // 渲染节点
+ const renderNode = () => {
+ if (slots.dot) {
+ return (
+
+ {slots.dot()}
+
+ )
+ }
+
+ return (
+
+ {renderIcon()}
+
+ )
+ }
+
+ // 渲染时间戳
+ const renderTimestamp = () => {
+ if (props.hideTimestamp)
+ return null
+
+ return (
+
+ {props.timestamp}
+
+ )
+ }
+
+ return () => {
+ return (
+
+ {/* 连接线 */}
+
+
+ {/* 节点 */}
+ {renderNode()}
+
+ {/* 内容区域 */}
+
+ {/* 顶部时间戳 */}
+ {props.placement === 'top' && renderTimestamp()}
+
+ {/* 内容 */}
+
+ {slots.default && slots.default()}
+
+
+ {/* 底部时间戳 */}
+ {props.placement === 'bottom' && renderTimestamp()}
+
+
+ )
+ }
+ },
+})
diff --git a/packages/ccui/ui/timeline/src/timeline-types.ts b/packages/ccui/ui/timeline/src/timeline-types.ts
new file mode 100644
index 00000000..32f74b4c
--- /dev/null
+++ b/packages/ccui/ui/timeline/src/timeline-types.ts
@@ -0,0 +1,79 @@
+import type { Component, ExtractPropTypes, PropType } from 'vue'
+
+// Timeline 主组件的 props
+export const timelineProps = {
+ // 暂时没有特殊的 props,主要通过插槽传递 TimelineItem
+} as const
+
+export type TimelineProps = ExtractPropTypes
+
+// TimelineItem 组件的 props
+export const timelineItemProps = {
+ /**
+ * 时间戳内容
+ */
+ timestamp: {
+ type: String,
+ default: '',
+ },
+ /**
+ * 是否隐藏时间戳
+ */
+ hideTimestamp: {
+ type: Boolean,
+ default: false,
+ },
+ /**
+ * 是否垂直居中
+ */
+ center: {
+ type: Boolean,
+ default: false,
+ },
+ /**
+ * 时间戳位置
+ */
+ placement: {
+ type: String as PropType<'top' | 'bottom'>,
+ default: 'bottom',
+ validator: (value: string) => ['top', 'bottom'].includes(value),
+ },
+ /**
+ * 节点类型
+ */
+ type: {
+ type: String as PropType<'primary' | 'success' | 'warning' | 'danger' | 'info' | ''>,
+ default: '',
+ validator: (value: string) => ['primary', 'success', 'warning', 'danger', 'info', ''].includes(value),
+ },
+ /**
+ * 节点颜色
+ */
+ color: {
+ type: String,
+ default: '',
+ },
+ /**
+ * 节点尺寸
+ */
+ size: {
+ type: String as PropType<'normal' | 'large'>,
+ default: 'normal',
+ validator: (value: string) => ['normal', 'large'].includes(value),
+ },
+ /**
+ * 自定义图标
+ */
+ icon: {
+ type: [String, Object] as PropType,
+ },
+ /**
+ * 是否空心点
+ */
+ hollow: {
+ type: Boolean,
+ default: false,
+ },
+} as const
+
+export type TimelineItemProps = ExtractPropTypes
diff --git a/packages/ccui/ui/timeline/src/timeline.scss b/packages/ccui/ui/timeline/src/timeline.scss
new file mode 100644
index 00000000..de1090ed
--- /dev/null
+++ b/packages/ccui/ui/timeline/src/timeline.scss
@@ -0,0 +1,157 @@
+.ccui-timeline {
+ margin: 0;
+ font-size: 14px;
+ list-style: none;
+ padding: 0;
+
+ // 最后一个时间线项目不显示尾部连接线
+ .ccui-timeline-item:last-child {
+ .ccui-timeline-item__tail {
+ display: none;
+ }
+ }
+
+ // 垂直居中的时间线项目
+ .ccui-timeline-item__center {
+ display: flex;
+ align-items: center;
+
+ .ccui-timeline-item__wrapper {
+ width: 100%;
+ }
+
+ .ccui-timeline-item__tail {
+ top: 0;
+ }
+ }
+
+ // 第一个垂直居中项目的特殊处理
+ .ccui-timeline-item__center:first-child {
+ .ccui-timeline-item__tail {
+ height: calc(50% + 10px);
+ top: calc(50% - 10px);
+ }
+ }
+
+ // 最后一个垂直居中项目的特殊处理
+ .ccui-timeline-item__center:last-child {
+ .ccui-timeline-item__tail {
+ display: block;
+ height: calc(50% - 10px);
+ }
+ }
+}
+
+.ccui-timeline-item {
+ position: relative;
+ padding-bottom: 20px;
+
+ // 内容包装器
+ &__wrapper {
+ position: relative;
+ padding-left: 28px;
+ top: -3px;
+ }
+
+ // 连接线
+ &__tail {
+ position: absolute;
+ left: 4px;
+ height: 100%;
+ border-left: 2px solid #e4e7ed;
+ }
+
+ // 图标样式
+ &__icon {
+ color: #ffffff;
+ font-size: 12px;
+ }
+
+ // 节点
+ &__node {
+ position: absolute;
+ background-color: #e4e7ed;
+ border-color: #e4e7ed;
+ border-radius: 50%;
+ box-sizing: border-box;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ // 普通尺寸
+ &--normal {
+ left: -1px;
+ width: 12px;
+ height: 12px;
+ }
+
+ // 大尺寸
+ &--large {
+ left: -2px;
+ width: 14px;
+ height: 14px;
+ }
+
+ // 空心样式
+ &.is-hollow {
+ background: #ffffff;
+ border-style: solid;
+ border-width: 2px;
+ }
+
+ // 不同类型的颜色
+ &--primary {
+ background-color: #409eff;
+ border-color: #409eff;
+ }
+
+ &--success {
+ background-color: #67c23a;
+ border-color: #67c23a;
+ }
+
+ &--warning {
+ background-color: #e6a23c;
+ border-color: #e6a23c;
+ }
+
+ &--danger {
+ background-color: #f56c6c;
+ border-color: #f56c6c;
+ }
+
+ &--info {
+ background-color: #909399;
+ border-color: #909399;
+ }
+ }
+
+ // 自定义节点
+ &__dot {
+ position: absolute;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
+ // 内容区域
+ &__content {
+ color: #303133;
+ }
+
+ // 时间戳
+ &__timestamp {
+ color: #909399;
+ line-height: 1;
+ font-size: 12px;
+
+ &.is-top {
+ margin-bottom: 8px;
+ padding-top: 4px;
+ }
+
+ &.is-bottom {
+ margin-top: 8px;
+ }
+ }
+}
diff --git a/packages/ccui/ui/timeline/src/timeline.tsx b/packages/ccui/ui/timeline/src/timeline.tsx
new file mode 100644
index 00000000..9524a061
--- /dev/null
+++ b/packages/ccui/ui/timeline/src/timeline.tsx
@@ -0,0 +1,28 @@
+import type { TimelineProps } from './timeline-types'
+import { defineComponent, provide } from 'vue'
+import { useNamespace } from '../../shared/hooks/use-namespace'
+import { timelineProps } from './timeline-types'
+import './timeline.scss'
+
+// Timeline 注入的 key,用于 TimelineItem 获取父组件信息
+export const TIMELINE_INJECTION_KEY = Symbol('timeline')
+
+export default defineComponent({
+ name: 'CTimeline',
+ props: timelineProps,
+ emits: [],
+ setup(props: TimelineProps, { slots }) {
+ const ns = useNamespace('timeline')
+
+ // 向子组件提供 Timeline 的插槽信息
+ provide(TIMELINE_INJECTION_KEY, slots)
+
+ return () => {
+ return (
+
+ {slots.default && slots.default()}
+
+ )
+ }
+ },
+})
diff --git a/packages/ccui/ui/timeline/test/timeline.test.ts b/packages/ccui/ui/timeline/test/timeline.test.ts
new file mode 100644
index 00000000..fa36863f
--- /dev/null
+++ b/packages/ccui/ui/timeline/test/timeline.test.ts
@@ -0,0 +1,141 @@
+import { mount } from '@vue/test-utils'
+import { describe, expect, it } from 'vitest'
+import { Timeline, TimelineItem } from '../index'
+
+describe('timeline', () => {
+ it('should render', () => {
+ const wrapper = mount(Timeline)
+ expect(wrapper.exists()).toBe(true)
+ expect(wrapper.classes()).toContain('ccui-timeline')
+ })
+
+ it('should render timeline items', () => {
+ const wrapper = mount(Timeline, {
+ slots: {
+ default: `
+ Test content 1
+ Test content 2
+ `,
+ },
+ })
+
+ expect(wrapper.element.tagName).toBe('UL')
+ })
+})
+
+describe('timelineItem', () => {
+ it('should render with timestamp', () => {
+ const wrapper = mount(TimelineItem, {
+ props: {
+ timestamp: '2018/4/12',
+ },
+ slots: {
+ default: 'Test content',
+ },
+ })
+
+ expect(wrapper.exists()).toBe(true)
+ expect(wrapper.classes()).toContain('ccui-timeline-item')
+ expect(wrapper.find('.ccui-timeline-item__timestamp').text()).toBe('2018/4/12')
+ expect(wrapper.find('.ccui-timeline-item__content').text()).toBe('Test content')
+ })
+
+ it('should hide timestamp when hideTimestamp is true', () => {
+ const wrapper = mount(TimelineItem, {
+ props: {
+ timestamp: '2018/4/12',
+ hideTimestamp: true,
+ },
+ slots: {
+ default: 'Test content',
+ },
+ })
+
+ expect(wrapper.find('.ccui-timeline-item__timestamp').exists()).toBe(false)
+ })
+
+ it('should render with different types', () => {
+ const wrapper = mount(TimelineItem, {
+ props: {
+ timestamp: '2018/4/12',
+ type: 'primary',
+ },
+ slots: {
+ default: 'Test content',
+ },
+ })
+
+ expect(wrapper.find('.ccui-timeline-item__node--primary').exists()).toBe(true)
+ })
+
+ it('should render with custom color', () => {
+ const wrapper = mount(TimelineItem, {
+ props: {
+ timestamp: '2018/4/12',
+ color: '#ff0000',
+ },
+ slots: {
+ default: 'Test content',
+ },
+ })
+
+ const node = wrapper.find('.ccui-timeline-item__node')
+ expect(node.attributes('style')).toContain('background-color: rgb(255, 0, 0)')
+ })
+
+ it('should render with large size', () => {
+ const wrapper = mount(TimelineItem, {
+ props: {
+ timestamp: '2018/4/12',
+ size: 'large',
+ },
+ slots: {
+ default: 'Test content',
+ },
+ })
+
+ expect(wrapper.find('.ccui-timeline-item__node--large').exists()).toBe(true)
+ })
+
+ it('should render with hollow style', () => {
+ const wrapper = mount(TimelineItem, {
+ props: {
+ timestamp: '2018/4/12',
+ hollow: true,
+ },
+ slots: {
+ default: 'Test content',
+ },
+ })
+
+ expect(wrapper.find('.ccui-timeline-item__node.is-hollow').exists()).toBe(true)
+ })
+
+ it('should render timestamp at top when placement is top', () => {
+ const wrapper = mount(TimelineItem, {
+ props: {
+ timestamp: '2018/4/12',
+ placement: 'top',
+ },
+ slots: {
+ default: 'Test content',
+ },
+ })
+
+ expect(wrapper.find('.ccui-timeline-item__timestamp.is-top').exists()).toBe(true)
+ })
+
+ it('should render with center alignment', () => {
+ const wrapper = mount(TimelineItem, {
+ props: {
+ timestamp: '2018/4/12',
+ center: true,
+ },
+ slots: {
+ default: 'Test content',
+ },
+ })
+
+ expect(wrapper.classes()).toContain('ccui-timeline-item__center')
+ })
+})
diff --git a/packages/docs/components/timeline/index.md b/packages/docs/components/timeline/index.md
new file mode 100644
index 00000000..e1a960de
--- /dev/null
+++ b/packages/docs/components/timeline/index.md
@@ -0,0 +1,191 @@
+# Timeline 时间线
+
+可视化地呈现时间流信息。
+
+### 何时使用
+
+- 当有一系列信息需按时间排列时
+- 需要有一条时间轴进行视觉上的串联时
+
+### 基本用法
+
+Timeline 可拆分成多个按照时间戳排列的活动,时间戳是其区分于其他控件的重要特征。
+
+:::demo
+
+```vue
+
+
+
+ 更新 Github 模板
+
+
+ 更新 Github 模板
+
+
+ 更新 Github 模板
+
+
+
+
+
+```
+
+:::
+
+### 自定义节点样式
+
+可根据实际场景自定义节点尺寸、颜色,或直接使用图标。
+
+:::demo
+
+```vue
+
+
+
+ 支持使用图标
+
+
+ 支持自定义颜色
+
+
+ 支持自定义尺寸
+
+
+ 支持空心点
+
+
+
+
+
+```
+
+:::
+
+### 自定义时间戳
+
+当内容在垂直方向上过高时,可将时间戳置于内容之上。
+
+:::demo
+
+```vue
+
+
+
+
+
更新 Github 模板
+
Tom 在 2018/4/12 20:46 提交了更新
+
+
+
+
+
更新 Github 模板
+
Tom 在 2018/4/3 20:46 提交了更新
+
+
+
+
+
更新 Github 模板
+
Tom 在 2018/4/2 20:46 提交了更新
+
+
+
+
+
+
+```
+
+:::
+
+### 垂直居中
+
+垂直居中样式的 Timeline-Item。
+
+:::demo
+
+```vue
+
+
+
+
+
更新 Github 模板
+
Tom 在 2018/4/12 20:46 提交了更新
+
+
+
+
+
更新 Github 模板
+
Tom 在 2018/4/3 20:46 提交了更新
+
+
+
+
+
+
+```
+
+:::
+
+## Timeline API
+
+### Timeline 插槽
+
+| 插槽名 | 说明 | 子标签 |
+| ---- | ---- | ---- |
+| default | timeline 组件的自定义默认内容 | Timeline-Item |
+
+## Timeline-Item API
+
+### Timeline-Item 属性
+
+| 参数 | 类型 | 默认值 | 说明 |
+| ---- | ---- | ---- | ---- |
+| timestamp | string | '' | 时间戳 |
+| hide-timestamp | boolean | false | 是否隐藏时间戳 |
+| center | boolean | false | 是否垂直居中 |
+| placement | 'top' \| 'bottom' | 'bottom' | 时间戳位置 |
+| type | 'primary' \| 'success' \| 'warning' \| 'danger' \| 'info' \| '' | '' | 节点类型 |
+| color | string | '' | 节点颜色 |
+| size | 'normal' \| 'large' | 'normal' | 节点尺寸 |
+| icon | string \| Component | — | 自定义图标 |
+| hollow | boolean | false | 是否空心点 |
+
+### Timeline-Item 插槽
+
+| 插槽名 | 说明 |
+| ---- | ---- |
+| default | 自定义内容 |
+| dot | 自定义节点 |
+
From 77139e59beb220675ba61cd50a175a4e7e2ab885 Mon Sep 17 00:00:00 2001
From: vaebe <18137693952@163.com>
Date: Thu, 13 Nov 2025 20:50:30 +0800
Subject: [PATCH 02/10] =?UTF-8?q?style:=20=E5=A2=9E=E5=8A=A0=E6=B7=B1?=
=?UTF-8?q?=E8=89=B2=E4=B8=BB=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages/ccui/ui/timeline/src/timeline.scss | 44 +++++++++++----------
1 file changed, 23 insertions(+), 21 deletions(-)
diff --git a/packages/ccui/ui/timeline/src/timeline.scss b/packages/ccui/ui/timeline/src/timeline.scss
index de1090ed..abf880c4 100644
--- a/packages/ccui/ui/timeline/src/timeline.scss
+++ b/packages/ccui/ui/timeline/src/timeline.scss
@@ -1,6 +1,8 @@
+@use '../../style-var/index.scss' as *;
+
.ccui-timeline {
margin: 0;
- font-size: 14px;
+ font-size: $ccui-font-size-lg;
list-style: none;
padding: 0;
@@ -58,20 +60,20 @@
position: absolute;
left: 4px;
height: 100%;
- border-left: 2px solid #e4e7ed;
+ border-left: 2px solid $ccui-dividing-line;
}
// 图标样式
&__icon {
- color: #ffffff;
- font-size: 12px;
+ color: $ccui-light-text;
+ font-size: $ccui-font-size-sm;
}
// 节点
&__node {
position: absolute;
- background-color: #e4e7ed;
- border-color: #e4e7ed;
+ background-color: $ccui-dividing-line;
+ border-color: $ccui-dividing-line;
border-radius: 50%;
box-sizing: border-box;
display: flex;
@@ -94,35 +96,35 @@
// 空心样式
&.is-hollow {
- background: #ffffff;
+ background: $ccui-base-bg;
border-style: solid;
border-width: 2px;
}
// 不同类型的颜色
&--primary {
- background-color: #409eff;
- border-color: #409eff;
+ background-color: $ccui-primary;
+ border-color: $ccui-primary;
}
&--success {
- background-color: #67c23a;
- border-color: #67c23a;
+ background-color: $ccui-success;
+ border-color: $ccui-success;
}
&--warning {
- background-color: #e6a23c;
- border-color: #e6a23c;
+ background-color: $ccui-warning;
+ border-color: $ccui-warning;
}
&--danger {
- background-color: #f56c6c;
- border-color: #f56c6c;
+ background-color: $ccui-danger;
+ border-color: $ccui-danger;
}
&--info {
- background-color: #909399;
- border-color: #909399;
+ background-color: $ccui-info;
+ border-color: $ccui-info;
}
}
@@ -136,14 +138,14 @@
// 内容区域
&__content {
- color: #303133;
+ color: $ccui-text;
}
// 时间戳
&__timestamp {
- color: #909399;
- line-height: 1;
- font-size: 12px;
+ color: $ccui-aide-text;
+ line-height: $ccui-line-height-base;
+ font-size: $ccui-font-size-sm;
&.is-top {
margin-bottom: 8px;
From 7cc64c535579be8d66c5555233ef5e6aaad03934 Mon Sep 17 00:00:00 2001
From: vaebe <18137693952@163.com>
Date: Thu, 13 Nov 2025 20:53:53 +0800
Subject: [PATCH 03/10] =?UTF-8?q?style:=20=E6=A0=B7=E5=BC=8F=E4=BC=98?=
=?UTF-8?q?=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages/ccui/ui/timeline/src/timeline.scss | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/packages/ccui/ui/timeline/src/timeline.scss b/packages/ccui/ui/timeline/src/timeline.scss
index abf880c4..5068d438 100644
--- a/packages/ccui/ui/timeline/src/timeline.scss
+++ b/packages/ccui/ui/timeline/src/timeline.scss
@@ -47,6 +47,16 @@
.ccui-timeline-item {
position: relative;
padding-bottom: 20px;
+ list-style: none;
+
+ // 确保隐藏所有列表标记
+ &::marker {
+ display: none;
+ }
+
+ &::before {
+ display: none;
+ }
// 内容包装器
&__wrapper {
From cf0da348246b24c7f63c6c1e3384e926b60733ed Mon Sep 17 00:00:00 2001
From: vaebe <18137693952@163.com>
Date: Thu, 13 Nov 2025 21:09:49 +0800
Subject: [PATCH 04/10] =?UTF-8?q?style:=20=E6=9B=B4=E6=96=B0=E6=A0=B7?=
=?UTF-8?q?=E5=BC=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages/ccui/ui/timeline/src/timeline.scss | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/packages/ccui/ui/timeline/src/timeline.scss b/packages/ccui/ui/timeline/src/timeline.scss
index 5068d438..2a34b624 100644
--- a/packages/ccui/ui/timeline/src/timeline.scss
+++ b/packages/ccui/ui/timeline/src/timeline.scss
@@ -69,7 +69,8 @@
&__tail {
position: absolute;
left: 4px;
- height: 100%;
+ top: 0;
+ height: calc(100% + 20px);
border-left: 2px solid $ccui-dividing-line;
}
@@ -93,6 +94,7 @@
// 普通尺寸
&--normal {
left: -1px;
+ top: 0;
width: 12px;
height: 12px;
}
@@ -100,6 +102,7 @@
// 大尺寸
&--large {
left: -2px;
+ top: 0;
width: 14px;
height: 14px;
}
@@ -141,6 +144,8 @@
// 自定义节点
&__dot {
position: absolute;
+ top: 0;
+ left: -6px;
display: flex;
justify-content: center;
align-items: center;
From 3102a1075412493ba768d0ea33befb461e86407d Mon Sep 17 00:00:00 2001
From: vaebe <18137693952@163.com>
Date: Thu, 13 Nov 2025 21:13:34 +0800
Subject: [PATCH 05/10] =?UTF-8?q?style:=20=E4=BF=AE=E5=A4=8D=E6=A0=B7?=
=?UTF-8?q?=E5=BC=8F=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages/ccui/ui/timeline/src/timeline.scss | 27 +++++++++++++++------
1 file changed, 20 insertions(+), 7 deletions(-)
diff --git a/packages/ccui/ui/timeline/src/timeline.scss b/packages/ccui/ui/timeline/src/timeline.scss
index 2a34b624..4f2ab091 100644
--- a/packages/ccui/ui/timeline/src/timeline.scss
+++ b/packages/ccui/ui/timeline/src/timeline.scss
@@ -14,32 +14,45 @@
}
// 垂直居中的时间线项目
- .ccui-timeline-item__center {
+ .ccui-timeline-item.ccui-timeline-item__center {
display: flex;
align-items: center;
+ min-height: 60px;
.ccui-timeline-item__wrapper {
width: 100%;
}
.ccui-timeline-item__tail {
- top: 0;
+ top: 6px;
+ height: calc(100% + 14px);
+ }
+
+ .ccui-timeline-item__node {
+ top: 50%;
+ transform: translateY(-50%);
+ }
+
+ .ccui-timeline-item__dot {
+ top: 50%;
+ transform: translateY(-50%);
}
}
// 第一个垂直居中项目的特殊处理
- .ccui-timeline-item__center:first-child {
+ .ccui-timeline-item.ccui-timeline-item__center:first-child {
.ccui-timeline-item__tail {
- height: calc(50% + 10px);
- top: calc(50% - 10px);
+ top: 50%;
+ height: calc(50% + 20px);
}
}
// 最后一个垂直居中项目的特殊处理
- .ccui-timeline-item__center:last-child {
+ .ccui-timeline-item.ccui-timeline-item__center:last-child {
.ccui-timeline-item__tail {
display: block;
- height: calc(50% - 10px);
+ height: 50%;
+ top: 6px;
}
}
}
From 7ff7790916ed8dd000049962b18432bf663f3390 Mon Sep 17 00:00:00 2001
From: vaebe <18137693952@163.com>
Date: Thu, 13 Nov 2025 21:41:42 +0800
Subject: [PATCH 06/10] =?UTF-8?q?feat:=20=E6=96=87=E6=A1=A3=E5=AE=8C?=
=?UTF-8?q?=E5=96=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../ccui/ui/timeline/src/timeline-types.ts | 4 +-
packages/docs/components/divider/index.md | 2 +-
packages/docs/components/timeline/index.md | 262 ++++++++++++------
3 files changed, 177 insertions(+), 91 deletions(-)
diff --git a/packages/ccui/ui/timeline/src/timeline-types.ts b/packages/ccui/ui/timeline/src/timeline-types.ts
index 32f74b4c..b170587c 100644
--- a/packages/ccui/ui/timeline/src/timeline-types.ts
+++ b/packages/ccui/ui/timeline/src/timeline-types.ts
@@ -7,6 +7,8 @@ export const timelineProps = {
export type TimelineProps = ExtractPropTypes
+export type TimelineItemType = 'primary' | 'success' | 'warning' | 'danger' | 'info' | ''
+
// TimelineItem 组件的 props
export const timelineItemProps = {
/**
@@ -42,7 +44,7 @@ export const timelineItemProps = {
* 节点类型
*/
type: {
- type: String as PropType<'primary' | 'success' | 'warning' | 'danger' | 'info' | ''>,
+ type: String as PropType,
default: '',
validator: (value: string) => ['primary', 'success', 'warning', 'danger', 'info', ''].includes(value),
},
diff --git a/packages/docs/components/divider/index.md b/packages/docs/components/divider/index.md
index 088052e0..88c21799 100644
--- a/packages/docs/components/divider/index.md
+++ b/packages/docs/components/divider/index.md
@@ -82,7 +82,7 @@ export default defineComponent({
| color | string | -- | 设置分隔线的颜色 |
| direction | [DirectionType](#directiontype) | horizontal | 设置分隔线方向 |
| border-style | [BorderStyleType](#borderstyletype) | solid | 设置分隔符样式 |
-| content-position | [ContentPositionType](#Contentpositiontype) | center | 设置分隔线文案的位置 |
+| content-position | [ContentPositionType](#contentpositiontype) | center | 设置分隔线文案的位置 |
| content-color | string | -- | 设置分隔线文案的颜色 |
| content-background-color | string | -- | 设置分隔线文案的背景颜色 |
diff --git a/packages/docs/components/timeline/index.md b/packages/docs/components/timeline/index.md
index e1a960de..728370ef 100644
--- a/packages/docs/components/timeline/index.md
+++ b/packages/docs/components/timeline/index.md
@@ -2,32 +2,18 @@
可视化地呈现时间流信息。
-### 何时使用
+## 何时使用
- 当有一系列信息需按时间排列时
- 需要有一条时间轴进行视觉上的串联时
-### 基本用法
+## 基本用法
Timeline 可拆分成多个按照时间戳排列的活动,时间戳是其区分于其他控件的重要特征。
:::demo
```vue
-
-
-
- 更新 Github 模板
-
-
- 更新 Github 模板
-
-
- 更新 Github 模板
-
-
-
-
+
+
+
+
+
+ 更新 Github 模板
+
+
+ 更新 Github 模板
+
+
+ 更新 Github 模板
+
+
+
+
+
+
```
:::
-### 自定义节点样式
+## 自定义节点样式
可根据实际场景自定义节点尺寸、颜色,或直接使用图标。
:::demo
```vue
-
-
-
- 支持使用图标
-
-
- 支持自定义颜色
-
-
- 支持自定义尺寸
-
-
- 支持空心点
-
-
-
-
+
+
+
+
+
+ 支持使用图标
+
+
+ 支持自定义颜色
+
+
+ 支持自定义尺寸
+
+
+ 支持空心点
+
+
+
+
+
+
```
:::
-### 自定义时间戳
+## 自定义时间戳
当内容在垂直方向上过高时,可将时间戳置于内容之上。
:::demo
```vue
-
-
-
-
-
更新 Github 模板
-
Tom 在 2018/4/12 20:46 提交了更新
-
-
-
-
-
更新 Github 模板
-
Tom 在 2018/4/3 20:46 提交了更新
-
-
-
-
-
更新 Github 模板
-
Tom 在 2018/4/2 20:46 提交了更新
-
-
-
-
-
+
+
+
+
+
+
+
更新 Github 模板
+
Tom 在 2018/4/12 20:46 提交了更新
+
+
+
+
+
更新 Github 模板
+
Tom 在 2018/4/3 20:46 提交了更新
+
+
+
+
+
更新 Github 模板
+
Tom 在 2018/4/2 20:46 提交了更新
+
+
+
+
+
+
+
```
:::
-### 垂直居中
+## 垂直居中
垂直居中样式的 Timeline-Item。
:::demo
```vue
+
+
-
-
-
-
更新 Github 模板
-
Tom 在 2018/4/12 20:46 提交了更新
-
-
-
-
-
更新 Github 模板
-
Tom 在 2018/4/3 20:46 提交了更新
-
-
-
+
+
+
+
+
更新 Github 模板
+
Tom 在 2018/4/12 20:46 提交了更新
+
+
+
+
+
更新 Github 模板
+
Tom 在 2018/4/3 20:46 提交了更新
+
+
+
+
+
+```
+
+:::
+
+## 自定义节点
+
+可以通过插槽自定义节点。
+
+:::demo
+
+```vue
+
+
+
+
+
+
+
+ ✓
+
+
+ 自定义节点内容
+
+
+ 普通节点
+
+
+
+
+
+
```
:::
-## Timeline API
+## API
+
+### Timeline Props
-### Timeline 插槽
+| 参数 | 说明 | 类型 | 可选值 | 默认值 |
+| ---- | ---- | ---- | ---- | ---- |
+| — | — | — | — | — |
+
+### Timeline Events
+
+| 事件名 | 说明 | 回调参数 |
+| ---- | ---- | ---- |
+| — | — | — |
+
+### Timeline Slots
| 插槽名 | 说明 | 子标签 |
| ---- | ---- | ---- |
| default | timeline 组件的自定义默认内容 | Timeline-Item |
-## Timeline-Item API
+### Timeline-Item Props
+
+| 参数 | 说明 | 类型 | 可选值 | 默认值 |
+| ---- | -------- | ---- | ---- | ---- |
+| timestamp | 时间戳 | string | — | '' |
+| hide-timestamp | 是否隐藏时间戳 | boolean | — | false |
+| center | 是否垂直居中 | boolean | — | false |
+| placement | 时间戳位置 | string | top/bottom | bottom |
+| type | 节点类型 | string | [TimelineItemType](#timelineitemtype) | — |
+| color | 节点颜色 | string | — | — |
+| size | 节点尺寸 | string | normal/large | normal |
+| icon | 自定义图标 | string/Component | — | — |
+| hollow | 是否空心点 | boolean | — | false |
-### Timeline-Item 属性
+### Timeline-Item Events
-| 参数 | 类型 | 默认值 | 说明 |
-| ---- | ---- | ---- | ---- |
-| timestamp | string | '' | 时间戳 |
-| hide-timestamp | boolean | false | 是否隐藏时间戳 |
-| center | boolean | false | 是否垂直居中 |
-| placement | 'top' \| 'bottom' | 'bottom' | 时间戳位置 |
-| type | 'primary' \| 'success' \| 'warning' \| 'danger' \| 'info' \| '' | '' | 节点类型 |
-| color | string | '' | 节点颜色 |
-| size | 'normal' \| 'large' | 'normal' | 节点尺寸 |
-| icon | string \| Component | — | 自定义图标 |
-| hollow | boolean | false | 是否空心点 |
+| 事件名 | 说明 | 回调参数 |
+| ---- | ---- | ---- |
+| — | — | — |
-### Timeline-Item 插槽
+### Timeline-Item Slots
| 插槽名 | 说明 |
| ---- | ---- |
| default | 自定义内容 |
| dot | 自定义节点 |
+## Divider类型定义
+
+### TimelineItemType
+
+```ts
+export type TimelineItemType = 'primary' | 'success' | 'warning' | 'danger' | 'info' | ''
+```
From c05e39c7e43962b66695b53bc2a236b66c53b2d9 Mon Sep 17 00:00:00 2001
From: vaebe <18137693952@163.com>
Date: Thu, 13 Nov 2025 21:51:31 +0800
Subject: [PATCH 07/10] =?UTF-8?q?feat:=20=E8=B7=9F=E6=96=B0=E6=A0=B7?=
=?UTF-8?q?=E5=BC=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages/docs/components/timeline/index.md | 50 ++++++++--------------
1 file changed, 19 insertions(+), 31 deletions(-)
diff --git a/packages/docs/components/timeline/index.md b/packages/docs/components/timeline/index.md
index 728370ef..1704db37 100644
--- a/packages/docs/components/timeline/index.md
+++ b/packages/docs/components/timeline/index.md
@@ -27,13 +27,13 @@ export default defineComponent({
-
+
更新 Github 模板
-
+
更新 Github 模板
-
+
更新 Github 模板
@@ -66,16 +66,16 @@ export default defineComponent({
-
+
支持使用图标
-
+
支持自定义颜色
-
+
支持自定义尺寸
-
+
支持空心点
@@ -108,22 +108,22 @@ export default defineComponent({
-
+
更新 Github 模板
-
Tom 在 2018/4/12 20:46 提交了更新
+
Tom 在 2025/09/09 20:46 提交了更新
-
+
更新 Github 模板
-
Tom 在 2018/4/3 20:46 提交了更新
+
Tom 在 2025/09/08 20:46 提交了更新
-
+
更新 Github 模板
-
Tom 在 2018/4/2 20:46 提交了更新
+
Tom 在 2025/09/07 20:46 提交了更新
@@ -156,16 +156,16 @@ export default defineComponent({
-
+
更新 Github 模板
-
Tom 在 2018/4/12 20:46 提交了更新
+
Tom 在 2025/09/09 20:46 提交了更新
-
+
更新 Github 模板
-
Tom 在 2018/4/3 20:46 提交了更新
+
Tom 在 2025/09/08 20:46 提交了更新
@@ -198,7 +198,7 @@ export default defineComponent({
-
+
✓
@@ -206,7 +206,7 @@ export default defineComponent({
自定义节点内容
-
+
普通节点
@@ -221,18 +221,6 @@ export default defineComponent({
## API
-### Timeline Props
-
-| 参数 | 说明 | 类型 | 可选值 | 默认值 |
-| ---- | ---- | ---- | ---- | ---- |
-| — | — | — | — | — |
-
-### Timeline Events
-
-| 事件名 | 说明 | 回调参数 |
-| ---- | ---- | ---- |
-| — | — | — |
-
### Timeline Slots
| 插槽名 | 说明 | 子标签 |
From 0b0c527a8c90f9d44a7ba89164e404e8fd9cb4b5 Mon Sep 17 00:00:00 2001
From: vaebe <18137693952@163.com>
Date: Thu, 13 Nov 2025 22:14:11 +0800
Subject: [PATCH 08/10] =?UTF-8?q?docs:=20=E4=BF=AE=E5=A4=8D=E6=96=87?=
=?UTF-8?q?=E6=A1=A3=E9=94=99=E8=AF=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages/docs/components/timeline/index.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/docs/components/timeline/index.md b/packages/docs/components/timeline/index.md
index 1704db37..06ca9169 100644
--- a/packages/docs/components/timeline/index.md
+++ b/packages/docs/components/timeline/index.md
@@ -254,7 +254,7 @@ export default defineComponent({
| default | 自定义内容 |
| dot | 自定义节点 |
-## Divider类型定义
+## Timeline类型定义
### TimelineItemType
From 53e355453e025e275d952080a03acf87d55a8804 Mon Sep 17 00:00:00 2001
From: vaebe <18137693952@163.com>
Date: Thu, 13 Nov 2025 22:19:14 +0800
Subject: [PATCH 09/10] =?UTF-8?q?feat:=20=20=E7=BB=84=E4=BB=B6=20name=20?=
=?UTF-8?q?=20=E4=B8=80=E5=AE=9A=E5=AD=98=E5=9C=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages/ccui/ui/timeline/index.ts | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/packages/ccui/ui/timeline/index.ts b/packages/ccui/ui/timeline/index.ts
index 61fe68bb..c4c29cbb 100644
--- a/packages/ccui/ui/timeline/index.ts
+++ b/packages/ccui/ui/timeline/index.ts
@@ -3,12 +3,12 @@ import Timeline from './src/timeline'
import TimelineItem from './src/timeline-item'
Timeline.install = function (app: App): void {
- app.component(Timeline.name || 'CTimeline', Timeline)
- app.component(TimelineItem.name || 'CTimelineItem', TimelineItem)
+ app.component(Timeline.name!, Timeline)
+ app.component(TimelineItem.name!, TimelineItem)
}
TimelineItem.install = function (app: App): void {
- app.component(TimelineItem.name || 'CTimelineItem', TimelineItem)
+ app.component(TimelineItem.name!, TimelineItem)
}
export { Timeline, TimelineItem }
@@ -18,7 +18,7 @@ export default {
category: '数据展示',
status: '100%',
install(app: App): void {
- app.component(Timeline.name || 'CTimeline', Timeline)
- app.component(TimelineItem.name || 'CTimelineItem', TimelineItem)
+ app.component(Timeline.name!, Timeline)
+ app.component(TimelineItem.name!, TimelineItem)
},
}
From 87313d28e4e16bc0b8f268983d657cd0b2b657b3 Mon Sep 17 00:00:00 2001
From: vaebe <18137693952@163.com>
Date: Thu, 13 Nov 2025 22:32:44 +0800
Subject: [PATCH 10/10] =?UTF-8?q?feat:=20=E4=BB=A3=E7=A0=81=E9=80=BB?=
=?UTF-8?q?=E8=BE=91=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages/ccui/ui/timeline/index.ts | 11 +---
packages/ccui/ui/timeline/src/timeline.tsx | 8 +--
.../ccui/ui/timeline/test/timeline.test.ts | 64 +++++++++++++++++--
3 files changed, 64 insertions(+), 19 deletions(-)
diff --git a/packages/ccui/ui/timeline/index.ts b/packages/ccui/ui/timeline/index.ts
index c4c29cbb..3b2ae153 100644
--- a/packages/ccui/ui/timeline/index.ts
+++ b/packages/ccui/ui/timeline/index.ts
@@ -2,23 +2,18 @@ import type { App } from 'vue'
import Timeline from './src/timeline'
import TimelineItem from './src/timeline-item'
-Timeline.install = function (app: App): void {
+Timeline.install = function (app: App) {
app.component(Timeline.name!, Timeline)
app.component(TimelineItem.name!, TimelineItem)
}
-TimelineItem.install = function (app: App): void {
- app.component(TimelineItem.name!, TimelineItem)
-}
-
export { Timeline, TimelineItem }
export default {
title: 'Timeline 时间线',
category: '数据展示',
status: '100%',
- install(app: App): void {
- app.component(Timeline.name!, Timeline)
- app.component(TimelineItem.name!, TimelineItem)
+ install(app: App) {
+ Timeline.install(app)
},
}
diff --git a/packages/ccui/ui/timeline/src/timeline.tsx b/packages/ccui/ui/timeline/src/timeline.tsx
index 9524a061..37eb969b 100644
--- a/packages/ccui/ui/timeline/src/timeline.tsx
+++ b/packages/ccui/ui/timeline/src/timeline.tsx
@@ -1,12 +1,9 @@
import type { TimelineProps } from './timeline-types'
-import { defineComponent, provide } from 'vue'
+import { defineComponent } from 'vue'
import { useNamespace } from '../../shared/hooks/use-namespace'
import { timelineProps } from './timeline-types'
import './timeline.scss'
-// Timeline 注入的 key,用于 TimelineItem 获取父组件信息
-export const TIMELINE_INJECTION_KEY = Symbol('timeline')
-
export default defineComponent({
name: 'CTimeline',
props: timelineProps,
@@ -14,9 +11,6 @@ export default defineComponent({
setup(props: TimelineProps, { slots }) {
const ns = useNamespace('timeline')
- // 向子组件提供 Timeline 的插槽信息
- provide(TIMELINE_INJECTION_KEY, slots)
-
return () => {
return (
diff --git a/packages/ccui/ui/timeline/test/timeline.test.ts b/packages/ccui/ui/timeline/test/timeline.test.ts
index fa36863f..78db997e 100644
--- a/packages/ccui/ui/timeline/test/timeline.test.ts
+++ b/packages/ccui/ui/timeline/test/timeline.test.ts
@@ -1,5 +1,6 @@
import { mount } from '@vue/test-utils'
import { describe, expect, it } from 'vitest'
+import { h } from 'vue'
import { Timeline, TimelineItem } from '../index'
describe('timeline', () => {
@@ -12,14 +13,17 @@ describe('timeline', () => {
it('should render timeline items', () => {
const wrapper = mount(Timeline, {
slots: {
- default: `
- Test content 1
- Test content 2
- `,
+ default: () => [
+ h(TimelineItem, { timestamp: '2018/4/12' }, () => 'Test content 1'),
+ h(TimelineItem, { timestamp: '2018/4/3' }, () => 'Test content 2'),
+ ],
},
})
expect(wrapper.element.tagName).toBe('UL')
+ expect(wrapper.findAll('.ccui-timeline-item')).toHaveLength(2)
+ expect(wrapper.findAll('.ccui-timeline-item__timestamp')[0].text()).toBe('2018/4/12')
+ expect(wrapper.findAll('.ccui-timeline-item__timestamp')[1].text()).toBe('2018/4/3')
})
})
@@ -138,4 +142,56 @@ describe('timelineItem', () => {
expect(wrapper.classes()).toContain('ccui-timeline-item__center')
})
+
+ it('should render with string icon', () => {
+ const wrapper = mount(TimelineItem, {
+ props: {
+ timestamp: '2018/4/12',
+ icon: 'icon-class-name',
+ },
+ slots: {
+ default: 'Test content',
+ },
+ })
+
+ expect(wrapper.find('.ccui-timeline-item__icon').exists()).toBe(true)
+ expect(wrapper.find('.icon-class-name').exists()).toBe(true)
+ })
+
+ it('should render with component icon', () => {
+ const IconComponent = {
+ name: 'TestIcon',
+ render() {
+ return h('span', { class: 'test-icon' }, 'Icon')
+ },
+ }
+
+ const wrapper = mount(TimelineItem, {
+ props: {
+ timestamp: '2018/4/12',
+ icon: IconComponent,
+ },
+ slots: {
+ default: 'Test content',
+ },
+ })
+
+ expect(wrapper.find('.ccui-timeline-item__icon').exists()).toBe(true)
+ expect(wrapper.find('.test-icon').exists()).toBe(true)
+ })
+
+ it('should render with custom dot slot', () => {
+ const wrapper = mount(TimelineItem, {
+ props: {
+ timestamp: '2018/4/12',
+ },
+ slots: {
+ default: 'Test content',
+ dot: 'Custom
',
+ },
+ })
+
+ expect(wrapper.find('.ccui-timeline-item__dot').exists()).toBe(true)
+ expect(wrapper.find('.custom-dot').exists()).toBe(true)
+ })
})