feat(web,plugin): P1 跨插件引用 — 前端 Phase 4
- plugins.ts: PluginFieldSchema 新增 ref_plugin/ref_fallback_label, PluginEntitySchema 新增 is_public - pluginData.ts: 新增 resolveRefLabels/getPluginEntityRegistry API - EntitySelect: 支持 refPlugin 跨插件查询,目标不可用时降级为禁用 Input - PluginCRUDPage: 表格列解析引用标签(蓝色 Tag),entity_select 表单传 refPlugin/fallbackLabel
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { Select, Spin } from 'antd';
|
||||
import { Select, Spin, Input, Tooltip } from 'antd';
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { listPluginData } from '../api/pluginData';
|
||||
|
||||
@@ -7,6 +8,10 @@ interface EntitySelectProps {
|
||||
entity: string;
|
||||
labelField: string;
|
||||
searchFields?: string[];
|
||||
/** 跨插件引用的目标插件 manifest ID(如 "erp-crm") */
|
||||
refPlugin?: string;
|
||||
/** 目标插件未安装时的降级显示文本 */
|
||||
fallbackLabel?: string;
|
||||
value?: string;
|
||||
onChange?: (value: string, label: string) => void;
|
||||
cascadeFrom?: string;
|
||||
@@ -20,6 +25,8 @@ export default function EntitySelect({
|
||||
entity,
|
||||
labelField,
|
||||
searchFields: _searchFields,
|
||||
refPlugin,
|
||||
fallbackLabel,
|
||||
value,
|
||||
onChange,
|
||||
cascadeFrom,
|
||||
@@ -29,6 +36,10 @@ export default function EntitySelect({
|
||||
}: EntitySelectProps) {
|
||||
const [options, setOptions] = useState<{ value: string; label: string }[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [targetUnavailable, setTargetUnavailable] = useState(false);
|
||||
|
||||
// 跨插件时使用目标插件 ID 查询
|
||||
const effectivePluginId = refPlugin || pluginId;
|
||||
|
||||
const fetchData = useCallback(
|
||||
async (keyword?: string) => {
|
||||
@@ -39,7 +50,7 @@ export default function EntitySelect({
|
||||
? { [cascadeFilter]: cascadeValue }
|
||||
: undefined;
|
||||
|
||||
const result = await listPluginData(pluginId, entity, 1, 20, {
|
||||
const result = await listPluginData(effectivePluginId, entity, 1, 20, {
|
||||
search: keyword,
|
||||
filter,
|
||||
});
|
||||
@@ -49,17 +60,39 @@ export default function EntitySelect({
|
||||
label: String(item.data?.[labelField] ?? item.id),
|
||||
}));
|
||||
setOptions(items);
|
||||
setTargetUnavailable(false);
|
||||
} catch {
|
||||
if (refPlugin) {
|
||||
setTargetUnavailable(true);
|
||||
setOptions([]);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[pluginId, entity, labelField, cascadeFrom, cascadeFilter, cascadeValue],
|
||||
[effectivePluginId, entity, labelField, cascadeFrom, cascadeFilter, cascadeValue, refPlugin],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [fetchData]);
|
||||
|
||||
// 目标插件未安装 → 降级显示
|
||||
if (targetUnavailable) {
|
||||
return (
|
||||
<Input
|
||||
value={value || ''}
|
||||
placeholder={fallbackLabel || `外部引用 (${refPlugin})`}
|
||||
disabled
|
||||
suffix={
|
||||
<Tooltip title="目标插件未安装,此字段暂时不可用">
|
||||
<QuestionCircleOutlined style={{ color: '#999' }} />
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
showSearch
|
||||
|
||||
Reference in New Issue
Block a user