+
+
+
{{ t('Loading...') }}
@@ -174,6 +197,7 @@ import { get_session_api_headers } from '@/shared/api/auth'
import { usePageTypeSlug } from '@/shared/utils/slug'
import { isSinglePageType } from '@/shared/utils/pagetype'
import SinglePageDetail from './SinglePageDetail.vue'
+import FilterBar from '@/core/components/FilterBar.vue'
const route = useRoute()
const router = useRouter()
@@ -201,6 +225,49 @@ const viewMode = ref<'card' | 'list'>(
(localStorage.getItem(`genericListViewMode:${entity.value}`) as 'card' | 'list') || 'list'
)
+// 过滤条件
+const filters = ref
>({})
+
+// 活跃过滤条件标签
+const activeFilterTags = computed(() => {
+ const tags: Array<{field: string, label: string, value: string}> = []
+ Object.entries(filters.value).forEach(([fieldName, value]) => {
+ if (value !== null && value !== undefined && value !== '' &&
+ !(Array.isArray(value) && value.length === 0)) {
+ const field = metaFields.value.find(f => f.fieldname === fieldName)
+ const label = field?.label || fieldName
+ let displayValue = value
+
+ if (Array.isArray(value)) {
+ displayValue = value.join(', ')
+ } else if (field?.fieldtype === 'Check') {
+ displayValue = value === 1 ? t('是') : t('否')
+ } else if (field?.options) {
+ // 对于有选项的字段,显示选项标签而不是值
+ const options = field.options.split('\n').filter((s: string) => s.trim() !== '')
+ if (options.includes(value)) {
+ displayValue = t(value)
+ }
+ }
+
+ tags.push({ field: fieldName, label, value: displayValue })
+ }
+ })
+ return tags
+})
+
+// 移除单个过滤条件
+function removeFilter(fieldName: string) {
+ filters.value[fieldName] = null
+ onFilterChange()
+}
+
+// 监听过滤条件变化
+function onFilterChange() {
+ page.value = 1 // 重置到第一页
+ loadData()
+}
+
const displayColumns = computed(() => {
// 生成列表列:优先 in_list_view,并排除分隔类
const isDisplayable = (f: any) => !['Section Break', 'Column Break', 'Tab Break'].includes(f.fieldtype)
@@ -243,13 +310,36 @@ async function loadData() {
const listUrl = `/api/data/${encodeURIComponent(entity.value)}`
// 仅请求所需字段,避免接口默认不返回
const fieldNames = displayColumns.value.map((c: any) => c.key).filter((k: string) => k && k !== 'actions')
+
+ // 构建过滤参数
+ const params: any = {
+ fields: JSON.stringify(fieldNames),
+ limit_start: (page.value - 1) * pageSize.value,
+ limit_page_length: pageSize.value,
+ order_by: 'modified desc'
+ }
+
+ // 添加过滤条件
+ const filterConditions: any[] = []
+ Object.entries(filters.value).forEach(([fieldName, value]) => {
+ if (value !== null && value !== undefined && value !== '' &&
+ !(Array.isArray(value) && value.length === 0)) {
+ if (Array.isArray(value)) {
+ // 多选字段使用 in 操作符
+ filterConditions.push([fieldName, 'in', value])
+ } else {
+ // 单选字段使用 = 操作符
+ filterConditions.push([fieldName, '=', value])
+ }
+ }
+ })
+
+ if (filterConditions.length > 0) {
+ params.filters = JSON.stringify(filterConditions)
+ }
+
const res = await axios.get(listUrl, {
- params: {
- fields: JSON.stringify(fieldNames),
- limit_start: (page.value - 1) * pageSize.value,
- limit_page_length: pageSize.value,
- order_by: 'modified desc'
- },
+ params,
headers: get_session_api_headers(), withCredentials: true
})
rows.value = res.data?.data || []
@@ -294,6 +384,7 @@ watch(() => route.params.entity, async (newEntity, oldEntity) => {
page.value = 1
searchQuery.value = ''
selectedKeys.value = []
+ filters.value = {} // 重置过滤条件
// 重新加载元数据和数据
await loadMeta()
// 只有在非单页模式下才加载列表数据
@@ -610,6 +701,74 @@ function formatDisplayValue(value: any, fieldName: string) {
text-overflow: ellipsis;
white-space: nowrap;
}
-
+ /* 简洁的活跃过滤条件标签样式 */
+.active-filters {
+ margin-right: 12px;
+}
+
+.filter-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+}
+
+.filter-tag {
+ display: inline-flex;
+ align-items: center;
+ background: #3b82f6;
+ color: white;
+ border-radius: 3px;
+ font-size: 11px;
+ font-weight: 500;
+ overflow: hidden;
+ transition: all 0.2s ease;
+}
+
+.filter-tag:hover {
+ background: #2563eb;
+}
+
+.tag-content {
+ display: flex;
+ align-items: center;
+ padding: 2px 6px;
+}
+
+.tag-label {
+ font-weight: 600;
+}
+
+.tag-separator {
+ margin: 0 2px;
+ opacity: 0.8;
+}
+
+.tag-value {
+ opacity: 0.9;
+}
+
+.remove-filter-btn {
+ background: rgba(255, 255, 255, 0.2);
+ border: none;
+ color: white;
+ cursor: pointer;
+ padding: 0;
+ width: 14px;
+ height: 14px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 0 3px 3px 0;
+ transition: all 0.2s ease;
+}
+
+.remove-filter-btn:hover {
+ background: rgba(255, 255, 255, 0.3);
+}
+
+.remove-filter-btn i {
+ font-size: 7px;
+}
+
diff --git a/apps/jingrow/frontend/src/locales/zh-CN.json b/apps/jingrow/frontend/src/locales/zh-CN.json
index e73f259..1201648 100644
--- a/apps/jingrow/frontend/src/locales/zh-CN.json
+++ b/apps/jingrow/frontend/src/locales/zh-CN.json
@@ -833,5 +833,17 @@
"Name Z-A": "名称 Z-A",
"Most Popular": "最受欢迎",
"Applications": "应用列表",
- "applications": "个应用"
+ "applications": "个应用",
+
+ "是": "是",
+ "否": "否",
+ "暂无可过滤的字段": "暂无可过滤的字段",
+ "清除所有过滤条件": "清除所有过滤条件",
+ "清除": "清除",
+ "保存当前过滤条件": "保存当前过滤条件",
+ "保存": "保存",
+ "保存筛选条件": "保存筛选条件",
+ "过滤器名称": "过滤器名称",
+ "请输入过滤器名称": "请输入过滤器名称",
+ "取消": "取消"
}
diff --git a/apps/jingrow/jingrow/ai/pagetype/local_ai_agent/local_ai_agent.json b/apps/jingrow/jingrow/ai/pagetype/local_ai_agent/local_ai_agent.json
index 7c38cde..696467d 100644
--- a/apps/jingrow/jingrow/ai/pagetype/local_ai_agent/local_ai_agent.json
+++ b/apps/jingrow/jingrow/ai/pagetype/local_ai_agent/local_ai_agent.json
@@ -46,13 +46,15 @@
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
- "label": "Status",
+ "in_standard_filter": 1,
+ "label": "状态",
"options": "\n草稿\n待执行\n进行中\n未完成\n已完成"
},
{
"default": "1",
"fieldname": "enabled",
"fieldtype": "Check",
+ "in_standard_filter": 1,
"label": "已启用"
},
{
@@ -114,6 +116,7 @@
"fieldtype": "Data",
"in_global_search": 1,
"in_list_view": 1,
+ "in_standard_filter": 1,
"label": "智能体名称",
"reqd": 1
},