Commit e29d6f91 by YunaiV

Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into feature/iot

# Conflicts:
#	pnpm-lock.yaml
#	src/utils/index.ts
parents c87ed7fe 41f2c719

20.7 KB | W: | H:

24.9 KB | W: | H:

.image/common/ai-feature.png
.image/common/ai-feature.png
.image/common/ai-feature.png
.image/common/ai-feature.png
  • 2-up
  • Swipe
  • Onion skin
...@@ -114,7 +114,8 @@ const include = [ ...@@ -114,7 +114,8 @@ const include = [
'element-plus/es/components/segmented/style/css', 'element-plus/es/components/segmented/style/css',
'@element-plus/icons-vue', '@element-plus/icons-vue',
'element-plus/es/components/footer/style/css', 'element-plus/es/components/footer/style/css',
'element-plus/es/components/empty/style/css' 'element-plus/es/components/empty/style/css',
'element-plus/es/components/mention/style/css'
] ]
const exclude = ['@iconify/json'] const exclude = ['@iconify/json']
......
{ {
"name": "yudao-ui-admin-vue3", "name": "yudao-ui-admin-vue3",
"version": "2.4.0-snapshot", "version": "2.4.1-snapshot",
"description": "基于vue3、vite4、element-plus、typesScript", "description": "基于vue3、vite4、element-plus、typesScript",
"author": "xingyu", "author": "xingyu",
"private": false, "private": false,
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^1.6.8", "axios": "^1.6.8",
"benz-amr-recorder": "^1.1.5", "benz-amr-recorder": "^1.1.5",
"bpmn-js-token-simulation": "^0.10.0", "bpmn-js-token-simulation": "^0.36.0",
"camunda-bpmn-moddle": "^7.0.1", "camunda-bpmn-moddle": "^7.0.1",
"cropperjs": "^1.6.1", "cropperjs": "^1.6.1",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
...@@ -74,6 +74,7 @@ ...@@ -74,6 +74,7 @@
"vue-i18n": "9.10.2", "vue-i18n": "9.10.2",
"vue-router": "4.4.5", "vue-router": "4.4.5",
"vue-types": "^5.1.1", "vue-types": "^5.1.1",
"vue3-signature": "^0.2.4",
"vuedraggable": "^4.1.0", "vuedraggable": "^4.1.0",
"web-storage-cache": "^1.1.1", "web-storage-cache": "^1.1.1",
"xml-js": "^1.6.11" "xml-js": "^1.6.11"
...@@ -92,6 +93,7 @@ ...@@ -92,6 +93,7 @@
"@typescript-eslint/eslint-plugin": "^7.1.0", "@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/parser": "^7.1.0", "@typescript-eslint/parser": "^7.1.0",
"@unocss/eslint-config": "^0.57.4", "@unocss/eslint-config": "^0.57.4",
"@unocss/eslint-plugin": "66.1.0-beta.5",
"@unocss/transformer-variant-group": "^0.58.5", "@unocss/transformer-variant-group": "^0.58.5",
"@vitejs/plugin-legacy": "^5.3.1", "@vitejs/plugin-legacy": "^5.3.1",
"@vitejs/plugin-vue": "^5.0.4", "@vitejs/plugin-vue": "^5.0.4",
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -14,9 +14,16 @@ export interface ChatMessageVO { ...@@ -14,9 +14,16 @@ export interface ChatMessageVO {
modelId: number // 模型编号 modelId: number // 模型编号
content: string // 聊天内容 content: string // 聊天内容
tokens: number // 消耗 Token 数量 tokens: number // 消耗 Token 数量
segmentIds?: number[] // 段落编号
segments?: {
id: number // 段落编号
content: string // 段落内容
documentId: number // 文档编号
documentName: string // 文档名称
}[]
createTime: Date // 创建时间 createTime: Date // 创建时间
roleAvatar: string // 角色头像 roleAvatar: string // 角色头像
userAvatar: string // 创建时间 userAvatar: string // 用户头像
} }
// AI chat 聊天 // AI chat 聊天
......
...@@ -20,9 +20,8 @@ export interface ImageVO { ...@@ -20,9 +20,8 @@ export interface ImageVO {
} }
export interface ImageDrawReqVO { export interface ImageDrawReqVO {
platform: string // 平台
prompt: string // 提示词 prompt: string // 提示词
model: string // 模型 modelId: number // 模型
style: string // 图像生成的风格 style: string // 图像生成的风格
width: string // 图片宽度 width: string // 图片宽度
height: string // 图片高度 height: string // 图片高度
...@@ -31,7 +30,7 @@ export interface ImageDrawReqVO { ...@@ -31,7 +30,7 @@ export interface ImageDrawReqVO {
export interface ImageMidjourneyImagineReqVO { export interface ImageMidjourneyImagineReqVO {
prompt: string // 提示词 prompt: string // 提示词
model: string // 模型 mj nijj modelId: number // 模型
base64Array: string[] // size不能为空 base64Array: string[] // size不能为空
width: string // 图片宽度 width: string // 图片宽度
height: string // 图片高度 height: string // 图片高度
......
import request from '@/config/axios'
// AI 知识库文档 VO
export interface KnowledgeDocumentVO {
id: number // 编号
knowledgeId: number // 知识库编号
name: string // 文档名称
contentLength: number // 字符数
tokens: number // token 数
segmentMaxTokens: number // 分片最大 token 数
retrievalCount: number // 召回次数
status: number // 是否启用
}
// AI 知识库文档 API
export const KnowledgeDocumentApi = {
// 查询知识库文档分页
getKnowledgeDocumentPage: async (params: any) => {
return await request.get({ url: `/ai/knowledge/document/page`, params })
},
// 查询知识库文档详情
getKnowledgeDocument: async (id: number) => {
return await request.get({ url: `/ai/knowledge/document/get?id=` + id })
},
// 新增知识库文档(单个)
createKnowledgeDocument: async (data: any) => {
return await request.post({ url: `/ai/knowledge/document/create`, data })
},
// 新增知识库文档(多个)
createKnowledgeDocumentList: async (data: any) => {
return await request.post({ url: `/ai/knowledge/document/create-list`, data })
},
// 修改知识库文档
updateKnowledgeDocument: async (data: any) => {
return await request.put({ url: `/ai/knowledge/document/update`, data })
},
// 修改知识库文档状态
updateKnowledgeDocumentStatus: async (data: any) => {
return await request.put({
url: `/ai/knowledge/document/update-status`,
data
})
},
// 删除知识库文档
deleteKnowledgeDocument: async (id: number) => {
return await request.delete({ url: `/ai/knowledge/document/delete?id=` + id })
}
}
import request from '@/config/axios'
// AI 知识库 VO
export interface KnowledgeVO {
id: number // 编号
name: string // 知识库名称
description: string // 知识库描述
embeddingModelId: number // 嵌入模型编号,高质量模式时维护
topK: number // topK
similarityThreshold: number // 相似度阈值
}
// AI 知识库 API
export const KnowledgeApi = {
// 查询知识库分页
getKnowledgePage: async (params: any) => {
return await request.get({ url: `/ai/knowledge/page`, params })
},
// 查询知识库详情
getKnowledge: async (id: number) => {
return await request.get({ url: `/ai/knowledge/get?id=` + id })
},
// 新增知识库
createKnowledge: async (data: KnowledgeVO) => {
return await request.post({ url: `/ai/knowledge/create`, data })
},
// 修改知识库
updateKnowledge: async (data: KnowledgeVO) => {
return await request.put({ url: `/ai/knowledge/update`, data })
},
// 删除知识库
deleteKnowledge: async (id: number) => {
return await request.delete({ url: `/ai/knowledge/delete?id=` + id })
},
// 获取知识库简单列表
getSimpleKnowledgeList: async () => {
return await request.get({ url: `/ai/knowledge/simple-list` })
}
}
import request from '@/config/axios'
// AI 知识库分段 VO
export interface KnowledgeSegmentVO {
id: number // 编号
documentId: number // 文档编号
knowledgeId: number // 知识库编号
vectorId: string // 向量库编号
content: string // 切片内容
contentLength: number // 切片内容长度
tokens: number // token 数量
retrievalCount: number // 召回次数
status: number // 文档状态
createTime: number // 创建时间
}
// AI 知识库分段 API
export const KnowledgeSegmentApi = {
// 查询知识库分段分页
getKnowledgeSegmentPage: async (params: any) => {
return await request.get({ url: `/ai/knowledge/segment/page`, params })
},
// 查询知识库分段详情
getKnowledgeSegment: async (id: number) => {
return await request.get({ url: `/ai/knowledge/segment/get?id=` + id })
},
// 删除知识库分段
deleteKnowledgeSegment: async (id: number) => {
return await request.delete({ url: `/ai/knowledge/segment/delete?id=` + id })
},
// 新增知识库分段
createKnowledgeSegment: async (data: KnowledgeSegmentVO) => {
return await request.post({ url: `/ai/knowledge/segment/create`, data })
},
// 修改知识库分段
updateKnowledgeSegment: async (data: KnowledgeSegmentVO) => {
return await request.put({ url: `/ai/knowledge/segment/update`, data })
},
// 修改知识库分段状态
updateKnowledgeSegmentStatus: async (data: any) => {
return await request.put({
url: `/ai/knowledge/segment/update-status`,
data
})
},
// 切片内容
splitContent: async (url: string, segmentMaxTokens: number) => {
return await request.get({
url: `/ai/knowledge/segment/split`,
params: { url, segmentMaxTokens }
})
},
// 获取文档处理列表
getKnowledgeSegmentProcessList: async (documentIds: number[]) => {
return await request.get({
url: `/ai/knowledge/segment/get-process-list`,
params: { documentIds: documentIds.join(',') }
})
},
// 搜索知识库分段
searchKnowledgeSegment: async (params: any) => {
return await request.get({
url: `/ai/knowledge/segment/search`,
params
})
}
}
import request from '@/config/axios'
// AI 聊天模型 VO
export interface ChatModelVO {
id: number // 编号
keyId: number // API 秘钥编号
name: string // 模型名字
model: string // 模型标识
platform: string // 模型平台
sort: number // 排序
status: number // 状态
temperature: number // 温度参数
maxTokens: number // 单条回复的最大 Token 数量
maxContexts: number // 上下文的最大 Message 数量
}
// AI 聊天模型 API
export const ChatModelApi = {
// 查询聊天模型分页
getChatModelPage: async (params: any) => {
return await request.get({ url: `/ai/chat-model/page`, params })
},
// 获得聊天模型列表
getChatModelSimpleList: async (status?: number) => {
return await request.get({
url: `/ai/chat-model/simple-list`,
params: {
status
}
})
},
// 查询聊天模型详情
getChatModel: async (id: number) => {
return await request.get({ url: `/ai/chat-model/get?id=` + id })
},
// 新增聊天模型
createChatModel: async (data: ChatModelVO) => {
return await request.post({ url: `/ai/chat-model/create`, data })
},
// 修改聊天模型
updateChatModel: async (data: ChatModelVO) => {
return await request.put({ url: `/ai/chat-model/update`, data })
},
// 删除聊天模型
deleteChatModel: async (id: number) => {
return await request.delete({ url: `/ai/chat-model/delete?id=` + id })
}
}
...@@ -13,6 +13,8 @@ export interface ChatRoleVO { ...@@ -13,6 +13,8 @@ export interface ChatRoleVO {
welcomeMessage: string // 角色设定 welcomeMessage: string // 角色设定
publicStatus: boolean // 是否公开 publicStatus: boolean // 是否公开
status: number // 状态 status: number // 状态
knowledgeIds?: number[] // 引用的知识库 ID 列表
toolIds?: number[] // 引用的工具 ID 列表
} }
// AI 聊天角色 分页请求 vo // AI 聊天角色 分页请求 vo
......
import request from '@/config/axios'
// AI 模型 VO
export interface ModelVO {
id: number // 编号
keyId: number // API 秘钥编号
name: string // 模型名字
model: string // 模型标识
platform: string // 模型平台
type: number // 模型类型
sort: number // 排序
status: number // 状态
temperature?: number // 温度参数
maxTokens?: number // 单条回复的最大 Token 数量
maxContexts?: number // 上下文的最大 Message 数量
}
// AI 模型 API
export const ModelApi = {
// 查询模型分页
getModelPage: async (params: any) => {
return await request.get({ url: `/ai/model/page`, params })
},
// 获得模型列表
getModelSimpleList: async (type?: number) => {
return await request.get({
url: `/ai/model/simple-list`,
params: {
type
}
})
},
// 查询模型详情
getModel: async (id: number) => {
return await request.get({ url: `/ai/model/get?id=` + id })
},
// 新增模型
createModel: async (data: ModelVO) => {
return await request.post({ url: `/ai/model/create`, data })
},
// 修改模型
updateModel: async (data: ModelVO) => {
return await request.put({ url: `/ai/model/update`, data })
},
// 删除模型
deleteModel: async (id: number) => {
return await request.delete({ url: `/ai/model/delete?id=` + id })
}
}
import request from '@/config/axios'
// AI 工具 VO
export interface ToolVO {
id: number // 工具编号
name: string // 工具名称
description: string // 工具描述
status: number // 状态
}
// AI 工具 API
export const ToolApi = {
// 查询工具分页
getToolPage: async (params: any) => {
return await request.get({ url: `/ai/tool/page`, params })
},
// 查询工具详情
getTool: async (id: number) => {
return await request.get({ url: `/ai/tool/get?id=` + id })
},
// 新增工具
createTool: async (data: ToolVO) => {
return await request.post({ url: `/ai/tool/create`, data })
},
// 修改工具
updateTool: async (data: ToolVO) => {
return await request.put({ url: `/ai/tool/update`, data })
},
// 删除工具
deleteTool: async (id: number) => {
return await request.delete({ url: `/ai/tool/delete?id=` + id })
},
// 获取工具简单列表
getToolSimpleList: async () => {
return await request.get({ url: `/ai/tool/simple-list` })
}
}
...@@ -20,3 +20,9 @@ export const getProcessDefinitionList = async (params) => { ...@@ -20,3 +20,9 @@ export const getProcessDefinitionList = async (params) => {
params params
}) })
} }
export const getSimpleProcessDefinitionList = async () => {
return await request.get({
url: '/bpm/process-definition/simple-list'
})
}
...@@ -72,3 +72,7 @@ export const deleteModel = async (id: number) => { ...@@ -72,3 +72,7 @@ export const deleteModel = async (id: number) => {
export const deployModel = async (id: number) => { export const deployModel = async (id: number) => {
return await request.post({ url: '/bpm/model/deploy?id=' + id }) return await request.post({ url: '/bpm/model/deploy?id=' + id })
} }
export const cleanModel = async (id: number) => {
return await request.delete({ url: '/bpm/model/clean?id=' + id })
}
...@@ -36,6 +36,7 @@ export type ApprovalTaskInfo = { ...@@ -36,6 +36,7 @@ export type ApprovalTaskInfo = {
assigneeUser: User assigneeUser: User
status: number status: number
reason: string reason: string
signPicUrl: string
} }
// 审批节点信息 // 审批节点信息
...@@ -89,7 +90,12 @@ export const getProcessInstanceCopyPage = async (params: any) => { ...@@ -89,7 +90,12 @@ export const getProcessInstanceCopyPage = async (params: any) => {
// 获取审批详情 // 获取审批详情
export const getApprovalDetail = async (params: any) => { export const getApprovalDetail = async (params: any) => {
return await request.get({ url: 'bpm/process-instance/get-approval-detail' , params }) return await request.get({ url: '/bpm/process-instance/get-approval-detail', params })
}
// 获取下一个执行的流程节点
export const getNextApprovalNodes = async (params: any) => {
return await request.get({ url: '/bpm/process-instance/get-next-approval-nodes', params })
} }
// 获取表单字段权限 // 获取表单字段权限
......
...@@ -83,5 +83,5 @@ export const reqCheck = (data: any) => { ...@@ -83,5 +83,5 @@ export const reqCheck = (data: any) => {
// 通过短信重置密码 // 通过短信重置密码
export const smsResetPassword = (data: any) => { export const smsResetPassword = (data: any) => {
return request.post({ url: '/system/auth/sms-reset-password', data }) return request.post({ url: '/system/auth/reset-password', data })
} }
...@@ -101,7 +101,7 @@ export const deleteSpu = (id: number) => { ...@@ -101,7 +101,7 @@ export const deleteSpu = (id: number) => {
} }
// 导出商品 Spu Excel // 导出商品 Spu Excel
export const exportSpu = async (params) => { export const exportSpu = async (params: any) => {
return await request.download({ url: '/product/spu/export', params }) return await request.download({ url: '/product/spu/export', params })
} }
......
<svg t="1740116949537" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1153" width="200" height="200"><path d="M440.32 296.96h283.30496v145.92h66.56V230.4H440.32V17.92H17.92v424.96H440.32V296.96zM373.76 376.32H84.48v-291.84H373.76v291.84zM586.24 588.8v143.36512H298.66496V586.24h-66.56v212.48512H586.24V1013.76H1008.64v-424.96h-422.4z m355.84 358.4h-289.28v-291.84H942.08v291.84z" p-id="1154" fill="#ffffff"></path></svg>
\ No newline at end of file
<svg t="1739406626368" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1300" width="200" height="200"><path d="M803.221 925.573H224.356c-68.568 0-124.352-55.784-124.352-124.353V222.356c0-68.568 55.784-124.352 124.352-124.352h355.311v64H224.356c-33.278 0-60.352 27.074-60.352 60.352V801.22c0 33.278 27.074 60.353 60.352 60.353H803.22c33.278 0 60.353-27.074 60.353-60.353V448.208h64V801.22c0 68.569-55.784 124.353-124.352 124.353z" fill="#ffffff" p-id="1301"></path><path d="M300.357 756.916l35.024-195.867L770.117 84.404c10.05-11.02 25.015-18.052 41.058-19.293 16.017-1.247 31.987 3.379 43.841 12.667l83.662 65.549c21.643 16.956 24.254 45.964 5.942 66.038l-437.613 479.8-206.65 67.751z m104.994-170.751l-13.14 73.487 69.671-22.842 415.465-455.517-59.909-46.939-412.087 451.811z" fill="#ffffff" p-id="1302"></path><path d="M732.25 220.897l41.144-49.023 81.151 68.11-41.145 49.023z" fill="#ffffff" p-id="1303"></path></svg>
\ No newline at end of file
...@@ -51,7 +51,8 @@ ...@@ -51,7 +51,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ComponentStyle, usePropertyForm } from '@/components/DiyEditor/util' import { ComponentStyle } from '@/components/DiyEditor/util'
import { useVModel } from '@vueuse/core'
/** /**
* 组件容器属性:目前右边部分 * 组件容器属性:目前右边部分
...@@ -61,7 +62,7 @@ defineOptions({ name: 'ComponentContainer' }) ...@@ -61,7 +62,7 @@ defineOptions({ name: 'ComponentContainer' })
const props = defineProps<{ modelValue: ComponentStyle }>() const props = defineProps<{ modelValue: ComponentStyle }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
const treeData = [ const treeData = [
{ {
......
...@@ -93,14 +93,14 @@ ...@@ -93,14 +93,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { CarouselProperty } from './config' import { CarouselProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
// 轮播图属性面板 // 轮播图属性面板
defineOptions({ name: 'CarouselProperty' }) defineOptions({ name: 'CarouselProperty' })
const props = defineProps<{ modelValue: CarouselProperty }>() const props = defineProps<{ modelValue: CarouselProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>
...@@ -68,15 +68,20 @@ ...@@ -68,15 +68,20 @@
</el-form> </el-form>
</ComponentContainerProperty> </ComponentContainerProperty>
<!-- 优惠券选择 --> <!-- 优惠券选择 -->
<CouponSelect ref="couponSelectDialog" v-model:multiple-selection="couponList" /> <CouponSelect
ref="couponSelectDialog"
v-model:multiple-selection="couponList"
:take-type="CouponTemplateTakeTypeEnum.USER.type"
@change="handleCouponSelect"
/>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { CouponCardProperty } from './config' import { CouponCardProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
import * as CouponTemplateApi from '@/api/mall/promotion/coupon/couponTemplate' import * as CouponTemplateApi from '@/api/mall/promotion/coupon/couponTemplate'
import { floatToFixed2 } from '@/utils' import { floatToFixed2 } from '@/utils'
import { PromotionDiscountTypeEnum } from '@/utils/constants' import { CouponTemplateTakeTypeEnum, PromotionDiscountTypeEnum } from '@/utils/constants'
import CouponSelect from '@/views/mall/promotion/coupon/components/CouponSelect.vue' import CouponSelect from '@/views/mall/promotion/coupon/components/CouponSelect.vue'
// 优惠券卡片属性面板 // 优惠券卡片属性面板
...@@ -84,7 +89,7 @@ defineOptions({ name: 'CouponCardProperty' }) ...@@ -84,7 +89,7 @@ defineOptions({ name: 'CouponCardProperty' })
const props = defineProps<{ modelValue: CouponCardProperty }>() const props = defineProps<{ modelValue: CouponCardProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
// 优惠券列表 // 优惠券列表
const couponList = ref<CouponTemplateApi.CouponTemplateVO[]>([]) const couponList = ref<CouponTemplateApi.CouponTemplateVO[]>([])
...@@ -93,10 +98,20 @@ const couponSelectDialog = ref() ...@@ -93,10 +98,20 @@ const couponSelectDialog = ref()
const handleAddCoupon = () => { const handleAddCoupon = () => {
couponSelectDialog.value.open() couponSelectDialog.value.open()
} }
const handleCouponSelect = () => {
formData.value.couponIds = couponList.value.map((coupon) => coupon.id)
}
watch( watch(
() => couponList.value, () => formData.value.couponIds,
() => { async () => {
formData.value.couponIds = couponList.value.map((coupon) => coupon.id) if (formData.value.couponIds?.length > 0) {
couponList.value = await CouponTemplateApi.getCouponTemplateList(formData.value.couponIds)
}
},
{
immediate: true,
deep: true
} }
) )
</script> </script>
......
...@@ -45,12 +45,12 @@ ...@@ -45,12 +45,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { DividerProperty } from './config' import { DividerProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
// 导航栏属性面板 // 导航栏属性面板
defineOptions({ name: 'DividerProperty' }) defineOptions({ name: 'DividerProperty' })
const props = defineProps<{ modelValue: DividerProperty }>() const props = defineProps<{ modelValue: DividerProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
//线类型 //线类型
const BORDER_TYPES = [ const BORDER_TYPES = [
......
...@@ -31,14 +31,14 @@ ...@@ -31,14 +31,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { FloatingActionButtonProperty } from './config' import { FloatingActionButtonProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
// 悬浮按钮属性面板 // 悬浮按钮属性面板
defineOptions({ name: 'FloatingActionButtonProperty' }) defineOptions({ name: 'FloatingActionButtonProperty' })
const props = defineProps<{ modelValue: FloatingActionButtonProperty }>() const props = defineProps<{ modelValue: FloatingActionButtonProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
import { HotZoneProperty } from '@/components/DiyEditor/components/mobile/HotZone/config' import { HotZoneProperty } from '@/components/DiyEditor/components/mobile/HotZone/config'
import HotZoneEditDialog from './components/HotZoneEditDialog/index.vue' import HotZoneEditDialog from './components/HotZoneEditDialog/index.vue'
...@@ -29,7 +29,7 @@ defineOptions({ name: 'HotZoneProperty' }) ...@@ -29,7 +29,7 @@ defineOptions({ name: 'HotZoneProperty' })
const props = defineProps<{ modelValue: HotZoneProperty }>() const props = defineProps<{ modelValue: HotZoneProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
// 热区编辑对话框 // 热区编辑对话框
const editDialogRef = ref() const editDialogRef = ref()
......
...@@ -21,14 +21,14 @@ ...@@ -21,14 +21,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { ImageBarProperty } from './config' import { ImageBarProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
// 图片展示属性面板 // 图片展示属性面板
defineOptions({ name: 'ImageBarProperty' }) defineOptions({ name: 'ImageBarProperty' })
const props = defineProps<{ modelValue: ImageBarProperty }>() const props = defineProps<{ modelValue: ImageBarProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
import { MagicCubeProperty } from '@/components/DiyEditor/components/mobile/MagicCube/config' import { MagicCubeProperty } from '@/components/DiyEditor/components/mobile/MagicCube/config'
/** 广告魔方属性面板 */ /** 广告魔方属性面板 */
...@@ -64,7 +64,7 @@ defineOptions({ name: 'MagicCubeProperty' }) ...@@ -64,7 +64,7 @@ defineOptions({ name: 'MagicCubeProperty' })
const props = defineProps<{ modelValue: MagicCubeProperty }>() const props = defineProps<{ modelValue: MagicCubeProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
// 选中的热区 // 选中的热区
const selectedHotAreaIndex = ref(-1) const selectedHotAreaIndex = ref(-1)
......
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
import { import {
EMPTY_MENU_GRID_ITEM_PROPERTY, EMPTY_MENU_GRID_ITEM_PROPERTY,
MenuGridProperty MenuGridProperty
...@@ -59,7 +59,7 @@ defineOptions({ name: 'MenuGridProperty' }) ...@@ -59,7 +59,7 @@ defineOptions({ name: 'MenuGridProperty' })
const props = defineProps<{ modelValue: MenuGridProperty }>() const props = defineProps<{ modelValue: MenuGridProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
import { import {
EMPTY_MENU_LIST_ITEM_PROPERTY, EMPTY_MENU_LIST_ITEM_PROPERTY,
MenuListProperty MenuListProperty
...@@ -39,7 +39,7 @@ defineOptions({ name: 'MenuListProperty' }) ...@@ -39,7 +39,7 @@ defineOptions({ name: 'MenuListProperty' })
const props = defineProps<{ modelValue: MenuListProperty }>() const props = defineProps<{ modelValue: MenuListProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>
...@@ -58,7 +58,7 @@ ...@@ -58,7 +58,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
import { import {
EMPTY_MENU_SWIPER_ITEM_PROPERTY, EMPTY_MENU_SWIPER_ITEM_PROPERTY,
MenuSwiperProperty MenuSwiperProperty
...@@ -70,7 +70,7 @@ defineOptions({ name: 'MenuSwiperProperty' }) ...@@ -70,7 +70,7 @@ defineOptions({ name: 'MenuSwiperProperty' })
const props = defineProps<{ modelValue: MenuSwiperProperty }>() const props = defineProps<{ modelValue: MenuSwiperProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>
...@@ -64,17 +64,22 @@ ...@@ -64,17 +64,22 @@
<script lang="ts" setup> <script lang="ts" setup>
import { NavigationBarCellProperty } from '../config' import { NavigationBarCellProperty } from '../config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
// 导航栏属性面板 // 导航栏属性面板
defineOptions({ name: 'NavigationBarCellProperty' }) defineOptions({ name: 'NavigationBarCellProperty' })
const props = defineProps<{ const props = withDefaults(
modelValue: NavigationBarCellProperty[] defineProps<{
isMp: boolean modelValue: NavigationBarCellProperty[]
}>() isMp: boolean
}>(),
{
modelValue: () => [],
isMp: true
}
)
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData: cellList } = usePropertyForm(props.modelValue, emit) const cellList = useVModel(props, 'modelValue', emit)
if (!cellList.value) cellList.value = []
// 单元格数量:小程序6个(右侧胶囊按钮占了2个),其它平台8个 // 单元格数量:小程序6个(右侧胶囊按钮占了2个),其它平台8个
const cellCount = computed(() => (props.isMp ? 6 : 8)) const cellCount = computed(() => (props.isMp ? 6 : 8))
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<div v-for="(cell, cellIndex) in cellList" :key="cellIndex" :style="getCellStyle(cell)"> <div v-for="(cell, cellIndex) in cellList" :key="cellIndex" :style="getCellStyle(cell)">
<span v-if="cell.type === 'text'">{{ cell.text }}</span> <span v-if="cell.type === 'text'">{{ cell.text }}</span>
<img v-else-if="cell.type === 'image'" :src="cell.imgUrl" alt="" class="h-full w-full" /> <img v-else-if="cell.type === 'image'" :src="cell.imgUrl" alt="" class="h-full w-full" />
<SearchBar v-else :property="getSearchProp" /> <SearchBar v-else :property="getSearchProp(cell)" />
</div> </div>
</div> </div>
<img <img
...@@ -51,14 +51,14 @@ const getCellStyle = (cell: NavigationBarCellProperty) => { ...@@ -51,14 +51,14 @@ const getCellStyle = (cell: NavigationBarCellProperty) => {
} as StyleValue } as StyleValue
} }
// 获得搜索框属性 // 获得搜索框属性
const getSearchProp = (cell: NavigationBarCellProperty) => { const getSearchProp = computed(() => (cell: NavigationBarCellProperty) => {
return { return {
height: 30, height: 30,
showScan: false, showScan: false,
placeholder: cell.placeholder, placeholder: cell.placeholder,
borderRadius: cell.borderRadius borderRadius: cell.borderRadius
} as SearchProperty } as SearchProperty
} })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.navigation-bar { .navigation-bar {
......
...@@ -66,7 +66,7 @@ ...@@ -66,7 +66,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { NavigationBarProperty } from './config' import { NavigationBarProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
import NavigationBarCellProperty from '@/components/DiyEditor/components/mobile/NavigationBar/components/CellProperty.vue' import NavigationBarCellProperty from '@/components/DiyEditor/components/mobile/NavigationBar/components/CellProperty.vue'
// 导航栏属性面板 // 导航栏属性面板
defineOptions({ name: 'NavigationBarProperty' }) defineOptions({ name: 'NavigationBarProperty' })
...@@ -77,7 +77,7 @@ const rules = { ...@@ -77,7 +77,7 @@ const rules = {
const props = defineProps<{ modelValue: NavigationBarProperty }>() const props = defineProps<{ modelValue: NavigationBarProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
if (!formData.value._local) { if (!formData.value._local) {
formData.value._local = { previewMp: true, previewOther: false } formData.value._local = { previewMp: true, previewOther: false }
} }
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { NoticeBarProperty } from './config' import { NoticeBarProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
// 通知栏属性面板 // 通知栏属性面板
defineOptions({ name: 'NoticeBarProperty' }) defineOptions({ name: 'NoticeBarProperty' })
// 表单校验 // 表单校验
...@@ -40,7 +40,7 @@ const rules = { ...@@ -40,7 +40,7 @@ const rules = {
const props = defineProps<{ modelValue: NoticeBarProperty }>() const props = defineProps<{ modelValue: NoticeBarProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { PageConfigProperty } from './config' import { PageConfigProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
// 导航栏属性面板 // 导航栏属性面板
defineOptions({ name: 'PageConfigProperty' }) defineOptions({ name: 'PageConfigProperty' })
// 表单校验 // 表单校验
...@@ -28,7 +28,7 @@ const rules = {} ...@@ -28,7 +28,7 @@ const rules = {}
const props = defineProps<{ modelValue: PageConfigProperty }>() const props = defineProps<{ modelValue: PageConfigProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>
...@@ -25,14 +25,14 @@ ...@@ -25,14 +25,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { PopoverProperty } from './config' import { PopoverProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
// 弹窗广告属性面板 // 弹窗广告属性面板
defineOptions({ name: 'PopoverProperty' }) defineOptions({ name: 'PopoverProperty' })
const props = defineProps<{ modelValue: PopoverProperty }>() const props = defineProps<{ modelValue: PopoverProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>
...@@ -135,7 +135,7 @@ ...@@ -135,7 +135,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ProductCardProperty } from './config' import { ProductCardProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
import SpuShowcase from '@/views/mall/product/spu/components/SpuShowcase.vue' import SpuShowcase from '@/views/mall/product/spu/components/SpuShowcase.vue'
// 商品卡片属性面板 // 商品卡片属性面板
...@@ -143,7 +143,7 @@ defineOptions({ name: 'ProductCardProperty' }) ...@@ -143,7 +143,7 @@ defineOptions({ name: 'ProductCardProperty' })
const props = defineProps<{ modelValue: ProductCardProperty }>() const props = defineProps<{ modelValue: ProductCardProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>
...@@ -85,7 +85,7 @@ ...@@ -85,7 +85,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ProductListProperty } from './config' import { ProductListProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
import SpuShowcase from '@/views/mall/product/spu/components/SpuShowcase.vue' import SpuShowcase from '@/views/mall/product/spu/components/SpuShowcase.vue'
// 商品栏属性面板 // 商品栏属性面板
...@@ -93,7 +93,7 @@ defineOptions({ name: 'ProductListProperty' }) ...@@ -93,7 +93,7 @@ defineOptions({ name: 'ProductListProperty' })
const props = defineProps<{ modelValue: ProductListProperty }>() const props = defineProps<{ modelValue: ProductListProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { PromotionArticleProperty } from './config' import { PromotionArticleProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
import * as ArticleApi from '@/api/mall/promotion/article/index' import * as ArticleApi from '@/api/mall/promotion/article/index'
// 营销文章属性面板 // 营销文章属性面板
...@@ -33,7 +33,7 @@ defineOptions({ name: 'PromotionArticleProperty' }) ...@@ -33,7 +33,7 @@ defineOptions({ name: 'PromotionArticleProperty' })
const props = defineProps<{ modelValue: PromotionArticleProperty }>() const props = defineProps<{ modelValue: PromotionArticleProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
// 文章列表 // 文章列表
const articles = ref<ArticleApi.ArticleVO>([]) const articles = ref<ArticleApi.ArticleVO>([])
......
...@@ -140,7 +140,7 @@ ...@@ -140,7 +140,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { PromotionCombinationProperty } from './config' import { PromotionCombinationProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationActivity' import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationActivity'
import { CommonStatusEnum } from '@/utils/constants' import { CommonStatusEnum } from '@/utils/constants'
import CombinationShowcase from '@/views/mall/promotion/combination/components/CombinationShowcase.vue' import CombinationShowcase from '@/views/mall/promotion/combination/components/CombinationShowcase.vue'
...@@ -150,7 +150,7 @@ defineOptions({ name: 'PromotionCombinationProperty' }) ...@@ -150,7 +150,7 @@ defineOptions({ name: 'PromotionCombinationProperty' })
const props = defineProps<{ modelValue: PromotionCombinationProperty }>() const props = defineProps<{ modelValue: PromotionCombinationProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
// 活动列表 // 活动列表
const activityList = ref<CombinationActivityApi.CombinationActivityVO[]>([]) const activityList = ref<CombinationActivityApi.CombinationActivityVO[]>([])
onMounted(async () => { onMounted(async () => {
......
...@@ -140,7 +140,7 @@ ...@@ -140,7 +140,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { PromotionPointProperty } from './config' import { PromotionPointProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
import PointShowcase from '@/views/mall/promotion/point/components/PointShowcase.vue' import PointShowcase from '@/views/mall/promotion/point/components/PointShowcase.vue'
// 秒杀属性面板 // 秒杀属性面板
...@@ -148,7 +148,7 @@ defineOptions({ name: 'PromotionPointProperty' }) ...@@ -148,7 +148,7 @@ defineOptions({ name: 'PromotionPointProperty' })
const props = defineProps<{ modelValue: PromotionPointProperty }>() const props = defineProps<{ modelValue: PromotionPointProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>
...@@ -140,7 +140,7 @@ ...@@ -140,7 +140,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { PromotionSeckillProperty } from './config' import { PromotionSeckillProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity' import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
import { CommonStatusEnum } from '@/utils/constants' import { CommonStatusEnum } from '@/utils/constants'
import SeckillShowcase from '@/views/mall/promotion/seckill/components/SeckillShowcase.vue' import SeckillShowcase from '@/views/mall/promotion/seckill/components/SeckillShowcase.vue'
...@@ -150,7 +150,7 @@ defineOptions({ name: 'PromotionSeckillProperty' }) ...@@ -150,7 +150,7 @@ defineOptions({ name: 'PromotionSeckillProperty' })
const props = defineProps<{ modelValue: PromotionSeckillProperty }>() const props = defineProps<{ modelValue: PromotionSeckillProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
// 活动列表 // 活动列表
const activityList = ref<SeckillActivityApi.SeckillActivityVO[]>([]) const activityList = ref<SeckillActivityApi.SeckillActivityVO[]>([])
onMounted(async () => { onMounted(async () => {
......
...@@ -59,7 +59,7 @@ ...@@ -59,7 +59,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
import { SearchProperty } from '@/components/DiyEditor/components/mobile/SearchBar/config' import { SearchProperty } from '@/components/DiyEditor/components/mobile/SearchBar/config'
/** 搜索框属性面板 */ /** 搜索框属性面板 */
...@@ -67,7 +67,7 @@ defineOptions({ name: 'SearchProperty' }) ...@@ -67,7 +67,7 @@ defineOptions({ name: 'SearchProperty' })
const props = defineProps<{ modelValue: SearchProperty }>() const props = defineProps<{ modelValue: SearchProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>
...@@ -80,13 +80,13 @@ ...@@ -80,13 +80,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { TabBarProperty, component, THEME_LIST } from './config' import { TabBarProperty, component, THEME_LIST } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
// 底部导航栏 // 底部导航栏
defineOptions({ name: 'TabBarProperty' }) defineOptions({ name: 'TabBarProperty' })
const props = defineProps<{ modelValue: TabBarProperty }>() const props = defineProps<{ modelValue: TabBarProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
// 将数据库的值更新到右侧属性栏 // 将数据库的值更新到右侧属性栏
component.property.items = formData.value.items component.property.items = formData.value.items
......
...@@ -101,13 +101,13 @@ ...@@ -101,13 +101,13 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { TitleBarProperty } from './config' import { TitleBarProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
// 导航栏属性面板 // 导航栏属性面板
defineOptions({ name: 'TitleBarProperty' }) defineOptions({ name: 'TitleBarProperty' })
const props = defineProps<{ modelValue: TitleBarProperty }>() const props = defineProps<{ modelValue: TitleBarProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
// 表单校验 // 表单校验
const rules = {} const rules = {}
......
...@@ -4,14 +4,14 @@ ...@@ -4,14 +4,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { UserCardProperty } from './config' import { UserCardProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
// 用户卡片属性面板 // 用户卡片属性面板
defineOptions({ name: 'UserCardProperty' }) defineOptions({ name: 'UserCardProperty' })
const props = defineProps<{ modelValue: UserCardProperty }>() const props = defineProps<{ modelValue: UserCardProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>
...@@ -4,14 +4,14 @@ ...@@ -4,14 +4,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { UserCouponProperty } from './config' import { UserCouponProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
// 用户卡券属性面板 // 用户卡券属性面板
defineOptions({ name: 'UserCouponProperty' }) defineOptions({ name: 'UserCouponProperty' })
const props = defineProps<{ modelValue: UserCouponProperty }>() const props = defineProps<{ modelValue: UserCouponProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>
...@@ -4,14 +4,14 @@ ...@@ -4,14 +4,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { UserOrderProperty } from './config' import { UserOrderProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
// 用户订单属性面板 // 用户订单属性面板
defineOptions({ name: 'UserOrderProperty' }) defineOptions({ name: 'UserOrderProperty' })
const props = defineProps<{ modelValue: UserOrderProperty }>() const props = defineProps<{ modelValue: UserOrderProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>
...@@ -4,14 +4,14 @@ ...@@ -4,14 +4,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { UserWalletProperty } from './config' import { UserWalletProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
// 用户资产属性面板 // 用户资产属性面板
defineOptions({ name: 'UserWalletProperty' }) defineOptions({ name: 'UserWalletProperty' })
const props = defineProps<{ modelValue: UserWalletProperty }>() const props = defineProps<{ modelValue: UserWalletProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>
...@@ -42,14 +42,14 @@ ...@@ -42,14 +42,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { VideoPlayerProperty } from './config' import { VideoPlayerProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
// 视频播放属性面板 // 视频播放属性面板
defineOptions({ name: 'VideoPlayerProperty' }) defineOptions({ name: 'VideoPlayerProperty' })
const props = defineProps<{ modelValue: VideoPlayerProperty }>() const props = defineProps<{ modelValue: VideoPlayerProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>
...@@ -110,7 +110,7 @@ ...@@ -110,7 +110,7 @@
<el-tag <el-tag
v-if="showPageConfig" v-if="showPageConfig"
:effect="selectedComponent?.uid === pageConfigComponent.uid ? 'dark' : 'plain'" :effect="selectedComponent?.uid === pageConfigComponent.uid ? 'dark' : 'plain'"
:type="selectedComponent?.uid === pageConfigComponent.uid ? '' : 'info'" :type="selectedComponent?.uid === pageConfigComponent.uid ? 'primary' : 'info'"
size="large" size="large"
@click="handleComponentSelected(pageConfigComponent)" @click="handleComponentSelected(pageConfigComponent)"
> >
...@@ -121,7 +121,7 @@ ...@@ -121,7 +121,7 @@
<el-tag <el-tag
v-if="component.position === 'fixed'" v-if="component.position === 'fixed'"
:effect="selectedComponent?.uid === component.uid ? 'dark' : 'plain'" :effect="selectedComponent?.uid === component.uid ? 'dark' : 'plain'"
:type="selectedComponent?.uid === component.uid ? '' : 'info'" :type="selectedComponent?.uid === component.uid ? 'primary' : 'info'"
closable closable
size="large" size="large"
@click="handleComponentSelected(component)" @click="handleComponentSelected(component)"
...@@ -191,7 +191,7 @@ import { cloneDeep, includes } from 'lodash-es' ...@@ -191,7 +191,7 @@ import { cloneDeep, includes } from 'lodash-es'
import { component as PAGE_CONFIG_COMPONENT } from '@/components/DiyEditor/components/mobile/PageConfig/config' import { component as PAGE_CONFIG_COMPONENT } from '@/components/DiyEditor/components/mobile/PageConfig/config'
import { component as NAVIGATION_BAR_COMPONENT } from './components/mobile/NavigationBar/config' import { component as NAVIGATION_BAR_COMPONENT } from './components/mobile/NavigationBar/config'
import { component as TAB_BAR_COMPONENT } from './components/mobile/TabBar/config' import { component as TAB_BAR_COMPONENT } from './components/mobile/TabBar/config'
import { isString } from '@/utils/is' import { isEmpty, isString } from '@/utils/is'
import { DiyComponent, DiyComponentLibrary, PageConfig } from '@/components/DiyEditor/util' import { DiyComponent, DiyComponentLibrary, PageConfig } from '@/components/DiyEditor/util'
import { componentConfigs } from '@/components/DiyEditor/components/mobile' import { componentConfigs } from '@/components/DiyEditor/components/mobile'
import { array, oneOfType } from 'vue-types' import { array, oneOfType } from 'vue-types'
...@@ -238,24 +238,42 @@ const props = defineProps({ ...@@ -238,24 +238,42 @@ const props = defineProps({
watch( watch(
() => props.modelValue, () => props.modelValue,
() => { () => {
const modelValue = isString(props.modelValue) const modelValue =
? (JSON.parse(props.modelValue) as PageConfig) isString(props.modelValue) && !isEmpty(props.modelValue)
: props.modelValue ? (JSON.parse(props.modelValue) as PageConfig)
pageConfigComponent.value.property = modelValue?.page || PAGE_CONFIG_COMPONENT.property : props.modelValue
pageConfigComponent.value.property =
(typeof modelValue !== 'string' && modelValue?.page) || PAGE_CONFIG_COMPONENT.property
navigationBarComponent.value.property = navigationBarComponent.value.property =
modelValue?.navigationBar || NAVIGATION_BAR_COMPONENT.property (typeof modelValue !== 'string' && modelValue?.navigationBar) ||
tabBarComponent.value.property = modelValue?.tabBar || TAB_BAR_COMPONENT.property NAVIGATION_BAR_COMPONENT.property
tabBarComponent.value.property =
(typeof modelValue !== 'string' && modelValue?.tabBar) || TAB_BAR_COMPONENT.property
// 查找对应的页面组件 // 查找对应的页面组件
pageComponents.value = (modelValue?.components || []).map((item) => { pageComponents.value = ((typeof modelValue !== 'string' && modelValue?.components) || []).map(
const component = componentConfigs[item.id] (item) => {
return { ...component, property: item.property } const component = componentConfigs[item.id]
}) return { ...component, property: item.property }
}
)
}, },
{ {
immediate: true immediate: true
} }
) )
/** 选择组件修改其属性后更新它的配置 */
watch(
selectedComponent,
(val: any) => {
if (!val || selectedComponentIndex.value === -1) {
return
}
pageComponents.value[selectedComponentIndex.value] = selectedComponent.value!
},
{ deep: true }
)
// 保存 // 保存
const handleSave = () => { const handleSave = () => {
// 发送保存通知 // 发送保存通知
......
import { ref, Ref } from 'vue'
import { PageConfigProperty } from '@/components/DiyEditor/components/mobile/PageConfig/config' import { PageConfigProperty } from '@/components/DiyEditor/components/mobile/PageConfig/config'
import { NavigationBarProperty } from '@/components/DiyEditor/components/mobile/NavigationBar/config' import { NavigationBarProperty } from '@/components/DiyEditor/components/mobile/NavigationBar/config'
import { TabBarProperty } from '@/components/DiyEditor/components/mobile/TabBar/config' import { TabBarProperty } from '@/components/DiyEditor/components/mobile/TabBar/config'
...@@ -78,34 +77,6 @@ export interface PageConfig { ...@@ -78,34 +77,6 @@ export interface PageConfig {
// 页面组件,只保留组件ID,组件属性 // 页面组件,只保留组件ID,组件属性
export interface PageComponent extends Pick<DiyComponent<any>, 'id' | 'property'> {} export interface PageComponent extends Pick<DiyComponent<any>, 'id' | 'property'> {}
// 属性表单监听
export function usePropertyForm<T>(modelValue: T, emit: Function): { formData: Ref<T> } {
const formData = ref<T>()
// 监听属性数据变动
watch(
() => modelValue,
() => {
formData.value = modelValue
},
{
deep: true,
immediate: true
}
)
// 监听表单数据变动
watch(
() => formData.value,
() => {
emit('update:modelValue', formData.value)
},
{
deep: true
}
)
return { formData } as { formData: Ref<T> }
}
// 页面组件库 // 页面组件库
export const PAGE_LIBS = [ export const PAGE_LIBS = [
{ {
......
...@@ -13,9 +13,16 @@ ...@@ -13,9 +13,16 @@
class="mb-4px flex flex-col gap-4px border border-gray-2 border-rounded rounded border-solid p-8px" class="mb-4px flex flex-col gap-4px border border-gray-2 border-rounded rounded border-solid p-8px"
> >
<!-- 操作按钮区 --> <!-- 操作按钮区 -->
<div class="m--8px m-b-4px flex flex-row items-center justify-between p-8px" style="background-color: var(--app-content-bg-color);"> <div
class="m--8px m-b-4px flex flex-row items-center justify-between p-8px"
style="background-color: var(--app-content-bg-color)"
>
<el-tooltip content="拖动排序"> <el-tooltip content="拖动排序">
<Icon icon="ic:round-drag-indicator" class="drag-icon cursor-move" style="color: #8a909c;" /> <Icon
icon="ic:round-drag-indicator"
class="drag-icon cursor-move"
style="color: #8a909c"
/>
</el-tooltip> </el-tooltip>
<el-tooltip content="删除"> <el-tooltip content="删除">
<Icon <Icon
...@@ -47,7 +54,7 @@ ...@@ -47,7 +54,7 @@
<script setup lang="ts"> <script setup lang="ts">
// 拖拽组件 // 拖拽组件
import VueDraggable from 'vuedraggable' import VueDraggable from 'vuedraggable'
import { usePropertyForm } from '@/components/DiyEditor/util' import { useVModel } from '@vueuse/core'
import { any, array } from 'vue-types' import { any, array } from 'vue-types'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
...@@ -66,7 +73,7 @@ const props = defineProps({ ...@@ -66,7 +73,7 @@ const props = defineProps({
}) })
// 定义事件 // 定义事件
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const formData = useVModel(props, 'modelValue', emit)
// 处理添加 // 处理添加
const handleAdd = () => formData.value.push(cloneDeep(props.emptyItem || {})) const handleAdd = () => formData.value.push(cloneDeep(props.emptyItem || {}))
......
...@@ -105,7 +105,7 @@ const editorConfig = computed((): IEditorConfig => { ...@@ -105,7 +105,7 @@ const editorConfig = computed((): IEditorConfig => {
}, },
// 超时时间,默认为 10 秒 // 超时时间,默认为 10 秒
timeout: 5 * 1000, // 5 秒 timeout: 15 * 1000, // 15 秒
// form-data fieldName,后端接口参数名称,默认值wangeditor-uploaded-image // form-data fieldName,后端接口参数名称,默认值wangeditor-uploaded-image
fieldName: 'file', fieldName: 'file',
......
...@@ -75,7 +75,7 @@ watch( ...@@ -75,7 +75,7 @@ watch(
<template> <template>
<ElIcon :class="prefixCls" :color="color" :size="size"> <ElIcon :class="prefixCls" :color="color" :size="size">
<svg v-if="isLocal" :class="getSvgClass" aria-hidden="true"> <svg v-if="isLocal" :class="getSvgClass">
<use :xlink:href="symbolId" /> <use :xlink:href="symbolId" />
</svg> </svg>
......
<template> <template>
<el-input v-model="valueRef" v-bind="$attrs"> <el-input v-model="modelValue" v-bind="$attrs">
<template #append> <template #append>
<el-color-picker v-model="colorRef" :predefine="PREDEFINE_COLORS" /> <el-color-picker v-model="color" :predefine="PREDEFINE_COLORS" />
</template> </template>
</el-input> </el-input>
</template> </template>
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { PREDEFINE_COLORS } from '@/utils/color' import { PREDEFINE_COLORS } from '@/utils/color'
import { useVModels } from '@vueuse/core'
/** /**
* 带颜色选择器输入框 * 带颜色选择器输入框
...@@ -19,33 +20,8 @@ const props = defineProps({ ...@@ -19,33 +20,8 @@ const props = defineProps({
modelValue: propTypes.string.def('').isRequired, modelValue: propTypes.string.def('').isRequired,
color: propTypes.string.def('').isRequired color: propTypes.string.def('').isRequired
}) })
watch(
() => props.modelValue,
(val: string) => {
if (val === unref(valueRef)) return
valueRef.value = val
}
)
const emit = defineEmits(['update:modelValue', 'update:color']) const emit = defineEmits(['update:modelValue', 'update:color'])
const { modelValue, color } = useVModels(props, emit)
// 输入框的值
const valueRef = ref(props.modelValue)
watch(
() => valueRef.value,
(val: string) => {
emit('update:modelValue', val)
}
)
// 颜色
const colorRef = ref(props.color)
watch(
() => colorRef.value,
(val: string) => {
emit('update:color', val)
}
)
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
:deep(.el-input-group__append) { :deep(.el-input-group__append) {
......
...@@ -15,6 +15,12 @@ ...@@ -15,6 +15,12 @@
</div> </div>
<div class="handler-item-text">审批人</div> <div class="handler-item-text">审批人</div>
</div> </div>
<div class="handler-item" @click="addNode(NodeType.TRANSACTOR_NODE)">
<div class="transactor handler-item-icon">
<span class="iconfont icon-transactor icon-size"></span>
</div>
<div class="handler-item-text">办理人</div>
</div>
<div class="handler-item" @click="addNode(NodeType.COPY_TASK_NODE)"> <div class="handler-item" @click="addNode(NodeType.COPY_TASK_NODE)">
<div class="handler-item-icon copy"> <div class="handler-item-icon copy">
<span class="iconfont icon-size icon-copy"></span> <span class="iconfont icon-size icon-copy"></span>
...@@ -40,12 +46,29 @@ ...@@ -40,12 +46,29 @@
<div class="handler-item-text">包容分支</div> <div class="handler-item-text">包容分支</div>
</div> </div>
<div class="handler-item" @click="addNode(NodeType.DELAY_TIMER_NODE)"> <div class="handler-item" @click="addNode(NodeType.DELAY_TIMER_NODE)">
<!-- TODO @芋艿 需要更换一下iconfont的图标 --> <div class="handler-item-icon delay">
<div class="handler-item-icon copy"> <span class="iconfont icon-size icon-delay"></span>
<span class="iconfont icon-size icon-copy"></span>
</div> </div>
<div class="handler-item-text">延迟器</div> <div class="handler-item-text">延迟器</div>
</div> </div>
<div class="handler-item" @click="addNode(NodeType.ROUTER_BRANCH_NODE)">
<div class="handler-item-icon router">
<span class="iconfont icon-size icon-router"></span>
</div>
<div class="handler-item-text">路由分支</div>
</div>
<div class="handler-item" @click="addNode(NodeType.TRIGGER_NODE)">
<div class="handler-item-icon trigger">
<span class="iconfont icon-size icon-trigger"></span>
</div>
<div class="handler-item-text">触发器</div>
</div>
<div class="handler-item" @click="addNode(NodeType.CHILD_PROCESS_NODE)">
<div class="handler-item-icon child-process">
<span class="iconfont icon-size icon-child-process"></span>
</div>
<div class="handler-item-text">子流程</div>
</div>
</div> </div>
<template #reference> <template #reference>
<div class="add-icon"><Icon icon="ep:plus" /></div> <div class="add-icon"><Icon icon="ep:plus" /></div>
...@@ -60,10 +83,12 @@ import { ...@@ -60,10 +83,12 @@ import {
ApproveMethodType, ApproveMethodType,
AssignEmptyHandlerType, AssignEmptyHandlerType,
AssignStartUserHandlerType, AssignStartUserHandlerType,
ConditionType,
NODE_DEFAULT_NAME, NODE_DEFAULT_NAME,
NodeType, NodeType,
RejectHandlerType, RejectHandlerType,
SimpleFlowNode SimpleFlowNode,
DEFAULT_CONDITION_GROUP_VALUE
} from './consts' } from './consts'
import { generateUUID } from '@/utils' import { generateUUID } from '@/utils'
...@@ -101,13 +126,13 @@ const addNode = (type: number) => { ...@@ -101,13 +126,13 @@ const addNode = (type: number) => {
} }
popoverShow.value = false popoverShow.value = false
if (type === NodeType.USER_TASK_NODE) { if (type === NodeType.USER_TASK_NODE || type === NodeType.TRANSACTOR_NODE) {
const id = 'Activity_' + generateUUID() const id = 'Activity_' + generateUUID()
const data: SimpleFlowNode = { const data: SimpleFlowNode = {
id: id, id: id,
name: NODE_DEFAULT_NAME.get(NodeType.USER_TASK_NODE) as string, name: NODE_DEFAULT_NAME.get(type) as string,
showText: '', showText: '',
type: NodeType.USER_TASK_NODE, type: type,
approveMethod: ApproveMethodType.SEQUENTIAL_APPROVE, approveMethod: ApproveMethodType.SEQUENTIAL_APPROVE,
// 超时处理 // 超时处理
rejectHandler: { rejectHandler: {
...@@ -120,7 +145,16 @@ const addNode = (type: number) => { ...@@ -120,7 +145,16 @@ const addNode = (type: number) => {
type: AssignEmptyHandlerType.APPROVE type: AssignEmptyHandlerType.APPROVE
}, },
assignStartUserHandlerType: AssignStartUserHandlerType.START_USER_AUDIT, assignStartUserHandlerType: AssignStartUserHandlerType.START_USER_AUDIT,
childNode: props.childNode childNode: props.childNode,
taskCreateListener: {
enable: false
},
taskAssignListener: {
enable: false
},
taskCompleteListener: {
enable: false
}
} }
emits('update:childNode', data) emits('update:childNode', data)
} }
...@@ -147,8 +181,11 @@ const addNode = (type: number) => { ...@@ -147,8 +181,11 @@ const addNode = (type: number) => {
showText: '', showText: '',
type: NodeType.CONDITION_NODE, type: NodeType.CONDITION_NODE,
childNode: undefined, childNode: undefined,
conditionType: 1, conditionSetting: {
defaultFlow: false defaultFlow: false,
conditionType: ConditionType.RULE,
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE
}
}, },
{ {
id: 'Flow_' + generateUUID(), id: 'Flow_' + generateUUID(),
...@@ -156,8 +193,9 @@ const addNode = (type: number) => { ...@@ -156,8 +193,9 @@ const addNode = (type: number) => {
showText: '未满足其它条件时,将进入此分支', showText: '未满足其它条件时,将进入此分支',
type: NodeType.CONDITION_NODE, type: NodeType.CONDITION_NODE,
childNode: undefined, childNode: undefined,
conditionType: undefined, conditionSetting: {
defaultFlow: true defaultFlow: true
}
} }
] ]
} }
...@@ -201,7 +239,11 @@ const addNode = (type: number) => { ...@@ -201,7 +239,11 @@ const addNode = (type: number) => {
showText: '', showText: '',
type: NodeType.CONDITION_NODE, type: NodeType.CONDITION_NODE,
childNode: undefined, childNode: undefined,
defaultFlow: false conditionSetting: {
defaultFlow: false,
conditionType: ConditionType.RULE,
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE
}
}, },
{ {
id: 'Flow_' + generateUUID(), id: 'Flow_' + generateUUID(),
...@@ -209,7 +251,9 @@ const addNode = (type: number) => { ...@@ -209,7 +251,9 @@ const addNode = (type: number) => {
showText: '未满足其它条件时,将进入此分支', showText: '未满足其它条件时,将进入此分支',
type: NodeType.CONDITION_NODE, type: NodeType.CONDITION_NODE,
childNode: undefined, childNode: undefined,
defaultFlow: true conditionSetting: {
defaultFlow: true
}
} }
] ]
} }
...@@ -225,6 +269,51 @@ const addNode = (type: number) => { ...@@ -225,6 +269,51 @@ const addNode = (type: number) => {
} }
emits('update:childNode', data) emits('update:childNode', data)
} }
if (type === NodeType.ROUTER_BRANCH_NODE) {
const data: SimpleFlowNode = {
id: 'GateWay_' + generateUUID(),
name: NODE_DEFAULT_NAME.get(NodeType.ROUTER_BRANCH_NODE) as string,
showText: '',
type: NodeType.ROUTER_BRANCH_NODE,
childNode: props.childNode
}
emits('update:childNode', data)
}
if (type === NodeType.TRIGGER_NODE) {
const data: SimpleFlowNode = {
id: 'Activity_' + generateUUID(),
name: NODE_DEFAULT_NAME.get(NodeType.TRIGGER_NODE) as string,
showText: '',
type: NodeType.TRIGGER_NODE,
childNode: props.childNode
}
emits('update:childNode', data)
}
if (type === NodeType.CHILD_PROCESS_NODE) {
const data: SimpleFlowNode = {
id: 'Activity_' + generateUUID(),
name: NODE_DEFAULT_NAME.get(NodeType.CHILD_PROCESS_NODE) as string,
showText: '',
type: NodeType.CHILD_PROCESS_NODE,
childNode: props.childNode,
childProcessSetting: {
calledProcessDefinitionKey: '',
calledProcessDefinitionName: '',
async: false,
skipStartUserNode: false,
startUserSetting: {
type: 1
},
timeoutSetting: {
enable: false
},
multiInstanceSetting: {
enable: false
}
}
}
emits('update:childNode', data)
}
} }
</script> </script>
......
...@@ -6,7 +6,11 @@ ...@@ -6,7 +6,11 @@
/> />
<!-- 审批节点 --> <!-- 审批节点 -->
<UserTaskNode <UserTaskNode
v-if="currentNode && currentNode.type === NodeType.USER_TASK_NODE" v-if="
currentNode &&
(currentNode.type === NodeType.USER_TASK_NODE ||
currentNode.type === NodeType.TRANSACTOR_NODE)
"
:flow-node="currentNode" :flow-node="currentNode"
@update:flow-node="handleModelValueUpdate" @update:flow-node="handleModelValueUpdate"
@find:parent-node="findFromParentNode" @find:parent-node="findFromParentNode"
...@@ -44,6 +48,24 @@ ...@@ -44,6 +48,24 @@
:flow-node="currentNode" :flow-node="currentNode"
@update:flow-node="handleModelValueUpdate" @update:flow-node="handleModelValueUpdate"
/> />
<!-- 路由分支节点 -->
<RouterNode
v-if="currentNode && currentNode.type === NodeType.ROUTER_BRANCH_NODE"
:flow-node="currentNode"
@update:flow-node="handleModelValueUpdate"
/>
<!-- 触发器节点 -->
<TriggerNode
v-if="currentNode && currentNode.type === NodeType.TRIGGER_NODE"
:flow-node="currentNode"
@update:flow-node="handleModelValueUpdate"
/>
<!-- 子流程节点 -->
<ChildProcessNode
v-if="currentNode && currentNode.type === NodeType.CHILD_PROCESS_NODE"
:flow-node="currentNode"
@update:flow-node="handleModelValueUpdate"
/>
<!-- 递归显示孩子节点 --> <!-- 递归显示孩子节点 -->
<ProcessNodeTree <ProcessNodeTree
v-if="currentNode && currentNode.childNode" v-if="currentNode && currentNode.childNode"
...@@ -67,6 +89,9 @@ import ExclusiveNode from './nodes/ExclusiveNode.vue' ...@@ -67,6 +89,9 @@ import ExclusiveNode from './nodes/ExclusiveNode.vue'
import ParallelNode from './nodes/ParallelNode.vue' import ParallelNode from './nodes/ParallelNode.vue'
import InclusiveNode from './nodes/InclusiveNode.vue' import InclusiveNode from './nodes/InclusiveNode.vue'
import DelayTimerNode from './nodes/DelayTimerNode.vue' import DelayTimerNode from './nodes/DelayTimerNode.vue'
import RouterNode from './nodes/RouterNode.vue'
import TriggerNode from './nodes/TriggerNode.vue'
import ChildProcessNode from './nodes/ChildProcessNode.vue'
import { SimpleFlowNode, NodeType } from './consts' import { SimpleFlowNode, NodeType } from './consts'
import { useWatchNode } from './node' import { useWatchNode } from './node'
defineOptions({ defineOptions({
......
...@@ -25,7 +25,6 @@ ...@@ -25,7 +25,6 @@
<script setup lang="ts"> <script setup lang="ts">
import SimpleProcessModel from './SimpleProcessModel.vue' import SimpleProcessModel from './SimpleProcessModel.vue'
import { updateBpmSimpleModel, getBpmSimpleModel } from '@/api/bpm/simple'
import { SimpleFlowNode, NodeType, NodeId, NODE_DEFAULT_TEXT } from './consts' import { SimpleFlowNode, NodeType, NodeId, NODE_DEFAULT_TEXT } from './consts'
import { getModel } from '@/api/bpm/model' import { getModel } from '@/api/bpm/model'
import { getForm, FormVO } from '@/api/bpm/form' import { getForm, FormVO } from '@/api/bpm/form'
...@@ -35,12 +34,13 @@ import * as DeptApi from '@/api/system/dept' ...@@ -35,12 +34,13 @@ import * as DeptApi from '@/api/system/dept'
import * as PostApi from '@/api/system/post' import * as PostApi from '@/api/system/post'
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
import * as UserGroupApi from '@/api/bpm/userGroup' import * as UserGroupApi from '@/api/bpm/userGroup'
import { BpmModelFormType } from '@/utils/constants'
defineOptions({ defineOptions({
name: 'SimpleProcessDesigner' name: 'SimpleProcessDesigner'
}) })
const emits = defineEmits(['success', 'init-finished']) // 保存成功事件 const emits = defineEmits(['success']) // 保存成功事件
const props = defineProps({ const props = defineProps({
modelId: { modelId: {
...@@ -56,16 +56,13 @@ const props = defineProps({ ...@@ -56,16 +56,13 @@ const props = defineProps({
required: false required: false
}, },
// 可发起流程的人员编号 // 可发起流程的人员编号
startUserIds : { startUserIds: {
type: Array, type: Array,
required: false required: false
},
value: {
type: [String, Object],
required: false
} }
}) })
const processData = inject('processData') as Ref
const loading = ref(false) const loading = ref(false)
const formFields = ref<string[]>([]) const formFields = ref<string[]>([])
const formType = ref(20) const formType = ref(20)
...@@ -76,9 +73,6 @@ const deptOptions = ref<DeptApi.DeptVO[]>([]) // 部门列表 ...@@ -76,9 +73,6 @@ const deptOptions = ref<DeptApi.DeptVO[]>([]) // 部门列表
const deptTreeOptions = ref() const deptTreeOptions = ref()
const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表 const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表
// 添加当前值的引用
const currentValue = ref<SimpleFlowNode | undefined>()
provide('formFields', formFields) provide('formFields', formFields)
provide('formType', formType) provide('formType', formType)
provide('roleList', roleOptions) provide('roleList', roleOptions)
...@@ -88,9 +82,11 @@ provide('deptList', deptOptions) ...@@ -88,9 +82,11 @@ provide('deptList', deptOptions)
provide('userGroupList', userGroupOptions) provide('userGroupList', userGroupOptions)
provide('deptTree', deptTreeOptions) provide('deptTree', deptTreeOptions)
provide('startUserIds', props.startUserIds) provide('startUserIds', props.startUserIds)
provide('tasks', [])
provide('processInstance', {})
const message = useMessage() // 国际化 const message = useMessage() // 国际化
const processNodeTree = ref<SimpleFlowNode | undefined>() const processNodeTree = ref<SimpleFlowNode | undefined>()
provide('processNodeTree', processNodeTree)
const errorDialogVisible = ref(false) const errorDialogVisible = ref(false)
let errorNodes: SimpleFlowNode[] = [] let errorNodes: SimpleFlowNode[] = []
...@@ -112,70 +108,13 @@ const updateModel = () => { ...@@ -112,70 +108,13 @@ const updateModel = () => {
} }
} }
// 加载流程数据
const loadProcessData = async (data: any) => {
try {
if (data) {
const parsedData = typeof data === 'string' ? JSON.parse(data) : data
processNodeTree.value = parsedData
currentValue.value = parsedData
// 确保数据加载后刷新视图
await nextTick()
if (simpleProcessModelRef.value?.refresh) {
await simpleProcessModelRef.value.refresh()
}
}
} catch (error) {
console.error('加载流程数据失败:', error)
}
}
// 监听属性变化
watch(
() => props.value,
async (newValue, oldValue) => {
if (newValue && newValue !== oldValue) {
await loadProcessData(newValue)
}
},
{ immediate: true, deep: true }
)
// 监听流程节点树变化,自动保存
watch(
() => processNodeTree.value,
async (newValue, oldValue) => {
if (newValue && oldValue && JSON.stringify(newValue) !== JSON.stringify(oldValue)) {
await saveSimpleFlowModel(newValue)
}
},
{ deep: true }
)
const saveSimpleFlowModel = async (simpleModelNode: SimpleFlowNode) => { const saveSimpleFlowModel = async (simpleModelNode: SimpleFlowNode) => {
if (!simpleModelNode) { if (!simpleModelNode) {
return return
} }
// 校验节点
errorNodes = []
validateNode(simpleModelNode, errorNodes)
if (errorNodes.length > 0) {
errorDialogVisible.value = true
return
}
try { try {
if (props.modelId) { processData.value = simpleModelNode
// 编辑模式
const data = {
id: props.modelId,
simpleModel: simpleModelNode
}
await updateBpmSimpleModel(data)
}
// 无论是编辑还是新建模式,都更新当前值并触发事件
currentValue.value = simpleModelNode
emits('success', simpleModelNode) emits('success', simpleModelNode)
} catch (error) { } catch (error) {
console.error('保存失败:', error) console.error('保存失败:', error)
...@@ -229,7 +168,7 @@ onMounted(async () => { ...@@ -229,7 +168,7 @@ onMounted(async () => {
const bpmnModel = await getModel(props.modelId) const bpmnModel = await getModel(props.modelId)
if (bpmnModel) { if (bpmnModel) {
formType.value = bpmnModel.formType formType.value = bpmnModel.formType
if (formType.value === 10) { if (formType.value === BpmModelFormType.NORMAL && bpmnModel.formId) {
const bpmnForm = (await getForm(bpmnModel.formId)) as unknown as FormVO const bpmnForm = (await getForm(bpmnModel.formId)) as unknown as FormVO
formFields.value = bpmnForm?.fields formFields.value = bpmnForm?.fields
} }
...@@ -246,61 +185,18 @@ onMounted(async () => { ...@@ -246,61 +185,18 @@ onMounted(async () => {
deptTreeOptions.value = handleTree(deptOptions.value as DeptApi.DeptVO[], 'id') deptTreeOptions.value = handleTree(deptOptions.value as DeptApi.DeptVO[], 'id')
// 获取用户组列表 // 获取用户组列表
userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList() userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList()
// 加载流程数据 // 加载流程数据
if (props.modelId) { if (processData.value) {
// 获取 SIMPLE 设计器模型 processNodeTree.value = processData?.value
const result = await getBpmSimpleModel(props.modelId)
if (result) {
await loadProcessData(result)
} else {
updateModel()
}
} else if (props.value) {
await loadProcessData(props.value)
} else { } else {
updateModel() updateModel()
} }
} finally { } finally {
loading.value = false loading.value = false
emits('init-finished')
} }
}) })
const simpleProcessModelRef = ref() const simpleProcessModelRef = ref()
/** 获取当前流程数据 */ defineExpose({})
const getCurrentFlowData = async () => {
try {
if (simpleProcessModelRef.value) {
const data = await simpleProcessModelRef.value.getCurrentFlowData()
if (data) {
currentValue.value = data
return data
}
}
return currentValue.value
} catch (error) {
console.error('获取流程数据失败:', error)
return currentValue.value
}
}
// 刷新方法
const refresh = async () => {
try {
if (currentValue.value) {
await loadProcessData(currentValue.value)
}
} catch (error) {
console.error('刷新失败:', error)
}
}
defineExpose({
getCurrentFlowData,
updateModel,
loadProcessData,
refresh
})
</script> </script>
<template> <template>
<div class="simple-process-model-container position-relative"> <div class="simple-process-model-container position-relative">
<div class="position-absolute top-0px right-0px bg-#fff"> <div class="position-absolute top-0px right-0px bg-#fff z-index-button-group">
<el-row type="flex" justify="end"> <el-row type="flex" justify="end">
<el-button-group key="scale-control" size="default"> <el-button-group key="scale-control" size="default">
<el-button v-if="!readonly" size="default" @click="exportJson">
<Icon icon="ep:download" /> 导出
</el-button>
<el-button v-if="!readonly" size="default" @click="importJson">
<Icon icon="ep:upload" />导入
</el-button>
<!-- 用于打开本地文件-->
<input
v-if="!readonly"
type="file"
id="files"
ref="refFile"
style="display: none"
accept=".json"
@change="importLocalFile"
/>
<el-button size="default" :icon="ScaleToOriginal" @click="processReZoom()" /> <el-button size="default" :icon="ScaleToOriginal" @click="processReZoom()" />
<el-button size="default" :plain="true" :icon="ZoomOut" @click="zoomOut()" /> <el-button size="default" :plain="true" :icon="ZoomOut" @click="zoomOut()" />
<el-button size="default" class="w-80px"> {{ scaleValue }}% </el-button> <el-button size="default" class="w-80px"> {{ scaleValue }}% </el-button>
<el-button size="default" :plain="true" :icon="ZoomIn" @click="zoomIn()" /> <el-button size="default" :plain="true" :icon="ZoomIn" @click="zoomIn()" />
<el-button size="default" @click="resetPosition">重置</el-button>
</el-button-group> </el-button-group>
</el-row> </el-row>
</div> </div>
<div class="simple-process-model" :style="`transform: scale(${scaleValue / 100});`"> <div
class="simple-process-model"
:style="`transform: translate(${currentX}px, ${currentY}px) scale(${scaleValue / 100});`"
@mousedown="startDrag"
@mousemove="onDrag"
@mouseup="stopDrag"
@mouseleave="stopDrag"
@mouseenter="setGrabCursor"
>
<ProcessNodeTree v-if="processNodeTree" v-model:flow-node="processNodeTree" /> <ProcessNodeTree v-if="processNodeTree" v-model:flow-node="processNodeTree" />
</div> </div>
</div> </div>
...@@ -34,6 +59,8 @@ import ProcessNodeTree from './ProcessNodeTree.vue' ...@@ -34,6 +59,8 @@ import ProcessNodeTree from './ProcessNodeTree.vue'
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from './consts' import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from './consts'
import { useWatchNode } from './node' import { useWatchNode } from './node'
import { ZoomOut, ZoomIn, ScaleToOriginal } from '@element-plus/icons-vue' import { ZoomOut, ZoomIn, ScaleToOriginal } from '@element-plus/icons-vue'
import { isString } from '@/utils/is'
import download from '@/utils/download'
defineOptions({ defineOptions({
name: 'SimpleProcessModel' name: 'SimpleProcessModel'
...@@ -52,17 +79,57 @@ const props = defineProps({ ...@@ -52,17 +79,57 @@ const props = defineProps({
}) })
const emits = defineEmits<{ const emits = defineEmits<{
'save': [node: SimpleFlowNode | undefined] save: [node: SimpleFlowNode | undefined]
}>() }>()
const processNodeTree = useWatchNode(props) const processNodeTree = useWatchNode(props)
provide('readonly', props.readonly) provide('readonly', props.readonly)
// TODO 可优化:拖拽有点卡顿
/** 拖拽、放大缩小等操作 */
let scaleValue = ref(100) let scaleValue = ref(100)
const MAX_SCALE_VALUE = 200 const MAX_SCALE_VALUE = 200
const MIN_SCALE_VALUE = 50 const MIN_SCALE_VALUE = 50
const isDragging = ref(false)
const startX = ref(0)
const startY = ref(0)
const currentX = ref(0)
const currentY = ref(0)
const initialX = ref(0)
const initialY = ref(0)
const setGrabCursor = () => {
document.body.style.cursor = 'grab'
}
const resetCursor = () => {
document.body.style.cursor = 'default'
}
const startDrag = (e: MouseEvent) => {
isDragging.value = true
startX.value = e.clientX - currentX.value
startY.value = e.clientY - currentY.value
setGrabCursor() // 设置小手光标
}
const onDrag = (e: MouseEvent) => {
if (!isDragging.value) return
e.preventDefault() // 禁用文本选择
// 使用 requestAnimationFrame 优化性能
requestAnimationFrame(() => {
currentX.value = e.clientX - startX.value
currentY.value = e.clientY - startY.value
})
}
const stopDrag = () => {
isDragging.value = false
resetCursor() // 重置光标
}
// 放大
const zoomIn = () => { const zoomIn = () => {
if (scaleValue.value == MAX_SCALE_VALUE) { if (scaleValue.value == MAX_SCALE_VALUE) {
return return
...@@ -70,7 +137,6 @@ const zoomIn = () => { ...@@ -70,7 +137,6 @@ const zoomIn = () => {
scaleValue.value += 10 scaleValue.value += 10
} }
// 缩小
const zoomOut = () => { const zoomOut = () => {
if (scaleValue.value == MIN_SCALE_VALUE) { if (scaleValue.value == MIN_SCALE_VALUE) {
return return
...@@ -82,10 +148,15 @@ const processReZoom = () => { ...@@ -82,10 +148,15 @@ const processReZoom = () => {
scaleValue.value = 100 scaleValue.value = 100
} }
const resetPosition = () => {
currentX.value = initialX.value
currentY.value = initialY.value
}
/** 校验节点设置 */
const errorDialogVisible = ref(false) const errorDialogVisible = ref(false)
let errorNodes: SimpleFlowNode[] = [] let errorNodes: SimpleFlowNode[] = []
// 校验节点设置。 暂时以 showText 为空 未节点错误配置
const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => { const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => {
if (node) { if (node) {
const { type, showText, conditionNodes } = node const { type, showText, conditionNodes } = node
...@@ -143,6 +214,52 @@ const getCurrentFlowData = async () => { ...@@ -143,6 +214,52 @@ const getCurrentFlowData = async () => {
defineExpose({ defineExpose({
getCurrentFlowData getCurrentFlowData
}) })
/** 导出 JSON */
const exportJson = () => {
download.json(new Blob([JSON.stringify(processNodeTree.value)]), 'model.json')
}
/** 导入 JSON */
const refFile = ref()
const importJson = () => {
refFile.value.click()
}
const importLocalFile = () => {
const file = refFile.value.files[0]
const reader = new FileReader()
reader.readAsText(file)
reader.onload = function () {
if (isString(this.result)) {
processNodeTree.value = JSON.parse(this.result)
emits('save', processNodeTree.value)
}
}
}
// 在组件初始化时记录初始位置
onMounted(() => {
initialX.value = currentX.value
initialY.value = currentY.value
})
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped>
.simple-process-model-container {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
user-select: none; // 禁用文本选择
}
.simple-process-model {
position: relative; // 确保相对定位
min-width: 100%; // 确保宽度为100%
min-height: 100%; // 确保高度为100%
}
.z-index-button-group {
z-index: 10;
}
</style>
...@@ -45,4 +45,3 @@ watch( ...@@ -45,4 +45,3 @@ watch(
provide('tasks', approveTasks) provide('tasks', approveTasks)
provide('processInstance', currentProcessInstance) provide('processInstance', currentProcessInstance)
</script> </script>
p
import { cloneDeep } from 'lodash-es'
import { TaskStatusEnum } from '@/api/bpm/task' import { TaskStatusEnum } from '@/api/bpm/task'
import * as RoleApi from '@/api/system/role' import * as RoleApi from '@/api/system/role'
import * as DeptApi from '@/api/system/dept' import * as DeptApi from '@/api/system/dept'
...@@ -14,9 +13,15 @@ import { ...@@ -14,9 +13,15 @@ import {
NODE_DEFAULT_NAME, NODE_DEFAULT_NAME,
AssignStartUserHandlerType, AssignStartUserHandlerType,
AssignEmptyHandlerType, AssignEmptyHandlerType,
FieldPermissionType FieldPermissionType,
HttpRequestParam,
ProcessVariableEnum,
ConditionType,
ConditionGroup,
COMPARISON_OPERATORS
} from './consts' } from './consts'
import { parseFormFields } from '@/components/FormCreate/src/utils/index' import { parseFormFields } from '@/components/FormCreate/src/utils'
export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlowNode> { export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlowNode> {
const node = ref<SimpleFlowNode>(props.flowNode) const node = ref<SimpleFlowNode>(props.flowNode)
watch( watch(
...@@ -46,9 +51,9 @@ export function useFormFieldsPermission(defaultPermission: FieldPermissionType) ...@@ -46,9 +51,9 @@ export function useFormFieldsPermission(defaultPermission: FieldPermissionType)
// 字段权限配置. 需要有 field, title, permissioin 属性 // 字段权限配置. 需要有 field, title, permissioin 属性
const fieldsPermissionConfig = ref<Array<Record<string, any>>>([]) const fieldsPermissionConfig = ref<Array<Record<string, any>>>([])
const formType = inject<Ref<number>>('formType') // 表单类型 const formType = inject<Ref<number | undefined>>('formType', ref()) // 表单类型
const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段 const formFields = inject<Ref<string[]>>('formFields', ref([])) // 流程表单字段
const getNodeConfigFormFields = (nodeFormFields?: Array<Record<string, string>>) => { const getNodeConfigFormFields = (nodeFormFields?: Array<Record<string, string>>) => {
nodeFormFields = toRaw(nodeFormFields) nodeFormFields = toRaw(nodeFormFields)
...@@ -104,16 +109,32 @@ export function useFormFieldsPermission(defaultPermission: FieldPermissionType) ...@@ -104,16 +109,32 @@ export function useFormFieldsPermission(defaultPermission: FieldPermissionType)
getNodeConfigFormFields getNodeConfigFormFields
} }
} }
/** /**
* @description 获取表单的字段 * @description 获取流程表单的字段
*/ */
export function useFormFields() { export function useFormFields() {
const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段 const formFields = inject<Ref<string[]>>('formFields', ref([])) // 流程表单字段
return parseFormCreateFields(unref(formFields)) return parseFormCreateFields(unref(formFields))
} }
// TODO @芋艿:后续需要把各种类似 useFormFieldsPermission 的逻辑,抽成一个通用方法。
/**
* @description 获取流程表单的字段和发起人字段
*/
export function useFormFieldsAndStartUser() {
const injectFormFields = inject<Ref<string[]>>('formFields', ref([])) // 流程表单字段
const formFields = parseFormCreateFields(unref(injectFormFields))
// 添加发起人
formFields.unshift({
field: ProcessVariableEnum.START_USER_ID,
title: '发起人',
required: true
})
return formFields
}
export type UserTaskFormType = { export type UserTaskFormType = {
//candidateParamArray: any[]
candidateStrategy: CandidateStrategy candidateStrategy: CandidateStrategy
approveMethod: ApproveMethodType approveMethod: ApproveMethodType
roleIds?: number[] // 角色 roleIds?: number[] // 角色
...@@ -136,10 +157,29 @@ export type UserTaskFormType = { ...@@ -136,10 +157,29 @@ export type UserTaskFormType = {
timeDuration?: number timeDuration?: number
maxRemindCount?: number maxRemindCount?: number
buttonsSetting: any[] buttonsSetting: any[]
taskCreateListenerEnable?: boolean
taskCreateListenerPath?: string
taskCreateListener?: {
header: HttpRequestParam[]
body: HttpRequestParam[]
}
taskAssignListenerEnable?: boolean
taskAssignListenerPath?: string
taskAssignListener?: {
header: HttpRequestParam[]
body: HttpRequestParam[]
}
taskCompleteListenerEnable?: boolean
taskCompleteListenerPath?: string
taskCompleteListener?: {
header: HttpRequestParam[]
body: HttpRequestParam[]
}
signEnable: boolean
reasonRequire: boolean
} }
export type CopyTaskFormType = { export type CopyTaskFormType = {
// candidateParamArray: any[]
candidateStrategy: CandidateStrategy candidateStrategy: CandidateStrategy
roleIds?: number[] // 角色 roleIds?: number[] // 角色
deptIds?: number[] // 部门 deptIds?: number[] // 部门
...@@ -156,15 +196,15 @@ export type CopyTaskFormType = { ...@@ -156,15 +196,15 @@ export type CopyTaskFormType = {
* @description 节点表单数据。 用于审批节点、抄送节点 * @description 节点表单数据。 用于审批节点、抄送节点
*/ */
export function useNodeForm(nodeType: NodeType) { export function useNodeForm(nodeType: NodeType) {
const roleOptions = inject<Ref<RoleApi.RoleVO[]>>('roleList') // 角色列表 const roleOptions = inject<Ref<RoleApi.RoleVO[]>>('roleList', ref([])) // 角色列表
const postOptions = inject<Ref<PostApi.PostVO[]>>('postList') // 岗位列表 const postOptions = inject<Ref<PostApi.PostVO[]>>('postList', ref([])) // 岗位列表
const userOptions = inject<Ref<UserApi.UserVO[]>>('userList') // 用户列表 const userOptions = inject<Ref<UserApi.UserVO[]>>('userList', ref([])) // 用户列表
const deptOptions = inject<Ref<DeptApi.DeptVO[]>>('deptList') // 部门列表 const deptOptions = inject<Ref<DeptApi.DeptVO[]>>('deptList', ref([])) // 部门列表
const userGroupOptions = inject<Ref<UserGroupApi.UserGroupVO[]>>('userGroupList') // 用户组列表 const userGroupOptions = inject<Ref<UserGroupApi.UserGroupVO[]>>('userGroupList', ref([])) // 用户组列表
const deptTreeOptions = inject('deptTree') // 部门树 const deptTreeOptions = inject('deptTree', ref()) // 部门树
const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段 const formFields = inject<Ref<string[]>>('formFields', ref([])) // 流程表单字段
const configForm = ref<UserTaskFormType | CopyTaskFormType>() const configForm = ref<UserTaskFormType | CopyTaskFormType>()
if (nodeType === NodeType.USER_TASK_NODE) { if (nodeType === NodeType.USER_TASK_NODE || nodeType === NodeType.TRANSACTOR_NODE) {
configForm.value = { configForm.value = {
candidateStrategy: CandidateStrategy.USER, candidateStrategy: CandidateStrategy.USER,
approveMethod: ApproveMethodType.SEQUENTIAL_APPROVE, approveMethod: ApproveMethodType.SEQUENTIAL_APPROVE,
...@@ -270,6 +310,11 @@ export function useNodeForm(nodeType: NodeType) { ...@@ -270,6 +310,11 @@ export function useNodeForm(nodeType: NodeType) {
showText = `表单内部门负责人` showText = `表单内部门负责人`
} }
// 审批人自选
if (configForm.value?.candidateStrategy === CandidateStrategy.APPROVE_USER_SELECT) {
showText = `审批人自选`
}
// 发起人自选 // 发起人自选
if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER_SELECT) { if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER_SELECT) {
showText = `发起人自选` showText = `发起人自选`
...@@ -506,6 +551,66 @@ export function useTaskStatusClass(taskStatus: TaskStatusEnum | undefined): stri ...@@ -506,6 +551,66 @@ export function useTaskStatusClass(taskStatus: TaskStatusEnum | undefined): stri
if (taskStatus === TaskStatusEnum.CANCEL) { if (taskStatus === TaskStatusEnum.CANCEL) {
return 'status-cancel' return 'status-cancel'
} }
return '' return ''
} }
/** 条件组件文字展示 */
export function getConditionShowText(
conditionType: ConditionType | undefined,
conditionExpression: string | undefined,
conditionGroups: ConditionGroup | undefined,
fieldOptions: Array<Record<string, any>>
) {
let showText = ''
if (conditionType === ConditionType.EXPRESSION) {
if (conditionExpression) {
showText = `表达式:${conditionExpression}`
}
}
if (conditionType === ConditionType.RULE) {
// 条件组是否为与关系
const groupAnd = conditionGroups?.and
let warningMessage: undefined | string = undefined
const conditionGroup = conditionGroups?.conditions.map((item) => {
return (
'(' +
item.rules
.map((rule) => {
if (rule.leftSide && rule.rightSide) {
return (
getFormFieldTitle(fieldOptions, rule.leftSide) +
' ' +
getOpName(rule.opCode) +
' ' +
rule.rightSide
)
} else {
// 有一条规则不完善。提示错误
warningMessage = '请完善条件规则'
return ''
}
})
.join(item.and ? ' 且 ' : ' 或 ') +
' ) '
)
})
if (warningMessage) {
showText = ''
} else {
showText = conditionGroup!.join(groupAnd ? ' 且 ' : ' 或 ')
}
}
return showText
}
/** 获取表单字段名称*/
const getFormFieldTitle = (fieldOptions: Array<Record<string, any>>, field: string) => {
const item = fieldOptions.find((item) => item.field === field)
return item?.title
}
/** 获取操作符名称 */
const getOpName = (opCode: string): string => {
const opName = COMPARISON_OPERATORS.find((item: any) => item.value === opCode)
return opName?.label
}
...@@ -134,7 +134,7 @@ ...@@ -134,7 +134,7 @@
:key="idx" :key="idx"
:label="item.title" :label="item.title"
:value="item.field" :value="item.field"
:disabled ="!item.required" :disabled="!item.required"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
...@@ -149,7 +149,7 @@ ...@@ -149,7 +149,7 @@
:key="idx" :key="idx"
:label="item.title" :label="item.title"
:value="item.field" :value="item.field"
:disabled ="!item.required" :disabled="!item.required"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
...@@ -195,9 +195,15 @@ ...@@ -195,9 +195,15 @@
<div class="field-permit-title"> <div class="field-permit-title">
<div class="setting-title-label first-title"> 字段名称 </div> <div class="setting-title-label first-title"> 字段名称 </div>
<div class="other-titles"> <div class="other-titles">
<span class="setting-title-label">只读</span> <span class="setting-title-label cursor-pointer" @click="updatePermission('READ')">
<span class="setting-title-label">可编辑</span> 只读
<span class="setting-title-label">隐藏</span> </span>
<span class="setting-title-label cursor-pointer" @click="updatePermission('WRITE')">
可编辑
</span>
<span class="setting-title-label cursor-pointer" @click="updatePermission('NONE')">
隐藏
</span>
</div> </div>
</div> </div>
<div <div
...@@ -368,6 +374,18 @@ const showCopyTaskNodeConfig = (node: SimpleFlowNode) => { ...@@ -368,6 +374,18 @@ const showCopyTaskNodeConfig = (node: SimpleFlowNode) => {
getNodeConfigFormFields(node.fieldsPermission) getNodeConfigFormFields(node.fieldsPermission)
} }
/** 批量更新权限 */
const updatePermission = (type: string) => {
fieldsPermissionConfig.value.forEach((field) => {
field.permission =
type === 'READ'
? FieldPermissionType.READ
: type === 'WRITE'
? FieldPermissionType.WRITE
: FieldPermissionType.NONE
})
}
defineExpose({ openDrawer, showCopyTaskNodeConfig }) // 暴露方法给父组件 defineExpose({ openDrawer, showCopyTaskNodeConfig }) // 暴露方法给父组件
</script> </script>
......
...@@ -124,6 +124,7 @@ const saveConfig = async () => { ...@@ -124,6 +124,7 @@ const saveConfig = async () => {
if (!valid) return false if (!valid) return false
const showText = getShowText() const showText = getShowText()
if (!showText) return false if (!showText) return false
currentNode.value.name = nodeName.value!
currentNode.value.showText = showText currentNode.value.showText = showText
if (configForm.value.delayType === DelayTypeEnum.FIXED_TIME_DURATION) { if (configForm.value.delayType === DelayTypeEnum.FIXED_TIME_DURATION) {
currentNode.value.delaySetting = { currentNode.value.delaySetting = {
......
<template>
<el-drawer
:append-to-body="true"
v-model="settingVisible"
:show-close="false"
:size="630"
:before-close="saveConfig"
>
<template #header>
<div class="config-header">
<input
v-if="showInput"
type="text"
class="config-editable-input"
@blur="blurEvent()"
v-mountedFocus
v-model="nodeName"
:placeholder="nodeName"
/>
<div v-else class="node-name">
{{ nodeName }} <Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()" />
</div>
<div class="divide-line"></div>
</div>
</template>
<div>
<el-form label-position="top">
<el-card class="mb-15px" v-for="(item, index) in routerGroups" :key="index">
<template #header>
<div class="flex flex-items-center">
<el-text size="large">路由{{ index + 1 }}</el-text>
<el-select class="ml-15px" v-model="item.nodeId" style="width: 180px">
<el-option
v-for="node in nodeOptions"
:key="node.value"
:label="node.label"
:value="node.value"
/>
</el-select>
<el-button class="mla" type="danger" link @click="deleteRouterGroup(index)">
删除
</el-button>
</div>
</template>
<Condition
:ref="($event) => (conditionRef[index] = $event)"
v-model="routerGroups[index]"
/>
</el-card>
</el-form>
<el-button class="w-1/1" type="primary" :icon="Plus" @click="addRouterGroup">
新增路由分支
</el-button>
</div>
<template #footer>
<el-divider />
<div>
<el-button type="primary" @click="saveConfig">确 定</el-button>
<el-button @click="closeDrawer">取 消</el-button>
</div>
</template>
</el-drawer>
</template>
<script setup lang="ts">
import { Plus } from '@element-plus/icons-vue'
import { SimpleFlowNode, NodeType, ConditionType, RouterSetting } from '../consts'
import { useWatchNode, useDrawer, useNodeName } from '../node'
import Condition from './components/Condition.vue'
defineOptions({
name: 'RouterNodeConfig'
})
const message = useMessage() // 消息弹窗
const props = defineProps({
flowNode: {
type: Object as () => SimpleFlowNode,
required: true
}
})
const processNodeTree = inject<Ref<SimpleFlowNode>>('processNodeTree')
// 抽屉配置
const { settingVisible, closeDrawer, openDrawer } = useDrawer()
// 当前节点
const currentNode = useWatchNode(props)
// 节点名称
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.ROUTER_BRANCH_NODE)
const routerGroups = ref<RouterSetting[]>([])
const nodeOptions = ref<any>([])
const conditionRef = ref([])
/** 保存配置 */
const saveConfig = async () => {
// 校验表单
let valid = true
for (const item of conditionRef.value) {
if (item && !(await item.validate())) {
valid = false
}
}
if (!valid) return false
const showText = getShowText()
if (!showText) return false
currentNode.value.name = nodeName.value!
currentNode.value.showText = showText
currentNode.value.routerGroups = routerGroups.value
settingVisible.value = false
return true
}
// 显示路由分支节点配置, 由父组件传过来
const showRouteNodeConfig = (node: SimpleFlowNode) => {
getRouterNode(processNodeTree?.value)
routerGroups.value = []
nodeName.value = node.name
if (node.routerGroups) {
routerGroups.value = node.routerGroups
}
}
const getShowText = () => {
if (!routerGroups.value || !Array.isArray(routerGroups.value) || routerGroups.value.length <= 0) {
message.warning('请配置路由!')
return ''
}
for (const route of routerGroups.value) {
if (!route.nodeId || !route.conditionType) {
message.warning('请完善路由配置项!')
return ''
}
if (route.conditionType === ConditionType.EXPRESSION && !route.conditionExpression) {
message.warning('请完善路由配置项!')
return ''
}
if (route.conditionType === ConditionType.RULE) {
for (const condition of route.conditionGroups.conditions) {
for (const rule of condition.rules) {
if (!rule.leftSide || !rule.rightSide) {
message.warning('请完善路由配置项!')
return ''
}
}
}
}
}
return `${routerGroups.value.length}条路由分支`
}
const addRouterGroup = () => {
routerGroups.value.push({
nodeId: '',
conditionType: ConditionType.RULE,
conditionExpression: '',
conditionGroups: {
and: true,
conditions: [
{
and: true,
rules: [
{
opCode: '==',
leftSide: '',
rightSide: ''
}
]
}
]
}
})
}
const deleteRouterGroup = (index: number) => {
routerGroups.value.splice(index, 1)
}
// 递归获取所有节点
const getRouterNode = (node) => {
// TODO 最好还需要满足以下要求
// 并行分支、包容分支内部节点不能跳转到外部节点
// 条件分支节点可以向上跳转到外部节点
while (true) {
if (!node) break
if (node.type !== NodeType.ROUTER_BRANCH_NODE && node.type !== NodeType.CONDITION_NODE) {
nodeOptions.value.push({
label: node.name,
value: node.id
})
}
if (!node.childNode || node.type === NodeType.END_EVENT_NODE) {
break
}
if (node.conditionNodes && node.conditionNodes.length) {
node.conditionNodes.forEach((item) => {
getRouterNode(item)
})
}
node = node.childNode
}
}
defineExpose({ openDrawer, showRouteNodeConfig }) // 暴露方法给父组件
</script>
...@@ -36,7 +36,8 @@ ...@@ -36,7 +36,8 @@
placement="top" placement="top"
:content="getUserNicknames(startUserIds)" :content="getUserNicknames(startUserIds)"
> >
{{ getUserNicknames(startUserIds.slice(0,2)) }} 等 {{ startUserIds.length }} 人可发起流程 {{ getUserNicknames(startUserIds.slice(0, 2)) }} 等
{{ startUserIds.length }} 人可发起流程
</el-tooltip> </el-tooltip>
</el-text> </el-text>
</el-tab-pane> </el-tab-pane>
...@@ -46,9 +47,15 @@ ...@@ -46,9 +47,15 @@
<div class="field-permit-title"> <div class="field-permit-title">
<div class="setting-title-label first-title"> 字段名称 </div> <div class="setting-title-label first-title"> 字段名称 </div>
<div class="other-titles"> <div class="other-titles">
<span class="setting-title-label">只读</span> <span class="setting-title-label cursor-pointer" @click="updatePermission('READ')">
<span class="setting-title-label">可编辑</span> 只读
<span class="setting-title-label">隐藏</span> </span>
<span class="setting-title-label cursor-pointer" @click="updatePermission('WRITE')">
可编辑
</span>
<span class="setting-title-label cursor-pointer" @click="updatePermission('NONE')">
隐藏
</span>
</div> </div>
</div> </div>
<div <div
...@@ -157,6 +164,17 @@ const showStartUserNodeConfig = (node: SimpleFlowNode) => { ...@@ -157,6 +164,17 @@ const showStartUserNodeConfig = (node: SimpleFlowNode) => {
getNodeConfigFormFields(node.fieldsPermission) getNodeConfigFormFields(node.fieldsPermission)
} }
/** 批量更新权限 */
const updatePermission = (type: string) => {
fieldsPermissionConfig.value.forEach((field) => {
field.permission =
type === 'READ'
? FieldPermissionType.READ
: type === 'WRITE'
? FieldPermissionType.WRITE
: FieldPermissionType.NONE
})
}
defineExpose({ openDrawer, showStartUserNodeConfig }) // 暴露方法给父组件 defineExpose({ openDrawer, showStartUserNodeConfig }) // 暴露方法给父组件
</script> </script>
......
<template>
<el-form ref="formRef" :model="condition" :rules="formRules" label-position="top">
<el-form-item label="配置方式" prop="conditionType">
<el-radio-group v-model="condition.conditionType" @change="changeConditionType">
<el-radio
v-for="(dict, indexConditionType) in conditionConfigTypes"
:key="indexConditionType"
:value="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="condition.conditionType === ConditionType.RULE && condition.conditionGroups"
label="条件规则"
>
<div class="condition-group-tool">
<div class="flex items-center">
<div class="mr-4">条件组关系</div>
<el-switch
v-model="condition.conditionGroups.and"
inline-prompt
active-text="且"
inactive-text="或"
/>
</div>
</div>
<el-space direction="vertical" :spacer="condition.conditionGroups.and ? '且' : '或'">
<el-card
class="condition-group"
style="width: 530px"
v-for="(equation, cIdx) in condition.conditionGroups.conditions"
:key="cIdx"
>
<div
class="condition-group-delete"
v-if="condition.conditionGroups.conditions.length > 1"
>
<Icon
color="#0089ff"
icon="ep:circle-close-filled"
:size="18"
@click="deleteConditionGroup(condition.conditionGroups.conditions, cIdx)"
/>
</div>
<template #header>
<div class="flex items-center justify-between">
<div>条件组</div>
<div class="flex">
<div class="mr-4">规则关系</div>
<el-switch
v-model="equation.and"
inline-prompt
active-text="且"
inactive-text="或"
/>
</div>
</div>
</template>
<div class="flex pt-2" v-for="(rule, rIdx) in equation.rules" :key="rIdx">
<div class="mr-2">
<el-form-item
:prop="`conditionGroups.conditions.${cIdx}.rules.${rIdx}.leftSide`"
:rules="{
required: true,
message: '左值不能为空',
trigger: 'change'
}"
>
<el-select style="width: 160px" v-model="rule.leftSide" clearable>
<el-option
v-for="(field, fIdx) in fieldOptions"
:key="fIdx"
:label="field.title"
:value="field.field"
:disabled="!field.required"
>
<el-tooltip
content="表单字段非必填时不能作为流程分支条件"
effect="dark"
placement="right-start"
v-if="!field.required"
>
<span>{{ field.title }}</span>
</el-tooltip>
</el-option>
</el-select>
</el-form-item>
</div>
<div class="mr-2">
<el-select v-model="rule.opCode" style="width: 100px">
<el-option
v-for="operator in COMPARISON_OPERATORS"
:key="operator.value"
:label="operator.label"
:value="operator.value"
/>
</el-select>
</div>
<div class="mr-2">
<el-form-item
:prop="`conditionGroups.conditions.${cIdx}.rules.${rIdx}.rightSide`"
:rules="{
required: true,
message: '右值不能为空',
trigger: 'blur'
}"
>
<el-input v-model="rule.rightSide" style="width: 160px" />
</el-form-item>
</div>
<div class="mr-1 flex items-center" v-if="equation.rules.length > 1">
<Icon icon="ep:delete" :size="18" @click="deleteConditionRule(equation, rIdx)" />
</div>
<div class="flex items-center">
<Icon icon="ep:plus" :size="18" @click="addConditionRule(equation, rIdx)" />
</div>
</div>
</el-card>
</el-space>
<div title="添加条件组" class="mt-4 cursor-pointer">
<Icon
color="#0089ff"
icon="ep:plus"
:size="24"
@click="addConditionGroup(condition.conditionGroups?.conditions)"
/>
</div>
</el-form-item>
<el-form-item
v-if="condition.conditionType === ConditionType.EXPRESSION"
label="条件表达式"
prop="conditionExpression"
>
<el-input
type="textarea"
v-model="condition.conditionExpression"
clearable
style="width: 100%"
/>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
import {
COMPARISON_OPERATORS,
CONDITION_CONFIG_TYPES,
ConditionType,
DEFAULT_CONDITION_GROUP_VALUE
} from '../../consts'
import { BpmModelFormType } from '@/utils/constants'
import { useFormFieldsAndStartUser } from '../../node'
const props = defineProps({
modelValue: {
type: Object,
required: true
}
})
const emit = defineEmits(['update:modelValue'])
const condition = computed({
get() {
return props.modelValue
},
set(newValue) {
emit('update:modelValue', newValue)
}
})
const formType = inject<Ref<number>>('formType') // 表单类型
const conditionConfigTypes = computed(() => {
return CONDITION_CONFIG_TYPES.filter((item) => {
// 业务表单暂时去掉条件规则选项
if (formType?.value === BpmModelFormType.CUSTOM && item.value === ConditionType.RULE) {
return false
} else {
return true
}
})
})
/** 条件规则可选择的表单字段 */
const fieldOptions = useFormFieldsAndStartUser()
// 表单校验规则
const formRules = reactive({
conditionType: [{ required: true, message: '配置方式不能为空', trigger: 'blur' }],
conditionExpression: [{ required: true, message: '条件表达式不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
/** 切换条件配置方式 */
const changeConditionType = () => {
if (condition.value.conditionType === ConditionType.RULE) {
if (!condition.value.conditionGroups) {
condition.value.conditionGroups = DEFAULT_CONDITION_GROUP_VALUE
}
}
}
const deleteConditionGroup = (conditions, index) => {
conditions.splice(index, 1)
}
const deleteConditionRule = (condition, index) => {
condition.rules.splice(index, 1)
}
const addConditionRule = (condition, index) => {
const rule = {
opCode: '==',
leftSide: '',
rightSide: ''
}
condition.rules.splice(index + 1, 0, rule)
}
const addConditionGroup = (conditions) => {
const condition = {
and: true,
rules: [
{
opCode: '==',
leftSide: '',
rightSide: ''
}
]
}
conditions.push(condition)
}
const validate = async () => {
if (!formRef) return false
return await formRef.value.validate()
}
defineExpose({ validate })
</script>
<style lang="scss" scoped>
.condition-group-tool {
display: flex;
justify-content: space-between;
width: 500px;
margin-bottom: 20px;
}
.condition-group {
position: relative;
&:hover {
border-color: #0089ff;
.condition-group-delete {
opacity: 1;
}
}
.condition-group-delete {
position: absolute;
top: 0;
left: 0;
display: flex;
cursor: pointer;
opacity: 0;
}
}
::v-deep(.el-card__header) {
padding: 8px var(--el-card-padding);
border-bottom: 1px solid var(--el-card-border-color);
box-sizing: border-box;
}
</style>
<template>
<el-form-item label-position="top" label="请求头">
<div class="flex pt-2" v-for="(item, index) in props.header" :key="index">
<div class="mr-2">
<el-form-item
:prop="`${bind}.header.${index}.key`"
:rules="{
required: true,
message: '参数名不能为空',
trigger: 'blur'
}"
>
<el-input class="w-160px" v-model="item.key" />
</el-form-item>
</div>
<div class="mr-2">
<el-select class="w-100px!" v-model="item.type">
<el-option
v-for="types in BPM_HTTP_REQUEST_PARAM_TYPES"
:key="types.value"
:label="types.label"
:value="types.value"
/>
</el-select>
</div>
<div class="mr-2">
<el-form-item
:prop="`${bind}.header.${index}.value`"
:rules="{
required: true,
message: '参数值不能为空',
trigger: 'blur'
}"
>
<el-input
v-if="item.type === BpmHttpRequestParamTypeEnum.FIXED_VALUE"
class="w-160px"
v-model="item.value"
/>
</el-form-item>
<el-form-item
:prop="`${bind}.header.${index}.value`"
:rules="{
required: true,
message: '参数值不能为空',
trigger: 'change'
}"
>
<el-select
v-if="item.type === BpmHttpRequestParamTypeEnum.FROM_FORM"
class="w-160px!"
v-model="item.value"
>
<el-option
v-for="(field, fIdx) in formFieldOptions"
:key="fIdx"
:label="field.title"
:value="field.field"
:disabled="!field.required"
/>
</el-select>
</el-form-item>
</div>
<div class="mr-1 flex items-center">
<Icon icon="ep:delete" :size="18" @click="deleteHttpRequestParam(props.header, index)" />
</div>
</div>
<el-button type="primary" text @click="addHttpRequestParam(props.header)">
<Icon icon="ep:plus" class="mr-5px" />添加一行
</el-button>
</el-form-item>
<el-form-item label-position="top" label="请求体">
<div class="flex pt-2" v-for="(item, index) in props.body" :key="index">
<div class="mr-2">
<el-form-item
:prop="`${bind}.body.${index}.key`"
:rules="{
required: true,
message: '参数名不能为空',
trigger: 'blur'
}"
>
<el-input class="w-160px" v-model="item.key" />
</el-form-item>
</div>
<div class="mr-2">
<el-select class="w-100px!" v-model="item.type">
<el-option
v-for="types in BPM_HTTP_REQUEST_PARAM_TYPES"
:key="types.value"
:label="types.label"
:value="types.value"
/>
</el-select>
</div>
<div class="mr-2">
<el-form-item
:prop="`${bind}.body.${index}.value`"
:rules="{
required: true,
message: '参数值不能为空',
trigger: 'blur'
}"
>
<el-input
v-if="item.type === BpmHttpRequestParamTypeEnum.FIXED_VALUE"
class="w-160px"
v-model="item.value"
/>
</el-form-item>
<el-form-item
:prop="`${bind}.body.${index}.value`"
:rules="{
required: true,
message: '参数值不能为空',
trigger: 'change'
}"
>
<el-select
v-if="item.type === BpmHttpRequestParamTypeEnum.FROM_FORM"
class="w-160px!"
v-model="item.value"
>
<el-option
v-for="(field, fIdx) in formFieldOptions"
:key="fIdx"
:label="field.title"
:value="field.field"
:disabled="!field.required"
/>
</el-select>
</el-form-item>
</div>
<div class="mr-1 flex items-center">
<Icon icon="ep:delete" :size="18" @click="deleteHttpRequestParam(props.body, index)" />
</div>
</div>
<el-button type="primary" text @click="addHttpRequestParam(props.body)">
<Icon icon="ep:plus" class="mr-5px" />添加一行
</el-button>
</el-form-item>
</template>
<script setup lang="ts">
import {
HttpRequestParam,
BPM_HTTP_REQUEST_PARAM_TYPES,
BpmHttpRequestParamTypeEnum
} from '../../consts'
import { useFormFieldsAndStartUser } from '../../node'
defineOptions({
name: 'HttpRequestParamSetting'
})
const props = defineProps({
header: {
type: Array as () => HttpRequestParam[],
required: false,
default: () => []
},
body: {
type: Array as () => HttpRequestParam[],
required: false,
default: () => []
},
bind: {
type: String,
required: true
}
})
// 流程表单字段,发起人字段
const formFieldOptions = useFormFieldsAndStartUser()
/** 添加请求配置项 */
const addHttpRequestParam = (arr: HttpRequestParam[]) => {
arr.push({
key: '',
type: BpmHttpRequestParamTypeEnum.FIXED_VALUE,
value: ''
})
}
/** 删除请求配置项 */
const deleteHttpRequestParam = (arr: HttpRequestParam[], index: number) => {
arr.splice(index, 1)
}
</script>
<style lang="scss" scoped></style>
<template>
<el-form-item>
<el-alert
title="仅支持 POST 请求,以请求体方式接收参数"
type="warning"
show-icon
:closable="false"
/>
</el-form-item>
<!-- 请求地址-->
<el-form-item
label-position="top"
label="请求地址"
:prop="`${formItemPrefix}.url`"
:rules="{
required: true,
message: '请求地址不能为空',
trigger: 'blur'
}"
>
<el-input v-model="setting.url" />
</el-form-item>
<!-- 请求头,请求体设置-->
<HttpRequestParamSetting :header="setting.header" :body="setting.body" :bind="formItemPrefix" />
<!-- 返回值设置-->
<div v-if="responseEnable">
<el-form-item label="返回值" label-position="top">
<el-alert
title="通过请求返回值, 可以修改流程表单的值"
type="warning"
show-icon
:closable="false"
/>
</el-form-item>
<el-form-item>
<div class="flex pt-2" v-for="(item, index) in setting.response" :key="index">
<div class="mr-2">
<el-form-item
:prop="`${formItemPrefix}.response.${index}.key`"
:rules="{
required: true,
message: '表单字段不能为空',
trigger: 'blur'
}"
>
<el-select class="w-160px!" v-model="item.key" placeholder="请选择表单字段">
<el-option
v-for="(field, fIdx) in formFields"
:key="fIdx"
:label="field.title"
:value="field.field"
:disabled="!field.required"
/>
</el-select>
</el-form-item>
</div>
<div class="mr-2">
<el-form-item
:prop="`${formItemPrefix}.response.${index}.value`"
:rules="{
required: true,
message: '请求返回字段不能为空',
trigger: 'blur'
}"
>
<el-input class="w-160px" v-model="item.value" placeholder="请求返回字段" />
</el-form-item>
</div>
<div class="mr-1 pt-1 cursor-pointer">
<Icon
icon="ep:delete"
:size="18"
@click="deleteHttpResponseSetting(setting.response!, index)"
/>
</div>
</div>
<el-button type="primary" text @click="addHttpResponseSetting(setting.response!)">
<Icon icon="ep:plus" class="mr-5px" />添加一行
</el-button>
</el-form-item>
</div>
</template>
<script setup lang="ts">
import HttpRequestParamSetting from './HttpRequestParamSetting.vue'
import { useFormFields } from '../../node'
const props = defineProps({
setting: {
type: Object,
required: true
},
responseEnable: {
type: Boolean,
required: true
},
formItemPrefix: {
type: String,
required: true
}
})
const { setting } = toRefs(props)
const emits = defineEmits(['update:setting'])
watch(
() => setting,
(val) => {
emits('update:setting', val)
}
)
/** 流程表单字段 */
const formFields = useFormFields()
/** 添加 HTTP 请求返回值设置项 */
const addHttpResponseSetting = (responseSetting: Record<string, string>[]) => {
responseSetting.push({
key: '',
value: ''
})
}
/** 删除 HTTP 请求返回值设置项 */
const deleteHttpResponseSetting = (responseSetting: Record<string, string>[], index: number) => {
responseSetting.splice(index, 1)
}
</script>
<style lang="scss" scoped></style>
<template>
<el-form ref="listenerFormRef" :model="configForm" label-position="top">
<div v-for="(listener, listenerIdx) in taskListener" :key="listenerIdx">
<el-divider content-position="left">
<el-text tag="b" size="large">{{ listener.name }}</el-text>
</el-divider>
<el-form-item>
<el-switch
v-model="configForm[`task${listener.type}ListenerEnable`]"
active-text="开启"
inactive-text="关闭"
/>
</el-form-item>
<div v-if="configForm[`task${listener.type}ListenerEnable`]">
<el-form-item>
<el-alert
title="仅支持 POST 请求,以请求体方式接收参数"
type="warning"
show-icon
:closable="false"
/>
</el-form-item>
<el-form-item
label="请求地址"
:prop="`task${listener.type}ListenerPath`"
:rules="{
required: true,
message: '请求地址不能为空',
trigger: 'blur'
}"
>
<el-input v-model="configForm[`task${listener.type}ListenerPath`]" />
</el-form-item>
<HttpRequestParamSetting
:header="configForm[`task${listener.type}Listener`].header"
:body="configForm[`task${listener.type}Listener`].body"
:bind="`task${listener.type}Listener`"
/>
</div>
</div>
</el-form>
</template>
<script setup lang="ts">
import HttpRequestParamSetting from './HttpRequestParamSetting.vue'
const props = defineProps({
modelValue: {
type: Object,
required: true
},
formFieldOptions: {
type: Object,
required: true
}
})
const emit = defineEmits(['update:modelValue'])
const listenerFormRef = ref()
const configForm = computed({
get() {
return props.modelValue
},
set(newValue) {
emit('update:modelValue', newValue)
}
})
const taskListener = ref([
{
name: '创建任务',
type: 'Create'
},
{
name: '指派任务执行人员',
type: 'Assign'
},
{
name: '完成任务',
type: 'Complete'
}
])
const validate = async () => {
if (!listenerFormRef) return false
return await listenerFormRef.value.validate()
}
defineExpose({ validate })
</script>
<template>
<div class="node-wrapper">
<div class="node-container">
<div
class="node-box"
:class="[
{ 'node-config-error': !currentNode.showText },
`${useTaskStatusClass(currentNode?.activityStatus)}`
]"
>
<div class="node-title-container">
<div
:class="`node-title-icon ${currentNode.childProcessSetting?.async === true ? 'async-child-process' : 'child-process'}`"
>
<span
:class="`iconfont ${currentNode.childProcessSetting?.async === true ? 'icon-async-child-process' : 'icon-child-process'}`"
>
</span>
</div>
<input
v-if="!readonly && showInput"
type="text"
class="editable-title-input"
@blur="blurEvent()"
v-mountedFocus
v-model="currentNode.name"
:placeholder="currentNode.name"
/>
<div v-else class="node-title" @click="clickTitle">
{{ currentNode.name }}
</div>
</div>
<div class="node-content" @click="openNodeConfig">
<div class="node-text" :title="currentNode.showText" v-if="currentNode.showText">
{{ currentNode.showText }}
</div>
<div class="node-text" v-else>
{{ NODE_DEFAULT_TEXT.get(NodeType.CHILD_PROCESS_NODE) }}
</div>
<Icon v-if="!readonly" icon="ep:arrow-right-bold" />
</div>
<div v-if="!readonly" class="node-toolbar">
<div class="toolbar-icon"
><Icon color="#0089ff" icon="ep:circle-close-filled" :size="18" @click="deleteNode"
/></div>
</div>
</div>
<!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
<NodeHandler
v-if="currentNode"
v-model:child-node="currentNode.childNode"
:current-node="currentNode"
/>
</div>
<ChildProcessNodeConfig
v-if="!readonly && currentNode"
ref="nodeSetting"
:flow-node="currentNode"
/>
</div>
</template>
<script setup lang="ts">
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
import NodeHandler from '../NodeHandler.vue'
import { useNodeName2, useWatchNode, useTaskStatusClass } from '../node'
import ChildProcessNodeConfig from '../nodes-config/ChildProcessNodeConfig.vue'
defineOptions({
name: 'ChildProcessNode'
})
const props = defineProps({
flowNode: {
type: Object as () => SimpleFlowNode,
required: true
}
})
// 定义事件,更新父组件。
const emits = defineEmits<{
'update:flowNode': [node: SimpleFlowNode | undefined]
}>()
// 是否只读
const readonly = inject<Boolean>('readonly')
// 监控节点的变化
const currentNode = useWatchNode(props)
// 节点名称编辑
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.CHILD_PROCESS_NODE)
const nodeSetting = ref()
// 打开节点配置
const openNodeConfig = () => {
if (readonly) {
return
}
nodeSetting.value.showChildProcessNodeConfig(currentNode.value)
nodeSetting.value.openDrawer()
}
// 删除节点。更新当前节点为孩子节点
const deleteNode = () => {
emits('update:flowNode', currentNode.value.childNode)
}
</script>
<style scoped></style>
...@@ -9,8 +9,7 @@ ...@@ -9,8 +9,7 @@
]" ]"
> >
<div class="node-title-container"> <div class="node-title-container">
<!-- TODO @芋艿 需要更换图标 --> <div class="node-title-icon delay-node"><span class="iconfont icon-delay"></span></div>
<div class="node-title-icon copy-task"><span class="iconfont icon-copy"></span></div>
<input <input
v-if="!readonly && showInput" v-if="!readonly && showInput"
type="text" type="text"
......
...@@ -77,7 +77,7 @@ const props = defineProps({ ...@@ -77,7 +77,7 @@ const props = defineProps({
const currentNode = useWatchNode(props) const currentNode = useWatchNode(props)
// 是否只读 // 是否只读
const readonly = inject<Boolean>('readonly') const readonly = inject<Boolean>('readonly')
const processInstance = inject<Ref<any>>('processInstance') const processInstance = inject<Ref<any>>('processInstance', ref({}))
// 审批信息的弹窗显示,用于只读模式 // 审批信息的弹窗显示,用于只读模式
const dialogVisible = ref(false) // 弹窗可见性 const dialogVisible = ref(false) // 弹窗可见性
const processInstanceInfos = ref<any[]>([]) // 流程的审批信息 const processInstanceInfos = ref<any[]>([]) // 流程的审批信息
......
...@@ -108,7 +108,7 @@ ...@@ -108,7 +108,7 @@
<script setup lang="ts"> <script setup lang="ts">
import NodeHandler from '../NodeHandler.vue' import NodeHandler from '../NodeHandler.vue'
import ProcessNodeTree from '../ProcessNodeTree.vue' import ProcessNodeTree from '../ProcessNodeTree.vue'
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts' import { SimpleFlowNode, NodeType, ConditionType, DEFAULT_CONDITION_GROUP_VALUE, NODE_DEFAULT_TEXT } from '../consts'
import { getDefaultConditionNodeName } from '../utils' import { getDefaultConditionNodeName } from '../utils'
import { useTaskStatusClass } from '../node' import { useTaskStatusClass } from '../node'
import { generateUUID } from '@/utils' import { generateUUID } from '@/utils'
...@@ -149,7 +149,7 @@ const blurEvent = (index: number) => { ...@@ -149,7 +149,7 @@ const blurEvent = (index: number) => {
showInputs.value[index] = false showInputs.value[index] = false
const conditionNode = currentNode.value.conditionNodes?.at(index) as SimpleFlowNode const conditionNode = currentNode.value.conditionNodes?.at(index) as SimpleFlowNode
conditionNode.name = conditionNode.name =
conditionNode.name || getDefaultConditionNodeName(index, conditionNode.defaultFlow) conditionNode.name || getDefaultConditionNodeName(index, conditionNode.conditionSetting?.defaultFlow)
} }
// 点击条件名称 // 点击条件名称
...@@ -178,8 +178,11 @@ const addCondition = () => { ...@@ -178,8 +178,11 @@ const addCondition = () => {
type: NodeType.CONDITION_NODE, type: NodeType.CONDITION_NODE,
childNode: undefined, childNode: undefined,
conditionNodes: [], conditionNodes: [],
conditionType: 1, conditionSetting: {
defaultFlow: false defaultFlow: false,
conditionType: ConditionType.RULE,
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE
}
} }
conditionNodes.splice(lastIndex, 0, conditionData) conditionNodes.splice(lastIndex, 0, conditionData)
} }
......
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
]" ]"
> >
<div class="branch-node-title-container"> <div class="branch-node-title-container">
<div v-if="showInputs[index]"> <div v-if="!readonly && showInputs[index]">
<input <input
type="text" type="text"
class="editable-title-input" class="editable-title-input"
...@@ -110,7 +110,7 @@ ...@@ -110,7 +110,7 @@
<script setup lang="ts"> <script setup lang="ts">
import NodeHandler from '../NodeHandler.vue' import NodeHandler from '../NodeHandler.vue'
import ProcessNodeTree from '../ProcessNodeTree.vue' import ProcessNodeTree from '../ProcessNodeTree.vue'
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts' import { SimpleFlowNode, NodeType, ConditionType, DEFAULT_CONDITION_GROUP_VALUE, NODE_DEFAULT_TEXT } from '../consts'
import { useTaskStatusClass } from '../node' import { useTaskStatusClass } from '../node'
import { getDefaultInclusiveConditionNodeName } from '../utils' import { getDefaultInclusiveConditionNodeName } from '../utils'
import { generateUUID } from '@/utils' import { generateUUID } from '@/utils'
...@@ -153,7 +153,7 @@ const blurEvent = (index: number) => { ...@@ -153,7 +153,7 @@ const blurEvent = (index: number) => {
showInputs.value[index] = false showInputs.value[index] = false
const conditionNode = currentNode.value.conditionNodes?.at(index) as SimpleFlowNode const conditionNode = currentNode.value.conditionNodes?.at(index) as SimpleFlowNode
conditionNode.name = conditionNode.name =
conditionNode.name || getDefaultInclusiveConditionNodeName(index, conditionNode.defaultFlow) conditionNode.name || getDefaultInclusiveConditionNodeName(index, conditionNode.conditionSetting?.defaultFlow)
} }
// 点击条件名称 // 点击条件名称
...@@ -182,8 +182,11 @@ const addCondition = () => { ...@@ -182,8 +182,11 @@ const addCondition = () => {
type: NodeType.CONDITION_NODE, type: NodeType.CONDITION_NODE,
childNode: undefined, childNode: undefined,
conditionNodes: [], conditionNodes: [],
conditionType: 1, conditionSetting: {
defaultFlow: false defaultFlow: false,
conditionType: ConditionType.RULE,
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE
}
} }
conditionNodes.splice(lastIndex, 0, conditionData) conditionNodes.splice(lastIndex, 0, conditionData)
} }
......
<template>
<div class="node-wrapper">
<div class="node-container">
<div
class="node-box"
:class="[
{ 'node-config-error': !currentNode.showText },
`${useTaskStatusClass(currentNode?.activityStatus)}`
]"
>
<div class="node-title-container">
<div class="node-title-icon router-node">
<span class="iconfont icon-router"></span>
</div>
<input
v-if="!readonly && showInput"
type="text"
class="editable-title-input"
@blur="blurEvent()"
v-mountedFocus
v-model="currentNode.name"
:placeholder="currentNode.name"
/>
<div v-else class="node-title" @click="clickTitle">
{{ currentNode.name }}
</div>
</div>
<div class="node-content" @click="openNodeConfig">
<div class="node-text" :title="currentNode.showText" v-if="currentNode.showText">
{{ currentNode.showText }}
</div>
<div class="node-text" v-else>
{{ NODE_DEFAULT_TEXT.get(NodeType.ROUTER_BRANCH_NODE) }}
</div>
<Icon v-if="!readonly" icon="ep:arrow-right-bold" />
</div>
<div v-if="!readonly" class="node-toolbar">
<div class="toolbar-icon"
><Icon color="#0089ff" icon="ep:circle-close-filled" :size="18" @click="deleteNode"
/></div>
</div>
</div>
<!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
<NodeHandler
v-if="currentNode"
v-model:child-node="currentNode.childNode"
:current-node="currentNode"
/>
</div>
<RouterNodeConfig v-if="!readonly && currentNode" ref="nodeSetting" :flow-node="currentNode" />
</div>
</template>
<script setup lang="ts">
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
import NodeHandler from '../NodeHandler.vue'
import { useNodeName2, useWatchNode, useTaskStatusClass } from '../node'
import RouterNodeConfig from '../nodes-config/RouterNodeConfig.vue'
defineOptions({
name: 'RouterNode'
})
const props = defineProps({
flowNode: {
type: Object as () => SimpleFlowNode,
required: true
}
})
// 定义事件,更新父组件
const emits = defineEmits<{
'update:flowNode': [node: SimpleFlowNode | undefined]
}>()
// 是否只读
const readonly = inject<Boolean>('readonly')
// 监控节点的变化
const currentNode = useWatchNode(props)
// 节点名称编辑
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.ROUTER_BRANCH_NODE)
const nodeSetting = ref()
// 打开节点配置
const openNodeConfig = () => {
if (readonly) {
return
}
nodeSetting.value.showRouteNodeConfig(currentNode.value)
nodeSetting.value.openDrawer()
}
// 删除节点。更新当前节点为孩子节点
const deleteNode = () => {
emits('update:flowNode', currentNode.value.childNode)
}
</script>
<style lang="scss" scoped></style>
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
><span class="iconfont icon-start-user"></span ><span class="iconfont icon-start-user"></span
></div> ></div>
<input <input
v-if="showInput" v-if="!readonly && showInput"
type="text" type="text"
class="editable-title-input" class="editable-title-input"
@blur="blurEvent()" @blur="blurEvent()"
...@@ -117,7 +117,7 @@ const props = defineProps({ ...@@ -117,7 +117,7 @@ const props = defineProps({
} }
}) })
const readonly = inject<Boolean>('readonly') // 是否只读 const readonly = inject<Boolean>('readonly') // 是否只读
const tasks = inject<Ref<any[]>>('tasks') const tasks = inject<Ref<any[]>>('tasks', ref([]))
// 定义事件,更新父组件。 // 定义事件,更新父组件。
const emits = defineEmits<{ const emits = defineEmits<{
'update:modelValue': [node: SimpleFlowNode | undefined] 'update:modelValue': [node: SimpleFlowNode | undefined]
......
<template>
<div class="node-wrapper">
<div class="node-container">
<div
class="node-box"
:class="[
{ 'node-config-error': !currentNode.showText },
`${useTaskStatusClass(currentNode?.activityStatus)}`
]"
>
<div class="node-title-container">
<div class="node-title-icon trigger-node">
<span class="iconfont icon-trigger"></span>
</div>
<input
v-if="!readonly && showInput"
type="text"
class="editable-title-input"
@blur="blurEvent()"
v-mountedFocus
v-model="currentNode.name"
:placeholder="currentNode.name"
/>
<div v-else class="node-title" @click="clickTitle">
{{ currentNode.name }}
</div>
</div>
<div class="node-content" @click="openNodeConfig">
<div class="node-text" :title="currentNode.showText" v-if="currentNode.showText">
{{ currentNode.showText }}
</div>
<div class="node-text" v-else>
{{ NODE_DEFAULT_TEXT.get(NodeType.TRIGGER_NODE) }}
</div>
<Icon v-if="!readonly" icon="ep:arrow-right-bold" />
</div>
<div v-if="!readonly" class="node-toolbar">
<div class="toolbar-icon"
><Icon color="#0089ff" icon="ep:circle-close-filled" :size="18" @click="deleteNode"
/></div>
</div>
</div>
<!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
<NodeHandler
v-if="currentNode"
v-model:child-node="currentNode.childNode"
:current-node="currentNode"
/>
</div>
<TriggerNodeConfig v-if="!readonly && currentNode" ref="nodeSetting" :flow-node="currentNode" />
</div>
</template>
<script setup lang="ts">
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
import NodeHandler from '../NodeHandler.vue'
import { useNodeName2, useWatchNode, useTaskStatusClass } from '../node'
import TriggerNodeConfig from '../nodes-config/TriggerNodeConfig.vue'
defineOptions({
name: 'TriggerNode'
})
const props = defineProps({
flowNode: {
type: Object as () => SimpleFlowNode,
required: true
}
})
// 定义事件,更新父组件
const emits = defineEmits<{
'update:flowNode': [node: SimpleFlowNode | undefined]
}>()
// 是否只读
const readonly = inject<Boolean>('readonly')
// 监控节点的变化
const currentNode = useWatchNode(props)
// 节点名称编辑
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.TRIGGER_NODE)
const nodeSetting = ref()
// 打开节点配置
const openNodeConfig = () => {
if (readonly) {
return
}
nodeSetting.value.showTriggerNodeConfig(currentNode.value)
nodeSetting.value.openDrawer()
}
// 删除节点。更新当前节点为孩子节点
const deleteNode = () => {
emits('update:flowNode', currentNode.value.childNode)
}
</script>
<style lang="scss" scoped></style>
...@@ -9,7 +9,14 @@ ...@@ -9,7 +9,14 @@
]" ]"
> >
<div class="node-title-container"> <div class="node-title-container">
<div class="node-title-icon user-task"><span class="iconfont icon-approve"></span></div> <div
:class="`node-title-icon ${currentNode.type === NodeType.TRANSACTOR_NODE ? 'transactor-task' : 'user-task'}`"
>
<span
:class="`iconfont ${currentNode.type === NodeType.TRANSACTOR_NODE ? 'icon-transactor' : 'icon-approve'}`"
>
</span>
</div>
<input <input
v-if="!readonly && showInput" v-if="!readonly && showInput"
type="text" type="text"
...@@ -28,7 +35,7 @@ ...@@ -28,7 +35,7 @@
{{ currentNode.showText }} {{ currentNode.showText }}
</div> </div>
<div class="node-text" v-else> <div class="node-text" v-else>
{{ NODE_DEFAULT_TEXT.get(NodeType.USER_TASK_NODE) }} {{ NODE_DEFAULT_TEXT.get(currentNode.type) }}
</div> </div>
<Icon icon="ep:arrow-right-bold" v-if="!readonly" /> <Icon icon="ep:arrow-right-bold" v-if="!readonly" />
</div> </div>
...@@ -131,7 +138,7 @@ const emits = defineEmits<{ ...@@ -131,7 +138,7 @@ const emits = defineEmits<{
// 是否只读 // 是否只读
const readonly = inject<Boolean>('readonly') const readonly = inject<Boolean>('readonly')
const tasks = inject<Ref<any[]>>('tasks') const tasks = inject<Ref<any[]>>('tasks', ref([]))
// 监控节点变化 // 监控节点变化
const currentNode = useWatchNode(props) const currentNode = useWatchNode(props)
// 节点名称编辑 // 节点名称编辑
......
...@@ -113,18 +113,21 @@ ...@@ -113,18 +113,21 @@
// 节点连线气泡卡片样式 // 节点连线气泡卡片样式
.handler-item-wrapper { .handler-item-wrapper {
width: 320px;
display: flex; display: flex;
flex-wrap: wrap;
cursor: pointer; cursor: pointer;
.handler-item { .handler-item {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
margin-top: 12px;
} }
.handler-item-icon { .handler-item-icon {
width: 60px; width: 50px;
height: 60px; height: 50px;
background: #fff; background: #fff;
border: 1px solid #e2e2e2; border: 1px solid #e2e2e2;
border-radius: 50%; border-radius: 50%;
...@@ -138,13 +141,14 @@ ...@@ -138,13 +141,14 @@
.icon-size { .icon-size {
font-size: 25px; font-size: 25px;
line-height: 60px; line-height: 50px;
} }
} }
.approve { .approve {
color: #ff943e; color: #ff943e;
} }
.copy { .copy {
color: #3296fa; color: #3296fa;
} }
...@@ -161,6 +165,30 @@ ...@@ -161,6 +165,30 @@
color: #345da2; color: #345da2;
} }
.delay {
color: #e47470;
}
.trigger {
color: #3373d2;
}
.router {
color: #ca3a31
}
.transactor {
color: #330099;
}
.child-process {
color: #996633;
}
.async-child-process {
color: #006666;
}
.handler-item-text { .handler-item-text {
margin-top: 4px; margin-top: 4px;
width: 80px; width: 80px;
...@@ -266,6 +294,30 @@ ...@@ -266,6 +294,30 @@
&.start-user { &.start-user {
color: #676565; color: #676565;
} }
&.delay-node {
color: #e47470;
}
&.trigger-node {
color: #3373d2;
}
&.router-node {
color: #ca3a31
}
&.transactor-task {
color: #330099;
}
&.child-process {
color: #996633;
}
&.async-child-process {
color: #006666;
}
} }
.node-title { .node-title {
...@@ -711,45 +763,64 @@ ...@@ -711,45 +763,64 @@
// iconfont 样式 // iconfont 样式
@font-face { @font-face {
font-family: 'iconfont'; /* Project id 4495938 */ font-family: "iconfont"; /* Project id 4495938 */
src: src: url('iconfont.woff2?t=1737639517142') format('woff2'),
url('iconfont.woff2?t=1724339470412') format('woff2'), url('iconfont.woff?t=1737639517142') format('woff'),
url('iconfont.woff?t=1724339470412') format('woff'), url('iconfont.ttf?t=1737639517142') format('truetype');
url('iconfont.ttf?t=1724339470412') format('truetype');
} }
.iconfont { .iconfont {
font-family: 'iconfont' !important; font-family: "iconfont" !important;
font-size: 16px; font-size: 16px;
font-style: normal; font-style: normal;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-trigger:before {
content: "\e6d3";
}
.icon-router:before {
content: "\e6b2";
}
.icon-delay:before {
content: "\e600";
}
.icon-start-user:before { .icon-start-user:before {
content: '\e679'; content: "\e679";
} }
.icon-inclusive:before { .icon-inclusive:before {
content: '\e602'; content: "\e602";
} }
.icon-copy:before { .icon-copy:before {
content: '\e7eb'; content: "\e7eb";
} }
.icon-handle:before { .icon-transactor:before {
content: '\e61c'; content: "\e61c";
} }
.icon-exclusive:before { .icon-exclusive:before {
content: '\e717'; content: "\e717";
} }
.icon-approve:before { .icon-approve:before {
content: '\e715'; content: "\e715";
} }
.icon-parallel:before { .icon-parallel:before {
content: '\e688'; content: "\e688";
}
.icon-async-child-process:before {
content: "\e6f2";
}
.icon-child-process:before {
content: "\e6c1";
} }
<!-- 列表选择通用组件,参考 ProductList 组件使用 --> <!-- 列表选择通用组件,参考 ProductList 组件使用 -->
<!-- TODO 芋艿:可能会移除 -->
<template> <template>
<Dialog v-model="dialogVisible" :appendToBody="true" :scroll="true" :title="title" width="60%"> <Dialog v-model="dialogVisible" :appendToBody="true" :scroll="true" :title="title" width="60%">
<el-table <el-table
......
...@@ -188,12 +188,8 @@ ...@@ -188,12 +188,8 @@
:scroll="true" :scroll="true"
max-height="600px" max-height="600px"
> >
<!-- append-to-body --> <div>
<div v-highlight> <pre><code v-dompurify-html="highlightedCode(previewResult)" class="hljs"></code></pre>
<code class="hljs">
<!-- 高亮代码块 -->
{{ previewResult }}
</code>
</div> </div>
</Dialog> </Dialog>
</div> </div>
...@@ -237,6 +233,8 @@ import { XmlNode, XmlNodeType, parseXmlString } from 'steady-xml' ...@@ -237,6 +233,8 @@ import { XmlNode, XmlNodeType, parseXmlString } from 'steady-xml'
// const eventName = reactive({ // const eventName = reactive({
// name: '' // name: ''
// }) // })
import hljs from 'highlight.js' // 导入代码高亮文件
import 'highlight.js/styles/github.css' // 导入代码高亮样式
defineOptions({ name: 'MyProcessDesigner' }) defineOptions({ name: 'MyProcessDesigner' })
...@@ -308,27 +306,17 @@ const props = defineProps({ ...@@ -308,27 +306,17 @@ const props = defineProps({
} }
}) })
// 监听value变化,重新加载流程图 /**
watch( * 代码高亮
() => props.value, */
(newValue) => { const highlightedCode = (code: string) => {
if (newValue && bpmnModeler) { // 高亮
createNewDiagram(newValue) if (previewType.value === 'json') {
} code = JSON.stringify(code, null, 2)
}, }
{ immediate: true } const result = hljs.highlight(code, { language: previewType.value, ignoreIllegals: true })
) return result.value || '&nbsp;'
}
// 监听processId和processName变化
watch(
[() => props.processId, () => props.processName],
([newId, newName]) => {
if (newId && newName && !props.value) {
createNewDiagram(null)
}
},
{ immediate: true }
)
provide('configGlobal', props) provide('configGlobal', props)
let bpmnModeler: any = null let bpmnModeler: any = null
...@@ -480,6 +468,7 @@ const initModelListeners = () => { ...@@ -480,6 +468,7 @@ const initModelListeners = () => {
emit('commandStack-changed', event) emit('commandStack-changed', event)
emit('input', xml) emit('input', xml)
emit('change', xml) emit('change', xml)
emit('save', xml)
} catch (e: any) { } catch (e: any) {
console.error(`[Process Designer Warn]: ${e.message || e}`) console.error(`[Process Designer Warn]: ${e.message || e}`)
} }
...@@ -568,6 +557,7 @@ const importLocalFile = () => { ...@@ -568,6 +557,7 @@ const importLocalFile = () => {
reader.onload = function () { reader.onload = function () {
let xmlStr = this.result let xmlStr = this.result
createNewDiagram(xmlStr) createNewDiagram(xmlStr)
emit('save', xmlStr)
} }
} }
/* ------------------------------------------------ refs methods ------------------------------------------------------ */ /* ------------------------------------------------ refs methods ------------------------------------------------------ */
......
...@@ -1438,6 +1438,45 @@ ...@@ -1438,6 +1438,45 @@
"isBody": true "isBody": true
} }
] ]
},
{
"name": "SignEnable",
"superClass": ["Element"],
"meta": {
"allowedIn": ["bpmn:UserTask"]
},
"properties": [
{
"name": "value",
"type": "Boolean",
"isBody": true
}
]
},
{
"name": "SkipExpression",
"extends": ["bpmn:UserTask"],
"properties": [
{
"name": "skipExpression",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "ReasonRequire",
"superClass": ["Element"],
"meta": {
"allowedIn": ["bpmn:UserTask"]
},
"properties": [
{
"name": "value",
"type": "Boolean",
"isBody": true
}
]
} }
], ],
"emumerations": [] "emumerations": []
......
<template> <template>
<div class="process-panel__container" :style="{ width: `${width}px` }"> <div class="process-panel__container" :style="{ width: `${width}px`, maxHeight: '600px' }">
<el-collapse v-model="activeTab" v-if="isReady"> <el-collapse v-model="activeTab" v-if="isReady">
<el-collapse-item name="base"> <el-collapse-item name="base">
<!-- class="panel-tab__title" --> <!-- class="panel-tab__title" -->
......
...@@ -152,6 +152,9 @@ watch( ...@@ -152,6 +152,9 @@ watch(
handleKeyUpdate(props.model.key) handleKeyUpdate(props.model.key)
handleNameUpdate(props.model.name) handleNameUpdate(props.model.name)
} }
},
{
immediate: true
} }
) )
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
4. 操作按钮 4. 操作按钮
5. 字段权限 5. 字段权限
6. 审批类型 6. 审批类型
7. 是否需要签名
--> -->
<template> <template>
<div> <div>
...@@ -122,13 +123,19 @@ ...@@ -122,13 +123,19 @@
</div> </div>
<el-divider content-position="left">字段权限</el-divider> <el-divider content-position="left">字段权限</el-divider>
<div class="field-setting-pane" v-if="formType === 10"> <div class="field-setting-pane" v-if="formType === BpmModelFormType.NORMAL">
<div class="field-permit-title"> <div class="field-permit-title">
<div class="setting-title-label first-title"> 字段名称 </div> <div class="setting-title-label first-title"> 字段名称 </div>
<div class="other-titles"> <div class="other-titles">
<span class="setting-title-label">只读</span> <span class="setting-title-label cursor-pointer" @click="updatePermission('READ')"
<span class="setting-title-label">可编辑</span> >只读</span
<span class="setting-title-label">隐藏</span> >
<span class="setting-title-label cursor-pointer" @click="updatePermission('WRITE')"
>可编辑</span
>
<span class="setting-title-label cursor-pointer" @click="updatePermission('NONE')"
>隐藏</span
>
</div> </div>
</div> </div>
<div class="field-setting-item" v-for="(item, index) in fieldsPermissionEl" :key="index"> <div class="field-setting-item" v-for="(item, index) in fieldsPermissionEl" :key="index">
...@@ -139,28 +146,54 @@ ...@@ -139,28 +146,54 @@
:value="FieldPermissionType.READ" :value="FieldPermissionType.READ"
size="large" size="large"
:label="FieldPermissionType.READ" :label="FieldPermissionType.READ"
><span></span @change="updateElementExtensions"
></el-radio> >
<span></span>
</el-radio>
</div> </div>
<div class="item-radio-wrap"> <div class="item-radio-wrap">
<el-radio <el-radio
:value="FieldPermissionType.WRITE" :value="FieldPermissionType.WRITE"
size="large" size="large"
:label="FieldPermissionType.WRITE" :label="FieldPermissionType.WRITE"
><span></span @change="updateElementExtensions"
></el-radio> >
<span></span>
</el-radio>
</div> </div>
<div class="item-radio-wrap"> <div class="item-radio-wrap">
<el-radio <el-radio
:value="FieldPermissionType.NONE" :value="FieldPermissionType.NONE"
size="large" size="large"
:label="FieldPermissionType.NONE" :label="FieldPermissionType.NONE"
><span></span @change="updateElementExtensions"
></el-radio> >
<span></span>
</el-radio>
</div> </div>
</el-radio-group> </el-radio-group>
</div> </div>
</div> </div>
<el-divider content-position="left">是否需要签名</el-divider>
<el-form-item prop="signEnable">
<el-switch
v-model="signEnable.value"
active-text="是"
inactive-text="否"
@change="updateElementExtensions"
/>
</el-form-item>
<el-divider content-position="left">审批意见</el-divider>
<el-form-item prop="reasonRequire">
<el-switch
v-model="reasonRequire.value"
active-text="必填"
inactive-text="非必填"
@change="updateElementExtensions"
/>
</el-form-item>
</div> </div>
</template> </template>
...@@ -180,6 +213,7 @@ import { ...@@ -180,6 +213,7 @@ import {
} from '@/components/SimpleProcessDesignerV2/src/consts' } from '@/components/SimpleProcessDesignerV2/src/consts'
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
import { useFormFieldsPermission } from '@/components/SimpleProcessDesignerV2/src/node' import { useFormFieldsPermission } from '@/components/SimpleProcessDesignerV2/src/node'
import { BpmModelFormType } from '@/utils/constants'
defineOptions({ name: 'ElementCustomConfig4UserTask' }) defineOptions({ name: 'ElementCustomConfig4UserTask' })
const props = defineProps({ const props = defineProps({
...@@ -218,6 +252,12 @@ const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFie ...@@ -218,6 +252,12 @@ const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFie
// 审批类型 // 审批类型
const approveType = ref({ value: ApproveType.USER }) const approveType = ref({ value: ApproveType.USER })
// 是否需要签名
const signEnable = ref({ value: false })
// 审批意见
const reasonRequire = ref({ value: false })
const elExtensionElements = ref() const elExtensionElements = ref()
const otherExtensions = ref() const otherExtensions = ref()
const bpmnElement = ref() const bpmnElement = ref()
...@@ -231,7 +271,6 @@ const resetCustomConfigList = () => { ...@@ -231,7 +271,6 @@ const resetCustomConfigList = () => {
bpmnElement.value.id, bpmnElement.value.id,
bpmnInstances().modeler bpmnInstances().modeler
) )
// 获取元素扩展属性 或者 创建扩展属性 // 获取元素扩展属性 或者 创建扩展属性
elExtensionElements.value = elExtensionElements.value =
bpmnElement.value.businessObject?.extensionElements ?? bpmnElement.value.businessObject?.extensionElements ??
...@@ -294,14 +333,13 @@ const resetCustomConfigList = () => { ...@@ -294,14 +333,13 @@ const resetCustomConfigList = () => {
} }
// 字段权限 // 字段权限
if (formType.value === 10) { if (formType.value === BpmModelFormType.NORMAL) {
const fieldsPermissionList = elExtensionElements.value.values?.filter( const fieldsPermissionList = elExtensionElements.value.values?.filter(
(ex) => ex.$type === `${prefix}:FieldsPermission` (ex) => ex.$type === `${prefix}:FieldsPermission`
) )
fieldsPermissionEl.value = [] fieldsPermissionEl.value = []
getNodeConfigFormFields() getNodeConfigFormFields()
// 由于默认添加了发起人元素,这里需要删掉 fieldsPermissionConfig.value = fieldsPermissionConfig.value
fieldsPermissionConfig.value = fieldsPermissionConfig.value.slice(1)
fieldsPermissionConfig.value.forEach((element) => { fieldsPermissionConfig.value.forEach((element) => {
element.permission = element.permission =
fieldsPermissionList?.find((obj) => obj.field === element.field)?.permission ?? '1' fieldsPermissionList?.find((obj) => obj.field === element.field)?.permission ?? '1'
...@@ -311,6 +349,16 @@ const resetCustomConfigList = () => { ...@@ -311,6 +349,16 @@ const resetCustomConfigList = () => {
}) })
} }
// 是否需要签名
signEnable.value =
elExtensionElements.value.values?.filter((ex) => ex.$type === `${prefix}:SignEnable`)?.[0] ||
bpmnInstances().moddle.create(`${prefix}:SignEnable`, { value: false })
// 审批意见
reasonRequire.value =
elExtensionElements.value.values?.filter((ex) => ex.$type === `${prefix}:ReasonRequire`)?.[0] ||
bpmnInstances().moddle.create(`${prefix}:ReasonRequire`, { value: false })
// 保留剩余扩展元素,便于后面更新该元素对应属性 // 保留剩余扩展元素,便于后面更新该元素对应属性
otherExtensions.value = otherExtensions.value =
elExtensionElements.value.values?.filter( elExtensionElements.value.values?.filter(
...@@ -322,7 +370,9 @@ const resetCustomConfigList = () => { ...@@ -322,7 +370,9 @@ const resetCustomConfigList = () => {
ex.$type !== `${prefix}:AssignEmptyUserIds` && ex.$type !== `${prefix}:AssignEmptyUserIds` &&
ex.$type !== `${prefix}:ButtonsSetting` && ex.$type !== `${prefix}:ButtonsSetting` &&
ex.$type !== `${prefix}:FieldsPermission` && ex.$type !== `${prefix}:FieldsPermission` &&
ex.$type !== `${prefix}:ApproveType` ex.$type !== `${prefix}:ApproveType` &&
ex.$type !== `${prefix}:SignEnable` &&
ex.$type !== `${prefix}:ReasonRequire`
) ?? [] ) ?? []
// 更新元素扩展属性,避免后续报错 // 更新元素扩展属性,避免后续报错
...@@ -373,7 +423,9 @@ const updateElementExtensions = () => { ...@@ -373,7 +423,9 @@ const updateElementExtensions = () => {
assignEmptyUserIdsEl.value, assignEmptyUserIdsEl.value,
approveType.value, approveType.value,
...buttonsSettingEl.value, ...buttonsSettingEl.value,
...fieldsPermissionEl.value ...fieldsPermissionEl.value,
signEnable.value,
reasonRequire.value
] ]
}) })
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), { bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
...@@ -456,6 +508,19 @@ function useButtonsSetting() { ...@@ -456,6 +508,19 @@ function useButtonsSetting() {
} }
} }
/** 批量更新权限 */
// TODO @lesan:这个页面,有一些 idea 红色报错,咱要不要 fix 下!
const updatePermission = (type: string) => {
fieldsPermissionEl.value.forEach((field) => {
field.permission =
type === 'READ'
? FieldPermissionType.READ
: type === 'WRITE'
? FieldPermissionType.WRITE
: FieldPermissionType.NONE
})
}
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表 const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
onMounted(async () => { onMounted(async () => {
// 获得用户列表 // 获得用户列表
...@@ -466,9 +531,9 @@ onMounted(async () => { ...@@ -466,9 +531,9 @@ onMounted(async () => {
<style lang="scss" scoped> <style lang="scss" scoped>
.button-setting-pane { .button-setting-pane {
display: flex; display: flex;
flex-direction: column;
font-size: 14px;
margin-top: 8px; margin-top: 8px;
font-size: 14px;
flex-direction: column;
.button-setting-desc { .button-setting-desc {
padding-right: 8px; padding-right: 8px;
......
<template> <template>
<div class="panel-tab__content"> <div class="panel-tab__content">
<el-radio-group v-model="approveMethod" @change="onApproveMethodChange"> <el-radio-group
v-if="type === 'UserTask'"
v-model="approveMethod"
@change="onApproveMethodChange"
>
<div class="flex-col"> <div class="flex-col">
<div v-for="(item, index) in APPROVE_METHODS" :key="index"> <div v-for="(item, index) in APPROVE_METHODS" :key="index">
<el-radio :value="item.value" :label="item.value"> <el-radio :value="item.value" :label="item.value">
...@@ -23,6 +27,9 @@ ...@@ -23,6 +27,9 @@
</div> </div>
</div> </div>
</el-radio-group> </el-radio-group>
<div v-else>
除了UserTask以外节点的多实例待实现
</div>
<!-- 与Simple设计器配置合并,保留以前的代码 --> <!-- 与Simple设计器配置合并,保留以前的代码 -->
<el-form label-width="90px" style="display: none"> <el-form label-width="90px" style="display: none">
<el-form-item label="快捷配置"> <el-form-item label="快捷配置">
...@@ -301,19 +308,21 @@ const approveMethod = ref() ...@@ -301,19 +308,21 @@ const approveMethod = ref()
const approveRatio = ref(100) const approveRatio = ref(100)
const otherExtensions = ref() const otherExtensions = ref()
const getElementLoopNew = () => { const getElementLoopNew = () => {
const extensionElements = if (props.type === 'UserTask') {
bpmnElement.value.businessObject?.extensionElements ?? const extensionElements =
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] }) bpmnElement.value.businessObject?.extensionElements ??
approveMethod.value = extensionElements.values.filter( bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
(ex) => ex.$type === `${prefix}:ApproveMethod` approveMethod.value = extensionElements.values.filter(
)?.[0]?.value (ex) => ex.$type === `${prefix}:ApproveMethod`
)?.[0]?.value
otherExtensions.value = otherExtensions.value =
extensionElements.values.filter((ex) => ex.$type !== `${prefix}:ApproveMethod`) ?? [] extensionElements.values.filter((ex) => ex.$type !== `${prefix}:ApproveMethod`) ?? []
if (!approveMethod.value) { if (!approveMethod.value) {
approveMethod.value = ApproveMethodType.SEQUENTIAL_APPROVE approveMethod.value = ApproveMethodType.SEQUENTIAL_APPROVE
updateLoopCharacteristics() updateLoopCharacteristics()
}
} }
} }
const onApproveMethodChange = () => { const onApproveMethodChange = () => {
......
...@@ -192,6 +192,16 @@ ...@@ -192,6 +192,16 @@
<!-- 选择弹窗 --> <!-- 选择弹窗 -->
<ProcessExpressionDialog ref="processExpressionDialogRef" @select="selectProcessExpression" /> <ProcessExpressionDialog ref="processExpressionDialogRef" @select="selectProcessExpression" />
</el-form-item> </el-form-item>
<el-form-item label="跳过表达式" prop="skipExpression">
<el-input
type="textarea"
v-model="userTaskForm.skipExpression"
clearable
style="width: 100%"
@change="updateSkipExpression"
/>
</el-form-item>
</el-form> </el-form>
</template> </template>
...@@ -220,7 +230,8 @@ const props = defineProps({ ...@@ -220,7 +230,8 @@ const props = defineProps({
const prefix = inject('prefix') const prefix = inject('prefix')
const userTaskForm = ref({ const userTaskForm = ref({
candidateStrategy: undefined, // 分配规则 candidateStrategy: undefined, // 分配规则
candidateParam: [] // 分配选项 candidateParam: [], // 分配选项
skipExpression: '' // 跳过表达式
}) })
const bpmnElement = ref() const bpmnElement = ref()
const bpmnInstances = () => (window as any)?.bpmnInstances const bpmnInstances = () => (window as any)?.bpmnInstances
...@@ -311,6 +322,13 @@ const resetTaskForm = () => { ...@@ -311,6 +322,13 @@ const resetTaskForm = () => {
(ex) => ex.$type !== `${prefix}:CandidateStrategy` && ex.$type !== `${prefix}:CandidateParam` (ex) => ex.$type !== `${prefix}:CandidateStrategy` && ex.$type !== `${prefix}:CandidateParam`
) ?? [] ) ?? []
// 跳过表达式
if (businessObject.skipExpression != undefined) {
userTaskForm.value.skipExpression = businessObject.skipExpression
} else {
userTaskForm.value.skipExpression = ''
}
// 改用通过extensionElements来存储数据 // 改用通过extensionElements来存储数据
return return
if (businessObject.candidateStrategy != undefined) { if (businessObject.candidateStrategy != undefined) {
...@@ -390,6 +408,18 @@ const updateElementTask = () => { ...@@ -390,6 +408,18 @@ const updateElementTask = () => {
}) })
} }
const updateSkipExpression = () => {
if (userTaskForm.value.skipExpression && userTaskForm.value.skipExpression !== '') {
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
skipExpression: userTaskForm.value.skipExpression
})
} else {
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
skipExpression: null
})
}
}
// 打开监听器弹窗 // 打开监听器弹窗
const processExpressionDialogRef = ref() const processExpressionDialogRef = ref()
const openProcessExpressionDialog = async () => { const openProcessExpressionDialog = async () => {
......
@use 'bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css'; @use 'bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css';
@use 'bpmn-js-token-simulation/assets/css/font-awesome.min.css';
@use 'bpmn-js-token-simulation/assets/css/normalize.css';
// 边框被 token-simulation 样式覆盖了 // 边框被 token-simulation 样式覆盖了
.djs-palette { .djs-palette {
...@@ -97,12 +95,12 @@ ...@@ -97,12 +95,12 @@
box-sizing: border-box; box-sizing: border-box;
} }
} }
svg { // svg {
width: 100%; // width: 100%;
height: 100%; // height: 100%;
min-height: 100%; // min-height: 100%;
overflow: hidden; // overflow: hidden;
} // }
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment