Browse Source

feat: 下拉、单选、多选框组件完成

huangziyang 1 month ago
parent
commit
2c2b9809fa

+ 1 - 0
package.json

@@ -13,6 +13,7 @@
     "format": "prettier --write src/"
     "format": "prettier --write src/"
   },
   },
   "dependencies": {
   "dependencies": {
+    "@element-plus/icons-vue": "^2.3.1",
     "@form-create/designer": "^3.2.11",
     "@form-create/designer": "^3.2.11",
     "@form-create/element-ui": "^3.2.22",
     "@form-create/element-ui": "^3.2.22",
     "@vueuse/core": "^13.1.0",
     "@vueuse/core": "^13.1.0",

+ 19 - 0
patches/@form-create__designer.patch

@@ -0,0 +1,19 @@
+diff --git a/src/components/FcDesigner.vue b/src/components/FcDesigner.vue
+index 80befe737bd814492e65ca885f9b1d3cc96de28d..c94b142dc69ba31862d4cc398218d8c70eeb2bcc 100644
+--- a/src/components/FcDesigner.vue
++++ b/src/components/FcDesigner.vue
+@@ -992,6 +992,14 @@ export default defineComponent({
+                     {language: 'xml'}
+                 ).value
+             },
++            closePreview() {
++                data.preview = {
++                    state: false,
++                    rule: [],
++                    option: {},
++                    api: {},
++                };
++            },
+             copyCode() {
+                 copyTextToClipboard(this.$refs.previewCode.innerText);
+             },

+ 10 - 2
pnpm-lock.yaml

@@ -4,13 +4,21 @@ settings:
   autoInstallPeers: true
   autoInstallPeers: true
   excludeLinksFromLockfile: false
   excludeLinksFromLockfile: false
 
 
+patchedDependencies:
+  '@form-create/designer':
+    hash: af030e5465c9411959d5b65dfb673a6fb99eeb8d02211ba445de78a83ea89f74
+    path: patches/@form-create__designer.patch
+
 importers:
 importers:
 
 
   .:
   .:
     dependencies:
     dependencies:
+      '@element-plus/icons-vue':
+        specifier: ^2.3.1
+        version: 2.3.1(vue@3.5.13(typescript@5.8.3))
       '@form-create/designer':
       '@form-create/designer':
         specifier: ^3.2.11
         specifier: ^3.2.11
-        version: 3.2.11(vue@3.5.13(typescript@5.8.3))
+        version: 3.2.11(patch_hash=af030e5465c9411959d5b65dfb673a6fb99eeb8d02211ba445de78a83ea89f74)(vue@3.5.13(typescript@5.8.3))
       '@form-create/element-ui':
       '@form-create/element-ui':
         specifier: ^3.2.22
         specifier: ^3.2.22
         version: 3.2.22(vue@3.5.13(typescript@5.8.3))
         version: 3.2.22(vue@3.5.13(typescript@5.8.3))
@@ -2589,7 +2597,7 @@ snapshots:
       '@form-create/utils': 3.2.18
       '@form-create/utils': 3.2.18
       vue: 3.5.13(typescript@5.8.3)
       vue: 3.5.13(typescript@5.8.3)
 
 
-  '@form-create/designer@3.2.11(vue@3.5.13(typescript@5.8.3))':
+  '@form-create/designer@3.2.11(patch_hash=af030e5465c9411959d5b65dfb673a6fb99eeb8d02211ba445de78a83ea89f74)(vue@3.5.13(typescript@5.8.3))':
     dependencies:
     dependencies:
       '@form-create/component-wangeditor': 3.2.14
       '@form-create/component-wangeditor': 3.2.14
       '@form-create/element-ui': 3.2.22(vue@3.5.13(typescript@5.8.3))
       '@form-create/element-ui': 3.2.22(vue@3.5.13(typescript@5.8.3))

+ 2 - 0
pnpm-workspace.yaml

@@ -0,0 +1,2 @@
+patchedDependencies:
+  '@form-create/designer': patches/@form-create__designer.patch

+ 31 - 10
src/App.vue

@@ -1,19 +1,30 @@
+<!-- eslint-disable @typescript-eslint/no-explicit-any -->
 <!-- eslint-disable @typescript-eslint/ban-ts-comment -->
 <!-- eslint-disable @typescript-eslint/ban-ts-comment -->
 <script setup lang="ts">
 <script setup lang="ts">
-import { ref, onMounted } from 'vue'
+import { ref, onMounted, nextTick } from 'vue'
 import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
 import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
-import type { Config, FcDesignerInstance, Handle } from '@form-create/designer'
+import type { Config, Handle } from '@form-create/designer'
 // import { formCreate } from '@form-create/designer'
 // import { formCreate } from '@form-create/designer'
 import { formConfig } from './form.config.ts'
 import { formConfig } from './form.config.ts'
 import FcDesigner from '@form-create/designer'
 import FcDesigner from '@form-create/designer'
+import { RcSelect } from './plugins/components/rc-select.config.ts'
+import { RcRadio } from './plugins/components/rc-radio.config.ts'
+import { MenuOptions } from './plugins/menu/options.ts'
+import type { FcDesignerInstance } from '@form-create/designer' // 注册组件
+import { RcCheckbox } from './plugins/components/rc-checkbox.ts'
 
 
 /**
 /**
  * @see https://view.form-create.com/methods
  * @see https://view.form-create.com/methods
  */
  */
 declare global {
 declare global {
   interface Window {
   interface Window {
-    __QIANKUN__EVENT__?: () => void // 事件中心
-    __QIANKUN__REQUSET__?: () => void // 请求API接口
+    __QIANKUN__EVENT__: {
+      on: (name: string, fn: (...arg: any[]) => void) => void
+      emit: (name: string, ...arg: any[]) => void
+    } // 事件中心
+    __QIANKUN__REQUSET__: () => void // 请求API接口
+    __QIANKUN__GUID__: () => string // 获取唯一标识
+    __QIANKUN__OPEN__TAG__: () => void // 打开标签页
   }
   }
 }
 }
 // 表单实例
 // 表单实例
@@ -25,17 +36,29 @@ const height = ref(qiankunWindow.__POWERED_BY_QIANKUN__ ? '90vh' : '100vh')
 const config = ref<Config>(formConfig)
 const config = ref<Config>(formConfig)
 // 顶部更多操作按钮
 // 顶部更多操作按钮
 const handle = ref<Handle>([])
 const handle = ref<Handle>([])
-console.log('子应用', window.__QIANKUN__EVENT__)
+// console.log('子应用', window.__QIANKUN__EVENT__)
 
 
 const onSave = (config: { options: string; rule: string }) => {
 const onSave = (config: { options: string; rule: string }) => {
   const rule = config.rule
   const rule = config.rule
-
   console.log(rule)
   console.log(rule)
 }
 }
 
 
 onMounted(() => {
 onMounted(() => {
-  // 删除logo
-  document.querySelector('a[href="https://form-create.com/"]')?.remove()
+  designer.value?.openPreview()
+  nextTick(() => {
+    // @ts-ignore
+    designer.value!.preview.state = false
+  })
+})
+
+onMounted(() => {
+  // 注册拖拽规则
+  designer.value?.addComponent(RcSelect)
+  designer.value?.addComponent(RcRadio)
+  designer.value?.addComponent(RcCheckbox)
+
+  // 注册菜单
+  designer.value?.addMenu(MenuOptions)
 })
 })
 </script>
 </script>
 
 
@@ -49,5 +72,3 @@ onMounted(() => {
     v-model:api="api"
     v-model:api="api"
   />
   />
 </template>
 </template>
-
-<style scoped></style>

+ 12 - 0
src/components/index.ts

@@ -0,0 +1,12 @@
+import formCreate from '@form-create/element-ui'
+import RcOptions from './rc-options/rc-options.vue'
+import RcSelect from './rc-select/rc-select.vue'
+import RcRadio from './rc-radio/rc-radio.vue'
+import RcCheckbox from './rc-checkbox/rc-checkbox.vue'
+const setupComponent = () => {
+  formCreate.component(RcOptions.__name!, RcOptions)
+  formCreate.component(RcSelect.__name!, RcSelect)
+  formCreate.component(RcRadio.__name!, RcRadio)
+  formCreate.component(RcRadio.__name!, RcCheckbox)
+}
+export default setupComponent

+ 26 - 0
src/components/rc-checkbox/rc-checkbox.vue

@@ -0,0 +1,26 @@
+<script lang="ts" name="rc-checkbox" setup>
+import type { FormCreateProps } from '@form-create/element-ui'
+import RcCreateOption from '../rc-options/rc-create-option.vue'
+import { ref } from 'vue'
+
+defineProps<{
+  formCreateInject: FormCreateProps & {
+    field: string
+  }
+}>()
+
+const checkList = ref([])
+</script>
+<template>
+  <RcCreateOption :formCreateInject="formCreateInject" v-slot="{ options }">
+    <el-checkbox-group v-model="checkList">
+      <el-checkbox
+        v-for="item in options"
+        size="large"
+        :key="item.key"
+        :value="item.label"
+        :label="item.label"
+      />
+    </el-checkbox-group>
+  </RcCreateOption>
+</template>

+ 16 - 0
src/components/rc-options/createOptions.ts

@@ -0,0 +1,16 @@
+export interface Option {
+  key: string
+  label: string
+  value: string
+  tags?: {
+    corpid: string
+    group_id: string
+    id: number
+    insert_time: number
+    order: number
+    status: number
+    tag_id: string
+    tag_name: string
+    update_time: number
+  }[]
+}

+ 34 - 0
src/components/rc-options/rc-create-option.vue

@@ -0,0 +1,34 @@
+<script lang="ts" name="rc-select" setup>
+import type { FormCreateProps } from '@form-create/element-ui'
+import type { Option } from '../rc-options/createOptions.ts'
+import { onMounted, ref } from 'vue'
+const props = defineProps<{
+  formCreateInject: FormCreateProps & {
+    field: string
+  }
+}>()
+
+const options = ref<Option[]>([])
+
+window.__QIANKUN__EVENT__?.on(props.formCreateInject.field, (tags: Option[]) => {
+  if (props.formCreateInject.rule?.props === void 0) {
+    return
+  }
+  ;(props.formCreateInject.rule.props as Record<string, Option[]>).options =
+    // eslint-disable-next-line vue/no-mutating-props
+    props.formCreateInject.rule.options = tags.map((item) => {
+      return {
+        ...item,
+        value: item.label,
+      }
+    })
+  options.value = tags
+})
+
+onMounted(() => {
+  options.value = props.formCreateInject.rule!.options as unknown as Option[]
+})
+</script>
+<template>
+  <slot :options="options" />
+</template>

+ 99 - 0
src/components/rc-options/rc-options.vue

@@ -0,0 +1,99 @@
+<script lang="ts" name="rc-options" setup>
+import { onUpdated, ref } from 'vue'
+import { Delete, Plus } from '@element-plus/icons-vue'
+import { type Option } from './createOptions'
+import { debounce } from '@/utils/debouce'
+import type { FormCreateProps } from '@form-create/element-ui'
+
+// 传递弹窗过来的数据
+window.__QIANKUN__EVENT__?.on('tagsChange', (tags: Option['tags']) => {
+  optionList.value[currentIndex].tags = tags
+})
+let currentIndex = 0
+const getId = window.__QIANKUN__GUID__ || (() => 'default-guid')
+
+const optionList = ref<Option[]>([])
+
+const props = defineProps<{
+  formCreateInject: FormCreateProps
+  field: string
+}>()
+
+// 新增选项
+const onAddClick = () => {
+  optionList.value.push({
+    key: getId(),
+    label: '',
+    value: '',
+    tags: [],
+  })
+}
+
+// 删除选项
+const onRemoveClick = (index: number) => {
+  optionList.value.splice(index, 1)
+}
+
+// 打开标签弹窗
+const onOpenTagsDialog = (index: number) => {
+  currentIndex = index
+  window.__QIANKUN__OPEN__TAG__?.()
+}
+
+onUpdated(
+  debounce(() => {
+    window.__QIANKUN__EVENT__?.emit(
+      (props.formCreateInject.rule?.props as Record<string, string>)?.field,
+      optionList.value,
+    )
+  }),
+)
+</script>
+<template>
+  <div class="options-container">
+    <template v-if="optionList.length > 0">
+      <div
+        v-for="(item, index) in optionList"
+        style="display: flex; gap: 5px; flex-direction: column"
+        :key="item.key"
+      >
+        <div class="options">
+          <el-input
+            v-model="item.label"
+            style="width: 240px"
+            autosize
+            type="textarea"
+            placeholder="请输入内容"
+          />
+          <el-icon style="cursor: pointer" @click="onRemoveClick(index)"><Delete /></el-icon>
+        </div>
+        <div class="tags">
+          <el-tag type="success" v-for="tag in item.tags" :key="tag.tag_id">{{
+            tag.tag_name
+          }}</el-tag>
+        </div>
+        <div style="width: 40%" @click="onOpenTagsDialog(index)">
+          <el-button :icon="Plus">添加标签</el-button>
+        </div>
+      </div>
+    </template>
+    <el-button @click="onAddClick" type="primary"> 添加选项 </el-button>
+  </div>
+</template>
+<style>
+.options-container {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+.options {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+
+.tags {
+  display: flex;
+  gap: 5px;
+}
+</style>

+ 22 - 0
src/components/rc-radio/rc-radio.vue

@@ -0,0 +1,22 @@
+<script lang="ts" name="rc-radio" setup>
+import type { FormCreateProps } from '@form-create/element-ui'
+import RcCreateOption from '../rc-options/rc-create-option.vue'
+import { ref } from 'vue'
+
+defineProps<{
+  formCreateInject: FormCreateProps & {
+    field: string
+  }
+}>()
+
+const radioValue = ref('')
+</script>
+<template>
+  <RcCreateOption :formCreateInject="formCreateInject" v-slot="{ options }">
+    <el-radio-group v-model="radioValue">
+      <el-radio v-for="item in options" size="large" :key="item.key" :value="item.label">{{
+        item.label
+      }}</el-radio>
+    </el-radio-group>
+  </RcCreateOption>
+</template>

+ 17 - 0
src/components/rc-select/rc-select.vue

@@ -0,0 +1,17 @@
+<script lang="ts" name="rc-select" setup>
+import type { FormCreateProps } from '@form-create/element-ui'
+import RcCreateOption from '../rc-options/rc-create-option.vue'
+
+defineProps<{
+  formCreateInject: FormCreateProps & {
+    field: string
+  }
+}>()
+</script>
+<template>
+  <RcCreateOption :formCreateInject="formCreateInject" v-slot="{ options }">
+    <el-select v-bind="$attrs">
+      <el-option v-for="item in options" :key="item.key" :label="item.label" :value="item.label" />
+    </el-select>
+  </RcCreateOption>
+</template>

+ 3 - 0
src/form.config.ts

@@ -27,6 +27,9 @@ export const formConfig: Config = {
     'upload',
     'upload',
     'timeRange',
     'timeRange',
     'dateRange',
     'dateRange',
+    'select',
+    'radio',
+    'checkbox',
   ],
   ],
   //是否显示组件的编号
   //是否显示组件的编号
   showComponentName: false,
   showComponentName: false,

+ 8 - 1
src/main.ts

@@ -10,9 +10,12 @@ import 'element-plus/dist/index.css'
 import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
 import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
 // @ts-ignore
 // @ts-ignore
 import { formCreate } from '@form-create/designer'
 import { formCreate } from '@form-create/designer'
+import * as ElementPlusIconsVue from '@element-plus/icons-vue'
+
 let app: any = null
 let app: any = null
 //封装弹窗方法
 //封装弹窗方法
 import { ElMessage } from 'element-plus'
 import { ElMessage } from 'element-plus'
+import setupComponent from './components'
 const message = (msg: string, type: MessageProps['type']) => {
 const message = (msg: string, type: MessageProps['type']) => {
   return ElMessage({
   return ElMessage({
     message: msg,
     message: msg,
@@ -20,16 +23,20 @@ const message = (msg: string, type: MessageProps['type']) => {
     customClass: '_fc-message-tip',
     customClass: '_fc-message-tip',
   })
   })
 }
 }
-
 //导入方法
 //导入方法
 formCreate.setData('message', message)
 formCreate.setData('message', message)
 
 
 //导入方法
 //导入方法
 async function bootstrapVue3(container: HTMLElement) {
 async function bootstrapVue3(container: HTMLElement) {
+  await setupComponent()
+
   if (qiankunWindow.__POWERED_BY_QIANKUN__) {
   if (qiankunWindow.__POWERED_BY_QIANKUN__) {
     console.log('处于独立运行模式...')
     console.log('处于独立运行模式...')
   }
   }
   app = createApp(App)
   app = createApp(App)
+  for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+    app.component(key, component)
+  }
   app.use(ELEMENT)
   app.use(ELEMENT)
   app.use(FcDesigner.formCreate)
   app.use(FcDesigner.formCreate)
   app.mount(container)
   app.mount(container)

+ 35 - 0
src/plugins/components/rc-checkbox.ts

@@ -0,0 +1,35 @@
+import type { DragRule } from '@form-create/designer'
+
+export const RcCheckbox: DragRule = {
+  menu: 'aide',
+  icon: 'icon-title',
+  label: '标题',
+  //唯一 ID
+  name: 'rc-checkbox',
+  //组件可以配置的事件
+  input: true,
+  languageKey: [],
+  rule() {
+    //组件的渲染规则
+    return {
+      //组件的名称, 与上一步是对应
+      type: 'rc-checkbox',
+      props: {
+        clearable: true,
+      },
+    }
+  },
+  props(rule) {
+    //组件右侧的配置项,与组件中的 props 对应
+    return [
+      {
+        type: 'rc-options',
+        field: 'options',
+        title: '选项内容',
+        props: {
+          field: rule.field,
+        },
+      },
+    ]
+  },
+}

+ 35 - 0
src/plugins/components/rc-radio.config.ts

@@ -0,0 +1,35 @@
+import type { DragRule } from '@form-create/designer'
+
+export const RcRadio: DragRule = {
+  menu: 'aide',
+  icon: 'icon-title',
+  label: '标题',
+  //唯一 ID
+  name: 'rc-radio',
+  //组件可以配置的事件
+  input: true,
+  languageKey: [],
+  rule() {
+    //组件的渲染规则
+    return {
+      //组件的名称, 与上一步是对应
+      type: 'rc-radio',
+      props: {
+        clearable: true,
+      },
+    }
+  },
+  props(rule) {
+    //组件右侧的配置项,与组件中的 props 对应
+    return [
+      {
+        type: 'rc-options',
+        field: 'options',
+        title: '选项内容',
+        props: {
+          field: rule.field,
+        },
+      },
+    ]
+  },
+}

+ 36 - 0
src/plugins/components/rc-select.config.ts

@@ -0,0 +1,36 @@
+import type { DragRule } from '@form-create/designer'
+
+export const RcSelect: DragRule = {
+  menu: 'aide',
+  icon: 'icon-title',
+  label: '标题',
+  //唯一 ID
+  name: 'rc-select',
+  //组件可以配置的事件
+  input: true,
+  languageKey: [],
+  rule() {
+    //组件的渲染规则
+    return {
+      //组件的名称, 与上一步是对应
+      type: 'rc-select',
+      props: {
+        placeholder: '请选择',
+        clearable: true,
+      },
+    }
+  },
+  props(rule) {
+    //组件右侧的配置项,与组件中的 props 对应
+    return [
+      {
+        type: 'rc-options',
+        field: 'options',
+        title: '选项内容',
+        props: {
+          field: rule.field,
+        },
+      },
+    ]
+  },
+}

+ 23 - 0
src/plugins/menu/options.ts

@@ -0,0 +1,23 @@
+import type { Menu } from '@form-create/designer'
+
+export const MenuOptions: Menu = {
+  title: '选项组件',
+  name: 'rc-component',
+  list: [
+    {
+      name: 'rc-select',
+      icon: 'icon-select',
+      label: '选择器',
+    },
+    {
+      name: 'rc-radio',
+      icon: 'icon-radio',
+      label: '单选框',
+    },
+    {
+      name: 'rc-checkbox',
+      icon: 'icon-checkbox',
+      label: '多选框',
+    },
+  ],
+}

+ 17 - 0
src/utils/debouce.ts

@@ -0,0 +1,17 @@
+// 防抖
+// 导出一个函数,用于防抖
+export function debounce(fn: (...arg: unknown[]) => void, delay: number = 300) {
+  // 定义一个变量,用于存储定时器
+  let timer: number | null = null
+  // 返回一个函数,用于接收参数
+  return function (...args: unknown[]) {
+    // 如果定时器存在,则清除定时器
+    if (timer) {
+      clearTimeout(timer)
+    }
+    // 设置定时器,延迟执行传入的函数
+    timer = setTimeout(() => {
+      fn(...args)
+    }, delay)
+  }
+}