This commit is contained in:
2025-10-17 14:39:35 +08:00
parent 39cc4f4c2e
commit 411f068d75
18 changed files with 1497 additions and 646 deletions

View File

@@ -0,0 +1,185 @@
import {
LAYOUT_TYPES,
FIELD_TYPES,
SYSTEM_STATUS_FIELD_TYPES,
SYSTEM_FIELD_TYPES,
EXCLUDED_GROUP_TYPES
} from '../../utils/constants.js';
import {
sortFieldOptions,
shouldSortOptions,
markLookupCopies,
} from '../../utils/field-utils.js';
/**
* 从属性列表中添加系统状态字段到字段数组中
* @param {Object} properties - 所有字段属性对象
* @param {Array} fields - 要添加字段的数组
*/
const addSystemStatusFields = (properties, fields) => {
const propertyList = Object.values(properties);
SYSTEM_STATUS_FIELD_TYPES.forEach(type => {
const field = propertyList.find(f => f.type === type);
if (field?.enabled) {
fields.push(field);
}
});
};
/**
* 处理常规行布局中的字段
* @param {Array} rowFields - 行中的字段列表
* @param {Object} properties - 字段属性映射
* @param {Array} fields - 要添加字段的数组
* @param {Array} spacers - 要添加间距元素的数组
*/
const processRowFields = (rowFields, properties, fields, spacers) => {
for (const field of rowFields) {
// 跳过标签和分割线布局
if ([LAYOUT_TYPES.LABEL, LAYOUT_TYPES.HR].includes(field.type)) continue;
// 处理间距元素
if (field.type === LAYOUT_TYPES.SPACER) {
spacers.push(field);
continue;
}
const fieldProperty = properties[field.code];
// 跳过子表和分组类型的字段,这些单独处理
if ([FIELD_TYPES.SUBTABLE, FIELD_TYPES.GROUP].includes(fieldProperty.type)) continue;
// 创建处理后的字段对象,支持选项排序的字段添加排序选项
const processedField = shouldSortOptions(fieldProperty.type)
? { ...fieldProperty, sortedOptions: sortFieldOptions(fieldProperty) }
: fieldProperty;
fields.push(processedField);
}
};
/**
* 处理子表字段
* @param {Object} layout - 子表布局对象
* @param {Object} properties - 字段属性映射
* @param {Array} fields - 要添加字段的数组
*/
const processSubtableFields = (layout, properties, fields) => {
const tableCode = layout.code;
const tableField = properties[tableCode];
if (tableField.type !== FIELD_TYPES.SUBTABLE) return;
const subtableFieldsMap = {};
// 处理子表中每个字段
layout.fields.forEach(({ code: fieldCode }) => {
const subField = tableField.fields[fieldCode];
const processedField = shouldSortOptions(subField.type)
? { ...subField, table: tableCode, sortedOptions: sortFieldOptions(subField) }
: { ...subField, table: tableCode };
subtableFieldsMap[fieldCode] = processedField;
fields.push(processedField);
});
// 将完整的子表信息也添加到字段列表中
fields.push({ ...tableField, fields: subtableFieldsMap });
};
/**
* 处理分组字段
* @param {Object} layout - 分组布局对象
* @param {Object} properties - 字段属性映射
* @param {Array} fields - 要添加字段的数组
* @param {Array} spacers - 要添加间距元素的数组
*/
const processGroupFields = (layout, properties, fields, spacers) => {
const groupCode = layout.code;
const groupField = properties[groupCode];
if (groupField.type !== FIELD_TYPES.GROUP) return;
const groupFieldsMap = {};
// 处理分组中的每个字段
layout.layout.forEach(row => {
row.fields.forEach(field => {
// 跳过标签和分割线布局
if ([LAYOUT_TYPES.LABEL, LAYOUT_TYPES.HR].includes(field.type)) return;
// 处理间距元素
if (field.type === LAYOUT_TYPES.SPACER) {
spacers.push({ ...field, group: groupCode });
return;
}
const groupSubField = properties[field.code];
// 跳过排除的字段类型
if (EXCLUDED_GROUP_TYPES.includes(groupSubField.type)) return;
const processedField = shouldSortOptions(groupSubField.type)
? { ...groupSubField, group: groupCode, sortedOptions: sortFieldOptions(groupSubField) }
: { ...groupSubField, group: groupCode };
groupFieldsMap[field.code] = processedField;
fields.push(processedField);
});
});
// 将完整的分组信息也添加到字段列表中
fields.push({ ...groupField, fields: groupFieldsMap });
};
/**
* 添加缺失的系统字段
* @param {Object} properties - 字段属性对象
* @param {Array} fields - 要添加字段的数组
*/
const addMissingSystemFields = (properties, fields) => {
const propertyList = Object.values(properties);
SYSTEM_FIELD_TYPES.forEach(type => {
const field = propertyList.find(f => f.type === type);
// 只添加还没有的系统字段
if (field && !fields.some(f => f.type === type)) {
fields.push(field);
}
});
};
/**
* 从表单属性和布局生成处理后的字段和间距数组
* @param {Object} properties - 字段属性对象
* @param {Array} layouts - 布局定义
* @returns {Object} 包含字段和间距数组的对象
*/
export const generateFields = (properties, layouts) => {
const fields = [];
const spacers = [];
// 首先添加系统状态字段
addSystemStatusFields(properties, fields);
// 处理每个布局项
layouts.forEach(layout => {
switch (layout.type) {
case LAYOUT_TYPES.ROW:
processRowFields(layout.fields, properties, fields, spacers);
break;
case LAYOUT_TYPES.SUBTABLE:
processSubtableFields(layout, properties, fields);
break;
case LAYOUT_TYPES.GROUP:
processGroupFields(layout, properties, fields, spacers);
break;
}
});
markLookupCopies(fields);
addMissingSystemFields(properties, fields);
return { fields, spacers };
};

View File

@@ -0,0 +1,106 @@
/**
* 添加字段信息功能模块
* 负责获取表单配置并添加字段信息标签
*/
import { generateFields } from './fields.js';
import { getGuestSpaceId, getAppId, isInDetailPage, isInAdminFormPage } from '../../utils/kintone-utils.js';
import { FieldLabelProcessor } from '../../page/detail/field-label-processor.js';
import { AdminFieldLabelProcessor } from '../../page/admin/form/admin-field-label-processor.js';
import { KintoneRestAPIClient } from '@kintone/rest-api-client';
import { PAGE_TYPES } from '../../utils/constants.js';
/**
* 从 Kintone API 获取表单数据
* @param {number} appId - 应用 ID
* @param {boolean} isPreview - 是否为预览模式
* @returns {Promise<Object>} 表单字段和布局数据
*/
const fetchFormData = async (appId, isPreview) => {
try {
// 初始化 Kintone REST API 客户端
const client = new KintoneRestAPIClient({
guestSpaceId: getGuestSpaceId(), // 访客空间 ID支持在访客空间中工作
});
const [formFieldsResult, layoutResult] = await Promise.all([
client.app.getFormFields({
app: appId,
preview: isPreview,
}),
client.app.getFormLayout({
app: appId,
preview: isPreview,
}),
]);
if (!formFieldsResult?.properties) {
throw new Error('Failed to retrieve form field data');
}
if (!layoutResult?.layout) {
throw new Error('Failed to retrieve form layout data');
}
return {
formFields: formFieldsResult.properties, // 表单字段属性
layout: layoutResult.layout, // 表单布局配置
};
} catch (error) {
console.error('Failed to fetch form data: ', error);
throw new Error(`Unable to retrieve form configuration: ${error.message}`);
}
};
/**
* 主函数:为 Kintone 表单添加字段信息标签
* @returns {Promise<void>}
*/
export const addFieldLabel = async () => {
try {
const appId = getAppId();
if (!appId || typeof appId !== 'number') {
return;
}
// 创建字段标签处理器实例
const labelProcessor = getFieldLabelProcessor(appId);
if (!labelProcessor) {
console.warn('Unable to create field label processor')
return;
}
// 从 API 获取表单配置数据
const { formFields, layout } = await fetchFormData(appId, labelProcessor.isPreview());
// 处理字段并生成标签数据
const { fields: fieldsWithLabels, spacers: spacerElements } =
generateFields(formFields, layout);
console.log(`Processed ${fieldsWithLabels.length} fields and ${spacerElements.length} spacer elements`);
// 字段元素
labelProcessor.processFieldLabels(fieldsWithLabels);
// 间距元素
labelProcessor.processSpacerLabels(spacerElements);
} catch (error) {
console.error('Failed to add field information: ', error);
throw error;
}
};
const getFieldLabelProcessor = (appId) => {
if (isInDetailPage()) {
return new FieldLabelProcessor({
appId,
pageType: PAGE_TYPES.DETAIL // 当前专注于详情页面
});
}
if (isInAdminFormPage()) {
return new AdminFieldLabelProcessor({
appId,
pageType: PAGE_TYPES.ADMIN // admin 表单页面
});
}
};

View File

@@ -0,0 +1,15 @@
// 颜色配置常量
export const COLORS = {
LABEL_TEXT: 'red',
SPACER_BORDER: '1px dotted red',
};
// 间距和尺寸配置常量
export const SPACING = {
GROUP_MARGIN_LEFT: '20px',
REFERENCE_TABLE_SPACER: '30px',
TABLE_COLUMN_PADDING: 8,
FIELD_CONTAINER_WIDTH: '100%',
};
export let IS_FIELD_TYPE_DISPLAY = false;