pagetype列表页增加过滤栏
This commit is contained in:
parent
33dd3ffc6e
commit
31430cf6b5
6
.gitignore
vendored
6
.gitignore
vendored
@ -11,17 +11,14 @@ dump.rdb
|
||||
*.rdb
|
||||
redis.conf.bak
|
||||
|
||||
# Jingrow Local 前端
|
||||
# Jingrow 前端
|
||||
node_modules
|
||||
frontend/dist/
|
||||
frontend/node_modules/
|
||||
frontend/.env.local
|
||||
frontend/.env.test
|
||||
frontend/.env.production
|
||||
frontend/public/files/
|
||||
|
||||
# Jingrow Framework 前端
|
||||
apps/jingrow/frontend/public/files/
|
||||
|
||||
# 忽略名为 test 的文件夹
|
||||
test/
|
||||
@ -29,6 +26,7 @@ test/
|
||||
|
||||
|
||||
# 忽略所有 文件夹
|
||||
**/frontend/public/files
|
||||
**/jfile/files/
|
||||
**/output/
|
||||
**/__pycache__/
|
||||
|
||||
429
apps/jingrow/frontend/src/core/components/FilterBar.vue
Normal file
429
apps/jingrow/frontend/src/core/components/FilterBar.vue
Normal file
@ -0,0 +1,429 @@
|
||||
<template>
|
||||
<div class="elegant-filter-bar">
|
||||
<!-- 过滤条件内容 -->
|
||||
<div class="filter-content">
|
||||
<div v-if="filterableFields.length === 0" class="empty-state">
|
||||
<i class="fa fa-filter"></i>
|
||||
<span>{{ t('暂无可过滤的字段') }}</span>
|
||||
</div>
|
||||
|
||||
<div v-else class="filter-row">
|
||||
<div
|
||||
v-for="field in filterableFields"
|
||||
:key="field.fieldname"
|
||||
class="filter-item"
|
||||
:class="{ 'has-value': filters[field.fieldname] }"
|
||||
>
|
||||
<!-- 文本输入框 -->
|
||||
<div v-if="isTextField(field.fieldtype)" class="filter-input">
|
||||
<n-input
|
||||
v-model:value="filters[field.fieldname]"
|
||||
:placeholder="getFieldPlaceholder(field)"
|
||||
clearable
|
||||
size="small"
|
||||
@input="onFilterChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 选择框 -->
|
||||
<div v-else-if="field.fieldtype === 'Select'" class="filter-input">
|
||||
<n-select
|
||||
v-model:value="filters[field.fieldname]"
|
||||
:options="getSelectOptions(field)"
|
||||
:placeholder="getFieldPlaceholder(field)"
|
||||
clearable
|
||||
size="small"
|
||||
@update:value="onFilterChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 多选框 -->
|
||||
<div v-else-if="isMultiSelectField(field.fieldtype)" class="filter-input">
|
||||
<n-select
|
||||
v-model:value="filters[field.fieldname]"
|
||||
:options="getSelectOptions(field)"
|
||||
multiple
|
||||
:placeholder="getFieldPlaceholder(field)"
|
||||
clearable
|
||||
size="small"
|
||||
@update:value="onFilterChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 复选框 -->
|
||||
<div v-else-if="field.fieldtype === 'Check'" class="filter-input">
|
||||
<n-select
|
||||
v-model:value="filters[field.fieldname]"
|
||||
:options="[
|
||||
{ label: t('是'), value: 1 },
|
||||
{ label: t('否'), value: 0 }
|
||||
]"
|
||||
:placeholder="getFieldPlaceholder(field)"
|
||||
clearable
|
||||
size="small"
|
||||
@update:value="onFilterChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 数字输入框 -->
|
||||
<div v-else-if="isNumberField(field.fieldtype)" class="filter-input">
|
||||
<n-input-number
|
||||
v-model:value="filters[field.fieldname]"
|
||||
:placeholder="getFieldPlaceholder(field)"
|
||||
clearable
|
||||
size="small"
|
||||
@update:value="onFilterChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 日期选择器 -->
|
||||
<div v-else-if="isDateField(field.fieldtype)" class="filter-input">
|
||||
<n-date-picker
|
||||
v-model:value="filters[field.fieldname]"
|
||||
:placeholder="getFieldPlaceholder(field)"
|
||||
clearable
|
||||
size="small"
|
||||
@update:value="onFilterChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 其他字段类型使用文本输入 -->
|
||||
<div v-else class="filter-input">
|
||||
<n-input
|
||||
v-model:value="filters[field.fieldname]"
|
||||
:placeholder="getFieldPlaceholder(field)"
|
||||
clearable
|
||||
size="small"
|
||||
@input="onFilterChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="filter-actions">
|
||||
<button
|
||||
v-if="hasActiveFilters"
|
||||
class="action-btn clear-btn"
|
||||
@click="clearAllFilters"
|
||||
:title="t('清除所有过滤条件')"
|
||||
>
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
<button
|
||||
class="action-btn save-btn"
|
||||
@click="showSaveDialog = true"
|
||||
:title="t('保存当前过滤条件')"
|
||||
>
|
||||
<i class="fa fa-bookmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 保存过滤条件对话框 -->
|
||||
<n-modal v-model:show="showSaveDialog">
|
||||
<n-card
|
||||
style="width: 400px"
|
||||
:title="t('保存筛选条件')"
|
||||
:bordered="false"
|
||||
size="huge"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
>
|
||||
<n-form :model="saveForm" ref="saveFormRef">
|
||||
<n-form-item :label="t('过滤器名称')" path="name">
|
||||
<n-input
|
||||
v-model:value="saveForm.name"
|
||||
:placeholder="t('请输入过滤器名称')"
|
||||
size="small"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<n-button @click="showSaveDialog = false" size="small">
|
||||
{{ t('取消') }}
|
||||
</n-button>
|
||||
<n-button type="primary" @click="saveFilter" size="small">
|
||||
{{ t('保存') }}
|
||||
</n-button>
|
||||
</div>
|
||||
</template>
|
||||
</n-card>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { NInput, NSelect, NInputNumber, NDatePicker, NModal, NCard, NForm, NFormItem, NButton, useMessage } from 'naive-ui'
|
||||
import { t } from '@/shared/i18n'
|
||||
|
||||
interface FilterField {
|
||||
fieldname: string
|
||||
label: string
|
||||
fieldtype: string
|
||||
options?: string
|
||||
in_list_view?: boolean
|
||||
in_standard_filter?: boolean
|
||||
}
|
||||
|
||||
interface Props {
|
||||
fields: FilterField[]
|
||||
modelValue: Record<string, any>
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: Record<string, any>): void
|
||||
(e: 'filter-change', filters: Record<string, any>): void
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const message = useMessage()
|
||||
const showSaveDialog = ref(false)
|
||||
const saveForm = ref({ name: '' })
|
||||
const saveFormRef = ref()
|
||||
|
||||
// 过滤条件
|
||||
const filters = ref<Record<string, any>>({ ...props.modelValue })
|
||||
|
||||
// 可过滤的字段:排除分隔符字段,只显示 in_standard_filter 的字段
|
||||
const filterableFields = computed(() => {
|
||||
const isFilterable = (f: FilterField) => !['Section Break', 'Column Break', 'Tab Break'].includes(f.fieldtype)
|
||||
const filterable = props.fields.filter(isFilterable)
|
||||
|
||||
// 只显示 in_standard_filter 的字段
|
||||
const standardFilterFields = filterable.filter(f => f.in_standard_filter)
|
||||
|
||||
return standardFilterFields.slice(0, 8) // 限制最多8个过滤字段
|
||||
})
|
||||
|
||||
// 活跃过滤条件数量
|
||||
const activeFilterCount = computed(() => {
|
||||
return Object.values(filters.value).filter(value =>
|
||||
value !== null && value !== undefined && value !== '' &&
|
||||
!(Array.isArray(value) && value.length === 0)
|
||||
).length
|
||||
})
|
||||
|
||||
// 是否有活跃的过滤条件
|
||||
const hasActiveFilters = computed(() => activeFilterCount.value > 0)
|
||||
|
||||
// 字段类型判断函数
|
||||
function isTextField(fieldtype: string): boolean {
|
||||
return ['Data', 'Text', 'Long Text', 'Comment'].includes(fieldtype)
|
||||
}
|
||||
|
||||
function isMultiSelectField(fieldtype: string): boolean {
|
||||
return ['MultiSelect', 'MultiSelect Pills', 'MultiSelect List'].includes(fieldtype)
|
||||
}
|
||||
|
||||
function isNumberField(fieldtype: string): boolean {
|
||||
return ['Int', 'Float', 'Currency', 'Percent'].includes(fieldtype)
|
||||
}
|
||||
|
||||
function isDateField(fieldtype: string): boolean {
|
||||
return ['Date', 'Datetime'].includes(fieldtype)
|
||||
}
|
||||
|
||||
// 获取字段占位符文本
|
||||
function getFieldPlaceholder(field: FilterField): string {
|
||||
const label = field.label || field.fieldname
|
||||
return t(label)
|
||||
}
|
||||
|
||||
// 获取选择框选项
|
||||
function getSelectOptions(field: FilterField) {
|
||||
if (!field.options) return []
|
||||
const options = field.options.split('\n').filter((s: string) => s.trim() !== '')
|
||||
return options.map((opt: string) => ({ label: t(opt), value: opt }))
|
||||
}
|
||||
|
||||
// 过滤条件变化
|
||||
function onFilterChange() {
|
||||
emit('update:modelValue', { ...filters.value })
|
||||
emit('filter-change', { ...filters.value })
|
||||
}
|
||||
|
||||
// 清除所有过滤条件
|
||||
function clearAllFilters() {
|
||||
filters.value = {}
|
||||
onFilterChange()
|
||||
}
|
||||
|
||||
// 保存过滤条件
|
||||
function saveFilter() {
|
||||
if (!saveForm.value.name.trim()) {
|
||||
message.warning(t('请输入过滤器名称'))
|
||||
return
|
||||
}
|
||||
|
||||
// 这里可以保存到本地存储或发送到服务器
|
||||
const savedFilters = JSON.parse(localStorage.getItem('savedFilters') || '{}')
|
||||
savedFilters[saveForm.value.name] = { ...filters.value }
|
||||
localStorage.setItem('savedFilters', JSON.stringify(savedFilters))
|
||||
|
||||
message.success(t('过滤条件已保存'))
|
||||
showSaveDialog.value = false
|
||||
saveForm.value.name = ''
|
||||
}
|
||||
|
||||
// 监听外部传入的过滤条件变化
|
||||
watch(() => props.modelValue, (newValue) => {
|
||||
filters.value = { ...newValue }
|
||||
}, { deep: true })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.elegant-filter-bar {
|
||||
background: white;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #6b7280;
|
||||
font-size: 13px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.empty-state i {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 8px;
|
||||
background: #f9fafb;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.filter-item.has-value {
|
||||
background: #f0f9ff;
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
.filter-item:hover {
|
||||
border-color: #cbd5e1;
|
||||
}
|
||||
|
||||
.filter-input {
|
||||
min-width: 120px;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.filter-input :deep(.n-input),
|
||||
.filter-input :deep(.n-select),
|
||||
.filter-input :deep(.n-date-picker),
|
||||
.filter-input :deep(.n-input-number) {
|
||||
border: none;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.filter-input :deep(.n-input:focus),
|
||||
.filter-input :deep(.n-select:focus),
|
||||
.filter-input :deep(.n-date-picker:focus),
|
||||
.filter-input :deep(.n-input-number:focus) {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.filter-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 4px;
|
||||
background: white;
|
||||
color: #6b7280;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 10px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background: #f3f4f6;
|
||||
border-color: #9ca3af;
|
||||
}
|
||||
|
||||
.clear-btn:hover {
|
||||
background: #fef2f2;
|
||||
border-color: #fca5a5;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.save-btn:hover {
|
||||
background: #f0f9ff;
|
||||
border-color: #93c5fd;
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.filter-row {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.filter-input {
|
||||
min-width: 150px;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.filter-actions {
|
||||
margin-left: 0;
|
||||
justify-content: flex-end;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 确保输入框完全显示 */
|
||||
.filter-input :deep(.n-input),
|
||||
.filter-input :deep(.n-select),
|
||||
.filter-input :deep(.n-date-picker),
|
||||
.filter-input :deep(.n-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -11,6 +11,21 @@
|
||||
<div class="filters">
|
||||
<n-input v-model:value="searchQuery" :placeholder="t('Search')" clearable style="width: 200px" />
|
||||
</div>
|
||||
<!-- 活跃过滤条件标签 -->
|
||||
<div v-if="activeFilterTags.length > 0" class="active-filters">
|
||||
<div class="filter-tags">
|
||||
<span v-for="tag in activeFilterTags" :key="tag.field" class="filter-tag">
|
||||
<span class="tag-content">
|
||||
<span class="tag-label">{{ tag.label }}</span>
|
||||
<span class="tag-separator">:</span>
|
||||
<span class="tag-value">{{ tag.value }}</span>
|
||||
</span>
|
||||
<button @click="removeFilter(tag.field)" class="remove-filter-btn">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="view-toggle">
|
||||
<button
|
||||
class="toggle-btn"
|
||||
@ -54,6 +69,14 @@
|
||||
</div>
|
||||
|
||||
<div class="page-content">
|
||||
<!-- 过滤栏 -->
|
||||
<FilterBar
|
||||
v-if="!isSinglePage && metaFields.length > 0"
|
||||
:fields="metaFields"
|
||||
v-model="filters"
|
||||
@filter-change="onFilterChange"
|
||||
/>
|
||||
|
||||
<div v-if="loading" class="loading">
|
||||
<i class="fa fa-spinner fa-spin"></i> {{ t('Loading...') }}
|
||||
</div>
|
||||
@ -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<Record<string, any>>({})
|
||||
|
||||
// 活跃过滤条件标签
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
/* 简洁的活跃过滤条件标签样式 */
|
||||
.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;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
@ -833,5 +833,17 @@
|
||||
"Name Z-A": "名称 Z-A",
|
||||
"Most Popular": "最受欢迎",
|
||||
"Applications": "应用列表",
|
||||
"applications": "个应用"
|
||||
"applications": "个应用",
|
||||
|
||||
"是": "是",
|
||||
"否": "否",
|
||||
"暂无可过滤的字段": "暂无可过滤的字段",
|
||||
"清除所有过滤条件": "清除所有过滤条件",
|
||||
"清除": "清除",
|
||||
"保存当前过滤条件": "保存当前过滤条件",
|
||||
"保存": "保存",
|
||||
"保存筛选条件": "保存筛选条件",
|
||||
"过滤器名称": "过滤器名称",
|
||||
"请输入过滤器名称": "请输入过滤器名称",
|
||||
"取消": "取消"
|
||||
}
|
||||
|
||||
@ -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
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user