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 | 3 | import cn.hutool.core.lang.Assert; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | 5 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 6 | -import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; | |
| 7 | 6 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormCreateReqVO; |
| 8 | 7 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO; |
| 9 | 8 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormUpdateReqVO; |
| ... | ... | @@ -107,17 +106,15 @@ public class BpmFormServiceImpl implements BpmFormService { |
| 107 | 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 | 110 | * 校验 Field,避免 field 重复 |
| 117 | 111 | * |
| 118 | 112 | * @param fields field 数组 |
| 119 | 113 | */ |
| 120 | 114 | private void checkFields(List<String> fields) { |
| 115 | + if (true) { // TODO 芋艿:兼容 Vue3 工作流:因为采用了新的表单设计器,所以暂时不校验 | |
| 116 | + return; | |
| 117 | + } | |
| 121 | 118 | Map<String, String> fieldMap = new HashMap<>(); // key 是 vModel,value 是 label |
| 122 | 119 | for (String field : fields) { |
| 123 | 120 | BpmFormFieldRespDTO fieldDTO = JsonUtils.parseObject(field, BpmFormFieldRespDTO.class); | ... | ... |
yudao-ui-admin-vue3/src/api/bpm/form/index.ts
| 1 | 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 | 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
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 | 8 | </fc-designer> |
| 9 | 9 | <!-- 表单保存的弹窗 --> |
| 10 | 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 | 12 | <el-form-item label="表单名" prop="name"> |
| 13 | 13 | <el-input v-model="formValues.name" placeholder="请输入表单名" /> |
| 14 | 14 | </el-form-item> |
| ... | ... | @@ -43,22 +43,26 @@ |
| 43 | 43 | </ContentWrap> |
| 44 | 44 | </template> |
| 45 | 45 | <script setup lang="ts" name="BpmFormEditor"> |
| 46 | +import { reactive } from 'vue' | |
| 47 | +import { FormInstance } from 'element-plus' | |
| 46 | 48 | import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
| 47 | 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 | 52 | const { t } = useI18n() // 国际化 |
| 51 | -// const message = useMessage() // 消息 | |
| 53 | +const message = useMessage() // 消息 | |
| 54 | +const { query } = useRoute() // 路由 | |
| 52 | 55 | |
| 53 | 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 | 61 | const formRules = reactive({ |
| 58 | 62 | name: [{ required: true, message: '表单名不能为空', trigger: 'blur' }], |
| 59 | 63 | status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }] |
| 60 | 64 | }) |
| 61 | -const formValues = reactive({ | |
| 65 | +const formValues = ref({ | |
| 62 | 66 | name: '', |
| 63 | 67 | status: CommonStatusEnum.ENABLE, |
| 64 | 68 | remark: '' |
| ... | ... | @@ -71,9 +75,42 @@ const handleSave = () => { |
| 71 | 75 | |
| 72 | 76 | // 提交保存表单 |
| 73 | 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 | 116 | </script> | ... | ... |
yudao-ui-admin-vue3/src/views/bpm/form/index.vue
| ... | ... | @@ -37,12 +37,9 @@ |
| 37 | 37 | /> |
| 38 | 38 | </template> |
| 39 | 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 | 43 | </XModal> |
| 47 | 44 | </div> |
| 48 | 45 | </ContentWrap> |
| ... | ... | @@ -50,21 +47,18 @@ |
| 50 | 47 | |
| 51 | 48 | <script setup lang="ts" name="BpmForm"> |
| 52 | 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 | 50 | // 业务相关的 import |
| 59 | 51 | import * as FormApi from '@/api/bpm/form' |
| 60 | 52 | import { allSchemas } from './form.data' |
| 61 | -import { useRouter } from 'vue-router' | |
| 62 | -// import { decodeFields } from '@/utils/formGenerator' // TODO 芋艿:可能要清理下 | |
| 63 | 53 | |
| 64 | 54 | const { t } = useI18n() // 国际化 |
| 65 | -const message = useMessage() // 消息弹窗 | |
| 66 | 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 | 63 | const [registerTable, { deleteData }] = useXTable({ |
| 70 | 64 | allSchemas: allSchemas, |
| ... | ... | @@ -72,23 +66,6 @@ const [registerTable, { deleteData }] = useXTable({ |
| 72 | 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 | 70 | const handleCreate = () => { |
| 94 | 71 | router.push({ |
| ... | ... | @@ -98,38 +75,25 @@ const handleCreate = () => { |
| 98 | 75 | |
| 99 | 76 | // 修改操作 |
| 100 | 77 | const handleUpdate = async (rowId: number) => { |
| 101 | - console.log(rowId, '修改') | |
| 102 | - if (true) { | |
| 103 | - message.success('动态表单开发中,预计 2 月底完成') | |
| 104 | - return | |
| 105 | - } | |
| 106 | 78 | await router.push({ |
| 107 | - path: '/bpm/manager/form/edit', | |
| 79 | + name: 'bpmFormEditor', | |
| 108 | 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 | 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 | 99 | </script> | ... | ... |