Browse Source

Update 优化Table的搜索组件(后台接口还需要适配)

Yue 2 days ago
parent
commit
b6d5379ec4

+ 0 - 123
UI/VAP_V3.VUE/docs/auto-row-merge.md

@@ -1,123 +0,0 @@
-# VbTable 自动行合并功能
-
-## 概述
-
-自动行合并功能允许表格组件根据数据内容自动计算并应用行合并,无需手动在数据中添加 `_rowSpan` 字段。
-
-## 使用方法
-
-### 基本用法
-
-```vue
-<VbTable
-  :columns="columns"
-  :data="data"
-  :auto-row-merge="true"
-  :auto-row-merge-fields="['department']"
-/>
-```
-
-### 多层级合并
-
-```vue
-<VbTable
-  :columns="columns"
-  :data="data"
-  :auto-row-merge="true"
-  :auto-row-merge-fields="['region', 'city', 'store']"
-/>
-```
-
-## Props 配置
-
-### autoRowMerge
-
-- **类型**: `boolean`
-- **默认值**: `false`
-- **说明**: 是否启用自动行合并功能
-
-### autoRowMergeFields
-
-- **类型**: `string[]`
-- **默认值**: `[]`
-- **说明**: 需要进行自动合并的字段列表,字段顺序决定层级关系
-
-## 工作原理
-
-1. **数据拷贝**: 组件会深拷贝原始数据,避免修改原始数据源
-2. **分组计算**: 根据指定的字段列表,逐行比较相邻数据的字段值
-3. **层级判断**: 对于多层级合并,会检查所有上级字段是否相同
-4. **自动设置**: 自动计算并设置 `_rowSpan` 值(使用 `rowSpanSuffix` 配置的后缀)
-
-## 示例
-
-### 示例 1: 单字段合并
-
-```javascript
-const columns = [
-  { name: "部门", field: "department" },
-  { name: "姓名", field: "name" },
-  { name: "职位", field: "position" }
-]
-
-const data = [
-  { department: "技术部", name: "张三", position: "工程师" },
-  { department: "技术部", name: "李四", position: "工程师" },
-  { department: "产品部", name: "王五", position: "产品经理" }
-]
-```
-
-使用 `:auto-row-merge-fields="['department']"` 后,相同部门的行会自动合并。
-
-### 示例 2: 多层级合并
-
-```javascript
-const columns = [
-  { name: "区域", field: "region" },
-  { name: "城市", field: "city" },
-  { name: "门店", field: "store" },
-  { name: "商品", field: "product" }
-]
-
-const data = [
-  { region: "华东", city: "上海", store: "门店A", product: "商品1" },
-  { region: "华东", city: "上海", store: "门店A", product: "商品2" },
-  { region: "华东", city: "南京", store: "门店B", product: "商品1" }
-]
-```
-
-使用 `:auto-row-merge-fields="['region', 'city', 'store']"` 后:
-- 相同区域的行会合并
-- 在同一区域内,相同城市的行会合并
-- 在同一城市内,相同门店的行会合并
-
-## 优势
-
-1. **简化代码**: 无需手动计算和设置 rowSpan 值
-2. **自动同步**: 数据变化时自动重新计算合并关系
-3. **易于维护**: 只需关注数据本身,不需要处理合并逻辑
-4. **支持层级**: 天然支持多层级的合并场景
-
-## 注意事项
-
-1. **性能考虑**: 自动合并会深拷贝数据,大数据量时可能影响性能
-2. **覆盖行为**: 如果数据中已存在手动设置的 rowSpan,自动合并会覆盖它
-3. **字段顺序**: 多层级合并时,字段顺序很重要,前面的字段是后面字段的父级
-4. **数据类型**: 确保用于合并的字段值可以进行相等比较
-
-## 与手动合并的对比
-
-| 特性       | 手动合并           | 自动合并       |
-| ---------- | ------------------ | -------------- |
-| 代码复杂度 | 高(需要计算逻辑) | 低(只需配置) |
-| 数据修改   | 需要修改原始数据   | 不修改原始数据 |
-| 维护成本   | 高                 | 低             |
-| 灵活性     | 高(可精确控制)   | 中(基于规则) |
-| 适用场景   | 复杂合并逻辑       | 常规分组合并   |
-
-## 最佳实践
-
-1. **简单场景**: 优先使用自动合并,代码更简洁
-2. **复杂场景**: 如果需要特殊的合并逻辑,可以结合手动和自动合并
-3. **大数据量**: 考虑在服务端预处理数据,或禁用自动合并
-4. **动态数据**: 自动合并特别适合数据频繁变化的场景

