Feat/resources: Add Resource manager plugin & source select configurator#1632
Feat/resources: Add Resource manager plugin & source select configurator#1632hexqi merged 27 commits intoopentiny:developfrom
Conversation
WalkthroughAdds 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (23)
packages/configurator/src/source-select-configurator/http.ts (2)
16-16: Avoid duplicating base URLs between packages.
baseUrlis 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: MakeappIdoptional and harden fallback.Treat
appIdas 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.mapwill throw ifparamsisn’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 constIf there’s a common type (e.g., EnginePlugin), prefer:
export default ({ ...metaData, entry }) satisfies EnginePluginpackages/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.
resourceListis a ref to an array; template unwrapping makesresourceList.lengthvalid.resourceList?.lengthis 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]. Useerror.message || String(error).- useNotify({ - type: 'error', - message: error - }) + useNotify({ + type: 'error', + message: error?.message || String(error) + })
122-124:updateCategoryappears 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-list→resource-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"andtabindex="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
imgSrcdoesn’t syncv-modelupstream 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 bysourceType.-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
⛔ Files ignored due to path filters (1)
packages/design-core/assets/plugin-icon-resource.svgis 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.jspackages/design-core/package.jsonpackages/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-resourceexists 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.resourceto 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-resourceand map to common tokens. No issues.packages/plugins/resource/src/Main.vue (1)
105-109: Resolved — helper reads appId from GlobalServiceIn 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 confirmedpackages/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 -C2If 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/stateinside 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 useerror.messagewhen 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.
openResourceListPanelrelies on thewatch(state.group.id)to fetch data. Verify it triggers reliably on the first open; if not, callgetSourceList()directly after settingstate.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., viagetMetaApi(META_SERVICE.GlobalService).getBaseInfo().idorMetaApp.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.
packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue
Show resolved
Hide resolved
packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue
Show resolved
Hide resolved
There was a problem hiding this comment.
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.httpbut 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: anytype 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/apimay 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().idandgetMergeMeta('engine.config')?.platformIdfor 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
📒 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
packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue
Show resolved
Hide resolved
packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
packages/design-core/assets/plugin-icon-resource.svgis 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
packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue
Show resolved
Hide resolved
packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue
Outdated
Show resolved
Hide resolved
packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue
Outdated
Show resolved
Hide resolved
packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue
Outdated
Show resolved
Hide resolved
packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (7)
packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue (4)
97-103: Avoid blanketas Componentcasts; 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: Redundantiflag on the regex.You already enumerate both cases
[a-zA-Z]; theiis 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(...)andgetBaseInfo().idalways exist. If either is unavailable, these lines will throw or send malformed payloads. Add small helpers to validate Http service, resolveappId, and readplatformId, 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 symmetricdeleteResourceGroup(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
anyhotspots.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 ofanyfor 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; trackselectedIdinstead.Adding
activeto server records couples UI state to API data. Track aselectedIdand 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
selectedIdfromsetup.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
📒 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
packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue
Outdated
Show resolved
Hide resolved
packages/configurator/src/source-select-configurator/SourceSelectConfigurator.vue
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
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 toolingIf 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 PluginPanelYou 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 i18nStrings 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 stateLong 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 templatesRefs auto-unwrap in templates; optional chaining is unnecessary.
- <search-empty :isShow="!resourceList?.length" /> + <search-empty :isShow="!resourceList.length" />
64-66: Provide a default for fixedPanelsAvoid 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 ?? nullAlso applies to: 123-127
107-118: Harden fetching: async/await + robust error message + defensive assignmentEnsure 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
📒 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
There was a problem hiding this comment.
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
📒 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).
There was a problem hiding this comment.
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=truebut the handler assumes a single file; also it reads from an intermediate variable. Normalize to File | File[] and usereader.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;
selectedSourcescan 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
sourceListto 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.
sourceTypetoggles 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
displayedSourceListfromsetup.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
📒 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 omitsrecords.
English | 简体中文
PR
PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
Background and solution
作为一个低代码平台缺少了资源管理功能,无法管理项目中常用的静态资源
What is the current behavior?
新增资源管理插件,用户可以上传资源并在设计器中使用,目前只支持图片
Issue Number: N/A
What is the new behavior?
Does this PR introduce a breaking change?
Other information
Summary by CodeRabbit
New Features
Enhancements
Chores