Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ui/src/components/global/mk-dropdown/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ defineExpose({ handleOpen, handleClose })
v-if="!hideWhenEmpty || hasDropdownContent(slots.dropdown?.())"
class="mk-dropdown"
ref="dropdownRef"
:popper-options="popperOptions"
:persistent="persistent && hasOpened"
v-bind="$attrs"
@visible-change="handleVisibleChange"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Here are my observations on this diff:

1. popper-options is a deprecated prop in Element Plus

Depending on your Element Plus version, :popper-options may have been replaced by :popper-class / :teleported / the newer popover config API. In Element Plus ≥ 2.0, the dropdown/select components use popperOptions as a plain object passed via $attrs, but the actual accepted shape is { modifiers, strategy, ... }. Verify against your installed version's docs — otherwise the option will be silently ignored.

Also make sure not to name it in snake_case with a hyphen mixed into a top-level attribute if your component isn't configured to inherit attrs (see next point).

2. Interaction with v-bind="$attrs"

Because $attrs is spread after :popper-options, any user-provided popperOptions will override popperOptions from the component unless inheritAttrs: false and explicit merging is done. Consider using v-bind="mergeAttrs({ popperOptions })" or apply $attrs first so your default can act as a fallback:

v-bind="$attrs"
:popper-options="{ ...defaults, ...($attrs.popperOptions ?? {}) }"

3. Where is popperOptions defined?

The diff only shows the template binding — ensure popperOptions is declared in <script setup> (likely as a defineProps field or local ref). If it's a ref, the binding should be :popper-options="popperOptions" (works for refs in templates) but if it's expected to be reactive on mutations you need to reassign the ref, not mutate .value deeply.

4. Type safety

If the codebase uses TypeScript, type the prop explicitly:

const props = defineProps<{
  popperOptions?: PopperOptions // from '@popperjs/core' or Element Plus types
}>()

Otherwise any propagated from the parent leaks in silently.

5. Potential perf nit

popperOptions objects passed inline (e.g. :popper-options="{ placement: 'bottom' }") create a new reference each render, causing the dropdown to re-initialize its Popper instance. Prefer a stable object from computed or readonly.

6. Naming convention consistency

Your file uses kebab-case (hide-when-empty, visible-change) for other props; :popper-options matches that, good. Just double-check the component's declared prop name is popperOptions (not popper-options) so binding resolution works correctly in both setups.

7. Missing guards

Add a guard so an undefined popperOptions doesn't get passed through to Element Plus (which expects an object):

:popper-options="popperOptions || undefined"

Summary: the change itself is fine syntactically, but verify prop deprecation status, $attrs precedence, and reactivity of the options object to avoid subtle overrides or re-init churn.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { computed } from 'vue'
import type { CSSProperties } from 'vue'
import type { ApplicationDisplaySetting } from '@/api/types'
import defaultFloatIconUrl from '/MaxKB.gif?url'

defineOptions({ name: 'DisplaySettingPreview' })

