Procházet zdrojové kódy

feat: 自定表单配置项

huangziyang před 1 měsícem
rodič
revize
ea4740cedb

+ 11 - 28
README.md

@@ -1,39 +1,22 @@
-# form
+# scrm子应用自定义表单
 
-This template should help get you started developing with Vue 3 in Vite.
+## 介绍
 
-## Recommended IDE Setup
+scrm子应用自定义表单,基于vue3的[form-create](https://form-create.com/v3/guide/)库进行二次开发的。
 
-[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
+> 因为是基于基座scrm开发的,所以不需要进行环境变量的区分
+>
+> 由于使用了vite的新特性,所以不要删除pnpm-lock.yaml文件,不然在运行的时候会报错
 
-## Type Support for `.vue` Imports in TS
+## 运行
 
-TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
-
-## Customize configuration
-
-See [Vite Configuration Reference](https://vite.dev/config/).
-
-## Project Setup
-
-```sh
+```bash
+安装依赖
 pnpm install
-```
-
-### Compile and Hot-Reload for Development
 
-```sh
+运行
 pnpm dev
-```
-
-### Type-Check, Compile and Minify for Production
 
-```sh
+打包
 pnpm build
 ```
-
-### Lint with [ESLint](https://eslint.org/)
-
-```sh
-pnpm lint
-```

+ 20 - 14
src/App.vue

@@ -10,10 +10,9 @@ import { RcRadio } from './plugins/components/rc-radio.config.ts'
 import { RcCheckbox } from './plugins/components/rc-checkbox.config.ts'
 // 菜单
 import { MenuOptions } from './plugins/menu/options.ts'
-import { MenuUpload } from './plugins/menu/upload.ts'
 
 import type { FcDesignerInstance } from '@form-create/designer' // 注册组件
-import { RcUpload } from './plugins/components/rc-upload.config.ts'
+import type { Rule } from '@form-create/element-ui'
 
 /**
  * @see https://view.form-create.com/methods
@@ -39,7 +38,6 @@ const height = ref('100vh')
 const config = ref<Config>(formConfig)
 // 顶部更多操作按钮
 const handle = ref<Handle>([])
-// console.log('子应用', window.__QIANKUN__EVENT__)
 
 const onSave = async (config: { options: string; rule: string }) => {
   const rule = config.rule
@@ -53,20 +51,33 @@ const onSave = async (config: { options: string; rule: string }) => {
 }
 
 const rule = ref(
-  '[{"type":"rc-checkbox","title":"多选框","props":{"clearable":true,"options":[{"key":"cw_1iqkm6k0p1n4111p61q8g8katdn1","label":"1234","value":"1234","validate":false,"tags":[]}]},"_fc_id":"id_Fd0jmadjp53oaec","name":"ref_Fw5smadjp53oafc","field":"Fnqymadjp53oagc","display":true,"hidden":false,"_fc_drag_tag":"rc-checkbox","options":[{"key":"cw_1iqkm6k0p1n4111p61q8g8katdn1","label":"1234","value":"1234","validate":false,"tags":[]}]},{"type":"rc-radio","title":"单选框","props":{"clearable":true,"options":[{"key":"cw_1iqkm6nli1v4f8p01pn01rs715r62","label":"1234","value":"1234","validate":false,"tags":[]}]},"_fc_id":"id_Fo8imadjp7v1ahc","name":"ref_F6j0madjp7v1aic","field":"Fhyhmadjp7v1ajc","display":true,"hidden":false,"_fc_drag_tag":"rc-radio","options":[{"key":"cw_1iqkm6nli1v4f8p01pn01rs715r62","label":"1234","value":"1234","validate":false,"tags":[]}]},{"type":"rc-upload","title":"图片","props":{"clearable":true},"_fc_id":"id_Fyhlmadknfeuabc","name":"ref_Fz6bmadknfeuacc","field":"Fj86madknfeuadc","display":true,"hidden":false,"_fc_drag_tag":"rc-upload"}]',
+  '[{"type":"rc-checkbox","title":"多选框","props":{"clearable":true,"options":[{"key":"cw_1iqn0n3e6mgc1vgp7di8as15s55","label":"123421","value":"123421","validate":false,"tags":[],"default":""}]},"_fc_id":"id_Fsbmmaeu7rtlabc","name":"ref_Finhmaeu7rtlacc","field":"Ft20maeu7rtladc","display":true,"hidden":false,"_fc_drag_tag":"rc-checkbox","options":[{"key":"cw_1iqn0n3e6mgc1vgp7di8as15s55","label":"123421","value":"123421","validate":false,"tags":[],"default":""}]},{"type":"rc-select","title":"下拉框","props":{"placeholder":"请选择","clearable":true,"options":[{"key":"cw_1iqn0n6gm1uos105812fafne1mqj6","label":"223","value":"223","validate":false,"tags":[],"default":""}]},"_fc_id":"id_Fuf6maeu7skxaec","name":"ref_Fgfdmaeu7skxafc","field":"Fgckmaeu7skxagc","display":true,"hidden":false,"_fc_drag_tag":"rc-select","options":[{"key":"cw_1iqn0n6gm1uos105812fafne1mqj6","label":"223","value":"223","validate":false,"tags":[],"default":""}],"synchronize_field":{"name":"备注名"}}]',
 )
 
+window.__QIANKUN__EVENT__.on('formCoverChange', (cover: string) => {
+  const newRule = JSON.parse(rule.value)
+  const firstType = newRule[0]?.type
+  if (firstType === 'rc-image') {
+    newRule[0].props.cover = cover
+  } else {
+    newRule.unshift({
+      type: 'rc-image',
+      props: {
+        cover,
+      },
+    } as Rule)
+  }
+
+  rule.value = JSON.stringify(newRule)
+  designer.value?.setRule(rule.value) // 重新赋值
+})
+
 onMounted(async () => {
   // 注册拖拽规则
   designer.value?.addMenu(MenuOptions)
-  designer.value?.addMenu(MenuUpload)
   designer.value?.addComponent(RcSelect)
   designer.value?.addComponent(RcRadio)
   designer.value?.addComponent(RcCheckbox)
-  designer.value?.addComponent(RcUpload)
-
-  // 注册菜单
-  console.log(designer.value)
 
   // 设置拖拽规则
   designer.value?.setRule(rule.value)
@@ -81,10 +92,5 @@ onMounted(async () => {
     :config="config"
     v-model:api="api"
     @save="onSave"
-    @inputDate="
-      () => {
-        console.log(12313213)
-      }
-    "
   />
 </template>

+ 10 - 3
src/components/rc-checkbox/rc-checkbox.vue

@@ -1,15 +1,22 @@
 <script lang="ts" name="rc-checkbox" setup>
 import type { FormCreateProps } from '@form-create/element-ui'
 import RcCreateOption from '../rc-options/create-option.vue'
-import { ref } from 'vue'
+import { onMounted, ref } from 'vue'
 
-defineProps<{
+const props = defineProps<{
   formCreateInject: FormCreateProps & {
     field: string
   }
+  defaultValue: string
 }>()
 
-const checkList = ref([])
+const checkList = ref<string[]>([])
+
+onMounted(() => {
+  if (props.defaultValue) {
+    checkList.value = [props.defaultValue]
+  }
+})
 </script>
 <template>
   <RcCreateOption :formCreateInject="formCreateInject" v-slot="{ options }">

+ 15 - 0
src/components/rc-image/rc-image.vue

@@ -0,0 +1,15 @@
+<script setup lang="ts" name="rc-image">
+defineProps<{
+  cover: string
+}>()
+</script>
+<template>
+  <div>
+    <img :src="cover" class="image" alt="" />
+  </div>
+</template>
+<style scoped>
+.image {
+  width: 100%;
+}
+</style>

+ 9 - 2
src/components/rc-radio/rc-radio.vue

@@ -1,15 +1,22 @@
 <script lang="ts" name="rc-radio" setup>
 import type { FormCreateProps } from '@form-create/element-ui'
 import RcCreateOption from '../rc-options/create-option.vue'
-import { ref } from 'vue'
+import { onMounted, ref } from 'vue'
 
-defineProps<{
+const props = defineProps<{
   formCreateInject: FormCreateProps & {
     field: string
   }
+  defaultValue: string
 }>()
 
 const radioValue = ref('')
+
+onMounted(() => {
+  if (props.defaultValue) {
+    radioValue.value = props.defaultValue
+  }
+})
 </script>
 <template>
   <RcCreateOption :formCreateInject="formCreateInject" v-slot="{ options }">

+ 145 - 0
src/components/rc-switch/rc-switch.vue

@@ -0,0 +1,145 @@
+<script lang="ts" name="rc-select" setup>
+import type { FormCreateProps } from '@form-create/element-ui'
+import { nextTick, onMounted, ref } from 'vue'
+
+const props = defineProps<{
+  formCreateInject: FormCreateProps
+}>()
+
+const seniority = ref(false)
+const selectTag = ref('')
+const centerDialogVisible = ref(false)
+const radio1 = ref('')
+// props.formCreateInject?.api?.activeRule
+const onSwitchChange = (val: boolean) => {
+  // 设置客户画像字段
+  if (val) {
+    props.formCreateInject!.api!.activeRule.synchronize_field = {
+      field_name: selectTag.value,
+      // TODO
+      synchronize_to: '',
+      synchronize_type: '',
+    }
+  } else {
+    props.formCreateInject!.api!.activeRule.synchronize_field = null
+  }
+}
+
+const onBeforeClose = (done: () => void) => {
+  radio1.value = props.formCreateInject?.api?.activeRule?.synchronize_field?.field_name || ''
+  done()
+}
+
+const onSave = () => {
+  centerDialogVisible.value = false
+  selectTag.value = radio1.value
+}
+
+onMounted(() => {
+  nextTick(() => {
+    // 这里是为了获取到父组件传递过来的值
+    if (!props.formCreateInject?.api?.activeRule) return
+
+    seniority.value = !!props.formCreateInject?.api?.activeRule?.synchronize_field
+    selectTag.value = props.formCreateInject?.api?.activeRule?.synchronize_field?.field_name || ''
+  })
+})
+</script>
+<template>
+  <div class="rc-switch">
+    <div class="edit">
+      <el-switch v-model="seniority" @change="onSwitchChange" />
+      <span>自动同步至客户画像</span>
+    </div>
+    <div class="aaatip" v-if="seniority">
+      <span>开启后,将同步至【客户画像-{{ selectTag }}】</span
+      ><el-text
+        class="mx-1"
+        type="primary"
+        style="cursor: pointer"
+        @click="centerDialogVisible = true"
+        >修改</el-text
+      >
+    </div>
+  </div>
+  <!-- <el-icon><Bell /></el-icon> -->
+  <el-dialog
+    destroy-on-close
+    v-model="centerDialogVisible"
+    :before-close="onBeforeClose"
+    title="同步客户画像设置"
+    center
+  >
+    <div class="body">
+      <div class="t">
+        <el-icon color="#1890ff"><BellFilled style="height: 40px" /></el-icon>
+        <div>
+          <div>1、请选择将表单信息同步至一下客户画像字段</div>
+          <div>2、建议避免多个元素设置相同画像字段,以免造成同步信息混乱</div>
+        </div>
+      </div>
+      <div class="tags">
+        <div class="title">基本信息</div>
+        <el-radio-group v-model="radio1">
+          <div class="el-radio" v-for="item in 10" :key="item">
+            <el-radio :value="item" size="small" border>Option {{ item }}</el-radio>
+          </div>
+        </el-radio-group>
+      </div>
+    </div>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="centerDialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="onSave">确定</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<style scoped>
+.dialog-footer {
+  width: 100%;
+  display: flex;
+  justify-content: flex-end;
+  gap: 10px;
+  align-items: center;
+}
+.rc-switch {
+  display: flex;
+  flex-direction: column;
+}
+
+.edit {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+
+.aaatip {
+  color: #999;
+}
+
+.t {
+  display: flex;
+  gap: 10px;
+  background-color: #f1f8fe;
+  padding: 10px;
+  align-items: center;
+}
+
+.body {
+  margin: 20px;
+}
+
+.tags {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+  margin-top: 20px;
+}
+
+.title {
+  font-weight: 600;
+  font-size: 16px;
+}
+</style>

+ 0 - 75
src/components/rc-upload/rc-upload.vue

@@ -1,75 +0,0 @@
-<script lang="ts" setup name="rc-upload">
-import { ref } from 'vue'
-import { ElMessage } from 'element-plus'
-import { Plus } from '@element-plus/icons-vue'
-import type { UploadProps } from 'element-plus'
-import type { FormCreateProps } from '@form-create/element-ui'
-
-const props = defineProps<{
-  formCreateInject: FormCreateProps
-  value: string
-}>()
-
-const imageUrl = ref('')
-
-const ImgType = ['jpeg', 'png', 'gif']
-const validateFileType = (name: string) => {
-  const n = name.split('/')[1]
-  return !!ImgType.includes(n)
-}
-
-const handleAvatarSuccess: UploadProps['onChange'] = async (uploadFile) => {
-  const formData = new FormData()
-  formData.append('file', uploadFile.raw as Blob)
-  formData.append('access_token_openwork', localStorage.getItem('access_token_openwork')!)
-  const res = await window.__QIANKUN__REQUSET__?.Upload._getUrl(formData)
-  console.log(res)
-}
-
-const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
-  if (validateFileType(rawFile.type)) {
-    ElMessage.error('Avatar picture must be JPG, PNG, or GIF format!')
-    return false
-  } else if (rawFile.size / 1024 / 1024 > 2) {
-    ElMessage.error('Avatar picture size can not exceed 2MB!')
-    return false
-  }
-  return true
-}
-</script>
-<template>
-  <el-upload
-    class="avatar-uploader"
-    :show-file-list="false"
-    :auto-upload="false"
-    :on-change="handleAvatarSuccess"
-    :before-upload="beforeAvatarUpload"
-    v-bind="$attrs"
-  >
-    <img v-if="imageUrl" :src="imageUrl" class="avatar" />
-    <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
-  </el-upload>
-</template>
-
-<style>
-.avatar-uploader .el-upload {
-  border: 1px dashed var(--el-border-color);
-  border-radius: 6px;
-  cursor: pointer;
-  position: relative;
-  overflow: hidden;
-  transition: var(--el-transition-duration-fast);
-}
-
-.avatar-uploader .el-upload:hover {
-  border-color: var(--el-color-primary);
-}
-
-.el-icon.avatar-uploader-icon {
-  font-size: 28px;
-  color: #8c939d;
-  width: 178px;
-  height: 178px;
-  text-align: center;
-}
-</style>

+ 58 - 6
src/form.config.ts

@@ -2,8 +2,6 @@ import type { Config } from '@form-create/designer'
 
 // 配置项
 export const formConfig: Config = {
-  //是否显示保存按钮
-  showSaveBtn: true,
   //配置设计区域显示方式
   device: 'mobile',
   // 隐藏所有子表单组件
@@ -39,6 +37,8 @@ export const formConfig: Config = {
   showLanguage: false,
   //是否显示json预览按钮
   showJsonPreview: false,
+  // 展示编辑按钮
+  showInputData: false,
   //隐藏组件的部分配置项
   hiddenItemConfig: {
     default: [
@@ -75,6 +75,8 @@ export const formConfig: Config = {
   },
   // 修改组件的默认信息
   updateDefaultRule: {},
+  //是否显示保存按钮
+  showSaveBtn: true,
   showDevice: false, // 隐藏多端适配选项
   showStyleForm: false, // 隐藏组件的样式配置
   showControl: false, // 隐藏组件的联动数据配置
@@ -84,12 +86,62 @@ export const formConfig: Config = {
   showCustomProps: false, //是否显示自定义props按钮
   // 自定义右侧菜单
   componentRule: {
-    // default: {},
-    select: {
+    default: {
       prepend: true,
-      rule() {
-        return []
+      rule(rule) {
+        return [
+          {
+            type: 'input',
+            field: 'defaultValue',
+            label: '默认值',
+            title: '默认值',
+            on: {
+              change: (val: string) => {
+                rule.value = val
+              },
+            },
+          },
+          {
+            type: 'rc-switch',
+            field: 'synchronize_field',
+            label: '高级功能',
+            title: '高级功能',
+            props: {
+              rule,
+            },
+          },
+        ]
       },
     },
   },
+  // 自定义表单事件
+  formRule() {
+    return [
+      {
+        type: 'input',
+        field: 'formName',
+        label: '表单名称',
+        title: '表单名称',
+      },
+      {
+        type: 'upload',
+        field: 'cover',
+        label: '头图',
+        title: '头图',
+        props: {
+          limit: 1,
+          accept: 'image/*',
+          action: '/openwork/media/uploadimg',
+          drag: true,
+          data: {
+            access_token_openwork: localStorage.getItem('access_token_openwork'),
+          },
+          onSuccess(res: { data: { url: string } }, file: { url: string }) {
+            file.url = res.data.url
+            window.__QIANKUN__EVENT__.emit('formCoverChange', res.data.url)
+          },
+        },
+      },
+    ]
+  },
 }

+ 0 - 34
src/plugins/components/rc-upload.config.ts

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

+ 0 - 13
src/plugins/menu/upload.ts

@@ -1,13 +0,0 @@
-import type { Menu } from '@form-create/designer'
-
-export const MenuUpload: Menu = {
-  title: '上传组件',
-  name: 'rc-upload-component',
-  list: [
-    {
-      name: 'rc-upload',
-      icon: 'icon-upload',
-      label: '图片',
-    },
-  ],
-}