Phase 5: 实体属性扩展功能

- 数据库层:
  - 新增 entity_attributes 表存储自定义属性
  - 新增 attribute_templates 表管理属性模板
  - 新增 attribute_history 表记录属性变更历史

- 后端 API:
  - GET/POST /api/v1/projects/{id}/attribute-templates - 属性模板管理
  - GET/POST/PUT/DELETE /api/v1/entities/{id}/attributes - 实体属性 CRUD
  - GET /api/v1/entities/{id}/attributes/history - 属性变更历史
  - GET /api/v1/projects/{id}/entities/search-by-attributes - 属性筛选搜索

- 前端 UI:
  - 实体详情面板添加属性展示
  - 属性编辑表单(支持文本、数字、日期、单选、多选)
  - 属性模板管理界面
  - 属性变更历史查看
  - 知识库实体卡片显示属性预览
  - 属性筛选搜索栏
This commit is contained in:
OpenClaw Bot
2026-02-20 00:10:49 +08:00
parent 626fa7e1c0
commit 7b67f3756e
7 changed files with 2209 additions and 27 deletions

View File

@@ -1270,6 +1270,142 @@
0%, 60%, 100% { transform: translateY(0); }
30% { transform: translateY(-10px); }
}
/* Phase 5: Entity Attributes */
.attributes-list {
max-height: 300px;
overflow-y: auto;
}
.attribute-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
background: #0a0a0a;
border-radius: 6px;
margin-bottom: 8px;
}
.attribute-info {
flex: 1;
}
.attribute-name {
font-weight: 500;
color: #e0e0e0;
margin-bottom: 4px;
}
.attribute-value {
color: #00d4ff;
font-size: 0.9rem;
}
.attribute-type {
font-size: 0.7rem;
color: #666;
margin-left: 8px;
}
.attribute-actions {
display: flex;
gap: 8px;
}
.attribute-btn {
background: transparent;
border: 1px solid #333;
color: #888;
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
font-size: 0.75rem;
}
.attribute-btn:hover {
border-color: #00d4ff;
color: #00d4ff;
}
.attribute-btn.delete:hover {
border-color: #ff6b6b;
color: #ff6b6b;
}
.attributes-add-form {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #333;
}
.templates-list {
max-height: 300px;
overflow-y: auto;
}
.template-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
background: #0a0a0a;
border-radius: 6px;
margin-bottom: 8px;
}
.template-info {
flex: 1;
}
.template-name {
font-weight: 500;
color: #e0e0e0;
}
.template-desc {
font-size: 0.8rem;
color: #666;
margin-top: 2px;
}
.history-item {
padding: 12px;
background: #0a0a0a;
border-radius: 6px;
margin-bottom: 8px;
font-size: 0.85rem;
}
.history-header {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
color: #888;
}
.history-change {
display: flex;
align-items: center;
gap: 8px;
}
.history-old {
color: #ff6b6b;
text-decoration: line-through;
}
.history-new {
color: #4ecdc4;
}
.history-arrow {
color: #666;
}
/* Phase 5: Attribute filter in KB */
.attribute-filter-bar {
display: flex;
gap: 12px;
margin-bottom: 16px;
padding: 12px;
background: #141414;
border-radius: 8px;
flex-wrap: wrap;
}
.attribute-filter-item {
display: flex;
align-items: center;
gap: 8px;
}
.attribute-filter-item select,
.attribute-filter-item input {
background: #1a1a1a;
border: 1px solid #333;
color: #e0e0e0;
padding: 6px 10px;
border-radius: 4px;
font-size: 0.85rem;
}
</style>
</head>
<body>
@@ -1441,7 +1577,32 @@
<div class="kb-main">
<!-- Entities Section -->
<div class="kb-section active" id="kbEntitiesSection">
<h3 style="margin-bottom:16px;">所有实体</h3>
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
<h3>所有实体</h3>
<button class="btn btn-small" onclick="showAttributeTemplates()">🏷️ 管理属性模板</button>
</div>
<!-- Attribute Filter Bar -->
<div class="attribute-filter-bar">
<div class="attribute-filter-item">
<label>属性筛选:</label>
<input type="text" id="attrFilterName" placeholder="属性名 (如: 职位)" style="width:120px;">
</div>
<div class="attribute-filter-item">
<select id="attrFilterOp">
<option value="eq">等于</option>
<option value="contains">包含</option>
<option value="gt">大于</option>
<option value="lt">小于</option>
</select>
</div>
<div class="attribute-filter-item">
<input type="text" id="attrFilterValue" placeholder="属性值">
</div>
<button class="btn-icon" onclick="searchByAttributes()">🔍 搜索</button>
<button class="btn-icon" onclick="loadKnowledgeBase()">重置</button>
</div>
<div class="kb-entity-grid" id="kbEntityGrid"></div>
</div>
<!-- Relations Section -->
@@ -1581,6 +1742,114 @@
</div>
</div>
<!-- Entity Attributes Modal -->
<div class="modal-overlay" id="attributesModal">
<div class="modal" style="max-width: 600px;">
<h3 class="modal-header">实体属性</h3>
<div id="attributesContent">
<div class="attributes-list" id="attributesList"></div>
<div class="attributes-add-form" id="attributesAddForm" style="display:none;">
<h4 style="color:#888;font-size:0.9rem;margin:16px 0 12px;">添加新属性</h4>
<div class="form-group">
<label>属性名称</label>
<input type="text" id="attrName" placeholder="如: 职位、部门、年龄">
</div>
<div class="form-group">
<label>属性类型</label>
<select id="attrType" onchange="onAttrTypeChange()">
<option value="text">文本</option>
<option value="number">数字</option>
<option value="date">日期</option>
<option value="select">单选</option>
<option value="multiselect">多选</option>
</select>
</div>
<div class="form-group" id="attrOptionsGroup" style="display:none;">
<label>选项 (用逗号分隔)</label>
<input type="text" id="attrOptions" placeholder="选项1, 选项2, 选项3">
</div>
<div class="form-group">
<label>属性值</label>
<div id="attrValueContainer">
<input type="text" id="attrValue" placeholder="输入属性值">
</div>
</div>
<div class="form-group">
<label>变更原因 (可选)</label>
<input type="text" id="attrChangeReason" placeholder="为什么添加/修改这个属性">
</div>
</div>
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="hideAttributesModal()">关闭</button>
<button class="btn" onclick="toggleAddAttributeForm()" id="toggleAddAttrBtn">添加属性</button>
<button class="btn" onclick="saveAttribute()" id="saveAttrBtn" style="display:none;">保存</button>
</div>
</div>
</div>
<!-- Attribute History Modal -->
<div class="modal-overlay" id="attrHistoryModal">
<div class="modal" style="max-width: 700px;">
<h3 class="modal-header">属性变更历史</h3>
<div id="attrHistoryContent" style="max-height: 400px; overflow-y: auto;">
<p style="color:#666;">加载中...</p>
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="hideAttrHistoryModal()">关闭</button>
</div>
</div>
</div>
<!-- Attribute Templates Modal -->
<div class="modal-overlay" id="attrTemplatesModal">
<div class="modal" style="max-width: 700px;">
<h3 class="modal-header">属性模板管理</h3>
<div id="attrTemplatesContent">
<div class="templates-list" id="templatesList"></div>
<div class="template-form" id="templateForm" style="display:none; margin-top: 20px; padding-top: 20px; border-top: 1px solid #333;">
<h4 style="color:#888;font-size:0.9rem;margin-bottom:12px;">新建模板</h4>
<div class="form-group">
<label>模板名称</label>
<input type="text" id="templateName" placeholder="如: 职位">
</div>
<div class="form-group">
<label>类型</label>
<select id="templateType" onchange="onTemplateTypeChange()">
<option value="text">文本</option>
<option value="number">数字</option>
<option value="date">日期</option>
<option value="select">单选</option>
<option value="multiselect">多选</option>
</select>
</div>
<div class="form-group">
<label>描述 (可选)</label>
<input type="text" id="templateDesc" placeholder="属性的用途说明">
</div>
<div class="form-group" id="templateOptionsGroup" style="display:none;">
<label>选项 (用逗号分隔)</label>
<input type="text" id="templateOptions" placeholder="选项1, 选项2, 选项3">
</div>
<div class="form-group">
<label>
<input type="checkbox" id="templateRequired"> 必填属性
</label>
</div>
<div class="form-group">
<label>默认值 (可选)</label>
<input type="text" id="templateDefault" placeholder="默认值">
</div>
</div>
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="hideAttrTemplatesModal()">关闭</button>
<button class="btn" onclick="toggleTemplateForm()" id="toggleTemplateBtn">新建模板</button>
<button class="btn" onclick="saveTemplate()" id="saveTemplateBtn" style="display:none;">保存模板</button>
</div>
</div>
</div>
<!-- Entity Editor Modal -->
<div class="modal-overlay" id="entityModal">
<div class="modal">
@@ -1691,6 +1960,7 @@
<!-- Context Menu -->
<div class="context-menu" id="contextMenu">
<div class="context-menu-item" onclick="editEntity()">✏️ 编辑实体</div>
<div class="context-menu-item" onclick="showEntityAttributes()">🏷️ 管理属性</div>
<div class="context-menu-item" onclick="showMergeModal()">🔄 合并实体</div>
<div class="context-menu-divider"></div>
<div class="context-menu-item" onclick="createEntityFromSelection()"> 标记为实体</div>