|
@@ -0,0 +1,223 @@
|
|
|
|
|
+<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 { Card, Collapse, 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';
|
|
|
|
|
+
|
|
|
|
|
+defineOptions({
|
|
|
|
|
+ name: 'FormDesign',
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const formConfig = ref<IFormConfig>({
|
|
|
|
|
+ config: {
|
|
|
|
|
+ wrapperClass: 'grid-cols-1',
|
|
|
|
|
+ showDefaultActions: false,
|
|
|
|
|
+ commonConfig: {
|
|
|
|
|
+ labelWidth: 100,
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ schemas: [],
|
|
|
|
|
+ currentItem: { component: '' } as IVFormComponent,
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const state = reactive<{
|
|
|
|
|
+ collapseKey: string;
|
|
|
|
|
+ controlList: Recordable<any>[];
|
|
|
|
|
+}>({
|
|
|
|
|
+ collapseKey: '1',
|
|
|
|
|
+ controlList: [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '输入型组件',
|
|
|
|
|
+ fields: ['Input', 'Textarea', 'InputPassword', 'InputNumber'],
|
|
|
|
|
+ list: basicControls,
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 选中表单项
|
|
|
|
|
+ * @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 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,
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+onMounted(async () => {});
|
|
|
|
|
+</script>
|
|
|
|
|
+<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>
|
|
|
|
|
+ </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"
|
|
|
|
|
+ />
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="w-[300px]">
|
|
|
|
|
+ <Tabs>
|
|
|
|
|
+ <Tabs.TabPane key="1" tab="组件属性">
|
|
|
|
|
+ <ControlConfig />
|
|
|
|
|
+ </Tabs.TabPane>
|
|
|
|
|
+ <Tabs.TabPane key="2" tab="表单属性">
|
|
|
|
|
+ <FormConfig v-model:config="formConfig.config" />
|
|
|
|
|
+ </Tabs.TabPane>
|
|
|
|
|
+ </Tabs>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|