Skip to content
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

[wip] refactor: refactor mobile site #2808

Open
wants to merge 17 commits into
base: dev
Choose a base branch
from
Open

Conversation

gimmyhehe
Copy link
Member

@gimmyhehe gimmyhehe commented Jan 15, 2025

PR

PR Checklist

Please check if your PR fulfills the following requirements:

  • The commit message follows our Commit Message Guidelines
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

What is the current behavior?

Issue Number: N/A

What is the new behavior?

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Summary by CodeRabbit

  • Style Updates

    • Updated CSS overflow-y property from scroll to auto in multiple components, improving scrollbar behavior.
    • Introduced custom scrollbar styles in the mobile application for enhanced visual presentation.
  • New Features

    • Added mobile preview iframe to component view for better visualization.
    • Enhanced state management in the demo component for improved responsiveness and maintainability.
    • Introduced new environment variables for mobile application configuration.
    • Added a new build script for mobile pages to the project.

Copy link

coderabbitai bot commented Jan 15, 2025

Walkthrough

The pull request involves a consistent CSS styling modification across multiple Vue components in the mobile demo application. Specifically, the changes alter the overflow-y property from scroll to auto in various components like buttons, inputs, lists, and radio buttons. This adjustment allows for more dynamic scrollbar behavior, where scrollbars appear only when content exceeds the container's height. Additionally, a new mobile preview iframe feature was introduced in the components view, enhancing the overall functionality of the application.

Changes

