330 lines
7.8 KiB
TypeScript
330 lines
7.8 KiB
TypeScript
/**
|
||
* 字段条件显示逻辑
|
||
*
|
||
* 支持的条件类型:
|
||
* - 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: []
|
||
}
|