Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
admin
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
16a98e5a
authored
Mar 30, 2025
by
YunaiV
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'feature/bpm' of
https://gitee.com/yudaocode/yudao-ui-admin-vue3
parents
8d9d9e76
e75dbd67
Show whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
529 additions
and
81 deletions
+529
-81
src/components/DeptSelectForm/index.vue
+122
-0
src/components/SimpleProcessDesignerV2/src/NodeHandler.vue
+3
-2
src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue
+6
-0
src/components/SimpleProcessDesignerV2/src/nodes-config/StartUserNodeConfig.vue
+45
-2
src/components/SimpleProcessDesignerV2/src/nodes-config/TriggerNodeConfig.vue
+6
-5
src/components/SimpleProcessDesignerV2/src/nodes-config/components/Condition.vue
+2
-1
src/components/SimpleProcessDesignerV2/src/nodes-config/components/ConditionDialog.vue
+3
-2
src/components/SimpleProcessDesignerV2/src/nodes/ExclusiveNode.vue
+11
-3
src/components/SimpleProcessDesignerV2/src/nodes/InclusiveNode.vue
+11
-3
src/views/bpm/model/CategoryDraggableModel.vue
+14
-1
src/views/bpm/model/form/BasicInfo.vue
+73
-1
src/views/bpm/model/form/ExtraSettings.vue
+81
-5
src/views/bpm/model/form/ProcessDesign.vue
+1
-0
src/views/bpm/model/form/index.vue
+12
-3
src/views/bpm/oa/leave/create.vue
+120
-53
src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue
+17
-0
src/views/bpm/simple/SimpleModelDesign.vue
+2
-0
No files found.
src/components/DeptSelectForm/index.vue
0 → 100644
View file @
16a98e5a
<
template
>
<Dialog
v-model=
"dialogVisible"
title=
"部门选择"
width=
"600"
>
<el-row
v-loading=
"formLoading"
>
<el-col
:span=
"24"
>
<ContentWrap
class=
"h-1/1"
>
<el-tree
ref=
"treeRef"
:data=
"deptTree"
:props=
"defaultProps"
show-checkbox
:check-strictly=
"checkStrictly"
check-on-click-node
default-expand-all
highlight-current
node-key=
"id"
@
check=
"handleCheck"
/>
</ContentWrap>
</el-col>
</el-row>
<template
#
footer
>
<el-button
:disabled=
"formLoading || !selectedDeptIds?.length"
type=
"primary"
@
click=
"submitForm"
>
确 定
</el-button>
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
</
template
>
</Dialog>
</template>
<
script
lang=
"ts"
setup
>
import
{
defaultProps
,
handleTree
}
from
'@/utils/tree'
import
*
as
DeptApi
from
'@/api/system/dept'
defineOptions
({
name
:
'DeptSelectForm'
})
const
emit
=
defineEmits
<
{
confirm
:
[
deptList
:
any
[]]
}
>
()
const
{
t
}
=
useI18n
()
// 国际化
const
message
=
useMessage
()
// 消息弹窗
const
props
=
defineProps
({
// 是否严格的遵循父子不互相关联
checkStrictly
:
{
type
:
Boolean
,
default
:
false
},
// 是否支持多选
multiple
:
{
type
:
Boolean
,
default
:
true
}
})
const
treeRef
=
ref
()
const
deptTree
=
ref
<
Tree
[]
>
([])
// 部门树形结构
const
selectedDeptIds
=
ref
<
number
[]
>
([])
// 选中的部门 ID 列表
const
dialogVisible
=
ref
(
false
)
// 弹窗的是否展示
const
formLoading
=
ref
(
false
)
// 表单的加载中
/** 打开弹窗 */
const
open
=
async
(
selectedList
?:
DeptApi
.
DeptVO
[])
=>
{
resetForm
()
formLoading
.
value
=
true
try
{
// 加载部门列表
const
deptData
=
await
DeptApi
.
getSimpleDeptList
()
deptTree
.
value
=
handleTree
(
deptData
)
}
finally
{
formLoading
.
value
=
false
}
dialogVisible
.
value
=
true
// 设置已选择的部门
if
(
selectedList
?.
length
)
{
await
nextTick
()
const
selectedIds
=
selectedList
.
map
((
dept
)
=>
dept
.
id
)
.
filter
((
id
):
id
is
number
=>
id
!==
undefined
)
selectedDeptIds
.
value
=
selectedIds
treeRef
.
value
?.
setCheckedKeys
(
selectedIds
)
}
}
/** 处理选中状态变化 */
const
handleCheck
=
(
data
:
any
,
checked
:
any
)
=>
{
selectedDeptIds
.
value
=
treeRef
.
value
.
getCheckedKeys
()
if
(
!
props
.
multiple
&&
selectedDeptIds
.
value
.
length
>
1
)
{
// 单选模式下,只保留最后选择的节点
const
lastSelectedId
=
selectedDeptIds
.
value
[
selectedDeptIds
.
value
.
length
-
1
]
selectedDeptIds
.
value
=
[
lastSelectedId
]
treeRef
.
value
.
setCheckedKeys
([
lastSelectedId
])
}
}
/** 提交选择 */
const
submitForm
=
async
()
=>
{
try
{
// 获取选中的完整部门数据
const
checkedNodes
=
treeRef
.
value
.
getCheckedNodes
()
message
.
success
(
t
(
'common.updateSuccess'
))
dialogVisible
.
value
=
false
emit
(
'confirm'
,
checkedNodes
)
}
finally
{
}
}
/** 重置表单 */
const
resetForm
=
()
=>
{
deptTree
.
value
=
[]
selectedDeptIds
.
value
=
[]
if
(
treeRef
.
value
)
{
treeRef
.
value
.
setCheckedKeys
([])
}
}
defineExpose
({
open
})
// 提供 open 方法,用于打开弹窗
</
script
>
src/components/SimpleProcessDesignerV2/src/NodeHandler.vue
View file @
16a98e5a
...
...
@@ -91,6 +91,7 @@ import {
DEFAULT_CONDITION_GROUP_VALUE
}
from
'./consts'
import
{
generateUUID
}
from
'@/utils'
import
{
cloneDeep
}
from
'lodash-es'
defineOptions
({
name
:
'NodeHandler'
...
...
@@ -184,7 +185,7 @@ const addNode = (type: number) => {
conditionSetting
:
{
defaultFlow
:
false
,
conditionType
:
ConditionType
.
RULE
,
conditionGroups
:
DEFAULT_CONDITION_GROUP_VALUE
conditionGroups
:
cloneDeep
(
DEFAULT_CONDITION_GROUP_VALUE
)
}
},
{
...
...
@@ -242,7 +243,7 @@ const addNode = (type: number) => {
conditionSetting
:
{
defaultFlow
:
false
,
conditionType
:
ConditionType
.
RULE
,
conditionGroups
:
DEFAULT_CONDITION_GROUP_VALUE
conditionGroups
:
cloneDeep
(
DEFAULT_CONDITION_GROUP_VALUE
)
}
},
{
...
...
src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue
View file @
16a98e5a
...
...
@@ -59,6 +59,11 @@ const props = defineProps({
startUserIds
:
{
type
:
Array
,
required
:
false
},
// 可发起流程的部门编号
startDeptIds
:
{
type
:
Array
,
required
:
false
}
})
...
...
@@ -82,6 +87,7 @@ provide('deptList', deptOptions)
provide
(
'userGroupList'
,
userGroupOptions
)
provide
(
'deptTree'
,
deptTreeOptions
)
provide
(
'startUserIds'
,
props
.
startUserIds
)
provide
(
'startDeptIds'
,
props
.
startDeptIds
)
provide
(
'tasks'
,
[])
provide
(
'processInstance'
,
{})
const
message
=
useMessage
()
// 国际化
...
...
src/components/SimpleProcessDesignerV2/src/nodes-config/StartUserNodeConfig.vue
View file @
16a98e5a
...
...
@@ -25,8 +25,16 @@
</
template
>
<el-tabs
type=
"border-card"
v-model=
"activeTabName"
>
<el-tab-pane
label=
"权限"
name=
"user"
>
<el-text
v-if=
"!startUserIds || startUserIds.length === 0"
>
全部成员可以发起流程
</el-text>
<el-text
v-else-if=
"startUserIds.length == 1"
>
<el-text
v-if=
"
(!startUserIds || startUserIds.length === 0) &&
(!startDeptIds || startDeptIds.length === 0)
"
>
全部成员可以发起流程
</el-text>
<div
v-else-if=
"startUserIds && startUserIds.length > 0"
>
<el-text
v-if=
"startUserIds.length == 1"
>
{{ getUserNicknames(startUserIds) }} 可发起流程
</el-text>
<el-text
v-else
>
...
...
@@ -40,6 +48,23 @@
{{ startUserIds.length }} 人可发起流程
</el-tooltip>
</el-text>
</div>
<div
v-else-if=
"startDeptIds && startDeptIds.length > 0"
>
<el-text
v-if=
"startDeptIds.length == 1"
>
{{ getDeptNames(startDeptIds) }} 可发起流程
</el-text>
<el-text
v-else
>
<el-tooltip
class=
"box-item"
effect=
"dark"
placement=
"top"
:content=
"getDeptNames(startDeptIds)"
>
{{ getDeptNames(startDeptIds.slice(0, 2)) }} 等
{{ startDeptIds.length }} 个部门可发起流程
</el-tooltip>
</el-text>
</div>
</el-tab-pane>
<el-tab-pane
label=
"表单字段权限"
name=
"fields"
v-if=
"formType === 10"
>
<div
class=
"field-setting-pane"
>
...
...
@@ -107,6 +132,7 @@
import
{
SimpleFlowNode
,
NodeType
,
FieldPermissionType
,
START_USER_BUTTON_SETTING
}
from
'../consts'
import
{
useWatchNode
,
useDrawer
,
useNodeName
,
useFormFieldsPermission
}
from
'../node'
import
*
as
UserApi
from
'@/api/system/user'
import
*
as
DeptApi
from
'@/api/system/dept'
defineOptions
({
name
:
'StartUserNodeConfig'
})
...
...
@@ -118,8 +144,12 @@ const props = defineProps({
})
// 可发起流程的用户编号
const
startUserIds
=
inject
<
Ref
<
any
[]
>>
(
'startUserIds'
)
// 可发起流程的部门编号
const
startDeptIds
=
inject
<
Ref
<
any
[]
>>
(
'startDeptIds'
)
// 用户列表
const
userOptions
=
inject
<
Ref
<
UserApi
.
UserVO
[]
>>
(
'userList'
)
// 部门列表
const
deptOptions
=
inject
<
Ref
<
DeptApi
.
DeptVO
[]
>>
(
'deptList'
)
// 抽屉配置
const
{
settingVisible
,
closeDrawer
,
openDrawer
}
=
useDrawer
()
// 当前节点
...
...
@@ -145,6 +175,19 @@ const getUserNicknames = (userIds: number[]): string => {
})
return
nicknames
.
join
(
','
)
}
const
getDeptNames
=
(
deptIds
:
number
[]):
string
=>
{
if
(
!
deptIds
||
deptIds
.
length
===
0
)
{
return
''
}
const
deptNames
:
string
[]
=
[]
deptIds
.
forEach
((
deptId
)
=>
{
const
found
=
deptOptions
?.
value
.
find
((
item
)
=>
item
.
id
===
deptId
)
if
(
found
&&
found
.
name
)
{
deptNames
.
push
(
found
.
name
)
}
})
return
deptNames
.
join
(
','
)
}
// 保存配置
const
saveConfig
=
async
()
=>
{
activeTabName
.
value
=
'user'
...
...
src/components/SimpleProcessDesignerV2/src/nodes-config/TriggerNodeConfig.vue
View file @
16a98e5a
...
...
@@ -254,6 +254,7 @@ import {
import
{
useWatchNode
,
useDrawer
,
useNodeName
,
useFormFields
,
getConditionShowText
}
from
'../node'
import
HttpRequestSetting
from
'./components/HttpRequestSetting.vue'
import
ConditionDialog
from
'./components/ConditionDialog.vue'
import
{
cloneDeep
}
from
'lodash-es'
const
{
proxy
}
=
getCurrentInstance
()
as
any
defineOptions
({
...
...
@@ -290,7 +291,7 @@ const configForm = ref<TriggerSetting>({
},
formSettings
:
[
{
conditionGroups
:
DEFAULT_CONDITION_GROUP_VALUE
,
conditionGroups
:
cloneDeep
(
DEFAULT_CONDITION_GROUP_VALUE
)
,
updateFormFields
:
{},
deleteFields
:
[]
}
...
...
@@ -346,7 +347,7 @@ const changeTriggerType = () => {
?
originalSetting
.
formSettings
:
[
{
conditionGroups
:
DEFAULT_CONDITION_GROUP_VALUE
,
conditionGroups
:
cloneDeep
(
DEFAULT_CONDITION_GROUP_VALUE
)
,
updateFormFields
:
{},
deleteFields
:
[]
}
...
...
@@ -361,7 +362,7 @@ const changeTriggerType = () => {
?
originalSetting
.
formSettings
:
[
{
conditionGroups
:
DEFAULT_CONDITION_GROUP_VALUE
,
conditionGroups
:
cloneDeep
(
DEFAULT_CONDITION_GROUP_VALUE
)
,
updateFormFields
:
undefined
,
deleteFields
:
[]
}
...
...
@@ -374,7 +375,7 @@ const changeTriggerType = () => {
/** 添加新的修改表单设置 */
const
addFormSetting
=
()
=>
{
configForm
.
value
.
formSettings
!
.
push
({
conditionGroups
:
DEFAULT_CONDITION_GROUP_VALUE
,
conditionGroups
:
cloneDeep
(
DEFAULT_CONDITION_GROUP_VALUE
)
,
updateFormFields
:
{},
deleteFields
:
[]
})
...
...
@@ -509,7 +510,7 @@ const showTriggerNodeConfig = (node: SimpleFlowNode) => {
},
formSettings: node.triggerSetting.formSettings || [
{
conditionGroups:
DEFAULT_CONDITION_GROUP_VALUE
,
conditionGroups:
cloneDeep(DEFAULT_CONDITION_GROUP_VALUE)
,
updateFormFields: {},
deleteFields: []
}
...
...
src/components/SimpleProcessDesignerV2/src/nodes-config/components/Condition.vue
View file @
16a98e5a
...
...
@@ -154,6 +154,7 @@ import {
}
from
'../../consts'
import
{
BpmModelFormType
}
from
'@/utils/constants'
import
{
useFormFieldsAndStartUser
}
from
'../../node'
import
{
cloneDeep
}
from
'lodash-es'
const
props
=
defineProps
({
modelValue
:
{
...
...
@@ -196,7 +197,7 @@ const formRef = ref() // 表单 Ref
const
changeConditionType
=
()
=>
{
if
(
condition
.
value
.
conditionType
===
ConditionType
.
RULE
)
{
if
(
!
condition
.
value
.
conditionGroups
)
{
condition
.
value
.
conditionGroups
=
DEFAULT_CONDITION_GROUP_VALUE
condition
.
value
.
conditionGroups
=
cloneDeep
(
DEFAULT_CONDITION_GROUP_VALUE
)
}
}
}
...
...
src/components/SimpleProcessDesignerV2/src/nodes-config/components/ConditionDialog.vue
View file @
16a98e5a
...
...
@@ -165,6 +165,7 @@ import {
}
from
'../../consts'
import
{
BpmModelFormType
}
from
'@/utils/constants'
import
{
useFormFieldsAndStartUser
}
from
'../../node'
import
{
cloneDeep
}
from
'lodash-es'
defineOptions
({
name
:
'ConditionDialog'
})
...
...
@@ -175,7 +176,7 @@ const condition = ref<{
conditionGroups
?:
ConditionGroup
}
>
({
conditionType
:
ConditionType
.
RULE
,
conditionGroups
:
DEFAULT_CONDITION_GROUP_VALUE
conditionGroups
:
cloneDeep
(
DEFAULT_CONDITION_GROUP_VALUE
)
})
const
emit
=
defineEmits
<
{
...
...
@@ -210,7 +211,7 @@ const formRef = ref() // 表单 Ref
const
changeConditionType
=
()
=>
{
if
(
condition
.
value
.
conditionType
===
ConditionType
.
RULE
)
{
if
(
!
condition
.
value
.
conditionGroups
)
{
condition
.
value
.
conditionGroups
=
DEFAULT_CONDITION_GROUP_VALUE
condition
.
value
.
conditionGroups
=
cloneDeep
(
DEFAULT_CONDITION_GROUP_VALUE
)
}
}
}
...
...
src/components/SimpleProcessDesignerV2/src/nodes/ExclusiveNode.vue
View file @
16a98e5a
...
...
@@ -108,11 +108,18 @@
<
script
setup
lang=
"ts"
>
import
NodeHandler
from
'../NodeHandler.vue'
import
ProcessNodeTree
from
'../ProcessNodeTree.vue'
import
{
SimpleFlowNode
,
NodeType
,
ConditionType
,
DEFAULT_CONDITION_GROUP_VALUE
,
NODE_DEFAULT_TEXT
}
from
'../consts'
import
{
SimpleFlowNode
,
NodeType
,
ConditionType
,
DEFAULT_CONDITION_GROUP_VALUE
,
NODE_DEFAULT_TEXT
}
from
'../consts'
import
{
getDefaultConditionNodeName
}
from
'../utils'
import
{
useTaskStatusClass
}
from
'../node'
import
{
generateUUID
}
from
'@/utils'
import
ConditionNodeConfig
from
'../nodes-config/ConditionNodeConfig.vue'
import
{
cloneDeep
}
from
'lodash-es'
const
{
proxy
}
=
getCurrentInstance
()
as
any
defineOptions
({
name
:
'ExclusiveNode'
...
...
@@ -149,7 +156,8 @@ const blurEvent = (index: number) => {
showInputs
.
value
[
index
]
=
false
const
conditionNode
=
currentNode
.
value
.
conditionNodes
?.
at
(
index
)
as
SimpleFlowNode
conditionNode
.
name
=
conditionNode
.
name
||
getDefaultConditionNodeName
(
index
,
conditionNode
.
conditionSetting
?.
defaultFlow
)
conditionNode
.
name
||
getDefaultConditionNodeName
(
index
,
conditionNode
.
conditionSetting
?.
defaultFlow
)
}
// 点击条件名称
...
...
@@ -181,7 +189,7 @@ const addCondition = () => {
conditionSetting
:
{
defaultFlow
:
false
,
conditionType
:
ConditionType
.
RULE
,
conditionGroups
:
DEFAULT_CONDITION_GROUP_VALUE
conditionGroups
:
cloneDeep
(
DEFAULT_CONDITION_GROUP_VALUE
)
}
}
conditionNodes
.
splice
(
lastIndex
,
0
,
conditionData
)
...
...
src/components/SimpleProcessDesignerV2/src/nodes/InclusiveNode.vue
View file @
16a98e5a
...
...
@@ -110,11 +110,18 @@
<
script
setup
lang=
"ts"
>
import
NodeHandler
from
'../NodeHandler.vue'
import
ProcessNodeTree
from
'../ProcessNodeTree.vue'
import
{
SimpleFlowNode
,
NodeType
,
ConditionType
,
DEFAULT_CONDITION_GROUP_VALUE
,
NODE_DEFAULT_TEXT
}
from
'../consts'
import
{
SimpleFlowNode
,
NodeType
,
ConditionType
,
DEFAULT_CONDITION_GROUP_VALUE
,
NODE_DEFAULT_TEXT
}
from
'../consts'
import
{
useTaskStatusClass
}
from
'../node'
import
{
getDefaultInclusiveConditionNodeName
}
from
'../utils'
import
{
generateUUID
}
from
'@/utils'
import
ConditionNodeConfig
from
'../nodes-config/ConditionNodeConfig.vue'
import
{
cloneDeep
}
from
'lodash-es'
const
{
proxy
}
=
getCurrentInstance
()
as
any
defineOptions
({
name
:
'InclusiveNode'
...
...
@@ -153,7 +160,8 @@ const blurEvent = (index: number) => {
showInputs
.
value
[
index
]
=
false
const
conditionNode
=
currentNode
.
value
.
conditionNodes
?.
at
(
index
)
as
SimpleFlowNode
conditionNode
.
name
=
conditionNode
.
name
||
getDefaultInclusiveConditionNodeName
(
index
,
conditionNode
.
conditionSetting
?.
defaultFlow
)
conditionNode
.
name
||
getDefaultInclusiveConditionNodeName
(
index
,
conditionNode
.
conditionSetting
?.
defaultFlow
)
}
// 点击条件名称
...
...
@@ -185,7 +193,7 @@ const addCondition = () => {
conditionSetting
:
{
defaultFlow
:
false
,
conditionType
:
ConditionType
.
RULE
,
conditionGroups
:
DEFAULT_CONDITION_GROUP_VALUE
conditionGroups
:
cloneDeep
(
DEFAULT_CONDITION_GROUP_VALUE
)
}
}
conditionNodes
.
splice
(
lastIndex
,
0
,
conditionData
)
...
...
src/views/bpm/model/CategoryDraggableModel.vue
View file @
16a98e5a
...
...
@@ -97,10 +97,23 @@
</el-table-column>
<el-table-column
label=
"可见范围"
prop=
"startUserIds"
min-width=
"150"
>
<
template
#
default=
"{ row }"
>
<el-text
v-if=
"!row.startUsers?.length"
>
全部可见
</el-text>
<el-text
v-if=
"!row.startUsers?.length
&& !row.startDepts?.length
"
>
全部可见
</el-text>
<el-text
v-else-if=
"row.startUsers.length === 1"
>
{{
row
.
startUsers
[
0
].
nickname
}}
</el-text>
<el-text
v-else-if=
"row.startDepts?.length === 1"
>
{{
row
.
startDepts
[
0
].
name
}}
</el-text>
<el-text
v-else-if=
"row.startDepts?.length > 1"
>
<el-tooltip
class=
"box-item"
effect=
"dark"
placement=
"top"
:content=
"row.startDepts.map((dept: any) => dept.name).join('、')"
>
{{
row
.
startDepts
[
0
].
name
}}
等
{{
row
.
startDepts
.
length
}}
个部门可见
</el-tooltip>
</el-text>
<el-text
v-else
>
<el-tooltip
class=
"box-item"
...
...
src/views/bpm/model/form/BasicInfo.vue
View file @
16a98e5a
...
...
@@ -77,6 +77,7 @@
>
<el-option
label=
"全员"
:value=
"0"
/>
<el-option
label=
"指定人员"
:value=
"1"
/>
<el-option
label=
"指定部门"
:value=
"2"
/>
</el-select>
<div
v-if=
"modelData.startUserType === 1"
class=
"mt-2 flex flex-wrap gap-2"
>
<div
...
...
@@ -99,6 +100,24 @@
<Icon
icon=
"ep:plus"
/>
选择人员
</el-button>
</div>
<div
v-if=
"modelData.startUserType === 2"
class=
"mt-2 flex flex-wrap gap-2"
>
<div
v-for=
"dept in selectedStartDepts"
:key=
"dept.id"
class=
"bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
>
<Icon
icon=
"ep:office-building"
class=
"!m-5px text-20px"
/>
{{
dept
.
name
}}
<Icon
icon=
"ep:close"
class=
"ml-2 cursor-pointer hover:text-red-500"
@
click=
"handleRemoveStartDept(dept)"
/>
</div>
<el-button
type=
"primary"
link
@
click=
"openStartDeptSelect"
>
<Icon
icon=
"ep:plus"
/>
选择部门
</el-button>
</div>
</el-form-item>
<el-form-item
label=
"流程管理员"
prop=
"managerUserIds"
class=
"mb-20px"
>
<div
class=
"flex flex-wrap gap-2"
>
...
...
@@ -127,11 +146,19 @@
<!-- 用户选择弹窗 -->
<UserSelectForm
ref=
"userSelectFormRef"
@
confirm=
"handleUserSelectConfirm"
/>
<!-- 部门选择弹窗 -->
<DeptSelectForm
ref=
"deptSelectFormRef"
:multiple=
"true"
:check-strictly=
"true"
@
confirm=
"handleDeptSelectConfirm"
/>
</
template
>
<
script
lang=
"ts"
setup
>
import
{
DICT_TYPE
,
getBoolDictOptions
,
getIntDictOptions
}
from
'@/utils/dict'
import
{
UserVO
}
from
'@/api/system/user'
import
{
DeptVO
}
from
'@/api/system/dept'
import
{
CategoryVO
}
from
'@/api/bpm/category'
const
props
=
defineProps
({
...
...
@@ -142,13 +169,19 @@ const props = defineProps({
userList
:
{
type
:
Array
,
required
:
true
},
deptList
:
{
type
:
Array
,
required
:
true
}
})
const
formRef
=
ref
()
const
selectedStartUsers
=
ref
<
UserVO
[]
>
([])
const
selectedStartDepts
=
ref
<
DeptVO
[]
>
([])
const
selectedManagerUsers
=
ref
<
UserVO
[]
>
([])
const
userSelectFormRef
=
ref
()
const
deptSelectFormRef
=
ref
()
const
currentSelectType
=
ref
<
'start'
|
'manager'
>
(
'start'
)
const
rules
=
{
...
...
@@ -174,6 +207,13 @@ watch(
}
else
{
selectedStartUsers
.
value
=
[]
}
if
(
newVal
.
startDeptIds
?.
length
)
{
selectedStartDepts
.
value
=
props
.
deptList
.
filter
((
dept
:
DeptVO
)
=>
newVal
.
startDeptIds
.
includes
(
dept
.
id
)
)
as
DeptVO
[]
}
else
{
selectedStartDepts
.
value
=
[]
}
if
(
newVal
.
managerUserIds
?.
length
)
{
selectedManagerUsers
.
value
=
props
.
userList
.
filter
((
user
:
UserVO
)
=>
newVal
.
managerUserIds
.
includes
(
user
.
id
)
...
...
@@ -193,6 +233,11 @@ const openStartUserSelect = () => {
userSelectFormRef
.
value
.
open
(
0
,
selectedStartUsers
.
value
)
}
/** 打开部门选择 */
const
openStartDeptSelect
=
()
=>
{
deptSelectFormRef
.
value
.
open
(
selectedStartDepts
.
value
)
}
/** 打开管理员选择 */
const
openManagerUserSelect
=
()
=>
{
currentSelectType
.
value
=
'manager'
...
...
@@ -214,9 +259,28 @@ const handleUserSelectConfirm = (_, users: UserVO[]) => {
}
}
/** 处理部门选择确认 */
const
handleDeptSelectConfirm
=
(
depts
:
DeptVO
[])
=>
{
modelData
.
value
=
{
...
modelData
.
value
,
startDeptIds
:
depts
.
map
((
d
)
=>
d
.
id
)
}
}
/** 处理发起人类型变化 */
const
handleStartUserTypeChange
=
(
value
:
number
)
=>
{
if
(
value
!==
1
)
{
if
(
value
===
0
)
{
modelData
.
value
=
{
...
modelData
.
value
,
startUserIds
:
[],
startDeptIds
:
[]
}
}
else
if
(
value
===
1
)
{
modelData
.
value
=
{
...
modelData
.
value
,
startDeptIds
:
[]
}
}
else
if
(
value
===
2
)
{
modelData
.
value
=
{
...
modelData
.
value
,
startUserIds
:
[]
...
...
@@ -232,6 +296,14 @@ const handleRemoveStartUser = (user: UserVO) => {
}
}
/** 移除部门 */
const
handleRemoveStartDept
=
(
dept
:
DeptVO
)
=>
{
modelData
.
value
=
{
...
modelData
.
value
,
startDeptIds
:
modelData
.
value
.
startDeptIds
.
filter
((
id
:
number
)
=>
id
!==
dept
.
id
)
}
}
/** 移除管理员 */
const
handleRemoveManagerUser
=
(
user
:
UserVO
)
=>
{
modelData
.
value
=
{
...
...
src/views/bpm/model/form/ExtraSettings.vue
View file @
16a98e5a
...
...
@@ -148,7 +148,7 @@
<div
class=
"flex"
>
<el-switch
v-model=
"processBeforeTriggerEnable"
@
change=
"handlePr
eProcessNotify
EnableChange"
@
change=
"handlePr
ocessBeforeTrigger
EnableChange"
/>
<div
class=
"ml-80px"
>
流程启动后通知
</div>
</div>
...
...
@@ -168,9 +168,9 @@
<div
class=
"flex"
>
<el-switch
v-model=
"processAfterTriggerEnable"
@
change=
"handleP
ostProcessNotify
EnableChange"
@
change=
"handleP
rocessAfterTrigger
EnableChange"
/>
<div
class=
"ml-80px"
>
流程
启动
后通知
</div>
<div
class=
"ml-80px"
>
流程
结束
后通知
</div>
</div>
<HttpRequestSetting
v-if=
"processAfterTriggerEnable"
...
...
@@ -180,6 +180,46 @@
/>
</div>
</el-form-item>
<el-form-item
class=
"mb-20px"
>
<
template
#
label
>
<el-text
size=
"large"
tag=
"b"
>
任务前置通知
</el-text>
</
template
>
<div
class=
"flex flex-col w-100%"
>
<div
class=
"flex"
>
<el-switch
v-model=
"taskBeforeTriggerEnable"
@
change=
"handleTaskBeforeTriggerEnableChange"
/>
<div
class=
"ml-80px"
>
任务执行时通知
</div>
</div>
<HttpRequestSetting
v-if=
"taskBeforeTriggerEnable"
v-model:setting=
"modelData.taskBeforeTriggerSetting"
:responseEnable=
"true"
:formItemPrefix=
"'taskBeforeTriggerSetting'"
/>
</div>
</el-form-item>
<el-form-item
class=
"mb-20px"
>
<
template
#
label
>
<el-text
size=
"large"
tag=
"b"
>
任务后置通知
</el-text>
</
template
>
<div
class=
"flex flex-col w-100%"
>
<div
class=
"flex"
>
<el-switch
v-model=
"taskAfterTriggerEnable"
@
change=
"handleTaskAfterTriggerEnableChange"
/>
<div
class=
"ml-80px"
>
任务结束后通知
</div>
</div>
<HttpRequestSetting
v-if=
"taskAfterTriggerEnable"
v-model:setting=
"modelData.taskAfterTriggerSetting"
:responseEnable=
"true"
:formItemPrefix=
"'taskAfterTriggerSetting'"
/>
</div>
</el-form-item>
</el-form>
</template>
...
...
@@ -248,7 +288,7 @@ const numberExample = computed(() => {
/** 是否开启流程前置通知 */
const
processBeforeTriggerEnable
=
ref
(
false
)
const
handlePr
eProcessNotify
EnableChange
=
(
val
:
boolean
|
string
|
number
)
=>
{
const
handlePr
ocessBeforeTrigger
EnableChange
=
(
val
:
boolean
|
string
|
number
)
=>
{
if
(
val
)
{
modelData
.
value
.
processBeforeTriggerSetting
=
{
url
:
''
,
...
...
@@ -263,7 +303,7 @@ const handlePreProcessNotifyEnableChange = (val: boolean | string | number) => {
/** 是否开启流程后置通知 */
const
processAfterTriggerEnable
=
ref
(
false
)
const
handleP
ostProcessNotify
EnableChange
=
(
val
:
boolean
|
string
|
number
)
=>
{
const
handleP
rocessAfterTrigger
EnableChange
=
(
val
:
boolean
|
string
|
number
)
=>
{
if
(
val
)
{
modelData
.
value
.
processAfterTriggerSetting
=
{
url
:
''
,
...
...
@@ -276,6 +316,36 @@ const handlePostProcessNotifyEnableChange = (val: boolean | string | number) =>
}
}
/** 是否开启任务前置通知 */
const
taskBeforeTriggerEnable
=
ref
(
false
)
const
handleTaskBeforeTriggerEnableChange
=
(
val
:
boolean
|
string
|
number
)
=>
{
if
(
val
)
{
modelData
.
value
.
taskBeforeTriggerSetting
=
{
url
:
''
,
header
:
[],
body
:
[],
response
:
[]
}
}
else
{
modelData
.
value
.
taskBeforeTriggerSetting
=
null
}
}
/** 是否开启任务后置通知 */
const
taskAfterTriggerEnable
=
ref
(
false
)
const
handleTaskAfterTriggerEnableChange
=
(
val
:
boolean
|
string
|
number
)
=>
{
if
(
val
)
{
modelData
.
value
.
taskAfterTriggerSetting
=
{
url
:
''
,
header
:
[],
body
:
[],
response
:
[]
}
}
else
{
modelData
.
value
.
taskAfterTriggerSetting
=
null
}
}
/** 表单选项 */
const
formField
=
ref
<
Array
<
{
field
:
string
;
title
:
string
}
>>
([])
const
formFieldOptions4Title
=
computed
(()
=>
{
...
...
@@ -341,6 +411,12 @@ const initData = () => {
if
(
modelData
.
value
.
processAfterTriggerSetting
)
{
processAfterTriggerEnable
.
value
=
true
}
if
(
modelData
.
value
.
taskBeforeTriggerSetting
)
{
taskBeforeTriggerEnable
.
value
=
true
}
if
(
modelData
.
value
.
taskAfterTriggerSetting
)
{
taskAfterTriggerEnable
.
value
=
true
}
}
defineExpose
({
initData
})
...
...
src/views/bpm/model/form/ProcessDesign.vue
View file @
16a98e5a
...
...
@@ -18,6 +18,7 @@
:model-key=
"modelData.key"
:model-name=
"modelData.name"
:start-user-ids=
"modelData.startUserIds"
:start-dept-ids=
"modelData.startDeptIds"
@
success=
"handleDesignSuccess"
/>
</
template
>
...
...
src/views/bpm/model/form/index.vue
View file @
16a98e5a
...
...
@@ -62,6 +62,7 @@
v-model=
"formData"
:categoryList=
"categoryList"
:userList=
"userList"
:deptList=
"deptList"
ref=
"basicInfoRef"
/>
</div>
...
...
@@ -92,6 +93,7 @@ import * as ModelApi from '@/api/bpm/model'
import
*
as
FormApi
from
'@/api/bpm/form'
import
{
CategoryApi
,
CategoryVO
}
from
'@/api/bpm/category'
import
*
as
UserApi
from
'@/api/system/user'
import
*
as
DeptApi
from
'@/api/system/dept'
import
*
as
DefinitionApi
from
'@/api/bpm/definition'
import
{
BpmModelFormType
,
BpmModelType
,
BpmAutoApproveType
}
from
'@/utils/constants'
import
BasicInfo
from
'./BasicInfo.vue'
...
...
@@ -153,6 +155,7 @@ const formData: any = ref({
visible
:
true
,
startUserType
:
undefined
,
startUserIds
:
[],
startDeptIds
:
[],
managerUserIds
:
[],
allowCancelRunningProcess
:
true
,
processIdRule
:
{
...
...
@@ -183,6 +186,7 @@ provide('modelData', formData)
const
formList
=
ref
([])
const
categoryList
=
ref
<
CategoryVO
[]
>
([])
const
userList
=
ref
<
UserApi
.
UserVO
[]
>
([])
const
deptList
=
ref
<
DeptApi
.
DeptVO
[]
>
([])
/** 初始化数据 */
const
actionType
=
route
.
params
.
type
as
string
...
...
@@ -200,14 +204,17 @@ const initData = async () => {
data
.
simpleModel
=
JSON
.
parse
(
data
.
simpleModel
)
}
formData
.
value
=
data
formData
.
value
.
startUserType
=
formData
.
value
.
startUserIds
?.
length
>
0
?
1
:
0
formData
.
value
.
startUserType
=
formData
.
value
.
startUserIds
?.
length
>
0
?
1
:
formData
.
value
?.
startDeptIds
?.
length
>
0
?
2
:
0
}
else
if
([
'update'
,
'copy'
].
includes
(
actionType
))
{
// 情况二:修改场景/复制场景
const
modelId
=
route
.
params
.
id
as
string
formData
.
value
=
await
ModelApi
.
getModel
(
modelId
)
formData
.
value
.
startUserType
=
formData
.
value
.
startUserIds
?.
length
>
0
?
1
:
0
formData
.
value
.
startUserType
=
formData
.
value
.
startUserIds
?.
length
>
0
?
1
:
formData
.
value
?.
startDeptIds
?.
length
>
0
?
2
:
0
// 特殊:复制场景
if
(
actionT
ype
===
'copy'
)
{
if
(
route
.
params
.
t
ype
===
'copy'
)
{
delete
formData
.
value
.
id
formData
.
value
.
name
+=
'副本'
formData
.
value
.
key
+=
'_copy'
...
...
@@ -225,6 +232,8 @@ const initData = async () => {
categoryList
.
value
=
await
CategoryApi
.
getCategorySimpleList
()
// 获取用户列表
userList
.
value
=
await
UserApi
.
getSimpleUserList
()
// 获取部门列表
deptList
.
value
=
await
DeptApi
.
getSimpleDeptList
()
// 最终,设置 currentStep 切换到第一步
currentStep
.
value
=
0
...
...
src/views/bpm/oa/leave/create.vue
View file @
16a98e5a
<
template
>
<el-row
:gutter=
"20"
>
<el-col
:span=
"16"
>
<ContentWrap
title=
"申请信息"
>
<el-form
ref=
"formRef"
v-loading=
"formLoading"
...
...
@@ -35,49 +38,41 @@
/>
</el-form-item>
<el-form-item
label=
"原因"
prop=
"reason"
>
<el-input
v-model=
"formData.reason"
placeholder=
"请输
请假原因"
type=
"textarea"
/>
<el-input
v-model=
"formData.reason"
placeholder=
"请输入
请假原因"
type=
"textarea"
/>
</el-form-item>
<el-col
v-if=
"startUserSelectTasks.length > 0"
>
<el-card
class=
"mb-10px"
>
<template
#
header
>
指定审批人
</
template
>
<el-form
:model=
"startUserSelectAssignees"
:rules=
"startUserSelectAssigneesFormRules"
ref=
"startUserSelectAssigneesFormRef"
>
<el-form-item
v-for=
"userTask in startUserSelectTasks"
:key=
"userTask.id"
:label=
"`任务【${userTask.name}】`"
:prop=
"userTask.id"
>
<el-select
v-model=
"startUserSelectAssignees[userTask.id]"
multiple
placeholder=
"请选择审批人"
>
<el-option
v-for=
"user in userList"
:key=
"user.id"
:label=
"user.nickname"
:value=
"user.id"
/>
</el-select>
</el-form-item>
</el-form>
</el-card>
</el-col>
<el-form-item>
<el-button
:disabled=
"formLoading"
type=
"primary"
@
click=
"submitForm"
>
确 定
</el-button>
<el-button
:disabled=
"formLoading"
type=
"primary"
@
click=
"submitForm"
>
确 定
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
</el-col>
<!-- 审批相关:流程信息 -->
<el-col
:span=
"8"
>
<ContentWrap
title=
"审批流程"
:bodyStyle=
"
{ padding: '0 20px 0' }">
<ProcessInstanceTimeline
ref=
"timelineRef"
:activity-nodes=
"activityNodes"
:show-status-icon=
"false"
@
select-user-confirm=
"selectUserConfirm"
/>
</ContentWrap>
</el-col>
</el-row>
</
template
>
<
script
lang=
"ts"
setup
>
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
*
as
LeaveApi
from
'@/api/bpm/leave'
import
{
useTagsViewStore
}
from
'@/store/modules/tagsView'
// 审批相关:import
import
*
as
DefinitionApi
from
'@/api/bpm/definition'
import
*
as
UserApi
from
'@/api/system/user'
import
ProcessInstanceTimeline
from
'@/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue'
import
*
as
ProcessInstanceApi
from
'@/api/bpm/processInstance'
import
{
CandidateStrategy
,
NodeId
}
from
'@/components/SimpleProcessDesignerV2/src/consts'
import
{
ApprovalNodeInfo
}
from
'@/api/bpm/processInstance'
defineOptions
({
name
:
'BpmOALeaveCreate'
})
...
...
@@ -100,30 +95,37 @@ const formRules = reactive({
})
const
formRef
=
ref
()
// 表单 Ref
//
指定审批人
//
审批相关:变量
const
processDefineKey
=
'oa_leave'
// 流程定义 Key
const
startUserSelectTasks
=
ref
([])
// 发起人需要选择审批人的用户任务列表
const
startUserSelectAssignees
=
ref
({})
// 发起人选择审批人的数据
const
startUserSelectAssigneesFormRef
=
ref
()
// 发起人选择审批人的表单 Ref
const
startUserSelectAssigneesFormRules
=
ref
({})
// 发起人选择审批人的表单 Rules
const
userList
=
ref
<
any
[]
>
([])
// 用户列表
const
tempStartUserSelectAssignees
=
ref
({})
// 历史发起人选择审批人的数据,用于每次表单变更时,临时保存
const
activityNodes
=
ref
<
ProcessInstanceApi
.
ApprovalNodeInfo
[]
>
([])
// 审批节点信息
const
processDefinitionId
=
ref
(
''
)
/** 提交表单 */
const
submitForm
=
async
()
=>
{
// 校验表单
//
1.1
校验表单
if
(
!
formRef
)
return
const
valid
=
await
formRef
.
value
.
validate
()
if
(
!
valid
)
return
// 校验指定审批人
//
1.2 审批相关:
校验指定审批人
if
(
startUserSelectTasks
.
value
?.
length
>
0
)
{
await
startUserSelectAssigneesFormRef
.
value
.
validate
()
for
(
const
userTask
of
startUserSelectTasks
.
value
)
{
if
(
Array
.
isArray
(
startUserSelectAssignees
.
value
[
userTask
.
id
])
&&
startUserSelectAssignees
.
value
[
userTask
.
id
].
length
===
0
)
{
return
message
.
warning
(
`请选择
${
userTask
.
name
}
的审批人`
)
}
}
}
// 提交请求
//
2.
提交请求
formLoading
.
value
=
true
try
{
const
data
=
{
...
formData
.
value
}
as
unknown
as
LeaveApi
.
LeaveVO
// 设置指定审批人
//
审批相关:
设置指定审批人
if
(
startUserSelectTasks
.
value
?.
length
>
0
)
{
data
.
startUserSelectAssignees
=
startUserSelectAssignees
.
value
}
...
...
@@ -137,28 +139,93 @@ const submitForm = async () => {
}
}
/** 审批相关:获取审批详情 */
const
getApprovalDetail
=
async
()
=>
{
try
{
const
data
=
await
ProcessInstanceApi
.
getApprovalDetail
({
processDefinitionId
:
processDefinitionId
.
value
,
// TODO 小北:可以支持 processDefinitionKey 查询
activityId
:
NodeId
.
START_USER_NODE_ID
,
processVariablesStr
:
JSON
.
stringify
({
day
:
daysDifference
()
})
// 解决 GET 无法传递对象的问题,后端 String 再转 JSON
})
if
(
!
data
)
{
message
.
error
(
'查询不到审批详情信息!'
)
return
}
// 获取审批节点,显示 Timeline 的数据
activityNodes
.
value
=
data
.
activityNodes
// 获取发起人自选的任务
startUserSelectTasks
.
value
=
data
.
activityNodes
?.
filter
(
(
node
:
ApprovalNodeInfo
)
=>
CandidateStrategy
.
START_USER_SELECT
===
node
.
candidateStrategy
)
// 恢复之前的选择审批人
if
(
startUserSelectTasks
.
value
?.
length
>
0
)
{
for
(
const
node
of
startUserSelectTasks
.
value
)
{
if
(
tempStartUserSelectAssignees
.
value
[
node
.
id
]
&&
tempStartUserSelectAssignees
.
value
[
node
.
id
].
length
>
0
)
{
startUserSelectAssignees
.
value
[
node
.
id
]
=
tempStartUserSelectAssignees
.
value
[
node
.
id
]
}
else
{
startUserSelectAssignees
.
value
[
node
.
id
]
=
[]
}
}
}
}
finally
{
}
}
/** 审批相关:选择发起人 */
const
selectUserConfirm
=
(
id
:
string
,
userList
:
any
[])
=>
{
startUserSelectAssignees
.
value
[
id
]
=
userList
?.
map
((
item
:
any
)
=>
item
.
id
)
}
// 计算天数差
// TODO @小北:可以搞到 formatTime 里面去,然后看看 dayjs 里面有没有现成的方法,或者辅助计算的方法。
const
daysDifference
=
()
=>
{
const
oneDay
=
24
*
60
*
60
*
1000
// 一天的毫秒数
const
diffTime
=
Math
.
abs
(
Number
(
formData
.
value
.
endTime
)
-
Number
(
formData
.
value
.
startTime
))
return
Math
.
floor
(
diffTime
/
oneDay
)
}
/** 初始化 */
onMounted
(
async
()
=>
{
// TODO @小北:这里可以简化,统一通过 getApprovalDetail 处理么?
const
processDefinitionDetail
=
await
DefinitionApi
.
getProcessDefinition
(
undefined
,
processDefineKey
)
if
(
!
processDefinitionDetail
)
{
message
.
error
(
'OA 请假的流程模型未配置,请检查!'
)
return
}
processDefinitionId
.
value
=
processDefinitionDetail
.
id
startUserSelectTasks
.
value
=
processDefinitionDetail
.
startUserSelectTasks
// 设置指定审批人
if
(
startUserSelectTasks
.
value
?.
length
>
0
)
{
// 设置校验规则
for
(
const
userTask
of
startUserSelectTasks
.
value
)
{
startUserSelectAssignees
.
value
[
userTask
.
id
]
=
[]
startUserSelectAssigneesFormRules
.
value
[
userTask
.
id
]
=
[
{
required
:
true
,
message
:
'请选择审批人'
,
trigger
:
'blur'
}
]
// 审批相关:加载最新的审批详情,主要用于节点预测
await
getApprovalDetail
()
})
/** 审批相关:预测流程节点会因为输入的参数值而产生新的预测结果值,所以需重新预测一次, formData.value可改成实际业务中的特定字段 */
watch
(
formData
.
value
,
(
newValue
,
oldValue
)
=>
{
if
(
!
oldValue
)
{
return
}
// 加载用户列表
userList
.
value
=
await
UserApi
.
getSimpleUserList
()
if
(
newValue
&&
Object
.
keys
(
newValue
).
length
>
0
)
{
// 记录之前的节点审批人
tempStartUserSelectAssignees
.
value
=
startUserSelectAssignees
.
value
startUserSelectAssignees
.
value
=
{}
// 加载最新的审批详情,主要用于节点预测
getApprovalDetail
()
}
})
},
{
immediate
:
true
}
)
</
script
>
src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue
View file @
16a98e5a
...
...
@@ -37,6 +37,11 @@
{{ getApprovalNodeTime(activity) }}
</div>
</div>
<div
v-if=
"activity.nodeType === NodeType.CHILD_PROCESS_NODE"
>
<el-button
type=
"primary"
plain
size=
"small"
@
click=
"handleChildProcess(activity)"
>
查看子流程
</el-button>
</div>
<!-- 需要自定义选择审批人 -->
<div
class=
"flex flex-wrap gap2 items-center"
...
...
@@ -194,6 +199,7 @@ withDefaults(
showStatusIcon
:
true
// 默认值为 true
}
)
const
{
push
}
=
useRouter
()
// 路由
// 审批节点
const
statusIconMap2
=
{
...
...
@@ -310,4 +316,15 @@ const handleUserSelectConfirm = (activityId: string, userList: any[]) => {
customApproveUsers
.
value
[
activityId
]
=
userList
||
[]
emit
(
'selectUserConfirm'
,
activityId
,
userList
)
}
/** 跳转子流程 */
const
handleChildProcess
=
(
activity
:
any
)
=>
{
// TODO @lesan:貌似跳不过去?!
push
({
name
:
'BpmProcessInstanceDetail'
,
query
:
{
id
:
activity
.
processInstanceId
}
})
}
</
script
>
src/views/bpm/simple/SimpleModelDesign.vue
View file @
16a98e5a
...
...
@@ -6,6 +6,7 @@
:model-name=
"modelName"
@
success=
"handleSuccess"
:start-user-ids=
"startUserIds"
:start-dept-ids=
"startDeptIds"
ref=
"designerRef"
/>
</ContentWrap>
...
...
@@ -22,6 +23,7 @@ defineProps<{
modelKey
?:
string
modelName
?:
string
startUserIds
?:
number
[]
startDeptIds
?:
number
[]
}
>
()
const
emit
=
defineEmits
([
'success'
])
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment