Skip to content

Feat/resources: Add Resource manager plugin & source select configurator#1632

Merged
hexqi merged 27 commits intoopentiny:developfrom
betterdancing:feat/resources
Sep 18, 2025
Merged

Feat/resources: Add Resource manager plugin & source select configurator#1632
hexqi merged 27 commits intoopentiny:developfrom
betterdancing:feat/resources

Conversation

@betterdancing
Copy link
Copy Markdown
Contributor

@betterdancing betterdancing commented Sep 16, 2025

English | 简体中文

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)
  • Built its own designer, fully self-validated

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:

Background and solution

作为一个低代码平台缺少了资源管理功能,无法管理项目中常用的静态资源

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

  • New Features

    • Resource Management plugin: browse/manage resource groups, add resources (upload/URL), copy links, delete (single/batch), edit group settings, open resource panels.
    • Resource selector configurator: popover selector with categories, search, type filter, thumbnails, preview, and direct selection into fields.
  • Enhancements

    • Img component source field: label changed to “资源选择”, selector widget enabled, label positioned on top.
  • Chores

    • Plugin integrated into registry, UI layout, exports, dev aliases, packaging and build configs.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Sep 16, 2025

Walkthrough

Adds a new Resource plugin package and UI, integrates it into engine exports/registry/layout, provides HTTP helpers, introduces and exports a SourceSelectConfigurator component, and updates Img.src schemas to use the new configurator and labelPosition metadata.

Changes

Cohort / File(s) Summary
Configurator: Source selector
packages/configurator/src/index.ts, packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue, packages/configurator/src/source-select-configurator/http.ts
Add and export SourceSelectConfigurator. New Vue component implements a Tiny popover resource selector (categories, search, thumbnail radio selection), emits update:modelValue; new HTTP helpers query material-center resource groups/resources.
Schemas updated to use source selector
packages/canvas/render/src/builtin/builtin.json, designer-demo/public/mock/bundle.json
Img src property label Chinese text changed to "资源选择", widget changed from InputConfiguratorSourceSelectConfigurator, and labelPosition: "top" added.
Resource plugin package
packages/plugins/resource/*
packages/plugins/resource/index.ts, packages/plugins/resource/meta.js, packages/plugins/resource/package.json, packages/plugins/resource/vite.config.ts, packages/plugins/resource/src/Main.vue, packages/plugins/resource/src/ResourceList.vue, packages/plugins/resource/src/ResourceSetting.vue, packages/plugins/resource/src/js/http.ts, packages/plugins/resource/src/styles/vars.less
New plugin package @opentiny/tiny-engine-plugin-resource: metadata, build config, styles, plugin entry, Main panel, ResourceList (CRUD, upload, batch ops, clipboard, UI), ResourceSetting (group create/update), and HTTP API wrappers for resource and group endpoints.
Engine integration & exports
packages/design-core/re-export.js, packages/design-core/registry.js, packages/register/src/constants.ts, packages/layout/src/defaultLayout.js, packages/design-core/package.json
Add META_APP.Resource constant, re-export Resource from design-core, append plugin to engine registry, include it in layout left-top plugins, and add workspace dependency @opentiny/tiny-engine-plugin-resource in design-core dependencies.
Dev/build wiring
packages/build/vite-config/src/vite-plugins/devAliasPlugin.js
Add development alias mapping @opentiny/tiny-engine-plugin-resource → local plugin source path (affects dev serve alias resolution only).
HTTP helpers for configurator
packages/configurator/src/source-select-configurator/http.ts
New helper functions: fetchResourceListByGroupId(resourceGroupId) and fetchResourceGroupByAppId(appId?) calling material-center endpoints.
Plugin HTTP wrappers
packages/plugins/resource/src/js/http.ts
New HTTP wrapper functions for resources and groups: fetch, create, batchCreate, delete, update; augments payloads with appId/platformId where required.
Minor formatting
packages/configurator/src/model-common/constants.js
Formatting edits (trailing commas/whitespace). No behavior changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant ImgPanel as Img Property Panel
  participant SSC as SourceSelectConfigurator
  participant HTTP as Configurator HTTP
  participant MetaAPI as getMetaApi(Http)

  User->>ImgPanel: Open src configurator
  ImgPanel->>SSC: mount/open (v-model)
  SSC->>HTTP: fetchResourceGroupByAppId()
  HTTP->>MetaAPI: GET /material-center/api/resource-group/{appId}
  MetaAPI-->>HTTP: Groups[]
  HTTP-->>SSC: Groups[]
  SSC->>SSC: init categories/resources, show popover
  User->>SSC: filter / choose resource
  SSC-->>ImgPanel: emit update:modelValue(resourceUrl)
  ImgPanel->>ImgPanel: bind src → resourceUrl
Loading
sequenceDiagram
  autonumber
  actor User
  participant Layout as App Layout
  participant ResourceMain as Resource Plugin (Main.vue)
  participant Setting as ResourceSetting.vue
  participant List as ResourceList.vue
  participant API as plugins/resource/src/js/http.ts
  participant Meta as getMetaApi(Http)

  User->>Layout: Open Resource plugin
  Layout->>ResourceMain: mount
  ResourceMain->>API: fetchResourceGroupByAppId()
  API->>Meta: GET /resource-group/{appId}
  Meta-->>API: GroupList
  API-->>ResourceMain: GroupList
  User->>ResourceMain: Open group settings
  ResourceMain->>Setting: openResourceSettingPanel(data)
  Setting->>API: createResourceGroup()/updateResourceGroup()
  API->>Meta: POST/PUT /resource-group
  Meta-->>API: OK
  Setting-->>ResourceMain: emit refreshCategory
  User->>ResourceMain: Open group resources
  ResourceMain->>List: openResourceListPanel(group)
  List->>API: fetchResourceListByGroupId(id)
  API->>Meta: GET /resource/find/{id}
  Meta-->>API: Resources
  API-->>List: Resources
  User->>List: Add / Delete / Batch actions
  List->>API: batchCreateResource()/deleteResource()/updateResourceGroup()
  API->>Meta: POST/DELETE/PUT endpoints
  Meta-->>API: OK
  API-->>List: Refresh state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

洞里我敲键又跳舞,面板开来分组铺。
缩略图里寻资源,标签向上选定路。
兔子轻点,列表齐步鼓 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Feat/resources: Add Resource manager plugin & source select configurator" is concise and accurately summarizes the PR's primary change—introducing a resource management plugin and a SourceSelectConfigurator—matching the file-level changes and objectives in the PR summary, so it clearly communicates the main intent to reviewers.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added the enhancement New feature or request label Sep 16, 2025
Copy link
Copy Markdown
Contributor

@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: 6

🧹 Nitpick comments (23)
packages/configurator/src/source-select-configurator/http.ts (2)

16-16: Avoid duplicating base URLs between packages.

baseUrl is duplicated here and in the resource plugin. Extract a shared constant (e.g., in meta-register or a small shared util) to prevent drift.


23-26: Make appId optional and harden fallback.

Treat appId as optional and guard GlobalService access to avoid runtime errors when services aren’t ready.

-export const fetchResourceGroupByAppId = (appId: number) =>
-  getMetaApi(META_SERVICE.Http).get(
-    `${baseUrl}/resource-group/${appId || getMetaApi(META_SERVICE.GlobalService).getBaseInfo().id}`
-  )
+export const fetchResourceGroupByAppId = (appId?: number) =>
+  getMetaApi(META_SERVICE.Http).get(
+    `${baseUrl}/resource-group/${
+      appId ?? getMetaApi(META_SERVICE.GlobalService)?.getBaseInfo?.().id
+    }`
+  )
packages/plugins/resource/src/js/http.js (2)

34-42: Guard batch payload against non-array input.

params.map will throw if params isn’t an array. Normalize to array.

-    params.map((item) => ({
+    (Array.isArray(params) ? params : [params]).map((item) => ({
       appId: getMetaApi(META_SERVICE.GlobalService).getBaseInfo().id,
       platformId: getMergeMeta('engine.config')?.platformId,
       ...item
     }))

45-46: DELETE via GET — confirm backend contract.

Using GET for deletions can be unsafe (caching, prefetchers). If the backend requires it, add client-side safeguards (no-cache headers) and confirm proxies/CDN won’t cache these routes.

Also applies to: 76-77

packages/plugins/resource/index.ts (1)

13-20: Default export shape is fine; optionally narrow the type.

To catch regressions at compile time and aid tooling, consider asserting const or satisfying a shared plugin type if available.

Apply within this block:

-export default {
-  ...metaData,
-  entry
-}
+export default {
+  ...metaData,
+  entry
+} as const

If there’s a common type (e.g., EnginePlugin), prefer:

export default ({ ...metaData, entry }) satisfies EnginePlugin
packages/plugins/resource/vite.config.ts (1)

23-24: Remove no-op resolve block.

resolve: {} has no effect and can be dropped.

-  resolve: {},
packages/plugins/resource/package.json (2)

10-12: Consider declaring Node engine due to Vite 5.

Vite 5 requires Node 18+. Declare engines to prevent accidental installs on unsupported Node versions.

   "type": "module",
   "main": "dist/index.js",
   "module": "dist/index.js",
+  "engines": {
+    "node": ">=18.0.0"
+  },

13-15: Preserve CSS side effects in consumers.

To reduce the risk of CSS being tree-shaken, explicitly mark side effects.

   "files": [
     "dist"
   ],
+  "sideEffects": [
+    "**/*.css"
+  ],
packages/plugins/resource/src/Main.vue (5)

35-36: Avoid optional chaining on ref in template.

resourceList is a ref to an array; template unwrapping makes resourceList.length valid. resourceList?.length is unnecessary and can confuse type tooling.

-        <search-empty :isShow="!resourceList?.length" />
+        <search-empty :isShow="!resourceList.length" />

