feat(web,plugin): P1 跨插件引用 — 前端 Phase 4
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

- 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:
iven
2026-04-19 00:54:34 +08:00
parent ef89ed38a1
commit 9e28d71295
4 changed files with 117 additions and 3 deletions

View File

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