Commit fe6adf693b94cfe6f3292f7e0396c7114e335b23
1 parent
a3f57a44
bpm:完成流程表单
Showing
7 changed files
with
127 additions
and
84 deletions
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmFormServiceImpl.java
| @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.bpm.service.definition; | @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.bpm.service.definition; | ||
| 3 | import cn.hutool.core.lang.Assert; | 3 | import cn.hutool.core.lang.Assert; |
| 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | 5 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 6 | -import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; | ||
| 7 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormCreateReqVO; | 6 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormCreateReqVO; |
| 8 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO; | 7 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO; |
| 9 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormUpdateReqVO; | 8 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormUpdateReqVO; |
| @@ -107,17 +106,15 @@ public class BpmFormServiceImpl implements BpmFormService { | @@ -107,17 +106,15 @@ public class BpmFormServiceImpl implements BpmFormService { | ||
| 107 | return null; | 106 | return null; |
| 108 | } | 107 | } |
| 109 | 108 | ||
| 110 | - private void checkKeyNCName(String key) { | ||
| 111 | - if (!ValidationUtils.isXmlNCName(key)) { | ||
| 112 | - throw exception(MODEL_KEY_VALID); | ||
| 113 | - } | ||
| 114 | - } | ||
| 115 | /** | 109 | /** |
| 116 | * 校验 Field,避免 field 重复 | 110 | * 校验 Field,避免 field 重复 |
| 117 | * | 111 | * |
| 118 | * @param fields field 数组 | 112 | * @param fields field 数组 |
| 119 | */ | 113 | */ |
| 120 | private void checkFields(List<String> fields) { | 114 | private void checkFields(List<String> fields) { |
| 115 | + if (true) { // TODO 芋艿:兼容 Vue3 工作流:因为采用了新的表单设计器,所以暂时不校验 | ||
| 116 | + return; | ||
| 117 | + } | ||
| 121 | Map<String, String> fieldMap = new HashMap<>(); // key 是 vModel,value 是 label | 118 | Map<String, String> fieldMap = new HashMap<>(); // key 是 vModel,value 是 label |
| 122 | for (String field : fields) { | 119 | for (String field : fields) { |
| 123 | BpmFormFieldRespDTO fieldDTO = JsonUtils.parseObject(field, BpmFormFieldRespDTO.class); | 120 | BpmFormFieldRespDTO fieldDTO = JsonUtils.parseObject(field, BpmFormFieldRespDTO.class); |
yudao-ui-admin-vue3/src/api/bpm/form/index.ts
| 1 | import request from '@/config/axios' | 1 | import request from '@/config/axios' |
| 2 | -import { FormVO } from './types' | 2 | + |
| 3 | +export type FormVO = { | ||
| 4 | + id: number | ||
| 5 | + name: string | ||
| 6 | + conf: string | ||
| 7 | + fields: string[] | ||
| 8 | + status: number | ||
| 9 | + remark: string | ||
| 10 | + createTime: string | ||
| 11 | +} | ||
| 3 | 12 | ||
| 4 | // 创建工作流的表单定义 | 13 | // 创建工作流的表单定义 |
| 5 | export const createFormApi = async (data: FormVO) => { | 14 | export const createFormApi = async (data: FormVO) => { |
yudao-ui-admin-vue3/src/api/bpm/form/types.ts deleted
yudao-ui-admin-vue3/src/router/modules/remaining.ts
| @@ -194,7 +194,7 @@ const remainingRouter: AppRouteRecordRaw[] = [ | @@ -194,7 +194,7 @@ const remainingRouter: AppRouteRecordRaw[] = [ | ||
| 194 | noCache: true, | 194 | noCache: true, |
| 195 | hidden: true, | 195 | hidden: true, |
| 196 | canTo: true, | 196 | canTo: true, |
| 197 | - title: '流程表单', | 197 | + title: '设计流程表单', |
| 198 | activeMenu: 'bpm/manager/form/formEditor' | 198 | activeMenu: 'bpm/manager/form/formEditor' |
| 199 | } | 199 | } |
| 200 | }, | 200 | }, |
yudao-ui-admin-vue3/src/utils/formCreate.ts
0 → 100644
| 1 | +/** | ||
| 2 | + * 针对 https://github.com/xaboy/form-create-designer 封装的工具类 | ||
| 3 | + */ | ||
| 4 | + | ||
| 5 | +// 编码表单 Conf | ||
| 6 | +export const encodeConf = (designerRef: object) => { | ||
| 7 | + // @ts-ignore | ||
| 8 | + return JSON.stringify(designerRef.value.getOption()) | ||
| 9 | +} | ||
| 10 | + | ||
| 11 | +// 编码表单 Fields | ||
| 12 | +export const encodeFields = (designerRef: object) => { | ||
| 13 | + // @ts-ignore | ||
| 14 | + const rule = designerRef.value.getRule() | ||
| 15 | + const fields: string[] = [] | ||
| 16 | + rule.forEach((item) => { | ||
| 17 | + fields.push(JSON.stringify(item)) | ||
| 18 | + }) | ||
| 19 | + return fields | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | +// 解码表单 Fields | ||
| 23 | +export const decodeFields = (fields: string[]) => { | ||
| 24 | + const rule: object[] = [] | ||
| 25 | + fields.forEach((item) => { | ||
| 26 | + rule.push(JSON.parse(item)) | ||
| 27 | + }) | ||
| 28 | + return rule | ||
| 29 | +} | ||
| 30 | + | ||
| 31 | +// 设置表单的 Conf 和 Fields | ||
| 32 | +export const setConfAndFields = (designerRef: object, conf: string, fields: string) => { | ||
| 33 | + // @ts-ignore | ||
| 34 | + designerRef.value.setOption(JSON.parse(conf)) | ||
| 35 | + // @ts-ignore | ||
| 36 | + designerRef.value.setRule(decodeFields(fields)) | ||
| 37 | +} | ||
| 38 | + | ||
| 39 | +// 设置表单的 Conf 和 Fields | ||
| 40 | +export const setConfAndFields2 = (detailPreview: object, conf: string, fields: string) => { | ||
| 41 | + // @ts-ignore | ||
| 42 | + detailPreview.value.option = JSON.parse(conf) | ||
| 43 | + // @ts-ignore | ||
| 44 | + detailPreview.value.rule = decodeFields(fields) | ||
| 45 | +} |
yudao-ui-admin-vue3/src/views/bpm/form/formEditor.vue
| @@ -8,7 +8,7 @@ | @@ -8,7 +8,7 @@ | ||
| 8 | </fc-designer> | 8 | </fc-designer> |
| 9 | <!-- 表单保存的弹窗 --> | 9 | <!-- 表单保存的弹窗 --> |
| 10 | <XModal v-model="dialogVisible" title="保存表单"> | 10 | <XModal v-model="dialogVisible" title="保存表单"> |
| 11 | - <el-form :model="formValues" :rules="formRules" label-width="80px"> | 11 | + <el-form ref="formRef" :model="formValues" :rules="formRules" label-width="80px"> |
| 12 | <el-form-item label="表单名" prop="name"> | 12 | <el-form-item label="表单名" prop="name"> |
| 13 | <el-input v-model="formValues.name" placeholder="请输入表单名" /> | 13 | <el-input v-model="formValues.name" placeholder="请输入表单名" /> |
| 14 | </el-form-item> | 14 | </el-form-item> |
| @@ -43,22 +43,26 @@ | @@ -43,22 +43,26 @@ | ||
| 43 | </ContentWrap> | 43 | </ContentWrap> |
| 44 | </template> | 44 | </template> |
| 45 | <script setup lang="ts" name="BpmFormEditor"> | 45 | <script setup lang="ts" name="BpmFormEditor"> |
| 46 | +import { reactive } from 'vue' | ||
| 47 | +import { FormInstance } from 'element-plus' | ||
| 46 | import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' | 48 | import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
| 47 | import { CommonStatusEnum } from '@/utils/constants' | 49 | import { CommonStatusEnum } from '@/utils/constants' |
| 48 | -import { reactive } from 'vue' | ||
| 49 | - | 50 | +import * as FormApi from '@/api/bpm/form' |
| 51 | +import { encodeConf, encodeFields, setConfAndFields } from '@/utils/formCreate' | ||
| 50 | const { t } = useI18n() // 国际化 | 52 | const { t } = useI18n() // 国际化 |
| 51 | -// const message = useMessage() // 消息 | 53 | +const message = useMessage() // 消息 |
| 54 | +const { query } = useRoute() // 路由 | ||
| 52 | 55 | ||
| 53 | const designer = ref() // 表单设计器 | 56 | const designer = ref() // 表单设计器 |
| 54 | 57 | ||
| 55 | -const dialogVisible = ref(false) | ||
| 56 | -const dialogLoading = ref(false) | 58 | +const dialogVisible = ref(false) // 弹窗是否展示 |
| 59 | +const dialogLoading = ref(false) // 弹窗的加载中 | ||
| 60 | +const formRef = ref<FormInstance>() | ||
| 57 | const formRules = reactive({ | 61 | const formRules = reactive({ |
| 58 | name: [{ required: true, message: '表单名不能为空', trigger: 'blur' }], | 62 | name: [{ required: true, message: '表单名不能为空', trigger: 'blur' }], |
| 59 | status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }] | 63 | status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }] |
| 60 | }) | 64 | }) |
| 61 | -const formValues = reactive({ | 65 | +const formValues = ref({ |
| 62 | name: '', | 66 | name: '', |
| 63 | status: CommonStatusEnum.ENABLE, | 67 | status: CommonStatusEnum.ENABLE, |
| 64 | remark: '' | 68 | remark: '' |
| @@ -71,9 +75,42 @@ const handleSave = () => { | @@ -71,9 +75,42 @@ const handleSave = () => { | ||
| 71 | 75 | ||
| 72 | // 提交保存表单 | 76 | // 提交保存表单 |
| 73 | const submitForm = async () => { | 77 | const submitForm = async () => { |
| 74 | - console.log('保存') | 78 | + // 参数校验 |
| 79 | + const elForm = unref(formRef) | ||
| 80 | + if (!elForm) return | ||
| 81 | + const valid = await elForm.validate() | ||
| 82 | + if (!valid) return | ||
| 83 | + | ||
| 84 | + // 提交请求 | ||
| 85 | + dialogLoading.value = true | ||
| 86 | + try { | ||
| 87 | + const data = formValues.value as FormApi.FormVO | ||
| 88 | + data.conf = encodeConf(designer) // 表单配置 | ||
| 89 | + data.fields = encodeFields(designer) // 表单字段 | ||
| 90 | + if (!data.id) { | ||
| 91 | + await FormApi.createFormApi(data) | ||
| 92 | + message.success(t('common.createSuccess')) | ||
| 93 | + } else { | ||
| 94 | + await FormApi.updateFormApi(data) | ||
| 95 | + message.success(t('common.updateSuccess')) | ||
| 96 | + } | ||
| 97 | + dialogVisible.value = false | ||
| 98 | + } finally { | ||
| 99 | + dialogLoading.value = false | ||
| 100 | + } | ||
| 75 | } | 101 | } |
| 76 | 102 | ||
| 77 | -// formValue.value = designer.value.getOption() | ||
| 78 | -// formValue.value = designer.value.getRule() | 103 | +// ========== 初始化 ========== |
| 104 | +onMounted(() => { | ||
| 105 | + // 场景一:新增表单 | ||
| 106 | + const id = query.id as unknown as number | ||
| 107 | + if (!id) { | ||
| 108 | + return | ||
| 109 | + } | ||
| 110 | + // 场景二:修改表单 | ||
| 111 | + FormApi.getFormApi(id).then((data) => { | ||
| 112 | + formValues.value = data | ||
| 113 | + setConfAndFields(designer, data.conf, data.fields) | ||
| 114 | + }) | ||
| 115 | +}) | ||
| 79 | </script> | 116 | </script> |
yudao-ui-admin-vue3/src/views/bpm/form/index.vue
| @@ -37,12 +37,9 @@ | @@ -37,12 +37,9 @@ | ||
| 37 | /> | 37 | /> |
| 38 | </template> | 38 | </template> |
| 39 | </XTable> | 39 | </XTable> |
| 40 | - <!-- TODO 芋艿:待完善 --> | ||
| 41 | - <XModal v-model="detailOpen" width="400" title="表单详情"> | ||
| 42 | - <!-- <div class="test-form"> | ||
| 43 | - <parser :key="new Date().getTime()" :form-conf="detailForm" /> | ||
| 44 | - </div> --> | ||
| 45 | - 表单详情 | 40 | + <!-- 表单详情的弹窗 --> |
| 41 | + <XModal v-model="detailOpen" width="800" title="表单详情"> | ||
| 42 | + <ViewForm :rule="detailPreview.rule" :option="detailPreview.option" v-if="detailOpen" /> | ||
| 46 | </XModal> | 43 | </XModal> |
| 47 | </div> | 44 | </div> |
| 48 | </ContentWrap> | 45 | </ContentWrap> |
| @@ -50,21 +47,18 @@ | @@ -50,21 +47,18 @@ | ||
| 50 | 47 | ||
| 51 | <script setup lang="ts" name="BpmForm"> | 48 | <script setup lang="ts" name="BpmForm"> |
| 52 | // 全局相关的 import | 49 | // 全局相关的 import |
| 53 | -// import { ref } from 'vue' | ||
| 54 | -import { useI18n } from '@/hooks/web/useI18n' | ||
| 55 | -import { useMessage } from '@/hooks/web/useMessage' | ||
| 56 | -import { useXTable } from '@/hooks/web/useXTable' | ||
| 57 | -// import { FormExpose } from '@/components/Form' | ||
| 58 | // 业务相关的 import | 50 | // 业务相关的 import |
| 59 | import * as FormApi from '@/api/bpm/form' | 51 | import * as FormApi from '@/api/bpm/form' |
| 60 | import { allSchemas } from './form.data' | 52 | import { allSchemas } from './form.data' |
| 61 | -import { useRouter } from 'vue-router' | ||
| 62 | -// import { decodeFields } from '@/utils/formGenerator' // TODO 芋艿:可能要清理下 | ||
| 63 | 53 | ||
| 64 | const { t } = useI18n() // 国际化 | 54 | const { t } = useI18n() // 国际化 |
| 65 | -const message = useMessage() // 消息弹窗 | ||
| 66 | const router = useRouter() // 路由 | 55 | const router = useRouter() // 路由 |
| 67 | 56 | ||
| 57 | +// 表单详情相关的变量和 import | ||
| 58 | +import viewForm from '@form-create/element-ui' | ||
| 59 | +const ViewForm = viewForm.$form() | ||
| 60 | +import { setConfAndFields2 } from '@/utils/formCreate' | ||
| 61 | + | ||
| 68 | // 列表相关的变量 | 62 | // 列表相关的变量 |
| 69 | const [registerTable, { deleteData }] = useXTable({ | 63 | const [registerTable, { deleteData }] = useXTable({ |
| 70 | allSchemas: allSchemas, | 64 | allSchemas: allSchemas, |
| @@ -72,23 +66,6 @@ const [registerTable, { deleteData }] = useXTable({ | @@ -72,23 +66,6 @@ const [registerTable, { deleteData }] = useXTable({ | ||
| 72 | deleteApi: FormApi.deleteFormApi | 66 | deleteApi: FormApi.deleteFormApi |
| 73 | }) | 67 | }) |
| 74 | 68 | ||
| 75 | -// 弹窗相关的变量 | ||
| 76 | -// const modelVisible = ref(false) // 是否显示弹出层 | ||
| 77 | -// const modelTitle = ref('edit') // 弹出层标题 | ||
| 78 | -// const modelLoading = ref(false) // 弹出层loading | ||
| 79 | -// const actionType = ref('') // 操作按钮的类型 | ||
| 80 | -// const actionLoading = ref(false) // 按钮 Loading | ||
| 81 | -// const formRef = ref<FormExpose>() // 表单 Ref | ||
| 82 | -// const detailData = ref() // 详情 Ref | ||
| 83 | - | ||
| 84 | -// 设置标题 | ||
| 85 | -// const setDialogTile = (type: string) => { | ||
| 86 | -// modelLoading.value = true | ||
| 87 | -// modelTitle.value = t('action.' + type) | ||
| 88 | -// actionType.value = type | ||
| 89 | -// modelVisible.value = true | ||
| 90 | -// } | ||
| 91 | - | ||
| 92 | // 新增操作 | 69 | // 新增操作 |
| 93 | const handleCreate = () => { | 70 | const handleCreate = () => { |
| 94 | router.push({ | 71 | router.push({ |
| @@ -98,38 +75,25 @@ const handleCreate = () => { | @@ -98,38 +75,25 @@ const handleCreate = () => { | ||
| 98 | 75 | ||
| 99 | // 修改操作 | 76 | // 修改操作 |
| 100 | const handleUpdate = async (rowId: number) => { | 77 | const handleUpdate = async (rowId: number) => { |
| 101 | - console.log(rowId, '修改') | ||
| 102 | - if (true) { | ||
| 103 | - message.success('动态表单开发中,预计 2 月底完成') | ||
| 104 | - return | ||
| 105 | - } | ||
| 106 | await router.push({ | 78 | await router.push({ |
| 107 | - path: '/bpm/manager/form/edit', | 79 | + name: 'bpmFormEditor', |
| 108 | query: { | 80 | query: { |
| 109 | - formId: rowId | 81 | + id: rowId |
| 110 | } | 82 | } |
| 111 | }) | 83 | }) |
| 112 | } | 84 | } |
| 113 | 85 | ||
| 114 | -// 详情操作 // TODO 芋艿:详情的实现 | ||
| 115 | -// const detailForm = ref({ | ||
| 116 | -// fields: [] | ||
| 117 | -// }) | ||
| 118 | -// 表单详情弹出窗显示隐藏 | ||
| 119 | -// const detailOpen = ref(false) | ||
| 120 | - | 86 | +// 详情操作 |
| 87 | +const detailOpen = ref(false) | ||
| 88 | +const detailPreview = ref({ | ||
| 89 | + rule: [], | ||
| 90 | + option: {} | ||
| 91 | +}) | ||
| 121 | const handleDetail = async (rowId: number) => { | 92 | const handleDetail = async (rowId: number) => { |
| 122 | - console.log(rowId, '详情') | ||
| 123 | - message.success('动态表单开发中,预计 2 月底完成') | ||
| 124 | - // FormApi.getFormApi(row.id).then((response) => { | ||
| 125 | - // // 设置值 | ||
| 126 | - // const data = response.data | ||
| 127 | - // detailForm.value = { | ||
| 128 | - // ...JSON.parse(data.conf), | ||
| 129 | - // fields: decodeFields([], data.fields) | ||
| 130 | - // } | ||
| 131 | - // // 弹窗打开 | ||
| 132 | - // detailOpen.value = true | ||
| 133 | - // }) | 93 | + // 设置表单 |
| 94 | + const data = await FormApi.getFormApi(rowId) | ||
| 95 | + setConfAndFields2(detailPreview, data.conf, data.fields) | ||
| 96 | + // 弹窗打开 | ||
| 97 | + detailOpen.value = true | ||
| 134 | } | 98 | } |
| 135 | </script> | 99 | </script> |