110-115: Notify with a string message.

Passing the Error object directly can render as [object Object]. Use error.message || String(error).

-          useNotify({
-            type: 'error',
-            message: error
-          })
+          useNotify({
+            type: 'error',
+            message: error?.message || String(error)
+          })

122-124: updateCategory appears unused.

It’s not referenced in the template or provided via context. Remove it or wire it where intended.

-    const updateCategory = (data) => {
-      resourceList.value = data
-    }

If consumers need it, add to provided panelState:

-    const panelState = reactive({
-      emitEvent: emit
-    })
+    const panelState = reactive({
+      emitEvent: emit,
+      updateCategory: (data) => (resourceList.value = data)
+    })

21-35: Typo in class name: resouce-listresource-list.

Fix both template and style for consistency/searchability.

-        <div class="resouce-list">
+        <div class="resource-list">
...
-.resouce-list {
+.resource-list {

Also applies to: 150-181


22-33: Clickable rows should be keyboard-accessible.

Consider role="button" and tabindex="0" with key handlers, or switch to a button element for a11y.

-          <div
+          <div
             v-for="item in resourceList"
             :key="item.id"
-            :class="['resource-item', { 'active-item': item.active }]"
-            @click="openResourceList(item)"
+            :class="['resource-item', { 'active-item': item.active }]"
+            role="button"
+            tabindex="0"
+            @click="openResourceList(item)"
+            @keydown.enter.space="openResourceList(item)"
           >
packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue (5)

154-157: NPE risk on categoryChange when group not found; add null checks and reuse a shared filter.

-const categoryChange = () => {
-  sourceOriginList.value = categoryData.value.find((item) => item.id === sourceCategory.value).resources
-  sourceList.value = sourceOriginList.value.filter((item) => item.name.includes(searchWords.value))
-}
+const filterSourceList = () => {
+  const term = (searchWords.value || '').trim()
+  const type = sourceType.value
+  sourceList.value = (sourceOriginList.value || []).filter((it) => {
+    const byName = (it?.name || '').includes(term)
+    const byType = type === 'all' ? true : it?.category === 'image'
+    return byName && byType
+  })
+}
+
+const categoryChange = () => {
+  const group = categoryData.value.find((it) => it.id === sourceCategory.value)
+  sourceOriginList.value = group?.resources || []
+  filterSourceList()
+}

173-178: Wire the “图片/全部” filter and make search null-safe.

-const sourceTypeChange = (type) => {
-  sourceType.value = type
-}
+const sourceTypeChange = (type) => {
+  sourceType.value = type
+  filterSourceList()
+}
@@
 watch(
   () => searchWords.value,
   () => {
-    sourceList.value = sourceOriginList.value.filter((item) => item.name.includes(searchWords.value))
+    filterSourceList()
   }
 )

Also applies to: 163-165


147-151: Notification should use error message string, not raw object.

-          useNotify({
-            type: 'error',
-            message: error
-          })
+          useNotify({ type: 'error', message: error?.message || String(error) })

65-73: Emit on manual URL edits (or remove the input).

The input bound to imgSrc doesn’t sync v-model upstream unless “确认” is used. If manual input should update the model, watch and emit.

-<tiny-input v-model="imgSrc"></tiny-input>
+<tiny-input v-model="imgSrc"></tiny-input>
+watch(
+  () => imgSrc.value,
+  (val) => emit('update:modelValue', val)
+)

Also applies to: 50-55


49-49: Add alt text for images for accessibility.

-<img :src="item.thumbnailUrl ?? item.resourceUrl" />
+<img :src="item.thumbnailUrl ?? item.resourceUrl" :alt="item.name || 'resource'" />
-<img :src="imgSrc" />
+<img :src="imgSrc" alt="selected resource" />

Also applies to: 66-66

packages/plugins/resource/src/ResourceSetting.vue (2)

38-41: Remove unused imports to fix ESLint pipeline failure.

-import { useLayout, useNotify, getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register'
+import { useLayout, useNotify } from '@opentiny/tiny-engine-meta-register'

53-59: Drop console.log and reset form when creating a new group.

Console noise breaks CI; also ensure “new” opens with empty fields.

 export const openResourceSettingPanel = (data) => {
   if (data) {
     state.data = { ...data }
-    console.log(state.data)
-  }
+  } else {
+    state.data = { name: '', description: '' }
+  }
   isShow.value = true
 }
packages/plugins/resource/src/ResourceList.vue (3)

260-274: Handle multiple-file selection in TinyFileUpload change event.

Current handler treats a single file; with multiple, process fileList too.

-const chooseFileChange = (file) => {
-  let base64String = ''
-  const reader = new FileReader()
-  reader.readAsDataURL(file.raw)
-  reader.onload = () => {
-    base64String = reader.result
-  }
-  reader.onloadend = () => {
-    addSourceData.value.push({
-      type: 'upload',
-      name: file.name,
-      resourceData: base64String
-    })
-  }
-}
+const toBase64 = (raw) =>
+  new Promise((resolve, reject) => {
+    const reader = new FileReader()
+    reader.onload = () => resolve(reader.result)
+    reader.onerror = reject
+    reader.readAsDataURL(raw)
+  })
+
+const chooseFileChange = async (file, fileList = []) => {
+  const files = Array.isArray(fileList) && fileList.length ? fileList : [file]
+  for (const f of files) {
+    const dataUrl = await toBase64(f.raw)
+    addSourceData.value.push({ type: 'upload', name: f.name, resourceData: dataUrl })
+  }
+}

209-213: Type filter UI not wired; implement filter by sourceType.

-const sourceType = ref('all')
+const sourceType = ref('all')
+const filteredList = computed(() =>
+  state.sourceList.filter((it) => (sourceType.value === 'all' ? true : it?.category === 'image'))
+)

And in template:

-<div v-for="item in state.sourceList" :key="item.id" class="source-list-item">
+<div v-for="item in filteredList" :key="item.id" class="source-list-item">

Also applies to: 276-278


34-34: Add alt text for thumbnails.

-<img :src="item.thumbnailUrl ?? item.resourceUrl" />
+<img :src="item.thumbnailUrl ?? item.resourceUrl" :alt="item.name || 'resource'" />

Also applies to: 546-550

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c6deea and 9f75375.

⛔ Files ignored due to path filters (1)
  • packages/design-core/assets/plugin-icon-resource.svg is excluded by !**/*.svg
📒 Files selected for processing (20)
  • designer-demo/public/mock/bundle.json (1 hunks)
  • packages/build/vite-config/src/vite-plugins/devAliasPlugin.js (1 hunks)
  • packages/canvas/render/src/builtin/builtin.json (1 hunks)
  • packages/configurator/src/index.ts (2 hunks)
  • packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue (1 hunks)
  • packages/configurator/src/source-select-configurator/http.ts (1 hunks)
  • packages/design-core/package.json (1 hunks)
  • packages/design-core/re-export.js (1 hunks)
  • packages/design-core/registry.js (2 hunks)
  • packages/layout/src/defaultLayout.js (1 hunks)
  • packages/plugins/resource/index.ts (1 hunks)
  • packages/plugins/resource/meta.js (1 hunks)
  • packages/plugins/resource/package.json (1 hunks)
  • packages/plugins/resource/src/Main.vue (1 hunks)
  • packages/plugins/resource/src/ResourceList.vue (1 hunks)
  • packages/plugins/resource/src/ResourceSetting.vue (1 hunks)
  • packages/plugins/resource/src/js/http.js (1 hunks)
  • packages/plugins/resource/src/styles/vars.less (1 hunks)
  • packages/plugins/resource/vite.config.ts (1 hunks)
  • packages/register/src/constants.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-09-30T07:51:10.036Z
Learnt from: chilingling
PR: opentiny/tiny-engine#837
File: packages/vue-generator/src/plugins/genDependenciesPlugin.js:66-66
Timestamp: 2024-09-30T07:51:10.036Z
Learning: In the `tiny-engine` project, `opentiny/tiny-engine-dsl-vue` refers to the current package itself, and importing types from it may cause circular dependencies.

Applied to files:

  • packages/build/vite-config/src/vite-plugins/devAliasPlugin.js
  • packages/design-core/package.json
  • packages/plugins/resource/package.json
🧬 Code graph analysis (4)
packages/configurator/src/source-select-configurator/http.ts (3)
packages/plugins/resource/src/js/http.js (5)
  • baseUrl (16-16)
  • fetchResourceListByGroupId (19-20)
  • fetchResourceListByGroupId (19-20)
  • fetchResourceGroupByAppId (54-57)
  • fetchResourceGroupByAppId (54-57)
packages/register/src/common.ts (1)
  • getMetaApi (20-30)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-24)
packages/plugins/resource/src/js/http.js (3)
packages/configurator/src/source-select-configurator/http.ts (1)
  • fetchResourceGroupByAppId (23-26)
packages/register/src/common.ts (2)
  • getMetaApi (20-30)
  • getMergeMeta (53-55)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-24)
packages/layout/src/defaultLayout.js (1)
packages/register/src/constants.ts (1)
  • META_APP (26-83)
packages/design-core/registry.js (1)
packages/plugins/materials/src/composable/types.ts (1)
  • Resource (49-49)
🪛 Biome (2.1.2)
packages/plugins/resource/src/js/http.js

[error] 20-20: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 27-27: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 37-37: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 39-39: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 48-48: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 51-51: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 63-63: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 73-73: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 73-73: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)

🪛 GitHub Check: push-check
packages/plugins/resource/src/ResourceSetting.vue

[failure] 102-102:
'res' is defined but never used. Allowed unused args must match /^_/u


[failure] 92-92:
'res' is defined but never used. Allowed unused args must match /^_/u


[failure] 89-89:
'valid' is defined but never used. Allowed unused args must match /^_/u


[failure] 56-56:
Unexpected console statement


[failure] 38-38:
'META_SERVICE' is defined but never used. Allowed unused vars must match /^_/u


[failure] 38-38:
'getMetaApi' is defined but never used. Allowed unused vars must match /^_/u

🪛 GitHub Actions: Push And Create PR Check
packages/plugins/resource/src/ResourceSetting.vue

[error] 38-38: Command 'npx eslint packages/plugins/resource/meta.js packages/plugins/resource/src/js/http.js packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue packages/plugins/resource/src/Main.vue packages/plugins/resource/src/ResourceList.vue packages/plugins/resource/src/ResourceSetting.vue packages/build/vite-config/src/vite-plugins/devAliasPlugin.js packages/design-core/re-export.js packages/design-core/registry.js packages/layout/src/defaultLayout.js' failed: ESLint: 'getMetaApi' is defined but never used. (no-unused-vars)

🔇 Additional comments (24)
packages/plugins/resource/meta.js (1)

1-6: LGTM. Verify icon registration.

Meta looks correct. Please confirm plugin-icon-resource exists in the SVG registry so the plugin renders an icon.

packages/configurator/src/index.ts (1)

34-34: Expose SourceSelectConfigurator — looks good.

Public export/import are consistent. Ensure docs and examples reference the new configurator.

Also applies to: 73-74

packages/layout/src/defaultLayout.js (1)

15-16: Add Resource to left-top plugins — OK.

Ordering matches adjacent entries. Ensure the registry includes engine.plugins.resource to avoid empty slots.

packages/build/vite-config/src/vite-plugins/devAliasPlugin.js (1)

32-32: Approve — dev alias for Resource plugin verified. packages/plugins/resource/index.ts exists and package.json name is @opentiny/tiny-engine-plugin-resource.

packages/design-core/package.json (1)

60-60: Workspace dependency verified — approve code changes.

packages/plugins/resource/package.json: name "@opentiny/tiny-engine-plugin-resource" (v2.8.0); packages/design-core/package.json lists it as "workspace:*".

packages/design-core/re-export.js (1)

30-30: Approve — default export exists; re-export is safe.
packages/plugins/resource/index.ts exports a default object (spreads metaData and entry), so export { default as Resource } from '@opentiny/tiny-engine-plugin-resource' is valid.

packages/plugins/resource/index.ts (3)

13-14: Meta/entry wiring — sanity-check duplicate 'entry' in meta.
rg search against packages/plugins/resource/meta.* returned no matches; inspect packages/plugins/resource/meta.* (including meta/index.*) for any exported 'entry'. If a meta file defines entry, the imported entry here will override it — remove or rename the duplicate to avoid confusion.


15-15: Side‑effect .less import — confirm SSR/build expectations.

packages/plugins/resource/index.ts imports './src/styles/vars.less' as a side‑effect; verify your library build or consumer bundler (e.g., designer-demo's Vite) processes Less for SSR or extract/move the import into the demo entry to avoid server/runtime import errors.


1-11: License header — OK; confirm repo‑wide consistency.

Header matches project style; standardize years/holders across packages/plugins index.ts files to avoid churn.

packages/register/src/constants.ts (1)

69-71: Approved — 'engine.plugins.resource' ID is consistent.

Confirmed exact matches in the codebase: packages/register/src/constants.ts:70, packages/plugins/resource/meta.js:2, packages/layout/src/defaultLayout.js:16, packages/design-core/registry.js:166.

designer-demo/public/mock/bundle.json (1)

4436-4449: Confirm SourceSelectConfigurator is correct and registered.

  • Component exists and is exported: packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue (exported in packages/configurator/src/index.ts).
  • v-model shape validated: component declares modelValue: String and emits 'update:modelValue' with sourceList[selectedSourceIndex].resourceUrl (a string) — keeping type: "string" and bindState: true for Img.src is correct.
  • Registration validated: packages/canvas/render/src/builtin/builtin.json and designer-demo/public/mock/bundle.json reference "SourceSelectConfigurator" for the Img.src widget — the key matches.
  • Optional suggestion: component filters images client-side but has no prop to enforce resource type; consider adding a prop (e.g., resourceType: "image") and passing it via widget.props, and optionally change zh_CN description to “从资源库选择图片”.
packages/plugins/resource/src/styles/vars.less (1)

1-20: Scoped design tokens look good.

Variables are neatly namespaced under .plugin-resource and map to common tokens. No issues.

packages/plugins/resource/src/Main.vue (1)

105-109: Resolved — helper reads appId from GlobalService

In packages/plugins/resource/src/js/http.js fetchResourceGroupByAppId is a zero-arg export that uses getMetaApi(META_SERVICE.GlobalService).getBaseInfo().id, so calling fetchResourceGroupByAppId() in Main.vue is correct. (There is a separate variant in packages/configurator that accepts an appId param with a fallback.)

packages/design-core/registry.js (2)

43-44: Confirmed: Resource is exported from re-export.
Found in packages/design-core/re-export.js:30 — export { default as Resource } from '@opentiny/tiny-engine-plugin-resource'.


165-167: LGTM — registry guard confirmed

packages/plugins/resource/meta.js defines id 'engine.plugins.resource', so the removal toggle aligns with the plugin meta.

packages/plugins/resource/vite.config.ts (1)

33-37: Verify style.css emission matches banner import.

The banner in packages/plugins/resource/vite.config.ts is "import './style.css'". The earlier check showed no packages/plugins/resource/dist directory, so emission couldn't be confirmed. From the repo root run:

pnpm -F @opentiny/tiny-engine-plugin-resource build
ls -la packages/plugins/resource/dist
fd -HI 'style.css' packages/plugins/resource/dist || rg -n --hidden 'style.css' packages/plugins/resource -C2

If no style.css is emitted at the package dist root, either configure the bundler to output the CSS at the bundle root (e.g., adjust rollupAssetFileNames) or update the banner to import the actual emitted path (e.g., './assets/style.[hash].css').

packages/canvas/render/src/builtin/builtin.json (1)

480-492: Confirm SourceSelectConfigurator registration and form-renderer labelPosition support

  • SourceSelectConfigurator is present at packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue and is exported from packages/configurator/src/index.ts.
  • I found no occurrences of labelPosition / label-position in the repo; verify the form renderer supports labelPosition: "top" (or change the JSON to the renderer's supported key).
  • Ensure the configurator is included in the designer runtime/widget registry so the JSON's component reference is available at runtime.
packages/plugins/resource/package.json (1)

27-31: Workspace protocol in published dependencies — confirm publish flow replaces it.

pnpm rewrites "workspace:" deps to semver when packing/publishing; confirm your CI actually runs a pnpm/lerna publish (pnpm publish / pnpm -r publish or lerna using npmClient: "pnpm") so "workspace:*" won't be published as-is. (pnpm.io)

File: packages/plugins/resource/package.json (lines 27–31) — currently contains "workspace:*" deps; replace with explicit ranges only if your publish flow does not use pnpm/pack.

packages/plugins/resource/src/ResourceSetting.vue (2)

42-51: Module-scoped state is shared across instances; confirm singleton usage.

If multiple instances mount, panels will conflict. If it’s guaranteed singleton via layout, fine; otherwise move isShow/state inside setup and manage via a store.

Also applies to: 61-63


131-158: LGTM: styles and structure.

Collapse/form structure and scoped styles look consistent with the plugin shell.

packages/plugins/resource/src/ResourceList.vue (3)

351-387: Clear selection and exit batch mode after successful batch delete.
[suggest_minor_issue]

 .then(() => {
   getSourceList()
-  selectedSources.value = []
+  selectedSources.value = []
+  enableBatchAction.value = false
 })

285-295: Notification should use error.message when available.
[raise_nitpick_refactor]

-          useNotify({
-            type: 'error',
-            message: error
-          })
+          useNotify({ type: 'error', message: error?.message || String(error) })

Also applies to: 338-347, 375-384


176-183: Confirm watch timing covers initial open.

openResourceListPanel relies on the watch(state.group.id) to fetch data. Verify it triggers reliably on the first open; if not, call getSourceList() directly after setting state.group.

Also applies to: 456-461

packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue (1)

134-152: fetchResourceGroupByAppId has a fallback; calling it without args is valid — but pass appId explicitly for clarity.

The function signature uses appId || getMetaApi(META_SERVICE.GlobalService).getBaseInfo().id, so calling it without arguments will fall back to the global app id and not necessarily fail. Still recommend explicitly obtaining and passing the appId (e.g., via getMetaApi(META_SERVICE.GlobalService).getBaseInfo().id or MetaApp.getCurrentAppId) for robustness.

Affected call sites:

  • packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue — getResourceData() (lines ~134–152)
  • packages/plugins/resource/src/Main.vue — getCategoryList() (around line ~106)

Likely an incorrect or invalid review comment.

Copy link
Copy Markdown
Contributor

@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 (5)
packages/plugins/resource/src/js/http.ts (5)

13-13: Fix incorrect metaService comment.

The comment indicates engine.plugins.appmanage.http but this file is for resource management, not app management.

-/* metaService: engine.plugins.appmanage.http */
+/* metaService: engine.plugins.resource.http */

26-26: Add TypeScript interfaces for API parameters.

The params: any type provides no type safety or documentation. Consider defining proper interfaces for better developer experience and runtime safety.

interface CreateResourceParams {
  name: string;
  url?: string;
  type?: string;
  resourceGroupId?: number;
  // Add other expected fields based on API requirements
}

interface CreateResourceGroupParams {
  name: string;
  description?: string;
  // Add other expected fields based on API requirements  
}

Then update the function signatures:

-export const createResource = (params: any) =>
+export const createResource = (params: CreateResourceParams) =>
-export const batchCreateResource = (params: any) =>
+export const batchCreateResource = (params: CreateResourceParams[]) =>
-export const createResourceGroup = (params: any) =>
+export const createResourceGroup = (params: CreateResourceGroupParams) =>

Also applies to: 34-34, 60-60


68-69: Add TypeScript interface for update parameters.

Similar to the create operations, the update operation should have typed parameters for better type safety.

interface UpdateResourceGroupParams {
  name?: string;
  description?: string;
  // Add other updatable fields
}
-export const updateResourceGroup = (id: number, params: any) =>
+export const updateResourceGroup = (id: number, params: UpdateResourceGroupParams) =>

16-16: Consider making base URL configurable.

The hardcoded base URL /material-center/api may not be suitable for all deployment environments. Consider making it configurable through environment variables or configuration files.

const baseUrl = getMergeMeta('engine.config')?.materialCenterApiUrl || '/material-center/api'

37-42: Optimize batch operation parameter mapping.

The current implementation calls getMetaApi(META_SERVICE.GlobalService).getBaseInfo().id and getMergeMeta('engine.config')?.platformId for every item in the array. Consider extracting these values once for better performance.

export const batchCreateResource = (params: any) => {
+  const appId = getMetaApi(META_SERVICE.GlobalService).getBaseInfo().id;
+  const platformId = getMergeMeta('engine.config')?.platformId;
+  
-  getMetaApi(META_SERVICE.Http).post(
-    `${baseUrl}/resource/create/batch`,
-    params.map((item: any) => ({
-      appId: getMetaApi(META_SERVICE.GlobalService).getBaseInfo().id,
-      platformId: getMergeMeta('engine.config')?.platformId,
-      ...item
-    }))
-  )
+  return getMetaApi(META_SERVICE.Http).post(
+    `${baseUrl}/resource/create/batch`,
+    params.map((item: any) => ({
+      appId,
+      platformId,
+      ...item
+    }))
+  );
+}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec9e29a and fe77497.

📒 Files selected for processing (1)
  • packages/plugins/resource/src/js/http.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/plugins/resource/src/js/http.ts (2)
packages/register/src/common.ts (2)
  • getMetaApi (20-30)
  • getMergeMeta (53-55)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-24)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: push-check

Copy link
Copy Markdown
Contributor

@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

♻️ Duplicate comments (5)
packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue (2)

94-102: Move module-scoped reactive state into setup (duplicate).

Module-level refs/functions are shared across component instances. Define them inside setup.

-const isShow = ref(false)
-
-const openPopover = () => {
-  isShow.value = true
-}
-
-const closePopover = () => {
-  isShow.value = false
-}

Add inside setup:

// inside setup(...)
const isShow = ref(false)
const openPopover = () => (isShow.value = true)
const closePopover = () => (isShow.value = false)

15-16: Prevent crash when no item is selected (duplicate).

Guard access and disable Confirm when selection is empty/out of range.

-            <tiny-button type="primary" plain @click="setSource"> 确认 </tiny-button>
+            <tiny-button
+              type="primary"
+              plain
+              :disabled="selectedSourceIndex == null || !sourceList[selectedSourceIndex]"
+              @click="setSource"
+            > 确认 </tiny-button>
-    const setSource = () => {
-      emit('update:modelValue', sourceList.value[selectedSourceIndex.value].resourceUrl)
-      imgSrc.value = sourceList.value[selectedSourceIndex.value].resourceUrl
-      closePopover()
-    }
+    const setSource = () => {
+      const idx = selectedSourceIndex.value
+      const item = Number.isInteger(idx) ? sourceList.value[idx] : null
+      if (!item) return
+      emit('update:modelValue', item.resourceUrl)
+      imgSrc.value = item.resourceUrl
+      closePopover()
+    }

Also applies to: 176-180

packages/plugins/resource/src/Main.vue (1)

21-36: Handle long group names (ellipsis + tooltip).

Apply:

-              <span>{{ item.name }}</span>
+              <span :title="item.name" class="group-name">{{ item.name }}</span>
 .resource-item {
   ...
+  .group-name {
+    display: inline-block;
+    max-width: 180px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    vertical-align: bottom;
+  }
 }
packages/plugins/resource/src/js/http.ts (2)

25-31: Fail fast on missing appId/platformId; tighten types for batch payloads.

Null access on GlobalService baseInfo can crash calls; batchCreateResource assumes Array. Centralize derivation and validate inputs.

Apply:

+// helper: appId/platformId resolution with validation
+const requireAppAndPlatform = () => {
+  const globalSvc = getMetaApi(META_SERVICE.GlobalService)
+  const base = globalSvc?.getBaseInfo?.()
+  const appId = base?.id
+  if (!appId) {
+    throw new Error('[resource/http] missing appId from GlobalService.getBaseInfo()')
+  }
+  const platformId = getMergeMeta('engine.config')?.platformId
+  return { appId, platformId }
+}
 export const createResource = (params: any) =>
-  getMetaApi(META_SERVICE.Http).post(`${baseUrl}/resource/create`, {
-    appId: getMetaApi(META_SERVICE.GlobalService).getBaseInfo().id,
-    platformId: getMergeMeta('engine.config')?.platformId,
-    ...params
-  })
+  getMetaApi(META_SERVICE.Http).post(`${baseUrl}/resource/create`, {
+    ...requireAppAndPlatform(),
+    ...params
+  })
-export const batchCreateResource = (params: any) =>
+export const batchCreateResource = (params: any[]) =>
   getMetaApi(META_SERVICE.Http).post(
     `${baseUrl}/resource/create/batch`,
-    params.map((item: any) => ({
-      appId: getMetaApi(META_SERVICE.GlobalService).getBaseInfo().id,
-      platformId: getMergeMeta('engine.config')?.platformId,
-      ...item
-    }))
+    (Array.isArray(params) ? params : []).map((item: any) => ({
+      ...requireAppAndPlatform(),
+      ...item
+    }))
   )
 export const fetchResourceGroupByAppId = () =>
-  getMetaApi(META_SERVICE.Http).get(
-    `${baseUrl}/resource-group/${getMetaApi(META_SERVICE.GlobalService).getBaseInfo().id}`
-  )
+  getMetaApi(META_SERVICE.Http).get(`${baseUrl}/resource-group/${requireAppAndPlatform().appId}`)
 export const createResourceGroup = (params: any) =>
-  getMetaApi(META_SERVICE.Http).post(`${baseUrl}/resource-group/create`, {
-    appId: getMetaApi(META_SERVICE.GlobalService).getBaseInfo().id,
-    platformId: getMergeMeta('engine.config')?.platformId,
-    ...params
-  })
+  getMetaApi(META_SERVICE.Http).post(`${baseUrl}/resource-group/create`, {
+    ...requireAppAndPlatform(),
+    ...params
+  })

Also applies to: 33-41, 52-56, 59-65


43-45: Use DELETE for destructive operations (avoid side effects on GET).

GET for deletion is unsafe and violates REST/cache semantics.

Prefer:

-export const deleteResource = (id: number) => getMetaApi(META_SERVICE.Http).get(`${baseUrl}/resource/delete/${id}`)
+export const deleteResource = (id: number) =>
+  getMetaApi(META_SERVICE.Http).delete(`${baseUrl}/resource/delete/${id}`)

If the backend cannot change, at least gate this behind a config and mark responses non-cacheable; otherwise proxies/prefetchers could trigger deletes.

🧹 Nitpick comments (15)
packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue (5)

163-166: Guard category lookup; handle missing group and reset selection.

-    const categoryChange = () => {
-      sourceOriginList.value = categoryData.value.find((item) => item.id === sourceCategory.value).resources
-      sourceList.value = sourceOriginList.value.filter((item) => item.name.includes(searchWords.value))
-    }
+    const categoryChange = () => {
+      const group = categoryData.value.find((item) => item.id === sourceCategory.value)
+      sourceOriginList.value = group?.resources || []
+      selectedSourceIndex.value = undefined
+      sourceList.value = sourceOriginList.value.filter((item) => item.name?.includes(searchWords.value))
+    }

144-160: Harden fetch handling; clearer error; reset selection after load.

-      fetchResourceGroupByAppId()
-        .then((res) => {
-          categoryData.value = res
-          categoryOptions.value = res.map((item) => ({ label: item.name, value: item.id, resources: item.resources }))
+      fetchResourceGroupByAppId()
+        .then((res) => {
+          const list = Array.isArray(res) ? res : []
+          categoryData.value = list
+          categoryOptions.value = list.map((item) => ({ label: item.name, value: item.id, resources: item.resources }))
           if (categoryOptions.value.length) {
             sourceCategory.value = categoryOptions.value[0].value
             sourceOriginList.value = categoryOptions.value[0].resources
             sourceList.value = categoryOptions.value[0].resources
+            selectedSourceIndex.value = undefined
           }
           openPopover()
         })
         .catch((error) => {
           useNotify({
             type: 'error',
-            message: error
+            message: error?.message ?? String(error)
           })
         })

172-174: Source type toggle is a no-op; wire it into filtering or hide the UI.

Minimal approach: apply type filter where you build sourceList.

Example:

const matchesType = (item) =>
  sourceType.value === 'all' || item.type === 'image' || item.mime?.startsWith('image/')

Use matchesType(...) in categoryChange and search filtering.


49-49: Add alt text and lazy loading to images.

-              <img :src="item.thumbnailUrl ?? item.resourceUrl" />
+              <img :src="item.thumbnailUrl ?? item.resourceUrl" :alt="item.name || 'resource'" loading="lazy" />
-          <img :src="imgSrc" />
+          <img :src="imgSrc" alt="selected resource" loading="lazy" />

Also applies to: 66-66


143-161: Avoid re-fetch on every open; reuse cached data per instance.

Early return if data already loaded:

if (categoryOptions.value.length) {
  openPopover()
  return
}
packages/plugins/resource/src/js/http.ts (1)

16-19: Add return typings for HTTP helpers.

Expose typed responses to consumers for better DX and safety.

Example:

export interface ResourceDTO { id: number; name: string; resourceUrl: string; thumbnailUrl?: string; /* ... */ }
export const fetchResourceListByGroupId = (resourceGroupId: number) =>
  getMetaApi(META_SERVICE.Http).get<ResourceDTO[]>(`${baseUrl}/resource/find/${resourceGroupId}`)
packages/plugins/resource/src/Main.vue (2)

105-116: Emit readable errors in notifications.

Passing raw Error/Object can render as “[object Object]”.

-        .catch((error) => {
-          useNotify({
-            type: 'error',
-            message: error
-          })
-        })
+        .catch((error) => {
+          useNotify({
+            type: 'error',
+            message: error?.message || String(error)
+          })
+        })

68-70: Internationalize user-facing strings.

Move hardcoded Chinese strings (title/docsContent/tips) into i18n resources.

packages/plugins/resource/src/ResourceList.vue (7)

39-53: Copy feedback popover closes immediately; hold it briefly.

-    const copySourceLink = async (item) => {
+    const copySourceLink = async (item) => {
       copySourceId.value = item.id
       try {
         await toClipboard(item.resourceUrl)
         copyTipContent.value = '复制URL成功'
       } catch (e) {
         copyTipContent.value = '复制失败'
         // eslint-disable-next-line no-console
         console.error('Clipboard operation failed:', e)
-      } finally {
-        copySourceId.value = ''
-      }
+      } finally {
+        setTimeout(() => (copySourceId.value = ''), 1500)
+      }
     }

220-238: Simplify and harden the name validator regex.

-              const regex = /^[a-zA-Z0-9_\-=+(){}[\]\u4e00-\u9fa5]+\.(png|jpg|jpeg|svg|PNG|JPG|JPEG|SVG)$/i
+              const regex = /^[\w\-=+(){}[\]\u4e00-\u9fa5]+\.(png|jpe?g|svg)$/i

22-27: Batch mode UX: clear selections when exiting.

-            <tiny-button @click="enableBatchAction = !enableBatchAction" class="action-item"> 批量操作 </tiny-button>
+            <tiny-button @click="toggleBatchMode" class="action-item"> 批量操作 </tiny-button>
     const selectedSources = ref([])
+    const toggleBatchMode = () => {
+      enableBatchAction.value = !enableBatchAction.value
+      if (!enableBatchAction.value) {
+        selectedSources.value = []
+      }
+    }

Also applies to: 212-214


30-38: Checkbox change handler: be robust to event shape.

-    const selectSource = (checked, item) => {
-      if (checked) {
+    const selectSource = (evtOrChecked, item) => {
+      const checked =
+        typeof evtOrChecked === 'boolean'
+          ? evtOrChecked
+          : evtOrChecked?.checked ?? evtOrChecked?.target?.checked
+      if (checked) {
         selectedSources.value.push(item)
       } else {
         selectedSources.value = selectedSources.value.filter((source) => source.id !== item.id)
       }
     }

Also applies to: 308-314


206-209: Type filter is unused; apply it to the rendered list.

-          <div v-for="item in state.sourceList" :key="item.id" class="source-list-item">
+          <div v-for="item in visibleSources" :key="item.id" class="source-list-item">
+    const visibleSources = computed(() =>
+      sourceType.value === 'image'
+        ? state.sourceList.filter((s) => s.category === 'image')
+        : state.sourceList
+    )

Also applies to: 30-31


281-292: Readable errors in notifications.

-        .catch((error) => {
+        .catch((error) => {
           useNotify({
             type: 'error',
-            message: error
+            message: error?.message || String(error)
           })
         })

31-33: Performance: lazy-load images.

-              <img :src="item.thumbnailUrl ?? item.resourceUrl" />
+              <img :src="item.thumbnailUrl ?? item.resourceUrl" loading="lazy" decoding="async" />

Also applies to: 540-599

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe77497 and 48f5a94.

⛔ Files ignored due to path filters (1)
  • packages/design-core/assets/plugin-icon-resource.svg is excluded by !**/*.svg
📒 Files selected for processing (6)
  • packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue (1 hunks)
  • packages/configurator/src/source-select-configurator/http.ts (1 hunks)
  • packages/plugins/resource/src/Main.vue (1 hunks)
  • packages/plugins/resource/src/ResourceList.vue (1 hunks)
  • packages/plugins/resource/src/ResourceSetting.vue (1 hunks)
  • packages/plugins/resource/src/js/http.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/plugins/resource/src/ResourceSetting.vue
  • packages/configurator/src/source-select-configurator/http.ts
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-01-14T06:57:07.645Z
Learnt from: rhlin
PR: opentiny/tiny-engine#1011
File: packages/canvas/render/type.d.ts:1-14
Timestamp: 2025-01-14T06:57:07.645Z
Learning: In the tiny-engine project, the team prefers to gradually refine TypeScript types as they become clearer, rather than prematurely defining specific types when the exact structure is not yet well-understood.

Applied to files:

  • packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue
📚 Learning: 2024-09-30T07:51:10.036Z
Learnt from: chilingling
PR: opentiny/tiny-engine#837
File: packages/vue-generator/src/plugins/genDependenciesPlugin.js:66-66
Timestamp: 2024-09-30T07:51:10.036Z
Learning: In the `tiny-engine` project, `opentiny/tiny-engine-dsl-vue` refers to the current package itself, and importing types from it may cause circular dependencies.

Applied to files:

  • packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue
🧬 Code graph analysis (1)
packages/plugins/resource/src/js/http.ts (3)
packages/configurator/src/source-select-configurator/http.ts (1)
  • fetchResourceGroupByAppId (22-25)
packages/register/src/common.ts (2)
  • getMetaApi (20-30)
  • getMergeMeta (53-55)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-24)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: push-check
🔇 Additional comments (3)
packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue (2)

208-365: Styles look good.

Nice use of CSS variables and scoped :deep selectors; consistent with project guidance.


3-10: Verify TinyPopover controlled prop and “height” support.

No official docs found for @opentiny/vue Popover — paste the Popover component implementation or provide the package/repo/docs link (or the @opentiny/vue package name + version from package.json) so I can confirm whether it supports v-model/modelValue or visible and whether a "height" prop exists. Context: packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue (lines 3–10) uses v-model="isShow" and height="844".

packages/plugins/resource/src/ResourceList.vue (1)

316-344: Confirm backend contract for deletions; consider dedicated API.

You’re removing resources by overwriting the group with a filtered resources array. If the backend supports a resource delete API, prefer calling it to avoid racey group overwrites.

  • Option A (preferred if available): call deleteResource(id) per item/batch.
  • Option B: keep group overwrite but ensure the payload is the canonical minimal shape the API expects (ids only vs full objects) and serialize concurrently.

If backend mandates the current pattern, acknowledge in a code comment and ensure optimistic UI handles failures.

Also applies to: 346-382

@hexqi hexqi added this to the v2.9.0 milestone Sep 17, 2025
Copy link
Copy Markdown
Contributor

@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

♻️ Duplicate comments (7)
packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue (4)

97-103: Avoid blanket as Component casts; only assert where type errors occur.

-    TinyPopover: TinyPopover as Component,
-    TinyButton: TinyButton as Component,
+    TinyPopover: TinyPopover,
+    TinyButton: TinyButton,

85-93: Move isShow/open/close into setup to avoid cross‑instance state leaks.

Module‑scoped refs/functions are shared across component instances. Make them per‑instance.

-const isShow = ref(false)
-
-const openPopover = () => {
-  isShow.value = true
-}
-
-const closePopover = () => {
-  isShow.value = false
-}
@@
 export default {
@@
-  setup(props, { emit }) {
+  setup(props, { emit }) {
+    const isShow = ref(false)
+    const openPopover = () => (isShow.value = true)
+    const closePopover = () => (isShow.value = false)
@@
     return {
       isShow,
       imgSrc,
@@
       openPopover,
       closePopover,

Also applies to: 181-197


167-171: Guard null selection and disable “确认” when nothing is selected.

Prevents runtime errors when no item is chosen.

-const setSource = () => {
-  emit('update:modelValue', sourceList.value[selectedSourceIndex.value].resourceUrl)
-  imgSrc.value = sourceList.value[selectedSourceIndex.value].resourceUrl
-  closePopover()
-}
+const setSource = () => {
+  const idx = selectedSourceIndex.value
+  const item = Number.isInteger(idx) ? sourceList.value[idx] : null
+  if (!item) return
+  emit('update:modelValue', item.resourceUrl)
+  imgSrc.value = item.resourceUrl
+  closePopover()
+}
-            <tiny-button type="primary" plain @click="setSource"> 确认 </tiny-button>
+            <tiny-button
+              type="primary"
+              plain
+              @click="setSource"
+              :disabled="selectedSourceIndex == null || !sourceList[selectedSourceIndex]"
+            >
+              确认
+            </tiny-button>

Also applies to: 14-15


172-179: Keep v-model in sync and reset selection on search.

Typing in the input doesn’t emit to parent; selection index can become stale after filtering.

-    const imgSrc = ref(props.modelValue)
+    const imgSrc = ref(props.modelValue)
@@
-    watch(
-      () => searchWords.value,
-      () => {
-        sourceList.value = sourceOriginList.value.filter((item) =>
-          item.name.toLowerCase().includes(searchWords.value.toLowerCase())
-        )
-      }
-    )
+    watch(
+      () => searchWords.value,
+      () => {
+        const q = (searchWords.value || '').toLowerCase().trim()
+        sourceList.value = sourceOriginList.value.filter((item) =>
+          ((item?.name || '').toLowerCase()).includes(q)
+        )
+        selectedSourceIndex.value = undefined
+      }
+    )
+
+    // sync local input <-> parent v-model
+    watch(imgSrc, (val) => emit('update:modelValue', val))
+    watch(
+      () => props.modelValue,
+      (val) => {
+        if (val !== imgSrc.value) imgSrc.value = val
+      }
+    )

Also applies to: 116-116, 71-71

packages/plugins/resource/src/ResourceSetting.vue (1)

94-94: Redundant i flag on the regex.

You already enumerate both cases [a-zA-Z]; the i is unnecessary.

-              const regex = /^[a-zA-Z0-9_\-\u4e00-\u9fa5]+$/i
+              const regex = /^[a-zA-Z0-9_\-\u4e00-\u9fa5]+$/
packages/plugins/resource/src/js/http.ts (2)

26-31: Fail fast when meta services or appId are missing; centralize context getters.

Multiple calls assume getMetaApi(...) and getBaseInfo().id always exist. If either is unavailable, these lines will throw or send malformed payloads. Add small helpers to validate Http service, resolve appId, and read platformId, then use them across calls.

Apply:

 const baseUrl = '/material-center/api'
+// Safe helpers
+const requireHttp = () => {
+  const http = getMetaApi(META_SERVICE.Http)
+  if (!http) throw new Error('[resource/http] Http service unavailable')
+  return http
+}
+const requireAppId = () => {
+  const appId = getMetaApi(META_SERVICE.GlobalService)?.getBaseInfo?.()?.id
+  if (appId === undefined || appId === null) throw new Error('[resource/http] Missing appId')
+  return appId
+}
+const getPlatformId = () => getMergeMeta('engine.config')?.platformId
@@
 export const createResource = (params: any) =>
-  getMetaApi(META_SERVICE.Http).post(`${baseUrl}/resource/create`, {
-    appId: getMetaApi(META_SERVICE.GlobalService).getBaseInfo().id,
-    platformId: getMergeMeta('engine.config')?.platformId,
-    ...params
-  })
+  requireHttp().post(`${baseUrl}/resource/create`, {
+    appId: requireAppId(),
+    platformId: getPlatformId(),
+    ...params
+  })
@@
 export const batchCreateResource = (params: any) =>
-  getMetaApi(META_SERVICE.Http).post(
+  requireHttp().post(
     `${baseUrl}/resource/create/batch`,
-    params.map((item: any) => ({
-      appId: getMetaApi(META_SERVICE.GlobalService).getBaseInfo().id,
-      platformId: getMergeMeta('engine.config')?.platformId,
-      ...item
-    }))
+    (Array.isArray(params) ? params : []).map((item: any) => ({
+      appId: requireAppId(),
+      platformId: getPlatformId(),
+      ...item
+    }))
   )
@@
-export const fetchResourceGroupByAppId = () =>
-  getMetaApi(META_SERVICE.Http).get(
-    `${baseUrl}/resource-group/${getMetaApi(META_SERVICE.GlobalService).getBaseInfo().id}`
-  )
+export const fetchResourceGroupByAppId = (appId?: number) =>
+  requireHttp().get(`${baseUrl}/resource-group/${appId ?? requireAppId()}`)
@@
 export const createResourceGroup = (params: any) =>
-  getMetaApi(META_SERVICE.Http).post(`${baseUrl}/resource-group/create`, {
-    appId: getMetaApi(META_SERVICE.GlobalService).getBaseInfo().id,
-    platformId: getMergeMeta('engine.config')?.platformId,
-    ...params
-  })
+  requireHttp().post(`${baseUrl}/resource-group/create`, {
+    appId: requireAppId(),
+    platformId: getPlatformId(),
+    ...params
+  })

Also applies to: 33-41, 47-50, 53-58, 16-16


43-45: Use HTTP DELETE for deletions; avoid GET for state-changing operations.

GET deletes are unsafe (caching, CSRF semantics) and violate REST conventions. Switch to DELETE.

-// 资源管理 -- 删除资源
-export const deleteResource = (id: number) => getMetaApi(META_SERVICE.Http).get(`${baseUrl}/resource/delete/${id}`)
+// 资源管理 -- 删除资源
+export const deleteResource = (id: number) => requireHttp().delete(`${baseUrl}/resource/${id}`)

If server requires the legacy path, send method: 'DELETE' to that URL instead. Consider adding a symmetric deleteResourceGroup(id: number) if the backend supports it.

🧹 Nitpick comments (15)
packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue (7)

154-157: categoryChange can throw when group isn’t found; make search case‑insensitive and reset selection.

-const categoryChange = () => {
-  sourceOriginList.value = categoryData.value.find((item) => item.id === sourceCategory.value).resources
-  sourceList.value = sourceOriginList.value.filter((item) => item.name.includes(searchWords.value))
-}
+const categoryChange = () => {
+  const group = categoryData.value.find((item) => item.id === sourceCategory.value)
+  sourceOriginList.value = group?.resources || []
+  const q = (searchWords.value || '').toLowerCase().trim()
+  sourceList.value = sourceOriginList.value.filter((item) => ((item?.name || '').toLowerCase()).includes(q))
+  selectedSourceIndex.value = undefined
+}

124-126: “图片/全部”切换未生效;补充按类型过滤(向后兼容 mimeType/type)。

Implements the UI’s intent without breaking when type info is absent.

-    const sourceType = ref('all')
+    const sourceType = ref('all')
+    const matchesType = (item) => {
+      if (sourceType.value === 'all') return true
+      const t = String(item?.type || item?.mimeType || '').toLowerCase()
+      return t === 'image' || t.startsWith('image/')
+    }
-  sourceList.value = sourceOriginList.value.filter((item) => item.name.includes(searchWords.value))
+  const q = (searchWords.value || '').toLowerCase().trim()
+  sourceList.value = sourceOriginList.value.filter(
+    (item) => matchesType(item) && ((item?.name || '').toLowerCase()).includes(q)
+  )
-  sourceList.value = sourceOriginList.value.filter((item) =>
-    item.name.toLowerCase().includes(searchWords.value.toLowerCase())
-  )
+  const q = (searchWords.value || '').toLowerCase().trim()
+  sourceList.value = sourceOriginList.value.filter(
+    (item) => matchesType(item) && ((item?.name || '').toLowerCase()).includes(q)
+  )

Also applies to: 154-157, 172-179


134-146: Reset selection when data is (re)loaded to avoid stale index.

           if (categoryOptions.value.length) {
             sourceCategory.value = categoryOptions.value[0].value
             sourceOriginList.value = categoryOptions.value[0].resources
             sourceList.value = categoryOptions.value[0].resources
           }
+          selectedSourceIndex.value = undefined

47-55: Add alt text and ARIA for a11y.

Improves screen‑reader usability.

-              <img :src="item.thumbnailUrl || item.resourceUrl" />
+              <img :src="item.thumbnailUrl || item.resourceUrl" :alt="item.name || 'resource'" />
               <tiny-radio
                 v-model="selectedSourceIndex"
                 @change="selectSource(index)"
                 :label="index"
-                text=""
+                text=""
+                aria-label="选择该资源"
               ></tiny-radio>
@@
-          <img :src="imgSrc" />
+          <img :src="imgSrc" alt="已选择的资源" />

Also applies to: 64-66


147-151: Surface readable error messages from exceptions.

         .catch((error) => {
           useNotify({
             type: 'error',
-            message: error
+            message: error?.message || String(error)
           })
         })

28-31: Minor UX: trigger search on Enter and clear selection on clear.

Optional, but standard behavior.

-            <tiny-search class="search" v-model="searchWords" placeholder="按资源名称搜索" clearable>
+            <tiny-search class="search" v-model="searchWords" placeholder="按资源名称搜索" clearable @clear="selectedSourceIndex = undefined">

Also applies to: 171-171


118-133: Lightweight typing for resource/category (optional).

Keeps with the team’s gradual typing preference but removes any hotspots.

type ResourceItem = {
  id: string | number
  name?: string
  resourceUrl: string
  thumbnailUrl?: string
  type?: string
  mimeType?: string
}

type CategoryItem = {
  id: string | number
  name: string
  resources: ResourceItem[]
}

const categoryData = ref<CategoryItem[]>([])
const categoryOptions = ref<{ label: string; value: string | number; resources: ResourceItem[] }[]>([])
const sourceOriginList = ref<ResourceItem[]>([])
const sourceList = ref<ResourceItem[]>([])
const selectedSourceIndex = ref<number | undefined>()

Also applies to: 130-133

packages/plugins/resource/src/js/http.ts (2)

24-41: Type the payloads instead of any for API correctness and DX.

Define minimal interfaces for create/batch/group to catch shape errors at compile time.

-import { getMetaApi, getMergeMeta, META_SERVICE } from '@opentiny/tiny-engine-meta-register'
+import { getMetaApi, getMergeMeta, META_SERVICE } from '@opentiny/tiny-engine-meta-register'
+
+interface ResourceCreatePayload {
+  name: string
+  url: string
+  type?: 'image' | string
+  description?: string
+  [k: string]: any
+}
+interface ResourceGroupPayload {
+  name: string
+  description?: string
+}
@@
-export const createResource = (params: any) =>
+export const createResource = (params: ResourceCreatePayload) =>
@@
-export const batchCreateResource = (params: any) =>
+export const batchCreateResource = (params: ResourceCreatePayload[]) =>
@@
-export const createResourceGroup = (params: any) =>
+export const createResourceGroup = (params: ResourceGroupPayload) =>
@@
-export const updateResourceGroup = (id: number, params: any) =>
+export const updateResourceGroup = (id: number, params: Partial<ResourceGroupPayload>) =>

Also applies to: 52-62


60-62: Comment and API name mismatch (“包括删除”).

This function updates a group; it doesn’t delete. Align the comment to avoid confusion, or add an explicit deleteResourceGroup.

-// 资源管理 -- 修改资源分组信息-包括删除
+// 资源管理 -- 修改资源分组信息
packages/plugins/resource/src/Main.vue (3)

108-119: Harden response handling and error messaging in getCategoryList().

Guard against differing response shapes and surface actionable error text.

-    const getCategoryList = () => {
-      fetchResourceGroupByAppId()
-        .then((res) => {
-          resourceList.value = res
-        })
-        .catch((error) => {
-          useNotify({
-            type: 'error',
-            message: error
-          })
-        })
-    }
+    const getCategoryList = () => {
+      fetchResourceGroupByAppId()
+        .then((res) => {
+          const list = Array.isArray(res) ? res : (res?.data ?? res?.list ?? [])
+          resourceList.value = list || []
+        })
+        .catch((error) => {
+          useNotify({
+            type: 'error',
+            message: error?.message || String(error)
+          })
+        })
+    }

Please confirm the actual Http client return shape (array vs { data }). If it’s always { data }, we can simplify accordingly.


21-33: Prevent UI breakage with long group names (ellipsis + tooltip).

Add truncation to avoid overflow and provide a tooltip for full text.

-            <span>
+            <span class="item-main">
               <svg-icon name="plugin-icon-resource"></svg-icon>
-              <span>{{ item.name }}</span>
+              <span class="name" :title="item.name">{{ item.name }}</span>
             </span>
 .resource-list {
@@
   .resource-item {
@@
-    .svg-icon {
+    .svg-icon {
       color: var(--te-resource-manage-draggable-icon-color);
       margin-right: 4px;
     }
+    .item-main {
+      display: inline-flex;
+      align-items: center;
+      min-width: 0;
+      max-width: calc(100% - 24px); // leave space for setting icon
+    }
+    .name {
+      display: inline-block;
+      min-width: 0;
+      max-width: 100%;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      vertical-align: bottom;
+    }

Also applies to: 146-179


88-106: Avoid mutating server objects for selection state; track selectedId instead.

Adding active to server records couples UI state to API data. Track a selectedId and compute classes.

-    const setItemActive = (data) => {
-      resourceList.value.forEach((item) => {
-        if (data.id === item.id) {
-          item.active = true
-        } else {
-          item.active = false
-        }
-      })
-    }
+    const selectedId = ref<number | null>(null)
+    const setItemActive = (data) => {
+      selectedId.value = data?.id ?? null
+    }
-            :class="['resource-item', { 'active-item': item.active }]"
+            :class="['resource-item', { 'active-item': item.id === selectedId }]"

Also return selectedId from setup.

packages/plugins/resource/src/ResourceSetting.vue (3)

55-64: Reset form state when creating a new group or closing the panel to avoid stale data.

Opening “new” after editing retains the previous record’s fields (and possibly id). Reset on open-without-data and on close.

-export const openResourceSettingPanel = (data) => {
-  if (data) {
-    state.data = { ...data }
-  }
-  isShow.value = true
-}
+const emptyData = () => ({ name: '', description: '' })
+export const openResourceSettingPanel = (data) => {
+  state.data = data ? { ...data } : emptyData()
+  isShow.value = true
+}
@@
-    const cancelResourceSetting = () => {
-      closeResourceSettingPanel()
-    }
+    const cancelResourceSetting = () => {
+      generalForm.value?.resetFields?.()
+      state.data = emptyData()
+      closeResourceSettingPanel()
+    }

Also applies to: 134-136


117-119: Surface meaningful error messages from exceptions.

Pass err.message (fallback to stringified error) to the notifier.

-            .catch((err) => {
-              useNotify({ message: err, type: 'error' })
-            })
+            .catch((err) => {
+              useNotify({ message: err?.message || String(err), type: 'error' })
+            })
@@
-            .catch((err) => {
-              useNotify({ message: err, type: 'error' })
-            })
+            .catch((err) => {
+              useNotify({ message: err?.message || String(err), type: 'error' })
+            })

Also applies to: 127-129


110-126: Trim name before submit to avoid invisible-validation issues.

Users can input trailing spaces that pass length checks but violate server rules.

-          updateResourceGroup(state.data.id, { name: state.data.name, description: state.data.description })
+          updateResourceGroup(state.data.id, { name: state.data.name.trim(), description: state.data.description })
@@
-          createResourceGroup(state.data)
+          createResourceGroup({ ...state.data, name: state.data.name.trim() })
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 48f5a94 and a4c6c45.

📒 Files selected for processing (6)
  • packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue (1 hunks)
  • packages/configurator/src/source-select-configurator/http.ts (1 hunks)
  • packages/plugins/resource/src/Main.vue (1 hunks)
  • packages/plugins/resource/src/ResourceList.vue (1 hunks)
  • packages/plugins/resource/src/ResourceSetting.vue (1 hunks)
  • packages/plugins/resource/src/js/http.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/plugins/resource/src/ResourceList.vue
  • packages/configurator/src/source-select-configurator/http.ts
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-01-14T06:57:07.645Z
Learnt from: rhlin
PR: opentiny/tiny-engine#1011
File: packages/canvas/render/type.d.ts:1-14
Timestamp: 2025-01-14T06:57:07.645Z
Learning: In the tiny-engine project, the team prefers to gradually refine TypeScript types as they become clearer, rather than prematurely defining specific types when the exact structure is not yet well-understood.

Applied to files:

  • packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue
📚 Learning: 2024-09-30T07:51:10.036Z
Learnt from: chilingling
PR: opentiny/tiny-engine#837
File: packages/vue-generator/src/plugins/genDependenciesPlugin.js:66-66
Timestamp: 2024-09-30T07:51:10.036Z
Learning: In the `tiny-engine` project, `opentiny/tiny-engine-dsl-vue` refers to the current package itself, and importing types from it may cause circular dependencies.

Applied to files:

  • packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue
🧬 Code graph analysis (1)
packages/plugins/resource/src/js/http.ts (3)
packages/configurator/src/source-select-configurator/http.ts (1)
  • fetchResourceGroupByAppId (22-25)
packages/register/src/common.ts (2)
  • getMetaApi (20-30)
  • getMergeMeta (53-55)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-24)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: push-check

Copy link
Copy Markdown
Contributor

@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

♻️ Duplicate comments (2)
packages/plugins/resource/src/Main.vue (2)

38-39: Event naming aligned with refreshCategory (good fix)

The child now emits refreshCategory and the parent handles it with the same name. Matches prior feedback.


43-43: Remove the metaService comment if not required by tooling

If this comment isn’t parsed by build tooling, drop it to avoid drift.

🧹 Nitpick comments (8)
packages/plugins/resource/src/Main.vue (8)

5-5: Prop name consistency for PluginPanel

You mix kebab- and camel-case props. For consistency with other props here (fixedPanels, docsContent, isShowDocsIcon), consider camelCase for fixedName, or confirm PluginPanel expects kebab-case. If not, the prop may be ignored.

-      :fixed-name="PLUGIN_NAME.Resource"
+      :fixedName="PLUGIN_NAME.Resource"

12-19: Externalize hardcoded UI strings to i18n

Strings like “新建分组”, the default title “资源管理”, and docsContent should use the project i18n. This also eases future localization.

Also applies to: 60-63, 71-71


21-33: Handle long group names and reduce hover layout shift; add keyboard focus state

Long names can overlap the settings icon; the setting button only appears on hover (mouse-only). Add ellipsis and show the settings icon on focus-within too.

Template tweak:

-            <span>
+            <span class="item-title">
               <svg-icon name="plugin-icon-resource"></svg-icon>
-              <span>{{ item.name }}</span>
+              <span class="item-name" :title="item.name">{{ item.name }}</span>
             </span>

Styles:

 .resource-item {
@@
-    .item-setting {
-      display: none;
-    }
+    .item-setting {
+      opacity: 0;
+      transition: opacity .16s;
+    }
+    .item-title {
+      flex: 1;
+      min-width: 0;
+      display: inline-flex;
+      align-items: center;
+      gap: 4px;
+    }
+    .item-name {
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
@@
-    &:hover {
+    &:hover {
       background-color: var(--te-resource-manage-draggable-row-bg-color-hover);
       color: var(--te-resource-manage-draggable-text-color);
-      .item-setting {
-        display: inline;
-      }
+      .item-setting { opacity: 1; }
     }
+    &:focus-within { .item-setting { opacity: 1; } }

Also applies to: 149-171


35-35: Avoid optional chaining on a ref in templates

Refs auto-unwrap in templates; optional chaining is unnecessary.

-        <search-empty :isShow="!resourceList?.length" />
+        <search-empty :isShow="!resourceList.length" />

64-66: Provide a default for fixedPanels

Avoid passing undefined to PluginPanel.

-    fixedPanels: {
-      type: Array
-    }
+    fixedPanels: {
+      type: Array,
+      default: () => []
+    }

73-78: Use a typed Symbol injection key (avoid string keys)

String keys can collide; Symbols are safer and TS-friendly.

Example:

// above setup()
const panelStateKey = Symbol('panelState') as InjectionKey<{ emitEvent: typeof emit }>

// provide
provide(panelStateKey, panelState)

87-95: Simplify selection state to selectedId (avoid mutating each item)

Current setItemActive walks the whole list and mutates objects. Track a selectedId and compute the class in the template; this reduces churn and keeps server data immutable.

Sketch:

  • state: const selectedId = ref<string | number | null>(null)
  • template: :class="['resource-item', { 'active-item': item.id === selectedId }]"
  • on select: selectedId.value = data?.id ?? null

Also applies to: 123-127


107-118: Harden fetching: async/await + robust error message + defensive assignment

Ensure message is a string and handle non-array responses.

-    const getCategoryList = () => {
-      fetchResourceGroupByAppId()
-        .then((res) => {
-          resourceList.value = res
-        })
-        .catch((error) => {
-          useNotify({
-            type: 'error',
-            message: error
-          })
-        })
-    }
+    const getCategoryList = async () => {
+      try {
+        const res = await fetchResourceGroupByAppId()
+        resourceList.value = Array.isArray(res) ? res : []
+      } catch (error: any) {
+        useNotify({
+          type: 'error',
+          message: error?.message ?? String(error)
+        })
+      }
+    }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4c6c45 and 636a57f.

📒 Files selected for processing (5)
  • packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue (1 hunks)
  • packages/plugins/resource/src/Main.vue (1 hunks)
  • packages/plugins/resource/src/ResourceList.vue (1 hunks)
  • packages/plugins/resource/src/ResourceSetting.vue (1 hunks)
  • packages/plugins/resource/src/styles/vars.less (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/plugins/resource/src/ResourceSetting.vue
  • packages/plugins/resource/src/ResourceList.vue
  • packages/plugins/resource/src/styles/vars.less
  • packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue

Copy link
Copy Markdown
Contributor

@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)
packages/layout/src/defaultLayout.js (2)

16-18: Consider placement for discoverability.

Placing Resource near Materials or Collections may match user mental model (assets next to components/data). Minor UX call.


16-16: Optionally gate behind a feature flag.

Since v1 supports images only and lacks tests/docs per PR, consider a feature toggle to ship dark but safe.

Happy to propose a minimal flag check in layout config if you share your feature-flag mechanism.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 636a57f and 71cb848.

📒 Files selected for processing (8)
  • packages/build/vite-config/src/vite-plugins/devAliasPlugin.js (1 hunks)
  • packages/configurator/src/index.ts (2 hunks)
  • packages/configurator/src/model-common/constants.js (1 hunks)
  • packages/design-core/package.json (1 hunks)
  • packages/design-core/re-export.js (1 hunks)
  • packages/design-core/registry.js (2 hunks)
  • packages/layout/src/defaultLayout.js (1 hunks)
  • packages/register/src/constants.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/configurator/src/model-common/constants.js
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/design-core/re-export.js
  • packages/configurator/src/index.ts
  • packages/design-core/package.json
  • packages/design-core/registry.js
  • packages/register/src/constants.ts
  • packages/build/vite-config/src/vite-plugins/devAliasPlugin.js
🧰 Additional context used
🧬 Code graph analysis (1)
packages/layout/src/defaultLayout.js (1)
packages/register/src/constants.ts (1)
  • META_APP (26-85)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: push-check
🔇 Additional comments (1)
packages/layout/src/defaultLayout.js (1)

16-16: LGTM — Resource plugin wiring verified.
Mapping 'engine.plugins.resource' exists (packages/register/src/constants.ts); registry registers Resource with removal guard (packages/design-core/registry.js); re-export present; workspace dep and plugin package found (packages/design-core/package.json, packages/plugins/resource/package.json).

Copy link
Copy Markdown
Contributor

@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

♻️ Duplicate comments (3)
packages/plugins/resource/src/ResourceList.vue (3)

259-273: Fix upload handler: support multiple files and remove base64 race.

multiple=true but the handler assumes a single file; also it reads from an intermediate variable. Normalize to File | File[] and use reader.onloadend.

-    const chooseFileChange = (file) => {
-      let base64String = ''
-      const reader = new FileReader()
-      reader.readAsDataURL(file.raw)
-      reader.onload = () => {
-        base64String = reader.result
-      }
-      reader.onloadend = () => {
-        addSourceData.value.push({
-          type: 'upload',
-          name: file.name,
-          resourceData: base64String
-        })
-      }
-    }
+    const chooseFileChange = (payload) => {
+      const files = Array.isArray(payload) ? payload : [payload]
+      files.forEach((file) => {
+        const raw = (file as any).raw || file
+        const reader = new FileReader()
+        reader.onloadend = () => {
+          addSourceData.value.push({
+            type: 'upload',
+            name: (file as any).name,
+            resourceData: reader.result as string
+          })
+        }
+        reader.readAsDataURL(raw)
+      })
+    }

33-37: Control checkbox selection to prevent stale batch counts; clear selection when toggling batch mode.

The checkboxes aren’t bound to selection state; selectedSources can drift from UI, causing the batch-delete count issue.

-              <tiny-checkbox
-                v-if="enableBatchAction"
-                name="tiny-checkbox"
-                @change="selectSource($event, item)"
-              ></tiny-checkbox>
+              <tiny-checkbox
+                v-if="enableBatchAction"
+                :modelValue="isSelected(item.id)"
+                @update:modelValue="toggleSelect($event, item)"
+              />
-    const selectSource = (checked, item) => {
-      if (checked) {
-        selectedSources.value.push(item)
-      } else {
-        selectedSources.value = selectedSources.value.filter((source) => source.id !== item.id)
-      }
-    }
+    const toggleSelect = (checked: boolean, item) => {
+      if (checked) {
+        if (!selectedSources.value.find((s) => s.id === item.id)) selectedSources.value.push(item)
+      } else {
+        selectedSources.value = selectedSources.value.filter((s) => s.id !== item.id)
+      }
+    }

Add this small helper and watcher (outside the ranges; place near other watch/methods):

const isSelected = (id: string) => !!selectedSources.value.find((s) => s.id === id)

watch(enableBatchAction, (on) => {
  if (!on) selectedSources.value = []
})

Also applies to: 312-318


351-387: Batch delete: compute count and payload from current list, not stale selections.

Intersect selections with the current sourceList to avoid incorrect counts and wrong payload after a single-item delete.

-      confirm({
+      // Recompute against current list to avoid stale selections
+      const selectedIds = new Set(selectedSources.value.map((s) => s.id))
+      const currentSelected = state.sourceList.filter((s) => selectedIds.has(s.id))
+      if (!currentSelected.length) {
+        useNotify({ type: 'info', message: '请先选择一个资源' })
+        return
+      }
+      confirm({
         title: '删除资源',
-        message: `即将删除${selectedSources.value.length}个资源,删除后不可恢复。`,
+        message: `即将删除${currentSelected.length}个资源,删除后不可恢复。`,
         exec: () => {
-          updateResourceGroup(state.group.id, {
+          updateResourceGroup(state.group.id, {
             name: state.group.name,
             description: state.group.description,
-            resources: state.sourceList
-              .map((source) => {
-                if (selectedSources.value.find((item) => item.id === source.id)) {
-                  return null
-                }
-                return { id: source.id }
-              })
-              .filter((source) => source !== null)
+            resources: state.sourceList
+              .filter((s) => !selectedIds.has(s.id))
+              .map((s) => ({ id: s.id }))
           })
             .then(() => {
               getSourceList()
               selectedSources.value = []
             })
🧹 Nitpick comments (4)
packages/plugins/model-manager/src/Main.vue (1)

28-31: Show empty state for filtered results (avoid blank list when search has 0 matches).

Currently the empty component only shows when there are zero models overall, not when a search yields no matches. Drive both list and empty-state from filteredModels.

-          <template v-if="models.length">
+          <template v-if="filteredModels.length">
@@
-          <search-empty :isShow="!models.length" />
+          <search-empty :isShow="!filteredModels.length" />

Also applies to: 45-46

packages/plugins/resource/src/ResourceList.vue (3)

298-309: Show copy tip for a moment; don’t hide immediately.

The manual popover closes instantly; users can’t see the result. Delay clearing the flag.

-    const copySourceLink = async (item) => {
-      copySourceId.value = item.id
-      try {
-        await toClipboard(item.resourceUrl)
-        copyTipContent.value = '复制URL成功'
-      } catch (e) {
-        copyTipContent.value = '复制失败'
-        // eslint-disable-next-line no-console
-        console.error('Clipboard operation failed:', e)
-      } finally {
-        copySourceId.value = ''
-      }
-    }
+    const copySourceLink = async (item) => {
+      try {
+        await toClipboard(item.resourceUrl)
+        copyTipContent.value = '复制URL成功'
+      } catch (e) {
+        copyTipContent.value = '复制失败'
+        // eslint-disable-next-line no-console
+        console.error('Clipboard operation failed:', e)
+      } finally {
+        copySourceId.value = item.id
+        setTimeout(() => (copySourceId.value = ''), 1200)
+      }
+    }

30-33: Wire type filter into rendering and empty state.

sourceType toggles currently have no effect. Use a filtered list.

-          <div v-for="item in state.sourceList" :key="item.id" class="source-list-item">
+          <div v-for="item in displayedSourceList" :key="item.id" class="source-list-item">
@@
-      <search-empty :isShow="!state.sourceList.length" />
+      <search-empty :isShow="!displayedSourceList.length" />

Add (outside ranges, inside setup):

const displayedSourceList = computed(() =>
  sourceType.value === 'all'
    ? state.sourceList
    : state.sourceList.filter((s) => (s.category || 'image') === sourceType.value)
)

And return displayedSourceList from setup.

Also applies to: 68-69


10-11: Copy update: mention JPEG in the hint to match accepted types.

Text says png/jpg/svg, but you also accept .jpeg.

-        <span class="resource-description">支持上传png、jpg、svg文件,支持批量上传</span>
+        <span class="resource-description">支持上传png、jpg、jpeg、svg文件,支持批量上传</span>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 71cb848 and d46ce97.

📒 Files selected for processing (4)
  • packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue (1 hunks)
  • packages/plugins/model-manager/src/Main.vue (2 hunks)
  • packages/plugins/resource/src/Main.vue (1 hunks)
  • packages/plugins/resource/src/ResourceList.vue (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/plugins/resource/src/Main.vue
  • packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: push-check
🔇 Additional comments (1)
packages/plugins/model-manager/src/Main.vue (1)

162-163: Defensive fallback LGTM.

models.value = data.records || [] is a safe default when API omits records.

@hexqi hexqi merged commit 1991b4a into opentiny:develop Sep 18, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants