flnxi-Wu/lib/field-conditions.ts
2026-05-25 16:42:15 +08:00

330 lines
7.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 字段条件显示逻辑
*
* 支持的条件类型:
* - equals: 等于
* - notEquals: 不等于
* - contains: 包含
* - notEmpty: 非空
* - isEmpty: 为空
*
* 支持的逻辑运算:
* - AND: 所有条件都满足
* - OR: 任一条件满足
*/
export type ConditionOperator = 'equals' | 'notEquals' | 'contains' | 'notEmpty' | 'isEmpty'
export type LogicOperator = 'AND' | 'OR'
export interface SingleCondition {
fieldKey: string // 依赖的字段key
operator: ConditionOperator
value?: string // 比较值(notEmpty/isEmpty不需要)
}
export interface FieldConditions {
logic: LogicOperator
conditions: SingleCondition[]
}
export type FieldWithConditions = {
key: string
conditions?: string | FieldConditions | null
}
/**
* 解析条件配置JSON字符串
*/
export function parseFieldConditions(conditionsJson: string | FieldConditions | null | undefined): FieldConditions | null {
if (!conditionsJson) {
return null
}
if (typeof conditionsJson === 'string' && conditionsJson.trim() === '') {
return null
}
try {
const parsed = typeof conditionsJson === 'string'
? JSON.parse(conditionsJson)
: conditionsJson
if (!parsed || typeof parsed !== 'object') {
return null
}
// 验证结构
if (!parsed.conditions || !Array.isArray(parsed.conditions)) {
return null
}
return {
logic: parsed.logic === 'OR' ? 'OR' : 'AND',
conditions: parsed.conditions.filter((c: any) =>
c && typeof c === 'object' && c.fieldKey && c.operator
)
}
} catch {
console.warn('[FieldConditions] Failed to parse conditions:', conditionsJson)
return null
}
}
/**
* 序列化条件配置为JSON字符串
*/
export function serializeFieldConditions(conditions: FieldConditions | null): string {
if (!conditions || !conditions.conditions || conditions.conditions.length === 0) {
return ''
}
// 过滤无效条件
const validConditions = conditions.conditions.filter(c =>
c.fieldKey && c.operator
)
if (validConditions.length === 0) {
return ''
}
return JSON.stringify({
logic: conditions.logic,
conditions: validConditions
})
}
/**
* 评估单个条件
*/
function evaluateSingleCondition(
condition: SingleCondition,
fieldValues: Record<string, unknown>
): boolean {
const fieldValue = fieldValues[condition.fieldKey]
const stringValue = fieldValue === null || fieldValue === undefined
? ''
: String(fieldValue).trim()
switch (condition.operator) {
case 'equals':
return stringValue === (condition.value || '')
case 'notEquals':
return stringValue !== (condition.value || '')
case 'contains':
return stringValue.includes(condition.value || '')
case 'notEmpty':
return stringValue !== ''
case 'isEmpty':
return stringValue === ''
default:
return true
}
}
/**
* 评估字段是否应该显示
*/
export function evaluateFieldVisibility(
conditions: FieldConditions | null,
fieldValues: Record<string, unknown>
): boolean {
// 没有条件配置,默认显示
if (!conditions || !conditions.conditions || conditions.conditions.length === 0) {
return true
}
const results = conditions.conditions.map(c =>
evaluateSingleCondition(c, fieldValues)
)
if (conditions.logic === 'OR') {
return results.some(r => r)
} else {
return results.every(r => r)
}
}
export function isFieldVisible(
field: FieldWithConditions,
fieldValues: Record<string, unknown>
): boolean {
return evaluateFieldVisibility(parseFieldConditions(field.conditions), fieldValues)
}
export function getVisibleTemplateFields<T extends FieldWithConditions>(
fields: T[],
fieldValues: Record<string, unknown>
): T[] {
if (!fields.length) {
return []
}
let visibleKeys = new Set<string>()
for (let pass = 0; pass < fields.length; pass += 1) {
const activeValues: Record<string, unknown> = {}
for (const field of fields) {
if (visibleKeys.has(field.key)) {
activeValues[field.key] = fieldValues[field.key]
}
}
const nextVisibleKeys = new Set<string>()
for (const field of fields) {
if (isFieldVisible(field, activeValues)) {
nextVisibleKeys.add(field.key)
}
}
const sameSize = nextVisibleKeys.size === visibleKeys.size
const sameKeys = sameSize && Array.from(nextVisibleKeys).every((key) => visibleKeys.has(key))
visibleKeys = nextVisibleKeys
if (sameKeys) {
break
}
}
return fields.filter((field) => visibleKeys.has(field.key))
}
export function filterVisibleFieldValues(
fields: FieldWithConditions[],
fieldValues: Record<string, unknown>
): Record<string, unknown> {
const visibleKeys = new Set(
getVisibleTemplateFields(fields, fieldValues).map((field) => field.key)
)
return Object.fromEntries(
Object.entries(fieldValues).filter(([key]) => visibleKeys.has(key))
)
}
export function hasFieldValue(value: unknown, type?: string | null): boolean {
if (type === 'checkbox') {
return value !== undefined && value !== null
}
if (value === undefined || value === null) {
return false
}
if (typeof value === 'string') {
return value.trim().length > 0
}
if (Array.isArray(value)) {
return value.length > 0
}
return true
}
/**
* 检测循环依赖
* 返回循环依赖的字段key列表,如果没有循环则返回空数组
*/
export function detectCircularDependencies(
fields: Array<{ key: string; conditions?: string | null }>
): string[] {
const dependencyMap = new Map<string, Set<string>>()
// 构建依赖图
for (const field of fields) {
const conditions = parseFieldConditions(field.conditions)
if (conditions) {
const deps = new Set<string>()
for (const c of conditions.conditions) {
deps.add(c.fieldKey)
}
dependencyMap.set(field.key, deps)
}
}
// DFS检测循环
const visited = new Set<string>()
const recursionStack = new Set<string>()
const circularFields: string[] = []
function dfs(fieldKey: string): boolean {
if (recursionStack.has(fieldKey)) {
return true // 发现循环
}
if (visited.has(fieldKey)) {
return false
}
visited.add(fieldKey)
recursionStack.add(fieldKey)
const deps = dependencyMap.get(fieldKey)
if (deps) {
const depsArray = Array.from(deps)
for (let i = 0; i < depsArray.length; i++) {
if (dfs(depsArray[i])) {
circularFields.push(fieldKey)
return true
}
}
}
recursionStack.delete(fieldKey)
return false
}
for (let i = 0; i < fields.length; i++) {
if (!visited.has(fields[i].key)) {
dfs(fields[i].key)
}
}
return Array.from(new Set(circularFields))
}
/**
* 获取条件运算符的显示名称
*/
export function getOperatorLabel(operator: ConditionOperator): string {
switch (operator) {
case 'equals':
return '等于'
case 'notEquals':
return '不等于'
case 'contains':
return '包含'
case 'notEmpty':
return '非空'
case 'isEmpty':
return '为空'
default:
return operator
}
}
/**
* 获取所有支持的运算符
*/
export function getAllOperators(): Array<{ value: ConditionOperator; label: string; needsValue: boolean }> {
return [
{ value: 'equals', label: '等于', needsValue: true },
{ value: 'notEquals', label: '不等于', needsValue: true },
{ value: 'contains', label: '包含', needsValue: true },
{ value: 'notEmpty', label: '非空', needsValue: false },
{ value: 'isEmpty', label: '为空', needsValue: false },
]
}
/**
* 默认条件配置
*/
export const DEFAULT_FIELD_CONDITIONS: FieldConditions = {
logic: 'AND',
conditions: []
}