+ 115 - 0
UI/VAP_V3.VUE/src/assets/sass/_table.scss

@@ -27,6 +27,7 @@
 		border-radius: 8px;
 		margin-bottom: 16px;
 		transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+		position: relative;
 
 		&:hover {
 			box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
@@ -37,6 +38,120 @@
 			font-weight: 600;
 			font-size: 16px;
 		}
+
+		// 工具栏主行布局
+		.toolbar-main-row {
+			align-items: center;
+		}
+
+		// 右侧工具栏区域
+		.toolbar-right-section {
+			margin-left: auto;
+			display: flex;
+			align-items: center;
+		}
+
+		// ==================== 简化搜索框样式 ====================
+		.simple-search-input {
+			margin-right: 15px;
+		}
+
+		// ==================== 悬浮面板基础样式 ====================
+		.advanced-search-panel,
+		.column-menu-panel {
+			position: absolute;
+			top: calc(100% - 12px);
+			right: 0;
+			max-height: 500px;
+			overflow-y: auto;
+			background: #ffffff;
+			border: 1px solid #e4e7ed;
+			border-radius: 4px;
+			box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+			z-index: 1000;
+			margin-top: 8px;
+			
+			// 过渡动画 - 出现和消失都有滑动效果
+			transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+			transform-origin: top right;
+			opacity: 1;
+			transform: translateY(0) scale(1);
+			pointer-events: auto;
+		}
+
+		// 面板隐藏状态
+		.panel-hidden {
+			opacity: 0;
+			transform: translateY(-10px) scale(0.95);
+			pointer-events: none;
+		}
+
+		// 高级搜索面板特定宽度(通过 inline style 动态设置)
+		.advanced-search-panel {
+			width: 370px;
+		}
+
+		// 列菜单面板固定宽度
+		.column-menu-panel {
+			width: 300px;
+			max-height: 400px;
+		}
+
+		// 面板头部
+		.panel-header {
+			display: flex;
+			justify-content: space-between;
+			align-items: center;
+			padding: 12px 15px;
+			border-bottom: 1px solid #e4e7ed;
+			background: #f5f7fa;
+		}
+
+		// 面板标题
+		.panel-title {
+			font-weight: 600;
+			font-size: 14px;
+			color: #303133;
+		}
+
+		// 关闭按钮
+		.close-btn {
+			padding: 4px;
+			color: #909399;
+			transition: all 0.3s;
+
+			&:hover {
+				color: #f56c6c;
+				background-color: #fef0f0;
+				transform: scale(1.1);
+			}
+		}
+
+		// 面板内容区
+		.panel-content {
+			padding: 15px;
+		}
+
+		// 搜索按钮区域
+		.search-buttons {
+			justify-content: center;
+			gap: 10px;
+			padding-top: 10px;
+			border-top: 1px solid #e4e7ed;
+		}
+
+		// ==================== 表单垂直布局样式 ====================
+		.el-form-vertical :deep(.el-form-item) {
+			display: block;
+			margin-bottom: 15px;
+		}
+
+		// 确保使用 searchFormItems 时每个字段独占一行
+		:deep(.vb-form .el-col) {
+			width: 100% !important;
+			max-width: 100% !important;
+			flex: 0 0 100% !important;
+		}
 	}
 
 	// ==================== 表格主体容器 ====================

+ 13 - 1
UI/VAP_V3.VUE/src/components/table/VbTable.vue

