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

107
utils/constants.js Normal file
View File

@@ -0,0 +1,107 @@
// 字段类型常量定义
export const FIELD_TYPES = {
SINGLE_LINE_TEXT: 'SINGLE_LINE_TEXT',
NUMBER: 'NUMBER',
MULTI_LINE_TEXT: 'MULTI_LINE_TEXT',
RICH_TEXT: 'RICH_TEXT',
LINK: 'LINK',
CHECK_BOX: 'CHECK_BOX',
RADIO_BUTTON: 'RADIO_BUTTON',
DROP_DOWN: 'DROP_DOWN',
MULTI_SELECT: 'MULTI_SELECT',
DATE: 'DATE',
TIME: 'TIME',
DATETIME: 'DATETIME',
USER_SELECT: 'USER_SELECT',
ORGANIZATION_SELECT: 'ORGANIZATION_SELECT',
GROUP_SELECT: 'GROUP_SELECT',
CALC: 'CALC',
RECORD_NUMBER: 'RECORD_NUMBER',
CREATOR: 'CREATOR',
CREATED_TIME: 'CREATED_TIME',
MODIFIER: 'MODIFIER',
UPDATED_TIME: 'UPDATED_TIME',
STATUS: 'STATUS',
STATUS_ASSIGNEE: 'STATUS_ASSIGNEE',
CATEGORY: 'CATEGORY',
FILE: 'FILE',
SUBTABLE: 'SUBTABLE',
GROUP: 'GROUP',
REFERENCE_TABLE: 'REFERENCE_TABLE',
};
// 布局类型常量定义
export const LAYOUT_TYPES = {
ROW: 'ROW',
SUBTABLE: 'SUBTABLE',
GROUP: 'GROUP',
LABEL: 'LABEL',
HR: 'HR',
SPACER: 'SPACER',
};
// 支持选项排序的字段类型列表
export const OPTION_SORTABLE_TYPES = [
FIELD_TYPES.CHECK_BOX,
FIELD_TYPES.DROP_DOWN,
FIELD_TYPES.MULTI_SELECT,
FIELD_TYPES.RADIO_BUTTON,
];
// 分组布局中排除的字段类型列表
export const EXCLUDED_GROUP_TYPES = [
FIELD_TYPES.CATEGORY,
FIELD_TYPES.STATUS,
FIELD_TYPES.STATUS_ASSIGNEE,
FIELD_TYPES.SUBTABLE,
FIELD_TYPES.GROUP,
];
// 系统字段类型列表(自动添加到所有表单中)
export const SYSTEM_FIELD_TYPES = [
FIELD_TYPES.RECORD_NUMBER,
FIELD_TYPES.CREATOR,
FIELD_TYPES.CREATED_TIME,
FIELD_TYPES.MODIFIER,
FIELD_TYPES.UPDATED_TIME,
];
// 系统状态字段类型列表
export const SYSTEM_STATUS_FIELD_TYPES = [
FIELD_TYPES.STATUS,
FIELD_TYPES.STATUS_ASSIGNEE,
FIELD_TYPES.CATEGORY,
];
// 支持查找复制的字段类型列表
export const LOOKUP_COPY_SUPPORTED_TYPES = [
FIELD_TYPES.SINGLE_LINE_TEXT,
FIELD_TYPES.NUMBER,
FIELD_TYPES.MULTI_LINE_TEXT,
FIELD_TYPES.RICH_TEXT,
FIELD_TYPES.LINK,
FIELD_TYPES.CHECK_BOX,
FIELD_TYPES.RADIO_BUTTON,
FIELD_TYPES.DROP_DOWN,
FIELD_TYPES.MULTI_SELECT,
FIELD_TYPES.DATE,
FIELD_TYPES.TIME,
FIELD_TYPES.DATETIME,
FIELD_TYPES.USER_SELECT,
FIELD_TYPES.ORGANIZATION_SELECT,
FIELD_TYPES.GROUP_SELECT,
FIELD_TYPES.CALC,
FIELD_TYPES.RECORD_NUMBER,
];
// 页面类型常量定义
export const PAGE_TYPES = {
DETAIL: 'detail',
EDIT: 'edit',
CREATE: 'create',
ADMIN: 'admin',
};
export const SCRIPT_FILES = [
'main.js' // 立即执行
];

70
utils/dom-utils.js Normal file
View File

@@ -0,0 +1,70 @@
/**
* DOM 操作工具模块
* 提供用于创建和管理 DOM 元素的辅助函数
*/
import { FIELD_TYPES } from './constants.js';
/**
* 从表元素中提取列宽度信息
* @param {HTMLElement} tableElement - 要分析的表元素
* @param {string} fieldType - 字段类型(影响返回的宽度数组)
* @returns {Array<number>} 表列宽度的数组
*/
export const getColumnWidths = (tableElement, fieldType) => {
// 获取所有标题元素并提取它们的客户端宽度
const headerElements = Array.from(tableElement.querySelectorAll('th'));
const columnWidths = headerElements.map(header => header.clientWidth);
// 对于子表返回所有宽度,其他表跳过第一列(通常是操作列)
return fieldType === FIELD_TYPES.SUBTABLE
? columnWidths // 子表保留所有列
: columnWidths.slice(1); // 其他表跳过第一列
};
/**
* 安全地将标签元素插入到指定目标元素之前
* @param {HTMLElement} targetElement - 要插入之前的目标元素
* @param {HTMLElement} labelElement - 要插入的标签元素
* @returns {boolean} 插入是否成功
*/
export const safelyInsertLabel = (targetElement, labelElement) => {
try {
if (!targetElement || !labelElement) {
console.warn('Failed to insert label: target element or label element does not exist');
return false;
}
if (!targetElement.before) {
console.warn('Failed to insert label: target element does not support before method');
return false;
}
targetElement.before(labelElement);
return true;
} catch (error) {
console.error('Error inserting label: ', error);
return false;
}
};
/**
* 安全地将标签元素追加到指定目标元素
* @param {HTMLElement} targetElement - 要追加到的目标元素
* @param {HTMLElement} labelElement - 要追加的标签元素
* @returns {boolean} 追加是否成功
*/
export const safelyAppendLabel = (targetElement, labelElement) => {
try {
if (!targetElement || !labelElement) {
console.warn('Failed to append label: target element or label element does not exist');
return false;
}
targetElement.appendChild(labelElement);
return true;
} catch (error) {
console.error('Error appending label: ', error);
return false;
}
};

85
utils/field-utils.js Normal file
View File

@@ -0,0 +1,85 @@
/**
* 字段处理工具函数模块
* 提供字段类型判断、选项排序、系统字段处理等工具函数
*/
import {
FIELD_TYPES,
OPTION_SORTABLE_TYPES,
LOOKUP_COPY_SUPPORTED_TYPES
} from './constants.js';
/**
* 排序字段选项,根据索引值进行升序排序
* @param {Object} field - 字段对象
* @returns {Array} 按索引排序后的选项标签数组
*/
export const sortFieldOptions = (field) => {
if (!field.options) return [];
return Object.values(field.options)
.sort((a, b) => Number(a.index) - Number(b.index))
.map(option => option.label);
};
/**
* 检查字段类型是否支持选项排序
* @param {string} fieldType - 字段类型
* @returns {boolean} 是否支持选项排序
*/
export const shouldSortOptions = (fieldType) => OPTION_SORTABLE_TYPES.includes(fieldType);
/**
* 标记查找复制字段
* @param {Array} fields - 要处理的字段数组
*/
export const markLookupCopies = (fields) => {
fields.forEach(field => {
// 检查字段是否有查找设置
if (field.lookup?.fieldMappings) {
field.lookup.fieldMappings.forEach(mapping => {
// 在字段数组中找到对应的目标字段并标记为查找复制
const targetField = fields.find(f =>
LOOKUP_COPY_SUPPORTED_TYPES.includes(f.type) && f.code === mapping.field
);
if (targetField) {
targetField.isLookupCopy = true;
}
});
}
});
};
export const FieldTypeChecker = {
isCategory: field => field.type === FIELD_TYPES.CATEGORY,
isCheckBox: field => field.type === FIELD_TYPES.CHECK_BOX,
isCreatedTime: field => field.type === FIELD_TYPES.CREATED_TIME,
isCreator: field => field.type === FIELD_TYPES.CREATOR,
isDate: field => field.type === FIELD_TYPES.DATE,
isDatetime: field => field.type === FIELD_TYPES.DATETIME,
isDropDown: field => field.type === FIELD_TYPES.DROP_DOWN,
isFile: field => field.type === FIELD_TYPES.FILE,
isGroup: field => field.type === FIELD_TYPES.GROUP,
isGroupSelect: field => field.type === FIELD_TYPES.GROUP_SELECT,
isInGroup: field => 'group' in field,
isInSubtable: field => 'table' in field,
isLink: field => field.type === FIELD_TYPES.LINK,
isLookup: field => 'lookup' in field,
isLookupCopy: field => field.isLookupCopy === true,
isModifier: field => field.type === FIELD_TYPES.MODIFIER,
isMultiLineText: field => field.type === FIELD_TYPES.MULTI_LINE_TEXT,
isMultiSelect: field => field.type === FIELD_TYPES.MULTI_SELECT,
isNotInSubtable: field => !('table' in field),
isNumber: field => field.type === FIELD_TYPES.NUMBER,
isOrganizationSelect: field => field.type === FIELD_TYPES.ORGANIZATION_SELECT,
isRadioButton: field => field.type === FIELD_TYPES.RADIO_BUTTON,
isRecordNumber: field => field.type === FIELD_TYPES.RECORD_NUMBER,
isReferenceTable: field => field.type === FIELD_TYPES.REFERENCE_TABLE,
isRichText: field => field.type === FIELD_TYPES.RICH_TEXT,
isSingleLineText: field => field.type === FIELD_TYPES.SINGLE_LINE_TEXT,
isStatus: field => field.type === FIELD_TYPES.STATUS,
isStatusAssignee: field => field.type === FIELD_TYPES.STATUS_ASSIGNEE,
isSubtable: field => field.type === FIELD_TYPES.SUBTABLE,
isTime: field => field.type === FIELD_TYPES.TIME,
isUpdatedTime: field => field.type === FIELD_TYPES.UPDATED_TIME,
isUserSelect: field => field.type === FIELD_TYPES.USER_SELECT,
};

100
utils/kintone-utils.js Normal file
View File

@@ -0,0 +1,100 @@
/**
* Kintone API 工具模块
* 提供与 Kintone 应用程序交互的通用工具函数
*/
/**
* 从当前 URL 中提取 Guest Space ID
* @returns {string|undefined} Guest Space ID如果未找到则为 undefined
*/
export const getGuestSpaceId = () => {
try {
const match = window.location.pathname.match(/\/guest\/([0-9]+)\//);
return match ? match[1] : undefined;
} catch (error) {
console.warn(error);
return undefined;
}
};
const getAppIdFromUrl = () => {
try {
const match = window.location.search.match(/\?app=([0-9]+)/);
return match ? Number(match[1]) : undefined;
} catch (error) {
console.warn(error);
return undefined;
}
};
/**
* 从 Kintone 获取当前 APP ID
* @returns {number|null} APP ID如果没有为 null
*/
export const getAppId = () => {
try {
if (isInAdminPage()) {
return getAppIdFromUrl() || null;
}
if (!isGlobalKintoneExist()) {
return null;
}
const appId = kintone.app.getId();
if (!appId || isNaN(appId)) {
console.warn('Retrieved app ID is invalid:', appId);
return null;
}
return appId;
} catch (error) {
console.warn('Failed to get app ID: ', error);
return null;
}
};
export const isGlobalKintoneExist = () => {
return typeof kintone !== 'undefined' && typeof kintone.app !== 'undefined';
};
/**
* 是否在 Kintone App 中
* @returns {boolean} 是否在 App 中
*/
export const isInAppPage = () => {
return isGlobalKintoneExist() && /^\/k\/\d+/.test(window.location.pathname);
};
/**
* 是否在 Kintone App Detail 中
* @returns {boolean} 是否在 App 中
*/
export const isInDetailPage = () => {
return isGlobalKintoneExist() && /^\/k\/\d+\/show/.test(window.location.pathname);
};
/**
* 是否在 Kintone App 的 /admin 页面
* @returns {boolean} 是否在 App /admin 页面
*/
export const isInAdminPage = () => {
return !isGlobalKintoneExist() && window.location.pathname.includes('/k/admin');
};
/**
* 是否在 Kintone App 的 /admin Form 页面
* @returns {boolean} 是否在 App /admin Form 页面
*/
export const isInAdminFormPage = () => {
return isInAdminPage() && window.location.hash.includes('#section=form');
};
/**
* 是否在 Kintone 的 space 页面
* @returns {boolean} 是否在 space 页面
*/
export const isInSpacePage = () => {
return isGlobalKintoneExist() && window.location.pathname.includes('/k/#/space');
};