-
Notifications
You must be signed in to change notification settings - Fork 5
feat: 新增 SkCountDown 倒计时组件 #47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
👷 Deploy request for skiyee-ui pending review.Visit the deploys page to approve it
|
总体概述本 PR 新增了一个 Vue 3 倒计时组件(sk-count-down),支持自定义格式、暂停/恢复/重置控制。同时添加了四个演示页面展示基础用法、手动控制、自定义插槽样式和多种时间格式。所有包的版本从 1.1.0 升级至 1.1.1。 变更总览
代码审查工作量评估🎯 3 (中等) | ⏱️ ~20-30 分钟 需要重点关注的部分:
诗歌
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Tip 📝 Customizable high-level summaries are now available in beta!You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.
Example instruction:
Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Nitpick comments (1)
packages/skiyee-uni-ui/src/components/sk-count-down.vue (1)
149-187: 建议消除重复代码。
tick函数中的两个分支(time 模式和 target 模式)包含几乎完全相同的逻辑。可以提取公共逻辑以提高代码可维护性。建议重构为:
function tick() { - if (props.time) { - // 倒计时模式(基于 duration) - // 注意:这种方式在暂停/恢复时可能需要调整,这里简化处理,每次 tick 减去 interval - // 更精确的做法是记录 startTime 和 pauseTime - // 但为了兼容 target 模式,我们统一使用 endTime 逻辑会更准,但 time 模式下 pause/start 需要重置 endTime - - // 简单实现:直接减去 interval,可能受执行时间影响导致不准 - // 优化实现:使用 endTime - const now = Date.now() - if (now >= endTime) { - remain.value = 0 - pause() - emit('finish') - } - else { - remain.value = endTime - now - timer = setTimeout(() => { - tick() - }, props.interval) - } - } - else { - // 目标时间模式 - const now = Date.now() - if (now >= endTime) { - remain.value = 0 - pause() - emit('finish') - } - else { - remain.value = endTime - now - timer = setTimeout(() => { - tick() - }, props.interval) - } - } + const now = Date.now() + if (now >= endTime) { + remain.value = 0 + pause() + emit('finish') + } + else { + remain.value = endTime - now + timer = setTimeout(() => { + tick() + }, props.interval) + } emit('change', timeData.value) }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
examples/uni/package.json(1 hunks)examples/uni/src/pages-feedback/count-down/base.vue(1 hunks)examples/uni/src/pages-feedback/count-down/control.vue(1 hunks)examples/uni/src/pages-feedback/count-down/custom.vue(1 hunks)examples/uni/src/pages-feedback/count-down/format.vue(1 hunks)examples/uni/src/pages.json(2 hunks)examples/uni/types/components.d.ts(1 hunks)examples/uni/types/pages.d.ts(1 hunks)package.json(1 hunks)packages/skiyee-uni-ui/package.json(1 hunks)packages/skiyee-uni-ui/src/components/sk-count-down.vue(1 hunks)
🧰 Additional context used
🪛 ESLint
examples/uni/src/pages-feedback/count-down/control.vue
[error] 2-2: Extra semicolon.
(style/semi)
[error] 4-4: Extra semicolon.
(style/semi)
[error] 5-5: Extra semicolon.
(style/semi)
[error] 8-8: Extra semicolon.
(style/semi)
[error] 12-12: Extra semicolon.
(style/semi)
[error] 16-16: Extra semicolon.
(style/semi)
[error] 23-23: Extra semicolon.
(style/semi)
examples/uni/src/pages-feedback/count-down/custom.vue
[error] 2-2: Extra semicolon.
(style/semi)
[error] 4-4: Extra semicolon.
(style/semi)
🔇 Additional comments (16)
packages/skiyee-uni-ui/package.json (1)
4-4: 版本号升级合理。从 1.1.0 升级到 1.1.1,符合语义化版本规范,适用于新增 SkCountDown 组件的特性。
examples/uni/package.json (1)
4-4: 版本号升级一致。示例项目的版本号与主包保持同步,版本管理一致。
examples/uni/src/pages.json (1)
640-655: 页面配置正确。新增的 4 个倒计时示例页面配置结构正确,与现有页面格式保持一致。
package.json (1)
4-4: 根项目版本号同步更新。工作区根项目版本号与子包保持同步,符合版本管理最佳实践。
examples/uni/types/pages.d.ts (1)
160-163: 路由类型定义正确。新增的 4 个路由类型定义与 pages.json 中的页面配置保持一致,确保了类型安全的导航。
examples/uni/src/pages-feedback/count-down/custom.vue (1)
10-29: 自定义样式示例实现良好。两个自定义样式示例正确使用了作用域插槽,展示了不同的 UI 模式,代码逻辑清晰。
examples/uni/src/pages-feedback/count-down/format.vue (1)
1-38: 格式化示例实现完整。该页面全面展示了 SkCountDown 组件的多种时间格式选项,包括默认格式、自定义格式、中文格式以及毫秒精度格式,示例清晰且代码结构良好。
examples/uni/types/components.d.ts (1)
14-15: The review comment is based on outdated information that does not match the current file state.The file
examples/uni/types/components.d.tscurrently contains 32 lines and shows no SkCountDown or SKCountDown declarations. Lines 14-15 containSkDialogandSkFieldrespectively. Since this is an auto-generated file (as indicated by the header comment "Generated by vite-plugin-uni-components"), the duplicate component declarations mentioned in the review either:
- Have already been removed
- Never existed in this specific file
- Existed in a different version
No CountDown component variants exist anywhere in the current file, and all components consistently follow the
Sk*naming pattern (SkBadge, SkButton, SkDialog, etc.).examples/uni/src/pages-feedback/count-down/control.vue (1)
27-44: 代码实现正确!模板部分展示了手动控制倒计时的用法,组件 API 调用正确,演示逻辑清晰。
examples/uni/src/pages-feedback/count-down/base.vue (2)
1-25: 代码实现正确!脚本部分结构清晰,遵循 Vue 3 Composition API 最佳实践,控制函数实现简洁明了。
27-73: 示例代码完整全面!模板部分展示了组件的多种使用场景,包括基础用法、自定义格式、手动控制和自定义样式,为用户提供了良好的参考示例。
packages/skiyee-uni-ui/src/components/sk-count-down.vue (5)
1-52: 类型定义完善!组件的类型定义清晰完整,Props 接口文档详尽,TimeData 接口正确导出供外部使用。
130-147: 初始化逻辑实现正确!初始化函数正确处理了
time和target两种模式,优先级设置合理,并使用Math.max防止负值,自动启动逻辑符合预期。
189-209: 控制方法实现正确!
start、pause和reset方法的实现逻辑正确,正确处理了暂停/恢复场景中的endTime重新计算,防止了重复启动。
211-227: 生命周期管理完善!组件正确处理了属性变化监听、初始化和清理逻辑,防止了内存泄漏,公共 API 暴露合理。
230-242: 模板设计灵活!插槽设计提供了丰富的数据访问方式,既暴露了完整的时间数据对象,也提供了独立的属性访问,默认渲染逻辑合理。需要注意的是,此处的插槽数据会受到前面提到的
formatTime参数突变问题的影响。
| <script setup lang="ts"> | ||
| import { ref } from 'vue'; | ||
|
|
||
| const countDown = ref(); | ||
| const time = ref(5000); | ||
|
|
||
| function start() { | ||
| countDown.value?.start(); | ||
| } | ||
|
|
||
| function pause() { | ||
| countDown.value?.pause(); | ||
| } | ||
|
|
||
| function reset() { | ||
| countDown.value?.reset(); | ||
| } | ||
|
|
||
| function onFinish() { | ||
| uni.showToast({ | ||
| title: '倒计时结束', | ||
| icon: 'none' | ||
| }); | ||
| } | ||
| </script> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
修复代码风格问题。
ESLint 检测到多处额外的分号。请移除这些分号以符合项目的代码风格规范。
建议应用以下修改:
-import { ref } from 'vue';
+import { ref } from 'vue'
-const countDown = ref();
-const time = ref(5000);
+const countDown = ref()
+const time = ref(5000)
function start() {
- countDown.value?.start();
+ countDown.value?.start()
}
function pause() {
- countDown.value?.pause();
+ countDown.value?.pause()
}
function reset() {
- countDown.value?.reset();
+ countDown.value?.reset()
}
function onFinish() {
uni.showToast({
title: '倒计时结束',
icon: 'none'
- });
+ })
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <script setup lang="ts"> | |
| import { ref } from 'vue'; | |
| const countDown = ref(); | |
| const time = ref(5000); | |
| function start() { | |
| countDown.value?.start(); | |
| } | |
| function pause() { | |
| countDown.value?.pause(); | |
| } | |
| function reset() { | |
| countDown.value?.reset(); | |
| } | |
| function onFinish() { | |
| uni.showToast({ | |
| title: '倒计时结束', | |
| icon: 'none' | |
| }); | |
| } | |
| </script> | |
| <script setup lang="ts"> | |
| import { ref } from 'vue' | |
| const countDown = ref() | |
| const time = ref(5000) | |
| function start() { | |
| countDown.value?.start() | |
| } | |
| function pause() { | |
| countDown.value?.pause() | |
| } | |
| function reset() { | |
| countDown.value?.reset() | |
| } | |
| function onFinish() { | |
| uni.showToast({ | |
| title: '倒计时结束', | |
| icon: 'none' | |
| }) | |
| } | |
| </script> |
🧰 Tools
🪛 ESLint
[error] 2-2: Extra semicolon.
(style/semi)
[error] 4-4: Extra semicolon.
(style/semi)
[error] 5-5: Extra semicolon.
(style/semi)
[error] 8-8: Extra semicolon.
(style/semi)
[error] 12-12: Extra semicolon.
(style/semi)
[error] 16-16: Extra semicolon.
(style/semi)
[error] 23-23: Extra semicolon.
(style/semi)
🤖 Prompt for AI Agents
In examples/uni/src/pages-feedback/count-down/control.vue around lines 1 to 25,
ESLint flagged extraneous semicolons; remove the unnecessary semicolons at the
ends of the import statement, ref declarations, and all function definitions so
the file follows project code-style (no trailing semicolons) while keeping code
structure and behavior unchanged.
| @@ -0,0 +1,31 @@ | |||
| <script setup lang="ts"> | |||
| import { ref } from 'vue'; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
修复代码风格问题。
ESLint 检测到多余的分号。为保持代码风格一致性,建议移除 import 语句末尾的分号。
应用以下修改:
-import { ref } from 'vue';
+import { ref } from 'vue'📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { ref } from 'vue'; | |
| import { ref } from 'vue' |
🧰 Tools
🪛 ESLint
[error] 2-2: Extra semicolon.
(style/semi)
🤖 Prompt for AI Agents
在 examples/uni/src/pages-feedback/count-down/custom.vue 第2行,import 语句末尾包含多余的分号导致
ESLint 报告代码风格问题;请删除该行结尾的分号以符合项目的代码风格规则并通过 lint 检查,保持文件中所有 import 语句一致性。
| <script setup lang="ts"> | ||
| import { ref } from 'vue'; | ||
|
|
||
| const time = ref(30 * 60 * 1000); // 30 minutes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
修复代码风格问题。
建议移除此行末尾的分号,与项目代码风格保持一致。
应用以下修改:
-const time = ref(30 * 60 * 1000); // 30 minutes
+const time = ref(30 * 60 * 1000) // 30 minutes📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const time = ref(30 * 60 * 1000); // 30 minutes | |
| const time = ref(30 * 60 * 1000) // 30 minutes |
🧰 Tools
🪛 ESLint
[error] 4-4: Extra semicolon.
(style/semi)
🤖 Prompt for AI Agents
In examples/uni/src/pages-feedback/count-down/custom.vue around line 4, the
statement "const time = ref(30 * 60 * 1000);" has a trailing semicolon that
violates the project code style; remove the semicolon at the end of the line so
it reads without the semicolon and ensure the file follows the project's
no-semicolon style.
| function formatTime(format: string | ((time: TimeData) => string), time: TimeData): string { | ||
| if (typeof format === 'function') { | ||
| return format(time) | ||
| } | ||
|
|
||
| let text = format | ||
|
|
||
| if (text.includes('DD')) { | ||
| text = text.replace('DD', fixedZero(time.days)) | ||
| } | ||
| else { | ||
| // 如果没有 DD,将天数转换为小时 | ||
| time.hours += time.days * 24 | ||
| } | ||
|
|
||
| if (text.includes('HH')) { | ||
| text = text.replace('HH', fixedZero(time.hours)) | ||
| } | ||
| if (text.includes('mm')) { | ||
| text = text.replace('mm', fixedZero(time.minutes)) | ||
| } | ||
| if (text.includes('ss')) { | ||
| text = text.replace('ss', fixedZero(time.seconds)) | ||
| } | ||
| if (text.includes('SSS')) { | ||
| text = text.replace('SSS', fixedZero(time.milliseconds).padStart(3, '0')) | ||
| } | ||
| if (text.includes('SS')) { | ||
| text = text.replace('SS', fixedZero(Math.floor(time.milliseconds / 10))) | ||
| } | ||
| if (text.includes('S')) { | ||
| text = text.replace('S', `${Math.floor(time.milliseconds / 100)}`) | ||
| } | ||
|
|
||
| return text | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
修复参数对象突变导致的数据不一致问题。
formatTime 函数在第 105 行修改了传入的 time 对象(time.hours += time.days * 24)。由于 timeData 是一个 computed 属性,其值会被 Vue 缓存,当 formatTime 修改缓存的对象后,会影响模板中插槽接收到的数据。
示例问题场景:
- 当格式为
'HH:mm:ss'(不包含 DD)且剩余 2 天 3 小时时 formatTime会将time.hours修改为51(3 + 2*24)- 插槽会接收到
{ days: 2, hours: 51 },这在语义上是不一致的 - 用户在自定义插槽中可能会显示 "2 天 51 小时",造成困惑
建议使用局部变量而不是修改参数对象:
function formatTime(format: string | ((time: TimeData) => string), time: TimeData): string {
if (typeof format === 'function') {
return format(time)
}
let text = format
+ let { days, hours, minutes, seconds, milliseconds } = time
if (text.includes('DD')) {
- text = text.replace('DD', fixedZero(time.days))
+ text = text.replace('DD', fixedZero(days))
}
else {
// 如果没有 DD,将天数转换为小时
- time.hours += time.days * 24
+ hours = hours + days * 24
}
if (text.includes('HH')) {
- text = text.replace('HH', fixedZero(time.hours))
+ text = text.replace('HH', fixedZero(hours))
}
if (text.includes('mm')) {
- text = text.replace('mm', fixedZero(time.minutes))
+ text = text.replace('mm', fixedZero(minutes))
}
if (text.includes('ss')) {
- text = text.replace('ss', fixedZero(time.seconds))
+ text = text.replace('ss', fixedZero(seconds))
}
if (text.includes('SSS')) {
- text = text.replace('SSS', fixedZero(time.milliseconds).padStart(3, '0'))
+ text = text.replace('SSS', fixedZero(milliseconds).padStart(3, '0'))
}
if (text.includes('SS')) {
- text = text.replace('SS', fixedZero(Math.floor(time.milliseconds / 10)))
+ text = text.replace('SS', fixedZero(Math.floor(milliseconds / 10)))
}
if (text.includes('S')) {
- text = text.replace('S', `${Math.floor(time.milliseconds / 100)}`)
+ text = text.replace('S', `${Math.floor(milliseconds / 100)}`)
}
return text
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function formatTime(format: string | ((time: TimeData) => string), time: TimeData): string { | |
| if (typeof format === 'function') { | |
| return format(time) | |
| } | |
| let text = format | |
| if (text.includes('DD')) { | |
| text = text.replace('DD', fixedZero(time.days)) | |
| } | |
| else { | |
| // 如果没有 DD,将天数转换为小时 | |
| time.hours += time.days * 24 | |
| } | |
| if (text.includes('HH')) { | |
| text = text.replace('HH', fixedZero(time.hours)) | |
| } | |
| if (text.includes('mm')) { | |
| text = text.replace('mm', fixedZero(time.minutes)) | |
| } | |
| if (text.includes('ss')) { | |
| text = text.replace('ss', fixedZero(time.seconds)) | |
| } | |
| if (text.includes('SSS')) { | |
| text = text.replace('SSS', fixedZero(time.milliseconds).padStart(3, '0')) | |
| } | |
| if (text.includes('SS')) { | |
| text = text.replace('SS', fixedZero(Math.floor(time.milliseconds / 10))) | |
| } | |
| if (text.includes('S')) { | |
| text = text.replace('S', `${Math.floor(time.milliseconds / 100)}`) | |
| } | |
| return text | |
| } | |
| function formatTime(format: string | ((time: TimeData) => string), time: TimeData): string { | |
| if (typeof format === 'function') { | |
| return format(time) | |
| } | |
| let text = format | |
| let { days, hours, minutes, seconds, milliseconds } = time | |
| if (text.includes('DD')) { | |
| text = text.replace('DD', fixedZero(days)) | |
| } | |
| else { | |
| // 如果没有 DD,将天数转换为小时 | |
| hours = hours + days * 24 | |
| } | |
| if (text.includes('HH')) { | |
| text = text.replace('HH', fixedZero(hours)) | |
| } | |
| if (text.includes('mm')) { | |
| text = text.replace('mm', fixedZero(minutes)) | |
| } | |
| if (text.includes('ss')) { | |
| text = text.replace('ss', fixedZero(seconds)) | |
| } | |
| if (text.includes('SSS')) { | |
| text = text.replace('SSS', fixedZero(milliseconds).padStart(3, '0')) | |
| } | |
| if (text.includes('SS')) { | |
| text = text.replace('SS', fixedZero(Math.floor(milliseconds / 10))) | |
| } | |
| if (text.includes('S')) { | |
| text = text.replace('S', `${Math.floor(milliseconds / 100)}`) | |
| } | |
| return text | |
| } |
🤖 Prompt for AI Agents
In packages/skiyee-uni-ui/src/components/sk-count-down.vue around lines 93 to
128, formatTime currently mutates the incoming time object at line ~105
(time.hours += time.days * 24) which causes Vue computed-cached data to change
and leads to inconsistent values downstream; fix by not mutating the parameter:
create local copies (e.g., const days = time.days, const hours = time.hours +
(text.includes('DD') ? 0 : days * 24) or compute a local hours variable when DD
is absent) and use those locals for replacements (replace HH with
fixedZero(hours)) while leaving the original time object untouched; ensure
millisecond formatting still uses the same local values when computing S/SS/SSS.
Summary by CodeRabbit
发布说明
新功能
Chores
✏️ Tip: You can customize this high-level summary in your review settings.