@@ -72,6 +72,12 @@ const props = withDefaults(
 		searchFormItems?: VbFormItem[] | (() => VbFormItem[]) // 搜索表单项配置,支持函数动态返回
 		customSearchFun?: (query: any) => void // 自定义搜索函数
 		resetSearchFormFun?: (query: any) => void // 重置搜索表单函数
+		showSimpleSearch?: boolean // 是否显示简化搜索框
+		simpleSearchPlaceholder?: string // 简化搜索框占位符
+		simpleSearchWidth?: string | number // 简化搜索框宽度
+		searchLabelWidth?: string | number // 高级搜索表单标签宽度
+		closeAdvancedSearchAfterQuery?: boolean // 查询后是否关闭高级搜索面板
+		advancedSearchWidth?: string | number // 高级搜索面板宽度
 
 		/* ==================== 左侧工具栏按钮配置 ==================== */
 		handleBtns?: ToolBtn[] // 操作按钮配置
@@ -157,11 +163,17 @@ const props = withDefaults(
 		method: "get",
 		sortOrder: "asc",
 		showToolbar: true,
-		showSearchBar: true,
+		showSearchBar: false, // 默认隐藏高级搜索
 		showSearchBtn: true,
 		showSearchBtnText: true,
 		searchFormRowItems: () => [],
 		searchFormItems: () => [],
+		showSimpleSearch: true,
+		simpleSearchPlaceholder: "请输入搜索内容",
+		simpleSearchWidth: "200px",
+		searchLabelWidth: "80px",
+		closeAdvancedSearchAfterQuery: true,
+		advancedSearchWidth: "370px",
 		showRightToolbar: true,
 		showRightColumnBtn: true,
 		showRightSearchBtn: true,

+ 9 - 1
UI/VAP_V3.VUE/src/components/table/composables/useTableContext.ts

@@ -39,7 +39,9 @@ export function useTableContext(options: UseTableOptions): TableContext {
 			displayData: state.displayData,
 			innerQueryParams: state.innerQueryParams,
 			toolbarHandleBtns: state.toolbarHandleBtns,
-			rowColumns: state.rowColumns
+			rowColumns: state.rowColumns,
+			advancedSearchVisible: state.advancedSearchVisible,
+			columnMenuVisible: state.columnMenuVisible
 		},
 
 		// 计算属性
@@ -65,6 +67,12 @@ export function useTableContext(options: UseTableOptions): TableContext {
 			showSearchBtnText: props.showSearchBtnText,
 			searchFormRowItems: computed(() => resolveDynamicProp(props.searchFormRowItems || [])),
 			searchFormItems: computed(() => resolveDynamicProp(props.searchFormItems || [])),
+			showSimpleSearch: props.showSimpleSearch,
+			simpleSearchPlaceholder: props.simpleSearchPlaceholder,
+			simpleSearchWidth: props.simpleSearchWidth,
+			searchLabelWidth: props.searchLabelWidth,
+			closeAdvancedSearchAfterQuery: props.closeAdvancedSearchAfterQuery,
+			advancedSearchWidth: props.advancedSearchWidth,
 
 			// 右侧工具栏配置
 			showRightToolbar: props.showRightToolbar,

+ 8 - 0
UI/VAP_V3.VUE/src/components/table/composables/useTableState.ts

@@ -59,6 +59,12 @@ export function useTableState(options: UseTableOptions) {
 	// 工具栏按钮状态(由 useTableToolbar 更新)
 	const toolbarHandleBtns = ref<any[]>([])
 
+	// 高级搜索面板显示状态
+	const advancedSearchVisible = ref<boolean>(false)
+
+	// 列显隐菜单显示状态
+	const columnMenuVisible = ref<boolean>(false)
+
 	// 表格样式状态(由 props 计算得出)
 	const tableStyle = computed(() => {
 		const style: any = {}
@@ -334,6 +340,8 @@ export function useTableState(options: UseTableOptions) {
 		sortParams,
 		expandedNodes,
 		toolbarHandleBtns,
+		advancedSearchVisible,
+		columnMenuVisible,
 
 		// 计算属性
 		rowColumns,

+ 8 - 0
UI/VAP_V3.VUE/src/components/table/models.ts

@@ -96,6 +96,8 @@ export interface TableContext {
 		innerQueryParams: Ref<any>
 		toolbarHandleBtns: Ref<ToolBtn[]>
 		rowColumns: Ref<Header[]>
+		advancedSearchVisible: Ref<boolean> // 高级搜索面板显示状态
+		columnMenuVisible: Ref<boolean> // 列显隐菜单显示状态
 	}
 
 	// ==================== 计算属性 ====================
@@ -121,6 +123,12 @@ export interface TableContext {
 		showSearchBtnText: boolean
 		searchFormRowItems: Ref<VbFormRowItem[]>
 		searchFormItems: Ref<VbFormItem[]>
+		showSimpleSearch: boolean
+		simpleSearchPlaceholder: string
+		simpleSearchWidth: string | number
+		searchLabelWidth: string | number
+		closeAdvancedSearchAfterQuery: boolean
+		advancedSearchWidth: string | number // 高级搜索面板宽度
 
 		// 右侧工具栏配置
 		showRightToolbar: boolean

+ 102 - 0
UI/VAP_V3.VUE/src/components/table/partials/toolbar/TableColumnMenu.vue

@@ -0,0 +1,102 @@
+<script setup lang="ts">
+import { VbUtil } from "@@/vb-dom"
+import { symbolKeys } from "./../../models"
+
+// 从统一上下文获取数据
+const context = inject(symbolKeys.tableContext)!
+
+// 从 context 获取列菜单显示状态
+const columnMenuVisible = context.state.columnMenuVisible
+
+// 获取列数据和 el-tree ref
+const columns = context.state.columns
+const tableBox = context.refs.tableBox
+const elTreeRef = ref()
+
+// 计算可见列 ID
+const visColumnIds = ref<string[]>([])
+
+function getVisibleIds() {
+	const ids: string[] = []
+	function calcVisibleIds(list: any[]) {
+		list.forEach((v) => {
+			if (v.visible === undefined || v.visible) {
+				ids.push(v.field)
+				if (v.children && v.children.length > 0) {
+					calcVisibleIds(v.children)
+				}
+			}
+		})
+	}
+	calcVisibleIds(columns.value)
+	return ids
+}
+
+function getColumnById(id: string, list: any[]): any | null {
+	for (let i = 0; i < list.length; i++) {
+		const item = list[i]
+		if (item.field === id) {
+			return item
+		}
+		if (item.children && item.children.length > 0) {
+			const ret = getColumnById(id, item.children)
+			if (ret) {
+				return ret
+			}
+		}
+	}
+	return null
+}
+
+// 切换列菜单
+const toggleColumnMenu = () => {
+	columnMenuVisible.value = !columnMenuVisible.value
+}
+
+// 列变化处理
+const onColumnChange = (data: any) => {
+	const column = getColumnById(data.field, columns.value)
+	if (column) {
+		column.visible = !(column.visible == undefined || column.visible)
+		VbUtil.EventHandlerUtil.trigger(tableBox.value, "vbtable.columns-change", columns.value)
+	}
+}
+
+// 监听 columns 变化,更新选中状态
+watch(
+	() => columns.value,
+	() => {
+		visColumnIds.value = getVisibleIds()
+		if (elTreeRef.value) {
+			elTreeRef.value.setCheckedKeys(visColumnIds.value)
+		}
+	},
+	{ immediate: true }
+)
+</script>
+
+<template>
+	<div class="column-menu-panel" :class="{ 'panel-hidden': !columnMenuVisible }">
+		<div class="panel-header">
+			<span class="panel-title">显隐列</span>
+			<vb-tooltip class="item" content="关闭" placement="top" :delay="1000">
+				<div
+					class="btn btn-sm btn-light-primary w-25px h-25px p-0 d-flex align-items-center justify-content-center"
+					@click="toggleColumnMenu">
+					<span class="bi bi-x-circle fs-5"></span>
+				</div>
+			</vb-tooltip>
+		</div>
+		<div class="panel-content">
+			<el-tree
+				ref="elTreeRef"
+				:data="columns"
+				show-checkbox
+				node-key="field"
+				:default-checked-keys="visColumnIds"
+				:check-on-click-node="true"
+				:props="{ label: 'name', children: 'children' }"
+				@check="onColumnChange"></el-tree>
+		</div>
+	</div>
+</template>

+ 23 - 112
UI/VAP_V3.VUE/src/components/table/partials/toolbar/TableRightToolbar.vue

@@ -1,14 +1,6 @@
 <script setup lang="ts">
 import { VbUtil, VbComponents } from "@@/vb-dom"
 import { type Header, symbolKeys } from "./../../models"
-const props = withDefaults(
-	defineProps<{
-		searchFormShow: boolean
-	}>(),
-	{
-		searchFormShow: true
-	}
-)
 
 // 从统一上下文获取数据
 const context = inject(symbolKeys.tableContext)!
@@ -23,15 +15,15 @@ const searchFormRowItems = context.config.searchFormRowItems
 const columns = context.state.columns
 const tableBox = context.refs.tableBox
 
+// 从 context 获取高级搜索面板显示状态
+const advancedSearchVisible = context.state.advancedSearchVisible
+// 从 context 获取列显隐菜单显示状态
+const columnMenuVisible = context.state.columnMenuVisible
+
 // 计算是否显示搜索按钮
 const showSearchBtn = computed(() => {
 	return showRightSearchBtn && (!!searchFormItems.value || !!searchFormRowItems.value)
 })
-const columnMenuRef = ref()
-const elTreeRef = ref()
-const emits = defineEmits<(e: "update:searchFormShow", v: boolean) => void>()
-
-const btnColumnId = ref(VbUtil.getUniqueIdWithPrefix("btn-column"))
 
 const style = computed(() => {
 	const ret: any = {}
@@ -42,40 +34,17 @@ const style = computed(() => {
 	return ret
 })
 
-const visColumnIds = ref()
-function getVisibleIds() {
-	const ids: string[] = []
-	function calcVisibleIds(list: Header[]) {
-		list.forEach((v) => {
-			if (v.visible === undefined || v.visible) {
-				ids.push(v.field)
-				if (v.children && v.children.length > 0) {
-					calcVisibleIds(v.children)
-				}
-			}
-		})
+// 搜索
+const toggleSearch = (event?: MouseEvent) => {
+	// 阻止事件冒泡,避免触发 handleClickOutside
+	if (event) {
+		event.stopPropagation()
 	}
-	calcVisibleIds(columns.value)
-	return ids
-}
-function getColumnById(id: string, list: Header[]): Header | null {
-	for (let i = 0; i < list.length; i++) {
-		const item = list[i]
-		if (item.field === id) {
-			return item
-		}
-		if (item.children && item.children.length > 0) {
-			const ret = getColumnById(id, item.children)
-			if (ret) {
-				return ret
-			}
-		}
+	// 如果列菜单是打开的,先关闭它
+	if (columnMenuVisible.value) {
+		columnMenuVisible.value = false
 	}
-	return null
-}
-// 搜索
-const toggleSearch = () => {
-	emits("update:searchFormShow", !props.searchFormShow)
+	advancedSearchVisible.value = !advancedSearchVisible.value
 }
 
 // 刷新
@@ -83,40 +52,14 @@ const refresh = () => {
 	VbUtil.EventHandlerUtil.trigger(tableBox.value, "vbtable.query")
 }
 
-const hideColumnMenu = () => {
-	VbComponents.MenuComponent.hideDropdowns(columnMenuRef.value)
-}
-const onColumnChange = (data: any) => {
-	const column = getColumnById(data.field, columns.value)
-	if (column) {
-		column.visible = !(column.visible == undefined || column.visible)
-		VbUtil.EventHandlerUtil.trigger(tableBox.value, "vbtable.columns-change", columns.value)
+// 切换列显隐菜单
+const toggleColumnMenu = () => {
+	// 如果高级搜索面板是打开的,先关闭它
+	if (advancedSearchVisible.value) {
+		advancedSearchVisible.value = false
 	}
+	columnMenuVisible.value = !columnMenuVisible.value
 }
-onMounted(() => {
-	const el = document.querySelector("#" + btnColumnId.value) as HTMLElement
-	if (!VbComponents.MenuComponent.getInstance(el)) {
-		VbComponents.MenuComponent.createInstance("#" + btnColumnId.value, {
-			dropdown: {
-				hoverTimeout: 200,
-				zindex: 105
-			},
-			accordion: {
-				slideSpeed: 250,
-				expand: false
-			}
-		})
-	}
-
-	visColumnIds.value = getVisibleIds()
-})
-watch(
-	() => columns.value,
-	() => {
-		visColumnIds.value = getVisibleIds()
-		elTreeRef.value.setCheckedKeys(visColumnIds.value)
-	}
-)
 </script>
 <template>
 	<div :style="style">
@@ -124,10 +67,10 @@ watch(
 			<vb-tooltip
 				v-if="showSearchBtn"
 				class="item"
-				:content="searchFormShow ? '隐藏搜索' : '显示搜索'"
+				:content="advancedSearchVisible ? '收起高级搜索' : '高级搜索'"
 				:delay="1000"
 				placement="top">
-				<el-button class="btn btn-light-primary p-3" circle @click="toggleSearch()">
+				<el-button class="btn btn-light-primary p-3" circle @click="toggleSearch">
 					<VbIcon icon-name="magnifier" icon-type="solid"></VbIcon>
 				</el-button>
 			</vb-tooltip>
@@ -142,42 +85,10 @@ watch(
 				placement="top"
 				:delay="1000"
 				v-if="showRightColumnBtn">
-				<el-button
-					class="btn btn-light-primary p-3"
-					circle
-					data-vb-menu-trigger="click"
-					data-vb-menu-static="true"
-					:data-vb-menu-target="`#${btnColumnId}`"
-					data-vb-menu-placement="auto-end">
+				<el-button class="btn btn-light-primary p-3" circle @click="toggleColumnMenu">
 					<VbIcon icon-name="burger-menu-5" icon-type="solid"></VbIcon>
 				</el-button>
 			</vb-tooltip>
 		</el-row>
-		<div
-			ref="columnMenuRef"
-			class="menu menu-sub menu-sub-dropdown menu-column w-auto p-5 px-10"
-			:id="btnColumnId"
-			data-vb-menu="true">
-			<div class="d-flex flex-column position-relative pe-5">
-				<div class="position-absolute" style="top: -5px; right: -20px" @click="hideColumnMenu">
-					<vb-tooltip class="item" content="关闭" placement="top" :delay="1000">
-						<div
-							class="btn btn-sm btn-light-primary w-25px h-25px p-0 d-flex align-items-center justify-content-center">
-							<span class="bi bi-x-circle fs-5"></span>
-						</div>
-					</vb-tooltip>
-				</div>
-
-				<el-tree
-					ref="elTreeRef"
-					:data="columns"
-					show-checkbox
-					node-key="field"
-					:default-checked-keys="visColumnIds"
-					:check-on-click-node="true"
-					:props="{ label: 'name', children: 'children' }"
-					@check="onColumnChange"></el-tree>
-			</div>
-		</div>
 	</div>
 </template>

+ 80 - 35
UI/VAP_V3.VUE/src/components/table/partials/toolbar/TableSearchFrom.vue

@@ -6,7 +6,24 @@ import { symbolKeys } from "./../../models"
 const context = inject(symbolKeys.tableContext)!
 
 // 解构配置和状态
-const { showSearchBtn, showSearchBtnText } = context.config
+const {
+	showSearchBtn,
+	showSearchBtnText,
+	searchLabelWidth,
+	closeAdvancedSearchAfterQuery,
+	advancedSearchWidth
+} = context.config
+
+// 从 context 获取高级搜索面板显示状态
+const advancedSearchVisible = context.state.advancedSearchVisible
+
+// 处理宽度值,如果是数字则添加 px 单位
+const panelWidth = computed(() => {
+	if (typeof advancedSearchWidth === "number") {
+		return `${advancedSearchWidth}px`
+	}
+	return advancedSearchWidth
+})
 
 // searchFormRowItems 和 searchFormItems 现在是响应式计算属性
 const searchFormRowItems = context.config.searchFormRowItems
@@ -18,11 +35,17 @@ const formSlotSuffix = context.constants.formSlotSuffix
 
 const searchFormRef = ref<any>()
 
+// 关闭面板
+function closePanel() {
+	advancedSearchVisible.value = false
+}
+
 const _searchFormItems = computed(() => {
 	if (searchFormItems.value || searchFormRowItems.value) {
 		const items = searchFormItems.value || []
 		items.forEach((v) => {
-			v.span = v.span || (v.field == "searchbtn" ? 1.5 : 5)
+			// 纵向布局:每个字段独占一行
+			v.span = 24
 		})
 		return items as any
 	}
@@ -40,6 +63,9 @@ function formatterFormSlot(v: string | number): string | undefined {
 
 function query() {
 	VbUtil.EventHandlerUtil.trigger(tableBox.value, "vbtable.query")
+	if (closeAdvancedSearchAfterQuery) {
+		setTimeout(() => closePanel(), 300) // 延迟关闭,让用户看到反馈
+	}
 }
 function resetForm() {
 	searchFormRef.value.clearValidate()
@@ -49,40 +75,59 @@ function resetForm() {
 </script>
 
 <template>
-	<div class="w-100 d-flex">
-		<div class="w-100 me-2">
-			<VbForm
-				v-if="searchFormRowItems || searchFormItems"
-				ref="searchFormRef"
-				v-model:data="queryParams"
-				:row-items="searchFormRowItems"
-				:items="_searchFormItems">
-				<template v-for="(_, name) in $slots" #[`${formatterFormSlot(name)}`]>
-					<slot v-if="name.toString().search(formSlotSuffix) == 0" :name="name" />
-				</template>
-			</VbForm>
-			<el-form
-				v-else-if="$slots['table-search-form']"
-				ref="searchFormRef"
-				:inline="true"
-				:model="queryParams"
-				label-width="100px"
-				class="el-row mb-3">
-				<slot name="table-search-form" />
-			</el-form>
+	<div
+		v-if="showSearchBtn"
+		ref="panelRef"
+		class="advanced-search-panel"
+		:class="{ 'panel-hidden': !advancedSearchVisible }"
+		:style="{ width: panelWidth }">
+		<div class="panel-header">
+			<span class="panel-title">高级搜索</span>
+			<vb-tooltip class="item" content="关闭" placement="top" :delay="1000">
+				<div
+					class="btn btn-sm btn-light-primary w-25px h-25px p-0 d-flex align-items-center justify-content-center"
+					@click="closePanel">
+					<span class="bi bi-x-circle fs-5"></span>
+				</div>
+			</vb-tooltip>
 		</div>
-		<div v-if="showSearchBtn" class="mb-3 d-flex">
-			<slot v-if="$slots.searchbtn" name="searchbtn"></slot>
-			<template v-else>
-				<el-button class="btn btn-primary" @click="query">
-					<VbIcon icon-name="magnifier" icon-type="solid"></VbIcon>
-					<span class="ms-2" v-if="showSearchBtnText">搜索</span>
-				</el-button>
-				<el-button class="btn btn-light-primary" @click="resetForm">
-					<VbIcon icon-name="arrows-loop" icon-type="solid"></VbIcon>
-					<span class="ms-2" v-if="showSearchBtnText">重置</span>
-				</el-button>
-			</template>
+
+		<div class="panel-content">
+			<div class="w-100">
+				<VbForm
+					v-if="searchFormRowItems || searchFormItems"
+					ref="searchFormRef"
+					v-model:data="queryParams"
+					:row-items="searchFormRowItems"
+					:items="_searchFormItems"
+					:label-width="searchLabelWidth">
+					<template v-for="(_, name) in $slots" #[`${formatterFormSlot(name)}`]>
+						<slot v-if="name.toString().search(formSlotSuffix) == 0" :name="name" />
+					</template>
+				</VbForm>
+				<el-form
+					v-else-if="$slots['table-search-form']"
+					ref="searchFormRef"
+					:inline="false"
+					:model="queryParams"
+					:label-width="searchLabelWidth"
+					class="el-form-vertical">
+					<slot name="table-search-form" />
+				</el-form>
+			</div>
+			<div class="search-buttons d-flex mt-3">
+				<slot v-if="$slots.searchbtn" name="searchbtn"></slot>
+				<template v-else>
+					<el-button class="btn btn-primary" @click="query">
+						<VbIcon icon-name="magnifier" icon-type="solid"></VbIcon>
+						<span class="ms-2" v-if="showSearchBtnText">搜索</span>
+					</el-button>
+					<el-button class="btn btn-light-primary" @click="resetForm">
+						<VbIcon icon-name="arrows-loop" icon-type="solid"></VbIcon>
+						<span class="ms-2" v-if="showSearchBtnText">重置</span>
+					</el-button>
+				</template>
+			</div>
 		</div>
 	</div>
 </template>

+ 29 - 0
UI/VAP_V3.VUE/src/components/table/partials/toolbar/TableSimpleSearch.vue

@@ -0,0 +1,29 @@
+<script setup lang="ts">
+import { VbUtil } from "@@/vb-dom"
+import { symbolKeys } from "./../../models"
+
+// 从统一上下文获取数据
+const context = inject(symbolKeys.tableContext)!
+
+// 解构配置和状态
+const { showSimpleSearch, simpleSearchPlaceholder, simpleSearchWidth } = context.config
+const queryParams = context.state.innerQueryParams
+const tableBox = context.refs.tableBox
+
+// 处理搜索
+function handleSearch() {
+	VbUtil.EventHandlerUtil.trigger(tableBox.value, "vbtable.query")
+}
+</script>
+
+<template>
+	<el-input
+		v-if="showSimpleSearch"
+		v-model="queryParams.search"
+		:placeholder="simpleSearchPlaceholder"
+		clearable
+		@keyup.enter="handleSearch"
+		@clear="handleSearch"
+		class="simple-search-input"
+		:style="{ width: simpleSearchWidth }" />
+</template>

+ 17 - 17
UI/VAP_V3.VUE/src/components/table/partials/toolbar/TableToolbar.vue

@@ -2,6 +2,8 @@
 import TableRightToolbar from "./TableRightToolbar.vue"
 import TableLeftToolbar from "./TableLeftToolbar.vue"
 import TableSearchFrom from "./TableSearchFrom.vue"
+import TableSimpleSearch from "./TableSimpleSearch.vue"
+import TableColumnMenu from "./TableColumnMenu.vue"
 import type { Header } from "./../../models"
 const props = withDefaults(
 	defineProps<{
@@ -13,9 +15,8 @@ const props = withDefaults(
 )
 const emits = defineEmits<{
 	(e: "columns-change", v: Header[]): void
-	(e: "update:searchFormShow", v: boolean): void
 }>()
-const showSearchBar = ref(props.showSearchBar)
+
 const onColumnsChange = (v: Header[]) => {
 	emits("columns-change", v)
 }
@@ -31,26 +32,25 @@ const onColumnsChange = (v: Header[]) => {
 			{{ tableTitle }}
 		</div>
 
-		<transition name="vb-fade">
-			<template v-if="showSearchBar">
-				<TableSearchFrom>
-					<template v-for="(_, name) in $slots" #[name]>
-						<slot :name="name" />
-					</template>
-				</TableSearchFrom>
-			</template>
-		</transition>
-
-		<el-row :gutter="10">
+		<el-row :gutter="10" class="toolbar-main-row">
 			<TableLeftToolbar>
 				<template #custom-tool-btn>
 					<slot name="custom-tool-btn"></slot>
 				</template>
 			</TableLeftToolbar>
-			<TableRightToolbar
-				v-if="showRightToolbar"
-				v-model:searchFormShow="showSearchBar"
-				@columns-change="onColumnsChange" />
+
+			<div class="toolbar-right-section">
+				<TableSimpleSearch />
+				<TableRightToolbar v-if="showRightToolbar" @columns-change="onColumnsChange" />
+			</div>
 		</el-row>
+
+		<TableSearchFrom>
+			<template v-for="(_, name) in $slots" #[name]>
+				<slot :name="name" />
+			</template>
+		</TableSearchFrom>
+
+		<TableColumnMenu />
 	</div>
 </template>