-
Notifications
You must be signed in to change notification settings - Fork 297
refactor: refactor mobile site #2808
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
Conversation
WalkthroughThis pull request spans a wide range of updates across the project. Numerous mobile demo Vue files have been tidied by removing extra empty lines and updating import paths. Several API definition files for chart and UI components have been modified by adding, removing, or updating “mobileDemo” properties and standardizing type declarations. New documentation components, utility hooks, and composition functions have been added in the components‐doc area. In addition, renderless logic in mobile components and theme variable definitions have been refined. Overall, the changes streamline configurations, improve consistency, and update styling and documentation throughout the codebase. Changes
Sequence Diagram(s)sequenceDiagram
participant Caller as Client
participant Hook as useTasksFinish Hook
Caller->>Hook: call finishTask()
Note right of Hook: Increment finishNum
alt finishNum < tasksNum
Note right of Hook: Promise remains pending
else finishNum == tasksNum
Hook->>Hook: Set isAllFinish to true and resolve internal promise
Hook->>Caller: waitTasks() resolves (all tasks finished)
end
Possibly related PRs
Poem
✨ Finishing Touches
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
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: 2
🧹 Nitpick comments (2)
examples/sites/demos/mobile/app/button/event.vue (1)
39-41
: Consider mobile-specific height handling.While changing
overflow-y
toauto
improves the UI by showing scrollbars only when needed, using a fixedheight: 100%
might cause issues on mobile devices with dynamic viewport heights (e.g., when the keyboard appears).Consider using
min-height
or viewport units:.button-wrap { padding: 0 10px; overflow-y: auto; - height: 100%; + min-height: 100vh; }examples/sites/demos/mobile/app/button/size.vue (1)
46-48
: Consider grid layout for button examples.The overflow handling looks good, but with multiple button examples, consider using a responsive grid layout to improve the mobile viewing experience.
Add a grid layout for better organization:
.button-wrap { padding: 0 10px; overflow-y: auto; height: 100%; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + gap: 16px; } .button-wrap .tiny-mobile-button { - margin-right: 16px; - margin-bottom: 16px; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
examples/sites/demos/mobile/app/button/base.vue
(1 hunks)examples/sites/demos/mobile/app/button/disabled.vue
(1 hunks)examples/sites/demos/mobile/app/button/event.vue
(1 hunks)examples/sites/demos/mobile/app/button/loading.vue
(1 hunks)examples/sites/demos/mobile/app/button/reset-time.vue
(1 hunks)examples/sites/demos/mobile/app/button/size.vue
(1 hunks)examples/sites/demos/mobile/app/input/textarea-scalable.vue
(1 hunks)examples/sites/demos/mobile/app/input/type.vue
(1 hunks)examples/sites/demos/mobile/app/list/imformation-list.vue
(1 hunks)examples/sites/demos/mobile/app/radio/base.vue
(1 hunks)examples/sites/demos/mobile/app/radio/disabled.vue
(1 hunks)examples/sites/demos/mobile/app/radio/event.vue
(1 hunks)examples/sites/demos/mobile/app/radio/label.vue
(1 hunks)examples/sites/src/views/components/components.vue
(2 hunks)
✅ Files skipped from review due to trivial changes (11)
- examples/sites/demos/mobile/app/radio/disabled.vue
- examples/sites/demos/mobile/app/radio/label.vue
- examples/sites/demos/mobile/app/radio/base.vue
- examples/sites/demos/mobile/app/button/disabled.vue
- examples/sites/demos/mobile/app/radio/event.vue
- examples/sites/demos/mobile/app/input/textarea-scalable.vue
- examples/sites/demos/mobile/app/button/reset-time.vue
- examples/sites/demos/mobile/app/input/type.vue
- examples/sites/demos/mobile/app/button/base.vue
- examples/sites/demos/mobile/app/button/loading.vue
- examples/sites/demos/mobile/app/list/imformation-list.vue
<div style="width: 300px; height: 500px"> | ||
<iframe width="100%" height="100%" :src="mobilePreviewUrl" frameborder="0"></iframe> | ||
</div> |
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.
🛠️ Refactor suggestion
Enhance the mobile preview container for better responsiveness and accessibility.
The mobile preview container needs improvements in several areas:
- Replace fixed dimensions with responsive units
- Add accessibility attributes
- Add loading and error states
- <div style="width: 300px; height: 500px">
- <iframe width="100%" height="100%" :src="mobilePreviewUrl" frameborder="0"></iframe>
+ <div class="mobile-preview-container">
+ <iframe
+ class="mobile-preview-frame"
+ :src="mobilePreviewUrl"
+ :title="i18nByKey('mobile-preview')"
+ frameborder="0"
+ @load="handleIframeLoad"
+ @error="handleIframeError"
+ ></iframe>
+ <div v-if="isIframeLoading" class="mobile-preview-loading">
+ <tiny-loading></tiny-loading>
+ </div>
+ <div v-if="iframeError" class="mobile-preview-error">
+ {{ i18nByKey('preview-error') }}
+ </div>
</div>
Add these styles to your <style>
section:
.mobile-preview-container {
width: min(300px, 100%);
height: 500px;
position: relative;
margin: 0 auto;
}
.mobile-preview-frame {
width: 100%;
height: 100%;
border: none;
}
.mobile-preview-loading,
.mobile-preview-error {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.9);
}
@@ -705,9 +708,11 @@ | |||
|
|||
const hasKey = (apiArr, key) => !apiArr.every((item) => item[key] === undefined) | |||
|
|||
const mobilePreviewUrl = `${import.meta.env.VITE_MOBILE_URL}?component=button&demo=base.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.
🛠️ Refactor suggestion
Make the mobile preview URL dynamic based on current component and demo.
The current implementation uses hardcoded values for component and demo parameters. This should be dynamic based on the current context.
- const mobilePreviewUrl = `${import.meta.env.VITE_MOBILE_URL}?component=button&demo=base.vue`
+ const mobilePreviewUrl = computed(() => {
+ if (!import.meta.env.VITE_MOBILE_URL) {
+ console.error('Mobile preview URL environment variable is not defined')
+ return ''
+ }
+ const component = state.cmpId
+ const demo = state.singleDemo?.demoId || ''
+ return `${import.meta.env.VITE_MOBILE_URL}?component=${component}&demo=${demo}`
+ })
Also applies to: 715-715
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: 1
🧹 Nitpick comments (3)
examples/sites/src/views/components/demo.vue (3)
56-60
: Add aria-label to improve accessibility.The mobile view button should have an aria-label for better screen reader support.
- <div class="mobile-view-btn"> - <tiny-button>{{ i18nByKey('yan-shi') }}</tiny-button> + <div class="mobile-view-btn"> + <tiny-button aria-label="Mobile preview">{{ i18nByKey('yan-shi') }}</tiny-button>
67-70
: Consider adding placeholder content or loading state.The mobile view container is empty. Consider adding a placeholder or loading state to improve user experience.
- <div class="phone-container"></div> + <div class="phone-container"> + <div class="phone-container-placeholder" aria-label="Mobile preview loading"> + <tiny-loading v-if="isLoading" /> + <div v-else class="placeholder-text">{{ i18nByKey('preview-placeholder') }}</div> + </div> + </div>
72-76
: Consider extracting repeated width logic.The width styling is duplicated from the demo content. Consider extracting it to a computed property or CSS variable for better maintainability.
+<script> export default { // ... other code ... computed: { + containerWidth() { + return this.demoConfig.isMobile ? 'width:60%' : 'width:100%' + } } } </script> <template> - <div class="demo-content" :style="demoConfig.isMobile ? 'width:60%' : 'width:100%'"> + <div class="demo-content" :style="containerWidth"> <!-- ... --> - <div class="demo-code" :style="demoConfig.isMobile ? 'width:60%' : 'width:100%'"> + <div class="demo-code" :style="containerWidth">
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
examples/sites/src/views/components/demo.vue
(3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: PR E2E Test (pnpm test:e2e3)
🔇 Additional comments (1)
examples/sites/src/views/components/demo.vue (1)
8-8
: LGTM! Clean implementation of responsive width.The conditional width styling provides a good balance between mobile and desktop layouts.
height: 600px; | ||
border: 0; | ||
position: relative; | ||
position: fixed; | ||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYsAAAMFCAYAAACFxdoKAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDY3IDc5LjE1Nzc0NywgMjAxNS8wMy8zMC0yMzo0MDo0MiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjg3MTZDNTAwMTI1MzExRUE5Mzk1OTFBNjdDQ0JBNUQ5IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjg3MTZDNTAxMTI1MzExRUE5Mzk1OTFBNjdDQ0JBNUQ5Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6ODcxNkM0RkUxMjUzMTFFQTkzOTU5MUE2N0NDQkE1RDkiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6ODcxNkM0RkYxMjUzMTFFQTkzOTU5MUE2N0NDQkE1RDkiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7YMDcrAAAcl0lEQVR42uzd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) | ||
no-repeat center top; | ||
background-size: contain; | ||
|
||
.mobile-view-container { | ||
width: 360px; | ||
height: 613px; | ||
position: absolute; | ||
left: 11px; | ||
top: 79px; | ||
transform: i18nByKeyX(0); | ||
overflow: hidden; | ||
} | ||
left: 65%; | ||
right: 10%; | ||
top: 30%; |
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.
🛠️ Refactor suggestion
Improve responsive layout handling.
The fixed positioning with percentage-based offsets might cause layout issues on different screen sizes. Consider:
- Using CSS Grid or Flexbox for more flexible positioning
- Adding media queries for better responsiveness
- Using viewport units instead of fixed percentages
.phone-container {
width: 395px;
height: 600px;
border: 0;
position: fixed;
- left: 65%;
- right: 10%;
- top: 30%;
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ max-width: 90vw;
+ max-height: 80vh;
}
+
+@media screen and (max-width: 768px) {
+ .phone-container {
+ width: 100%;
+ height: auto;
+ aspect-ratio: 395/600;
+ }
+}
Committable suggestion skipped: line range outside the PR's diff.
7445257
to
a67e83e
Compare
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: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
examples/sites/src/views/components/components.vue
(11 hunks)examples/sites/src/views/components/demo.vue
(6 hunks)
🔇 Additional comments (9)
examples/sites/src/views/components/demo.vue (7)
11-11
: Good use of centralized state management!The transition to using
state.langKey
,state.copyTip
, andstate.copyIcon
improves reactivity and state management.Also applies to: 18-18, 21-21
Line range hint
38-38
: Well-implemented event handling for mobile preview!The
@get-iframe-demo
event handler is consistently applied in both default and single demo modes, ensuring the mobile preview functionality works uniformly.Also applies to: 42-48
56-58
: Good improvements to mobile view container!The changes enhance clarity through:
- More semantic class naming with
mobile-demo-container
- Clear user interaction with the "演示" (demo) button for mobile preview
Line range hint
84-115
: Excellent adoption of Vue 3 best practices!The changes demonstrate good practices:
- Using
<script setup>
syntax with TypeScript- Well-defined props with proper types
- Explicit emits declaration
119-139
: Well-structured component and state setup!Good practices observed:
- Component name defined for debugging
- Logically grouped state properties
- Proper use of reactive state for UI elements
372-379
: Improve responsive layout handling.The fixed positioning with percentage-based offsets might cause layout issues on different screen sizes. Consider:
- Using CSS Grid or Flexbox for more flexible positioning
- Adding media queries for better responsiveness
- Using viewport units instead of fixed percentages
Also applies to: 1105-1128
Line range hint
266-332
: Verify the demo file extension assumption.The code assumes all demo files have a .vue extension when constructing the iframe URL. This might cause issues if demos use different file extensions.
Run this script to check for non-.vue demo files:
examples/sites/src/views/components/components.vue (2)
164-171
: Enhance the mobile preview container for better responsiveness and accessibility.The mobile preview container needs improvements in several areas:
- Replace fixed dimensions with responsive units
- Add accessibility attributes
- Add loading and error states
581-586
: Make the mobile preview URL dynamic based on current component and demo.The current implementation uses hardcoded values for component and demo parameters. This should be dynamic based on the current context.
if (demoConfig.isMobile) { | ||
// 点击目录列表更新iframe显示 | ||
const hashId = data.link.slice(1) | ||
getIframeConetent(state.cmpId, hashId + '.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.
Add input validation for hash IDs.
The direct use of hash IDs in URL construction without validation could lead to XSS vulnerabilities. Consider:
- Validating hash IDs against allowed patterns
- Encoding special characters in the URL
Apply this diff to add validation:
- const hashId = data.link.slice(1)
- getIframeConetent(state.cmpId, hashId + '.vue')
+ const hashId = data.link.slice(1)
+ if (!/^[a-zA-Z0-9-_]+$/.test(hashId)) {
+ console.error('Invalid hash ID')
+ return
+ }
+ const encodedHash = encodeURIComponent(hashId)
+ getIframeConetent(state.cmpId, encodedHash + '.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.
if (demoConfig.isMobile) { | |
// 点击目录列表更新iframe显示 | |
const hashId = data.link.slice(1) | |
getIframeConetent(state.cmpId, hashId + '.vue') | |
} | |
if (demoConfig.isMobile) { | |
// 点击目录列表更新iframe显示 | |
const hashId = data.link.slice(1) | |
if (!/^[a-zA-Z0-9-_]+$/.test(hashId)) { | |
console.error('Invalid hash ID') | |
return | |
} | |
const encodedHash = encodeURIComponent(hashId) | |
getIframeConetent(state.cmpId, encodedHash + '.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.
Actionable comments posted: 5
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
examples/sites/vite-dosearch-plugin.js
(0 hunks)examples/sites/vite.config.ts
(3 hunks)examples/sites/vite.extend.ts
(1 hunks)
💤 Files with no reviewable changes (1)
- examples/sites/vite-dosearch-plugin.js
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: PR E2E Test (pnpm test:e2e3)
🔇 Additional comments (1)
examples/sites/vite.config.ts (1)
137-141
: Ensure 'tinyCustom.copyTarget' is properly defined after modifying the config.When adding the
viteStaticCopy
plugin, you accessnewViteConfig.tinyCustom.copyTarget
. Verify thattinyCustom.copyTarget
remains defined aftermodifyViteConfig
is called.
const demosPath = '../plusdocs/pc' | ||
const apisPath = '../plusdocs/apis' | ||
viteConfig.resolve.alias['@menu'] = path.resolve(demosPath) | ||
viteConfig.resolve.alias['@demo'] = path.resolve(demosPath) | ||
viteConfig.tinyCustom.copyTarget = [ | ||
{ | ||
src: `${demosPath}/*`, | ||
dest: '@demos' | ||
}, | ||
{ | ||
src: `${apisPath}/*`, | ||
dest: '@demos/apis' | ||
} | ||
] | ||
} |
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.
Ensure 'tinyCustom.copyTarget' and 'resolve.alias' are defined in 'plus' mode.
In the plus
function, you're assigning values to viteConfig.tinyCustom.copyTarget
and viteConfig.resolve.alias
, but these objects may not be initialized. This might cause runtime errors if they are undefined.
Initialize the properties before assignment:
+viteConfig.resolve = viteConfig.resolve || {}
+viteConfig.resolve.alias = viteConfig.resolve.alias || {}
+viteConfig.tinyCustom = viteConfig.tinyCustom || {}
+viteConfig.tinyCustom.copyTarget = viteConfig.tinyCustom.copyTarget || []
const demosPath = '../plusdocs/pc'
const apisPath = '../plusdocs/apis'
📝 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 demosPath = '../plusdocs/pc' | |
const apisPath = '../plusdocs/apis' | |
viteConfig.resolve.alias['@menu'] = path.resolve(demosPath) | |
viteConfig.resolve.alias['@demo'] = path.resolve(demosPath) | |
viteConfig.tinyCustom.copyTarget = [ | |
{ | |
src: `${demosPath}/*`, | |
dest: '@demos' | |
}, | |
{ | |
src: `${apisPath}/*`, | |
dest: '@demos/apis' | |
} | |
] | |
} | |
viteConfig.resolve = viteConfig.resolve || {} | |
viteConfig.resolve.alias = viteConfig.resolve.alias || {} | |
viteConfig.tinyCustom = viteConfig.tinyCustom || {} | |
viteConfig.tinyCustom.copyTarget = viteConfig.tinyCustom.copyTarget || [] | |
const demosPath = '../plusdocs/pc' | |
const apisPath = '../plusdocs/apis' | |
viteConfig.resolve.alias['@menu'] = path.resolve(demosPath) | |
viteConfig.resolve.alias['@demo'] = path.resolve(demosPath) | |
viteConfig.tinyCustom.copyTarget = [ | |
{ | |
src: `${demosPath}/*`, | |
dest: '@demos' | |
}, | |
{ | |
src: `${apisPath}/*`, | |
dest: '@demos/apis' | |
} | |
] | |
} |
mobile(viteConfig) { | ||
viteConfig.build.rollupOptions.input.mobile = path.resolve(__dirname, './mobile.html') | ||
}, |
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.
Initialize 'build.rollupOptions.input' before assignment in 'mobile' mode.
In the mobile
function within modeModifyMap
, viteConfig.build.rollupOptions.input
may not be initialized. Assigning a value without initialization can lead to runtime errors.
Apply this diff to ensure the nested properties are initialized:
+viteConfig.build = viteConfig.build || {}
+viteConfig.build.rollupOptions = viteConfig.build.rollupOptions || {}
+viteConfig.build.rollupOptions.input = viteConfig.build.rollupOptions.input || {}
viteConfig.build.rollupOptions.input.mobile = path.resolve(__dirname, './mobile.html')
📝 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.
mobile(viteConfig) { | |
viteConfig.build.rollupOptions.input.mobile = path.resolve(__dirname, './mobile.html') | |
}, | |
mobile(viteConfig) { | |
viteConfig.build = viteConfig.build || {} | |
viteConfig.build.rollupOptions = viteConfig.build.rollupOptions || {} | |
viteConfig.build.rollupOptions.input = viteConfig.build.rollupOptions.input || {} | |
viteConfig.build.rollupOptions.input.mobile = path.resolve(__dirname, './mobile.html') | |
}, |
} | ||
} | ||
} | ||
import { delStaticPlugin, viteDocsearchPlugin, modifyViteConfig } from './vite.extend.ts' |
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.
Remove '.ts' extension from import path to ensure compatibility.
When importing modules, it's recommended to omit the file extension for consistency and to avoid potential issues with module resolution.
Apply this diff to correct the import statement:
-import { delStaticPlugin, viteDocsearchPlugin, modifyViteConfig } from './vite.extend.ts'
+import { delStaticPlugin, viteDocsearchPlugin, modifyViteConfig } from './vite.extend'
📝 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 { delStaticPlugin, viteDocsearchPlugin, modifyViteConfig } from './vite.extend.ts' | |
import { delStaticPlugin, viteDocsearchPlugin, modifyViteConfig } from './vite.extend' |
} | ||
} | ||
|
||
if (env.NODE_ENV === 'development') { | ||
viteConfig.define['process.env'] = {} | ||
} | ||
|
||
return viteConfig | ||
const newViteConfig = modifyViteConfig(viteConfig, env, { plugins: { viteStaticCopy } }) |
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.
Fix function call to 'modifyViteConfig': remove the unexpected third argument.
The function modifyViteConfig
is defined to accept two arguments (viteConfig
and env
), but it's being called with three arguments. This inconsistency may lead to unexpected behavior.
Apply this diff to correct the function call:
-const newViteConfig = modifyViteConfig(viteConfig, env, { plugins: { viteStaticCopy } })
+const newViteConfig = modifyViteConfig(viteConfig, env)
Committable suggestion skipped: line range outside the PR's diff.
…le modes on mobile devices will redirect to the first demo
…nto refactor-mobile-site
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: 3
🧹 Nitpick comments (1)
examples/sites/src/views/components/components.vue (1)
1126-1134
: Improve mobile view container positioning.The mobile view container and iframe container use fixed dimensions and positioning which could cause layout issues.
Apply this diff to improve the layout:
.mobile-view-container { - width: 400px; + width: min(400px, 100%); + margin-left: auto; } .mobile-iframe-container { position: relative; - top: 10%; - left: 14%; + top: min(10%, 60px); + left: 50%; + transform: translateX(-50%); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
examples/sites/src/views/components/components.vue
(12 hunks)
🔇 Additional comments (2)
examples/sites/src/views/components/components.vue (2)
38-38
: LGTM: Event handler consistently implemented.The
@get-iframe-demo
event handler is properly added to both single and multiple demo modes.Also applies to: 47-47
699-704
:⚠️ Potential issueAdd input validation for hash IDs.
The direct use of hash IDs in URL construction without validation could lead to XSS vulnerabilities.
Apply this diff to add validation:
if (demoConfig.isMobile) { // 点击目录列表更新iframe显示 const hashId = data.link.slice(1) + if (!/^[a-zA-Z0-9-_]+$/.test(hashId)) { + console.error('Invalid hash ID') + return + } getIframeConetent(state.cmpId, hashId + '.vue') }Likely invalid or redundant comment.
<div v-if="demoConfig.isMobile" class="mobile-view-container"> | ||
<!-- 移动端展示内容 --> | ||
<div class="phone-container"> | ||
<div style="width: 275px; height: 478px" class="mobile-iframe-container"> | ||
<iframe ref="iframeRef" width="100%" height="100%" :src="iframeUrl" frameborder="0"></iframe> | ||
</div> | ||
</div> | ||
</div> |
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.
🛠️ Refactor suggestion
Enhance the mobile preview container for better accessibility and responsiveness.
The mobile preview container needs improvements:
- The iframe lacks important accessibility attributes (title, aria-label)
- Fixed dimensions (275px x 478px) may not work well on all screen sizes
Apply this diff to improve accessibility and responsiveness:
- <div style="width: 275px; height: 478px" class="mobile-iframe-container">
- <iframe ref="iframeRef" width="100%" height="100%" :src="iframeUrl" frameborder="0"></iframe>
+ <div class="mobile-iframe-container">
+ <iframe
+ ref="iframeRef"
+ :title="i18nByKey('mobile-preview')"
+ :aria-label="i18nByKey('mobile-preview')"
+ class="mobile-preview-frame"
+ :src="iframeUrl"
+ frameborder="0"
+ ></iframe>
Add these styles:
.mobile-iframe-container {
width: min(275px, 100%);
height: 478px;
position: relative;
}
.mobile-preview-frame {
width: 100%;
height: 100%;
border: none;
}
const getIframeConetent = (demoId, demoName) => { | ||
const frameWindow = iframeRef.value.contentWindow | ||
frameWindow.postMessage({ from: 'tiny-vue-site', component: state.cmpId, demo: demoName }) | ||
router.push(`#${demoId}`) | ||
} |
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.
Add origin validation and error handling for postMessage communication.
The iframe communication lacks security measures and error handling:
- Missing origin validation for postMessage
- No error handling for messaging failures
Apply this diff to improve security and reliability:
const getIframeConetent = (demoId, demoName) => {
const frameWindow = iframeRef.value.contentWindow
- frameWindow.postMessage({ from: 'tiny-vue-site', component: state.cmpId, demo: demoName })
+ try {
+ const targetOrigin = import.meta.env.VITE_MOBILE_URL
+ frameWindow.postMessage(
+ { from: 'tiny-vue-site', component: state.cmpId, demo: demoName },
+ targetOrigin
+ )
+ } catch (error) {
+ console.error('Failed to send message to iframe:', error)
+ }
router.push(`#${demoId}`)
}
📝 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 getIframeConetent = (demoId, demoName) => { | |
const frameWindow = iframeRef.value.contentWindow | |
frameWindow.postMessage({ from: 'tiny-vue-site', component: state.cmpId, demo: demoName }) | |
router.push(`#${demoId}`) | |
} | |
const getIframeConetent = (demoId, demoName) => { | |
const frameWindow = iframeRef.value.contentWindow | |
try { | |
const targetOrigin = import.meta.env.VITE_MOBILE_URL | |
frameWindow.postMessage( | |
{ from: 'tiny-vue-site', component: state.cmpId, demo: demoName }, | |
targetOrigin | |
) | |
} catch (error) { | |
console.error('Failed to send message to iframe:', error) | |
} | |
router.push(`#${demoId}`) | |
} |
.phone-container { | ||
width: 395px; | ||
min-width: 395px; | ||
height: 600px; | ||
border: 0; | ||
position: fixed; | ||
left: 65%; | ||
right: 10%; | ||
top: 30%; | ||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYsAAAMFCAYAAACFxdoKAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDY3IDc5LjE1Nzc0NywgMjAxNS8wMy8zMC0yMzo0MDo0MiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjg3MTZDNTAwMTI1MzExRUE5Mzk1OTFBNjdDQ0JBNUQ5IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjg3MTZDNTAxMTI1MzExRUE5Mzk1OTFBNjdDQ0JBNUQ5Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6ODcxNkM0RkUxMjUzMTFFQTkzOTU5MUE2N0NDQkE1RDkiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6ODcxNkM0RkYxMjUzMTFFQTkzOTU5MUE2N0NDQkE1RDkiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7YMDcrAAAcl0lEQVR42uzd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) | ||
no-repeat center top; | ||
background-size: contain; | ||
z-index: 1; | ||
} |
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.
🛠️ Refactor suggestion
Improve responsiveness of the phone container.
The phone container uses fixed positioning and hardcoded dimensions which could cause layout issues on different screen sizes.
Apply this diff to improve responsiveness:
.phone-container {
- width: 395px;
- min-width: 395px;
+ width: min(395px, 100%);
+ min-width: min(395px, 100%);
height: 600px;
border: 0;
position: fixed;
- left: 65%;
- right: 10%;
+ right: max(10%, calc((100% - 395px) / 2));
top: 30%;
background: url(data:image/png;base64,...) no-repeat center top;
background-size: contain;
z-index: 1;
}
📝 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.
.phone-container { | |
width: 395px; | |
min-width: 395px; | |
height: 600px; | |
border: 0; | |
position: fixed; | |
left: 65%; | |
right: 10%; | |
top: 30%; | |
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYsAAAMFCAYAAACFxdoKAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDY3IDc5LjE1Nzc0NywgMjAxNS8wMy8zMC0yMzo0MDo0MiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjg3MTZDNTAwMTI1MzExRUE5Mzk1OTFBNjdDQ0JBNUQ5IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjg3MTZDNTAxMTI1MzExRUE5Mzk1OTFBNjdDQ0JBNUQ5Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6ODcxNkM0RkUxMjUzMTFFQTkzOTU5MUE2N0NDQkE1RDkiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6ODcxNkM0RkYxMjUzMTFFQTkzOTU5MUE2N0NDQkE1RDkiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7YMDcrAAAcl0lEQVR42uzd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) | |
no-repeat center top; | |
background-size: contain; | |
z-index: 1; | |
} | |
.phone-container { | |
width: min(395px, 100%); | |
min-width: min(395px, 100%); | |
height: 600px; | |
border: 0; | |
position: fixed; | |
right: max(10%, calc((100% - 395px) / 2)); | |
top: 30%; | |
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYsAAAMFCAYAAACFxdoKAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDY3IDc5LjE1Nzc0NywgMjAxNS8wMy8zMC0yMzo0MDo0MiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjg3MTZDNTAwMTI1MzExRUE5Mzk1OTFBNjdDQ0JBNUQ5IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjg3MTZDNTAxMTI1MzExRUE5Mzk1OTFBNjdDQ0JBNUQ5Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6ODcxNkM0RkUxMjUzMTFFQTkzOTU5MUE2N0NDQkE1RDkiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6ODcxNkM0RkYxMjUzMTFFQTkzOTU5MUE2N0NDQkE1RDkiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7YMDcrAAAcl0lEQVR42uzd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) | |
no-repeat center top; | |
background-size: contain; | |
z-index: 1; | |
} |
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: 1
🔭 Outside diff range comments (1)
examples/sites/src/views/components/demo.vue (1)
Line range hint
311-322
: Add error handling and loading states for iframe demo.The iframe demo loading lacks proper error handling and loading states:
- No loading indicator while demo loads
- No error handling if demo fails to load
- No cleanup of loading state on error
const getIframeDemo = (demo) => { + if (isIframeLoading.value) return + if (demo?.codeFiles.length > 0) { + isIframeLoading.value = true + iframeError.value = null + getIframeConetent(demo.demoId, demo.codeFiles[0]) jumpToMobileDemoAndHash(demo.demoId) state.currDemoId = demo.demoId state.currJson.demos.forEach((item) => { if (item.demoId === demo.demoId) { item.isIntersecting = true } }) + } else { + iframeError.value = i18nByKey('demo-not-found') } }
♻️ Duplicate comments (3)
examples/sites/src/views/components/components.vue (3)
164-171
: 🛠️ Refactor suggestionEnhance the mobile preview container for better accessibility and responsiveness.
The mobile preview container needs improvements:
- The iframe lacks important accessibility attributes (title, aria-label)
- Fixed dimensions (395px x 600px) may not work well on all screen sizes
<div v-if="demoConfig.isMobile" class="mobile-view-container"> <div class="phone-container"> <div class="mobile-iframe-container"> - <iframe ref="iframeRef" width="100%" height="100%" :src="iframeUrl" frameborder="0"></iframe> + <iframe + ref="iframeRef" + :title="i18nByKey('mobile-preview')" + :aria-label="i18nByKey('mobile-preview')" + class="mobile-preview-frame" + :src="iframeUrl" + frameborder="0" + ></iframe> </div> </div> </div>
701-706
:⚠️ Potential issueAdd input validation for hash IDs.
The direct use of hash IDs in URL construction without validation could lead to XSS vulnerabilities.
if (demoConfig.isMobile) { // 点击目录列表更新iframe显示 const hashId = data.link.slice(1) + if (!/^[a-zA-Z0-9-_]+$/.test(hashId)) { + console.error('Invalid hash ID') + return + } getIframeConetent(state.cmpId, hashId + '.vue') }
1113-1135
: 🛠️ Refactor suggestionImprove responsive layout handling.
The fixed positioning with percentage-based offsets might cause layout issues on different screen sizes.
.phone-container { - width: 394px; - height: 770px; + width: min(394px, 100%); + height: min(770px, 90vh); border: 0; position: fixed; - top: 280px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); background: url(...) no-repeat center top; background-size: cover; z-index: 1; } .mobile-view-container { - width: 400px; + width: min(400px, 100%); } .mobile-iframe-container { position: relative; top: 79px; left: 10px; - width: 360px; - height: 612px; + width: min(360px, 90%); + height: min(612px, 80vh); }
🧹 Nitpick comments (2)
examples/sites/mobile/App.vue (1)
71-103
: Consider using a CSS custom properties file for better maintainability.The scrollbar styling uses CSS variables but they're defined inline. Consider:
- Moving these CSS variables to a dedicated custom properties file
- Using semantic names for the variables (e.g.,
--scrollbar-width
instead of--tvm-size-scrollbar-width
)- ::-webkit-scrollbar { - width: var(--tvm-size-scrollbar-width); - height: var(--tvm-size-scrollbar-height); - } + @import '@/styles/variables.less'; + + ::-webkit-scrollbar { + width: var(--scrollbar-width); + height: var(--scrollbar-height); + }examples/sites/src/views/components/demo.vue (1)
56-60
: Add loading and error states for the mobile preview button.The mobile preview button lacks loading and error states. Consider:
- Adding a loading state while the iframe loads
- Adding error handling if the demo fails to load
<div v-else-if="demoConfig.isMobile" class="mobile-demo-container"> <div class="mobile-view-btn"> - <tiny-mobile-button type="secondary" @click="openIframe(demo)"> - {{ i18nByKey('yan-shi') }} - </tiny-mobile-button> + <tiny-mobile-button + type="secondary" + :loading="isIframeLoading" + :disabled="isIframeLoading" + @click="openIframe(demo)" + > + {{ isIframeLoading ? i18nByKey('loading') : i18nByKey('yan-shi') }} + </tiny-mobile-button> + <div v-if="iframeError" class="error-message"> + {{ i18nByKey('preview-error') }} + </div> </div> </div>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
examples/sites/mobile/App.vue
(2 hunks)examples/sites/src/views/components/components.vue
(12 hunks)examples/sites/src/views/components/demo.vue
(5 hunks)
🔇 Additional comments (2)
examples/sites/mobile/App.vue (1)
2-2
: LGTM! The class name aligns with the component's purpose.The addition of the "mobile-preview" class to the root div improves semantic clarity and maintainability.
examples/sites/src/views/components/demo.vue (1)
Line range hint
305-309
: Add origin validation and error handling for postMessage communication.The iframe communication lacks security measures and error handling:
- Missing origin validation for postMessage
- No error handling for messaging failures
const getIframeConetent = (demoId, demoName) => { const frameWindow = iframeRef.value.contentWindow - frameWindow.postMessage({ from: 'tiny-vue-site', component: state.cmpId, demo: demoName }) + try { + const targetOrigin = import.meta.env.VITE_MOBILE_URL + frameWindow.postMessage( + { from: 'tiny-vue-site', component: state.cmpId, demo: demoName }, + targetOrigin + ) + } catch (error) { + console.error('Failed to send message to iframe:', error) + } router.push(`#${demoId}`) }
if (demoConfig.isMobile && !hash) { | ||
// 初始化iframe,当前组件第一个demo展示 | ||
state.iframeUrl = `${import.meta.env.VITE_MOBILE_URL}?component=${state.cmpId}&demo=${ | ||
state.currJson?.demos[0].codeFiles[0] | ||
}` | ||
} else { | ||
state.iframeUrl = `${import.meta.env.VITE_MOBILE_URL}?component=${state.cmpId}&demo=${hash}.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.
Add input validation and error handling for iframe URL construction.
The iframe URL construction lacks validation and error handling:
- No validation of component and demo parameters
- No error handling if environment variable is missing
+const getIframeUrl = (component, demo) => {
+ if (!import.meta.env.VITE_MOBILE_URL) {
+ console.error('Mobile preview URL environment variable is not defined')
+ return ''
+ }
+ if (!component || !/^[a-zA-Z0-9-_]+$/.test(component)) {
+ console.error('Invalid component ID')
+ return ''
+ }
+ const demoFile = demo ? `${demo}.vue` : state.currJson?.demos[0]?.codeFiles[0]
+ if (!demoFile || !/^[a-zA-Z0-9-_/.]+$/.test(demoFile)) {
+ console.error('Invalid demo file')
+ return ''
+ }
+ return `${import.meta.env.VITE_MOBILE_URL}?component=${encodeURIComponent(component)}&demo=${encodeURIComponent(demoFile)}`
+}
if (demoConfig.isMobile && !hash) {
- state.iframeUrl = `${import.meta.env.VITE_MOBILE_URL}?component=${state.cmpId}&demo=${state.currJson?.demos[0].codeFiles[0]}`
+ state.iframeUrl = getIframeUrl(state.cmpId)
} else {
- state.iframeUrl = `${import.meta.env.VITE_MOBILE_URL}?component=${state.cmpId}&demo=${hash}.vue`
+ state.iframeUrl = getIframeUrl(state.cmpId, hash)
}
📝 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.
if (demoConfig.isMobile && !hash) { | |
// 初始化iframe,当前组件第一个demo展示 | |
state.iframeUrl = `${import.meta.env.VITE_MOBILE_URL}?component=${state.cmpId}&demo=${ | |
state.currJson?.demos[0].codeFiles[0] | |
}` | |
} else { | |
state.iframeUrl = `${import.meta.env.VITE_MOBILE_URL}?component=${state.cmpId}&demo=${hash}.vue` | |
} | |
const getIframeUrl = (component, demo) => { | |
if (!import.meta.env.VITE_MOBILE_URL) { | |
console.error('Mobile preview URL environment variable is not defined') | |
return '' | |
} | |
if (!component || !/^[a-zA-Z0-9-_]+$/.test(component)) { | |
console.error('Invalid component ID') | |
return '' | |
} | |
const demoFile = demo ? `${demo}.vue` : state.currJson?.demos[0]?.codeFiles[0] | |
if (!demoFile || !/^[a-zA-Z0-9-_/.]+$/.test(demoFile)) { | |
console.error('Invalid demo file') | |
return '' | |
} | |
return `${import.meta.env.VITE_MOBILE_URL}?component=${encodeURIComponent(component)}&demo=${encodeURIComponent(demoFile)}` | |
} | |
if (demoConfig.isMobile && !hash) { | |
state.iframeUrl = getIframeUrl(state.cmpId) | |
} else { | |
state.iframeUrl = getIframeUrl(state.cmpId, hash) | |
} |
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: 1
🔭 Outside diff range comments (1)
examples/sites/src/views/components/demo.vue (1)
Line range hint
1114-1136
: Improve phone container responsiveness and accessibility.The current implementation with fixed positioning and dimensions might cause issues. Consider:
- Using responsive units
- Adding accessibility attributes
- Handling different screen sizes
.phone-container { - width: 394px; - height: 770px; + width: min(394px, 90vw); + height: min(770px, 90vh); border: 0; position: fixed; - top: 280px; + top: 50%; + transform: translateY(-50%); background: url(data:image/png;base64,...) no-repeat center top; background-size: cover; z-index: 1; } .mobile-view-container { - width: 400px; + width: min(400px, 100%); } .mobile-iframe-container { position: relative; top: 79px; left: 10px; - width: 360px; - height: 612px; + width: calc(100% - 20px); + height: calc(100% - 158px); + overflow: hidden; + border-radius: 12px; }
🧹 Nitpick comments (1)
examples/sites/src/views/components/demo.vue (1)
372-379
: Consider enhancing mobile container responsiveness.While the current implementation works, consider these improvements:
- Add media queries for better mobile responsiveness
- Use CSS Grid or CSS variables for more flexible layouts
- Add transitions for smoother user experience
.mobile-demo-container { display: flex; flex-direction: column; background: #fff; border-radius: 6px; border: 1px solid #dcdfe6; padding: 26px 18px 42px; + @media (max-width: 768px) { + padding: 16px 12px 24px; + } + transition: all 0.3s ease; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
examples/sites/mobile/App.vue
(2 hunks)examples/sites/src/views/components/components.vue
(12 hunks)examples/sites/src/views/components/demo.vue
(6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- examples/sites/mobile/App.vue
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: PR E2E Test (pnpm test:e2e3)
🔇 Additional comments (8)
examples/sites/src/views/components/demo.vue (5)
11-11
: LGTM! Good use of reactive state management.The changes improve state management by centralizing reactive properties in a state object, which is a Vue best practice.
Also applies to: 18-18, 21-21
56-60
: LGTM! Clean implementation of mobile preview functionality.The mobile preview container and button implementation follows Vue's event handling patterns correctly.
Line range hint
84-115
: LGTM! Well-structured Vue 3 setup syntax.The migration to script setup syntax with proper typing of props and emits improves code organization and type safety.
119-139
: LGTM! Well-organized reactive state management.The state management implementation is clean with:
- Proper component name definition
- Well-structured reactive state object
- Correct computed property implementations
141-177
: LGTM! Effective use of Vue watchers.The watchers are well-implemented with:
- Proper use of immediate option for initial execution
- Correct deep watching for nested changes
- Efficient component loading handling
examples/sites/src/views/components/components.vue (3)
306-310
:⚠️ Potential issueAdd origin validation and error handling to postMessage.
The current postMessage implementation lacks security measures:
const getIframeConetent = (demoId, demoName) => { + if (!iframeRef.value?.contentWindow) { + console.error('Iframe not ready') + return + } const frameWindow = iframeRef.value.contentWindow - frameWindow.postMessage({ from: 'tiny-vue-site', component: state.cmpId, demo: demoName }) + try { + const targetOrigin = new URL(import.meta.env.VITE_MOBILE_URL).origin + frameWindow.postMessage( + { from: 'tiny-vue-site', component: state.cmpId, demo: demoName }, + targetOrigin + ) + } catch (error) { + console.error('Failed to send message to iframe:', error) + } router.push(`#${demoId}`) }Likely invalid or redundant comment.
163-170
:⚠️ Potential issueAdd security measures and accessibility attributes to iframe.
The current iframe implementation needs improvements:
- Add sandbox attribute for security
- Add title and aria-label for accessibility
- Add loading state handling
<div v-if="demoConfig.isMobile" class="mobile-view-container"> <!-- 移动端展示内容 --> <div class="phone-container"> <div class="mobile-iframe-container"> - <iframe ref="iframeRef" width="100%" height="100%" :src="iframeUrl" frameborder="0"></iframe> + <iframe + ref="iframeRef" + width="100%" + height="100%" + :src="iframeUrl" + :title="i18nByKey('mobile-preview')" + :aria-label="i18nByKey('mobile-preview')" + sandbox="allow-scripts allow-same-origin" + loading="lazy" + @load="handleIframeLoad" + @error="handleIframeError" + ></iframe> + <div v-if="isIframeLoading" class="iframe-loading"> + <tiny-loading></tiny-loading> + </div> </div> </div> </div>Likely invalid or redundant comment.
588-595
:⚠️ Potential issueAdd validation and error handling to iframe URL construction.
The current URL construction needs improvements:
- Validate environment variables
- Sanitize URL parameters
- Add error handling
+const getIframeUrl = (component, demo) => { + if (!import.meta.env.VITE_MOBILE_URL) { + console.error('Mobile preview URL environment variable is not defined') + return '' + } + const baseUrl = new URL(import.meta.env.VITE_MOBILE_URL) + const params = new URLSearchParams() + params.append('component', encodeURIComponent(component)) + params.append('demo', encodeURIComponent(demo)) + return `${baseUrl}?${params.toString()}` +} if (demoConfig.isMobile && !hash) { - state.iframeUrl = `${import.meta.env.VITE_MOBILE_URL}?component=${state.cmpId}&demo=${state.currJson?.demos[0].codeFiles[0]}` + state.iframeUrl = getIframeUrl( + state.cmpId, + state.currJson?.demos[0]?.codeFiles[0] || '' + ) } else { - state.iframeUrl = `${import.meta.env.VITE_MOBILE_URL}?component=${state.cmpId}&demo=${hash}.vue` + state.iframeUrl = getIframeUrl(state.cmpId, `${hash}.vue`) }Likely invalid or redundant comment.
const getIframeDemo = (demo) => { | ||
if (demo?.codeFiles.length > 0) { | ||
getIframeConetent(demo.demoId, demo.codeFiles[0]) | ||
jumpToMobileDemoAndHash(demo.demoId) | ||
state.currDemoId = demo.demoId | ||
state.currJson.demos.forEach((item) => { | ||
if (item.demoId === demo.demoId) { | ||
item.isIntersecting = true | ||
} | ||
}) | ||
} | ||
} | ||
|
||
const jumpToMobileDemoAndHash = (hash) => { | ||
if (apiModeState.demoMode === 'single') { | ||
const singleDemo = state.currJson.demos.find((d) => d.demoId === hash) | ||
|
||
// 单示例模式下如果没有匹配到锚点对应的示例,则这不加载示例直接跳转锚点id | ||
if (singleDemo) { | ||
state.singleDemo = singleDemo | ||
scrollToLayoutTop() | ||
} | ||
} else if (apiModeState.demoMode === 'default') { | ||
// 多示例模式,自动会切到相应的位置。只需要记录singleDemo就好了 | ||
state.singleDemo = state.currJson.demos.find((d) => d.demoId === hash) | ||
scrollByHash(hash) | ||
} | ||
} |
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.
Add input validation and error handling to demo and hash handling.
The current implementation needs improvements:
- Validate hash IDs
- Handle edge cases
- Add error boundaries
const getIframeDemo = (demo) => {
+ if (!demo?.codeFiles?.length) {
+ console.error('Invalid demo object')
+ return
+ }
if (demo?.codeFiles.length > 0) {
getIframeConetent(demo.demoId, demo.codeFiles[0])
jumpToMobileDemoAndHash(demo.demoId)
state.currDemoId = demo.demoId
state.currJson.demos.forEach((item) => {
if (item.demoId === demo.demoId) {
item.isIntersecting = true
}
})
}
}
const jumpToMobileDemoAndHash = (hash) => {
+ if (!hash || typeof hash !== 'string') {
+ console.error('Invalid hash')
+ return
+ }
+ if (!/^[a-zA-Z0-9-_]+$/.test(hash)) {
+ console.error('Invalid hash format')
+ return
+ }
if (apiModeState.demoMode === 'single') {
const singleDemo = state.currJson.demos.find((d) => d.demoId === hash)
if (singleDemo) {
state.singleDemo = singleDemo
scrollToLayoutTop()
}
} else if (apiModeState.demoMode === 'default') {
state.singleDemo = state.currJson.demos.find((d) => d.demoId === hash)
scrollByHash(hash)
}
}
📝 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 getIframeDemo = (demo) => { | |
if (demo?.codeFiles.length > 0) { | |
getIframeConetent(demo.demoId, demo.codeFiles[0]) | |
jumpToMobileDemoAndHash(demo.demoId) | |
state.currDemoId = demo.demoId | |
state.currJson.demos.forEach((item) => { | |
if (item.demoId === demo.demoId) { | |
item.isIntersecting = true | |
} | |
}) | |
} | |
} | |
const jumpToMobileDemoAndHash = (hash) => { | |
if (apiModeState.demoMode === 'single') { | |
const singleDemo = state.currJson.demos.find((d) => d.demoId === hash) | |
// 单示例模式下如果没有匹配到锚点对应的示例,则这不加载示例直接跳转锚点id | |
if (singleDemo) { | |
state.singleDemo = singleDemo | |
scrollToLayoutTop() | |
} | |
} else if (apiModeState.demoMode === 'default') { | |
// 多示例模式,自动会切到相应的位置。只需要记录singleDemo就好了 | |
state.singleDemo = state.currJson.demos.find((d) => d.demoId === hash) | |
scrollByHash(hash) | |
} | |
} | |
const getIframeDemo = (demo) => { | |
if (!demo?.codeFiles?.length) { | |
console.error('Invalid demo object') | |
return | |
} | |
if (demo?.codeFiles.length > 0) { | |
getIframeConetent(demo.demoId, demo.codeFiles[0]) | |
jumpToMobileDemoAndHash(demo.demoId) | |
state.currDemoId = demo.demoId | |
state.currJson.demos.forEach((item) => { | |
if (item.demoId === demo.demoId) { | |
item.isIntersecting = true | |
} | |
}) | |
} | |
} | |
const jumpToMobileDemoAndHash = (hash) => { | |
if (!hash || typeof hash !== 'string') { | |
console.error('Invalid hash') | |
return | |
} | |
if (!/^[a-zA-Z0-9-_]+$/.test(hash)) { | |
console.error('Invalid hash format') | |
return | |
} | |
if (apiModeState.demoMode === 'single') { | |
const singleDemo = state.currJson.demos.find((d) => d.demoId === hash) | |
if (singleDemo) { | |
state.singleDemo = singleDemo | |
scrollToLayoutTop() | |
} | |
} else if (apiModeState.demoMode === 'default') { | |
state.singleDemo = state.currJson.demos.find((d) => d.demoId === hash) | |
scrollByHash(hash) | |
} | |
} |
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: 0
🧹 Nitpick comments (2)
examples/sites/src/views/components/components.vue (2)
1099-1105
: Optimize the phone container background image.The background image is embedded as a base64 string, which increases the file size and isn't cacheable. Consider:
- Moving it to a separate image file
- Using a CDN or public assets directory
- background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYsAAAMFCAYAAACFxdoKAAAAGXRFWHRTb2Z0...) no-repeat center top; + background: url('@/assets/images/phone-frame.png') no-repeat center top;
Line range hint
1-1118
: Consider splitting the component into smaller, focused components.The component has grown large and handles multiple responsibilities:
- Documentation rendering
- Demo management
- Mobile preview
- API documentation
Consider extracting these into separate components for better maintainability:
DocumentationRenderer
DemoManager
MobilePreview
ApiDocumentation
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
examples/sites/src/views/components/components.vue
(8 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: PR E2E Test (pnpm test:e2e3)
🔇 Additional comments (4)
examples/sites/src/views/components/components.vue (4)
171-178
: Enhance mobile preview container accessibility and responsiveness.The mobile preview container needs improvements in accessibility and responsiveness:
- Add accessibility attributes to iframe (title, aria-label)
- Use responsive units instead of fixed dimensions
1096-1106
: Improve phone container responsiveness.The phone container uses fixed positioning and hardcoded dimensions which could cause layout issues.
352-356
: Add origin validation and error handling for iframe communication.The postMessage communication lacks security measures and error handling:
- Missing origin validation
- No error handling for messaging failures
634-641
: Add input validation for URL parameters.The iframe URL construction needs input validation to prevent XSS vulnerabilities:
- Validate component and demo parameters
- Encode parameters before URL construction
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: 5
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
examples/sites/vite.extend.ts
(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: build
🔇 Additional comments (1)
examples/sites/vite.extend.ts (1)
1-4
: LGTM! Good choice of dependencies.The imports are well-organized and use modern practices. Using
fs-extra
instead ofnode:fs
is a good choice as it provides additional useful filesystem operations.
plus(viteConfig) { | ||
const demosPath = '../plusdocs/pc' | ||
const apisPath = '../plusdocs/apis' | ||
viteConfig.resolve.alias['@menu'] = path.resolve(demosPath) | ||
viteConfig.resolve.alias['@demo'] = path.resolve(demosPath) | ||
viteConfig.tinyCustom.copyTarget = [ | ||
{ | ||
src: `${demosPath}/*`, | ||
dest: '@demos' | ||
}, | ||
{ | ||
src: `${apisPath}/*`, | ||
dest: '@demos/apis' | ||
} | ||
] | ||
} |
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.
🛠️ Refactor suggestion
Validate paths and preserve existing configuration in plus mode.
The plus mode configuration needs path validation and should preserve existing settings:
plus(viteConfig) {
const demosPath = '../plusdocs/pc'
const apisPath = '../plusdocs/apis'
+
+ // Validate paths
+ const resolvedDemosPath = path.resolve(demosPath)
+ const resolvedApisPath = path.resolve(apisPath)
+ if (!fs.existsSync(resolvedDemosPath)) {
+ console.warn(`[plus] Demos path does not exist: ${resolvedDemosPath}`)
+ }
+ if (!fs.existsSync(resolvedApisPath)) {
+ console.warn(`[plus] APIs path does not exist: ${resolvedApisPath}`)
+ }
+
+ // Initialize and preserve existing configuration
+ viteConfig.resolve = viteConfig.resolve || {}
+ viteConfig.resolve.alias = viteConfig.resolve.alias || {}
viteConfig.resolve.alias['@menu'] = path.resolve(demosPath)
viteConfig.resolve.alias['@demo'] = path.resolve(demosPath)
+
+ viteConfig.tinyCustom = viteConfig.tinyCustom || {}
+ const existingCopyTarget = viteConfig.tinyCustom.copyTarget || []
viteConfig.tinyCustom.copyTarget = [
+ ...existingCopyTarget,
{
src: `${demosPath}/*`,
dest: '@demos'
},
Committable suggestion skipped: line range outside the PR's diff.
export const viteDocsearchPlugin = (env) => { | ||
if (env.VITE_BUILD_TARGET !== 'inner') { | ||
return null | ||
} | ||
return { | ||
name: 'vite-docsearch-plugin', | ||
enforce: 'pre', | ||
transform(code, id) { | ||
if (id.includes('@docsearch')) { | ||
const newCode = code.replace( | ||
'Promise((function(t){var n=new XMLHttpRequest;', | ||
'Promise((function(t){t=window.handleGlobalSearchData(t);var n=new XMLHttpRequest;' | ||
) | ||
return { | ||
code: newCode, | ||
map: null | ||
} | ||
} | ||
} | ||
} | ||
} |
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.
🛠️ Refactor suggestion
Improve code transformation reliability.
The current string-based code transformation is fragile and could break with @docsearch updates. Consider these improvements:
- Add version check for @docsearch to ensure compatibility
- Use AST transformation instead of string replacement
- Add validation for the transformed code
- Add type checking for environment variables
export const viteDocsearchPlugin = (env) => {
+ // Type check environment variables
+ if (typeof env?.VITE_BUILD_TARGET !== 'string') {
+ console.warn('[docsearch] VITE_BUILD_TARGET is not properly defined')
+ return null
+ }
+
if (env.VITE_BUILD_TARGET !== 'inner') {
return null
}
+
return {
name: 'vite-docsearch-plugin',
enforce: 'pre',
transform(code, id) {
if (id.includes('@docsearch')) {
+ // Validate input code contains expected pattern
+ if (!code.includes('Promise((function(t){var n=new XMLHttpRequest;')) {
+ console.warn('[docsearch] Unexpected code pattern, skipping transformation')
+ return null
+ }
+
const newCode = code.replace(
'Promise((function(t){var n=new XMLHttpRequest;',
'Promise((function(t){t=window.handleGlobalSearchData(t);var n=new XMLHttpRequest;'
)
+
+ // Validate transformed code
+ if (!newCode.includes('window.handleGlobalSearchData')) {
+ console.error('[docsearch] Code transformation failed')
+ return null
+ }
+
return {
code: newCode,
map: null
}
}
}
}
}
📝 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.
export const viteDocsearchPlugin = (env) => { | |
if (env.VITE_BUILD_TARGET !== 'inner') { | |
return null | |
} | |
return { | |
name: 'vite-docsearch-plugin', | |
enforce: 'pre', | |
transform(code, id) { | |
if (id.includes('@docsearch')) { | |
const newCode = code.replace( | |
'Promise((function(t){var n=new XMLHttpRequest;', | |
'Promise((function(t){t=window.handleGlobalSearchData(t);var n=new XMLHttpRequest;' | |
) | |
return { | |
code: newCode, | |
map: null | |
} | |
} | |
} | |
} | |
} | |
export const viteDocsearchPlugin = (env) => { | |
// Type check environment variables | |
if (typeof env?.VITE_BUILD_TARGET !== 'string') { | |
console.warn('[docsearch] VITE_BUILD_TARGET is not properly defined') | |
return null | |
} | |
if (env.VITE_BUILD_TARGET !== 'inner') { | |
return null | |
} | |
return { | |
name: 'vite-docsearch-plugin', | |
enforce: 'pre', | |
transform(code, id) { | |
if (id.includes('@docsearch')) { | |
// Validate input code contains expected pattern | |
if (!code.includes('Promise((function(t){var n=new XMLHttpRequest;')) { | |
console.warn('[docsearch] Unexpected code pattern, skipping transformation') | |
return null | |
} | |
const newCode = code.replace( | |
'Promise((function(t){var n=new XMLHttpRequest;', | |
'Promise((function(t){t=window.handleGlobalSearchData(t);var n=new XMLHttpRequest;' | |
) | |
// Validate transformed code | |
if (!newCode.includes('window.handleGlobalSearchData')) { | |
console.error('[docsearch] Code transformation failed') | |
return null | |
} | |
return { | |
code: newCode, | |
map: null | |
} | |
} | |
} | |
} | |
} |
export const modifyViteConfig = (viteConfig, env) => { | ||
const { VITE_TINY_THEME, VITE_APP_MODE } = env | ||
const doModeModify = modeModifyMap[VITE_APP_MODE] | ||
if (doModeModify) { | ||
doModeModify(viteConfig) | ||
} | ||
const doThemeModify = themeModifyMap[VITE_TINY_THEME] | ||
return doThemeModify ? doThemeModify(viteConfig) : viteConfig | ||
} |
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.
🛠️ Refactor suggestion
Add type checking and validation for configuration modification.
The configuration modifier should validate inputs and handle errors:
export const modifyViteConfig = (viteConfig, env) => {
+ // Validate inputs
+ if (!viteConfig || typeof viteConfig !== 'object') {
+ throw new Error('[config] Invalid vite configuration')
+ }
+ if (!env || typeof env !== 'object') {
+ throw new Error('[config] Invalid environment object')
+ }
+
const { VITE_TINY_THEME, VITE_APP_MODE } = env
+
+ // Type check environment variables
+ if (VITE_APP_MODE && typeof VITE_APP_MODE !== 'string') {
+ console.warn('[config] VITE_APP_MODE is defined but not a string')
+ return viteConfig
+ }
+ if (VITE_TINY_THEME && typeof VITE_TINY_THEME !== 'string') {
+ console.warn('[config] VITE_TINY_THEME is defined but not a string')
+ return viteConfig
+ }
+
const doModeModify = modeModifyMap[VITE_APP_MODE]
if (doModeModify) {
- doModeModify(viteConfig)
+ try {
+ doModeModify(viteConfig)
+ } catch (error) {
+ console.error(`[config] Mode modification failed:`, error)
+ }
}
+
const doThemeModify = themeModifyMap[VITE_TINY_THEME]
- return doThemeModify ? doThemeModify(viteConfig) : viteConfig
+ if (!doThemeModify) {
+ return viteConfig
+ }
+
+ try {
+ return doThemeModify(viteConfig)
+ } catch (error) {
+ console.error(`[config] Theme modification failed:`, error)
+ return viteConfig
+ }
}
📝 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.
export const modifyViteConfig = (viteConfig, env) => { | |
const { VITE_TINY_THEME, VITE_APP_MODE } = env | |
const doModeModify = modeModifyMap[VITE_APP_MODE] | |
if (doModeModify) { | |
doModeModify(viteConfig) | |
} | |
const doThemeModify = themeModifyMap[VITE_TINY_THEME] | |
return doThemeModify ? doThemeModify(viteConfig) : viteConfig | |
} | |
export const modifyViteConfig = (viteConfig, env) => { | |
// Validate inputs | |
if (!viteConfig || typeof viteConfig !== 'object') { | |
throw new Error('[config] Invalid vite configuration') | |
} | |
if (!env || typeof env !== 'object') { | |
throw new Error('[config] Invalid environment object') | |
} | |
const { VITE_TINY_THEME, VITE_APP_MODE } = env | |
// Type check environment variables | |
if (VITE_APP_MODE && typeof VITE_APP_MODE !== 'string') { | |
console.warn('[config] VITE_APP_MODE is defined but not a string') | |
return viteConfig | |
} | |
if (VITE_TINY_THEME && typeof VITE_TINY_THEME !== 'string') { | |
console.warn('[config] VITE_TINY_THEME is defined but not a string') | |
return viteConfig | |
} | |
const doModeModify = modeModifyMap[VITE_APP_MODE] | |
if (doModeModify) { | |
try { | |
doModeModify(viteConfig) | |
} catch (error) { | |
console.error(`[config] Mode modification failed:`, error) | |
} | |
} | |
const doThemeModify = themeModifyMap[VITE_TINY_THEME] | |
if (!doThemeModify) { | |
return viteConfig | |
} | |
try { | |
return doThemeModify(viteConfig) | |
} catch (error) { | |
console.error(`[config] Theme modification failed:`, error) | |
return viteConfig | |
} | |
} |
export const delStaticPlugin = () => { | ||
let config | ||
return { | ||
name: 'inline-plugin-del-static', | ||
apply: 'build', | ||
configResolved(_config) { | ||
config = _config | ||
}, | ||
async closeBundle() { | ||
const targetPath = path.join(config.build.outDir, '@demos') | ||
const files = await fg(['**/*.spec.js', '**/*.spec.ts'], { | ||
dot: true, | ||
cwd: targetPath | ||
}) | ||
files.forEach((filename) => { | ||
const filePath = path.join(targetPath, filename) | ||
fs.unlink(filePath) | ||
}) | ||
} | ||
} | ||
} |
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.
🛠️ Refactor suggestion
Add error handling and logging to the plugin.
The plugin should handle potential errors during file deletion and provide logging for better debugging:
export const delStaticPlugin = () => {
let config
return {
name: 'inline-plugin-del-static',
apply: 'build',
configResolved(_config) {
config = _config
},
async closeBundle() {
const targetPath = path.join(config.build.outDir, '@demos')
+ if (!fs.existsSync(targetPath)) {
+ console.log(`[del-static] Target directory not found: ${targetPath}`)
+ return
+ }
const files = await fg(['**/*.spec.js', '**/*.spec.ts'], {
dot: true,
cwd: targetPath
})
+ console.log(`[del-static] Found ${files.length} spec files to delete`)
files.forEach((filename) => {
const filePath = path.join(targetPath, filename)
- fs.unlink(filePath)
+ try {
+ fs.unlinkSync(filePath)
+ console.log(`[del-static] Deleted: ${filename}`)
+ } catch (error) {
+ console.error(`[del-static] Failed to delete ${filename}:`, error)
+ }
})
}
}
}
Committable suggestion skipped: line range outside the PR's diff.
saas(viteConfig) { | ||
viteConfig.css = { | ||
postcss: { | ||
plugins: [tailwindCss] | ||
} | ||
} | ||
|
||
viteConfig.tinyCustom.copyTarget.push({ | ||
src: `./demos/mobile-first/*`, | ||
dest: '@demos/mobile-first' | ||
}) | ||
|
||
const menuPath = path.resolve('./demos/saas') | ||
viteConfig.resolve.alias['@menu'] = menuPath | ||
// 这里逻辑是兼容saas官网工程的alias写法 | ||
if (!viteConfig.resolve.alias['@opentiny/vue-theme']) { | ||
viteConfig.resolve.alias['@opentiny/vue-theme'] = '@opentiny/vue-theme-saas' | ||
viteConfig.resolve.alias['@opentiny/vue-icon'] = '@opentiny/vue-icon-saas' | ||
} | ||
return viteConfig | ||
} |
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.
Initialize configuration objects and validate paths.
The theme configuration needs proper initialization and path validation:
saas(viteConfig) {
+ // Initialize configuration objects
+ viteConfig.css = viteConfig.css || {}
+ viteConfig.css.postcss = viteConfig.css.postcss || {}
+ viteConfig.css.postcss.plugins = viteConfig.css.postcss.plugins || []
+
viteConfig.css = {
postcss: {
plugins: [tailwindCss]
}
}
+ viteConfig.tinyCustom = viteConfig.tinyCustom || {}
+ viteConfig.tinyCustom.copyTarget = viteConfig.tinyCustom.copyTarget || []
viteConfig.tinyCustom.copyTarget.push({
src: `./demos/mobile-first/*`,
dest: '@demos/mobile-first'
})
const menuPath = path.resolve('./demos/saas')
+ // Validate paths before setting aliases
+ if (!fs.existsSync(menuPath)) {
+ console.warn(`[theme] Menu path does not exist: ${menuPath}`)
+ }
+
+ viteConfig.resolve = viteConfig.resolve || {}
+ viteConfig.resolve.alias = viteConfig.resolve.alias || {}
viteConfig.resolve.alias['@menu'] = menuPath
Committable suggestion skipped: line range outside the PR's diff.
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: 1
🧹 Nitpick comments (1)
examples/sites/src/views/components/cmp-config.js (1)
40-40
: Update documentation to reflect the removed export.Since
faqMdConfig
has been removed from the exports, ensure that the documentation is updated to reflect this change.Consider:
- Updating the README or API documentation
- Adding a migration guide if this was a public API
- Adding a comment explaining why this configuration was removed
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
examples/sites/src/views/components/cmp-config.js
(1 hunks)examples/sites/src/views/components/components.vue
(8 hunks)
🔇 Additional comments (3)
examples/sites/src/views/components/cmp-config.js (1)
40-40
: Verify removal offaqMdConfig
export.The removal of
faqMdConfig
from exports could be a breaking change if other modules depend on this configuration.Let's verify the impact of this removal:
✅ Verification successful
No usage found for
faqMdConfig
export.The removal of
faqMdConfig
from exports is safe as there are no references to this configuration object in the codebase. All imports from this file only use the exports that are being preserved (languageMap
,vueComponents
,getWebdocPath
,staticDemoPath
).🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Search for any references to faqMdConfig in the codebase echo "Searching for faqMdConfig usage..." rg "faqMdConfig" --type js --type vue # Search for any dynamic imports of this file to check for potential destructuring echo -e "\nSearching for dynamic imports..." rg "import.*from.*cmp-config" --type js --type vueLength of output: 306
Script:
#!/bin/bash # Search for any references to faqMdConfig with context echo "Searching for faqMdConfig usage..." rg "faqMdConfig" -C 2 # Search for imports from cmp-config echo -e "\nSearching for imports from cmp-config..." rg "from ['\"].*cmp-config" -C 2 # List all .vue files that might use the config echo -e "\nListing .vue files..." fd -e vueLength of output: 67912
examples/sites/src/views/components/components.vue (2)
342-346
:⚠️ Potential issueAdd origin validation and error handling for postMessage communication.
The iframe communication lacks security measures and error handling:
- Missing origin validation for postMessage
- No error handling for messaging failures
Apply this diff to improve security and reliability:
const getIframeContent = (demoId, demoName) => { const frameWindow = iframeRef.value.contentWindow - frameWindow.postMessage({ from: 'tiny-vue-site', component: state.cmpId, demo: demoName }) + try { + const targetOrigin = import.meta.env.VITE_MOBILE_URL + if (!targetOrigin) { + console.error('Mobile preview URL environment variable is not defined') + return + } + frameWindow.postMessage( + { from: 'tiny-vue-site', component: state.cmpId, demo: demoName }, + targetOrigin + ) + } catch (error) { + console.error('Failed to send message to iframe:', error) + } router.push(`#${demoId}`) }Likely invalid or redundant comment.
171-178
: 🛠️ Refactor suggestionEnhance the mobile preview container for better accessibility and responsiveness.
The mobile preview container needs improvements in several areas:
- Add accessibility attributes (title, aria-label)
- Replace fixed dimensions with responsive units
- Add loading and error states for better UX
Apply this diff to improve the implementation:
- <div v-if="demoConfig.isMobile" class="mobile-view-container"> - <!-- 移动端展示内容 --> - <div class="phone-container"> - <div class="mobile-iframe-container"> - <iframe ref="iframeRef" width="100%" height="100%" :src="state.iframeUrl" frameborder="0"></iframe> - </div> - </div> - </div> + <div v-if="demoConfig.isMobile" class="mobile-view-container"> + <div class="phone-container"> + <div class="mobile-iframe-container"> + <iframe + ref="iframeRef" + class="mobile-preview-frame" + :title="i18nByKey('mobile-preview')" + :aria-label="i18nByKey('mobile-preview')" + :src="state.iframeUrl" + @load="handleIframeLoad" + @error="handleIframeError" + frameborder="0" + ></iframe> + <div v-if="isIframeLoading" class="mobile-preview-loading"> + <tiny-loading></tiny-loading> + </div> + <div v-if="iframeError" class="mobile-preview-error"> + {{ i18nByKey('preview-error') }} + </div> + </div> + </div> + </div>Likely invalid or redundant comment.
.phone-container { | ||
width: 394px; | ||
height: 770px; | ||
border: 0; | ||
position: fixed; | ||
top: 200px; | ||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYsAAAMFCAYAAACFxdoKAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDY3IDc5LjE1Nzc0NywgMjAxNS8wMy8zMC0yMzo0MDo0MiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjg3MTZDNTAwMTI1MzExRUE5Mzk1OTFBNjdDQ0JBNUQ5IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjg3MTZDNTAxMTI1MzExRUE5Mzk1OTFBNjdDQ0JBNUQ5Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6ODcxNkM0RkUxMjUzMTFFQTkzOTU5MUE2N0NDQkE1RDkiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6ODcxNkM0RkYxMjUzMTFFQTkzOTU5MUE2N0NDQkE1RDkiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7YMDcrAAAcl0lEQVR42uzd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) | ||
no-repeat center top; | ||
background-size: contain; | ||
z-index: 1; | ||
} | ||
|
||
.mobile-view-container { | ||
width: 400px; | ||
margin: 0 20px; | ||
} | ||
|
||
.mobile-iframe-container { | ||
position: relative; | ||
top: 79px; | ||
left: 10px; | ||
width: 360px; | ||
height: 612px; | ||
} |
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.
🛠️ Refactor suggestion
Improve the mobile preview styles for better responsiveness.
The mobile preview styles use fixed dimensions and positioning which could cause layout issues on different screen sizes.
Apply this diff to improve responsiveness:
.phone-container {
- width: 394px;
+ width: min(394px, 100%);
height: 770px;
border: 0;
position: fixed;
top: 200px;
+ right: max(10%, calc((100% - 394px) / 2));
background: url(data:image/png;base64,...) no-repeat center top;
background-size: contain;
z-index: 1;
}
.mobile-view-container {
- width: 400px;
+ width: min(400px, 100%);
margin: 0 20px;
}
.mobile-iframe-container {
position: relative;
top: 79px;
left: 10px;
- width: 360px;
+ width: min(360px, calc(100% - 20px));
height: 612px;
+ overflow: hidden;
}
📝 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.
.phone-container { | |
width: 394px; | |
height: 770px; | |
border: 0; | |
position: fixed; | |
top: 200px; | |
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYsAAAMFCAYAAACFxdoKAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDY3IDc5LjE1Nzc0NywgMjAxNS8wMy8zMC0yMzo0MDo0MiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjg3MTZDNTAwMTI1MzExRUE5Mzk1OTFBNjdDQ0JBNUQ5IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjg3MTZDNTAxMTI1MzExRUE5Mzk1OTFBNjdDQ0JBNUQ5Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6ODcxNkM0RkUxMjUzMTFFQTkzOTU5MUE2N0NDQkE1RDkiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6ODcxNkM0RkYxMjUzMTFFQTkzOTU5MUE2N0NDQkE1RDkiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7YMDcrAAAcl0lEQVR42uzd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) | |
no-repeat center top; | |
background-size: contain; | |
z-index: 1; | |
} | |
.mobile-view-container { | |
width: 400px; | |
margin: 0 20px; | |
} | |
.mobile-iframe-container { | |
position: relative; | |
top: 79px; | |
left: 10px; | |
width: 360px; | |
height: 612px; | |
} | |
.phone-container { | |
width: min(394px, 100%); | |
height: 770px; | |
border: 0; | |
position: fixed; | |
top: 200px; | |
right: max(10%, calc((100% - 394px) / 2)); | |
background: url(data:image/png;base64,...) no-repeat center top; | |
background-size: contain; | |
z-index: 1; | |
} | |
.mobile-view-container { | |
width: min(400px, 100%); | |
margin: 0 20px; | |
} | |
.mobile-iframe-container { | |
position: relative; | |
top: 79px; | |
left: 10px; | |
width: min(360px, calc(100% - 20px)); | |
height: 612px; | |
overflow: hidden; | |
} |
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: 3
♻️ Duplicate comments (3)
examples/sites/src/views/components/components.vue (3)
65-72
: 🛠️ Refactor suggestionEnhance the mobile preview container for better accessibility and responsiveness.
The mobile preview container needs improvements:
- The iframe lacks important accessibility attributes (title, aria-label)
- Fixed dimensions may not work well on all screen sizes
192-196
:⚠️ Potential issueAdd origin validation and error handling for postMessage communication.
The iframe communication lacks security measures and error handling:
- Missing origin validation for postMessage
- No error handling for messaging failures
643-666
: 🛠️ Refactor suggestionImprove the mobile preview styles for better responsiveness.
The mobile preview styles use fixed dimensions and positioning which could cause layout issues on different screen sizes.
🧹 Nitpick comments (8)
examples/sites/src/views/components/contributor.vue (2)
21-21
: Add prop validation for better type safety.The
componentId
prop should include type validation and default value.-const props = defineProps({ componentId: {} }) +const props = defineProps({ + componentId: { + type: String, + required: true + } +})
22-25
: Optimize computed property for better performance.The computed property can be simplified and made more efficient.
const contributors = computed(() => { - const id = props.componentId?.includes('grid') ? 'grid' : props.componentId - return getCmpContributors(id) + return getCmpContributors(props.componentId?.includes('grid') ? 'grid' : props.componentId) })examples/sites/src/views/components/header.vue (1)
21-21
: Add type validation for props.Props should include type validation and default values.
-const props = defineProps({ currentJson: {}, mdString: {} }) +const props = defineProps({ + currentJson: { + type: Object, + required: true, + default: () => ({}) + }, + mdString: { + type: String, + required: true, + default: '' + } +})examples/sites/src/views/components/anchor.vue (3)
16-16
: Remove unused import.The
ref
import is not used in the component.-import { ref, computed } from 'vue' +import { computed } from 'vue'🧰 Tools
🪛 ESLint
[error] 16-16: 'ref' is defined but never used.
(unused-imports/no-unused-imports)
18-18
: Add type validation for props.Props should include type validation and default values.
-const props = defineProps({ anchorAffix: {}, currentJson: {}, activeTab: {}, langKey: {}, apiTypes: {} }) +const props = defineProps({ + anchorAffix: { + type: Boolean, + default: false + }, + currentJson: { + type: Object, + required: true + }, + activeTab: { + type: String, + required: true + }, + langKey: { + type: String, + required: true + }, + apiTypes: { + type: Array, + required: true + } +})
60-63
: Move defineEmits to top of script setup.The
defineEmits
should be placed at the top of the script setup section, after imports.Move the defineEmits statement to the top of the script setup section, right after the imports.
🧰 Tools
🪛 ESLint
[error] 60-60: defineEmits should be the first statement in
<script setup>
(after any potential import statements or type definitions).(vue/define-macros-order)
examples/sites/src/views/components/api-docs.vue (2)
139-150
: Optimize getRowData function for better performance.The nested loops in
getRowData
can be inefficient for large datasets. Consider using a more efficient lookup method.const getRowData = (type) => { - const tableData = props.tableData - let rowData - for (const comp of Object.values(tableData)) { - for (const apiGroup of Object.values(comp)) { - rowData = apiGroup.find((i) => i.type === type) - if (rowData) { - return rowData - } - } - } + return Object.values(props.tableData) + .flatMap(comp => Object.values(comp)) + .flatMap(apiGroup => apiGroup) + .find(item => item.type === type) }
103-103
: Add type validation for props.Props should include type validation and default values.
-const props = defineProps({ currentJson: {}, chartCode: {}, tableData: {} }) +const props = defineProps({ + currentJson: { + type: Object, + required: true, + default: () => ({}) + }, + chartCode: { + type: Boolean, + default: false + }, + tableData: { + type: Object, + required: true, + default: () => ({}) + } +})
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
examples/sites/demos/apis/modal.js
(34 hunks)examples/sites/package.json
(1 hunks)examples/sites/src/router.js
(1 hunks)examples/sites/src/views/components/anchor.vue
(1 hunks)examples/sites/src/views/components/api-docs.vue
(1 hunks)examples/sites/src/views/components/components.vue
(2 hunks)examples/sites/src/views/components/contributor.vue
(1 hunks)examples/sites/src/views/components/float-settings.vue
(1 hunks)examples/sites/src/views/components/header.vue
(1 hunks)examples/sites/src/views/components/index.vue
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- examples/sites/src/router.js
🚧 Files skipped from review as they are similar to previous changes (1)
- examples/sites/package.json
🧰 Additional context used
🪛 ESLint
examples/sites/src/views/components/api-docs.vue
[error] 94-165: Expected blank line before this block.
(vue/padding-line-between-blocks)
[error] 125-125: defineEmits should be the first statement in <script setup>
(after any potential import statements or type definitions).
(vue/define-macros-order)
[error] 166-219: Expected blank line before this block.
(vue/padding-line-between-blocks)
examples/sites/src/views/components/anchor.vue
[error] 15-64: Expected blank line before this block.
(vue/padding-line-between-blocks)
[error] 16-16: 'ref' is defined but never used.
(unused-imports/no-unused-imports)
[error] 17-17: Expected 1 empty line after import statement not followed by another import.
(import/newline-after-import)
[error] 60-60: defineEmits should be the first statement in <script setup>
(after any potential import statements or type definitions).
(vue/define-macros-order)
[error] 65-116: Expected blank line before this block.
(vue/padding-line-between-blocks)
examples/sites/src/views/components/contributor.vue
[error] 16-26: Expected blank line before this block.
(vue/padding-line-between-blocks)
[error] 27-63: Expected blank line before this block.
(vue/padding-line-between-blocks)
examples/sites/src/views/components/header.vue
[error] 16-31: Expected blank line before this block.
(vue/padding-line-between-blocks)
[error] 20-20: Expected 1 empty line after import statement not followed by another import.
(import/newline-after-import)
[error] 32-68: Expected blank line before this block.
(vue/padding-line-between-blocks)
examples/sites/src/views/components/components.vue
[error] 98-98: 'TinyGrid' is defined but never used.
(unused-imports/no-unused-imports)
[error] 98-98: 'TinyGridColumn' is defined but never used.
(unused-imports/no-unused-imports)
[error] 98-98: 'TinyTooltip' is defined but never used.
(unused-imports/no-unused-imports)
[error] 105-105: 'AsyncHighlight' is defined but never used.
(unused-imports/no-unused-imports)
[error] 106-106: 'VersionTip' is defined but never used.
(unused-imports/no-unused-imports)
[error] 352-352: Unexpected console statement.
(no-console)
examples/sites/src/views/components/index.vue
[error] 4-6: Expected blank line before this block.
(vue/padding-line-between-blocks)
🔇 Additional comments (6)
examples/sites/demos/apis/modal.js (4)
2-2
: LGTM: Mode configuration aligns with refactoring goals.The removal of the 'mobile' mode and consolidation to ['pc', 'mobile-first'] aligns with the PR's mobile site refactoring objectives.
71-71
: LGTM: Type definitions standardized.Type definitions have been standardized from 'String'/'Boolean' to lowercase 'string'/'boolean', following TypeScript conventions.
Also applies to: 82-82, 283-283, 294-294, 317-317, 405-405
278-279
: Verify the impact of removing mobile mode support.The
modelValue / v-model
property now only supports 'pc' mode, which is different from other properties that support both 'pc' and 'mobile-first'. This might impact mobile-first implementations.
40-42
: Consider documenting the migration path.Multiple properties have been updated to remove 'mobile' mode support. While this aligns with the refactoring goals, consider documenting the migration path for users who need to transition from 'mobile' to 'mobile-first' mode.
Also applies to: 65-67, 99-101, 111-113, 134-136, 146-148, 159-161, 171-173, 183-185, 195-197, 207-209, 219-221, 231-233, 254-256, 266-268, 311-313, 326-328, 350-352, 363-365, 375-377, 387-389, 399-401, 423-425, 435-437, 450-452, 463-465, 476-478, 489-491, 502-504, 515-517, 530-532, 542-544
examples/sites/src/views/components/float-settings.vue (1)
131-137
: Verify the impact of filtering style settings.The new logic filters out
apiMode
style settings when not in PC mode or whentemplateModeState.isSaas
is true. This could affect users who rely on these settings.Run this script to check for any components that might be affected by this change:
✅ Verification successful
🏁 Script executed:
#!/bin/bash # Description: Find components that use apiMode style settings # Test: Search for apiMode usage in components. Expect: List of affected components. rg -A 5 'apiMode' --type vueLength of output: 56
I'll run a revised script that explicitly searches in *.vue files since the default file type "vue" isn’t recognized by ripgrep. This should help us identify any components referencing "apiMode" style settings.
🏁 Script executed:
#!/bin/bash # Description: Search for usage of 'apiMode' in all Vue component files. # Using the glob pattern for .vue files to bypass the unrecognized file type issue. rg -g '*.vue' -A 5 'apiMode'Length of output: 13153
Impact of Filtering apiMode Settings Verified
The filtering in float-settings.vue removes the apiMode style settings on non‑PC or SaaS modes and sets apiMode to "Options" by default. The shell script search confirms that multiple components (e.g., App.vue, demo.vue, layout.vue) using apiMode handle the "Options" value as expected. This behavior appears intentional and aligns with downstream usage.
examples/sites/src/views/components/index.vue (1)
1-6
: LGTM! Clean and focused component implementation.The component follows Vue.js best practices:
- Uses script setup for better TypeScript support
- Single responsibility principle - acts as a wrapper for ComponentDocs
🧰 Tools
🪛 ESLint
[error] 4-6: Expected blank line before this block.
(vue/padding-line-between-blocks)
<a :href="item.homepage" class="cmp-contributor-item" rel="noopener noreferrer" target="_blank"> | ||
<img class="cmp-contributor-avatar" :src="item.avatar" :alt="item.nickname" /> | ||
</a> |
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.
Add security attributes to external links.
External links should include security attributes to prevent potential security vulnerabilities.
- <a :href="item.homepage" class="cmp-contributor-item" rel="noopener noreferrer" target="_blank">
+ <a :href="item.homepage" class="cmp-contributor-item" rel="noopener noreferrer nofollow" target="_blank">
📝 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.
<a :href="item.homepage" class="cmp-contributor-item" rel="noopener noreferrer" target="_blank"> | |
<img class="cmp-contributor-avatar" :src="item.avatar" :alt="item.nickname" /> | |
</a> | |
<a :href="item.homepage" class="cmp-contributor-item" rel="noopener noreferrer nofollow" target="_blank"> | |
<img class="cmp-contributor-avatar" :src="item.avatar" :alt="item.nickname" /> | |
</a> |
<template> | ||
<header class="docs-header"> | ||
<div class="docs-title-wrap"> | ||
<div class="markdown-body markdown-top-body" v-html="mdContent"></div> |
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.
Sanitize HTML content to prevent XSS attacks.
Using v-html
with unsanitized markdown content can lead to XSS vulnerabilities.
Consider using a sanitizer library like DOMPurify:
+import DOMPurify from 'dompurify'
+
const mdContent = computed(() =>
- marked(props.mdString, {
+ DOMPurify.sanitize(marked(props.mdString, {
gfm: true,
highlight(code, language) {
const validLanguage = hljs.getLanguage(language) ? language : 'plaintext'
return hljs.highlight(code, { language: validLanguage }).value
}
- })
+ }))
)
></tiny-grid-column> | ||
<tiny-grid-column field="desc" :title="i18nByKey('desc')"> | ||
<template #default="data"> | ||
<span v-html="data.row.desc"></span> |
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.
Sanitize HTML content to prevent XSS attacks.
Using v-html
with unsanitized content in the description can lead to XSS vulnerabilities.
Consider using a sanitizer library like DOMPurify:
+import DOMPurify from 'dompurify'
+
- <span v-html="data.row.desc"></span>
+ <span v-html="DOMPurify.sanitize(data.row.desc)"></span>
…nto refactor-mobile-site
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 (2)
examples/sites/src/views/components/demo.vue (2)
53-60
: Consider improving mobile view implementation.The mobile view implementation could be enhanced for better maintainability and user experience:
- The condition
v-else-if="demoConfig.isMobile"
might be better expressed as a computed property- The button text could be externalized to i18n
- <div v-else-if="demoConfig.isMobile" class="pc-demo-container"> + <div v-else-if="isMobileMode" class="pc-demo-container"> <div class="mobile-view-btn"> - <tiny-button @click="openIframe(demo)">{{ i18nByKey('yan-shi') }}</tiny-button> + <tiny-button @click="openIframe(demo)">{{ i18nByKey('preview') }}</tiny-button> </div> </div>Add this computed property to the script:
const isMobileMode = computed(() => demoConfig.isMobile)
96-113
: Enhance type safety for demo prop.The demo prop type could be more strictly typed using an interface.
+interface Demo { + demoId: string + name: Record<string, string> + desc: Record<string, string> + codeFiles: string[] + files?: Array<{ code: string; fileName: string; language: string }> + isOpen?: boolean +} const props = defineProps({ demo: { - type: Object, + type: Object as PropType<Demo>, default: () => ({}) }, // ... other props })
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
examples/sites/src/views/components/demo.vue
(5 hunks)packages/mobile/components/tabs/src/tab-nav/renderless/index.ts
(0 hunks)packages/mobile/components/tabs/src/tab-nav/renderless/vue.ts
(1 hunks)
💤 Files with no reviewable changes (1)
- packages/mobile/components/tabs/src/tab-nav/renderless/index.ts
🔇 Additional comments (1)
examples/sites/src/views/components/demo.vue (1)
352-356
: Enhance mobile responsiveness.The mobile view could be improved with more comprehensive media queries and better overflow handling.
Note: This is similar to a previous review comment about responsive layout handling, so I'm skipping the suggestion.
@@ -87,7 +87,7 @@ export const renderless = ( | |||
showExpandItem: false, | |||
showExpandTabs: props.showExpandTabs, | |||
expandHeaderStyle: {}, | |||
mode: props._mode || parent.$mode || tinyMode || 'pc', | |||
mode: props._mode || 'mobile', |
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.
🛠️ Refactor suggestion
Update documentation to reflect mode property changes.
The mode property behavior has been significantly simplified. Please update the component documentation to reflect:
- The removal of
parent.$mode
andtinyMode
fallbacks - The new default value of 'mobile'
- The implications for component behavior
💡 Verification agent
🧩 Analysis chain
Verify impact of mode property changes.
The simplified mode logic removes dependencies on parent.$mode
and tinyMode
, defaulting to 'mobile' instead of 'pc'. This could be a breaking change for components that rely on these removed fallback values.
Run this script to identify potential impacts:
🏁 Script executed:
#!/bin/bash
# Search for components that might be affected by the mode changes
echo "Searching for parent.$mode usage..."
rg -A 3 "parent\.\$mode"
echo "Searching for tinyMode usage..."
rg -A 3 "tinyMode"
echo "Searching for mode-dependent component behavior..."
ast-grep --pattern $'mode = $_'
Length of output: 12874
Potential breaking change due to removed fallbacks in mode property.
• In packages/mobile/components/tabs/src/tab-nav/renderless/vue.ts, the mode is now initialized with only props._mode or a hard-coded fallback of "mobile" rather than the previous chain (props._mode || parent.$mode || tinyMode || 'pc').
• Our search shows extensive use of tinyMode (and parent.$mode in related renderless modules) elsewhere in the codebase, indicating that some components may rely on those fallbacks to determine non-mobile or legacy modes.
• If these components expect parent.$mode or tinyMode to be used when props._mode is undefined, this change might lead to unexpected behavior.
• I recommend verifying that all dependencies are now intended to work solely in "mobile" mode; if so, the documentation (and possibly tests) should be updated to reflect the removal of the previous fallback logic.
const copyCode = async (demo) => { | ||
if (demo.isOpen) { | ||
const idx = parseInt(state.tabValue.slice(3)) | ||
|
||
onMounted(() => { | ||
if (demoContainer.value) { | ||
props.observer?.observe?.(demoContainer.value) | ||
} | ||
navigator.clipboard.writeText(demo.files[idx].code) | ||
} else { | ||
await getDemoCode(demo) | ||
|
||
nextTick(() => { | ||
emit('mounted') | ||
}) | ||
}) | ||
navigator.clipboard.writeText(demo.files[0].code) | ||
} | ||
state.copyTip = i18nByKey('copyCodeOk') | ||
state.copyIcon = 'i-ti-check' | ||
} |
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.
🛠️ Refactor suggestion
Add error handling to clipboard operations.
The copy code function should handle clipboard API failures gracefully.
const copyCode = async (demo) => {
+ try {
if (demo.isOpen) {
const idx = parseInt(state.tabValue.slice(3))
- navigator.clipboard.writeText(demo.files[idx].code)
+ await navigator.clipboard.writeText(demo.files[idx].code)
} else {
await getDemoCode(demo)
- navigator.clipboard.writeText(demo.files[0].code)
+ await navigator.clipboard.writeText(demo.files[0].code)
}
state.copyTip = i18nByKey('copyCodeOk')
state.copyIcon = 'i-ti-check'
+ } catch (error) {
+ console.error('Failed to copy code:', error)
+ state.copyTip = i18nByKey('copyCodeError')
+ state.copyIcon = 'i-ti-close'
+ }
}
📝 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 copyCode = async (demo) => { | |
if (demo.isOpen) { | |
const idx = parseInt(state.tabValue.slice(3)) | |
onMounted(() => { | |
if (demoContainer.value) { | |
props.observer?.observe?.(demoContainer.value) | |
} | |
navigator.clipboard.writeText(demo.files[idx].code) | |
} else { | |
await getDemoCode(demo) | |
nextTick(() => { | |
emit('mounted') | |
}) | |
}) | |
navigator.clipboard.writeText(demo.files[0].code) | |
} | |
state.copyTip = i18nByKey('copyCodeOk') | |
state.copyIcon = 'i-ti-check' | |
} | |
const copyCode = async (demo) => { | |
try { | |
if (demo.isOpen) { | |
const idx = parseInt(state.tabValue.slice(3)) | |
await navigator.clipboard.writeText(demo.files[idx].code) | |
} else { | |
await getDemoCode(demo) | |
await navigator.clipboard.writeText(demo.files[0].code) | |
} | |
state.copyTip = i18nByKey('copyCodeOk') | |
state.copyIcon = 'i-ti-check' | |
} catch (error) { | |
console.error('Failed to copy code:', error) | |
state.copyTip = i18nByKey('copyCodeError') | |
state.copyIcon = 'i-ti-close' | |
} | |
} |
const getDemoCodeFn = async (demo, forceUpdate?: boolean) => { | ||
// 获取code代码文本 | ||
if (!demo.files || forceUpdate) { | ||
const cmpId = router.currentRoute.value.params.cmpId | ||
const promises = demo.codeFiles.map(async (fileName) => { | ||
// 切换option-api和composition-api | ||
const demoName = apiModeFn.getDemoName(`${getWebdocPath(cmpId)}/${fileName}`) | ||
let code = '' | ||
|
||
const path = isMobileFirst.value ? `@demos/mobile-first/app/${demoName}` : `${staticDemoPath}/${demoName}` | ||
code = await fetchDemosFile(path) | ||
.then((code) => { | ||
return code | ||
}) | ||
.catch(() => { | ||
return `${demoName}示例资源不存在,请检查文件名是否正确?` | ||
}) | ||
const ext = $split(fileName, '.', -1) | ||
const language = languageMap[ext] || '' | ||
return { code, fileName, language } | ||
}) | ||
demo.files = await Promise.all(promises) | ||
return demo.files | ||
} | ||
return demo.files | ||
} |
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.
🛠️ Refactor suggestion
Improve error handling and performance in demo code loading.
The demo code loading function could benefit from:
- Better error handling with specific error types
- Memoization for frequently accessed paths
- Loading state management
+const loadingState = ref(false)
+const memoizedPaths = new Map<string, string>()
const getDemoCodeFn = async (demo, forceUpdate?: boolean) => {
+ loadingState.value = true
try {
if (!demo.files || forceUpdate) {
const cmpId = router.currentRoute.value.params.cmpId
const promises = demo.codeFiles.map(async (fileName) => {
const demoName = apiModeFn.getDemoName(`${getWebdocPath(cmpId)}/${fileName}`)
- let code = ''
+ const pathKey = isMobileFirst.value ? `mobile-first/${demoName}` : demoName
+
+ if (memoizedPaths.has(pathKey) && !forceUpdate) {
+ return {
+ code: memoizedPaths.get(pathKey)!,
+ fileName,
+ language: languageMap[$split(fileName, '.', -1)] || ''
+ }
+ }
const path = isMobileFirst.value
? `@demos/mobile-first/app/${demoName}`
: `${staticDemoPath}/${demoName}`
- code = await fetchDemosFile(path)
- .then((code) => {
- return code
- })
- .catch(() => {
- return `${demoName}示例资源不存在,请检查文件名是否正确?`
- })
+ try {
+ const code = await fetchDemosFile(path)
+ memoizedPaths.set(pathKey, code)
+ return {
+ code,
+ fileName,
+ language: languageMap[$split(fileName, '.', -1)] || ''
+ }
+ } catch (error) {
+ console.error(`Failed to load demo ${demoName}:`, error)
+ throw new Error(`Failed to load demo ${demoName}: ${error.message}`)
+ }
- const ext = $split(fileName, '.', -1)
- const language = languageMap[ext] || ''
- return { code, fileName, language }
})
demo.files = await Promise.all(promises)
return demo.files
}
return demo.files
+ } catch (error) {
+ console.error('Error in getDemoCodeFn:', error)
+ throw error
+ } finally {
+ loadingState.value = false
}
}
📝 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 getDemoCodeFn = async (demo, forceUpdate?: boolean) => { | |
// 获取code代码文本 | |
if (!demo.files || forceUpdate) { | |
const cmpId = router.currentRoute.value.params.cmpId | |
const promises = demo.codeFiles.map(async (fileName) => { | |
// 切换option-api和composition-api | |
const demoName = apiModeFn.getDemoName(`${getWebdocPath(cmpId)}/${fileName}`) | |
let code = '' | |
const path = isMobileFirst.value ? `@demos/mobile-first/app/${demoName}` : `${staticDemoPath}/${demoName}` | |
code = await fetchDemosFile(path) | |
.then((code) => { | |
return code | |
}) | |
.catch(() => { | |
return `${demoName}示例资源不存在,请检查文件名是否正确?` | |
}) | |
const ext = $split(fileName, '.', -1) | |
const language = languageMap[ext] || '' | |
return { code, fileName, language } | |
}) | |
demo.files = await Promise.all(promises) | |
return demo.files | |
} | |
return demo.files | |
} | |
const loadingState = ref(false) | |
const memoizedPaths = new Map<string, string>() | |
const getDemoCodeFn = async (demo, forceUpdate?: boolean) => { | |
loadingState.value = true | |
try { | |
if (!demo.files || forceUpdate) { | |
const cmpId = router.currentRoute.value.params.cmpId | |
const promises = demo.codeFiles.map(async (fileName) => { | |
const demoName = apiModeFn.getDemoName(`${getWebdocPath(cmpId)}/${fileName}`) | |
const pathKey = isMobileFirst.value ? `mobile-first/${demoName}` : demoName | |
if (memoizedPaths.has(pathKey) && !forceUpdate) { | |
return { | |
code: memoizedPaths.get(pathKey)!, | |
fileName, | |
language: languageMap[$split(fileName, '.', -1)] || '' | |
} | |
} | |
const path = isMobileFirst.value | |
? `@demos/mobile-first/app/${demoName}` | |
: `${staticDemoPath}/${demoName}` | |
try { | |
const code = await fetchDemosFile(path) | |
memoizedPaths.set(pathKey, code) | |
return { | |
code, | |
fileName, | |
language: languageMap[$split(fileName, '.', -1)] || '' | |
} | |
} catch (error) { | |
console.error(`Failed to load demo ${demoName}:`, error) | |
throw new Error(`Failed to load demo ${demoName}: ${error.message}`) | |
} | |
}) | |
demo.files = await Promise.all(promises) | |
return demo.files | |
} | |
return demo.files | |
} catch (error) { | |
console.error('Error in getDemoCodeFn:', error) | |
throw error | |
} finally { | |
loadingState.value = false | |
} | |
} |
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: 5
♻️ Duplicate comments (3)
examples/sites/src/views/components/components.vue (3)
385-392
:⚠️ Potential issuePrevious security and accessibility concerns about iframe usage are still valid.
The iframe implementation needs improvements in security and accessibility as previously suggested.
See the previous review comment about enhancing the mobile preview container for better responsiveness and accessibility.
183-187
:⚠️ Potential issuePrevious security concerns about postMessage communication are still valid.
The iframe communication needs improvements in security measures as previously suggested.
See the previous review comment about adding origin validation and error handling for postMessage communication.
467-472
:⚠️ Potential issuePrevious input validation concerns are still valid.
The hash ID handling needs input validation as previously suggested.
See the previous review comment about adding input validation and error handling to demo and hash handling.
🧹 Nitpick comments (4)
examples/sites/src/views/components/mobile.vue (2)
30-30
: Consider making the phone frame position responsive.The fixed top position of 200px might not work well on different screen sizes.
- top: 200px; + top: max(200px, 20vh);
31-31
: Move the base64 background image to an asset file.Large base64 strings in CSS can make the code harder to maintain. Consider moving it to a separate image file.
- Save the base64 image as
phone-frame.png
in the assets directory- Update the CSS:
- background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...); + background: url('@/assets/phone-frame.png');examples/sites/src/views/components/index.vue (2)
1-3
: Consider adding error boundaries and loading states.The dynamic component rendering could benefit from additional error handling and loading states. Also, consider whether the child components need any props or events to function correctly.
<template> - <component :is="componentDocs" /> + <Suspense> + <template #default> + <component :is="componentDocs" v-if="componentDocs" /> + </template> + <template #fallback> + <div>Loading...</div> + </template> + </Suspense> </template>
13-19
: Consider using a more maintainable component resolution approach.The current if-else chain could become harder to maintain as more modes or themes are added. Consider using an object map for better maintainability.
-if (mode === 'mobile') { - componentDocs = Mobile -} else if (mode === 'pc' && theme === 'saas') { - componentDocs = Saas -} else { - componentDocs = Pc -} +const COMPONENT_MAP = { + mobile: Mobile, + pc: { + saas: Saas, + default: Pc + } +} + +const componentDocs = mode === 'mobile' + ? COMPONENT_MAP.mobile + : (COMPONENT_MAP.pc[theme] || COMPONENT_MAP.pc.default)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
examples/sites/src/views/components/components.vue
(1 hunks)examples/sites/src/views/components/index.vue
(1 hunks)examples/sites/src/views/components/mobile.vue
(1 hunks)examples/sites/src/views/components/pc.vue
(1 hunks)examples/sites/src/views/components/saas.vue
(1 hunks)
✅ Files skipped from review due to trivial changes (2)
- examples/sites/src/views/components/pc.vue
- examples/sites/src/views/components/saas.vue
🔇 Additional comments (2)
examples/sites/src/views/components/components.vue (2)
3-10
: LGTM! Component header implementation looks good.The ComponentHeader component is well-structured with proper prop passing and slot usage.
498-616
: Previous concerns about responsive design are still valid.The phone container and mobile preview styles need improvements in responsiveness as previously suggested.
See the previous review comments about:
- Improving the mobile preview styles for better responsiveness
- Improving responsiveness of the phone container
// eslint-disable-next-line no-eval | ||
const json = jsData ? eval('(' + jsData.slice(15) + ')') : {} |
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.
Replace unsafe eval with JSON.parse.
Using eval
is unsafe and can lead to security vulnerabilities. Use JSON.parse
instead.
- const json = jsData ? eval('(' + jsData.slice(15) + ')') : {}
+ const json = jsData ? JSON.parse(jsData.slice(15)) : {}
- let apiJson = eval('(' + apiData.slice(15) + ')')
+ let apiJson = JSON.parse(apiData.slice(15))
Also applies to: 346-347
// 将请求合并起来,这样页面更新一次,页面刷新的时机就固定了 | ||
// const testUrl = `/@demos/apis/${getWebdocPath(state.cmpId) === 'chart' ? state.cmpId : getWebdocPath(state.cmpId)}.js` | ||
const testUrl = `https://res.hc-cdn.com/tiny-vue-web-doc/3.20.7.20250117141151/@demos/apis/cascader.js` |
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.
Remove hardcoded URL and use environment variables.
The hardcoded URL https://res.hc-cdn.com/tiny-vue-web-doc/3.20.7.20250117141151/@demos/apis/cascader.js
should be moved to environment variables for better maintainability and flexibility.
- const testUrl = `https://res.hc-cdn.com/tiny-vue-web-doc/3.20.7.20250117141151/@demos/apis/cascader.js`
+ const testUrl = `${import.meta.env.VITE_API_BASE_URL}/@demos/apis/cascader.js`
📝 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 testUrl = `/@demos/apis/${getWebdocPath(state.cmpId) === 'chart' ? state.cmpId : getWebdocPath(state.cmpId)}.js` | |
const testUrl = `https://res.hc-cdn.com/tiny-vue-web-doc/3.20.7.20250117141151/@demos/apis/cascader.js` | |
// 将请求合并起来,这样页面更新一次,页面刷新的时机就固定了 | |
// const testUrl = `/@demos/apis/${getWebdocPath(state.cmpId) === 'chart' ? state.cmpId : getWebdocPath(state.cmpId)}.js` | |
const testUrl = `${import.meta.env.VITE_API_BASE_URL}/@demos/apis/cascader.js` |
<!-- 移动端展示内容 --> | ||
<div class="phone-container"> | ||
<div class="mobile-iframe-container"> | ||
<iframe ref="iframeRef" width="100%" height="100%" :src="iframeUrl" frameborder="0"></iframe> |
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.
🛠️ Refactor suggestion
Add accessibility attributes to iframe.
The iframe element should have a title and aria-label for better accessibility.
- <iframe ref="iframeRef" width="100%" height="100%" :src="iframeUrl" frameborder="0"></iframe>
+ <iframe
+ ref="iframeRef"
+ width="100%"
+ height="100%"
+ :src="iframeUrl"
+ frameborder="0"
+ title="Mobile preview"
+ aria-label="Mobile preview of the component">
+ </iframe>
📝 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.
<iframe ref="iframeRef" width="100%" height="100%" :src="iframeUrl" frameborder="0"></iframe> | |
<iframe | |
ref="iframeRef" | |
width="100%" | |
height="100%" | |
:src="iframeUrl" | |
frameborder="0" | |
title="Mobile preview" | |
aria-label="Mobile preview of the component"> | |
</iframe> |
const iframeUrl = ref('') | ||
const iframeRef = ref() |
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.
💡 Verification agent
🧩 Analysis chain
Verify iframeUrl update mechanism.
The iframeUrl ref is initialized but there's no visible logic to update it. Ensure that the URL is properly set, possibly through props or a computed property.
🏁 Script executed:
#!/bin/bash
# Search for iframeUrl updates in the codebase
rg -A 5 "iframeUrl"
Length of output: 11567
Missing iframeUrl update logic in mobile.vue
The mobile.vue file initializes iframeUrl as an empty string and binds it to the iframe’s src attribute without any subsequent logic to update it. Although other parts of the code (especially in file-upload-related modules) update their iframeUrl, there’s no evidence that mobile.vue receives an updated value from props, events, or computed logic. Please verify whether this component is intended to have a dynamic URL—if so, update iframeUrl accordingly, for example, via props or computed properties.
- File: examples/sites/src/views/components/mobile.vue (lines 20–21) only defines:
- const iframeUrl = ref('')
- const iframeRef = ref()
- There’s no logic within mobile.vue to update iframeUrl, which could leave the iframe’s src empty.
let componentDocs | ||
const mode = import.meta.env.VITE_APP_MODE | ||
const theme = import.meta.env.VITE_TINY_THEME |
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.
🛠️ Refactor suggestion
Use const
for immutable variables and validate environment variables.
The componentDocs
variable doesn't need to be mutable, and environment variables should be validated to prevent runtime errors.
-let componentDocs
-const mode = import.meta.env.VITE_APP_MODE
-const theme = import.meta.env.VITE_TINY_THEME
+const mode = import.meta.env.VITE_APP_MODE ?? 'pc'
+const theme = import.meta.env.VITE_TINY_THEME ?? ''
+
+if (!['mobile', 'pc'].includes(mode)) {
+ console.warn(`Invalid mode: ${mode}. Falling back to 'pc'`)
+}
Committable suggestion skipped: line range outside the PR's diff.
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: 8
♻️ Duplicate comments (2)
examples/sites/src/views/components/mobile.vue (2)
8-8
:⚠️ Potential issueAdd accessibility attributes to iframe.
The iframe element should have a title and aria-label for better accessibility.
Apply this diff to improve accessibility:
- <iframe ref="iframeRef" width="100%" height="100%" :src="iframeUrl" frameborder="0"></iframe> + <iframe + ref="iframeRef" + width="100%" + height="100%" + :src="iframeUrl" + frameborder="0" + title="Mobile preview" + aria-label="Mobile preview of the component"> + </iframe>
60-83
: 🛠️ Refactor suggestionImprove responsiveness of the mobile preview container.
The mobile preview container uses fixed dimensions which could cause layout issues on different screen sizes.
Apply this diff to improve responsiveness:
.phone-container { - width: 394px; + width: min(394px, 100%); height: 770px; border: 0; position: fixed; top: 200px; + right: max(10%, calc((100% - 394px) / 2)); background: url(data:image/png;base64,...) no-repeat center top; background-size: contain; z-index: 1; } .mobile-view-container { - width: 400px; + width: min(400px, 100%); margin: 0 20px; } .mobile-iframe-container { position: relative; top: 79px; left: 10px; - width: 360px; + width: min(360px, calc(100% - 20px)); height: 612px; + overflow: hidden; }
🧹 Nitpick comments (2)
examples/sites/src/views/components/composition/useTasksFinish.ts (2)
14-21
: Add timeout and cancellation mechanisms.The waitTasks implementation could benefit from timeout and cancellation capabilities to prevent hanging promises.
Consider adding timeout and cancellation support:
-const waitTasks = async () => { +const waitTasks = async (timeout?: number) => { if (isAllFinish) { return isAllFinish } - return new Promise((resolve) => { + return new Promise((resolve, reject) => { doResolve = resolve + if (timeout) { + setTimeout(() => { + reject(new Error('Timeout waiting for tasks to complete')) + }, timeout) + } }) }
23-26
: Add TypeScript interface and JSDoc documentation.The hook's interface and usage should be properly documented.
Consider adding type definitions and documentation:
+interface TasksFinishHook { + /** Marks a single task as complete */ + finishTask: () => void; + /** Waits for all tasks to complete */ + waitTasks: (timeout?: number) => Promise<boolean>; +} +/** + * Hook to track completion of multiple tasks + * @param tasksNum - Number of tasks to track + * @returns Object with methods to mark tasks as complete and wait for completion + * @throws {Error} If tasksNum is not a positive number + */ -const useTasksFinish = (tasksNum) => { +const useTasksFinish = (tasksNum: number): TasksFinishHook => { // ... implementation ... } export default useTasksFinish
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
examples/sites/src/views/components/components.vue
(1 hunks)examples/sites/src/views/components/composition/useTasksFinish.ts
(1 hunks)examples/sites/src/views/components/mobile.vue
(1 hunks)examples/sites/src/views/components/pc.vue
(1 hunks)
🧰 Additional context used
🪛 ESLint
examples/sites/src/views/components/components.vue
[error] 93-93: 'fetchDemosFile' is defined but never used.
(unused-imports/no-unused-imports)
[error] 95-95: 'demoConfig' is defined but never used.
(unused-imports/no-unused-imports)
[error] 108-108: defineProps should be the first statement in <script setup>
(after any potential import statements or type definitions).
(vue/define-macros-order)
const finishTask = () => { | ||
finishNum++ | ||
if (finishNum === tasksNum) { | ||
isAllFinish = true | ||
doResolve(true) | ||
} | ||
} |
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.
🛠️ Refactor suggestion
Add safety checks to prevent race conditions and invalid states.
The finishTask implementation needs additional guards against race conditions and invalid states.
Consider applying these improvements:
const finishTask = () => {
+ if (isAllFinish) {
+ return
+ }
+ if (finishNum >= tasksNum) {
+ throw new Error('All tasks are already finished')
+ }
finishNum++
if (finishNum === tasksNum) {
isAllFinish = true
- doResolve(true)
+ doResolve?.(true)
}
}
📝 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 finishTask = () => { | |
finishNum++ | |
if (finishNum === tasksNum) { | |
isAllFinish = true | |
doResolve(true) | |
} | |
} | |
const finishTask = () => { | |
+ if (isAllFinish) { | |
+ return | |
+ } | |
+ if (finishNum >= tasksNum) { | |
+ throw new Error('All tasks are already finished') | |
+ } | |
finishNum++ | |
if (finishNum === tasksNum) { | |
isAllFinish = true | |
- doResolve(true) | |
+ doResolve?.(true) | |
} | |
} |
const useTasksFinish = (tasksNum) => { | ||
let finishNum = 0 | ||
let isAllFinish = false | ||
let doResolve |
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.
🛠️ Refactor suggestion
Add TypeScript types and parameter validation.
The hook initialization could benefit from improved type safety and input validation.
Consider applying these improvements:
-const useTasksFinish = (tasksNum) => {
+const useTasksFinish = (tasksNum: number) => {
+ if (typeof tasksNum !== 'number' || tasksNum <= 0) {
+ throw new Error('tasksNum must be a positive number')
+ }
- let finishNum = 0
- let isAllFinish = false
- let doResolve
+ let finishNum = 0
+ let isAllFinish = false
+ let doResolve: ((value: boolean) => void) | undefined
📝 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 useTasksFinish = (tasksNum) => { | |
let finishNum = 0 | |
let isAllFinish = false | |
let doResolve | |
const useTasksFinish = (tasksNum: number) => { | |
if (typeof tasksNum !== 'number' || tasksNum <= 0) { | |
throw new Error('tasksNum must be a positive number') | |
} | |
let finishNum = 0 | |
let isAllFinish = false | |
let doResolve: ((value: boolean) => void) | undefined | |
// ... rest of the hook implementation | |
} |
fetchDemosFile(`@demos/app/${getWebdocPath(cmpId)}/webdoc/${cmpId}.${lang}.md`), | ||
import( |
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.
Add input validation and error handling for URL construction.
The URL construction in loadData
lacks validation and error handling:
- No validation of
cmpId
andlang
parameters - No error handling for failed imports
Apply this diff to improve security and reliability:
const loadData = ({ cmpId, lang }) => {
+ if (!cmpId || !/^[a-zA-Z0-9-_]+$/.test(cmpId)) {
+ return Promise.reject(new Error('Invalid component ID'))
+ }
+ if (!lang || !/^[a-z]{2}(-[A-Z]{2})?$/.test(lang)) {
+ return Promise.reject(new Error('Invalid language code'))
+ }
const promiseArr = [
fetchDemosFile(`@demos/app/${getWebdocPath(cmpId)}/webdoc/${cmpId}.${lang}.md`),
import(
/* @vite-ignore */
`${baseUrl}@demos/apis/${getWebdocPath(cmpId) === 'chart' ? cmpId : getWebdocPath(cmpId)}.js`
),
import(
/* @vite-ignore */
`${baseUrl}@demos/app/${getWebdocPath(cmpId)}/webdoc/${cmpId}.js`
)
]
- return Promise.all(promiseArr).then(([mdString, apisJson, demosJson]) => {
- return {
- mdString,
- apisJson: apisJson.default,
- demosJson: demosJson.default
- }
- })
+ return Promise.all(promiseArr)
+ .then(([mdString, apisJson, demosJson]) => {
+ if (!mdString || !apisJson?.default || !demosJson?.default) {
+ throw new Error('Failed to load required data')
+ }
+ return {
+ mdString,
+ apisJson: apisJson.default,
+ demosJson: demosJson.default
+ }
+ })
+ .catch((error) => {
+ console.error('Failed to load component data:', error)
+ throw error
+ })
Also applies to: 26-30
const iframeUrl = ref('') | ||
const iframeRef = ref() | ||
|
||
const changeDemo = (demo) => { | ||
const { cmpId } = router.currentRoute.value.params | ||
const frameWindow = iframeRef.value.contentWindow | ||
frameWindow.postMessage({ from: 'tiny-vue-site', component: cmpId, demo: demo.codeFiles[0] }) | ||
router.push(`#${demo.demoId}`) | ||
} | ||
|
||
const mobilePreview = import.meta.env.VITE_MOBILE_URL | ||
const pageInit = (demo) => { | ||
const { cmpId } = router.currentRoute.value.params | ||
iframeUrl.value = `${mobilePreview}?component=${cmpId}&demo=${demo.codeFiles[0]}` | ||
} |
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.
Add input validation and error handling for iframe URL updates.
The URL construction and iframe updates lack validation and error handling.
Apply this diff to improve security and reliability:
+const validateUrl = (url) => {
+ if (!url || !url.startsWith(mobilePreview)) {
+ console.error('Invalid mobile preview URL')
+ return false
+ }
+ return true
+}
const pageInit = (demo) => {
const { cmpId } = router.currentRoute.value.params
- iframeUrl.value = `${mobilePreview}?component=${cmpId}&demo=${demo.codeFiles[0]}`
+ if (!cmpId || !demo?.codeFiles?.length) {
+ console.error('Invalid component ID or demo')
+ return
+ }
+ const url = `${mobilePreview}?component=${encodeURIComponent(cmpId)}&demo=${encodeURIComponent(demo.codeFiles[0])}`
+ if (validateUrl(url)) {
+ iframeUrl.value = url
+ }
}
📝 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 iframeUrl = ref('') | |
const iframeRef = ref() | |
const changeDemo = (demo) => { | |
const { cmpId } = router.currentRoute.value.params | |
const frameWindow = iframeRef.value.contentWindow | |
frameWindow.postMessage({ from: 'tiny-vue-site', component: cmpId, demo: demo.codeFiles[0] }) | |
router.push(`#${demo.demoId}`) | |
} | |
const mobilePreview = import.meta.env.VITE_MOBILE_URL | |
const pageInit = (demo) => { | |
const { cmpId } = router.currentRoute.value.params | |
iframeUrl.value = `${mobilePreview}?component=${cmpId}&demo=${demo.codeFiles[0]}` | |
} | |
const iframeUrl = ref('') | |
const iframeRef = ref() | |
const changeDemo = (demo) => { | |
const { cmpId } = router.currentRoute.value.params | |
const frameWindow = iframeRef.value.contentWindow | |
frameWindow.postMessage({ from: 'tiny-vue-site', component: cmpId, demo: demo.codeFiles[0] }) | |
router.push(`#${demo.demoId}`) | |
} | |
const mobilePreview = import.meta.env.VITE_MOBILE_URL | |
const validateUrl = (url) => { | |
if (!url || !url.startsWith(mobilePreview)) { | |
console.error('Invalid mobile preview URL') | |
return false | |
} | |
return true | |
} | |
const pageInit = (demo) => { | |
const { cmpId } = router.currentRoute.value.params | |
if (!cmpId || !demo?.codeFiles?.length) { | |
console.error('Invalid component ID or demo') | |
return | |
} | |
const url = `${mobilePreview}?component=${encodeURIComponent(cmpId)}&demo=${encodeURIComponent(demo.codeFiles[0])}` | |
if (validateUrl(url)) { | |
iframeUrl.value = url | |
} | |
} |
import { i18nByKey, getWord, $clone, fetchDemosFile, useApiMode, useTemplateMode } from '@/tools' | ||
import DemoBox from '@/views/components/demo' | ||
import demoConfig from '@demos/config.js' |
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.
Remove unused imports.
The imports fetchDemosFile
and demoConfig
are defined but never used.
Apply this diff to remove unused imports:
-import { i18nByKey, getWord, $clone, fetchDemosFile, useApiMode, useTemplateMode } from '@/tools'
+import { i18nByKey, getWord, $clone, useApiMode, useTemplateMode } from '@/tools'
-import demoConfig from '@demos/config.js'
📝 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 { i18nByKey, getWord, $clone, fetchDemosFile, useApiMode, useTemplateMode } from '@/tools' | |
import DemoBox from '@/views/components/demo' | |
import demoConfig from '@demos/config.js' | |
import { i18nByKey, getWord, $clone, useApiMode, useTemplateMode } from '@/tools' | |
import DemoBox from '@/views/components/demo' |
🧰 Tools
🪛 ESLint
[error] 93-93: 'fetchDemosFile' is defined but never used.
(unused-imports/no-unused-imports)
[error] 95-95: 'demoConfig' is defined but never used.
(unused-imports/no-unused-imports)
const copyText = (text) => { | ||
navigator.clipboard.writeText(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.
Add error handling for clipboard operations.
The clipboard API operation lacks error handling.
Apply this diff to improve error handling:
const copyText = (text) => {
- navigator.clipboard.writeText(text)
+ if (!text) {
+ console.error('No text to copy')
+ return
+ }
+ navigator.clipboard.writeText(text)
+ .then(() => {
+ // Optionally show success message
+ console.log('Text copied successfully')
+ })
+ .catch((error) => {
+ console.error('Failed to copy text:', error)
+ })
}
const scrollByHash = (hash) => { | ||
setTimeout(() => { | ||
if (!hash) { | ||
scrollRef.value.scrollTo({ | ||
top: 0, | ||
left: 0 | ||
}) | ||
} else if (state.currJson.types[hash]) { | ||
jumpToApi(hash) | ||
} else { | ||
let scrollTarget | ||
try { | ||
// 用户打开官网有时候会带一些特殊字符的hash,try catch一下防止js报错 | ||
scrollTarget = document.querySelector(`#${hash}`) | ||
} catch (err) {} | ||
|
||
if (scrollTarget && !isRunningTest) { | ||
// doc-layout-scroller(滚动) > tabs > tab-content(relative), 造成 scrollTarget.offsetTop 是相对于 tab-content的距离 | ||
// 所以滚动需要修正 tab-title的占位高度才行 | ||
scrollRef.value.scrollTo({ | ||
top: scrollTarget.offsetTop, | ||
left: 0, | ||
behavior: 'smooth' | ||
}) | ||
} | ||
} | ||
}, 0) | ||
} |
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.
🛠️ Refactor suggestion
Improve error handling in scrollByHash function.
The scrollByHash function has several issues:
- Silently catches errors without proper handling
- Missing validation for hash parameter
- No error handling for scrollTo operations
Apply this diff to improve error handling:
const scrollByHash = (hash) => {
+ if (hash && !/^[a-zA-Z0-9-_]+$/.test(hash)) {
+ console.error('Invalid hash format')
+ return
+ }
setTimeout(() => {
if (!hash) {
- scrollRef.value.scrollTo({
- top: 0,
- left: 0
- })
+ try {
+ scrollRef.value.scrollTo({
+ top: 0,
+ left: 0
+ })
+ } catch (error) {
+ console.error('Failed to scroll to top:', error)
+ }
} else if (state.currJson.types[hash]) {
jumpToApi(hash)
} else {
let scrollTarget
try {
- // 用户打开官网有时候会带一些特殊字符的hash,try catch一下防止js报错
scrollTarget = document.querySelector(`#${hash}`)
- } catch (err) {}
+ } catch (error) {
+ console.error('Failed to query selector:', error)
+ return
+ }
if (scrollTarget && !isRunningTest) {
// doc-layout-scroller(滚动) > tabs > tab-content(relative), 造成 scrollTarget.offsetTop 是相对于 tab-content的距离
// 所以滚动需要修正 tab-title的占位高度才行
- scrollRef.value.scrollTo({
- top: scrollTarget.offsetTop,
- left: 0,
- behavior: 'smooth'
- })
+ try {
+ scrollRef.value.scrollTo({
+ top: scrollTarget.offsetTop,
+ left: 0,
+ behavior: 'smooth'
+ })
+ } catch (error) {
+ console.error('Failed to scroll to target:', error)
+ }
}
}
}, 0)
}
📝 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 scrollByHash = (hash) => { | |
setTimeout(() => { | |
if (!hash) { | |
scrollRef.value.scrollTo({ | |
top: 0, | |
left: 0 | |
}) | |
} else if (state.currJson.types[hash]) { | |
jumpToApi(hash) | |
} else { | |
let scrollTarget | |
try { | |
// 用户打开官网有时候会带一些特殊字符的hash,try catch一下防止js报错 | |
scrollTarget = document.querySelector(`#${hash}`) | |
} catch (err) {} | |
if (scrollTarget && !isRunningTest) { | |
// doc-layout-scroller(滚动) > tabs > tab-content(relative), 造成 scrollTarget.offsetTop 是相对于 tab-content的距离 | |
// 所以滚动需要修正 tab-title的占位高度才行 | |
scrollRef.value.scrollTo({ | |
top: scrollTarget.offsetTop, | |
left: 0, | |
behavior: 'smooth' | |
}) | |
} | |
} | |
}, 0) | |
} | |
const scrollByHash = (hash) => { | |
if (hash && !/^[a-zA-Z0-9-_]+$/.test(hash)) { | |
console.error('Invalid hash format') | |
return | |
} | |
setTimeout(() => { | |
if (!hash) { | |
try { | |
scrollRef.value.scrollTo({ | |
top: 0, | |
left: 0 | |
}) | |
} catch (error) { | |
console.error('Failed to scroll to top:', error) | |
} | |
} else if (state.currJson.types[hash]) { | |
jumpToApi(hash) | |
} else { | |
let scrollTarget | |
try { | |
scrollTarget = document.querySelector(`#${hash}`) | |
} catch (error) { | |
console.error('Failed to query selector:', error) | |
return | |
} | |
if (scrollTarget && !isRunningTest) { | |
try { | |
scrollRef.value.scrollTo({ | |
top: scrollTarget.offsetTop, | |
left: 0, | |
behavior: 'smooth' | |
}) | |
} catch (error) { | |
console.error('Failed to scroll to target:', error) | |
} | |
} | |
} | |
}, 0) | |
} |
|
||
const { apiModeState } = useApiMode() | ||
const { templateModeState, staticPath, optionsList } = useTemplateMode() | ||
const props = defineProps({ loadData: {} }) |
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.
Fix macro order in script setup.
defineProps
should be the first statement in <script setup>
after imports.
Apply this diff to fix the macro order:
<script setup lang="ts">
import { reactive, computed, watch, onMounted, nextTick, ref } from 'vue'
import { useRoute } from 'vue-router'
import { TinyButtonGroup, TinyTabs, TinyTabItem } from '@opentiny/vue'
import { debounce } from '@opentiny/utils'
import { i18nByKey, getWord, $clone, useApiMode, useTemplateMode } from '@/tools'
import DemoBox from '@/views/components/demo'
import { router } from '@/router.js'
import { getWebdocPath } from './cmp-config'
import AsideAnchor from './anchor.vue'
import ComponentHeader from './header.vue'
import ComponentContributor from './contributor.vue'
import ApiDocs from './api-docs.vue'
import useTasksFinish from './composition/useTasksFinish'
+const props = defineProps({ loadData: {} })
+
defineOptions({
name: 'CmpPageVue'
})
-
-const props = defineProps({ loadData: {} })
📝 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 props = defineProps({ loadData: {} }) | |
<script setup lang="ts"> | |
import { reactive, computed, watch, onMounted, nextTick, ref } from 'vue' | |
import { useRoute } from 'vue-router' | |
import { TinyButtonGroup, TinyTabs, TinyTabItem } from '@opentiny/vue' | |
import { debounce } from '@opentiny/utils' | |
import { i18nByKey, getWord, $clone, useApiMode, useTemplateMode } from '@/tools' | |
import DemoBox from '@/views/components/demo' | |
import { router } from '@/router.js' | |
import { getWebdocPath } from './cmp-config' | |
import AsideAnchor from './anchor.vue' | |
import ComponentHeader from './header.vue' | |
import ComponentContributor from './contributor.vue' | |
import ApiDocs from './api-docs.vue' | |
import useTasksFinish from './composition/useTasksFinish' | |
const props = defineProps({ loadData: {} }) | |
defineOptions({ | |
name: 'CmpPageVue' | |
}) | |
</script> |
🧰 Tools
🪛 ESLint
[error] 108-108: defineProps should be the first statement in <script setup>
(after any potential import statements or type definitions).
(vue/define-macros-order)
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: 24
🔭 Outside diff range comments (5)
examples/sites/demos/mobile/app/slider/webdoc/slider.js (1)
177-182
: Incorrect English translations in demo descriptions.The English translations for demo names and descriptions are incorrect or placeholder text. They should accurately reflect their Chinese counterparts.
Apply these corrections:
- 'en-US': 'button type' + 'en-US': 'Step' - 'en-US': 'button round' + 'en-US': 'Disabled' - 'en-US': 'events' + 'en-US': 'Maximum and Minimum Values' - 'en-US': 'events' + 'en-US': 'Range Selection' - 'en-US': 'events' + 'en-US': 'Total Steps' - 'en-US': 'events' + 'en-US': 'Format Tooltip' - 'en-US': 'events' + 'en-US': 'Input Box Mode' - 'en-US': 'events' + 'en-US': 'Tooltip' - 'en-US': 'events' + 'en-US': 'Events' - 'en-US': 'events' + 'en-US': 'Events' - 'en-US': 'events' + 'en-US': 'Events' - 'en-US': 'events' + 'en-US': 'Custom Slot' - 'en-US': 'events' + 'en-US': 'Vertical Mode'Also update their corresponding descriptions:
- 'en-US': '<p>button type</p>' + 'en-US': '<p>Configure the slider step size by setting <code>step</code></p>' - 'en-US': '<p>button round</p>' + 'en-US': '<p>Disable slider movement by setting the <code>disabled</code> property</p>' - 'en-US': '<p>bbutton click</p>' + 'en-US': '<p>Set the slider value range using <code>min</code> and <code>max</code></p>' - 'en-US': '<p>bbutton click</p>' + 'en-US': '<p>Set initial range selection by using an array for <code>v-model</code></p>' - 'en-US': '<p>bbutton click</p>' + 'en-US': '<p>Set the total number of steps using the <code>num-pages</code> property. When pressing PageDown/PageUp, the moving distance is (max-min)/num-pages.</p>' - 'en-US': '<p>bbutton click</p>' + 'en-US': '<p>Set the value using the <code>format-tooltip</code> property.</p>' - 'en-US': '<p>bbutton click</p>' + 'en-US': '<p>Enable slider input box mode by configuring <code>show-input</code></p>' - 'en-US': '<p>bbutton click</p>' + 'en-US': '<p>Disable slider tooltip by setting <code>show-tip="false"</code> (enabled by default)</p>' - 'en-US': '<p>bbutton click</p>' + 'en-US': '<p><code>change</code> event</p>' - 'en-US': '<p>bbutton click</p>' + 'en-US': '<p><code>start</code> event when slider starts moving</p>' - 'en-US': '<p>bbutton click</p>' + 'en-US': '<p><code>stop</code> event when slider stops moving</p>' - 'en-US': '<p>bbutton click</p>' + 'en-US': '<p>Custom slot</p>' - 'en-US': '<p>bbutton click</p>' + 'en-US': '<p>Display vertical slider mode by setting the <code>vertical</code> property (horizontal by default)<br>Component height can be set using <code>height</code> in vertical mode</p>'Also applies to: 189-194, 201-206, 213-218, 225-231, 237-243, 250-255, 262-267, 274-279, 286-291, 298-303, 310-315, 322-328
examples/sites/demos/mobile/app/navbar/webdoc/navbar.js (1)
123-171
: Fix incorrect English demo descriptions.The English descriptions for demos reference buttons instead of navbar functionality.
Apply these changes:
demoId: 'left-right-arrow', desc: { 'zh-CN': '<p>显示左右箭头</p>', - 'en-US': '<p>button type</p>' + 'en-US': '<p>Show left and right arrows</p>' }demoId: 'left-right-text', desc: { 'zh-CN': '<p>指定左右文本</p>', - 'en-US': '<p>button round</p>' + 'en-US': '<p>Set left and right text</p>' }demoId: 'slots', desc: { 'zh-CN': '<p>自定义左右侧内容</p>', - 'en-US': '<p>button click</p>' + 'en-US': '<p>Custom left and right content</p>' }demoId: 'z-index', desc: { 'zh-CN': '<p>设置层</p>', - 'en-US': '<p>button click</p>' + 'en-US': '<p>Set z-index level</p>' }examples/sites/demos/mobile/app/tabs/webdoc/tabs.js (2)
203-206
: Fix incorrect size demo description and add missing prop documentation.The description incorrectly states that the
size
prop sets the font color, which appears to be a copy-paste error. Additionally, thesize
prop is referenced in the demos but not documented in the props section.
- Fix the demo description:
- 'zh-CN': '通过 <code>size</code> 设置当前页签的字体色。', + 'zh-CN': '通过 <code>size</code> 设置页签的尺寸。', - 'en-US': 'Set the font color of the current tag using the <code>size</code> attribute.' + 'en-US': 'Set the size of the tabs using the <code>size</code> attribute.'
- Add the missing
size
prop to the props section.
236-247
: Add missing stretch prop documentation.The stretch demo references a
stretch
prop that controls tab width behavior, but this prop is not documented in the props section.Add the missing prop documentation:
], + { + name: 'stretch', + type: 'boolean', + defaultValue: 'false', + desc: { + 'zh-CN': '设置页签项的宽度是否自撑开', + 'en-US': 'Set whether the width of the tab items should stretch automatically' + }, + demoId: 'stretch' + },examples/sites/demos/mobile/app/input/webdoc/input.js (1)
542-543
: Fix the disabled property documentation.The documentation incorrectly references
code
instead ofdisabled
in the description.- 'zh-CN': '<p>通过 <code>code</code> 属性禁用输入框。</p>', - 'en-US': '<p>To disabled input through <code>code</code> props.</p>' + 'zh-CN': '<p>通过 <code>disabled</code> 属性禁用输入框。</p>', + 'en-US': '<p>Disable input through <code>disabled</code> prop.</p>'
♻️ Duplicate comments (8)
examples/sites/demos/mobile/app/pull-refresh/webdoc/pull-refresh.js (1)
134-141
:⚠️ Potential issueRemove duplicate component definition.
The second
pull-refresh
component definition appears to be redundant as it contains no configuration.Apply this diff to remove the duplicate definition:
- { - name: 'pull-refresh', - type: 'component', - props: [], - events: [], - methods: [], - slots: [] - }examples/sites/demos/mobile/app/switch/webdoc/switch.js (1)
50-58
:⚠️ Potential issueFix v-model prop definition.
The v-model prop has several issues:
- The type should be
boolean | string | number
to match thetrue-value
andfalse-value
types.- The English description "display different button" appears to be copied from a button component.
Apply this diff to fix the issues:
{ name: 'v-model', - type: 'string', + type: 'boolean | string | number', defaultValue: '', desc: { 'zh-CN': '<p>绑定值</p>', - 'en-US': 'display different button' + 'en-US': 'Binding value for the switch' }, demoId: 'base' }examples/sites/demos/mobile/app/minipicker/webdoc/minipicker.js (1)
111-137
:⚠️ Potential issueAdd missing event types and fix incorrect demo reference.
- Event types are missing for all events
- The
confirm
event incorrectly references the 'cancel' demoApply this diff to add event types and fix the demo reference:
{ name: 'cancel', + type: '() => void', defaultValue: '', desc: { 'zh-CN': '<p>点击取消按钮时触发</p>', 'en-US': 'Click' }, - demoId: '' + demoId: 'cancel' }, { name: 'change', + type: '(columnIndex: number, value: any) => void', defaultValue: '', desc: { 'zh-CN': '<p>选项改变时触发。</p>', 'en-US': 'Click' }, demoId: 'change' }, { name: 'confirm', + type: '(value: any) => void', defaultValue: '', desc: { 'zh-CN': '<p>点击确定按钮时触发</p>', 'en-US': 'Click' }, - demoId: 'cancel' + demoId: 'confirm' }examples/sites/demos/mobile/app/tabs/webdoc/tabs.js (1)
161-168
:⚠️ Potential issueDocument missing TabItem APIs.
The documentation is incomplete as the
tab-item
component is missing essential props that are mentioned in the demos.As mentioned in the basic usage demo, the following props need to be documented:
title
: For setting the tab labelname
: For identifying the tabdisabled
: For disabling the tabexamples/sites/demos/mobile/app/timeline/webdoc/timeline.js (3)
90-92
:⚠️ Potential issueFix incorrect type definition for
start
property.The
start
property's type is incorrectly defined as'start'
. It should be'number'
since it represents a numeric starting value.- type: 'start', + type: 'number',
122-124
: 🛠️ Refactor suggestionImprove event type definition.
The
click
event type should follow TypeScript function type syntax for better type checking and documentation.- type: 'Function(arg1,arg2)', + type: '(index: number, data: { name: string, time: string }) => void',
144-145
:⚠️ Potential issueFix typo in slot name.
The slot name 'conternt' appears to be a typo and should be 'content'.
- name: 'conternt', + name: 'content',examples/sites/demos/mobile/app/checkbox/webdoc/checkbox.js (1)
128-134
:⚠️ Potential issueDocument checkbox-group and checkbox-button components.
The
checkbox-group
andcheckbox-button
components lack documentation despite being referenced in demos. This documentation gap could make it difficult for developers to understand and use these components correctly.Based on the demos section and common checkbox patterns, consider documenting:
For
checkbox-group
:- props: [], + props: [ + { + name: 'v-model', + type: 'array', + defaultValue: '[]', + desc: { + 'zh-CN': '绑定值,选中的 checkbox 的 label 值', + 'en-US': 'Binding value, the label values of selected checkboxes' + }, + demoId: 'checkbox-group' + }, + { + name: 'disabled', + type: 'boolean', + defaultValue: 'false', + desc: { + 'zh-CN': '是否禁用所有复选框', + 'en-US': 'Whether to disable all checkboxes' + }, + demoId: 'disabled' + } + ], + events: [ + { + name: 'change', + type: '(value: array) => void', + desc: { + 'zh-CN': '当绑定值变化时触发的事件', + 'en-US': 'Event triggered when the binding value changes' + }, + demoId: 'checkbox-group' + } + ]For
checkbox-button
:- props: [], + props: [ + { + name: 'label', + type: 'string | number | boolean', + defaultValue: '', + desc: { + 'zh-CN': '选中状态的值', + 'en-US': 'Value when selected' + }, + demoId: 'checkbox-group' + }, + { + name: 'disabled', + type: 'boolean', + defaultValue: 'false', + desc: { + 'zh-CN': '是否禁用', + 'en-US': 'Whether to disable' + }, + demoId: 'disabled' + } + ]Also applies to: 135-142
🧹 Nitpick comments (16)
examples/sites/demos/mobile/app/search/webdoc/search.js (1)
167-167
: Fix typos and improve demo descriptions in English.
- Fix the typo "bbutton" to "button"
- Enhance English descriptions to match the detail level of Chinese descriptions
- 'en-US': '<p>bbutton click</p>' + 'en-US': '<p>button click event handling</p>'Also applies to: 168-168, 179-179, 180-180
examples/sites/demos/mobile/app/file-upload/webdoc/file-upload.js (1)
701-768
: Add English comments to type definitions.For consistency with the rest of the documentation, consider adding English translations for the comments in the type definitions. For example:
interface IData { - [propsName?: any]: any // 上传参数可自定义 + [propsName?: any]: any // 上传参数可自定义 | Custom upload parameters } interface IFile { - name: string // 上传文件名 - percentage: string // 上传进度百分比 - raw: File // 原始文件信息 - size: number // 文件大小 - status: string // 上传状态 - uid: string // uid + name: string // 上传文件名 | Upload file name + percentage: string // 上传进度百分比 | Upload progress percentage + raw: File // 原始文件信息 | Original file information + size: number // 文件大小 | File size + status: string // 上传状态 | Upload status + uid: string // uid | Unique identifier }examples/sites/demos/mobile/app/slider/webdoc/slider.js (1)
253-253
: Fix unclosed HTML tag in Chinese description.The
<code>
tag is not properly closed.- 'zh-CN': '<p>通过配置<code>show-input<code>开启滑块输入框模式</p>', + 'zh-CN': '<p>通过配置<code>show-input</code>开启滑块输入框模式</p>',examples/sites/demos/mobile/app/numeric/webdoc/numeric.js (3)
79-79
: Fix incorrect demoId reference.The
min
prop is referencing themax
demo. It should have its own demo or reference a more appropriate one.- demoId: 'max' + demoId: 'min'
224-224
: Fix typo in English demo descriptions.Multiple demo descriptions contain a typo in the word "button" ('bbutton').
- 'en-US': '<p>bbutton click</p>' + 'en-US': '<p>button click</p>'Also applies to: 236-236, 248-248, 260-260
306-309
: Enhance type documentation with more detailed comments.The interface documentation could be more descriptive about the purpose and usage of each property.
interface IStepStrategy { - value: number | string, // 5 或者 '5' - mode: 'strictly' | 'restore' + // The step value, can be numeric (5) or string ('5') + value: number | string, + // Mode of step behavior: + // - strictly: Only allow values that are multiples of the step + // - restore: Restore to the nearest valid step value when focus is lost + mode: 'strictly' | 'restore' }examples/sites/src/views/components/contributor.vue (1)
21-21
: Add type definition and validation for componentId prop.The prop definition lacks type information and validation.
Apply this diff to add type definition and validation:
-const props = defineProps({ componentId: {} }) +const props = defineProps({ + componentId: { + type: String, + required: true + } +})examples/sites/src/views/components/saas.vue (1)
5-5
: Use i18n for text localization.The text "文档类型:" is hardcoded in Chinese. Use i18n to support multiple languages.
Apply this diff to use i18n:
- <span class="cmp-mode-title">文档类型: </span> + <span class="cmp-mode-title">{{ i18nByKey('doc-type') }}</span>examples/sites/demos/mobile/app/loading/webdoc/loading.js (1)
13-18
: Enhance English documentation quality.The English descriptions are less detailed than their Chinese counterparts. Consider improving them:
// For target property - 'en-US': 'Loading the DOM node to be overwritten. A DOM object or character string can be transferred. If a character string is transferred, it is transferred to document.querySelector as a parameter to obtain the corresponding DOM node.' + 'en-US': 'The DOM node that Loading will cover. Accepts either a DOM object or a selector string. If a string is provided, it will be passed to document.querySelector to find the target node.' // For tiny_mode property - 'en-US': 'display different button' + 'en-US': 'Specifies the device type ("pc" or "mobile") for rendering' // For type property - 'en-US': 'display different button' + 'en-US': 'Sets the loading style type'Also applies to: 23-28, 33-38
examples/sites/demos/mobile/app/label/webdoc/label.js (1)
64-66
: Fix typo in English description.There's a typo in the English description for the
position
property.- 'en-US': 'position of whole line dispaly' + 'en-US': 'position of whole line display'examples/sites/demos/mobile/app/progress/webdoc/progress.js (1)
12-12
: Consider adding union type validation.The
color
property accepts a complex union type. Consider adding runtime validation to ensure the provided value matches one of the allowed types.examples/sites/demos/mobile/app/user-head/webdoc/user-head.js (1)
90-99
: Add enum type fortype
property.The
type
property accepts specific string values ('icon', 'image', 'label'). Consider defining an enum type for better type safety and documentation.Add a type definition:
+ types: [ + { + name: 'IType', + type: 'type', + code: `type IType = 'icon' | 'image' | 'label'` + } + ]Then update the property type:
- type: 'string', + type: 'IType',examples/sites/demos/mobile/app/form/webdoc/form.js (1)
68-69
: Update the demoId reference.The
model
property references an empty demoId. Consider linking it to a relevant demo, such as 'validate' which likely demonstrates form model usage.examples/sites/demos/mobile/app/modal/webdoc/modal.js (2)
99-100
: Fix the English translation.The English translation "Equiphorus input aria-label attribute" appears to be incorrect.
- 'en-US': 'Equiphorus input aria-label attribute' + 'en-US': 'Equivalent to native input aria-label attribute'
331-331
: Improve event documentation.The
clear
event documentation could be more detailed by specifying when it's applicable (only whenclearable
is true).desc: { - 'zh-CN': '在点击由 clearable 属性生成的清空按钮时触发', - 'en-US': 'Trigger when the clear button generated by the clearable property is clicked' + 'zh-CN': '在点击由 clearable 属性生成的清空按钮时触发,仅当 clearable 为 true 时可用', + 'en-US': 'Triggered when clicking the clear button (only available when clearable is true)' },Also applies to: 332-332, 333-333, 334-334, 335-335, 336-336
examples/sites/demos/mobile/app/input/webdoc/input.js (1)
349-358
: Improve event type specificity.The
input
event type could be more specific by including the value type in the documentation.name: 'input', - type: '(event: InputEvent) => void', + type: '(event: InputEvent & { target: { value: string | number } }) => void', defaultValue: '', desc: { 'zh-CN': '输入值时触发事件', - 'en-US': 'An event is triggered when a value is entered.' + 'en-US': 'Triggered when the input value changes, providing the new value in event.target.value' },
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (72)
examples/sites/demos/apis/chart-attributes-demo.js
(0 hunks)examples/sites/demos/apis/chart-autonavi-map.js
(0 hunks)examples/sites/demos/apis/chart-baidu-map.js
(0 hunks)examples/sites/demos/apis/chart-bar.js
(0 hunks)examples/sites/demos/apis/chart-boxplot.js
(0 hunks)examples/sites/demos/apis/chart-candle.js
(0 hunks)examples/sites/demos/apis/chart-demo.js
(0 hunks)examples/sites/demos/apis/chart-docs.js
(0 hunks)examples/sites/demos/apis/chart-events.js
(0 hunks)examples/sites/demos/apis/chart-funnel.js
(0 hunks)examples/sites/demos/apis/chart-gauge.js
(0 hunks)examples/sites/demos/apis/chart-graph.js
(0 hunks)examples/sites/demos/apis/chart-heatmap.js
(0 hunks)examples/sites/demos/apis/chart-histogram.js
(0 hunks)examples/sites/demos/apis/chart-line.js
(0 hunks)examples/sites/demos/apis/chart-liquidfill.js
(0 hunks)examples/sites/demos/apis/chart-map.js
(0 hunks)examples/sites/demos/apis/chart-pie.js
(0 hunks)examples/sites/demos/apis/chart-question.js
(0 hunks)examples/sites/demos/apis/chart-radar.js
(0 hunks)examples/sites/demos/apis/chart-ring.js
(0 hunks)examples/sites/demos/apis/chart-sankey.js
(0 hunks)examples/sites/demos/apis/chart-scatter.js
(0 hunks)examples/sites/demos/apis/chart-sunburst.js
(0 hunks)examples/sites/demos/apis/chart-tree.js
(0 hunks)examples/sites/demos/apis/chart-waterfall.js
(0 hunks)examples/sites/demos/apis/chart-wordcloud.js
(0 hunks)examples/sites/demos/apis/chart.js
(0 hunks)examples/sites/demos/apis/link.js
(0 hunks)examples/sites/demos/mobile/app/actionsheet/webdoc/actionsheet.js
(1 hunks)examples/sites/demos/mobile/app/alert/webdoc/alert.js
(2 hunks)examples/sites/demos/mobile/app/avatar/webdoc/avatar.js
(1 hunks)examples/sites/demos/mobile/app/badge/webdoc/badge.js
(2 hunks)examples/sites/demos/mobile/app/button/webdoc/button.js
(2 hunks)examples/sites/demos/mobile/app/checkbox/webdoc/checkbox.js
(1 hunks)examples/sites/demos/mobile/app/checkboxgroup/webdoc/checkboxgroup.js
(1 hunks)examples/sites/demos/mobile/app/container/webdoc/container.js
(2 hunks)examples/sites/demos/mobile/app/dropdownmenu/webdoc/dropdownmenu.js
(1 hunks)examples/sites/demos/mobile/app/exception/webdoc/exception.js
(1 hunks)examples/sites/demos/mobile/app/file-upload/webdoc/file-upload.js
(2 hunks)examples/sites/demos/mobile/app/form/webdoc/form.js
(2 hunks)examples/sites/demos/mobile/app/imageviewer/webdoc/imageviewer.js
(1 hunks)examples/sites/demos/mobile/app/index-bar/webdoc/index-bar.js
(1 hunks)examples/sites/demos/mobile/app/input/webdoc/input.js
(1 hunks)examples/sites/demos/mobile/app/label/webdoc/label.js
(1 hunks)examples/sites/demos/mobile/app/list/webdoc/list.js
(1 hunks)examples/sites/demos/mobile/app/loading/webdoc/loading.js
(2 hunks)examples/sites/demos/mobile/app/mask/webdoc/mask.js
(1 hunks)examples/sites/demos/mobile/app/minipicker/webdoc/minipicker.js
(1 hunks)examples/sites/demos/mobile/app/modal/webdoc/modal.js
(2 hunks)examples/sites/demos/mobile/app/multi-select/webdoc/multi-select.js
(2 hunks)examples/sites/demos/mobile/app/navbar/webdoc/navbar.js
(1 hunks)examples/sites/demos/mobile/app/numeric/webdoc/numeric.js
(2 hunks)examples/sites/demos/mobile/app/popover/webdoc/popover.js
(2 hunks)examples/sites/demos/mobile/app/progress/webdoc/progress.js
(2 hunks)examples/sites/demos/mobile/app/pull-refresh/webdoc/pull-refresh.js
(1 hunks)examples/sites/demos/mobile/app/radio/webdoc/radio.js
(1 hunks)examples/sites/demos/mobile/app/search/webdoc/search.js
(2 hunks)examples/sites/demos/mobile/app/slider/webdoc/slider.js
(1 hunks)examples/sites/demos/mobile/app/switch/webdoc/switch.js
(1 hunks)examples/sites/demos/mobile/app/tabbar/webdoc/tabbar.js
(1 hunks)examples/sites/demos/mobile/app/table/webdoc/table.js
(1 hunks)examples/sites/demos/mobile/app/tabs/webdoc/tabs.js
(1 hunks)examples/sites/demos/mobile/app/tag/webdoc/tag.js
(2 hunks)examples/sites/demos/mobile/app/timeline/webdoc/timeline.js
(1 hunks)examples/sites/demos/mobile/app/toast/webdoc/toast.js
(1 hunks)examples/sites/demos/mobile/app/user-head/webdoc/user-head.js
(1 hunks)examples/sites/src/views/components/components.vue
(1 hunks)examples/sites/src/views/components/contributor.vue
(1 hunks)examples/sites/src/views/components/mobile.vue
(1 hunks)examples/sites/src/views/components/pc.vue
(1 hunks)examples/sites/src/views/components/saas.vue
(1 hunks)
💤 Files with no reviewable changes (29)
- examples/sites/demos/apis/chart-graph.js
- examples/sites/demos/apis/chart-funnel.js
- examples/sites/demos/apis/chart-baidu-map.js
- examples/sites/demos/apis/link.js
- examples/sites/demos/apis/chart-sunburst.js
- examples/sites/demos/apis/chart.js
- examples/sites/demos/apis/chart-radar.js
- examples/sites/demos/apis/chart-candle.js
- examples/sites/demos/apis/chart-autonavi-map.js
- examples/sites/demos/apis/chart-boxplot.js
- examples/sites/demos/apis/chart-sankey.js
- examples/sites/demos/apis/chart-map.js
- examples/sites/demos/apis/chart-tree.js
- examples/sites/demos/apis/chart-histogram.js
- examples/sites/demos/apis/chart-heatmap.js
- examples/sites/demos/apis/chart-liquidfill.js
- examples/sites/demos/apis/chart-waterfall.js
- examples/sites/demos/apis/chart-demo.js
- examples/sites/demos/apis/chart-attributes-demo.js
- examples/sites/demos/apis/chart-question.js
- examples/sites/demos/apis/chart-wordcloud.js
- examples/sites/demos/apis/chart-docs.js
- examples/sites/demos/apis/chart-events.js
- examples/sites/demos/apis/chart-line.js
- examples/sites/demos/apis/chart-pie.js
- examples/sites/demos/apis/chart-bar.js
- examples/sites/demos/apis/chart-ring.js
- examples/sites/demos/apis/chart-gauge.js
- examples/sites/demos/apis/chart-scatter.js
🚧 Files skipped from review as they are similar to previous changes (6)
- examples/sites/src/views/components/pc.vue
- examples/sites/demos/mobile/app/checkboxgroup/webdoc/checkboxgroup.js
- examples/sites/demos/mobile/app/mask/webdoc/mask.js
- examples/sites/demos/mobile/app/table/webdoc/table.js
- examples/sites/demos/mobile/app/tabbar/webdoc/tabbar.js
- examples/sites/demos/mobile/app/exception/webdoc/exception.js
🧰 Additional context used
🪛 ESLint
examples/sites/src/views/components/components.vue
[error] 90-90: 'fetchDemosFile' is defined but never used.
(unused-imports/no-unused-imports)
[error] 92-92: 'demoConfig' is defined but never used.
(unused-imports/no-unused-imports)
[error] 105-105: defineProps should be the first statement in <script setup>
(after any potential import statements or type definitions).
(vue/define-macros-order)
examples/sites/src/views/components/contributor.vue
[error] 16-25: Expected blank line before this block.
(vue/padding-line-between-blocks)
[error] 26-62: Expected blank line before this block.
(vue/padding-line-between-blocks)
🔇 Additional comments (46)
examples/sites/demos/mobile/app/search/webdoc/search.js (5)
14-16
: Improve English descriptions for better clarity.Several props still have unclear or incorrect English descriptions that use the placeholder text "display different button".
Also applies to: 46-47, 56-57
27-28
: Fix incorrect demo references.Some props are still referencing incorrect demo examples that don't demonstrate their functionality.
Also applies to: 38-39
61-107
: Well-documented events section!The events section has clear descriptions, proper type definitions, and correct demo references.
114-116
: Improve default slot documentation.The English description for the default slot is still overly simplified and doesn't match the Chinese description.
184-194
: Well-structured type definition!The ITypeValue interface is properly defined and consistently used across props and events.
examples/sites/demos/mobile/app/file-upload/webdoc/file-upload.js (3)
281-365
: LGTM! Well-documented events section.The events documentation is comprehensive, with clear parameter types and bilingual descriptions.
366-397
: LGTM! Clear and concise methods documentation.The methods documentation provides clear descriptions and appropriate usage contexts.
398-419
: LGTM! Sufficient slots documentation.The slots documentation clearly describes the purpose of each slot in both languages.
examples/sites/demos/mobile/app/toast/webdoc/toast.js (3)
4-8
: LGTM! Well-structured API documentation.The component API structure follows a clear and consistent pattern.
8-49
: Fix props documentation inconsistencies and improve English translations.Several issues need to be addressed in the props documentation:
- Type consistency:
time
andzIndex
props have string default values but are typed as numbers- Missing/incomplete English descriptions:
zIndex
prop is missing English descriptiontype
prop's English description is incompletetime
prop's English description needs grammatical improvement
66-102
: LGTM! Well-documented demos.The demo documentation is complete with clear descriptions in both Chinese and English.
examples/sites/demos/mobile/app/slider/webdoc/slider.js (3)
66-68
: Incorrect English description for therange
prop.The English description "display different button" doesn't match the Chinese description which describes range selection functionality.
- 'en-US': 'display different button' + 'en-US': 'Whether to enable range selection'
107-109
: Incorrect English description for thevalue
prop.The English description "display different button" is incorrect and doesn't match the Chinese description which describes the value type requirements.
- 'en-US': 'display different button' + 'en-US': 'Set the current value for single slider, must be an integer or array.'
156-156
: Methods documentation needs to be updatedThe methods array is empty but should document the following public methods available in the slider component:
- Value manipulation (through v-model)
formatTipValue
: Formats the tooltip valuecalculateValue
: Calculates the slider value based on positiondisplayTip/hideTip
: Controls tooltip visibility- Event handlers:
start
,change
,stop
examples/sites/demos/mobile/app/switch/webdoc/switch.js (5)
4-7
: LGTM! Well-structured component documentation.The component documentation follows a clear and organized structure.
9-48
: LGTM! Well-defined props.The disabled, false-value, mini, and true-value props are well-defined with appropriate types, default values, and clear descriptions in both Chinese and English.
60-71
: LGTM! Well-defined event.The change event is properly typed and documented with clear descriptions.
72-73
: LGTM! Empty methods and slots sections.It's appropriate to have empty methods and slots arrays if the component doesn't expose any.
102-112
: undefinedexamples/sites/demos/mobile/app/navbar/webdoc/navbar.js (2)
10-18
: Fix boolean property default values.Boolean properties should have boolean default values, not strings.
Apply these changes:
name: 'fixed', type: 'boolean', - defaultValue: 'true', + defaultValue: true,name: 'left-arrow', type: 'boolean', - defaultValue: '', + defaultValue: false,name: 'right-arrow', type: 'boolean', - defaultValue: '', + defaultValue: false,Also applies to: 20-28, 40-48
80-88
: Update z-index property type to number.The z-index property should be of type number, not string, as it represents a numeric stacking order.
name: 'z-index', - type: 'string', + type: 'number', defaultValue: '',examples/sites/demos/mobile/app/minipicker/webdoc/minipicker.js (2)
15-15
: Improve English translations for properties.All property descriptions use the generic text "display different button" which doesn't accurately describe their functionality.
Also applies to: 25-25, 35-35, 45-45, 55-55, 65-65, 75-75, 85-85, 95-95, 105-105
67-67
: Fix incorrect demo references.The following properties incorrectly reference the 'cancel-button-text' demo when they should have their own dedicated demos:
swipe-duration
title
visible
Also applies to: 77-77, 97-97
examples/sites/demos/mobile/app/tabs/webdoc/tabs.js (1)
147-147
: Verify if there are methods to document.The component has no documented methods. Consider if there should be methods for programmatic control, such as:
- Switching tabs programmatically
- Getting the current active tab
- Adding/removing tabs dynamically
Run this script to check for potential methods:
✅ Verification successful
🏁 Script executed:
#!/bin/bash # Search for potential method definitions in the Tabs component ast-grep --pattern 'methods: { $$$ }'Length of output: 41
🏁 Script executed:
#!/bin/bash # Let's search for all occurrences of "methods:" in the Tabs component file to verify if there are any defined methods. rg -n "methods:" examples/sites/demos/mobile/app/tabs/webdoc/tabs.jsLength of output: 114
No Methods Found for Documentation
The search confirms that both occurrences of the methods property (lines 147 and 166) are empty arrays. Since no methods are defined in this component, there's nothing to document regarding programmatic control at this point.
examples/sites/src/views/components/contributor.vue (2)
9-11
: Add security attributes to external links.External links should include security attributes to prevent potential security vulnerabilities.
Apply this diff to add the
nofollow
attribute:- <a :href="item.homepage" class="cmp-contributor-item" rel="noopener noreferrer" target="_blank"> + <a :href="item.homepage" class="cmp-contributor-item" rel="noopener noreferrer nofollow" target="_blank">
26-62
: LGTM!The styles are well-organized, scoped appropriately, and follow best practices.
🧰 Tools
🪛 ESLint
[error] 26-62: Expected blank line before this block.
(vue/padding-line-between-blocks)
examples/sites/src/views/components/mobile.vue (3)
14-14
: Add accessibility attributes to iframe.The iframe element should have a title and aria-label for better accessibility.
Apply this diff:
- <iframe ref="iframeRef" width="100%" height="100%" :src="iframeUrl" frameborder="0"></iframe> + <iframe + ref="iframeRef" + width="100%" + height="100%" + :src="iframeUrl" + frameborder="0" + title="Mobile preview" + aria-label="Mobile preview of the component"> + </iframe>
48-62
: Add input validation and error handling for iframe URL updates.The URL construction and iframe updates lack validation and error handling.
Apply this diff to improve security and reliability:
+const validateUrl = (url) => { + if (!url || !url.startsWith(mobilePreview)) { + console.error('Invalid mobile preview URL') + return false + } + return true +} const pageInit = (demo) => { const { cmpId } = router.currentRoute.value.params - iframeUrl.value = `${mobilePreview}?component=${cmpId}&demo=${demo.codeFiles[0]}` + if (!cmpId || !demo?.codeFiles?.length) { + console.error('Invalid component ID or demo') + return + } + const url = `${mobilePreview}?component=${encodeURIComponent(cmpId)}&demo=${encodeURIComponent(demo.codeFiles[0])}` + if (validateUrl(url)) { + iframeUrl.value = url + } }
66-76
: Improve responsiveness of the phone container.The phone container uses fixed positioning and hardcoded dimensions which could cause layout issues on different screen sizes.
Apply this diff to improve responsiveness:
.phone-container { - width: 394px; + width: min(394px, 100%); height: 770px; border: 0; position: fixed; top: 200px; + right: max(10%, calc((100% - 394px) / 2)); background: url(data:image/png;base64,...) no-repeat center top; background-size: contain; z-index: 1; }examples/sites/src/views/components/components.vue (3)
90-92
: Remove unused imports.The imports
fetchDemosFile
anddemoConfig
are defined but never used.Apply this diff to remove unused imports:
-import { i18nByKey, getWord, $clone, fetchDemosFile, useApiMode } from '@/tools' +import { i18nByKey, getWord, $clone, useApiMode } from '@/tools' -import demoConfig from '@demos/config.js'🧰 Tools
🪛 ESLint
[error] 90-90: 'fetchDemosFile' is defined but never used.
(unused-imports/no-unused-imports)
[error] 92-92: 'demoConfig' is defined but never used.
(unused-imports/no-unused-imports)
101-105
: Fix macro order in script setup.
defineProps
should be the first statement in<script setup>
after imports.Apply this diff to fix the macro order:
-defineOptions({ - name: 'CmpPageVue' -}) - -const props = defineProps({ loadData: {}, appMode: {}, demoKey: {} }) +const props = defineProps({ loadData: {}, appMode: {}, demoKey: {} }) + +defineOptions({ + name: 'CmpPageVue' +})🧰 Tools
🪛 ESLint
[error] 105-105: defineProps should be the first statement in
<script setup>
(after any potential import statements or type definitions).(vue/define-macros-order)
371-373
: Add error handling for clipboard operations.The clipboard API operation lacks error handling.
Apply this diff to improve error handling:
const copyText = (text) => { - navigator.clipboard.writeText(text) + if (!text) { + console.error('No text to copy') + return + } + navigator.clipboard.writeText(text) + .then(() => { + // Optionally show success message + console.log('Text copied successfully') + }) + .catch((error) => { + console.error('Failed to copy text:', error) + }) }examples/sites/demos/mobile/app/container/webdoc/container.js (1)
28-28
:⚠️ Potential issueFix typo in demo reference.
- demoId: 'basic-usaage' + demoId: 'basic-usage'Likely invalid or redundant comment.
examples/sites/demos/mobile/app/tag/webdoc/tag.js (2)
132-134
: LGTM! Well-structured type definition.The IColor type definition is clear and includes helpful comments about custom color values.
62-63
:⚠️ Potential issueDocument the 'close' and 'click' events.
Add documentation for the following events:
- events: [], + events: [ + { + name: 'close', + type: 'Function()', + desc: { + 'zh-CN': '关闭标签时触发', + 'en-US': 'Triggered when the tag is closed' + } + }, + { + name: 'click', + type: 'Function()', + desc: { + 'zh-CN': '点击标签时触发', + 'en-US': 'Triggered when the tag is clicked' + } + } + ],Likely invalid or redundant comment.
examples/sites/demos/mobile/app/radio/webdoc/radio.js (2)
86-93
: Implement radio-group and radio-button components.Both components are defined but lack implementation details. This could lead to confusion for developers trying to use these components.
Would you like me to help implement the missing properties, events, and slots for these components?
Also applies to: 94-101
50-58
:⚠️ Potential issueFix inconsistency in value prop configuration.
The value prop accepts object type but its default value is shown as a string.
Apply this diff to fix the inconsistency:
name: 'value', type: 'object | string', - defaultValue: '{}', + defaultValue: {}, desc: { 'zh-CN': '<p>绑定值,该属性默认为{}</p>', - 'en-US': 'display different button' + 'en-US': 'Binding value, defaults to an empty object' },Likely invalid or redundant comment.
examples/sites/demos/mobile/app/actionsheet/webdoc/actionsheet.js (2)
64-71
:⚠️ Potential issueFix method configuration.
The visibleHandle method has incorrect type and description.
Apply this diff to fix the issues:
name: 'visibleHandle', - type: 'string', + type: 'Function', defaultValue: '', desc: { 'zh-CN': '<p>菜单列表隐藏方法</p>', - 'en-US': 'Click' + 'en-US': 'Method to hide the menu list' },Likely invalid or redundant comment.
20-28
:⚠️ Potential issueFix menus prop configuration.
The menus prop has several issues:
- Default value of '50' doesn't match the Array type
- English description is incorrect
Apply this diff to fix the issues:
name: 'menus', type: 'Array', - defaultValue: '50', + defaultValue: '[]', desc: { 'zh-CN': '<p>列表数组</p>', - 'en-US': 'display different button' + 'en-US': 'List array configuration' },Likely invalid or redundant comment.
examples/sites/demos/mobile/app/badge/webdoc/badge.js (1)
50-60
:⚠️ Potential issueFix incorrect demo mapping for max property.
The max property is incorrectly mapped to the 'is-mini' demo.
Apply this diff to fix the demo mapping:
name: 'max', type: 'number', defaultValue: '', desc: { 'zh-CN': "指定徽章显示的最大值,如果实际获取的徽章值超过该最大值,则以最大值后接一个 '+' 的形式显示徽章数,要求 value 是 number 类型", 'en-US': 'Specifies the maximum number of badges to be displayed. If the actual badge value exceeds the maximum value, the number of badges is displayed in the format of "+". The value must be of the number type.' }, - demoId: 'is-mini' + demoId: 'max'Likely invalid or redundant comment.
examples/sites/demos/mobile/app/button/webdoc/button.js (1)
84-84
: Fix incorrect type definition for click event.The click event type is defined as
PointEvent
which seems to be a typo.Apply this fix:
- type: '(event: PointEvent) => void', + type: '(event: PointerEvent) => void',examples/sites/demos/mobile/app/imageviewer/webdoc/imageviewer.js (1)
13-16
: Fix incorrect English descriptions.Multiple properties have incorrect or duplicated English descriptions. For example, many properties incorrectly use "display different button" as their description.
Each property should have a unique and accurate English description. For example:
// For arrow-show - 'en-US': 'display different button' + 'en-US': 'Controls the visibility of image navigation arrows' // For async-close - 'en-US': 'display different button' + 'en-US': 'Enables asynchronous closing behavior' // For close-show - 'en-US': 'display different button' + 'en-US': 'Controls the visibility of the close button' // For events - 'en-US': 'Click' + 'en-US': 'Triggered when the current image changes' // For slots - 'en-US': 'Click' + 'en-US': 'Custom content for the image index display'Also applies to: 23-26, 33-36, 43-46, 53-56, 63-66, 73-76, 83-86, 93-96, 103-106, 115-117, 124-126, 136-138
examples/sites/demos/mobile/app/label/webdoc/label.js (2)
31-32
: Fix boolean property default values.The boolean properties
is-required
andwholeline
have string literals'false'
as default values, which should be boolean values instead.- defaultValue: 'false', + defaultValue: false,Also applies to: 90-92
13-16
: Enhance English documentation quality.The English descriptions are significantly less detailed than their Chinese counterparts. Consider:
- Making English descriptions as informative as Chinese ones
- Adding examples where appropriate
Here's an example of improved English descriptions:
// For decimal - 'en-US': 'decimal' + 'en-US': '<p>Number of decimal places to display</p>' // For ellipsis - 'en-US': 'ellipsis' + 'en-US': '<p>Maximum number of lines to display before showing ellipsis</p>' // For position - 'en-US': 'position of whole line display' + 'en-US': '<p>Text alignment when displayed as whole line: left, center, or right</p>'Also applies to: 23-26, 33-36, 43-46, 53-56, 63-66, 73-76, 83-86, 93-96
examples/sites/demos/mobile/app/alert/webdoc/alert.js (1)
1-217
: LGTM!The alert component API is well-structured with comprehensive documentation for all properties, events, slots, and types. The descriptions are clear in both Chinese and English, and all demo IDs are properly linked.
examples/sites/demos/mobile/app/multi-select/webdoc/multi-select.js (1)
91-92
: Verify demo references for consistency.The
wheel
demo is referenced but the actual demo file is namedtype-wheel.vue
. This inconsistency could lead to broken demo links.Run this script to verify demo references:
#!/bin/bash # Description: Verify that referenced demos exist and match the actual demo files # List all available demos echo "Available demos:" fd -e vue . examples/sites/demos/mobile/app/multi-select/ # Check specific demo references echo -e "\nChecking referenced demos:" for demo in wheel type-wheel; do fd -e vue "^${demo}.vue$" examples/sites/demos/mobile/app/multi-select/ done
'zh-CN': '<p>是否禁用下拉刷新</p>', | ||
'en-US': 'disablde pull down' | ||
}, |
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.
🛠️ Refactor suggestion
Fix documentation quality issues.
Several documentation issues need to be addressed:
- Fix typos in English descriptions:
- "disablde pull down" → "disable pull down"
- "disablded pull up" → "disable pull up"
- Improve event descriptions:
- Replace generic "Click" with proper descriptions for pull-down and pull-up events
Apply these fixes:
- 'en-US': 'disablde pull down'
+ 'en-US': 'disable pull down'
- 'en-US': 'disablded pull up'
+ 'en-US': 'disable pull up'
- 'en-US': 'Click'
+ 'en-US': 'Triggered when content is pulled down to refresh'
- 'en-US': 'Click'
+ 'en-US': 'Triggered when content is pulled up to load more'
Also applies to: 24-26, 96-98, 106-108
'zh-CN': '<p>下拉加载文字</p>', | ||
'en-US': 'pull down loading text' | ||
}, | ||
demoId: 'pulling-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.
🛠️ Refactor suggestion
Fix inconsistent demo IDs.
There are mismatches between demo IDs referenced in the API documentation and the actual demo entries:
- Events reference demoId 'event' but the demo is named '刷新事件'
- Props reference 'pulling-text' for different text-related demos
Ensure consistent demo IDs by:
- Updating the demo ID in the events section:
- demoId: 'event'
+ demoId: '刷新事件'
- Using specific demo IDs for different text-related props to match their respective demos.
Also applies to: 77-77, 99-99, 109-109, 253-253
name: 'buttons', | ||
type: 'boolean', | ||
defaultValue: '', | ||
desc: { | ||
'zh-CN': '<p>设置组件基本按钮禁用或启用。</p>', | ||
'en-US': 'display different button' | ||
}, | ||
demoId: '' | ||
}, |
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.
🛠️ Refactor suggestion
Improve English descriptions for props documentation.
Several props have generic or duplicated English descriptions that need to be improved for clarity:
buttons
: "display different button" should describe the enable/disable functionalitydisplay
: "display different button" should describe the file deletion featurefile-icon-list
: "display different button" should describe the icon path configurationfile-title
: "display different button" should describe the header title settingheader-show
: "display different button" should describe the header visibilitymini-mode
: "display different button" should describe the mini mode featuresize
: "display different button" should describe the button size optionstype
: "display different button" should describe the text type setting
Consider updating the English descriptions to match their Chinese counterparts for better documentation quality.
Also applies to: 96-104, 106-114, 127-135, 137-145, 191-199, 231-239, 251-259
methods: [ | ||
{ | ||
name: 'timeout', | ||
type: 'Function', | ||
defaultValue: '', | ||
desc: { | ||
'zh-CN': '<p>消失前的回调</p>', | ||
'en-US': '' | ||
}, | ||
demoId: 'base' | ||
} | ||
], |
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.
🛠️ Refactor suggestion
Add English description for the timeout method.
The timeout method is missing its English description. Please provide a clear description of the callback functionality.
Apply this diff:
desc: {
'zh-CN': '<p>消失前的回调</p>',
- 'en-US': ''
+ 'en-US': '<p>Callback function before toast disappears</p>'
},
📝 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.
methods: [ | |
{ | |
name: 'timeout', | |
type: 'Function', | |
defaultValue: '', | |
desc: { | |
'zh-CN': '<p>消失前的回调</p>', | |
'en-US': '' | |
}, | |
demoId: 'base' | |
} | |
], | |
methods: [ | |
{ | |
name: 'timeout', | |
type: 'Function', | |
defaultValue: '', | |
desc: { | |
'zh-CN': '<p>消失前的回调</p>', | |
'en-US': '<p>Callback function before toast disappears</p>' | |
}, | |
demoId: 'base' | |
} | |
], |
slots: [ | ||
{ | ||
name: 'default', | ||
defaultValue: '', | ||
desc: { | ||
'zh-CN': '<p>自定义标题</p>', | ||
'en-US': 'Click' | ||
}, | ||
demoId: 'left-right-arrow' | ||
}, | ||
{ | ||
name: 'left', | ||
defaultValue: '', | ||
desc: { | ||
'zh-CN': '<p>自定义左侧内容</p>', | ||
'en-US': 'Click' | ||
}, | ||
demoId: 'left-right-text' | ||
}, | ||
{ | ||
name: 'right', | ||
defaultValue: '', | ||
desc: { | ||
'zh-CN': '<p>自定义右侧内容</p>', | ||
'en-US': 'Click' | ||
}, | ||
demoId: 'left-right-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.
Fix incorrect English descriptions for slots.
The English descriptions for slots are incorrectly set to "Click" and should match their Chinese counterparts.
Apply these changes:
name: 'default',
defaultValue: '',
desc: {
'zh-CN': '<p>自定义标题</p>',
- 'en-US': 'Click'
+ 'en-US': 'Custom title'
}
name: 'left',
defaultValue: '',
desc: {
'zh-CN': '<p>自定义左侧内容</p>',
- 'en-US': 'Click'
+ 'en-US': 'Custom left content'
}
name: 'right',
defaultValue: '',
desc: {
'zh-CN': '<p>自定义右侧内容</p>',
- 'en-US': 'Click'
+ 'en-US': 'Custom right content'
}
📝 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.
slots: [ | |
{ | |
name: 'default', | |
defaultValue: '', | |
desc: { | |
'zh-CN': '<p>自定义标题</p>', | |
'en-US': 'Click' | |
}, | |
demoId: 'left-right-arrow' | |
}, | |
{ | |
name: 'left', | |
defaultValue: '', | |
desc: { | |
'zh-CN': '<p>自定义左侧内容</p>', | |
'en-US': 'Click' | |
}, | |
demoId: 'left-right-text' | |
}, | |
{ | |
name: 'right', | |
defaultValue: '', | |
desc: { | |
'zh-CN': '<p>自定义右侧内容</p>', | |
'en-US': 'Click' | |
}, | |
demoId: 'left-right-text' | |
} | |
] | |
slots: [ | |
{ | |
name: 'default', | |
defaultValue: '', | |
desc: { | |
'zh-CN': '<p>自定义标题</p>', | |
'en-US': 'Custom title' | |
}, | |
demoId: 'left-right-arrow' | |
}, | |
{ | |
name: 'left', | |
defaultValue: '', | |
desc: { | |
'zh-CN': '<p>自定义左侧内容</p>', | |
'en-US': 'Custom left content' | |
}, | |
demoId: 'left-right-text' | |
}, | |
{ | |
name: 'right', | |
defaultValue: '', | |
desc: { | |
'zh-CN': '<p>自定义右侧内容</p>', | |
'en-US': 'Custom right content' | |
}, | |
demoId: 'left-right-text' | |
} | |
] |
'en-US': 'display different button' | ||
}, |
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.
🛠️ Refactor suggestion
Improve English descriptions.
Several properties have generic or reused English descriptions that don't match their Chinese counterparts:
backgroundColor
: "display different button" should be "Set background color"messageTotal
: "display different button" should be "Message count in the top-right corner"messageType
: "display different button" should be "Message reminder type in the top-right corner, 'basic' shows a dot without count"messageUpperLimit
: "display different button" should be "Upper limit for message count display"size
: "display different button" should be "Set avatar size"v-model/modelValue
: "display different button" should be "Icon class name when type=icon, text string when type=label, resource path when type=image"
Also applies to: 35-36, 45-46, 55-56, 85-86, 106-107
'zh-CN': '<p>内容对象列表,对象字段包含:icon图片地址、label标题、subLabel子标题</p>', | ||
'en-US': 'display different button' | ||
}, |
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.
🛠️ Refactor suggestion
Improve English translations for better clarity.
Several properties have incomplete or incorrect English descriptions that simply state "display different button". This doesn't accurately describe the property's purpose.
Apply these improvements to the English translations:
- 'en-US': 'display different button'
+ 'en-US': 'Whether to show the overlay layer, defaults to true'
- 'en-US': 'display different button'
+ 'en-US': 'Maximum height of the popover'
- 'en-US': 'display different button'
+ 'en-US': 'Controls the visibility state'
- 'en-US': 'display different button'
+ 'en-US': 'Whether the component is disabled'
- 'en-US': 'display different button'
+ 'en-US': 'Icon to display on the left side of menu options'
- 'en-US': 'display different button'
+ 'en-US': 'Options data array'
- 'en-US': 'display different button'
+ 'en-US': 'CSS class name for the title'
- 'en-US': 'display different button'
+ 'en-US': 'Menu item type, can be filter, sort, or selection. Defaults to selection'
- 'en-US': 'display different button'
+ 'en-US': 'Currently selected value, can be bound using v-model'
Also applies to: 74-76, 84-86, 94-95, 104-105, 114-115, 134-135, 144-145, 154-155
'zh-CN': '<p>标题和选项的选中态颜色</p>', | ||
'en-US': 'display different button' | ||
}, |
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.
🛠️ Refactor suggestion
Improve English translations for better clarity.
Similar to the popover component, several properties have incomplete English descriptions that simply state "display different button".
Apply these improvements to the English translations:
- 'en-US': 'display different button'
+ 'en-US': 'Active color for selected titles and options'
- 'en-US': 'display different button'
+ 'en-US': 'Whether to close the menu when clicking outside, defaults to true'
- 'en-US': 'display different button'
+ 'en-US': 'Whether to close the menu when clicking the overlay, defaults to true'
- 'en-US': 'display different button'
+ 'en-US': 'Direction of the dropdown menu expansion, defaults to down'
- 'en-US': 'display different button'
+ 'en-US': 'Animation duration for the dropdown menu'
- 'en-US': 'display different button'
+ 'en-US': 'Whether to show the overlay layer, defaults to true'
- 'en-US': 'display different button'
+ 'en-US': 'Whether the dropdown item is disabled, defaults to false'
- 'en-US': 'display different button'
+ 'en-US': 'Icon to display on the left side of the menu item'
- 'en-US': 'display different button'
+ 'en-US': 'Array of options data'
- 'en-US': 'display different button'
+ 'en-US': 'Title of the menu item'
- 'en-US': 'display different button'
+ 'en-US': 'Custom class name for the title'
- 'en-US': 'display different button'
+ 'en-US': 'Type of menu item, can be filter, sort, or selection. Defaults to selection'
- 'en-US': 'display different button'
+ 'en-US': 'Currently selected value, can be bound using v-model'
Also applies to: 24-26, 34-36, 44-46, 54-56, 64-66, 93-95, 103-105, 113-115, 123-125, 133-135, 143-145, 153-155
name: 'change', | ||
defaultValue: '', | ||
desc: { | ||
'zh-CN': '<p>点击选项时触发,type为 selection 时启用</p>', | ||
'en-US': 'Click' | ||
}, | ||
demoId: 'event' | ||
}, |
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.
🛠️ Refactor suggestion
Add missing event type definitions.
The event definitions are missing their type information, which is important for TypeScript users.
Add the missing type information:
name: 'change',
+ type: '(value: string) => void',
defaultValue: '',
name: 'close',
+ type: '() => void',
defaultValue: '',
name: 'open',
+ type: '() => void',
defaultValue: '',
Also applies to: 170-177, 179-186
name: 'form-item', | ||
type: 'component', | ||
props: [], | ||
events: [], | ||
methods: [], | ||
slots: [] | ||
} |
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.
🛠️ Refactor suggestion
Complete the form-item component documentation.
The form-item component definition is empty. Consider adding its properties, events, methods, and slots for completeness.
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: 9
♻️ Duplicate comments (7)
examples/sites/src/views/components/saas.vue (1)
33-44
:⚠️ Potential issueAdd input validation for dynamic imports.
The URL construction for dynamic imports lacks validation and sanitization.
Apply this diff to add validation:
+const validatePath = (path) => { + if (!path || !/^[a-zA-Z0-9-_/]+$/.test(path)) { + console.error('Invalid path') + return false + } + return true +} const baseUrl = import.meta.env.BASE_URL const loadData = ({ cmpId, lang }) => { + if (!validatePath(cmpId) || !validatePath(lang)) { + return Promise.reject(new Error('Invalid parameters')) + } + const webdocPath = getWebdocPath(cmpId) + if (!validatePath(webdocPath)) { + return Promise.reject(new Error('Invalid webdoc path')) + } const promiseArr = [ fetchDemosFile(`${staticPath.value}/${getWebdocPath(cmpId)}/webdoc/${cmpId}.${lang}.md`), import( /* @vite-ignore */ `${baseUrl}@demos/apis/${getWebdocPath(cmpId) === 'chart' ? cmpId : getWebdocPath(cmpId)}.js` ), import( /* @vite-ignore */ `${baseUrl}${staticPath.value}/${getWebdocPath(cmpId)}/webdoc/${cmpId}.js` ) ]examples/sites/src/views/components/demo.vue (2)
224-236
:⚠️ Potential issueAdd error handling to clipboard operations.
The copy code function should handle clipboard API failures gracefully.
Apply this diff to improve error handling:
const copyCode = async (demo) => { + try { if (demo.isOpen) { const idx = parseInt(state.tabValue.slice(3)) - navigator.clipboard.writeText(demo.files[idx].code) + await navigator.clipboard.writeText(demo.files[idx].code) } else { await getDemoCode(demo) - navigator.clipboard.writeText(demo.files[0].code) + await navigator.clipboard.writeText(demo.files[0].code) } state.copyTip = i18nByKey('copyCodeOk') state.copyIcon = 'i-ti-check' + } catch (error) { + console.error('Failed to copy code:', error) + state.copyTip = i18nByKey('copyCodeError') + state.copyIcon = 'i-ti-close' + } }
179-204
: 🛠️ Refactor suggestionImprove error handling and performance in demo code loading.
The demo code loading function could benefit from better error handling, memoization, and loading state management.
Apply this diff to improve the implementation:
+const loadingState = ref(false) +const memoizedPaths = new Map<string, string>() const getDemoCodeFn = async (demo, forceUpdate?: boolean) => { + loadingState.value = true try { if (!demo.files || forceUpdate) { const cmpId = router.currentRoute.value.params.cmpId const promises = demo.codeFiles.map(async (fileName) => { const demoName = apiModeFn.getDemoName(`${getWebdocPath(cmpId)}/${fileName}`) - let code = '' + const pathKey = isMobileFirst.value ? `mobile-first/${demoName}` : demoName + + if (memoizedPaths.has(pathKey) && !forceUpdate) { + return { + code: memoizedPaths.get(pathKey)!, + fileName, + language: languageMap[$split(fileName, '.', -1)] || '' + } + } const path = isMobileFirst.value ? `@demos/mobile-first/app/${demoName}` : `${staticDemoPath}/${demoName}` - code = await fetchDemosFile(path) - .then((code) => { - return code - }) - .catch(() => { - return `${demoName}示例资源不存在,请检查文件名是否正确?` - }) + try { + const code = await fetchDemosFile(path) + memoizedPaths.set(pathKey, code) + return { + code, + fileName, + language: languageMap[$split(fileName, '.', -1)] || '' + } + } catch (error) { + console.error(`Failed to load demo ${demoName}:`, error) + throw new Error(`Failed to load demo ${demoName}: ${error.message}`) + } - const ext = $split(fileName, '.', -1) - const language = languageMap[ext] || '' - return { code, fileName, language } }) demo.files = await Promise.all(promises) return demo.files } return demo.files + } catch (error) { + console.error('Error in getDemoCodeFn:', error) + throw error + } finally { + loadingState.value = false } }examples/sites/src/views/components/components.vue (4)
90-92
: 🛠️ Refactor suggestionRemove unused imports.
The imports
fetchDemosFile
anddemoConfig
are defined but never used.Apply this diff to remove unused imports:
-import { i18nByKey, getWord, $clone, fetchDemosFile, useApiMode } from '@/tools' +import { i18nByKey, getWord, $clone, useApiMode } from '@/tools' -import demoConfig from '@demos/config.js'🧰 Tools
🪛 ESLint
[error] 90-90: 'fetchDemosFile' is defined but never used.
(unused-imports/no-unused-imports)
[error] 92-92: 'demoConfig' is defined but never used.
(unused-imports/no-unused-imports)
101-105
: 🛠️ Refactor suggestionFix macro order in script setup.
defineProps
should be the first statement in<script setup>
after imports.Apply this diff to fix the macro order:
+const props = defineProps({ loadData: {}, appMode: {}, demoKey: {} }) + defineOptions({ name: 'CmpPageVue' }) - -const props = defineProps({ loadData: {}, appMode: {}, demoKey: {} })🧰 Tools
🪛 ESLint
[error] 105-105: defineProps should be the first statement in
<script setup>
(after any potential import statements or type definitions).(vue/define-macros-order)
375-377
:⚠️ Potential issueAdd error handling for clipboard operations.
The clipboard API operation lacks error handling.
Apply this diff to improve error handling:
const copyText = (text) => { - navigator.clipboard.writeText(text) + if (!text) { + console.error('No text to copy') + return + } + navigator.clipboard.writeText(text) + .then(() => { + // Optionally show success message + console.log('Text copied successfully') + }) + .catch((error) => { + console.error('Failed to copy text:', error) + }) }
224-251
: 🛠️ Refactor suggestionImprove error handling in scrollByHash function.
The scrollByHash function has several issues:
- Silently catches errors without proper handling
- Missing validation for hash parameter
- No error handling for scrollTo operations
Apply this diff to improve error handling:
const scrollByHash = (hash) => { + if (hash && !/^[a-zA-Z0-9-_]+$/.test(hash)) { + console.error('Invalid hash format') + return + } setTimeout(() => { if (!hash) { - scrollRef.value.scrollTo({ - top: 0, - left: 0 - }) + try { + scrollRef.value.scrollTo({ + top: 0, + left: 0 + }) + } catch (error) { + console.error('Failed to scroll to top:', error) + } } else if (state.currJson.types[hash]) { jumpToApi(hash) } else { let scrollTarget try { - // 用户打开官网有时候会带一些特殊字符的hash,try catch一下防止js报错 scrollTarget = document.querySelector(`#${hash}`) - } catch (err) {} + } catch (error) { + console.error('Failed to query selector:', error) + return + } if (scrollTarget && !isRunningTest) { // doc-layout-scroller(滚动) > tabs > tab-content(relative), 造成 scrollTarget.offsetTop 是相对于 tab-content的距离 // 所以滚动需要修正 tab-title的占位高度才行 - scrollRef.value.scrollTo({ - top: scrollTarget.offsetTop, - left: 0, - behavior: 'smooth' - }) + try { + scrollRef.value.scrollTo({ + top: scrollTarget.offsetTop, + left: 0, + behavior: 'smooth' + }) + } catch (error) { + console.error('Failed to scroll to target:', error) + } } } }, 0) }
🧹 Nitpick comments (11)
examples/sites/src/views/components/components/float-settings.vue (1)
132-137
: Consider extracting mode check to a shared configuration.While the logic is correct, consider these improvements for better maintainability:
- Move the mode check to a shared configuration file
- Define the default API mode as a constant
-const isPC = import.meta.env.VITE_APP_MODE === 'pc' +import { IS_PC_MODE, DEFAULT_API_MODE } from '@/config/modes' -if (!isPC || templateModeState.isSaas) { +if (!IS_PC_MODE || templateModeState.isSaas) { state.styleSettings = state.styleSettings.filter((item) => item.name !== 'apiMode') - apiModeState.apiMode = 'Options' + apiModeState.apiMode = DEFAULT_API_MODE }examples/sites/src/views/components/components/anchor.vue (4)
9-9
: Consider making the container ID configurable.The container ID is hardcoded to "#doc-layout-scroller". Consider making it a prop to improve component reusability.
- container-id="#doc-layout-scroller" + :container-id="props.containerId"
20-21
: Translate comments to English.For better international collaboration, consider translating Chinese comments to English.
-// 实例锚点 +// Demo anchor links -// 组件API锚点 +// Component API anchor linksAlso applies to: 31-32
70-70
: Consider consolidating viewport height calculations.Multiple instances of similar viewport height calculations could be consolidated into a CSS variable for better maintainability.
+:root { + --anchor-viewport-offset: 300px; +} .cmp-page-anchor { :deep(.tiny-anchor__affix) { - max-height: calc(100vh - 300px); + max-height: calc(100vh - var(--anchor-viewport-offset)); } } .catalog { - height: calc(100vh - 280px); + height: calc(100vh - var(--anchor-viewport-offset) + 20px); } .tiny-anchor__dot { - max-height: calc(100vh - 300px); + max-height: calc(100vh - var(--anchor-viewport-offset)); }Also applies to: 88-88, 93-93
107-115
: Add cross-browser scrollbar styling.The scrollbar styling is currently only supported in WebKit browsers. Consider adding Firefox (scrollbar-width) and future-proof (scrollbar-gutter) properties.
.catalog { + scrollbar-width: thin; + scrollbar-gutter: stable; } .catalog::-webkit-scrollbar { width: 10px; background-color: #f5f5f5; } .catalog::-webkit-scrollbar-thumb { border-radius: 10px; background-color: #c1c1c1; + border: 2px solid #f5f5f5; }examples/sites/src/views/components/components/api-docs.vue (3)
139-150
: Handle null/undefined rowData for getRowData.
If the requested type isn’t found in the table data, “getRowData” currently returns “undefined.” Consider returning a default or handling this scenario inside “jumpToApi” to avoid potential runtime errors.
94-165
: Address spacing before and after blocks in <script setup>.
ESLint flags the lack of blank lines between “<script setup>” statements and adjacent blocks (vue/padding-line-between-blocks). Adding a blank line before and after these blocks helps maintain code readability and team consistency.Example fix:
<script setup> + import { ref } from 'vue' ... } + const emit = defineEmits(['jump-to-demo'])🧰 Tools
🪛 ESLint
[error] 94-165: Expected blank line before this block.
(vue/padding-line-between-blocks)
[error] 125-125: defineEmits should be the first statement in
<script setup>
(after any potential import statements or type definitions).(vue/define-macros-order)
166-219
: Add a blank line before <style> block.
Again, ESLint (vue/padding-line-between-blocks) expects a blank line before the <style> block to maintain a clear separation from the preceding <script> block.Example fix:
defineExpose({ jumpToApi }) + <style scoped lang="less">
🧰 Tools
🪛 ESLint
[error] 166-219: Expected blank line before this block.
(vue/padding-line-between-blocks)
examples/sites/src/views/components/plus.vue (1)
24-27
: Use English comments for better maintainability.The Chinese comment should be in English for better international collaboration.
- // 兼容ts文档, 如果是ts文档则不拉取demos配置 + // Skip demos config for TypeScript documentation (interfaces, types, classes)examples/sites/src/views/components/components/contributor.vue (1)
9-11
: Add security attributes for external links.External links should include additional security attributes to prevent potential security issues.
- <a :href="item.homepage" class="cmp-contributor-item" rel="noopener noreferrer" target="_blank"> + <a + :href="item.homepage" + class="cmp-contributor-item" + rel="noopener noreferrer" + target="_blank" + referrerpolicy="no-referrer" + >examples/sites/src/views/components/saas.vue (1)
56-62
: Add a blank line before the style block.According to the static analysis hints, a blank line is expected before the style block.
Apply this diff to fix the formatting:
</script> + <style scoped lang="less">
🧰 Tools
🪛 ESLint
[error] 56-62: Expected blank line before this block.
(vue/padding-line-between-blocks)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
examples/sites/demos/apis/form.js
(18 hunks)examples/sites/src/views/components/components.vue
(1 hunks)examples/sites/src/views/components/components/anchor.vue
(1 hunks)examples/sites/src/views/components/components/api-docs.vue
(1 hunks)examples/sites/src/views/components/components/contributor.vue
(1 hunks)examples/sites/src/views/components/components/float-settings.vue
(1 hunks)examples/sites/src/views/components/components/header.vue
(1 hunks)examples/sites/src/views/components/components/version-tip.vue
(1 hunks)examples/sites/src/views/components/demo.vue
(4 hunks)examples/sites/src/views/components/index.vue
(1 hunks)examples/sites/src/views/components/pc.vue
(1 hunks)examples/sites/src/views/components/plus.vue
(1 hunks)examples/sites/src/views/components/saas.vue
(1 hunks)examples/sites/src/views/layout/layout.vue
(1 hunks)
✅ Files skipped from review due to trivial changes (2)
- examples/sites/src/views/components/components/version-tip.vue
- examples/sites/src/views/layout/layout.vue
🚧 Files skipped from review as they are similar to previous changes (2)
- examples/sites/src/views/components/pc.vue
- examples/sites/demos/apis/form.js
🧰 Additional context used
🪛 ESLint
examples/sites/src/views/components/components/header.vue
[error] 16-31: Expected blank line before this block.
(vue/padding-line-between-blocks)
[error] 20-20: Expected 1 empty line after import statement not followed by another import.
(import/newline-after-import)
[error] 32-68: Expected blank line before this block.
(vue/padding-line-between-blocks)
examples/sites/src/views/components/saas.vue
[error] 56-62: Expected blank line before this block.
(vue/padding-line-between-blocks)
examples/sites/src/views/components/components/contributor.vue
[error] 16-25: Expected blank line before this block.
(vue/padding-line-between-blocks)
[error] 26-62: Expected blank line before this block.
(vue/padding-line-between-blocks)
examples/sites/src/views/components/components/api-docs.vue
[error] 94-165: Expected blank line before this block.
(vue/padding-line-between-blocks)
[error] 125-125: defineEmits should be the first statement in <script setup>
(after any potential import statements or type definitions).
(vue/define-macros-order)
[error] 166-219: Expected blank line before this block.
(vue/padding-line-between-blocks)
examples/sites/src/views/components/components/anchor.vue
[error] 15-64: Expected blank line before this block.
(vue/padding-line-between-blocks)
[error] 16-16: 'ref' is defined but never used.
(unused-imports/no-unused-imports)
[error] 17-17: Expected 1 empty line after import statement not followed by another import.
(import/newline-after-import)
[error] 60-60: defineEmits should be the first statement in <script setup>
(after any potential import statements or type definitions).
(vue/define-macros-order)
[error] 65-116: Expected blank line before this block.
(vue/padding-line-between-blocks)
examples/sites/src/views/components/components.vue
[error] 90-90: 'fetchDemosFile' is defined but never used.
(unused-imports/no-unused-imports)
[error] 92-92: 'demoConfig' is defined but never used.
(unused-imports/no-unused-imports)
[error] 105-105: defineProps should be the first statement in <script setup>
(after any potential import statements or type definitions).
(vue/define-macros-order)
🔇 Additional comments (4)
examples/sites/src/views/components/components/float-settings.vue (2)
51-60
: LGTM! Changes integrate well with existing code.The filtered settings are correctly rendered in the template and properly handled in the settings change handler.
Also applies to: 158-173
131-137
: LGTM! Changes align with mobile site refactoring goals.The changes successfully:
- Remove mobile-specific logic
- Implement a clean mode-based approach
- Maintain consistent behavior across different modes
examples/sites/src/views/components/components/api-docs.vue (1)
153-162
: Consider graceful handling when row is not found in jumpToApi.
The code checks for the DOM element’s existence before expanding the row, but not for the actual data object. If “rowData” is undefined, “apiTableRef.value.forEach” might behave unexpectedly.Would you like to verify if “rowData” can be undefined here under certain conditions? If so, we could add a guard clause.
examples/sites/src/views/components/index.vue (1)
11-13
: Useconst
for immutable variables and validate environment variables.The
componentDocs
variable doesn't need to be mutable, and environment variables should be validated to prevent runtime errors.
</div> | ||
</template> | ||
<script setup lang="ts"> | ||
import { ref, computed } 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.
🛠️ Refactor suggestion
Remove unused import.
The 'ref' import is not used in the code.
-import { ref, computed } from 'vue'
+import { computed } 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, computed } from 'vue' | |
import { computed } from 'vue' |
🧰 Tools
🪛 ESLint
[error] 16-16: 'ref' is defined but never used.
(unused-imports/no-unused-imports)
<script setup lang="ts"> | ||
import { ref, computed } from 'vue' | ||
import { TinyAnchor } from '@opentiny/vue' | ||
const props = defineProps({ anchorAffix: {}, currentJson: {}, activeTab: {}, langKey: {}, apiTypes: {} }) |
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.
🛠️ Refactor suggestion
Add proper prop type definitions and validation.
Props are defined without type information or validation. Consider adding proper TypeScript types and validation rules.
-const props = defineProps({ anchorAffix: {}, currentJson: {}, activeTab: {}, langKey: {}, apiTypes: {} })
+interface Props {
+ anchorAffix: boolean
+ currentJson: {
+ demos?: Array<{
+ demoId: string
+ name: Record<string, string>
+ }>
+ apis?: Array<{
+ name: string
+ [key: string]: any
+ }>
+ }
+ activeTab: 'demos' | 'api'
+ langKey: string
+ apiTypes: string[]
+}
+
+const props = withDefaults(defineProps<Props>(), {
+ anchorAffix: true,
+ currentJson: () => ({}),
+ activeTab: 'demos',
+ langKey: 'en',
+ apiTypes: () => []
+})
const emit = defineEmits(['link-click']) | ||
const handleAnchorClick = (...args) => { | ||
emit('link-click', ...args) | ||
} |
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.
🛠️ Refactor suggestion
Move defineEmits to the top and improve event typing.
According to Vue best practices, defineEmits should be placed at the top of the script, right after imports. Also, consider adding proper event type definitions.
+const emit = defineEmits<{
+ (e: 'link-click', ...args: any[]): void
+}>()
// Rest of the code...
-const emit = defineEmits(['link-click'])
const handleAnchorClick = (...args) => {
emit('link-click', ...args)
}
📝 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 emit = defineEmits(['link-click']) | |
const handleAnchorClick = (...args) => { | |
emit('link-click', ...args) | |
} | |
// Ensure this defineEmits call is placed at the top of your <script setup> block, | |
// immediately after any import statements or type definitions. | |
const emit = defineEmits<{ | |
(e: 'link-click', ...args: any[]): void | |
}>() | |
const handleAnchorClick = (...args) => { | |
emit('link-click', ...args) | |
} |
🧰 Tools
🪛 ESLint
[error] 60-60: defineEmits should be the first statement in <script setup>
(after any potential import statements or type definitions).
(vue/define-macros-order)
showIcon: true // 配置是否显示展开图标 | ||
} | ||
|
||
const emit = defineEmits(['jump-to-demo']) |
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.
🛠️ Refactor suggestion
Reorder defineEmits to meet recommended macro ordering.
According to the Vue style guide (vue/define-macros-order), “defineEmits” should appear immediately after your imports and before other statements.
Below is a suggested diff for lines around 125 to move defineEmits right after defineProps:
...
const props = defineProps({ currentJson: {}, chartCode: {}, tableData: {} })
+const emit = defineEmits(['jump-to-demo'])
+
const OuterLink = iconOuterLink()
...
Committable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 ESLint
[error] 125-125: defineEmits should be the first statement in <script setup>
(after any potential import statements or type definitions).
(vue/define-macros-order)
import Saas from './saas.vue' | ||
import Mobile from './mobile.vue' | ||
import Pc from './pc.vue' | ||
import Plus from './pc.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.
Fix incorrect import path for Plus component.
The Plus component is incorrectly imported from './pc.vue' instead of './plus.vue'.
-import Plus from './pc.vue'
+import Plus from './plus.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 Plus from './pc.vue' | |
import Plus from './plus.vue' |
return Promise.all(promiseArr).then(([mdString, apisJson, demosJson]) => { | ||
return { | ||
mdString, | ||
apisJson: apisJson.default, | ||
demosJson: demosJson?.default | ||
} | ||
}) | ||
} |
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.
🛠️ Refactor suggestion
Add error handling for Promise.all.
The Promise chain should include error handling to gracefully handle failures.
- return Promise.all(promiseArr).then(([mdString, apisJson, demosJson]) => {
+ return Promise.all(promiseArr)
+ .then(([mdString, apisJson, demosJson]) => {
return {
mdString,
apisJson: apisJson.default,
demosJson: demosJson?.default
}
+ })
+ .catch(error => {
+ console.error('Failed to load documentation:', error)
+ throw error
+ })
📝 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.
return Promise.all(promiseArr).then(([mdString, apisJson, demosJson]) => { | |
return { | |
mdString, | |
apisJson: apisJson.default, | |
demosJson: demosJson?.default | |
} | |
}) | |
} | |
return Promise.all(promiseArr) | |
.then(([mdString, apisJson, demosJson]) => { | |
return { | |
mdString, | |
apisJson: apisJson.default, | |
demosJson: demosJson?.default | |
} | |
}) | |
.catch(error => { | |
console.error('Failed to load documentation:', error) | |
throw error | |
}) | |
} |
import { TinyTooltip } from '@opentiny/vue' | ||
import { i18nByKey, getCmpContributors } from '@/tools' | ||
|
||
const props = defineProps({ componentId: {} }) |
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.
🛠️ Refactor suggestion
Add prop validation for componentId.
The componentId prop should have proper validation to ensure it's provided and of the correct type.
-const props = defineProps({ componentId: {} })
+const props = defineProps({
+ componentId: {
+ type: String,
+ required: true
+ }
+})
📝 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 props = defineProps({ componentId: {} }) | |
const props = defineProps({ | |
componentId: { | |
type: String, | |
required: true | |
} | |
}) |
<template> | ||
<header class="docs-header"> | ||
<div class="docs-title-wrap"> | ||
<div class="markdown-body markdown-top-body" v-html="mdContent"></div> |
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.
Sanitize HTML content to prevent XSS attacks.
Using v-html with unsanitized content from markdown could lead to XSS vulnerabilities.
- <div class="markdown-body markdown-top-body" v-html="mdContent"></div>
+ <div class="markdown-body markdown-top-body" v-html="sanitizeHtml(mdContent)"></div>
Add the following to the script section:
import DOMPurify from 'dompurify'
const sanitizeHtml = (html) => DOMPurify.sanitize(html)
import hljs from 'highlight.js' | ||
import { marked } from 'marked' | ||
import VersionTip from './version-tip.vue' | ||
const props = defineProps({ currentJson: {}, mdString: {} }) |
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.
🛠️ Refactor suggestion
Add prop validation and default values.
Props should have proper validation and default values.
-const props = defineProps({ currentJson: {}, mdString: {} })
+const props = defineProps({
+ currentJson: {
+ type: Object,
+ required: true,
+ default: () => ({})
+ },
+ mdString: {
+ type: String,
+ required: true,
+ default: ''
+ }
+})
📝 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 props = defineProps({ currentJson: {}, mdString: {} }) | |
const props = defineProps({ | |
currentJson: { | |
type: Object, | |
required: true, | |
default: () => ({}) | |
}, | |
mdString: { | |
type: String, | |
required: true, | |
default: '' | |
} | |
}) |
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: 1
🧹 Nitpick comments (2)
packages/mobile/components/slider/src/renderless/index.ts (2)
241-245
: Consider adding type guards for better type safety.While the logic is correct, the SVG class check could benefit from a type guard to ensure type safety.
- return ( - state.isDouble && - (hasClass(previousElementSibling, cls) || - (event.target as SVGAElement).className.baseVal === 'tiny-slider-right-svg') - ) + return ( + state.isDouble && + (hasClass(previousElementSibling, cls) || + (event.target instanceof SVGElement && event.target.className.baseVal === 'tiny-slider-right-svg')) + )
320-320
: Consider translating the comment to English.The implementation of two-way binding is correct, but for consistency, consider updating the Chinese comment to English.
- emit('update:modelValue', api.getActiveButtonValue()) // 添加了一个emit触发input事件,实现双向绑定 + emit('update:modelValue', api.getActiveButtonValue()) // Added emit to trigger input event for two-way binding
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
examples/sites/demos/mobile/app/slider/webdoc/slider.js
(1 hunks)packages/mobile/components/slider/src/renderless/index.ts
(3 hunks)packages/theme-mobile/src/slider/index.less
(1 hunks)packages/theme-mobile/src/slider/vars.less
(1 hunks)
🔇 Additional comments (13)
packages/theme-mobile/src/slider/vars.less (1)
8-8
: Ensure important text variable is defined:
The CSS variable "--tvm-Slider-tips-text-color" is now set to "var(--tvm-color-text-important)" without a fallback. Please confirm that "--tvm-color-text-important" is always defined in your theme settings; otherwise, missing a fallback could lead to unexpected styling issues.packages/theme-mobile/src/slider/index.less (4)
17-17
: Verify slider prefix syntax:
The declaration "@slider-prefix-cls: ~'@{css-prefix}mobile-slider';" uses the tilde operator for literal string output. Ensure that this approach meets your team’s conventions and correctly interpolates the CSS prefix in all mobile slider components.
26-26
: Review hard-coded width calculation:
The width is set with "calc(100% - 89px)". Please verify that the 89px offset is intentional and documented. For maintainability, you might consider extracting this value into a variable if it is used in multiple places or may change in the future.
120-123
: Consistent slider tip styling update:
The styling for ".slider-prefix-cls__tips" has been updated so that the background now uses "--tvm-Slider-handle-bg-color" and both the border and text color use "--tvm-Slider-tips-text-color" without fallbacks. Ensure that these changes are aligned with your design guidelines and that "--tvm-Slider-tips-text-color" is reliably defined (as updated in vars.less) to prevent any unintended visual issues.
1-173
: Overall consistency check:
Double-check that all components referencing these slider variables have been updated accordingly, and that the removal of fallback values does not impact the interface when the expected theme variables are absent. This will help maintain a consistent look and prevent potential runtime CSS issues.packages/mobile/components/slider/src/renderless/index.ts (2)
226-229
: LGTM! Clean object assignment.The simplified object assignment improves code readability while maintaining the same functionality.
622-622
: LGTM! Improved type safety.The addition of explicit type annotations and simplified default parameter syntax enhances code quality.
examples/sites/demos/mobile/app/slider/webdoc/slider.js (6)
4-8
: LGTM! Well-structured component API documentation.The component API structure follows a clear and organized format.
66-68
: Incorrect English description for therange
prop.The English description "display different button" doesn't match the Chinese description which describes range selection functionality.
- 'en-US': 'display different button' + 'en-US': 'Whether to enable range selection'
107-109
: Incorrect English description for thevalue
prop.The English description "display different button" is incorrect and doesn't match the Chinese description which describes the value type requirements.
- 'en-US': 'display different button' + 'en-US': 'Set the current value for single slider, must be an integer or array.'
123-154
: LGTM! Well-documented events.The events documentation is comprehensive with clear type definitions and descriptions.
156-156
: Methods documentation needs to be updated.The methods array is empty but should document the following public methods available in the slider component:
- Value manipulation (through v-model)
formatTipValue
: Formats the tooltip valuecalculateValue
: Calculates the slider value based on positiondisplayTip/hideTip
: Controls tooltip visibility- Event handlers:
start
,change
,stop
157-168
: LGTM! Clear and accurate slot documentation.The slot documentation provides clear information about usage and scope parameters.
'zh-CN': '<p>通过配置<code>show-input<code>开启滑块输入框模式。</p>', | ||
'en-US': '<p>Enable slider input box mode by configuring<code>show input<code>.</p>' |
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.
Fix unclosed HTML code tags.
Several demo descriptions have unclosed <code>
tags.
- 'zh-CN': '<p>通过配置<code>show-input<code>开启滑块输入框模式。</p>',
- 'en-US': '<p>Enable slider input box mode by configuring<code>show input<code>.</p>'
+ 'zh-CN': '<p>通过配置<code>show-input</code>开启滑块输入框模式。</p>',
+ 'en-US': '<p>Enable slider input box mode by configuring<code>show input</code>.</p>'
📝 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.
'zh-CN': '<p>通过配置<code>show-input<code>开启滑块输入框模式。</p>', | |
'en-US': '<p>Enable slider input box mode by configuring<code>show input<code>.</p>' | |
'zh-CN': '<p>通过配置<code>show-input</code>开启滑块输入框模式。</p>', | |
'en-US': '<p>Enable slider input box mode by configuring<code>show input</code>.</p>' |
[e2e-test-warn] The title of the Pull request should look like "fix(vue-renderless): [action-menu, alert] fix xxx bug". Please make sure you've read our contributing guide |
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: 12
🔭 Outside diff range comments (1)
examples/sites/src/views/components-doc/components/float-settings.vue (1)
197-218
: Fix potential issues in scroll listener setup.The scroll listener implementation has several issues:
- The
footer
variable is undefined but used in line 205- The debounced function might be called after component unmount
Apply this fix:
const setScrollListener = () => { nextTick(() => { const docLayout = document.getElementById('doc-layout-scroller') const nav = document.querySelector('.nav') + const footer = document.querySelector('footer') if (docLayout) { - docLayout.onscroll = debounce(100, false, () => { + const debouncedScroll = debounce(100, false, () => { const { scrollTop: layoutScrollTop } = docLayout const headerHeight = docLayout.querySelector('header')?.clientHeight || 0 + if (!footer) return const footerTop = footer.getBoundingClientRect().top const navHeight = nav?.clientHeight || 0 const currSettingsBottom = parseInt(window.getComputedStyle(floatSettings.value).getPropertyValue('bottom')) state.showBackTop = layoutScrollTop > 500 state.showSettings = footerTop - navHeight - headerHeight > 220 if (!state.initBottomVal) { state.initBottomVal = currSettingsBottom } }) + docLayout.onscroll = debouncedScroll + return () => { + docLayout.onscroll = null + debouncedScroll.cancel?.() + } } }) }
🧹 Nitpick comments (19)
examples/sites/src/views/components-doc/components/header.vue (2)
22-30
: Add error handling for markdown parsing.The markdown parsing could fail with invalid input. Consider adding try-catch block and error state handling.
const mdContent = computed(() => - marked(props.mdString, { - gfm: true, - highlight(code, language) { - const validLanguage = hljs.getLanguage(language) ? language : 'plaintext' - return hljs.highlight(code, { language: validLanguage }).value - } - }) + try { + return marked(props.mdString, { + gfm: true, + highlight(code, language) { + const validLanguage = hljs.getLanguage(language) ? language : 'plaintext' + return hljs.highlight(code, { language: validLanguage }).value + } + }) + } catch (error) { + console.error('Failed to parse markdown:', error) + return 'Failed to parse content' + } )
33-67
: Consider accessibility improvements in styles.A few suggestions to improve accessibility:
- Add sufficient color contrast for better readability
- Ensure font sizes meet WCAG guidelines
- Consider responsive design for different screen sizes
.docs-header { padding: 16px 40px; min-height: 102px; background-color: #fff; - box-shadow: 12px 0 20px 6px rgba(0, 0, 0, 0.06); + box-shadow: 12px 0 20px 6px rgba(0, 0, 0, 0.1); .markdown-top-body { z-index: var(--docs-markdown-top-body-zindex); - font-size: 14px; + font-size: clamp(14px, 1vw, 16px); transition: all ease-in-out 0.3s; :deep(h1) { margin: 0; padding: 0; - font-size: 24px; + font-size: clamp(24px, 2vw, 32px); line-height: 40px; } } }examples/sites/src/views/components-doc/composition/useTasksFinish.ts (1)
23-26
: Add return type interface.Define an interface for the hook's return type to improve documentation and type safety.
+interface TasksFinishHook { + finishTask: () => void; + waitTasks: () => Promise<boolean>; +} const useTasksFinish = (tasksNum: number) => { // ... existing code ... - return { finishTask, waitTasks } + return { finishTask, waitTasks } as TasksFinishHook } -export default useTasksFinish +export default useTasksFinish;examples/sites/src/views/components-doc/mobile.vue (2)
9-19
: Consider adding 'sandbox' or 'allow' attributes to the iframe.
Embedding external content may pose security risks; including “sandbox” or specifying “allow” attributes helps mitigate potential security vulnerabilities.<iframe ref="iframeRef" width="100%" height="100%" :src="iframeUrl" frameborder="0" + sandbox="allow-scripts allow-same-origin" ></iframe>
65-76
: Check mobile layout responsiveness.
Fixed width and height (394px x 770px) might not adapt to smaller screens. Check if truly required or consider a responsive approach.examples/sites/src/views/components-doc/common.vue (2)
90-90
: Remove unused 'fetchDemosFile' import.
ESLint highlights an unused import. Clean up for better maintainability.-import { i18nByKey, getWord, $clone, fetchDemosFile, useApiMode } from '@/tools' +import { i18nByKey, getWord, $clone, useApiMode } from '@/tools'🧰 Tools
🪛 ESLint
[error] 90-90: 'fetchDemosFile' is defined but never used.
(unused-imports/no-unused-imports)
91-91
: Remove unused 'demoConfig' import.
Similarly, 'demoConfig' is never used; removing it will clear lint warnings and declutter.-import demoConfig from '@demos/config.js'
🧰 Tools
🪛 ESLint
[error] 91-91: 'demoConfig' is defined but never used.
(unused-imports/no-unused-imports)
examples/sites/src/views/components-doc/pc.vue (1)
11-31
: Consider error handling for failed imports.The loadData function should handle potential import failures gracefully.
Add error handling:
const loadData = ({ cmpId, lang }) => { const promiseArr = [ fetchDemosFile(`@demos/app/${getWebdocPath(cmpId)}/webdoc/${cmpId}.${lang}.md`), import( /* @vite-ignore */ `${baseUrl}@demos/apis/${getWebdocPath(cmpId) === 'chart' ? cmpId : getWebdocPath(cmpId)}.js` ), import( /* @vite-ignore */ `${baseUrl}@demos/app/${getWebdocPath(cmpId)}/webdoc/${cmpId}.js` ) ] - return Promise.all(promiseArr).then(([mdString, apisJson, demosJson]) => { + return Promise.all(promiseArr).then(([mdString, apisJson, demosJson]) => { return { mdString, apisJson: apisJson.default, demosJson: demosJson.default } + }).catch(error => { + console.error(`Failed to load documentation for ${cmpId}:`, error) + return { + mdString: '', + apisJson: {}, + demosJson: {} + } }) }examples/sites/src/views/components-doc/plus.vue (1)
29-36
: Consider consistent error handling across components.Similar to pc.vue, consider adding error handling for failed imports.
Add error handling:
- return Promise.all(promiseArr).then(([mdString, apisJson, demosJson]) => { + return Promise.all(promiseArr).then(([mdString, apisJson, demosJson]) => { return { mdString, apisJson: apisJson.default, demosJson: demosJson?.default } + }).catch(error => { + console.error(`Failed to load documentation for ${cmpId}:`, error) + return { + mdString: '', + apisJson: {}, + demosJson: null + } })examples/sites/src/views/components-doc/components/contributor.vue (3)
21-21
: Add type validation for componentId prop.The componentId prop should have type validation.
-const props = defineProps({ componentId: {} }) +const props = defineProps({ + componentId: { + type: String, + required: true + } +})
9-11
: Add security attributes for external links.The external link already has
rel="noopener noreferrer"
which is good, but consider adding additional security measures.- <a :href="item.homepage" class="cmp-contributor-item" rel="noopener noreferrer" target="_blank"> + <a :href="item.homepage" class="cmp-contributor-item" rel="noopener noreferrer" target="_blank" referrerpolicy="no-referrer">
15-62
: Fix ESLint block spacing issues.Add blank lines between template, script, and style blocks to comply with ESLint rules.
</template> + <script setup> // ... script content ... </script> + <style scoped lang="less"> // ... style content ... </style>🧰 Tools
🪛 ESLint
[error] 16-25: Expected blank line before this block.
(vue/padding-line-between-blocks)
[error] 26-62: Expected blank line before this block.
(vue/padding-line-between-blocks)
examples/sites/src/views/components-doc/saas.vue (1)
12-54
: Consider adding error handling for data loading.The
loadData
function should include error handling for failed file fetches to provide a better user experience.Apply this diff to add error handling:
const loadData = ({ cmpId, lang }) => { const promiseArr = [ fetchDemosFile(`${staticPath.value}/${getWebdocPath(cmpId)}/webdoc/${cmpId}.${lang}.md`), import( /* @vite-ignore */ `${baseUrl}@demos/apis/${getWebdocPath(cmpId) === 'chart' ? cmpId : getWebdocPath(cmpId)}.js` ), import( /* @vite-ignore */ `${baseUrl}${staticPath.value}/${getWebdocPath(cmpId)}/webdoc/${cmpId}.js` ) ] return Promise.all(promiseArr).then(([mdString, apisJson, demosJson]) => { return { mdString, apisJson: apisJson.default, demosJson: demosJson.default } + }).catch(error => { + console.error('Failed to load documentation:', error) + return { + mdString: 'Failed to load documentation content.', + apisJson: {}, + demosJson: {} + } }) }examples/sites/src/views/components-doc/components/anchor.vue (2)
16-18
: Add type definitions and validation for props.The props lack proper type definitions and validation rules.
Apply this diff to add type definitions and validation:
-const props = defineProps({ anchorAffix: {}, currentJson: {}, activeTab: {}, langKey: {}, apiTypes: {} }) +const props = defineProps({ + anchorAffix: { + type: Boolean, + default: false + }, + currentJson: { + type: Object, + required: true + }, + activeTab: { + type: String, + required: true, + validator: (value: string) => ['demos', 'apis'].includes(value) + }, + langKey: { + type: String, + required: true + }, + apiTypes: { + type: Array, + required: true + } +})🧰 Tools
🪛 ESLint
[error] 16-16: 'ref' is defined but never used.
(unused-imports/no-unused-imports)
[error] 17-17: Expected 1 empty line after import statement not followed by another import.
(import/newline-after-import)
31-58
: Consider extracting API link generation logic.The API link generation logic in
apiAnchorLinks
is complex and could be extracted into a separate function for better maintainability.Apply this diff to extract the logic:
+const generateApiTypeLinks = (apiGroup, apiTypes) => { + const { name } = apiGroup + return apiTypes + .filter((i) => apiGroup[i]?.length) + .map((i) => ({ + key: i, + link: `#${name}--${i}`, + title: i + })) +} const apiAnchorLinks = computed(() => { if (!props.currentJson.apis?.length) { return [] } const apiAnchorLinks = [] props.currentJson.apis?.forEach((apiGroup) => { const { name } = apiGroup - const typeLinks = props.apiTypes - .filter((i) => apiGroup[i]?.length) - .map((i) => ({ - key: i, - link: `#${name}--${i}`, - title: i - })) + const typeLinks = generateApiTypeLinks(apiGroup, props.apiTypes) const linkItem = { key: name, link: `#cmp-${name}`, title: name, children: typeLinks } apiAnchorLinks.push(linkItem) }) return apiAnchorLinks })examples/sites/src/views/components-doc/components/api-docs.vue (1)
139-150
: Optimize row data retrieval performance.The nested loops in
getRowData
could be inefficient for large datasets.Consider using a Map for O(1) lookup:
+const rowDataMap = new Map() + +const buildRowDataMap = (tableData) => { + for (const comp of Object.values(tableData)) { + for (const apiGroup of Object.values(comp)) { + for (const row of apiGroup) { + if (row.type) { + rowDataMap.set(row.type, row) + } + } + } + } +} + const getRowData = (type) => { - const tableData = props.tableData - let rowData - for (const comp of Object.values(tableData)) { - for (const apiGroup of Object.values(comp)) { - rowData = apiGroup.find((i) => i.type === type) - if (rowData) { - return rowData - } - } - } + if (rowDataMap.size === 0) { + buildRowDataMap(props.tableData) + } + return rowDataMap.get(type) }examples/sites/src/views/components-doc/components/demo.vue (1)
250-264
: Extract playground URL construction logic.The URL construction in
openPlayground
contains complex logic that could be extracted.Apply this diff to extract the logic:
+const buildPlaygroundUrl = (demo, open = true) => { + const cmpId = router.currentRoute.value.params.cmpId + const tinyTheme = templateModeState.isSaas ? 'saas' : currentThemeKey.value.split('-')[0] + const openModeQuery = open ? '' : '&openMode=preview' + const apiMode = isMobileFirst.value ? 'Options' : apiModeState.apiMode + + return `${import.meta.env.VITE_PLAYGROUND_URL}?cmpId=${cmpId}&fileName=${ + demo.codeFiles[0] + }&apiMode=${apiMode}&mode=${templateModeState.mode}&theme=${tinyTheme}${openModeQuery}` +} + const openPlayground = (demo, open = true) => { - const cmpId = router.currentRoute.value.params.cmpId - const tinyTheme = templateModeState.isSaas ? 'saas' : currentThemeKey.value.split('-')[0] - const openModeQuery = open ? '' : '&openMode=preview' - // TODO: 目前mf只有Options写法,后续再放开compositon - const url = `${import.meta.env.VITE_PLAYGROUND_URL}?cmpId=${cmpId}&fileName=${demo.codeFiles[0]}&apiMode=${ - isMobileFirst.value ? 'Options' : apiModeState.apiMode - }&mode=${templateModeState.mode}&theme=${tinyTheme}${openModeQuery}` + const url = buildPlaygroundUrl(demo, open) if (open) { window.open(url) } else { showPreview(url) } }examples/sites/src/views/components-doc/components/float-settings.vue (1)
8-40
: Consider removing commented theme settings code.The theme settings section is marked as "temporarily hidden" but contains a significant amount of commented code. If this is truly temporary, consider:
- Moving this code to a separate component for future use
- Creating a ticket to track its reintegration
- Adding a TODO comment with the ticket reference instead of keeping the entire code block
examples/sites/playground/App.vue (1)
87-89
: Fix formatting issues.The current formatting of the imports object assignment doesn't comply with the project's style guide.
Apply this fix to match the project's formatting rules:
- imports[ - `@opentiny/vue-design-${tinyTheme}` - ] = `${cdnHost}/@opentiny/vue-design-${tinyTheme}${versionDelimiter}${version}/${fileDelimiter}index.js` + imports[`@opentiny/vue-design-${tinyTheme}`] = `${cdnHost}/@opentiny/vue-design-${tinyTheme}${versionDelimiter}${version}/${fileDelimiter}index.js`🧰 Tools
🪛 ESLint
[error] 87-88: Replace
⏎······
@opentiny/vue-design-${tinyTheme}with
@opentiny/vue-design-${tinyTheme}]·=
(prettier/prettier)
[error] 89-89: Replace
]·=
with·
(prettier/prettier)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (21)
examples/sites/mobile/App.vue
(2 hunks)examples/sites/playground/App.vue
(2 hunks)examples/sites/src/router.js
(1 hunks)examples/sites/src/tools/useTemplateMode.js
(1 hunks)examples/sites/src/views/components-doc/cmp-config.js
(1 hunks)examples/sites/src/views/components-doc/common.vue
(1 hunks)examples/sites/src/views/components-doc/components/anchor.vue
(1 hunks)examples/sites/src/views/components-doc/components/api-docs.vue
(1 hunks)examples/sites/src/views/components-doc/components/contributor.vue
(1 hunks)examples/sites/src/views/components-doc/components/demo.vue
(1 hunks)examples/sites/src/views/components-doc/components/float-settings.vue
(1 hunks)examples/sites/src/views/components-doc/components/header.vue
(1 hunks)examples/sites/src/views/components-doc/components/version-tip.vue
(1 hunks)examples/sites/src/views/components-doc/composition/useTasksFinish.ts
(1 hunks)examples/sites/src/views/components-doc/index.vue
(1 hunks)examples/sites/src/views/components-doc/mobile.vue
(1 hunks)examples/sites/src/views/components-doc/pc.vue
(1 hunks)examples/sites/src/views/components-doc/plus.vue
(1 hunks)examples/sites/src/views/components-doc/saas.vue
(1 hunks)examples/sites/src/views/layout/layout.vue
(1 hunks)packages/vue/package.json
(1 hunks)
✅ Files skipped from review due to trivial changes (3)
- examples/sites/src/tools/useTemplateMode.js
- packages/vue/package.json
- examples/sites/src/views/components-doc/components/version-tip.vue
🚧 Files skipped from review as they are similar to previous changes (3)
- examples/sites/src/views/layout/layout.vue
- examples/sites/src/router.js
- examples/sites/mobile/App.vue
🧰 Additional context used
🪛 ESLint
examples/sites/src/views/components-doc/components/header.vue
[error] 16-31: Expected blank line before this block.
(vue/padding-line-between-blocks)
[error] 20-20: Expected 1 empty line after import statement not followed by another import.
(import/newline-after-import)
[error] 32-68: Expected blank line before this block.
(vue/padding-line-between-blocks)
examples/sites/playground/App.vue
[error] 87-88: Replace ⏎······
@opentiny/vue-design-${tinyTheme}with
@opentiny/vue-design-${tinyTheme}]·=
(prettier/prettier)
[error] 89-89: Replace ]·=
with ·
(prettier/prettier)
examples/sites/src/views/components-doc/components/contributor.vue
[error] 16-25: Expected blank line before this block.
(vue/padding-line-between-blocks)
[error] 26-62: Expected blank line before this block.
(vue/padding-line-between-blocks)
examples/sites/src/views/components-doc/saas.vue
[error] 56-62: Expected blank line before this block.
(vue/padding-line-between-blocks)
examples/sites/src/views/components-doc/common.vue
[error] 90-90: 'fetchDemosFile' is defined but never used.
(unused-imports/no-unused-imports)
[error] 91-91: 'demoConfig' is defined but never used.
(unused-imports/no-unused-imports)
[error] 105-105: defineProps should be the first statement in <script setup>
(after any potential import statements or type definitions).
(vue/define-macros-order)
examples/sites/src/views/components-doc/components/anchor.vue
[error] 15-64: Expected blank line before this block.
(vue/padding-line-between-blocks)
[error] 16-16: 'ref' is defined but never used.
(unused-imports/no-unused-imports)
[error] 17-17: Expected 1 empty line after import statement not followed by another import.
(import/newline-after-import)
[error] 60-60: defineEmits should be the first statement in <script setup>
(after any potential import statements or type definitions).
(vue/define-macros-order)
[error] 65-116: Expected blank line before this block.
(vue/padding-line-between-blocks)
examples/sites/src/views/components-doc/components/api-docs.vue
[error] 94-165: Expected blank line before this block.
(vue/padding-line-between-blocks)
[error] 125-125: defineEmits should be the first statement in <script setup>
(after any potential import statements or type definitions).
(vue/define-macros-order)
[error] 166-219: Expected blank line before this block.
(vue/padding-line-between-blocks)
🔇 Additional comments (12)
examples/sites/src/views/components-doc/mobile.vue (5)
2-8
: Great use of the new ComponentDocs component.
It seamlessly integrates the “mobile” mode while passing in the right props and event handlers. No issues noted here.
48-49
: Local references look fine.
Defining both iframeUrl and iframeRef as refs is clear and correct.
58-63
: Validate demo.codeFiles before constructing the URL.
If demo.codeFiles is empty or undefined, the iframe URL may break. Include checks to avoid runtime errors.
78-81
: Spacing and margin.
The .mobile-view-container approach is clear, but ensure consistent spacing if reused across different breakpoints. Looks good overall.
83-89
: Positioning the iframe within phone-container.
The relative positioning and top/left offsets are straightforward. Verify it doesn’t conflict on smaller screens or alternate device sizes.examples/sites/src/views/components-doc/pc.vue (1)
1-3
: LGTM! Clean and focused template.The template is well-structured, passing appropriate props to ComponentDocs for PC documentation.
examples/sites/src/views/components-doc/cmp-config.js (1)
40-40
: LGTM! Clean export statement.The removal of faqMdConfig from exports aligns with the refactoring goals.
examples/sites/src/views/components-doc/plus.vue (1)
24-27
: LGTM! Good TypeScript documentation handling.The special case handling for TypeScript documentation types is well-implemented.
examples/sites/src/views/components-doc/saas.vue (1)
1-10
: LGTM! Clean and well-structured template.The template structure is clean and follows Vue best practices. The
ComponentDocs
component is used effectively with proper prop bindings and slot usage.examples/sites/src/views/components-doc/components/anchor.vue (1)
20-29
: LGTM! Clean and efficient demo links computation.The
demoAnchorLinks
computed property is well-implemented with proper null checks and mapping.examples/sites/src/views/components-doc/components/float-settings.vue (1)
131-137
: Verify the impact of removing API mode functionality.The changes remove the ability to switch API modes for non-PC and Saas environments. While this might be intentional, please ensure that:
- Users are properly notified of this change
- Documentation is updated to reflect this limitation
- No existing features depend on API mode switching in these environments
examples/sites/playground/App.vue (1)
8-8
: LGTM! Import path update aligns with refactoring goals.The change from
@/views/components/cmp-config
to@/views/components-doc/cmp-config
is consistent with the PR's objective to refactor the mobile site structure.
<template> | ||
<header class="docs-header"> | ||
<div class="docs-title-wrap"> | ||
<div class="markdown-body markdown-top-body" v-html="mdContent"></div> |
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.
Consider XSS protection for v-html content.
Using v-html with marked content could expose XSS vulnerabilities. Consider using DOMPurify or a similar sanitizer before rendering the markdown content.
- <div class="markdown-body markdown-top-body" v-html="mdContent"></div>
+ <div class="markdown-body markdown-top-body" v-html="sanitize(mdContent)"></div>
Add DOMPurify to your dependencies and use it in the script section:
import DOMPurify from 'dompurify'
// ...
const sanitize = (html) => DOMPurify.sanitize(html)
import hljs from 'highlight.js' | ||
import { marked } from 'marked' | ||
import VersionTip from './version-tip.vue' | ||
const props = defineProps({ currentJson: {}, mdString: {} }) |
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.
🛠️ Refactor suggestion
Add proper type definitions and validation for props.
The current props definition lacks type checking and validation. Consider adding proper type definitions and default values.
-const props = defineProps({ currentJson: {}, mdString: {} })
+const props = defineProps({
+ currentJson: {
+ type: Object,
+ required: true,
+ default: () => ({})
+ },
+ mdString: {
+ type: String,
+ required: true,
+ default: ''
+ }
+})
📝 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 props = defineProps({ currentJson: {}, mdString: {} }) | |
const props = defineProps({ | |
currentJson: { | |
type: Object, | |
required: true, | |
default: () => ({}) | |
}, | |
mdString: { | |
type: String, | |
required: true, | |
default: '' | |
} | |
}) |
const finishTask = () => { | ||
finishNum++ | ||
if (finishNum === tasksNum) { | ||
isAllFinish = true | ||
doResolve(true) | ||
} | ||
} |
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.
Add safety checks and prevent race conditions.
The function needs bounds checking to prevent over-counting and should handle state updates atomically.
- const finishTask = () => {
+ const finishTask = (): void => {
+ if (finishNum >= tasksNum) {
+ return;
+ }
finishNum++
if (finishNum === tasksNum) {
isAllFinish = true
- doResolve(true)
+ doResolve?.(true)
}
}
📝 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 finishTask = () => { | |
finishNum++ | |
if (finishNum === tasksNum) { | |
isAllFinish = true | |
doResolve(true) | |
} | |
} | |
const finishTask = (): void => { | |
if (finishNum >= tasksNum) { | |
return; | |
} | |
finishNum++ | |
if (finishNum === tasksNum) { | |
isAllFinish = true | |
doResolve?.(true) | |
} | |
} |
const useTasksFinish = (tasksNum) => { | ||
let finishNum = 0 | ||
let isAllFinish = false | ||
let doResolve |
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.
🛠️ Refactor suggestion
Add TypeScript type annotations and parameter validation.
The code would benefit from proper type annotations and input validation to improve type safety and prevent runtime errors.
-const useTasksFinish = (tasksNum) => {
+const useTasksFinish = (tasksNum: number) => {
+ if (tasksNum <= 0 || !Number.isInteger(tasksNum)) {
+ throw new Error('tasksNum must be a positive integer');
+ }
- let finishNum = 0
- let isAllFinish = false
- let doResolve
+ let finishNum: number = 0
+ let isAllFinish: boolean = false
+ let doResolve: (value: boolean) => void
📝 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 useTasksFinish = (tasksNum) => { | |
let finishNum = 0 | |
let isAllFinish = false | |
let doResolve | |
const useTasksFinish = (tasksNum: number) => { | |
if (tasksNum <= 0 || !Number.isInteger(tasksNum)) { | |
throw new Error('tasksNum must be a positive integer'); | |
} | |
let finishNum: number = 0 | |
let isAllFinish: boolean = false | |
let doResolve: (value: boolean) => void |
const waitTasks = async () => { | ||
if (isAllFinish) { | ||
return isAllFinish | ||
} | ||
return new Promise((resolve) => { | ||
doResolve = resolve | ||
}) | ||
} |
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.
🛠️ Refactor suggestion
Improve Promise handling and add type safety.
The Promise handling needs improvement to handle edge cases and prevent potential issues with multiple calls.
- const waitTasks = async () => {
+ const waitTasks = (): Promise<boolean> => {
if (isAllFinish) {
- return isAllFinish
+ return Promise.resolve(isAllFinish)
}
+ if (doResolve) {
+ return new Promise((resolve) => {
+ const originalResolve = doResolve
+ doResolve = (value) => {
+ originalResolve(value)
+ resolve(value)
+ }
+ })
+ }
return new Promise((resolve) => {
doResolve = resolve
})
}
📝 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 waitTasks = async () => { | |
if (isAllFinish) { | |
return isAllFinish | |
} | |
return new Promise((resolve) => { | |
doResolve = resolve | |
}) | |
} | |
const waitTasks = (): Promise<boolean> => { | |
if (isAllFinish) { | |
return Promise.resolve(isAllFinish) | |
} | |
if (doResolve) { | |
return new Promise((resolve) => { | |
const originalResolve = doResolve | |
doResolve = (value) => { | |
originalResolve(value) | |
resolve(value) | |
} | |
}) | |
} | |
return new Promise((resolve) => { | |
doResolve = resolve | |
}) | |
} |
name: 'CmpPageVue' | ||
}) | ||
|
||
const props = defineProps({ loadData: {}, appMode: {}, demoKey: {} }) |
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.
🛠️ Refactor suggestion
Move defineProps above defineOptions.
Vue requires defineProps to be the first statement in <script setup>. This resolves a lint issue and aligns with best practices.
<script setup lang="ts">
+const props = defineProps({ loadData: {}, appMode: {}, demoKey: {} })
defineOptions({
name: 'CmpPageVue'
})
-const props = defineProps({ loadData: {}, appMode: {}, demoKey: {} })
...
</script>
📝 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 props = defineProps({ loadData: {}, appMode: {}, demoKey: {} }) | |
<script setup lang="ts"> | |
const props = defineProps({ loadData: {}, appMode: {}, demoKey: {} }) | |
defineOptions({ | |
name: 'CmpPageVue' | |
}) | |
... | |
</script> |
🧰 Tools
🪛 ESLint
[error] 105-105: defineProps should be the first statement in <script setup>
(after any potential import statements or type definitions).
(vue/define-macros-order)
import Saas from './saas.vue' | ||
import Mobile from './mobile.vue' | ||
import Pc from './pc.vue' | ||
import Plus from './pc.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.
Import from the matching "plus.vue" file.
You’ve imported Plus from './pc.vue' but used it for the “plus” mode. This likely is an accidental mismatch.
-import Plus from './pc.vue'
+import Plus from './plus.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 Plus from './pc.vue' | |
import Plus from './plus.vue' |
></tiny-grid-column> | ||
<tiny-grid-column field="desc" :title="i18nByKey('desc')"> | ||
<template #default="data"> | ||
<span v-html="data.row.desc"></span> |
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.
Security: Sanitize HTML content before rendering.
Using v-html
with unsanitized content can lead to XSS vulnerabilities.
Consider using a sanitization library like DOMPurify:
+import DOMPurify from 'dompurify'
<template #default="data">
- <span v-html="data.row.desc"></span>
+ <span v-html="DOMPurify.sanitize(data.row.desc)"></span>
</template>
const apiTableRef = ref() | ||
const jumpToApi = (hash) => { | ||
const rowData = getRowData(hash) | ||
const row = document.getElementById(hash).closest('.tiny-grid-body__row') | ||
if (row) { | ||
apiTableRef.value.forEach((i) => { | ||
i.setCurrentRow(rowData) | ||
i.setRowExpansion(rowData, true) | ||
}) | ||
} | ||
} |
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.
🛠️ Refactor suggestion
Add error handling for DOM operations.
The jumpToApi
function assumes DOM elements exist without proper error handling.
Apply this diff to add error handling:
const jumpToApi = (hash) => {
const rowData = getRowData(hash)
- const row = document.getElementById(hash).closest('.tiny-grid-body__row')
- if (row) {
+ const element = document.getElementById(hash)
+ if (!element) {
+ console.warn(`Element with id '${hash}' not found`)
+ return
+ }
+ const row = element.closest('.tiny-grid-body__row')
+ if (row && rowData) {
apiTableRef.value.forEach((i) => {
i.setCurrentRow(rowData)
i.setRowExpansion(rowData, true)
})
+ } else {
+ console.warn('Row or row data not found')
}
}
📝 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 apiTableRef = ref() | |
const jumpToApi = (hash) => { | |
const rowData = getRowData(hash) | |
const row = document.getElementById(hash).closest('.tiny-grid-body__row') | |
if (row) { | |
apiTableRef.value.forEach((i) => { | |
i.setCurrentRow(rowData) | |
i.setRowExpansion(rowData, true) | |
}) | |
} | |
} | |
const apiTableRef = ref() | |
const jumpToApi = (hash) => { | |
const rowData = getRowData(hash) | |
const element = document.getElementById(hash) | |
if (!element) { | |
console.warn(`Element with id '${hash}' not found`) | |
return | |
} | |
const row = element.closest('.tiny-grid-body__row') | |
if (row && rowData) { | |
apiTableRef.value.forEach((i) => { | |
i.setCurrentRow(rowData) | |
i.setRowExpansion(rowData, true) | |
}) | |
} else { | |
console.warn('Row or row data not found') | |
} | |
} |
const copyCode = async (demo) => { | ||
if (demo.isOpen) { | ||
const idx = parseInt(state.tabValue.slice(3)) | ||
|
||
navigator.clipboard.writeText(demo.files[idx].code) | ||
} else { | ||
await getDemoCode(demo) | ||
|
||
navigator.clipboard.writeText(demo.files[0].code) | ||
} | ||
state.copyTip = i18nByKey('copyCodeOk') | ||
state.copyIcon = 'i-ti-check' | ||
} |
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.
🛠️ Refactor suggestion
Add error handling for clipboard operations.
The copyCode
function doesn't handle clipboard API failures.
Apply this diff to add error handling:
const copyCode = async (demo) => {
+ try {
if (demo.isOpen) {
const idx = parseInt(state.tabValue.slice(3))
- navigator.clipboard.writeText(demo.files[idx].code)
+ await navigator.clipboard.writeText(demo.files[idx].code)
} else {
await getDemoCode(demo)
- navigator.clipboard.writeText(demo.files[0].code)
+ await navigator.clipboard.writeText(demo.files[0].code)
}
state.copyTip = i18nByKey('copyCodeOk')
state.copyIcon = 'i-ti-check'
+ } catch (error) {
+ console.error('Failed to copy code:', error)
+ state.copyTip = i18nByKey('copyCodeFailed')
+ state.copyIcon = 'i-ti-close'
+ }
}
📝 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 copyCode = async (demo) => { | |
if (demo.isOpen) { | |
const idx = parseInt(state.tabValue.slice(3)) | |
navigator.clipboard.writeText(demo.files[idx].code) | |
} else { | |
await getDemoCode(demo) | |
navigator.clipboard.writeText(demo.files[0].code) | |
} | |
state.copyTip = i18nByKey('copyCodeOk') | |
state.copyIcon = 'i-ti-check' | |
} | |
const copyCode = async (demo) => { | |
try { | |
if (demo.isOpen) { | |
const idx = parseInt(state.tabValue.slice(3)) | |
await navigator.clipboard.writeText(demo.files[idx].code) | |
} else { | |
await getDemoCode(demo) | |
await navigator.clipboard.writeText(demo.files[0].code) | |
} | |
state.copyTip = i18nByKey('copyCodeOk') | |
state.copyIcon = 'i-ti-check' | |
} catch (error) { | |
console.error('Failed to copy code:', error) | |
state.copyTip = i18nByKey('copyCodeFailed') | |
state.copyIcon = 'i-ti-close' | |
} | |
} |
PR
重构官网套件 & 分离移动端api文档
PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
What is the current behavior?
Issue Number: N/A
What is the new behavior?
Does this PR introduce a breaking change?
Other information
Summary by CodeRabbit