DESKTOP-USV654P\pc 8 bulan lalu
induk
melakukan
bc0007287d

+ 15 - 13
apps/web-baicai/src/views/form/design/components/control-config.vue

@@ -8,7 +8,15 @@ import { useFormDesignState } from '../hooks/useFormDesignState';
 const { formConfig } = useFormDesignState();
 
 const handleUpdateConfig = (record: any) => {
-  formConfig.value.currentItem = { ...formConfig.value.currentItem, ...record };
+  const findIndex = formConfig.value.schemas.findIndex(
+    (item) => item.key === formConfig.value.selectSchema?.key,
+  );
+  const data = {
+    ...formConfig.value.selectSchema,
+    ...record,
+  };
+  formConfig.value.selectSchema = data;
+  formConfig.value.schemas[findIndex] = data;
 };
 
 const [Form, { setValues, resetForm }] = useVbenForm({
@@ -184,25 +192,19 @@ const [Form, { setValues, resetForm }] = useVbenForm({
 });
 
 watch(
-  () => formConfig.value,
-  () => {
-    if (formConfig.value.currentItem) {
+  () => formConfig.value.selectSchema,
+  (value) => {
+    if (value?.key) {
       resetForm();
-      setValues({ ...formConfig.value.currentItem });
-      // formConfig.value.currentItem.itemProps =
-      //   formConfig.value.currentItem.itemProps || {};
-      // formConfig.value.currentItem.itemProps.labelCol =
-      //   formConfig.value.currentItem.itemProps.labelCol || {};
-      // formConfig.value.currentItem.itemProps.wrapperCol =
-      //   formConfig.value.currentItem.itemProps.wrapperCol || {};
+      setValues({ ...formConfig.value.selectSchema });
     }
   },
-  { deep: true, immediate: true },
+  { deep: true, immediate: false },
 );
 </script>
 <template>
   <div>
-    <div v-if="!formConfig.currentItem?.key" class="form-edit-empty">
+    <div v-if="!formConfig.selectSchema?.key" class="form-edit-empty">
       未选择控件
     </div>
     <Form v-else />

+ 25 - 69
apps/web-baicai/src/views/form/design/components/form-edit-item.vue → apps/web-baicai/src/views/form/design/components/form/form-item/index.vue

@@ -1,30 +1,31 @@
 <script lang="ts" setup>
 import type { PropType } from 'vue';
 
-import type { IVFormComponent } from '../types';
+import type { IVFormComponent } from '../../../types';
 
-import { onMounted } from 'vue';
+import { computed } from 'vue';
+
+import { cn } from '@vben/utils';
 
 import { Form, Input, InputNumber } from 'ant-design-vue';
 
 import { Icon } from '#/components/icon';
 
-import { useFormDesignState } from '../hooks/useFormDesignState';
-import { remove } from '../utils';
+import { useFormDesignState } from '../../../hooks/useFormDesignState';
+import { remove } from '../../../utils';
 
-const props = defineProps({
+defineProps({
   schema: {
     type: Object as PropType<IVFormComponent>,
     required: true,
   },
-  formConfigSelect: {
-    type: Object as PropType<IVFormComponent>,
-    required: true,
-  },
 });
 
 const { formConfig, formDesignMethods } = useFormDesignState();
 
+const selectSchema = computed(() => {
+  return formConfig.value.selectSchema;
+});
 /**
  * 删除当前项
  */
@@ -33,7 +34,7 @@ const handleDelete = () => {
     schemas.some((formItem, index) => {
       const { key } = formItem;
 
-      if (key === props.formConfigSelect.key) {
+      if (key === selectSchema.value?.key) {
         let params: IVFormComponent;
         if (schemas.length === 1) {
           params = { component: '' } as IVFormComponent;
@@ -43,14 +44,7 @@ const handleDelete = () => {
               ? (schemas[index + 1] as IVFormComponent)
               : (schemas[index - 1] as IVFormComponent);
         }
-
-        // const params =
-        //   schemas.length === 1
-        //     ? ({ component: '' } as IVFormComponent)
-        //     : schemas.length - 1 > index
-        //       ? (schemas[index + 1] as IVFormComponent)
-        //       : (schemas[index - 1] as IVFormComponent);
-        formDesignMethods.handleSetSelectItem(params);
+        formDesignMethods.setSelectSchema(params);
         remove(schemas, index);
         return true;
       }
@@ -59,22 +53,18 @@ const handleDelete = () => {
   };
   traverse(formConfig.value!.schemas);
 };
-
-onMounted(() => {});
 </script>
 <template>
   <div
-    class="form-edit-item"
-    @click="formDesignMethods.handleSetSelectItem(schema)"
+    class="form-edit-item relative w-full"
+    @click="formDesignMethods.setSelectSchema(schema)"
   >
     <Form.Item
-      class="form-edit-item-view"
+      class="pointer p-4"
       :key="schema.key"
       :label="schema.hideLabel ? '' : schema.label"
       :required="!!schema.rules"
-      :class="{
-        active: formConfigSelect.key === schema.key,
-      }"
+      :class="cn(selectSchema?.key === schema.key ? 'bg-muted' : '')"
     >
       <Input
         :placeholder="schema.componentProps?.placeholder"
@@ -104,15 +94,18 @@ onMounted(() => {});
       />
     </Form.Item>
     <div
-      class="form-edit-item-action"
-      v-if="formConfigSelect.key === schema.key"
+      class="absolute right-[0px] top-[-12px]"
+      v-if="selectSchema?.key === schema.key"
     >
-      <div class="svgicon form-edit-item-delete">
-        <Icon icon="tdesign:delete" @click="handleDelete" />
+      <div class="svgicon border border-[#f64c4c] text-[#f64c4c]">
+        <Icon icon="tdesign:delete" @click.stop="handleDelete" />
       </div>
     </div>
-    <div class="form-edit-item-drag" v-if="formConfigSelect.key === schema.key">
-      <div class="svgicon form-edit-item-move">
+    <div
+      class="absolute left-[-12px] top-[-12px]"
+      v-if="selectSchema?.key === schema.key"
+    >
+      <div class="svgicon border border-[#90b665] text-[#90b665]">
         <Icon icon="tdesign:drag-move" class="drag-widget" />
       </div>
     </div>
@@ -121,44 +114,7 @@ onMounted(() => {});
 
 <style scoped lang="less">
 .form-edit-item {
-  position: relative;
-  width: 100%;
-
-  &-view {
-    padding: 10px;
-    cursor: pointer;
-    margin-bottom: 0;
-  }
-
-  & > div.active {
-    background: #eef4ff;
-  }
-
-  &-action {
-    position: absolute;
-    top: -12px;
-    right: 43px;
-  }
-
-  &-drag {
-    position: absolute;
-    top: -12px;
-    right: 10px;
-  }
-
-  &-delete {
-    border: 1px solid #f64c4c !important;
-    color: #f64c4c !important;
-  }
-
-  &-move {
-    border: 1px solid #90b665 !important;
-    color: #90b665 !important;
-  }
-
   .svgicon {
-    color: #0960bd;
-    border: 1px solid #0960bd;
     border-radius: 50%;
     width: 24px;
     height: 24px;

+ 10 - 25
apps/web-baicai/src/views/form/design/components/form-edit.vue → apps/web-baicai/src/views/form/design/components/form/index.vue

@@ -1,33 +1,21 @@
 <script lang="ts" setup>
-import type { PropType } from 'vue';
-
-import type { IVFormComponent } from '../types';
+import type { IVFormComponent } from '../../types';
 
 import { cloneDeep } from '@vben/utils';
 
 import { Form } from 'ant-design-vue';
 import Draggable from 'vuedraggable';
 
-import { useFormDesignState } from '../hooks/useFormDesignState';
-import formEditItem from './form-edit-item.vue';
-
-defineProps({
-  formConfigSelect: {
-    type: Object as PropType<IVFormComponent>,
-    required: true,
-  },
-});
+import { useFormDesignState } from '../../hooks/useFormDesignState';
+import FormItem from './form-item/index.vue';
 
-const emit = defineEmits(['change']);
-
-const { formConfig } = useFormDesignState();
+const { formConfig, formDesignMethods } = useFormDesignState();
 
 const handleMoveAdd = ({ newIndex }: any) => {
   formConfig.value.schemas = formConfig.value.schemas || [];
-
   const schemas = formConfig.value.schemas;
   schemas[newIndex] = cloneDeep(schemas[newIndex]) as IVFormComponent;
-  emit('change', schemas[newIndex]);
+  formDesignMethods.setSelectSchema(schemas[newIndex]);
 };
 
 /**
@@ -35,7 +23,9 @@ const handleMoveAdd = ({ newIndex }: any) => {
  * @param e {Object} 事件对象
  */
 const handleDragStart = (e: any) => {
-  emit('change', formConfig.value.schemas[e.oldIndex]);
+  formDesignMethods.setSelectSchema(
+    formConfig.value.schemas[e.oldIndex] as IVFormComponent,
+  );
 };
 </script>
 <template>
@@ -47,7 +37,7 @@ const handleDragStart = (e: any) => {
       <Form
         label-align="right"
         :label-col="{
-          style: { width: `${formConfig.config.commonConfig.labelWidth}px` },
+          style: { width: `${formConfig.config?.commonConfig?.labelWidth}px` },
         }"
       >
         <div class="form-edit-list">
@@ -69,11 +59,7 @@ const handleDragStart = (e: any) => {
             @start="handleDragStart"
           >
             <template #item="{ element, index }">
-              <formEditItem
-                :key="index"
-                :schema="element"
-                :form-config-select="formConfigSelect"
-              />
+              <FormItem :key="index" :schema="element" />
             </template>
           </Draggable>
         </div>
@@ -93,7 +79,6 @@ const handleDragStart = (e: any) => {
   &-container {
     width: 100%;
     height: 100%;
-    background-color: #fff;
     box-sizing: border-box;
   }
 

+ 60 - 0
apps/web-baicai/src/views/form/design/components/widget/index.vue

@@ -0,0 +1,60 @@
+<script lang="ts" setup>
+import type { Recordable } from '@vben/types';
+
+import type { IVFormComponent } from '../../types';
+
+import { reactive } from 'vue';
+
+import { Collapse } from 'ant-design-vue';
+
+import { basicControls } from '../../config/formItemProps';
+import WidgetItem from './widget-item/index.vue';
+
+const emit = defineEmits(['clone', 'start', 'dragstart', 'click']);
+
+const state = reactive<{
+  collapseKey: string;
+  schemas: Recordable<any>[];
+}>({
+  collapseKey: '1',
+  schemas: [
+    {
+      title: '输入型组件',
+      fields: ['Input', 'Textarea', 'InputPassword', 'InputNumber'],
+      schemas: basicControls,
+    },
+  ],
+});
+
+const handleClick = (element: any) => {
+  emit('click', element);
+};
+const handleStart = (component: string) => {
+  emit('start', component);
+};
+const handleDragstart = (schemas: IVFormComponent[], index: number) => {
+  emit('dragstart', schemas, index);
+};
+</script>
+<template>
+  <Collapse
+    :bordered="false"
+    ghost
+    expand-icon-position="end"
+    v-model:active-key="state.collapseKey"
+  >
+    <Collapse.Panel
+      v-for="(item, index) in state.schemas"
+      :key="index + 1"
+      :header="item.title"
+    >
+      <WidgetItem
+        :fields="item.fields"
+        :schemas="item.schemas"
+        @start="handleStart"
+        @click="handleClick"
+        @dragstart="handleDragstart"
+      />
+    </Collapse.Panel>
+  </Collapse>
+</template>

+ 36 - 56
apps/web-baicai/src/views/form/design/components/control-mark.vue → apps/web-baicai/src/views/form/design/components/widget/widget-item/index.vue

@@ -1,33 +1,35 @@
 <script lang="ts" setup>
 import type { PropType } from 'vue';
 
-import type { IVFormComponent } from '../types';
+import type { IVFormComponent } from '../../../types';
+
+import { cloneDeep } from '@vben/utils';
 
 import Draggable from 'vuedraggable';
 
 import { Icon } from '#/components/icon';
 
+import { generateKey } from '../../../utils';
+
 const props = defineProps({
   fields: {
     type: Array as PropType<Array<string>>,
     required: true,
   },
-  list: {
+  schemas: {
     type: Array as PropType<IVFormComponent[]>,
     required: true,
   },
-  handleListPush: {
-    type: Function,
-    default: null,
-  },
 });
-const emit = defineEmits(['copy', 'start', 'addAttrs', 'handleListPush']);
+const emit = defineEmits(['start', 'dragstart', 'click']);
 const handleClick = (element: any) => {
-  emit('handleListPush', element);
+  emit('click', element);
 };
 
 const handleClone = (element: any) => {
-  return props.handleListPush(element);
+  const formItem = cloneDeep(element);
+  generateKey(formItem);
+  return formItem;
 };
 
 const handleStart = (element: any, data: IVFormComponent[]) => {
@@ -38,14 +40,14 @@ const handleStart = (element: any, data: IVFormComponent[]) => {
 // };
 
 const handleDragstart = (index: number) => {
-  emit('addAttrs', props.list, index);
+  emit('dragstart', props.schemas, index);
 };
 </script>
 <template>
   <div>
     <Draggable
-      tag="ul"
-      :model-value="list"
+      tag="div"
+      :model-value="schemas"
       v-bind="{
         group: { name: 'form-draggable', pull: 'clone', put: false },
         sort: false,
@@ -54,65 +56,43 @@ const handleDragstart = (index: number) => {
         ghostClass: 'ghost',
       }"
       item-key="type"
-      class="control-mark"
-      @start="handleStart($event, list)"
+      class="widget-draggable grid grid-cols-2 gap-2 px-2"
+      @start="handleStart($event, schemas)"
     >
       <template #item="{ element, index }">
-        <li
+        <div
           v-if="fields.includes(element.component)"
-          class="control-mark-item"
+          class="widget-draggable-item hover:bg-muted float-left border p-2"
           :class="{ 'no-put': element.component === 'divider' }"
           @dragstart="handleDragstart(index)"
           @click="handleClick(element)"
         >
-          <a>
-            <Icon :icon="element.icon" class="svg" />
+          <a class="text-foreground hover:text-muted-foreground">
+            <Icon v-if="element.icon" :icon="element.icon" class="mr-2" />
             <span>{{ element.label }}</span>
           </a>
-        </li>
+        </div>
       </template>
     </Draggable>
   </div>
 </template>
 
 <style scoped lang="less">
-.control-mark {
-  width: 100%;
-
-  &-item {
-    float: left;
-    width: 44%;
-    margin-right: 6%;
-    margin-bottom: 8px;
-    padding: 8px 0;
-    text-align: center;
-    font-size: 12px;
-    border: 1px solid #e4e4e4;
-
-    & a {
-      color: #333;
-
-      & svg {
-        width: 14px !important;
-        height: 14px !important;
-        margin-right: 5px;
-      }
-    }
-
-    &:hover {
-      border: 1px dashed #eef4ff;
-      background-color: #eef4ff;
-      cursor: v-bind("props.disabled ? 'not-allowed' : 'pointer'");
+// .widget-draggable {
+//   &-item {
+//     & a {
+//       color: #333;
+//     }
 
-      & a {
-        color: #0960bd;
-        cursor: v-bind("props.disabled ? 'not-allowed' : 'pointer'");
-      }
-    }
-  }
-}
+//     &:hover {
+//       background-color: #eef4ff;
+//       cursor: v-bind("props.disabled ? 'not-allowed' : 'pointer'");
 
-.clear {
-  clear: both;
-}
+//       & a {
+//         color: #0960bd;
+//         cursor: v-bind("props.disabled ? 'not-allowed' : 'pointer'");
+//       }
+//     }
+//   }
+// }
 </style>

+ 3 - 5
apps/web-baicai/src/views/form/design/form-modal.vue

@@ -1,5 +1,5 @@
 <script lang="ts" setup>
-import { onMounted, ref, unref } from 'vue';
+import { computed, ref, unref } from 'vue';
 
 import { useVbenModal } from '@vben/common-ui';
 
@@ -34,18 +34,16 @@ const [Modal, { close, setState, getData }] = useVbenModal({
       const data = getData<Record<string, any>>();
       isUpdate.value = !!data.isUpdate;
       modelRef.value = { ...data.baseData };
-      setState({ title: unref(isUpdate) ? '表单设计' : '表单设计' });
 
       setState({ loading: false });
     }
   },
-  title: '表单设计',
 });
 
-onMounted(async () => {});
+const getTitle = computed(() => (unref(isUpdate) ? '表单设计' : '表单设计'));
 </script>
 <template>
-  <Modal>
+  <Modal :title="getTitle">
     <formDesign />
   </Modal>
 </template>

+ 10 - 169
apps/web-baicai/src/views/form/design/index.vue

@@ -1,22 +1,16 @@
 <script lang="ts" setup>
 import type { Ref } from 'vue';
 
-import type { Recordable } from '@vben/types';
-
 import type { IFormConfig, IFormDesignMethods, IVFormComponent } from './types';
 
-import { onMounted, provide, reactive, ref } from 'vue';
-
-import { cloneDeep } from '@vben/utils';
+import { onMounted, provide, ref } from 'vue';
 
-import { Card, Collapse, Tabs } from 'ant-design-vue';
+import { Card, Tabs } from 'ant-design-vue';
 
 import ControlConfig from './components/control-config.vue';
-import ControlMark from './components/control-mark.vue';
 import FormConfig from './components/form-config.vue';
-import FormEdit from './components/form-edit.vue';
-import { basicControls } from './config/formItemProps';
-import { generateKey } from './utils';
+import FormEdit from './components/form/index.vue';
+import Widget from './components/widget/index.vue';
 
 defineOptions({
   name: 'FormDesign',
@@ -31,147 +25,17 @@ const formConfig = ref<IFormConfig>({
     },
   },
   schemas: [],
-  currentItem: { component: '' } as IVFormComponent,
-});
-
-const state = reactive<{
-  collapseKey: string;
-  controlList: Recordable<any>[];
-}>({
-  collapseKey: '1',
-  controlList: [
-    {
-      title: '输入型组件',
-      fields: ['Input', 'Textarea', 'InputPassword', 'InputNumber'],
-      list: basicControls,
-    },
-  ],
+  selectSchema: { component: '' } as IVFormComponent,
 });
 
-/**
- * 选中表单项
- * @param schema 当前选中的表单项
- */
-const handleSetSelectItem = (schema: IVFormComponent) => {
-  formConfig.value.currentItem = schema as any;
-  // handleChangePropsTabs(
-  //   schema.key ? (formConfig.value.activeKey! === 1 ? 2 : formConfig.value.activeKey!) : 1,
-  // );
+const setSelectSchema = (schema: IVFormComponent) => {
+  formConfig.value.selectSchema = schema as any;
 };
 
-const setGlobalConfigState = (formItem: IVFormComponent) => {
-  // formItem.colProps = formItem.colProps || {};
-  // formItem.colProps.span = globalConfigState.span;
-  formItem.aaa = '';
-  // console.log('setGlobalConfigState', formItem);
-};
-
-// 把表单配置项注入到子组件中,子组件可通过inject获取,获取到的数据为响应式
 provide<Ref<IFormConfig>>('formConfig', formConfig as any);
 
-/**
- * 添加属性
- * @param schemas
- * @param index
- */
-const handleAddAttrs = (_formItems: IVFormComponent[], _index: number) => {};
-
-const handleListPushDrag = (item: IVFormComponent) => {
-  const formItem = cloneDeep(item);
-  setGlobalConfigState(formItem);
-  generateKey(formItem);
-
-  return formItem;
-};
-
-/**
- * 添加到表单中
- * @param newIndex {object} 事件对象
- * @param schemas {IVFormComponent[]} 表单项列表
- * @param isCopy {boolean} 是否复制
- */
-const handleBeforeColAdd = (
-  { newIndex }: any,
-  schemas: IVFormComponent[],
-  isCopy = false,
-) => {
-  const item = schemas[newIndex] as IVFormComponent;
-  isCopy && generateKey(item);
-  handleSetSelectItem(item);
-};
-/**
- * 复制表单项,如果表单项为栅格布局,则遍历所有自表单项重新生成key
- * @param {IVFormComponent} formItem
- * @return {IVFormComponent}
- */
-const copyFormItem = (formItem: IVFormComponent) => {
-  const newFormItem = cloneDeep(formItem);
-  // if (newFormItem.component === 'Grid') {
-  //   formItemsForEach([formItem], (item) => {
-  //     generateKey(item);
-  //   });
-  // }
-  return newFormItem;
-};
-/**
- * 复制或者添加表单,isCopy为true时则复制表单
- * @param item {IVFormComponent} 当前点击的组件
- * @param isCopy {boolean} 是否复制
- */
-const handleCopy = (
-  item: IVFormComponent = formConfig.value.currentItem as IVFormComponent,
-  isCopy = true,
-) => {
-  const key = formConfig.value.currentItem?.key;
-  /**
-   * 遍历当表单项配置,如果是复制,则复制一份表单项,如果不是复制,则直接添加到表单项中
-   * @param schemas
-   */
-  const traverse = (schemas: IVFormComponent[]) => {
-    // 使用some遍历,找到目标后停止遍历
-    schemas.some((formItem: IVFormComponent, index: number) => {
-      if (formItem.key === key) {
-        // 判断是不是复制
-        isCopy
-          ? schemas.splice(index, 0, copyFormItem(formItem))
-          : schemas.splice(index + 1, 0, item);
-        const event = {
-          newIndex: index + 1,
-        };
-        // 添加到表单项中
-        handleBeforeColAdd(event, schemas, isCopy);
-        return true;
-      }
-      return false;
-    });
-  };
-  if (formConfig.value.schemas) {
-    traverse(formConfig.value.schemas as any);
-  }
-};
-
-/**
- * 单击控件时添加到面板中
- * @param item {IVFormComponent} 当前点击的组件
- */
-const handleListPush = (item: IVFormComponent) => {
-  // console.log('handleListPush', item);
-  const formItem = cloneDeep(item);
-  setGlobalConfigState(formItem);
-  generateKey(formItem);
-  if (!formConfig.value.currentItem?.key) {
-    handleSetSelectItem(formItem);
-    formConfig.value.schemas && formConfig.value.schemas.push(formItem as any);
-
-    return;
-  }
-  handleCopy(formItem, false);
-};
-
-// 把祖先组件的方法项注入到子组件中,子组件可通过inject获取
 provide<IFormDesignMethods>('formDesignMethods', {
-  handleAddAttrs,
-  handleSetSelectItem,
+  setSelectSchema,
 });
 
 onMounted(async () => {});
@@ -179,34 +43,11 @@ onMounted(async () => {});
 <template>
   <div class="flex h-full w-full">
     <div class="w-[300px]">
-      <Collapse
-        :bordered="false"
-        ghost
-        expand-icon-position="end"
-        v-model:active-key="state.collapseKey"
-      >
-        <Collapse.Panel
-          v-for="(item, index) in state.controlList"
-          :key="index + 1"
-          :header="item.title"
-        >
-          <ControlMark
-            :fields="item.fields"
-            :list="item.list"
-            @add-attrs="handleAddAttrs"
-            :handle-list-push="handleListPushDrag"
-            @handle-list-push="handleListPush"
-          />
-        </Collapse.Panel>
-      </Collapse>
+      <Widget />
     </div>
     <div class="h-full flex-1 pl-2 pr-2">
       <Card title="设计区域" class="h-full w-full">
-        <FormEdit
-          :form-config="formConfig"
-          :form-config-select="formConfig.currentItem"
-          @change="handleSetSelectItem"
-        />
+        <FormEdit />
       </Card>
     </div>
     <div class="w-[300px]">

+ 2 - 4
apps/web-baicai/src/views/form/design/types/index.ts

@@ -29,15 +29,13 @@ export interface IFormConfig {
     showDefaultActions: boolean;
     wrapperClass: string;
   };
-  currentItem?: IVFormComponent;
+  selectSchema?: IVFormComponent;
 }
 
 /**
  * 设计方法
  */
 export interface IFormDesignMethods {
-  // 添加控件属性
-  handleAddAttrs(schemas: IVFormComponent[], index: number): void;
   // 设置当前选中的控件
-  handleSetSelectItem(item: IVFormComponent): void;
+  setSelectSchema(item: IVFormComponent): void;
 }

+ 1 - 1
apps/web-baicai/src/views/form/design/utils/index.ts

@@ -11,7 +11,7 @@ import { buildUUID, toLine } from '#/utils';
  */
 export function generateKey(formItem?: IVFormComponent): boolean | string {
   if (formItem && formItem.component) {
-    const key = `${toLine(formItem.component)}_${buildUUID()}`;
+    const key = `k${toLine(formItem.component)}_${buildUUID()}`;
     formItem.key = key;
     formItem.fieldName = key;