File Path Change Summary
examples/sites/demos/mobile/app/button/*.vue Changed overflow-y from scroll to auto in .button-wrap class
examples/sites/demos/mobile/app/input/*.vue Changed overflow-y from scroll to auto in .demo-input class
examples/sites/demos/mobile/app/list/imformation-list.vue Changed overflow-y from scroll to auto in .demo class
examples/sites/demos/mobile/app/radio/*.vue Changed overflow-y from scroll to auto in .demo class
examples/sites/src/views/components/components.vue Added mobile preview iframe with dynamic iframeUrl and new methods for handling demo loading
examples/sites/src/views/components/demo.vue Refactored component structure for mobile responsiveness, updated state management, and simplified conditional rendering
examples/sites/vite-dosearch-plugin.js Deleted file containing a Vite plugin function for handling global search data
examples/sites/vite.config.ts Removed delStatic function, simplified path logic, and integrated new plugin configurations
examples/sites/vite.extend.ts Added new plugins for static resource deletion and docsearch functionality, along with configuration mapping
examples/sites/mobile/App.vue Updated template for mobile preview styling and changed styles to use Less
examples/sites/env/.env.mobilepages Added several environment variable declarations for mobile application configuration
examples/sites/package.json Added new script for building mobile pages

Poem

🐰 Scrollbars dancing, now they're shy
Appearing only when content flies high
From buttons to inputs, a fluid design
Mobile demos looking mighty fine
CodeRabbit's touch, a styling delight! 🎨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a46ec22 and e1ef5bb.

📒 Files selected for processing (1)
  • examples/sites/env/.env.mobilepages (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • examples/sites/env/.env.mobilepages
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: build
  • GitHub Check: PR E2E Test (pnpm test:e2e3)

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?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a 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 to auto improves the UI by showing scrollbars only when needed, using a fixed height: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 9a71be6 and a67e83e.

📒 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

Comment on lines 179 to 181
<div style="width: 300px; height: 500px">
<iframe width="100%" height="100%" :src="mobilePreviewUrl" frameborder="0"></iframe>
</div>
Copy link

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:

  1. Replace fixed dimensions with responsive units
  2. Add accessibility attributes
  3. 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`
Copy link

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

@gimmyhehe gimmyhehe changed the title [wip] fix: init mobile site refactor [wip] refactor: refactor mobile site Jan 15, 2025
Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between a67e83e and 7445257.

📒 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.

Comment on lines 425 to 433
height: 600px;
border: 0;
position: relative;
position: fixed;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYsAAAMFCAYAAACFxdoKAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDY3IDc5LjE1Nzc0NywgMjAxNS8wMy8zMC0yMzo0MDo0MiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjg3MTZDNTAwMTI1MzExRUE5Mzk1OTFBNjdDQ0JBNUQ5IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjg3MTZDNTAxMTI1MzExRUE5Mzk1OTFBNjdDQ0JBNUQ5Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6ODcxNkM0RkUxMjUzMTFFQTkzOTU5MUE2N0NDQkE1RDkiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6ODcxNkM0RkYxMjUzMTFFQTkzOTU5MUE2N0NDQkE1RDkiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7YMDcrAAAcl0lEQVR42uzde4ysd1nA8ZnZmd3TU+jpKbWU0qYVgaKCRS4aISBGU6EFIQGEGA2RiEQLJBIjmBBIjAmKt2ijEDD4h2hEJVDKRRCDUKjBmtJqFRou0vQGtZz2eK47V59nzvuu0z17ztmZfWd2zsznk7yZmd2Z3dnZmd93fu878079wIEDte1oNBqPON3v94eHg8Fg43v5tXq9Pjydx/N7o6fzeC759fJ7YSWO74nD8+L0s+J8r4rjV8VyaSx7Y2n2er1Gt9utNZvN4bILBrE8PMHlzi8OH64B01Q+1uqz/sUxZg1ifBqsrKx0Yjka49nB+PKDsdwZY9o3Yrk5xrBb4/BwfK0b5+nGeQY5Jo6OncMrf2JMHB5uNZ7m8fJ7efqCCy6Y2d/Z3KV/7P5YHh9/7OXxh78gDl8UN8IVcSPujeMn/bPj68NlF9WL67yTvxdYQDF21VutVgxl9bU4uRaH+Xi/IpZnnng+PegUTxhvieXWWG6O5Wux3BNL+2z5O2cdiyfH8pS48V4ZN/A1EYB92YKsZJR3o64njdT1unskMLdOMUbVi2UtxrrHxvLiHPdWV1cPHzp06GCMdzfE6Y/G978ey3diOTrXf+OUV0NlbXP10pMyEJ1O59Xnnnvu45rNZmt9fX142fJnlMcBFnQGMjzMtSTtdjtPd2IsPBhPlG/rdrufjHHyi7HcFmdZX7bVUDkVuzaW18Qf+MxYzo8/sJ4ziDIsAgEsi/IJdo6BRQRacfzCOP6TMdt4bnz/rjj92Th9U5wtl3sWfmYR5bwqSvmW+NI1Uc595WXyvGUsrFoCqG3MFopxMTd83x/Lh2O8/Eyc/lh8v7uIM4uLIwa/EH/E6yIYT8yZxOZZhEgAnPzEuwhBPQ4viZOvj7H06jj8nlhuiOWBRZpZPD2Wt8XXXxJLzKpWN2YRAGxzUB7ZThGHR+Pwg7G8N751S5zunc0zi0viSr88Dq+LP+TKfB9ERkIoAMZXrq4v1sLke81+MZYnx9ffFYe5TePQ2TizeHpc8XfE8RdneKxiApjajOOuCMbH4vA9cfKOHINnObNoTHi5fIfcM2L5k5WVlZfFbEIoAKYoxtrLY8ZxXSxvr51Y7T/TdypPEot61O2nIg7vj0Y8r3xDHQDTk7s8yvdoNBqNV8a4+7740k/PdGYz5mqoRoTimjh8Z1zpp2YoTvWuawCmo3h16ZfjSfvvx+E/1U68A3xi+/efeY9E48ws8rzXxpV8n1AA7J58IdHa2toPx1j8OzEm/+ws3uC8rVjkS3/jyuRLYt8bV/JioQDYPTn+djq5f8LaZTEm/0ocvqJ24j0ZuxuLXq/3I+12+/qo2MVZMKEA2D3l/vSKsfj7Ixhvi8MX5B5wdzMWV8XM4l0RistszAaYrxnGcCBvNPIzgN6Y4ZhpLMqXwUa5LovlN+P083IrvB3/AcyPHKvLj3eIMfrZcfir8eXHzHRmEVfi3IhD7ufpZeW0B4C5nWHsiWD8XIzVP5+fPlr1e99OGYvcoB2/7NeazeaafwXA/Cq3X+Sn9OUG71wbVM48phqL+MU/Gr/kzVGpC70zG+DsCEaxTfnKWHKD99OmPbPIQPxSrv/KN+LZoA1w1gXjeTGOvzYOL55aLOKX/VhE4uqcUeTbywE469SLzxbKPW5U8rlFm2NxQUTiJXF4mfdSAJzVs4zHxPLSOPp9lcciApEvkX1hflqT1U8AZ69if37PieW5tcn3ML5lLPbHD/+Z6MTjvUwWYCFcmK9srZ3Y6F1NLOIHPv+cc865utlsNmyrADj7tVqtfLNeObuoJBbnx4zipb1e7xKrnwAWQ7GLpoviMPcY/oPlPqU2L9uKRa7XiuWSc88991l5ut1u17y3AmAxYpG7M4/ZxVNiXL+8GO9PWrYVi+Kdfz8e9bnctgqAxVIEIXdf/tTcjVNOBjYv243F+fkxfZ1O57zcVmFWsXsm/ScCnEoxrj8mxvmXxcTgOeXnEY0u29GMH5IfnnFF/sBi3yJu3RkHIoI9KP5pJ934OYUszuPGAsZWjh0xjlwRY8wlk47x+c6+J8QPe3Q5cDHb/2MGotfr5TI4VeHz/5K7iBcNYAdaMbt41KQXbsYA9fxY9hmEdiUUw49GLGZ09a1iXXx04nCGkS+DEwxgwhlGvufiqgjGhXHywbFjEc9YMxYtA9Bs/285k+h2u/W83U83oyu/ly9pzuMzWi211Q837YSzVD4hzQ3dMXY8LsaOR00Ui/gBl3pvxcz/cWUoxhqAc7tSRiJnGFOe8QxGY5RxKl5eN6tgLMozF4FlLuQYv7a2lo/l1aNHj+7Z7stlHxGLGIDOcVPOVtzmjUlfTFC+Yi2DUfXs4sR29uGbeOqjG9vj9w3i9w2m+WHwI9dhGNNF+D/n7WU7IHPxrCXuh7lKOx5fuTunp8Rj7KtjxyIWsZjtrCKfttcnHUVGXz1V9UiUA/VWM548nXe0CEa/MclTkjFnNfG7GovyAF1dXfXCEebJRbE8NZbPxfLQWE9+Ymm5/WYnB8JxVz9tMbuo51LlIJQRO92qsSIYjf709l2fq+YWJhRlfItnc+74zMsTmNy10+Mmmim7+SgC1NjqfR5bzDwaVQ9+Gb383fmzF+12Lfa948NhmBeNYhlMckHY9rPfaT1LXuRn3xFDjzPm5qGeKxImisVOV4mwGJrNZj83ZJ9pBrCystK3Dn6iByjsqnxFVIz3+XaJJ8Rj+MKx9w1VDgLM37P4U4l/9iCXKp+Nx88bbnQ/08+MqNSncXvkdvNi3/sLM7CW74uBeRp34rH26LhvnjNuLJreETxbOSDuZKNnObDm7j8q/r/V4+cOdz+y+Y2C5eliIJ/KM4tyYM1nP4vw4Vvl7ZVxNXtnXmKR40beL+NxNvZ2NE97ZqwY5PNVPxMNIDmgZyymMQCV+5/Kl6+W77fI3xUD3vA9FtN91exGlOpTftPhzGJR/J/c6Zmr8SeflB0/fnzsNUpisQt1LwfgfKnqmANQPlOtTevNcTlQ58/P61juXqS4ruX7O6b+DLl8w+GC/K/NKFgYYrE7zzpzUM6nnDkdPON2giIw+S7q2rTfRV1cl3oxXd24yrN8huzZOMwfL+nbxWYUH3fYLwfI0UFyZB/0uTG7Vuxuw60GmFksYzCKZ++D8g1xI69YGB7PGUi+UknYAbFY5loU2wmKUAzK18PmPphy1xoN0wlALNgcjtEdPgkFMC8MRgCIBQBiAYBYACAWAIgFAGIBgFgAgFgAIBYAiAUAYgGAWAAgFgCIBQBiAQBiAYBYACAWAIgFAGIBgFgAIBYAiAUAiAUAYgGAWAAgFgDMp+Z2zjQYDGorKyu1vXv3Dk+vr6/XWq3W8GvtdrtWr9eHpzudTq3f79fW1tZqvV5veHp1dXX4/bxMs9kcLnmZlN/rdrvD8+Zl8rL5vfIyeTx/R5WXyeP595SXaTQaw+u2+TJbmeTnlOcrz1vebnnevEx5u+X387zl7ZZ/2+bbevNlNt/WVV1mUf+nO7lMLnnd/B/n8/+YP3/0cJn+r3nd8m87fvz48HK7GouNM8cVyiuZVzgPc8k/dDhFicP8el7x8niePw/Tdi5TRmmcy5Tn28ll8mubL7PlNOwUP6e8/FY/Z/Tnbee8eZ68E4x+bTuXGb2tN1+mqv/P2f4/nfT/l+cpBx7/x/n8P262TP/X8gl9np5mLOoHDhwYbHdmcd555w2vLADzI2ckR48eHc40TjeO59qhiNAXDh8+/PoYy/+r/N6+fftsswBg58QCALEAQCwAEAsAxAIAsQBALAAQCwAQCwDEAgCxAEAsABALAMQCALEAQCwAQCwAEAsAxAIAsQBALAAQCwDEAgCxAACxAEAsABALAMQCALEAQCwAEAsAxAIAxAIAsQBALAAQCwDEAgCxAEAsABALABALAMQCALEAQCwAEAsAxAIAsQAAsQBALAAQCwDEAgCxAEAsABALAMQCAMQCALEAQCwAEAsAxAIAsQBALAAQCwAQCwDEAgCxAEAsABALAMQCALEAQCwAQCwAEAsAxAIAsQBALAAQCwDEAoAF0Ynl2GAwGOtCTbcbwHLo9/u5PDZC8ew4OWg0GvfG8Y6ZBQBD9Xq91u12a+vr61dGIK6PaLy71+u9Or6118wCgA0Rh1qEYiWicVEsL4yZxTP27NmT66M+YGYBwMbsIldFZTTyeLgoTr9hO5cVC4AllRu52+32k8UCgDMGQywAqIRYACAWAIgFAGIBgFgAIBYAiAUAYgEAYgGAWAAgFgCIBQBiAYBYACAWAIgFAIgFAGIBgFgAIBYAiAUAYgGAWAAgFgAgFgCIBQBiAYBYACAWAIgFAGIBgFgAgFgAIBYAiAUAYgGAWAAgFgCIBQBiAQBiAYBYACAWAIgFAGIBgFgAIBYAIBYAiAUAYgGAWAAgFgCIBQBiAYBYAIBYACAWAIgFAGIBgFgAIBYAiAUAYgEAYgGAWAAgFgCIBQBiAYBYACAWAIgFAIgFAGIBgFgAIBYAiAUAYgGAWAAgFgAgFgCIBQBiAYBYACAWAIgFAGIBAGIBsNwG2zlT0+0EsGR1GAxqKysrtVardTyOf1AsAHhEJJrNZkZi0O1276vX6x+KL/+BWACwIeJQazQaByMan4qZxT/2+/1PxpfvFQsANmYVa2trGYs7jx079scRjtvjy0e2e3mxAFgSEYpcDXU8QvFgLEfytFgAsGUzyrE/V0uNcyEAEAsAxAIAsQBALAAQCwDEAgCxAACxAEAsABALAMQCALEAQCwAEAsAxAIAxAIAsQBALAAQCwDEAgCxAEAsABALABALAMQCgKo13QQAy2kwGIgFAFvqxrKeoRgnFlZDASzRTCKWfXH08n6/v3ecy4oFwBKo1+u1TqdTO378+NMiFH8U0XhzfPlp+a3tXN5qKIAl0ev1huN+xOKHut3uD6yurj43IvKW+Nq/m1kAsDG7iFAMZxgxs2jG4U/E4Zu2c1mxAFgioxu1Ixxr7Xb7WrEA4EzxWBMLACohFgCIBQBiAYBYACAWAIgFAGIBgFgAgFgAIBYAiAUAYgGAWAAgFgCIBQBiAQBiAYBYACAWAIgFAGIBgFgAIBYAiAUAiAUAYgGAWAAgFgCIBQBzZjAYbOt8TTcVwPIFol6v1xqN4XzhkFgAcKpI1Fqt1pE4/TdiAcCGDMXKysrweLfbfShO3xhH3yMWAGzMKprNZgbjWL/f/0LMKj4YX/5ULPeIBQD/P+BHLGK5rdPpvDXicVt8qb/dy3o1FMCSyNVQsXRiObSystIf57JmFgDLJScJreGRRmOsCwGAWAAgFgCIBQBiAYBYACAWAIgFAIgFAGIBgFgAIBYAiAUAYgGAWAAgFgAgFgCIBQBiAYBYACAWAIgFAGIBgFgAwCM13QQAy2ffvn1mFgBUSywAEAsAxAIAsQBALAAQCwDEAgCxAACxAEAsABALAMQCALEAQCwAEAsAxAIAxAIAsQBALAAQCwDEAgCxAEAsABALABALAMQCALEAQCwAEAsAxAIAsQBALABALAAQCwDEAgCxAEAsABALAMQCALEAALEAQCwAEAsAxAIAsQBALAAQCwAQCwDEAgCxAEAsABALAMQCALEAQCwAQCwAEAsAxAIAsQBALAAQCwDEAgCxAACxAEAsABALAMQCALEAQCwAEAsAxAIAxAIAsQBALAAQCwDEAgCxAEAsABALABALAMQCALEAQCwAEAsAxAIAsQAAsQBALAAQCwDEAgCxAEAsABALAMQCAMQCALEAQCwAEAsAxAIAsQBALAAQCwAQCwDEAgCxAEAsABALAMQCALEAQCwAQCwAEAsAxAIAsQBALAAQCwDEAgCxAACxAEAsABALAMQCALEAQCwAEAsAxAIAxAIAsQBALAAQCwDEAgCxAEAsAEAsABALAMQCALEAQCwAEAsAxAIAsQAAsQBALAAQCwDEAgCxAEAsABALAMQCAMQCALEAQCwAEAsAxAIAsQBALAAQCwAQCwDEAgCxAEAsABALAMQCALEAQCwAQCwAEAsAxAIAsQBALAAQCwDEAgDEAgCxAEAsABALAMQCALEAQCwAEAsAEAsAxAIAsQBALAAQCwDEAgCxAEAsAEAsABALAMQCALEAQCwAEAsAxAIAsQAAsQBALAAQCwDEAgCxAEAsABALAMQCAMQCALEAQCwAEAsAxAIAsQBALAAQCwAQCwDEAgCxAEAsABALAMQCALEAALEAQCwAEAsAxAIAsQBALAAQCwDEAgDEAgCxAEAsABALAMQCALEAQCwAEAsAEAsAxAIAsQBALAAQCwDEAgCxAEAsAEAsABALAMQCALEAQCwAEAsAxAIAsQAAsQBALAAQCwDEAgCxAEAsABALABALAMQCALEAQCwAEAsAxAIAsQBALABALAAQCwDEAgCxAEAsABALAMQCALEAALEAQCwAEAsAxAIAsQBALAAQCwDEAgDEAgCxAEAsABALAMQCALEAQCwAEAsAEAsAxAIAsQBALAAQCwDEAgCxAEAsAEAsABALAMQCALEAQCwAEAsAxAIAxAIAsQBALAAQCwDEAgCxAEAsABALABALAMQCALEAQCwAEAsAxAIAsQBALABALAAQCwDEAgCxAEAsABALAMQCALEAALEAQCwAEAsAxAIAsQDg7NXc7hkHg0Gt3W4v8201mOAy9R1cFhj/sbZ8f3i9Phyf+/3+7seivDJHjhwZHoqFWIBYzFcwcpmLmcWsrtAca+zSZQHO6gEQALEAALEAQCwAEAsAxAIAsQBALAAQCwDEAgDEAgCxAEAsABALAMQCALEAQCwAEAsAOFnTTcA86ff7g263uzCf3ZsfQ9xqtfxjEQuYdBAdhF6vl4Goj8RiuCzsA67ZrK2srCz034hYQCWKSNTjoFbEYjQiaaH+3pgpPeJ0/s2NRmO4gFjAKWYTGYdOp5PHh2FYtDhs9XePhiMjubq6OsgZRh7Ps7h3IBZQTChyW0Qxm6hvHkSXKZi5lMEsZhgDwUAs4EQoyg3XBsXacNtMPYMR4Ri0Wq3hLCNvm2KmAWLB8oUiZhIZCivot7pxBoN6sWpquE7OdgzmlXsmU59RdDod97MzzDLa7XZj84ZwEAsWXrFevm5Gsf3bK1dBWQ2FWLBcU4oTvJlgvNtsuNEbxIKlGfdsp5hMbvRut9tmGIgFy/Ms2YA3+W0HYsHCy3col++lYLJYFG9adGMgFizyWDecVYjFDoOblvGNi4gFSyDXuZcDHZMrd41idoFYsJCzioyFWUV1swuxQCxYvFLYqF2pcl9aVkVR5ROQfPPnJPepZt4ZcxcDHuQwX5rN5nAXIB6bVCEDUcxW65NMFBrlM0Jg7h7cGQsPTiqLRer3+4dizD82dizcGWE+5Zsac5cpVkNRhdyzcdyXujG7+GbE4sGJZhYALMcEoxj3x34GIhYwx6sNyh0MQkX6xTJRLOy5DOZztUHfamKqFE88Ho7l/kkum7E45iaE+dPr9Rres0LFHojljlgeGjsWzWbzmA1oVCG3xMb9KZ8RezZcgbwtPTapaEZRa7VatbW1tXvjPvXVST6RMV9tcY+bkgqDYYCrSPHqFTcElTwu19fXc2lHKI5P8jMaMdX9fFy4404J8/VM0IZtqnzikfenbrd7fxwenuS+FZ1oZCwOigVVPYnJKW4sRrodWF1d9UooKp1ZxGPywTi8Pd9jMVEsYvlm/IBD5bMZ2Cmx2PkD2yoopqDT7/cPTzprzVdb3B1Tk28V5XFzUtXsou5lnxPHYuDJG1U++SjuT9+Kx+V9k25XzEf0w1Gbv2u1Wv+br75wB6Wi2UXd/Wl8+UqyfNUKVKV4HH43xvmPxOPy5mLmP1Es8oKfizvpXaa9VB2MXJ3CmRW7Ih+Ue5p1i1CV/IyZWP4njt4R97MjE6+GKn7QfUeOHPm3/Lm5Yc2zQaqa/uazZGPftsJaK1bdWRdMpfer/PyKXq/31RjX7yrG++Ey9s8qDvMt4DfEs8D7PBNkCsHoC8ZpH9CDeJLWt/qJacQixvQH4vDj8Rj8z3J7xUTbLEYe1J8/duzYp6NC/VzHBRXeYRuCcdrbx4yCqeh0OjmruDmOfnHH99OR4w/F1OSjMcO414OaaQQjnoTYMd7JM4qB2TxTlO+tuDEO76wyFnnnvSkq9A8RDHdgKhf3qUYMjvVc5bLs78PI2XveFsWswp2DyhXbJcpZRX+nP2/z1PdAdCIrdLf3XDClZ9PD1S6tViujMVi2aBSRsJNApi7uX9+N5YY4+o1KHrtb/IJ/iSJ9Ol8RZdsFVSteaVcvNrwNXy1VDp4LHIhc3bTxd5azdq86ZJoPtbif/WWM55+I491K7sdbfC33G/LnvV7vqvhFz847dhx30zOVaJR7DshlUZ9pj+66ww4CmfJsYvhYijH7prifvT++9O3KnvSc4hd+KX7RH8YM4/q4o1/oX8AMwrGws4v8ACOBYBahKPYumxuzfzuW/6h0hnyaX3xjxOIJ3W737VGqNf8KZhUNYLIZRTyGHoox+93xpZuq3mtx4zQP3CPxy3Kd10fKKwPA3Dre6/X+OsbqD8RyvOonX43TPcOLX3h3LO+M0/mSWsEAmMNZRYox+paYXfxZHP3uNH7Pdl4fmx+W8RtxRe4uX8ECwHyEohiXb4+T18fylWn9rm29mSKuyL+urq6+MYLxbZ97ATAfirH4K51OJzdo/3O/35/ahr9tvfwkZhb5jtsbIxT9brf73mazeXF+fZI9FwKwc8VanruLDdp/P/UwjXHeLEPuufB1McO4Y9IP0ABgZ3K7ckTiyzEWvzXG5L+dxfbkcV/YnjuC+3hc0U5cyd+Nsl1lhgEwc/n5Q+/Yv3//J2b1CyeZGuQneX0mgvHaKNtNNnoDzEbucjw/BjvG3dfFyU/N8ndPuh4p9/9xayxvihnGRyIaXW+oApie/KS7ZrP5p/Hk/Lfi5G3FODwz9QMHDmyvKpu2T5SrnuIPuCRmGi+Po9fFea7MdWf2JQWww8G5eOVp8TGouZvxd0UoPhtfP5RjbD5Bv+CCC+Z+ZjHqvrjy18fy6rjyH4pJRjv/EKumACaLxMj4eTSWv4jl12PJ7cWHdmstTpV7brst6veGWL6U69PiD3pi/t35R2cZraYCOM0z95FXmMaY2Y3lv2Pc/L342g0RkAd2ewytYjXU6B9YVvGqmGG8Jb50TbPZ3FdeJs9brqKy6xCA2iN2z58fOxzj6P2xfDjGy8/E6Y/F97vlE+7yvOXpWa6Gmso+oeOPuD3+oOvi8NqIw2siHs+MP/L89fX1/IS04RQrt+qbbQDLJse9kd2JDwORO/6LANwVT7Jzm0TuMTY/j+Keebre0/wAgYdi+atYclfnT4povDKWV+zZs+fSmG2slhtoyhtPOIBFn0GkjES73c7ZQSfGwoMxFt4WT54/GYH4Yiz5Kqf1ebz+U1kNVW7BLws6cnpfHP/eOOsvx9dfETdazqFW8nsZj/J8AIuknEnEGNdfXV09fOjQoYMx1t0QsfhofPvrcfw7cXh0ZHXUluPpbq6GmnUsyqnX/lgeHxe5PL7/gvjei+I8V8T39sY0rL7VtG30RgeYJ+VYt/nLxdKJMe7h+P4tcfzWGOdujvN/Lb52T4x77dHxVCxOjsXojbsSx/fE4Xlx+llxvlfF8dyNyKWx7I2lGbOORkRk+LGbu/TRm/kPf3iCy51fHD7s4QRTVT7WZv5sMvf0mu9LjrGtE8vRGM8OxpcfjOXOGNO+EcvNMYbdGoeH42vdOE++iTk3ZD9i7Jz3WPyfAAMAmxj+h7FTWDsAAAAASUVORK5CYII=)
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%;
Copy link

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:

  1. Using CSS Grid or Flexbox for more flexible positioning
  2. Adding media queries for better responsiveness
  3. 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.

@James-9696 James-9696 force-pushed the refactor-mobile-site branch from 7445257 to a67e83e Compare January 15, 2025 09:43
Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between 794839b and d9702e8.

📒 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, and state.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:

  1. More semantic class naming with mobile-demo-container
  2. 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:

  1. Using <script setup> syntax with TypeScript
  2. Well-defined props with proper types
  3. Explicit emits declaration

119-139: Well-structured component and state setup!

Good practices observed:

  1. Component name defined for debugging
  2. Logically grouped state properties
  3. 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:

  1. Using CSS Grid or Flexbox for more flexible positioning
  2. Adding media queries for better responsiveness
  3. 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:

  1. Replace fixed dimensions with responsive units
  2. Add accessibility attributes
  3. 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.

Comment on lines 693 to 698
if (demoConfig.isMobile) {
// 点击目录列表更新iframe显示
const hashId = data.link.slice(1)
getIframeConetent(state.cmpId, hashId + '.vue')
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add input validation for hash IDs.

The direct use of hash IDs in URL construction without validation could lead to XSS vulnerabilities. Consider:

  1. Validating hash IDs against allowed patterns
  2. 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.

Suggested change
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')
}

Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between d9702e8 and c8a2bfb.

📒 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 access newViteConfig.tinyCustom.copyTarget. Verify that tinyCustom.copyTarget remains defined after modifyViteConfig is called.

Comment on lines +80 to +94
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'
}
]
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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'
}
]
}

Comment on lines +76 to +78
mobile(viteConfig) {
viteConfig.build.rollupOptions.input.mobile = path.resolve(__dirname, './mobile.html')
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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 } })
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between c8a2bfb and f64bd8e.

📒 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 issue

Add 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.

Comment on lines 164 to 171
<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>
Copy link

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:

  1. The iframe lacks important accessibility attributes (title, aria-label)
  2. 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;
}

Comment on lines 305 to 309
const getIframeConetent = (demoId, demoName) => {
const frameWindow = iframeRef.value.contentWindow
frameWindow.postMessage({ from: 'tiny-vue-site', component: state.cmpId, demo: demoName })
router.push(`#${demoId}`)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add origin validation and error handling for postMessage communication.

The iframe communication lacks security measures and error handling:

  1. Missing origin validation for postMessage
  2. 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.

Suggested change
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}`)
}

Comment on lines 1111 to 1124
.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/eHBhY2tldCBlbmQ9InIiPz7YMDcrAAAcl0lEQVR42uzde4ysd1nA8ZnZmd3TU+jpKbWU0qYVgaKCRS4aISBGU6EFIQGEGA2RiEQLJBIjmBBIjAmKt2ijEDD4h2hEJVDKRRCDUKjBmtJqFRou0vQGtZz2eK47V59nzvuu0z17ztmZfWd2zsznk7yZmd2Z3dnZmd93fu878079wIEDte1oNBqPON3v94eHg8Fg43v5tXq9Pjydx/N7o6fzeC759fJ7YSWO74nD8+L0s+J8r4rjV8VyaSx7Y2n2er1Gt9utNZvN4bILBrE8PMHlzi8OH64B01Q+1uqz/sUxZg1ifBqsrKx0Yjka49nB+PKDsdwZY9o3Yrk5xrBb4/BwfK0b5+nGeQY5Jo6OncMrf2JMHB5uNZ7m8fJ7efqCCy6Y2d/Z3KV/7P5YHh9/7OXxh78gDl8UN8IVcSPujeMn/bPj68NlF9WL67yTvxdYQDF21VutVgxl9bU4uRaH+Xi/IpZnnng+PegUTxhvieXWWG6O5Wux3BNL+2z5O2cdiyfH8pS48V4ZN/A1EYB92YKsZJR3o64njdT1unskMLdOMUbVi2UtxrrHxvLiHPdWV1cPHzp06GCMdzfE6Y/G978ey3diOTrXf+OUV0NlbXP10pMyEJ1O59Xnnnvu45rNZmt9fX142fJnlMcBFnQGMjzMtSTtdjtPd2IsPBhPlG/rdrufjHHyi7HcFmdZX7bVUDkVuzaW18Qf+MxYzo8/sJ4ziDIsAgEsi/IJdo6BRQRacfzCOP6TMdt4bnz/rjj92Th9U5wtl3sWfmYR5bwqSvmW+NI1Uc595WXyvGUsrFoCqG3MFopxMTd83x/Lh2O8/Eyc/lh8v7uIM4uLIwa/EH/E6yIYT8yZxOZZhEgAnPzEuwhBPQ4viZOvj7H06jj8nlhuiOWBRZpZPD2Wt8XXXxJLzKpWN2YRAGxzUB7ZThGHR+Pwg7G8N751S5zunc0zi0viSr88Dq+LP+TKfB9ERkIoAMZXrq4v1sLke81+MZYnx9ffFYe5TePQ2TizeHpc8XfE8RdneKxiApjajOOuCMbH4vA9cfKOHINnObNoTHi5fIfcM2L5k5WVlZfFbEIoAKYoxtrLY8ZxXSxvr51Y7T/TdypPEot61O2nIg7vj0Y8r3xDHQDTk7s8yvdoNBqNV8a4+7740k/PdGYz5mqoRoTimjh8Z1zpp2YoTvWuawCmo3h16ZfjSfvvx+E/1U68A3xi+/efeY9E48ws8rzXxpV8n1AA7J58IdHa2toPx1j8OzEm/+ws3uC8rVjkS3/jyuRLYt8bV/JioQDYPTn+djq5f8LaZTEm/0ocvqJ24j0ZuxuLXq/3I+12+/qo2MVZMKEA2D3l/vSKsfj7Ixhvi8MX5B5wdzMWV8XM4l0RistszAaYrxnGcCBvNPIzgN6Y4ZhpLMqXwUa5LovlN+P083IrvB3/AcyPHKvLj3eIMfrZcfir8eXHzHRmEVfi3IhD7ufpZeW0B4C5nWHsiWD8XIzVP5+fPlr1e99OGYvcoB2/7NeazeaafwXA/Cq3X+Sn9OUG71wbVM48phqL+MU/Gr/kzVGpC70zG+DsCEaxTfnKWHKD99OmPbPIQPxSrv/KN+LZoA1w1gXjeTGOvzYOL55aLOKX/VhE4uqcUeTbywE469SLzxbKPW5U8rlFm2NxQUTiJXF4mfdSAJzVs4zHxPLSOPp9lcciApEvkX1hflqT1U8AZ69if37PieW5tcn3ML5lLPbHD/+Z6MTjvUwWYCFcmK9srZ3Y6F1NLOIHPv+cc865utlsNmyrADj7tVqtfLNeObuoJBbnx4zipb1e7xKrnwAWQ7GLpoviMPcY/oPlPqU2L9uKRa7XiuWSc88991l5ut1u17y3AmAxYpG7M4/ZxVNiXL+8GO9PWrYVi+Kdfz8e9bnctgqAxVIEIXdf/tTcjVNOBjYv243F+fkxfZ1O57zcVmFWsXsm/ScCnEoxrj8mxvmXxcTgOeXnEY0u29GMH5IfnnFF/sBi3yJu3RkHIoI9KP5pJ934OYUszuPGAsZWjh0xjlwRY8wlk47x+c6+J8QPe3Q5cDHb/2MGotfr5TI4VeHz/5K7iBcNYAdaMbt41KQXbsYA9fxY9hmEdiUUw49GLGZ09a1iXXx04nCGkS+DEwxgwhlGvufiqgjGhXHywbFjEc9YMxYtA9Bs/285k+h2u/W83U83oyu/ly9pzuMzWi211Q837YSzVD4hzQ3dMXY8LsaOR00Ui/gBl3pvxcz/cWUoxhqAc7tSRiJnGFOe8QxGY5RxKl5eN6tgLMozF4FlLuQYv7a2lo/l1aNHj+7Z7stlHxGLGIDOcVPOVtzmjUlfTFC+Yi2DUfXs4sR29uGbeOqjG9vj9w3i9w2m+WHwI9dhGNNF+D/n7WU7IHPxrCXuh7lKOx5fuTunp8Rj7KtjxyIWsZjtrCKfttcnHUVGXz1V9UiUA/VWM548nXe0CEa/MclTkjFnNfG7GovyAF1dXfXCEebJRbE8NZbPxfLQWE9+Ymm5/WYnB8JxVz9tMbuo51LlIJQRO92qsSIYjf709l2fq+YWJhRlfItnc+74zMsTmNy10+Mmmim7+SgC1NjqfR5bzDwaVQ9+Gb383fmzF+12Lfa948NhmBeNYhlMckHY9rPfaT1LXuRn3xFDjzPm5qGeKxImisVOV4mwGJrNZj83ZJ9pBrCystK3Dn6iByjsqnxFVIz3+XaJJ8Rj+MKx9w1VDgLM37P4U4l/9iCXKp+Nx88bbnQ/08+MqNSncXvkdvNi3/sLM7CW74uBeRp34rH26LhvnjNuLJreETxbOSDuZKNnObDm7j8q/r/V4+cOdz+y+Y2C5eliIJ/KM4tyYM1nP4vw4Vvl7ZVxNXtnXmKR40beL+NxNvZ2NE97ZqwY5PNVPxMNIDmgZyymMQCV+5/Kl6+W77fI3xUD3vA9FtN91exGlOpTftPhzGJR/J/c6Zmr8SeflB0/fnzsNUpisQt1LwfgfKnqmANQPlOtTevNcTlQ58/P61juXqS4ruX7O6b+DLl8w+GC/K/NKFgYYrE7zzpzUM6nnDkdPON2giIw+S7q2rTfRV1cl3oxXd24yrN8huzZOMwfL+nbxWYUH3fYLwfI0UFyZB/0uTG7Vuxuw60GmFksYzCKZ++D8g1xI69YGB7PGUi+UknYAbFY5loU2wmKUAzK18PmPphy1xoN0wlALNgcjtEdPgkFMC8MRgCIBQBiAYBYACAWAIgFAGIBgFgAgFgAIBYAiAUAYgGAWAAgFgCIBQBiAQBiAYBYACAWAIgFAGIBgFgAIBYAiAUAiAUAYgGAWAAgFgDMp+Z2zjQYDGorKyu1vXv3Dk+vr6/XWq3W8GvtdrtWr9eHpzudTq3f79fW1tZqvV5veHp1dXX4/bxMs9kcLnmZlN/rdrvD8+Zl8rL5vfIyeTx/R5WXyeP595SXaTQaw+u2+TJbmeTnlOcrz1vebnnevEx5u+X387zl7ZZ/2+bbevNlNt/WVV1mUf+nO7lMLnnd/B/n8/+YP3/0cJn+r3nd8m87fvz48HK7GouNM8cVyiuZVzgPc8k/dDhFicP8el7x8niePw/Tdi5TRmmcy5Tn28ll8mubL7PlNOwUP6e8/FY/Z/Tnbee8eZ68E4x+bTuXGb2tN1+mqv/P2f4/nfT/l+cpBx7/x/n8P262TP/X8gl9np5mLOoHDhwYbHdmcd555w2vLADzI2ckR48eHc40TjeO59qhiNAXDh8+/PoYy/+r/N6+fftsswBg58QCALEAQCwAEAsAxAIAsQBALAAQCwAQCwDEAgCxAEAsABALAMQCALEAQCwAQCwAEAsAxAIAsQBALAAQCwDEAgCxAACxAEAsABALAMQCALEAQCwAEAsAxAIAxAIAsQBALAAQCwDEAgCxAEAsABALABALAMQCALEAQCwAEAsAxAIAsQAAsQBALAAQCwDEAgCxAEAsABALAMQCAMQCALEAQCwAEAsAxAIAsQBALAAQCwAQCwDEAgCxAEAsABALAMQCALEAQCwAQCwAEAsAxAIAsQBALAAQCwDEAoAF0Ynl2GAwGOtCTbcbwHLo9/u5PDZC8ew4OWg0GvfG8Y6ZBQBD9Xq91u12a+vr61dGIK6PaLy71+u9Or6118wCgA0Rh1qEYiWicVEsL4yZxTP27NmT66M+YGYBwMbsIldFZTTyeLgoTr9hO5cVC4AllRu52+32k8UCgDMGQywAqIRYACAWAIgFAGIBgFgAIBYAiAUAYgEAYgGAWAAgFgCIBQBiAYBYACAWAIgFAIgFAGIBgFgAIBYAiAUAYgGAWAAgFgAgFgCIBQBiAYBYACAWAIgFAGIBgFgAgFgAIBYAiAUAYgGAWAAgFgCIBQBiAQBiAYBYACAWAIgFAGIBgFgAIBYAIBYAiAUAYgGAWAAgFgCIBQBiAYBYAIBYACAWAIgFAGIBgFgAIBYAiAUAYgEAYgGAWAAgFgCIBQBiAYBYACAWAIgFAIgFAGIBgFgAIBYAiAUAYgGAWAAgFgAgFgCIBQBiAYBYACAWAIgFAGIBAGIBsNwG2zlT0+0EsGR1GAxqKysrtVardTyOf1AsAHhEJJrNZkZi0O1276vX6x+KL/+BWACwIeJQazQaByMan4qZxT/2+/1PxpfvFQsANmYVa2trGYs7jx079scRjtvjy0e2e3mxAFgSEYpcDXU8QvFgLEfytFgAsGUzyrE/V0uNcyEAEAsAxAIAsQBALAAQCwDEAgCxAACxAEAsABALAMQCALEAQCwAEAsAxAIAxAIAsQBALAAQCwDEAgCxAEAsABALABALAMQCgKo13QQAy2kwGIgFAFvqxrKeoRgnFlZDASzRTCKWfXH08n6/v3ecy4oFwBKo1+u1TqdTO378+NMiFH8U0XhzfPlp+a3tXN5qKIAl0ev1huN+xOKHut3uD6yurj43IvKW+Nq/m1kAsDG7iFAMZxgxs2jG4U/E4Zu2c1mxAFgioxu1Ixxr7Xb7WrEA4EzxWBMLACohFgCIBQBiAYBYACAWAIgFAGIBgFgAgFgAIBYAiAUAYgGAWAAgFgCIBQBiAQBiAYBYACAWAIgFAGIBgFgAIBYAiAUAiAUAYgGAWAAgFgCIBQBzZjAYbOt8TTcVwPIFol6v1xqN4XzhkFgAcKpI1Fqt1pE4/TdiAcCGDMXKysrweLfbfShO3xhH3yMWAGzMKprNZgbjWL/f/0LMKj4YX/5ULPeIBQD/P+BHLGK5rdPpvDXicVt8qb/dy3o1FMCSyNVQsXRiObSystIf57JmFgDLJScJreGRRmOsCwGAWAAgFgCIBQBiAYBYACAWAIgFAIgFAGIBgFgAIBYAiAUAYgGAWAAgFgAgFgCIBQBiAYBYACAWAIgFAGIBgFgAwCM13QQAy2ffvn1mFgBUSywAEAsAxAIAsQBALAAQCwDEAgCxAACxAEAsABALAMQCALEAQCwAEAsAxAIAxAIAsQBALAAQCwDEAgCxAEAsABALABALAMQCALEAQCwAEAsAxAIAsQBALABALAAQCwDEAgCxAEAsABALAMQCALEAALEAQCwAEAsAxAIAsQBALAAQCwAQCwDEAgCxAEAsABALAMQCALEAQCwAQCwAEAsAxAIAsQBALAAQCwDEAgCxAACxAEAsABALAMQCALEAQCwAEAsAxAIAxAIAsQBALAAQCwDEAgCxAEAsABALABALAMQCALEAQCwAEAsAxAIAsQAAsQBALAAQCwDEAgCxAEAsABALAMQCAMQCALEAQCwAEAsAxAIAsQBALAAQCwAQCwDEAgCxAEAsABALAMQCALEAQCwAQCwAEAsAxAIAsQBALAAQCwDEAgCxAACxAEAsABALAMQCALEAQCwAEAsAxAIAxAIAsQBALAAQCwDEAgCxAEAsAEAsABALAMQCALEAQCwAEAsAxAIAsQAAsQBALAAQCwDEAgCxAEAsABALAMQCAMQCALEAQCwAEAsAxAIAsQBALAAQCwAQCwDEAgCxAEAsABALAMQCALEAQCwAQCwAEAsAxAIAsQBALAAQCwDEAgDEAgCxAEAsABALAMQCALEAQCwAEAsAEAsAxAIAsQBALAAQCwDEAgCxAEAsAEAsABALAMQCALEAQCwAEAsAxAIAsQAAsQBALAAQCwDEAgCxAEAsABALAMQCAMQCALEAQCwAEAsAxAIAsQBALAAQCwAQCwDEAgCxAEAsABALAMQCALEAALEAQCwAEAsAxAIAsQBALAAQCwDEAgDEAgCxAEAsABALAMQCALEAQCwAEAsAEAsAxAIAsQBALAAQCwDEAgCxAEAsAEAsABALAMQCALEAQCwAEAsAxAIAsQAAsQBALAAQCwDEAgCxAEAsABALABALAMQCALEAQCwAEAsAxAIAsQBALABALAAQCwDEAgCxAEAsABALAMQCALEAALEAQCwAEAsAxAIAsQBALAAQCwDEAgDEAgCxAEAsABALAMQCALEAQCwAEAsAEAsAxAIAsQBALAAQCwDEAgCxAEAsAEAsABALAMQCALEAQCwAEAsAxAIAxAIAsQBALAAQCwDEAgCxAEAsABALABALAMQCALEAQCwAEAsAxAIAsQBALABALAAQCwDEAgCxAEAsABALAMQCALEAALEAQCwAEAsAxAIAsQDg7NXc7hkHg0Gt3W4v8201mOAy9R1cFhj/sbZ8f3i9Phyf+/3+7seivDJHjhwZHoqFWIBYzFcwcpmLmcWsrtAca+zSZQHO6gEQALEAALEAQCwAEAsAxAIAsQBALAAQCwDEAgDEAgCxAEAsABALAMQCALEAQCwAEAsAOFnTTcA86ff7g263uzCf3ZsfQ9xqtfxjEQuYdBAdhF6vl4Goj8RiuCzsA67ZrK2srCz034hYQCWKSNTjoFbEYjQiaaH+3pgpPeJ0/s2NRmO4gFjAKWYTGYdOp5PHh2FYtDhs9XePhiMjubq6OsgZRh7Ps7h3IBZQTChyW0Qxm6hvHkSXKZi5lMEsZhgDwUAs4EQoyg3XBsXacNtMPYMR4Ri0Wq3hLCNvm2KmAWLB8oUiZhIZCivot7pxBoN6sWpquE7OdgzmlXsmU59RdDod97MzzDLa7XZj84ZwEAsWXrFevm5Gsf3bK1dBWQ2FWLBcU4oTvJlgvNtsuNEbxIKlGfdsp5hMbvRut9tmGIgFy/Ms2YA3+W0HYsHCy3col++lYLJYFG9adGMgFizyWDecVYjFDoOblvGNi4gFSyDXuZcDHZMrd41idoFYsJCzioyFWUV1swuxQCxYvFLYqF2pcl9aVkVR5ROQfPPnJPepZt4ZcxcDHuQwX5rN5nAXIB6bVCEDUcxW65NMFBrlM0Jg7h7cGQsPTiqLRer3+4dizD82dizcGWE+5Zsac5cpVkNRhdyzcdyXujG7+GbE4sGJZhYALMcEoxj3x34GIhYwx6sNyh0MQkX6xTJRLOy5DOZztUHfamKqFE88Ho7l/kkum7E45iaE+dPr9Rres0LFHojljlgeGjsWzWbzmA1oVCG3xMb9KZ8RezZcgbwtPTapaEZRa7VatbW1tXvjPvXVST6RMV9tcY+bkgqDYYCrSPHqFTcElTwu19fXc2lHKI5P8jMaMdX9fFy4404J8/VM0IZtqnzikfenbrd7fxwenuS+FZ1oZCwOigVVPYnJKW4sRrodWF1d9UooKp1ZxGPywTi8Pd9jMVEsYvlm/IBD5bMZ2Cmx2PkD2yoopqDT7/cPTzprzVdb3B1Tk28V5XFzUtXsou5lnxPHYuDJG1U++SjuT9+Kx+V9k25XzEf0w1Gbv2u1Wv+br75wB6Wi2UXd/Wl8+UqyfNUKVKV4HH43xvmPxOPy5mLmP1Es8oKfizvpXaa9VB2MXJ3CmRW7Ih+Ue5p1i1CV/IyZWP4njt4R97MjE6+GKn7QfUeOHPm3/Lm5Yc2zQaqa/uazZGPftsJaK1bdWRdMpfer/PyKXq/31RjX7yrG++Ey9s8qDvMt4DfEs8D7PBNkCsHoC8ZpH9CDeJLWt/qJacQixvQH4vDj8Rj8z3J7xUTbLEYe1J8/duzYp6NC/VzHBRXeYRuCcdrbx4yCqeh0OjmruDmOfnHH99OR4w/F1OSjMcO414OaaQQjnoTYMd7JM4qB2TxTlO+tuDEO76wyFnnnvSkq9A8RDHdgKhf3qUYMjvVc5bLs78PI2XveFsWswp2DyhXbJcpZRX+nP2/z1PdAdCIrdLf3XDClZ9PD1S6tViujMVi2aBSRsJNApi7uX9+N5YY4+o1KHrtb/IJ/iSJ9Ol8RZdsFVSteaVcvNrwNXy1VDp4LHIhc3bTxd5azdq86ZJoPtbif/WWM55+I491K7sdbfC33G/LnvV7vqvhFz847dhx30zOVaJR7DshlUZ9pj+66ww4CmfJsYvhYijH7prifvT++9O3KnvSc4hd+KX7RH8YM4/q4o1/oX8AMwrGws4v8ACOBYBahKPYumxuzfzuW/6h0hnyaX3xjxOIJ3W737VGqNf8KZhUNYLIZRTyGHoox+93xpZuq3mtx4zQP3CPxy3Kd10fKKwPA3Dre6/X+OsbqD8RyvOonX43TPcOLX3h3LO+M0/mSWsEAmMNZRYox+paYXfxZHP3uNH7Pdl4fmx+W8RtxRe4uX8ECwHyEohiXb4+T18fylWn9rm29mSKuyL+urq6+MYLxbZ97ATAfirH4K51OJzdo/3O/35/ahr9tvfwkZhb5jtsbIxT9brf73mazeXF+fZI9FwKwc8VanruLDdp/P/UwjXHeLEPuufB1McO4Y9IP0ABgZ3K7ckTiyzEWvzXG5L+dxfbkcV/YnjuC+3hc0U5cyd+Nsl1lhgEwc/n5Q+/Yv3//J2b1CyeZGuQneX0mgvHaKNtNNnoDzEbucjw/BjvG3dfFyU/N8ndPuh4p9/9xayxvihnGRyIaXW+oApie/KS7ZrP5p/Hk/Lfi5G3FODwz9QMHDmyvKpu2T5SrnuIPuCRmGi+Po9fFea7MdWf2JQWww8G5eOVp8TGouZvxd0UoPhtfP5RjbD5Bv+CCC+Z+ZjHqvrjy18fy6rjyH4pJRjv/EKumACaLxMj4eTSWv4jl12PJ7cWHdmstTpV7brst6veGWL6U69PiD3pi/t35R2cZraYCOM0z95FXmMaY2Y3lv2Pc/L342g0RkAd2ewytYjXU6B9YVvGqmGG8Jb50TbPZ3FdeJs9brqKy6xCA2iN2z58fOxzj6P2xfDjGy8/E6Y/F97vlE+7yvOXpWa6Gmso+oeOPuD3+oOvi8NqIw2siHs+MP/L89fX1/IS04RQrt+qbbQDLJse9kd2JDwORO/6LANwVT7Jzm0TuMTY/j+Keebre0/wAgYdi+atYclfnT4povDKWV+zZs+fSmG2slhtoyhtPOIBFn0GkjES73c7ZQSfGwoMxFt4WT54/GYH4Yiz5Kqf1ebz+U1kNVW7BLws6cnpfHP/eOOsvx9dfETdazqFW8nsZj/J8AIuknEnEGNdfXV09fOjQoYMx1t0QsfhofPvrcfw7cXh0ZHXUluPpbq6GmnUsyqnX/lgeHxe5PL7/gvjei+I8V8T39sY0rL7VtG30RgeYJ+VYt/nLxdKJMe7h+P4tcfzWGOdujvN/Lb52T4x77dHxVCxOjsXojbsSx/fE4Xlx+llxvlfF8dyNyKWx7I2lGbOORkRk+LGbu/TRm/kPf3iCy51fHD7s4QRTVT7WZv5sMvf0mu9LjrGtE8vRGM8OxpcfjOXOGNO+EcvNMYbdGoeH42vdOE++iTk3ZD9i7Jz3WPyfAAMAmxj+h7FTWDsAAAAASUVORK5CYII=)
no-repeat center top;
background-size: contain;
z-index: 1;
}
Copy link

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.

Suggested change
.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;
}

Copy link

@coderabbitai coderabbitai bot left a 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:

  1. No loading indicator while demo loads
  2. No error handling if demo fails to load
  3. 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 suggestion

Enhance the mobile preview container for better accessibility and responsiveness.

The mobile preview container needs improvements:

  1. The iframe lacks important accessibility attributes (title, aria-label)
  2. 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 issue

Add 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 suggestion

Improve 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:

  1. Moving these CSS variables to a dedicated custom properties file
  2. 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:

  1. Adding a loading state while the iframe loads
  2. 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

📥 Commits

Reviewing files that changed from the base of the PR and between f64bd8e and ef2c059.

📒 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:

  1. Missing origin validation for postMessage
  2. 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}`)
 }

Comment on lines 587 to 594
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`
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add input validation and error handling for iframe URL construction.

The iframe URL construction lacks validation and error handling:

  1. No validation of component and demo parameters
  2. 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.

Suggested change
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)
}

Copy link

@coderabbitai coderabbitai bot left a 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:

  1. Using responsive units
  2. Adding accessibility attributes
  3. 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:

  1. Add media queries for better mobile responsiveness
  2. Use CSS Grid or CSS variables for more flexible layouts
  3. 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

📥 Commits

Reviewing files that changed from the base of the PR and between ef2c059 and 5cea622.

📒 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 issue

Add 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 issue

Add security measures and accessibility attributes to iframe.

The current iframe implementation needs improvements:

  1. Add sandbox attribute for security
  2. Add title and aria-label for accessibility
  3. 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 issue

Add validation and error handling to iframe URL construction.

The current URL construction needs improvements:

  1. Validate environment variables
  2. Sanitize URL parameters
  3. 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.

Comment on lines 312 to 339
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)
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add input validation and error handling to demo and hash handling.

The current implementation needs improvements:

  1. Validate hash IDs
  2. Handle edge cases
  3. 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.

Suggested change
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)
}
}

Copy link

@coderabbitai coderabbitai bot left a 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:

  1. Moving it to a separate image file
  2. 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:

  1. Documentation rendering
  2. Demo management
  3. Mobile preview
  4. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 5cea622 and 392e598.

📒 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:

  1. Add accessibility attributes to iframe (title, aria-label)
  2. 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:

  1. Missing origin validation
  2. 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:

  1. Validate component and demo parameters
  2. Encode parameters before URL construction

Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between a765414 and 19d78fa.

📒 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 of node:fs is a good choice as it provides additional useful filesystem operations.

Comment on lines +81 to +96
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'
}
]
}
Copy link

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.

Comment on lines +30 to +50
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
}
}
}
}
}
Copy link

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:

  1. Add version check for @docsearch to ensure compatibility
  2. Use AST transformation instead of string replacement
  3. Add validation for the transformed code
  4. 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.

Suggested change
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
}
}
}
}
}

Comment on lines +100 to +108
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
}
Copy link

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.

Suggested change
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
}
}

Comment on lines +7 to +27
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)
})
}
}
}
Copy link

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.

Comment on lines +54 to +74
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
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants