Browse Source

perf: 优化useVbenForm样式 (#6611)

* perf(style): 优化useVbenForm垂直布局 actions 样式

* perf(style): 优化useVbenForm actions 布局样式

- 操作按钮组显示位置
```
actionPosition?: 'center' | 'left' | 'right';
```
- 操作按钮组的样式
```
actionType?: 'block' | 'inline'
inline: 行类显示,block: 新一行单独显示
```

* perf: 优化useVbenForm actions 布局样式

删除 actionType
增加 actionLayout
- actionLayout?: 'inline' | 'newLine' | 'rowEnd';
- newLine: 在新行显示。rowEnd: 在行内显示,靠右对齐(默认)。inline: 使用grid默认样式
- 删除无用代码 queryFormStyle

* perf: 优化useVbenForm使用案例

* perf: 优化form组件样式

去掉padding,改为gap

* docs: update vben-form.md

* fix: 修复FormMessage位置

* perf: Avoid direct mutation of props object.

-  props.actionLayout = props.actionLayout || 'rowEnd';
-  props.actionPosition = props.actionPosition || 'right';
+  const actionLayout = props.actionLayout || 'rowEnd';
+  const actionPosition = props.actionPosition || 'right';

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix: 修复 wrapperClass 权重

* fix: 全局搜索结果不匹配 #6603

* fix: 避免FormMessage溢出

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
xueyang 1 tháng trước cách đây
mục cha
commit
9fc594434f

+ 2 - 0
docs/src/components/common-ui/vben-form.md

@@ -308,6 +308,8 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
 | showCollapseButton | 是否显示折叠按钮 | `boolean` | `false` |
 | wrapperClass | 表单的布局,基于tailwindcss | `any` | - |
 | actionWrapperClass | 表单操作区域class | `any` | - |
+| actionLayout | 表单操作按钮位置 | `'newLine' \| 'rowEnd' \| 'inline'` | `rowEnd` |
+| actionPosition | 表单操作按钮对齐方式 | `'left' \| 'center' \| 'right'` | `right` |
 | handleReset | 表单重置回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
 | handleSubmit | 表单提交回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
 | handleValuesChange | 表单值变化回调 | `(values: Record<string, any>, fieldsChanged: string[]) => void` | - |

+ 48 - 25
packages/@core/ui-kit/form-ui/src/components/form-actions.vue

@@ -34,17 +34,6 @@ const submitButtonOptions = computed(() => {
 //   return !!unref(rootProps).showCollapseButton;
 // });
 
-const queryFormStyle = computed(() => {
-  if (!unref(rootProps).actionWrapperClass) {
-    return {
-      'grid-column': `-2 / -1`,
-      marginLeft: 'auto',
-    };
-  }
-
-  return {};
-});
-
 async function handleSubmit(e: Event) {
   e?.preventDefault();
   e?.stopPropagation();
@@ -86,22 +75,59 @@ watch(
   },
 );
 
+const actionWrapperClass = computed(() => {
+  const props = unref(rootProps);
+  const actionLayout = props.actionLayout || 'rowEnd';
+  const actionPosition = props.actionPosition || 'right';
+
+  const cls = [
+    'flex',
+    'w-full',
+    'items-center',
+    'gap-3',
+    props.compact ? 'pb-2' : 'pb-4',
+    props.layout === 'vertical' ? 'self-end' : 'self-center',
+    props.actionWrapperClass,
+  ];
+
+  switch (actionLayout) {
+    case 'newLine': {
+      cls.push('col-span-full');
+      break;
+    }
+    case 'rowEnd': {
+      cls.push('col-[-2/-1]');
+      break;
+    }
+    // 'inline' 不需要额外类名,保持默认
+  }
+
+  switch (actionPosition) {
+    case 'center': {
+      cls.push('justify-center');
+      break;
+    }
+    case 'left': {
+      cls.push('justify-start');
+      break;
+    }
+    default: {
+      // case 'right': 默认右对齐
+      cls.push('justify-end');
+      break;
+    }
+  }
+
+  return cls.join(' ');
+});
+
 defineExpose({
   handleReset,
   handleSubmit,
 });
 </script>
 <template>
-  <div
-    :class="
-      cn(
-        'col-span-full w-full text-right',
-        rootProps.compact ? 'pb-2' : 'pb-6',
-        rootProps.actionWrapperClass,
-      )
-    "
-    :style="queryFormStyle"
-  >
+  <div :class="cn(actionWrapperClass)">
     <template v-if="rootProps.actionButtonsReverse">
       <!-- 提交按钮前 -->
       <slot name="submit-before"></slot>
@@ -109,7 +135,6 @@ defineExpose({
       <component
         :is="COMPONENT_MAP.PrimaryButton"
         v-if="submitButtonOptions.show"
-        class="ml-3"
         type="button"
         @click="handleSubmit"
         v-bind="submitButtonOptions"
@@ -124,7 +149,6 @@ defineExpose({
     <component
       :is="COMPONENT_MAP.DefaultButton"
       v-if="resetButtonOptions.show"
-      class="ml-3"
       type="button"
       @click="handleReset"
       v-bind="resetButtonOptions"
@@ -139,7 +163,6 @@ defineExpose({
       <component
         :is="COMPONENT_MAP.PrimaryButton"
         v-if="submitButtonOptions.show"
-        class="ml-3"
         type="button"
         @click="handleSubmit"
         v-bind="submitButtonOptions"
@@ -152,9 +175,9 @@ defineExpose({
     <slot name="expand-before"></slot>
 
     <VbenExpandableArrow
+      class="ml-[-0.3em]"
       v-if="rootProps.showCollapseButton"
       v-model:model-value="collapsed"
-      class="ml-2"
     >
       <span>{{ collapsed ? $t('expand') : $t('collapse') }}</span>
     </VbenExpandableArrow>

+ 2 - 2
packages/@core/ui-kit/form-ui/src/form-render/form-field.vue

@@ -295,7 +295,7 @@ onUnmounted(() => {
         'form-is-required': shouldRequired,
         'flex-col': isVertical,
         'flex-row items-center': !isVertical,
-        'pb-6': !compact,
+        'pb-4': !compact,
         'pb-2': compact,
       }"
       class="relative flex"
@@ -386,7 +386,7 @@ onUnmounted(() => {
         </div>
 
         <Transition name="slide-up" v-if="!compact">
-          <FormMessage class="absolute bottom-1" />
+          <FormMessage class="absolute" />
         </Transition>
       </div>
     </FormItem>

+ 10 - 0
packages/@core/ui-kit/form-ui/src/form-render/form.vue

@@ -41,6 +41,16 @@ const emits = defineEmits<{
   submit: [event: any];
 }>();
 
+const wrapperClass = computed(() => {
+  const cls = ['flex flex-col'];
+  if (props.layout === 'vertical') {
+    cls.push(props.compact ? 'gap-x-2' : 'gap-x-4');
+  } else {
+    cls.push('gap-2');
+  }
+  return cn(...cls, props.wrapperClass);
+});
+
 provideFormRenderProps(props);
 
 const { isCalculated, keepFormItemIndex, wrapperRef } = useExpandable(props);

+ 9 - 0
packages/@core/ui-kit/form-ui/src/types.ts

@@ -354,6 +354,15 @@ export interface VbenFormProps<
    * 操作按钮是否反转(提交按钮前置)
    */
   actionButtonsReverse?: boolean;
+  /**
+   * 操作按钮组的样式
+   * newLine: 在新行显示。rowEnd: 在行内显示,靠右对齐(默认)。inline: 使用grid默认样式
+   */
+  actionLayout?: 'inline' | 'newLine' | 'rowEnd';
+  /**
+   * 操作按钮组显示位置,默认靠右显示
+   */
+  actionPosition?: 'center' | 'left' | 'right';
   /**
    * 表单操作区域class
    */

+ 1 - 1
packages/effects/layouts/src/widgets/global-search/search-panel.vue

@@ -98,7 +98,7 @@ async function handleEnter() {
   }
   const to = result[index];
   if (to) {
-    searchHistory.value.push(to);
+    searchHistory.value = uniqueByField([...searchHistory.value, to], 'path');
     handleClose();
     await nextTick();
     if (isHttpUrl(to.path)) {

+ 69 - 0
playground/src/views/examples/form/query.vue

@@ -125,6 +125,70 @@ const [QueryForm1] = useVbenForm({
   wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
 });
 
+const [QueryForm2] = useVbenForm({
+  // 操作按钮组 newLine: 在新行显示。rowEnd: 在行内显示,靠右对齐(默认)。inline: 使用grid默认样式
+  actionLayout: 'newLine',
+  actionPosition: 'left', // 操作按钮组在左侧显示
+  // 默认折叠
+  collapsed: true,
+  collapsedRows: 3,
+  // 所有表单项共用,可单独在表单内覆盖
+  commonConfig: {
+    // 所有表单项
+    componentProps: {
+      class: 'w-full',
+    },
+  },
+  // 提交函数
+  handleSubmit: onSubmit,
+  // 垂直布局,label和input在不同行,值为vertical
+  // 水平布局,label和input在同一行
+  layout: 'vertical',
+  schema: [
+    {
+      // 组件需要在 #/adapter.ts内注册,并加上类型
+      component: 'Input',
+      // 对应组件的参数
+      componentProps: {
+        placeholder: '请输入用户名',
+      },
+      // 字段名
+      fieldName: 'username',
+      // 界面显示的label
+      label: '字符串',
+    },
+    {
+      component: 'InputPassword',
+      componentProps: {
+        placeholder: '请输入密码',
+      },
+      fieldName: 'password',
+      label: '密码',
+    },
+    {
+      component: 'InputNumber',
+      componentProps: {
+        placeholder: '请输入',
+      },
+      fieldName: 'number',
+      label: '数字(带后缀)',
+      suffix: () => '¥',
+    },
+    {
+      component: 'DatePicker',
+      fieldName: 'datePicker',
+      label: '日期选择框',
+    },
+  ],
+  // 是否可展开
+  showCollapseButton: true,
+  submitButtonOptions: {
+    content: '查询',
+  },
+  // 大屏一行显示3个,中屏一行显示2个,小屏一行显示1个
+  wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
+});
+
 function onSubmit(values: Record<string, any>) {
   message.success({
     content: `form values: ${JSON.stringify(values)}`,
@@ -140,6 +204,11 @@ function onSubmit(values: Record<string, any>) {
     <Card class="mb-5" title="查询表单,默认展开">
       <QueryForm />
     </Card>
+
+    <Card class="mb-5" title="查询表单,默认展开,垂直布局">
+      <QueryForm2 />
+    </Card>
+
     <Card title="查询表单,默认折叠,折叠时保留2行">
       <QueryForm1 />
     </Card>