Expand Down Expand Up @@ -79,6 +80,6 @@ const previewHeaderStyle = computed<CSSProperties>(() => ({
</div>
</div>
<!-- 浮窗图标 -->
<img :src="previewImages.float_icon || './MaxKB.gif'" alt="" class="size-8 shrink-0 object-contain" />
<img :src="previewImages.float_icon || defaultFloatIconUrl" alt="" class="size-8 shrink-0 object-contain" />
</div>
</template>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Here's my review of the diff:

Issues / potential concerns:

  1. Absolute path import (/MaxKB.gif?url) — Starts with /, which resolves from the project root (or public dir depending on bundler). This is fragile and inconsistent with the previous relative ./MaxKB.gif. It can break in sub-path deployments (e.g., apps served under /app/) and differs across Vite/Webpack. Prefer a relative import like import defaultFloatIconUrl from '@/assets/MaxKB.gif?url' or importing from src/assets.

  2. Importing a .gif into the bundle — Using ?url pulls the asset into the JS bundle pipeline, so the image gets fingerprinted/hashed. That's fine, but ensure your bundler is configured to handle GIFs. If this file is meant to be swapped out at deploy time, keeping it in public/ and referencing via a static path is usually better.

  3. No fallback for load failure — The :src still has no @error handler. If previewImages.float_icon is a bad URL, the <img> will be broken. Consider:

    <img :src="..." @error="($event.target as HTMLImageElement).src = defaultFloatIconUrl" ... />

    or compute a validated src.

  4. Missing explicit width/height or aspect ratio — size-8 sets both, but with object-contain the actual rendered content may look tiny for wide GIFs. Not critical, just a UX note.

  5. alt="" — Empty alt makes the image decorative, which is correct here since it's a preview icon. Fine.

  6. Type safety for previewImages.float_icon — Ensure it's typed as string | undefined; otherwise the || fallback logically works but TS won't complain — worth double-checking ApplicationDisplaySetting typing.

  7. Changelog / naming — defaultFloatIconUrl is descriptive; good. But consider centralizing all default asset URLs in one module so future defaults don't get scattered across components.

Optimization suggestions:

  • Move MaxKB.gif under src/assets/ and import relatively.
  • Add an @error fallback for robustness.
  • If the GIF never changes, cache-busting via hash is fine; if it's user-replaceable, use public/.
  • For performance, if the GIF is large, consider lazy loading (loading="lazy" decoding="async").

Overall the change is functionally sound, but the absolute import path is the main thing I'd fix before merging.

Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ function handleRefresh() {
placement="bottom-end"
persistent
:class="{ 'w-full': $slots.trigger }"
:popper-options="fitTriggerWidth ? { modifiers: [{ name: 'computeStyles', options: { roundOffsets: false } }] } : undefined"
:popper-style="dropdownStyle"
:popper-class="popperClass"
@visible-change="handleVisibleChange"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ function handleImportCreate(file: UploadFile) {
placement="bottom-end"
persistent
:class="{ 'w-full': $slots.trigger }"
:popper-options="fitTriggerWidth ? { modifiers: [{ name: 'computeStyles', options: { roundOffsets: false } }] } : undefined"
:popper-style="dropdownStyle"
:popper-class="popperClass"
@visible-change="handleVisibleChange"
Expand Down
1 change: 1 addition & 0 deletions ui/src/views/tool/components/ButtonCreateTool.vue
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ function handleRefresh() {
placement="bottom-end"
persistent
:class="{ 'w-full': $slots.trigger }"
:popper-options="fitTriggerWidth ? { modifiers: [{ name: 'computeStyles', options: { roundOffsets: false } }] } : undefined"
:popper-style="dropdownStyle"
:popper-class="popperClass"
@visible-change="handleVisibleChange"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const showContent = computed(() => isSuccess.value || props.showContentOnError)
<slot name="header" />
</template>
<!-- 内容区独立交互,避免链接、表单和嵌套详情点击时收起外层卡片。 -->
<div v-if="show" class="mt-4 cursor-auto space-y-2" @click.stop>
<div v-if="show" class="mt-4 cursor-auto space-y-2 wrap-anywhere" @click.stop>
<slot v-if="showContent" />
<div v-else class="mk-gray-card-sm">
<h6 class="mb-2">错误日志</h6>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Here's a review of the diff:

Change summary

  • Added wrap-anywhere to the content <div> that wraps the slot (v-if="showContent") and the error log fallback.

Potential issues

  1. wrap-anywhere is not a standard Tailwind utility (as of common versions). Tailwind provides break-words, break-all, break-normal, whitespace-*, and (newer) text-wrap/text-balance/text-pretty, but not wrap-anywhere. Unless this is a custom utility defined in your CSS (e.g., via a plugin or @layer utilities), it will be silently ignored — no error, but no effect. Worth verifying whether it exists in your setup.

  2. If intended to enable word-breaking in long strings, the standard equivalent is likely:

    • break-words → overflow-wrap: break-word
    • [overflow-wrap:anywhere] → arbitrary value if you specifically want anywhere (which also affects min-content sizing, unlike break-word)

    If you truly want the anywhere semantics, use the arbitrary variant: [overflow-wrap:anywhere]. Note this differs subtly from break-word for min-content calculations, which matters inside flex/grid layouts.

  3. Scope too broad. The class applies to the whole content container, including both the slotted content and the error-log card. This means every child element inherits overflow-wrap: anywhere behavior at the container level (though children can override). Long identifier-like strings (hashes, URLs, base64 blobs) will wrap, which is the goal — but non-text elements in the slot are unaffected, so this is generally safe. Just confirm the intent is "all descendants may break," not only the error log.

  4. Interaction with @click.stop. No functional interaction; the change is purely presentational. No concern there.

  5. Class ordering / lint. Some setups enforce Tailwind class ordering (e.g., prettier-plugin-tailwindcss). Placing wrap-anywhere after space-y-2 may be reordered by the formatter. Minor.

Suggestions

  • Confirm wrap-anywhere is defined in your design system/CSS. If not, prefer the standard break-words or [overflow-wrap:anywhere].
  • If the goal is specifically long unbroken strings in logs, consider scoping to the error block:
    <div v-else class="mk-gray-card-sm break-words">
    rather than the entire content wrapper, to avoid changing slotted-content rendering.
  • Add a brief comment explaining why wrapping is needed (e.g., <!-- allow long identifiers/URLs to wrap -->) since the rationale isn't obvious from the class name alone.

No correctness bugs introduced by the change itself, assuming the utility exists. The main risk is a silent no-op if wrap-anywhere isn't registered.

Expand Down
Loading