Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
B
binfast-admin-view
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
binfast
binfast-admin-view
Commits
8d4896cf
Commit
8d4896cf
authored
Nov 04, 2025
by
刘斌
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: 基础版本更新
parent
a336722d
Hide whitespace changes
Inline
Side-by-side
Showing
26 changed files
with
1009 additions
and
702 deletions
+1009
-702
sysTenant.ts
apps/web-antd/src/api/system/sysTenant.ts
+10
-0
index.ts
apps/web-antd/src/api/workflow/instance/index.ts
+21
-3
model.d.ts
apps/web-antd/src/api/workflow/instance/model.d.ts
+13
-0
slot.ts
apps/web-antd/src/components/global/slot.ts
+27
-0
editor.vue
apps/web-antd/src/components/tinymce/src/editor.vue
+5
-0
tree-select-panel.vue
apps/web-antd/src/components/tree/src/tree-select-panel.vue
+104
-132
data.tsx
apps/web-antd/src/views/auth/role/data.tsx
+32
-10
deptDataScopeModal.vue
apps/web-antd/src/views/auth/role/deptDataScopeModal.vue
+33
-43
list.vue
apps/web-antd/src/views/system/dict/type/list.vue
+3
-3
list.vue
apps/web-antd/src/views/system/tenant/list.vue
+18
-0
flow-actions.vue
...td/src/views/workflow/components/actions/flow-actions.vue
+418
-0
index.ts
apps/web-antd/src/views/workflow/components/actions/index.ts
+1
-0
apply-modal.vue
apps/web-antd/src/views/workflow/components/apply-modal.vue
+9
-0
approval-details.vue
...b-antd/src/views/workflow/components/approval-details.vue
+0
-2
approval-panel.vue
...web-antd/src/views/workflow/components/approval-panel.vue
+73
-447
approval-timeline-item.vue
.../src/views/workflow/components/approval-timeline-item.vue
+2
-4
approval-timeline.vue
...-antd/src/views/workflow/components/approval-timeline.vue
+8
-9
flow-preview.vue
apps/web-antd/src/views/workflow/components/flow-preview.vue
+16
-2
hook.ts
apps/web-antd/src/views/workflow/components/hook.ts
+14
-8
type.d.ts
apps/web-antd/src/views/workflow/components/type.d.ts
+8
-0
index.vue
apps/web-antd/src/views/workflow/processInstance/index.vue
+2
-1
instance-invalid-modal.vue
...views/workflow/processInstance/instance-invalid-modal.vue
+180
-33
menu.vue
packages/@core/ui-kit/menu-ui/src/components/menu.vue
+1
-1
directive.ts
packages/effects/access/src/directive.ts
+2
-4
use-access.ts
packages/effects/access/src/use-access.ts
+4
-0
style.css
packages/effects/plugins/src/vxe-table/style.css
+5
-0
No files found.
apps/web-antd/src/api/system/sysTenant.ts
View file @
8d4896cf
...
@@ -146,3 +146,13 @@ export function tenantSyncDict(tenantId?: string) {
...
@@ -146,3 +146,13 @@ export function tenantSyncDict(tenantId?: string) {
successMessageMode
:
'message'
,
successMessageMode
:
'message'
,
});
});
}
}
/**
* 同步租户配置
* @returns void
*/
export
function
tenantSyncConfig
()
{
return
requestClient
.
get
(
'/system/tenant/syncTenantConfig'
,
{
successMessageMode
:
'message'
,
});
}
apps/web-antd/src/api/workflow/instance/index.ts
View file @
8d4896cf
import
type
{
TaskInfo
}
from
'../task/model'
;
import
type
{
TaskInfo
}
from
'../task/model'
;
import
type
{
FlowInfoResponse
}
from
'./model'
;
import
type
{
FlowInfoResponse
,
FlowInstanceVariableResp
}
from
'./model'
;
import
type
{
ID
,
IDS
,
PageQuery
,
PageResult
}
from
'#/api/baseModel'
;
import
type
{
ID
,
IDS
,
PageQuery
,
PageResult
}
from
'#/api/baseModel'
;
...
@@ -106,8 +106,8 @@ export function flowInfo(businessId: string) {
...
@@ -106,8 +106,8 @@ export function flowInfo(businessId: string) {
* @returns Map<string,any>
* @returns Map<string,any>
*/
*/
export
function
instanceVariable
(
instanceId
:
string
)
{
export
function
instanceVariable
(
instanceId
:
string
)
{
return
requestClient
.
get
<
Record
<
string
,
any
>
>
(
return
requestClient
.
get
<
FlowInstanceVariableResp
>
(
`/workflow/instance/
v
ariable/
${
instanceId
}
`
,
`/workflow/instance/
instanceV
ariable/
${
instanceId
}
`
,
);
);
}
}
...
@@ -122,3 +122,21 @@ export function workflowInstanceInvalid(data: {
...
@@ -122,3 +122,21 @@ export function workflowInstanceInvalid(data: {
successMessageMode
:
'message'
,
successMessageMode
:
'message'
,
});
});
}
}
/**
* 修改流程参数
* @param data 参数
* @param data.instanceId 实例ID
* @param data.key 参数key
* @param data.value 值
* @returns void
*/
export
function
updateFlowVariable
(
data
:
{
instanceId
:
string
;
key
:
string
;
value
:
any
;
})
{
return
requestClient
.
put
(
'/workflow/instance/updateVariable'
,
data
,
{
successMessageMode
:
'message'
,
});
}
apps/web-antd/src/api/workflow/instance/model.d.ts
View file @
8d4896cf
export
{};
export
interface
Flow
{
export
interface
Flow
{
id
:
string
;
id
:
string
;
createTime
:
string
;
createTime
:
string
;
...
@@ -39,3 +41,14 @@ export interface FlowInfoResponse {
...
@@ -39,3 +41,14 @@ export interface FlowInfoResponse {
instanceId
:
string
;
instanceId
:
string
;
list
:
Flow
[];
list
:
Flow
[];
}
}
export
interface
FlowInstanceVariableResp
{
/**
* json字符串 流程变量
*/
variable
:
string
;
variableList
:
{
key
:
string
;
value
:
any
;
}[];
}
apps/web-antd/src/components/global/slot.ts
0 → 100644
View file @
8d4896cf
import
{
defineComponent
,
h
}
from
'vue'
;
/**
* 使用默认插槽来自定义组件
* 给vbenForm的components使用
*/
export
const
DefaultSlot
=
defineComponent
({
name
:
'DefaultSlot'
,
inheritAttrs
:
false
,
props
:
{
/**
* 绑定到根节点的div上的属性
*/
rootDivAttrs
:
{
type
:
Object
,
default
:
()
=>
({}),
},
},
render
()
{
/**
* 获取属性 传递给作用域插槽供外部使用
*/
const
attrs
=
this
.
$attrs
;
return
h
(
'div'
,
{
...
this
.
rootDivAttrs
},
this
.
$slots
.
default
?.(
attrs
));
},
});
apps/web-antd/src/components/tinymce/src/editor.vue
View file @
8d4896cf
...
@@ -133,6 +133,11 @@ const initOptions = computed((): InitOptions => {
...
@@ -133,6 +133,11 @@ const initOptions = computed((): InitOptions => {
toolbar_mode
:
'sliding'
,
toolbar_mode
:
'sliding'
,
// 隐藏下面的 按xxx获取帮助
// 隐藏下面的 按xxx获取帮助
help_accessibility
:
false
,
help_accessibility
:
false
,
// https://blog.csdn.net/qq_46380656/article/details/122171418
// 避免图片地址和链接地址转换成相对路径
relative_urls
:
false
,
remove_script_host
:
false
,
convert_urls
:
false
,
...
options
,
...
options
,
/**
/**
* 覆盖默认的base64行为
* 覆盖默认的base64行为
...
...
apps/web-antd/src/components/tree/src/tree-select-panel.vue
View file @
8d4896cf
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
type
{
CheckboxChangeEvent
}
from
'ant-design-vue/es/checkbox/interface'
;
import
type
{
CheckboxChangeEvent
}
from
'ant-design-vue/es/checkbox/interface'
;
import
type
{
DataNode
}
from
'ant-design-vue/es/tree'
;
import
type
{
DataNode
}
from
'ant-design-vue/es/tree'
;
import
type
{
CheckInfo
}
from
'ant-design-vue/es/vc-tree/props'
;
import
type
{
PropType
,
SetupContext
}
from
'vue'
;
import
{
computed
,
nextTick
,
onMounted
,
ref
}
from
'vue'
;
import
{
computed
,
nextTick
,
onMounted
,
ref
,
useSlots
,
watch
}
from
'vue
'
;
import
{
treeToList
}
from
'@vben/utils
'
;
import
{
findGroupParentIds
,
treeToList
}
from
'@vben/utils'
;
import
{
Button
,
Checkbox
,
Tree
}
from
'ant-design-vue'
;
import
{
Checkbox
,
Tree
}
from
'ant-design-vue'
;
import
{
uniq
}
from
'lodash-es'
;
/** 需要禁止透传 */
/** 需要禁止透传 */
defineOptions
({
inheritAttrs
:
false
});
defineOptions
({
inheritAttrs
:
false
});
const
props
=
defineProps
({
const
props
=
withDefaults
(
defineProps
<
Props
>
(),
{
checkStrictly
:
{
expandAllOnInit
:
false
,
default
:
true
,
fieldNames
:
()
=>
({
key
:
'id'
,
title
:
'label'
}),
type
:
Boolean
,
resetOnStrictlyChange
:
true
,
},
treeData
:
()
=>
[],
expandAllOnInit
:
{
default
:
false
,
type
:
Boolean
,
},
fieldNames
:
{
default
:
()
=>
({
key
:
'id'
,
title
:
'name'
}),
type
:
Object
as
PropType
<
{
key
:
string
;
title
:
string
}
>
,
},
/** 点击节点关联/独立时 清空已勾选的节点 */
resetOnStrictlyChange
:
{
default
:
true
,
type
:
Boolean
,
},
treeData
:
{
default
:
()
=>
[],
type
:
Array
as
PropType
<
DataNode
[]
>
,
},
});
});
const
emit
=
defineEmits
<
{
checkStrictlyChange
:
[
boolean
]
}
>
();
const
expandStatus
=
ref
(
false
);
interface
Props
{
const
selectAllStatus
=
ref
(
false
);
/**
* 是否展开所有节点 mount
*/
expandAllOnInit
?:
boolean
;
/**
* 自定义字段
*/
fieldNames
?:
{
key
:
string
;
title
:
string
};
/**
* 点击节点关联/独立时 清空已勾选的节点
*/
resetOnStrictlyChange
?:
boolean
;
/**
* 树结构数据
*/
treeData
?:
DataNode
[];
}
/**
/**
* 后台的这个字段跟antd/ele是反的
* 展开的状态
* 组件库这个字段代表不关联
* 后台这个代表关联
*/
*/
const
innerCheckedStrictly
=
computed
(()
=>
{
const
expandStatus
=
ref
(
false
);
return
!
props
.
checkStrictly
;
/**
});
* 全选状态
*/
const
selectAllStatus
=
ref
(
false
);
const
associationText
=
computed
(()
=>
{
const
associationText
=
computed
(()
=>
{
return
props
.
checkStrictly
?
'父子节点关联'
:
'父子节点独立'
;
return
checkStrictly
.
value
?
'父子节点关联'
:
'父子节点独立'
;
});
});
/**
/**
* 这个只用于界面显示
* 这个只用于界面显示
* 关联情况下 只会有最末尾的节点被选中
* 关联情况下 只会有最末尾的节点被选中
*/
*/
const
checkedKeys
=
defineModel
(
'value'
,
{
const
checkedKeys
=
defineModel
<
(
number
|
string
)[]
>
(
'value'
,
{
default
:
()
=>
[],
default
:
()
=>
[],
type
:
Array
as
PropType
<
(
number
|
string
)[]
>
,
});
});
// 所有节点的ID
const
allKeys
=
computed
(()
=>
{
const
idField
=
props
.
fieldNames
.
key
;
return
treeToList
(
props
.
treeData
).
map
((
item
:
any
)
=>
item
[
idField
]);
});
/** 已经选择的所有节点 包括子/父节点 用于提交 */
const
checkedRealKeys
=
ref
<
(
number
|
string
)[]
>
([]);
/**
/**
* 取第一次的menuTree id 设置到checkedMenuKeys
* 是否节点关联 后端字段跟前端字段是反的
* 主要为了解决没有任何修改 直接点击保存的情况
*
* length为0情况(即新增时候没有勾选节点) 勾选这里会延迟触发 节点会拼接上父节点 导致ID重复
*/
*/
const
stop
=
watch
([
checkedKeys
,
()
=>
props
.
treeData
],
()
=>
{
const
checkStrictly
=
defineModel
<
boolean
>
(
'checkStrictly'
,
{
if
(
default
:
()
=>
true
,
props
.
checkStrictly
&&
});
checkedKeys
.
value
.
length
>
0
&&
props
.
treeData
.
length
>
0
const
computedCheckedKeys
=
computed
<
any
>
({
)
{
get
()
{
/** 找到父节点 添加上 */
const
parentIds
=
findGroupParentIds
(
props
.
treeData
,
checkedKeys
.
value
as
any
,
{
id
:
props
.
fieldNames
.
key
},
);
/**
/**
* uniq 解决上面的id重复问题
* 严格模式(节点不关联) 需要返回{checked: string[] | number[], halfChecked: string[]}
* @see https://www.antdv.com/components/tree-cn#tree-props
*/
*/
checkedRealKeys
.
value
=
uniq
([...
parentIds
,
...
checkedKeys
.
value
]);
if
(
!
checkStrictly
.
value
)
{
stop
();
return
{
}
checked
:
[...
checkedKeys
.
value
],
if
(
!
props
.
checkStrictly
&&
checkedKeys
.
value
.
length
>
0
)
{
halfChecked
:
[],
/** 节点独立 这里是全部的节点 */
};
checkedRealKeys
.
value
=
checkedKeys
.
value
;
}
stop
();
return
checkedKeys
.
value
;
}
},
set
(
v
)
{
if
(
!
checkStrictly
.
value
)
{
checkedKeys
.
value
=
[...
v
.
checked
,
...
v
.
halfChecked
];
return
;
}
checkedKeys
.
value
=
v
;
},
});
});
/**
// 所有节点的ID
*
const
allKeys
=
computed
(()
=>
{
* @param checkedStateKeys 已经选中的子节点的ID
const
idField
=
props
.
fieldNames
.
key
;
* @param info info.halfCheckedKeys为父节点的ID
return
treeToList
(
props
.
treeData
).
map
((
item
:
any
)
=>
item
[
idField
]);
*/
});
type
CheckedState
<
T
=
number
|
string
>
=
|
T
[]
|
{
checked
:
T
[];
halfChecked
:
T
[]
};
function
handleChecked
(
checkedStateKeys
:
CheckedState
,
info
:
CheckInfo
)
{
// 数组的话为节点关联
if
(
Array
.
isArray
(
checkedStateKeys
))
{
const
halfCheckedKeys
:
number
[]
=
(
info
.
halfCheckedKeys
||
[])
as
number
[];
checkedRealKeys
.
value
=
[...
halfCheckedKeys
,
...
checkedStateKeys
];
}
else
{
checkedRealKeys
.
value
=
[...
checkedStateKeys
.
checked
];
// fix: Invalid prop: type check failed for prop "value". Expected Array, got Object
checkedKeys
.
value
=
[...
checkedStateKeys
.
checked
];
}
}
function
handle
Expand
Change
(
e
:
CheckboxChangeEvent
)
{
function
handle
CheckedAll
Change
(
e
:
CheckboxChangeEvent
)
{
// 这个用于展示
// 这个用于展示
checkedKeys
.
value
=
e
.
target
.
checked
?
allKeys
.
value
:
[];
checkedKeys
.
value
=
e
.
target
.
checked
?
allKeys
.
value
:
[];
// 这个用于提交
checkedRealKeys
.
value
=
e
.
target
.
checked
?
allKeys
.
value
:
[];
}
}
const
expandedKeys
=
ref
<
string
[]
>
([]);
const
expandedKeys
=
ref
<
string
[]
>
([]);
function
handleExpandOrCollapseAll
(
e
:
CheckboxChangeEvent
)
{
function
handleExpandOrCollapseAll
()
{
const
expand
=
e
.
target
.
checked
;
expandStatus
.
value
=
!
expandStatus
.
value
;
expandedKeys
.
value
=
expand
?
allKeys
.
value
:
[];
expandedKeys
.
value
=
expand
Status
.
value
?
allKeys
.
value
:
[];
}
}
function
handleCheckStrictlyChange
(
e
:
CheckboxChangeEvent
)
{
function
handleCheckStrictlyChange
()
{
emit
(
'checkStrictlyChange'
,
e
.
target
.
checked
);
if
(
props
.
resetOnStrictlyChange
)
{
if
(
props
.
resetOnStrictlyChange
)
{
checkedKeys
.
value
=
[];
checkedKeys
.
value
=
[];
checkedRealKeys
.
value
=
[];
}
}
}
}
/**
* 暴露方法来获取用于提交的全部节点
* uniq去重(保险方案)
*/
defineExpose
({
getCheckedKeys
:
()
=>
uniq
(
checkedRealKeys
.
value
),
});
onMounted
(
async
()
=>
{
onMounted
(
async
()
=>
{
if
(
props
.
expandAllOnInit
)
{
if
(
props
.
expandAllOnInit
)
{
await
nextTick
();
await
nextTick
();
expandedKeys
.
value
=
allKeys
.
value
;
expandedKeys
.
value
=
allKeys
.
value
;
}
}
});
});
const
slots
=
useSlots
()
as
SetupContext
[
'slots'
];
</
script
>
</
script
>
<
template
>
<
template
>
<div
class=
"bg-background w-full rounded-lg border-[1px] p-[12px]"
>
<div
class=
"bg-background w-full rounded-lg border-[1px] p-[12px]"
>
<!--
<div
class=
"flex flex-col gap-6 text-[13px]"
>
<div>
computedCheckedKeys
{{
computedCheckedKeys
}}
</div>
<div>
checkedKeys
{{
checkedKeys
}}
</div>
</div>
-->
<div
class=
"flex items-center justify-between gap-2 border-b-[1px] pb-2"
>
<div
class=
"flex items-center justify-between gap-2 border-b-[1px] pb-2"
>
<div>
<div
class=
"opacity-75"
>
<span>
节点状态:
</span>
<span>
节点状态:
</span>
<span
:class=
"[
props.
checkStrictly ? 'text-primary' : 'text-red-500']"
>
<span
:class=
"[checkStrictly ? 'text-primary' : 'text-red-500']"
>
{{
associationText
}}
{{
associationText
}}
</span>
</span>
</div>
</div>
<div>
已选中
<span
class=
"text-primary mx-1 font-semibold"
>
{{
checkedRealKeys
.
length
}}
</span>
个节点
</div>
</div>
</div>
<div
<div
class=
"flex flex-wrap items-center justify-between border-b-[1px] py-2"
class=
"flex flex-wrap items-center justify-between border-b-[1px] py-2"
>
>
<Button
size=
"small"
@
click=
"handleExpandOrCollapseAll"
>
展开/折叠全部
</Button>
<Checkbox
<Checkbox
v-model:checked=
"
expand
Status"
v-model:checked=
"
selectAll
Status"
@
change=
"handle
ExpandOrCollapseAll
"
@
change=
"handle
CheckedAllChange
"
>
>
展开/折叠全部
</Checkbox>
<Checkbox
v-model:checked=
"selectAllStatus"
@
change=
"handleExpandChange"
>
全选/取消全选
全选/取消全选
</Checkbox>
</Checkbox>
<Checkbox
:checked=
"checkStrictly"
@
change=
"handleCheckStrictlyChange"
>
<Checkbox
v-model:checked=
"checkStrictly"
@
change=
"handleCheckStrictlyChange"
>
父子节点关联
父子节点关联
</Checkbox>
</Checkbox>
</div>
</div>
<div
class=
"py-2"
>
<div
class=
"py-2"
>
<Tree
<Tree
v-if=
"treeData.length > 0"
:check-strictly=
"!checkStrictly"
v-model:check-strictly=
"innerCheckedStrictly"
v-model:checked-keys=
"computedCheckedKeys"
v-model:checked-keys=
"checkedKeys"
v-model:expanded-keys=
"expandedKeys"
v-model:expanded-keys=
"expandedKeys"
:checkable=
"true"
:checkable=
"true"
:field-names=
"fieldNames"
:field-names=
"fieldNames"
:selectable=
"false"
:selectable=
"false"
:tree-data=
"treeData"
:tree-data=
"treeData"
@
check=
"handleChecked"
>
>
<template
<template
v-for=
"slotName in Object.keys(slots)"
v-for=
"slotName in Object.keys(
$
slots)"
:key=
"slotName"
:key=
"slotName"
#[
slotName
]="
data
"
#[
slotName
]="
data
"
>
>
...
@@ -219,3 +174,20 @@ const slots = useSlots() as SetupContext['slots'];
...
@@ -219,3 +174,20 @@ const slots = useSlots() as SetupContext['slots'];
</div>
</div>
</div>
</div>
</template>
</template>
<
style
lang=
"scss"
scoped
>
:deep
(
.ant-tree
)
{
// 勾选框居中
&
.ant-tree-checkbox
{
margin
:
0
;
margin-right
:
6px
;
}
// 展开图标居中
&
.ant-tree-switcher
{
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
}
}
</
style
>
apps/web-antd/src/views/auth/role/data.tsx
View file @
8d4896cf
import
type
{
VbenFormSchema
}
from
'#/adapter/form'
;
import
type
{
VbenFormSchema
}
from
'#/adapter/form'
;
import
type
{
VxeTableGridOptions
}
from
'#/adapter/vxe-table'
;
import
type
{
VxeTableGridOptions
}
from
'#/adapter/vxe-table'
;
import
{
markRaw
}
from
'vue'
;
import
{
useAccess
}
from
'@vben/access'
;
import
{
useAccess
}
from
'@vben/access'
;
import
{
DictEnum
}
from
'@vben/constants'
;
import
{
DictEnum
}
from
'@vben/constants'
;
import
{
getVxePopupContainer
}
from
'@vben/utils'
;
import
{
getVxePopupContainer
}
from
'@vben/utils'
;
...
@@ -8,6 +10,8 @@ import { getVxePopupContainer } from '@vben/utils';
...
@@ -8,6 +10,8 @@ import { getVxePopupContainer } from '@vben/utils';
import
{
Tag
}
from
'ant-design-vue'
;
import
{
Tag
}
from
'ant-design-vue'
;
import
{
changeRoleStatus
}
from
'#/api/auth/role'
;
import
{
changeRoleStatus
}
from
'#/api/auth/role'
;
import
{
DefaultSlot
}
from
'#/components/global/slot'
;
import
{
TreeSelectPanel
}
from
'#/components/tree'
;
import
{
getDictOptions
}
from
'#/utils/dict'
;
import
{
getDictOptions
}
from
'#/utils/dict'
;
/**
/**
...
@@ -134,15 +138,6 @@ export const dataScopeModalSchemas: VbenFormSchema[] = [
...
@@ -134,15 +138,6 @@ export const dataScopeModalSchemas: VbenFormSchema[] = [
fieldName
:
'id'
,
fieldName
:
'id'
,
label
:
'角色ID'
,
label
:
'角色ID'
,
},
},
{
component
:
'Radio'
,
dependencies
:
{
show
:
()
=>
false
,
triggerFields
:
[
''
],
},
fieldName
:
'deptCheckStrictly'
,
label
:
'deptCheckStrictly'
,
},
{
{
component
:
'Input'
,
component
:
'Input'
,
componentProps
:
{
componentProps
:
{
...
@@ -171,12 +166,39 @@ export const dataScopeModalSchemas: VbenFormSchema[] = [
...
@@ -171,12 +166,39 @@ export const dataScopeModalSchemas: VbenFormSchema[] = [
label
:
'权限范围'
,
label
:
'权限范围'
,
},
},
{
{
component
:
'TreeSelect'
,
component
:
'Radio'
,
dependencies
:
{
show
:
()
=>
false
,
triggerFields
:
[
''
],
},
fieldName
:
'deptCheckStrictly'
,
label
:
'deptCheckStrictly'
,
},
{
// 这种的场景基本上是一个组件需要绑定两个或以上的场景
component
:
markRaw
(
DefaultSlot
),
defaultValue
:
[],
defaultValue
:
[],
componentProps
:
{
rootDivAttrs
:
{
class
:
'w-full'
,
},
},
dependencies
:
{
dependencies
:
{
show
:
(
values
)
=>
values
.
dataScope
===
5
,
show
:
(
values
)
=>
values
.
dataScope
===
5
,
triggerFields
:
[
'dataScope'
],
triggerFields
:
[
'dataScope'
],
},
},
renderComponentContent
:
(
model
)
=>
({
default
:
(
attrs
:
any
)
=>
{
return
(
<
TreeSelectPanel
expand
-
all
-
on
-
init=
{
true
}
treeData=
{
attrs
.
treeData
}
v
-
model
:
checkStrictly=
{
model
.
deptCheckStrictly
}
v
-
model
:
value=
{
model
.
deptIds
}
/>
);
},
}),
fieldName
:
'deptIds'
,
fieldName
:
'deptIds'
,
help
:
'更改后立即生效'
,
help
:
'更改后立即生效'
,
label
:
'部门权限'
,
label
:
'部门权限'
,
...
...
apps/web-antd/src/views/auth/role/deptDataScopeModal.vue
View file @
8d4896cf
...
@@ -2,14 +2,14 @@
...
@@ -2,14 +2,14 @@
import
type
{
SysDeptApi
}
from
'#/api/auth/dept'
;
import
type
{
SysDeptApi
}
from
'#/api/auth/dept'
;
import
type
{
SysRoleApi
}
from
'#/api/auth/role'
;
import
type
{
SysRoleApi
}
from
'#/api/auth/role'
;
import
{
ref
}
from
'vue'
;
import
{
useVbenModal
}
from
'@vben/common-ui'
;
import
{
useVbenModal
}
from
'@vben/common-ui'
;
import
{
findGroupParentIds
}
from
'@vben/utils'
;
import
{
uniq
}
from
'lodash-es'
;
import
{
useVbenForm
}
from
'#/adapter/form'
;
import
{
useVbenForm
}
from
'#/adapter/form'
;
import
{
treeList
}
from
'#/api/auth/dept'
;
import
{
treeList
}
from
'#/api/auth/dept'
;
import
{
authDataScope
,
getRoleDeptIds
}
from
'#/api/auth/role'
;
import
{
authDataScope
,
getRoleDeptIds
}
from
'#/api/auth/role'
;
import
{
TreeSelectPanel
}
from
'#/components/tree'
;
import
{
defaultFormValueGetter
,
useBeforeCloseDiff
}
from
'#/utils/popup'
;
import
{
defaultFormValueGetter
,
useBeforeCloseDiff
}
from
'#/utils/popup'
;
import
{
dataScopeModalSchemas
}
from
'./data'
;
import
{
dataScopeModalSchemas
}
from
'./data'
;
...
@@ -29,30 +29,36 @@ const [BasicForm, formApi] = useVbenForm({
...
@@ -29,30 +29,36 @@ const [BasicForm, formApi] = useVbenForm({
showDefaultActions
:
false
,
showDefaultActions
:
false
,
});
});
const
deptTree
=
ref
<
DeptTreeOption
[]
>
([]);
// const deptTree = ref
<
DeptTreeOption
[]
>
([]);
/**
* 保存部门数据 用于获取祖先节点
*/
let
treeData
:
DeptTreeOption
[]
=
[];
async
function
setupDeptTree
(
id
:
number
)
{
async
function
setupDeptTree
(
id
:
number
)
{
const
[
roleDeptIds
,
deptTreeList
]
=
await
Promise
.
all
([
const
[
roleDeptIds
,
deptTreeList
]
=
await
Promise
.
all
([
getRoleDeptIds
(
id
),
getRoleDeptIds
(
id
),
treeList
(),
treeList
(),
]);
]);
// const resp = await getRoleDeptIds(id);
// const resp = await getRoleDeptIds(id);
formApi
.
setFieldValue
(
'deptIds'
,
roleDeptIds
);
/**
* 设置部门树数据
*/
formApi
.
updateSchema
([
{
fieldName
:
'deptIds'
,
componentProps
:
{
treeData
:
deptTreeList
}
},
]);
/**
* 设置选中 必须先传递treeData
* Note: Tree missing follow keys: '1981565541727186945'
*/
await
formApi
.
setFieldValue
(
'deptIds'
,
roleDeptIds
);
// 设置菜单信息
// 设置菜单信息
deptTree
.
value
=
deptTreeList
;
treeData
=
deptTreeList
;
}
async
function
customFormValueGetter
()
{
const
v
=
await
defaultFormValueGetter
(
formApi
)();
// 获取勾选信息
const
menuIds
=
deptSelectRef
.
value
?.[
0
]?.
getCheckedKeys
()
??
[];
const
mixStr
=
v
+
menuIds
.
join
(
','
);
return
mixStr
;
}
}
const
{
onBeforeClose
,
markInitialized
,
resetInitialized
}
=
useBeforeCloseDiff
(
const
{
onBeforeClose
,
markInitialized
,
resetInitialized
}
=
useBeforeCloseDiff
(
{
{
initializedGetter
:
customFormValueGetter
,
initializedGetter
:
defaultFormValueGetter
(
formApi
)
,
currentGetter
:
customFormValueGetter
,
currentGetter
:
defaultFormValueGetter
(
formApi
)
,
},
},
);
);
...
@@ -63,6 +69,7 @@ const [BasicModal, modalApi] = useVbenModal({
...
@@ -63,6 +69,7 @@ const [BasicModal, modalApi] = useVbenModal({
onConfirm
:
handleConfirm
,
onConfirm
:
handleConfirm
,
onOpenChange
:
async
(
isOpen
)
=>
{
onOpenChange
:
async
(
isOpen
)
=>
{
if
(
!
isOpen
)
{
if
(
!
isOpen
)
{
treeData
=
[];
return
null
;
return
null
;
}
}
modalApi
.
modalLoading
(
true
);
modalApi
.
modalLoading
(
true
);
...
@@ -78,11 +85,6 @@ const [BasicModal, modalApi] = useVbenModal({
...
@@ -78,11 +85,6 @@ const [BasicModal, modalApi] = useVbenModal({
},
},
});
});
/**
* 这里拿到的是一个数组ref
*/
const
deptSelectRef
=
ref
();
async
function
handleConfirm
()
{
async
function
handleConfirm
()
{
try
{
try
{
modalApi
.
lock
(
true
);
modalApi
.
lock
(
true
);
...
@@ -93,9 +95,16 @@ async function handleConfirm() {
...
@@ -93,9 +95,16 @@ async function handleConfirm() {
// formApi.getValues拿到的是一个readonly对象,不能直接修改,需要cloneDeep
// formApi.getValues拿到的是一个readonly对象,不能直接修改,需要cloneDeep
const
data
=
await
formApi
.
getValues
();
const
data
=
await
formApi
.
getValues
();
// 不为自定义权限的话 删除部门id
// 不为自定义权限的话 删除部门id
let
deptIds
:
number
[]
=
[];
if
(
data
.
dataScope
===
5
)
{
if
(
data
.
dataScope
===
5
)
{
deptIds
=
deptSelectRef
.
value
?.[
0
]?.
getCheckedKeys
()
??
[];
let
{
deptIds
,
deptCheckStrictly
}
=
data
;
// 节点关联 需要拼接上祖级ID(获取的是不带的)
if
(
deptCheckStrictly
)
{
// 找到所有父级ID
const
parentIds
=
findGroupParentIds
(
treeData
,
deptIds
,
{
id
:
'id'
});
// 去重
deptIds
=
uniq
([...
parentIds
,
...
deptIds
]);
}
// 赋值
data
.
deptIds
=
deptIds
;
data
.
deptIds
=
deptIds
;
}
else
{
}
else
{
data
.
deptIds
=
[];
data
.
deptIds
=
[];
...
@@ -115,29 +124,10 @@ async function handleClosed() {
...
@@ -115,29 +124,10 @@ async function handleClosed() {
await
formApi
.
resetForm
();
await
formApi
.
resetForm
();
resetInitialized
();
resetInitialized
();
}
}
/**
* 通过回调更新 无法通过v-model
* @param value 菜单选择是否严格模式
*/
function
handleCheckStrictlyChange
(
value
:
boolean
)
{
formApi
.
setFieldValue
(
'deptCheckStrictly'
,
value
);
}
</
script
>
</
script
>
<
template
>
<
template
>
<BasicModal
class=
"min-h-[600px] w-[550px]"
title=
"分配权限"
>
<BasicModal
class=
"min-h-[600px] w-[550px]"
title=
"分配权限"
>
<BasicForm>
<BasicForm
/>
<template
#
deptIds=
"slotProps"
>
<TreeSelectPanel
ref=
"deptSelectRef"
v-bind=
"slotProps"
:check-strictly=
"formApi.form.values.deptCheckStrictly"
:expand-all-on-init=
"true"
:tree-data=
"deptTree"
@
check-strictly-change=
"handleCheckStrictlyChange"
/>
</
template
>
</BasicForm>
</BasicModal>
</BasicModal>
</
template
>
</
template
>
apps/web-antd/src/views/system/dict/type/list.vue
View file @
8d4896cf
...
@@ -137,7 +137,7 @@ function onDelete(row: DictTypeApi.DictType) {
...
@@ -137,7 +137,7 @@ function onDelete(row: DictTypeApi.DictType) {
async
function
handleRefreshCache
()
{
async
function
handleRefreshCache
()
{
await
refreshDictTypeCache
();
await
refreshDictTypeCache
();
await
table
Api
.
query
();
await
grid
Api
.
query
();
}
}
</
script
>
</
script
>
<
template
>
<
template
>
...
@@ -145,12 +145,12 @@ async function handleRefreshCache() {
...
@@ -145,12 +145,12 @@ async function handleRefreshCache() {
<DictTypeModal
@
success=
"onRefresh"
/>
<DictTypeModal
@
success=
"onRefresh"
/>
<Grid
table-title=
"字典类型列表"
>
<Grid
table-title=
"字典类型列表"
>
<template
#
toolbar-tools
>
<template
#
toolbar-tools
>
<
a-b
utton
<
B
utton
v-access:code=
"['system:dict:edit']"
v-access:code=
"['system:dict:edit']"
@
click=
"handleRefreshCache"
@
click=
"handleRefreshCache"
>
>
刷新缓存
刷新缓存
</
a-b
utton>
</
B
utton>
<Button
<Button
type=
"primary"
type=
"primary"
v-access:code=
"['system:dict:add']"
v-access:code=
"['system:dict:add']"
...
...
apps/web-antd/src/views/system/tenant/list.vue
View file @
8d4896cf
...
@@ -139,6 +139,18 @@ function handleSyncTenantDict() {
...
@@ -139,6 +139,18 @@ function handleSyncTenantDict() {
},
},
});
});
}
}
function
handleSyncTenantConfig
()
{
Modal
.
confirm
({
title
:
'提示'
,
iconType
:
'warning'
,
content
:
'确认同步租户参数配置?'
,
onOk
:
async
()
=>
{
await
syncTenantConfig
();
await
tableApi
.
query
();
},
});
}
</
script
>
</
script
>
<
template
>
<
template
>
<Page
auto-content-height
>
<Page
auto-content-height
>
...
@@ -152,6 +164,12 @@ function handleSyncTenantDict() {
...
@@ -152,6 +164,12 @@ function handleSyncTenantDict() {
>
>
同步租户字典
同步租户字典
</Button>
</Button>
<Button
v-access:code=
"['system:tenant:edit']"
@
click=
"handleSyncTenantConfig"
>
同步租户参数配置
</Button>
<Button
<Button
v-access:code=
"['system:tenant:export']"
v-access:code=
"['system:tenant:export']"
@
click=
"handleDownloadExcel"
@
click=
"handleDownloadExcel"
...
...
apps/web-antd/src/views/workflow/components/actions/flow-actions.vue
0 → 100644
View file @
8d4896cf
<
script
setup
lang=
"ts"
>
import
type
{
ApprovalType
}
from
'../type'
;
import
type
{
User
}
from
'#/api/core/user'
;
import
type
{
TaskInfo
}
from
'#/api/workflow/task/model'
;
import
{
computed
,
h
}
from
'vue'
;
import
{
useRouter
}
from
'vue-router'
;
import
{
useVbenModal
}
from
'@vben/common-ui'
;
import
{
cn
,
getPopupContainer
}
from
'@vben/utils'
;
import
{
ArrowLeftOutlined
,
CheckOutlined
,
EditOutlined
,
ExclamationCircleOutlined
,
MenuOutlined
,
RollbackOutlined
,
UsergroupAddOutlined
,
UsergroupDeleteOutlined
,
UserOutlined
,
}
from
'@ant-design/icons-vue'
;
import
{
Button
,
Dropdown
,
Menu
,
MenuItem
,
Modal
,
Space
}
from
'ant-design-vue'
;
import
{
cancelProcessApply
,
deleteByInstanceIds
,
}
from
'#/api/workflow/instance'
;
import
{
taskOperation
,
terminationTask
,
updateAssignee
,
}
from
'#/api/workflow/task'
;
import
{
approvalModal
,
approvalRejectionModal
,
flowInterfereModal
}
from
'..'
;
import
{
approveWithReasonModal
}
from
'../helper'
;
import
userSelectModal
from
'../user-select-modal.vue'
;
interface
Props
{
/**
* 行数据的taskInfo?
*/
task
?:
TaskInfo
;
/**
* 审批类型 根据不同类型显示按钮
*/
type
:
ApprovalType
;
/**
* 为审批类型时候 显示的按钮(按钮权限)
*/
buttonPermissions
:
Record
<
string
,
boolean
>
;
}
const
props
=
defineProps
<
Props
>
();
const
emit
=
defineEmits
<
{
reload
:
[];
}
>
();
// 是否显示 `其他` 按钮
const
showButtonOther
=
computed
(()
=>
{
const
moreCollections
=
new
Set
([
'addSign'
,
'subSign'
,
'transfer'
,
'trust'
]);
return
Object
.
keys
(
props
.
buttonPermissions
).
some
(
(
key
)
=>
moreCollections
.
has
(
key
)
&&
props
.
buttonPermissions
[
key
],
);
});
// 进行中 可以撤销
const
revocable
=
computed
(()
=>
props
.
task
?.
flowStatus
===
'waiting'
);
async
function
handleCancel
()
{
Modal
.
confirm
({
title
:
'提示'
,
content
:
'确定要撤销该申请吗?'
,
centered
:
true
,
okButtonProps
:
{
danger
:
true
},
onOk
:
async
()
=>
{
await
cancelProcessApply
({
businessId
:
props
.
task
!
.
businessId
,
message
:
'申请人撤销流程!'
,
});
emit
(
'reload'
);
},
});
}
/**
* 是否可编辑/删除
*/
const
editableAndRemoveable
=
computed
(()
=>
{
if
(
!
props
.
task
)
{
return
false
;
}
return
[
'back'
,
'cancel'
,
'draft'
].
includes
(
props
.
task
.
flowStatus
);
});
const
router
=
useRouter
();
function
handleEdit
()
{
const
path
=
props
.
task
?.
formPath
;
if
(
path
)
{
router
.
push
({
path
,
query
:
{
id
:
props
.
task
!
.
businessId
}
});
}
}
function
handleRemove
()
{
Modal
.
confirm
({
title
:
'提示'
,
content
:
'确定删除该申请吗?'
,
centered
:
true
,
okButtonProps
:
{
danger
:
true
},
onOk
:
async
()
=>
{
await
deleteByInstanceIds
([
props
.
task
!
.
id
]);
emit
(
'reload'
);
},
});
}
/**
* 审批驳回
*/
const
[
RejectionModal
,
rejectionModalApi
]
=
useVbenModal
({
connectedComponent
:
approvalRejectionModal
,
});
function
handleRejection
()
{
rejectionModalApi
.
setData
({
taskId
:
props
.
task
?.
id
,
definitionId
:
props
.
task
?.
definitionId
,
nodeCode
:
props
.
task
?.
nodeCode
,
});
rejectionModalApi
.
open
();
}
/**
* 审批终止
*/
function
handleTermination
()
{
approveWithReasonModal
({
title
:
'审批终止'
,
description
:
'确定终止当前审批流程吗?'
,
onOk
:
async
(
reason
)
=>
{
await
terminationTask
({
taskId
:
props
.
task
!
.
id
,
comment
:
reason
});
emit
(
'reload'
);
},
});
}
/**
* 审批通过
*/
const
[
ApprovalModal
,
approvalModalApi
]
=
useVbenModal
({
connectedComponent
:
approvalModal
,
});
function
handleApproval
()
{
const
{
buttonPermissions
}
=
props
;
// 是否具有抄送权限
const
copyPermission
=
buttonPermissions
?.
copy
??
false
;
// 是否具有选人权限
const
assignPermission
=
buttonPermissions
?.
pop
??
false
;
approvalModalApi
.
setData
({
taskId
:
props
.
task
?.
id
,
copyPermission
,
assignPermission
,
});
approvalModalApi
.
open
();
}
/**
* 委托
*/
const
[
DelegationModal
,
delegationModalApi
]
=
useVbenModal
({
connectedComponent
:
userSelectModal
,
});
function
handleDelegation
(
userList
:
User
[])
{
if
(
userList
.
length
===
0
)
return
;
const
current
=
userList
[
0
];
approveWithReasonModal
({
title
:
'委托'
,
description
:
`确定委托给[
${
current
?.
nickName
}]
吗
?
`,
onOk: async (reason) => {
await taskOperation(
{ taskId: props.task!.id, userId: current!.userId, message: reason },
'delegateTask',
);
emit('reload');
},
});
}
/**
* 转办
*/
const [TransferModal, transferModalApi] = useVbenModal({
connectedComponent: userSelectModal,
});
function handleTransfer(userList: User[]) {
if (userList.length === 0) return;
const current = userList[0];
approveWithReasonModal({
title: '转办',
description: `
确定转办给
[
$
{
current
?.
nickName
}]
吗
?
`,
onOk: async (reason) => {
await taskOperation(
{ taskId: props.task!.id, userId: current!.userId, message: reason },
'transferTask',
);
emit('reload');
},
});
}
const [AddSignatureModal, addSignatureModalApi] = useVbenModal({
connectedComponent: userSelectModal,
});
function handleAddSignature(userList: User[]) {
if (userList.length === 0) return;
const userIds = userList.map((user) => user.userId);
Modal.confirm({
title: '提示',
content: '确认加签吗?',
centered: true,
onOk: async () => {
await taskOperation({ taskId: props.task!.id, userIds }, 'addSignature');
emit('reload');
},
});
}
const [ReductionSignatureModal, reductionSignatureModalApi] = useVbenModal({
connectedComponent: userSelectModal,
});
function handleReductionSignature(userList: User[]) {
if (userList.length === 0) return;
const userIds = userList.map((user) => user.userId);
Modal.confirm({
title: '提示',
content: '确认减签吗?',
centered: true,
onOk: async () => {
await taskOperation(
{ taskId: props.task!.id, userIds },
'reductionSignature',
);
emit('reload');
},
});
}
// 流程干预
const [FlowInterfereModal, flowInterfereModalApi] = useVbenModal({
connectedComponent: flowInterfereModal,
});
function handleFlowInterfere() {
flowInterfereModalApi.setData({ taskId: props.task?.id });
flowInterfereModalApi.open();
}
// 修改办理人
const [UpdateAssigneeModal, updateAssigneeModalApi] = useVbenModal({
connectedComponent: userSelectModal,
});
function handleUpdateAssignee(userList: User[]) {
if (userList.length === 0) return;
const current = userList[0];
if (!current) return;
Modal.confirm({
title: '修改办理人',
content: `
确定修改办理人为
$
{
current
?.
nickName
}
吗
?
`,
centered: true,
onOk: async () => {
await updateAssignee([props.task!.id], current.userId);
emit('reload');
},
});
}
/**
* 是否显示 加签/减签操作
*/
const showMultiActions = computed(() => {
if (!props.task) {
return false;
}
if (Number(props.task.nodeRatio) > 0) {
return true;
}
return false;
});
</
script
>
<
template
>
<div
:class=
"
cn(
'absolute bottom-0 left-0',
'border-t-solid border-t-[1px]',
'bg-background w-full p-3',
)
"
>
<div
class=
"flex justify-end"
>
<Space
v-if=
"type === 'myself'"
>
<Button
v-if=
"revocable"
danger
ghost
type=
"primary"
:icon=
"h(RollbackOutlined)"
@
click=
"handleCancel"
>
撤销申请
</Button>
<Button
type=
"primary"
ghost
v-if=
"editableAndRemoveable"
:icon=
"h(EditOutlined)"
@
click=
"handleEdit"
>
重新编辑
</Button>
<Button
v-if=
"editableAndRemoveable"
danger
ghost
type=
"primary"
:icon=
"h(EditOutlined)"
@
click=
"handleRemove"
>
删除
</Button>
</Space>
<Space
v-if=
"type === 'approve'"
>
<Button
type=
"primary"
ghost
:icon=
"h(CheckOutlined)"
@
click=
"handleApproval"
>
通过
</Button>
<Button
v-if=
"buttonPermissions?.termination"
danger
ghost
type=
"primary"
:icon=
"h(ExclamationCircleOutlined)"
@
click=
"handleTermination"
>
终止
</Button>
<Button
v-if=
"buttonPermissions?.back"
danger
ghost
type=
"primary"
:icon=
"h(ArrowLeftOutlined)"
@
click=
"handleRejection"
>
驳回
</Button>
<Dropdown
:get-popup-container=
"getPopupContainer"
placement=
"bottomRight"
>
<template
#
overlay
>
<Menu>
<MenuItem
v-if=
"buttonPermissions?.trust"
key=
"1"
@
click=
"() => delegationModalApi.open()"
>
<UserOutlined
class=
"mr-2"
/>
委托
</MenuItem>
<MenuItem
v-if=
"buttonPermissions?.transfer"
key=
"2"
@
click=
"() => transferModalApi.open()"
>
<RollbackOutlined
class=
"mr-2"
/>
转办
</MenuItem>
<MenuItem
v-if=
"showMultiActions && buttonPermissions?.addSign"
key=
"3"
@
click=
"() => addSignatureModalApi.open()"
>
<UsergroupAddOutlined
class=
"mr-2"
/>
加签
</MenuItem>
<MenuItem
v-if=
"showMultiActions && buttonPermissions?.subSign"
key=
"4"
@
click=
"() => reductionSignatureModalApi.open()"
>
<UsergroupDeleteOutlined
class=
"mr-2"
/>
减签
</MenuItem>
</Menu>
</
template
>
<Button
v-if=
"showButtonOther"
:icon=
"h(MenuOutlined)"
>
其他
</Button>
</Dropdown>
<ApprovalModal
@
complete=
"$emit('reload')"
/>
<RejectionModal
@
complete=
"$emit('reload')"
/>
<DelegationModal
mode=
"single"
@
finish=
"handleDelegation"
/>
<TransferModal
mode=
"single"
@
finish=
"handleTransfer"
/>
<AddSignatureModal
mode=
"multiple"
@
finish=
"handleAddSignature"
/>
<ReductionSignatureModal
mode=
"multiple"
@
finish=
"handleReductionSignature"
/>
</Space>
<Space
v-if=
"type === 'admin'"
>
<Button
@
click=
"handleFlowInterfere"
>
流程干预
</Button>
<Button
@
click=
"() => updateAssigneeModalApi.open()"
>
修改办理人
</Button>
<FlowInterfereModal
@
complete=
"$emit('reload')"
/>
<UpdateAssigneeModal
mode=
"single"
@
finish=
"handleUpdateAssignee"
/>
</Space>
</div>
</div>
</template>
apps/web-antd/src/views/workflow/components/actions/index.ts
0 → 100644
View file @
8d4896cf
export
{
default
as
FlowActions
}
from
'./flow-actions.vue'
;
apps/web-antd/src/views/workflow/components/apply-modal.vue
View file @
8d4896cf
...
@@ -43,6 +43,8 @@ const [BasicModal, modalApi] = useVbenModal({
...
@@ -43,6 +43,8 @@ const [BasicModal, modalApi] = useVbenModal({
if
(
!
isOpen
)
{
if
(
!
isOpen
)
{
return
null
;
return
null
;
}
}
modalApi
.
modalLoading
(
true
);
const
{
taskId
}
=
modalApi
.
getData
()
as
ModalProps
;
const
{
taskId
}
=
modalApi
.
getData
()
as
ModalProps
;
// 查询是否有按钮权限
// 查询是否有按钮权限
...
@@ -63,6 +65,8 @@ const [BasicModal, modalApi] = useVbenModal({
...
@@ -63,6 +65,8 @@ const [BasicModal, modalApi] = useVbenModal({
},
},
},
},
]);
]);
modalApi
.
modalLoading
(
false
);
},
},
});
});
...
@@ -108,6 +112,11 @@ const [BasicForm, formApi] = useVbenForm({
...
@@ -108,6 +112,11 @@ const [BasicForm, formApi] = useVbenForm({
component
:
'Input'
,
component
:
'Input'
,
defaultValue
:
[],
defaultValue
:
[],
label
:
'抄送人'
,
label
:
'抄送人'
,
// 默认不显示
dependencies
:
{
if
:
false
,
triggerFields
:
[
''
],
},
},
},
],
],
showDefaultActions
:
false
,
showDefaultActions
:
false
,
...
...
apps/web-antd/src/views/workflow/components/approval-details.vue
View file @
8d4896cf
...
@@ -21,8 +21,6 @@ defineOptions({
...
@@ -21,8 +21,6 @@ defineOptions({
defineProps
<
{
defineProps
<
{
currentFlowInfo
:
FlowInfoResponse
;
currentFlowInfo
:
FlowInfoResponse
;
iframeHeight
:
number
;
iframeLoaded
:
boolean
;
task
:
TaskInfo
;
task
:
TaskInfo
;
}
>
();
}
>
();
</
script
>
</
script
>
...
...
apps/web-antd/src/views/workflow/components/approval-panel.vue
View file @
8d4896cf
<!-- 该文件需要重构 但我没空 -->
<!--
TODO: 优化项
会先加载流程信息 再加载业务表单信息
-->
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
type
{
User
}
from
'#/api/core/user'
;
import
type
{
ApprovalType
}
from
'./type'
;
import
type
{
FlowInfoResponse
}
from
'#/api/workflow/instance/model'
;
import
type
{
FlowInfoResponse
}
from
'#/api/workflow/instance/model'
;
import
type
{
TaskInfo
}
from
'#/api/workflow/task/model'
;
import
type
{
TaskInfo
}
from
'#/api/workflow/task/model'
;
import
{
computed
,
h
,
onUnmounted
,
ref
,
watch
}
from
'vue'
;
import
{
computed
,
ref
,
watch
}
from
'vue'
;
import
{
useRouter
}
from
'vue-router'
;
import
{
Fallback
,
useVbenModal
,
VbenAvatar
}
from
'@vben/common-ui'
;
import
{
Fallback
,
VbenAvatar
}
from
'@vben/common-ui'
;
import
{
DictEnum
}
from
'@vben/constants'
;
import
{
DictEnum
}
from
'@vben/constants'
;
import
{
cn
,
getPopupContainer
}
from
'@vben/utils'
;
import
{
cn
}
from
'@vben/utils'
;
import
{
import
{
CopyOutlined
}
from
'@ant-design/icons-vue'
;
ArrowLeftOutlined
,
import
{
useClipboard
}
from
'@vueuse/core'
;
CheckOutlined
,
import
{
Button
,
Card
,
Divider
,
message
,
TabPane
,
Tabs
}
from
'ant-design-vue'
;
CopyOutlined
,
EditOutlined
,
ExclamationCircleOutlined
,
MenuOutlined
,
RollbackOutlined
,
UsergroupAddOutlined
,
UsergroupDeleteOutlined
,
UserOutlined
,
}
from
'@ant-design/icons-vue'
;
import
{
useClipboard
,
useEventListener
}
from
'@vueuse/core'
;
import
{
Button
,
Card
,
Divider
,
Dropdown
,
Menu
,
MenuItem
,
message
,
Modal
,
Space
,
TabPane
,
Tabs
,
}
from
'ant-design-vue'
;
import
{
isObject
}
from
'lodash-es'
;
import
{
import
{
flowInfo
}
from
'#/api/workflow/instance'
;
cancelProcessApply
,
import
{
getTaskByTaskId
}
from
'#/api/workflow/task'
;
deleteByInstanceIds
,
flowInfo
,
}
from
'#/api/workflow/instance'
;
import
{
getTaskByTaskId
,
taskOperation
,
terminationTask
,
updateAssignee
,
}
from
'#/api/workflow/task'
;
import
{
renderDict
}
from
'#/utils/render'
;
import
{
renderDict
}
from
'#/utils/render'
;
import
{
approvalModal
,
approvalRejectionModal
,
flowInterfereModal
}
from
'.
'
;
import
{
FlowActions
}
from
'./actions
'
;
import
ApprovalDetails
from
'./approval-details.vue'
;
import
ApprovalDetails
from
'./approval-details.vue'
;
import
FlowPreview
from
'./flow-preview.vue'
;
import
FlowPreview
from
'./flow-preview.vue'
;
import
{
approveWithReasonModal
}
from
'./helper'
;
import
userSelectModal
from
'./user-select-modal.vue'
;
defineOptions
({
defineOptions
({
name
:
'ApprovalPanel'
,
name
:
'ApprovalPanel'
,
inheritAttrs
:
false
,
inheritAttrs
:
false
,
});
});
const
props
=
defineProps
<
{
task
?:
TaskInfo
;
type
:
ApprovalType
}
>
();
const
props
=
defineProps
<
Props
>
();
/**
/**
* 下面按钮点击后会触发的事件
* 下面按钮点击后会触发的事件
*/
*/
const
emit
=
defineEmits
<
{
reload
:
[]
}
>
();
defineEmits
<
{
reload
:
[]
}
>
();
interface
Props
{
/**
* 行数据(list)的info
*/
task
?:
TaskInfo
;
/**
* 审批类型
*/
type
:
ApprovalType
;
}
const
currentTask
=
ref
<
TaskInfo
>
();
/**
/**
*
是否显示 加签/减签操作
*
目前的作用只为了获取按钮权限 因为list接口(行数据)获取为空
*/
*/
const
showMultiActions
=
computed
(()
=>
{
const
onlyForBtnPermissionTask
=
ref
<
TaskInfo
>
();
if
(
!
currentTask
.
value
)
{
return
false
;
}
if
(
Number
(
currentTask
.
value
.
nodeRatio
)
>
0
)
{
return
true
;
}
return
false
;
});
/**
/**
* 按钮权限
* 按钮权限
*/
*/
const
buttonPermissions
=
computed
(()
=>
{
const
buttonPermissions
=
computed
(()
=>
{
const
record
:
Record
<
string
,
boolean
>
=
{};
const
record
:
Record
<
string
,
boolean
>
=
{};
if
(
!
current
Task
.
value
)
{
if
(
!
onlyForBtnPermission
Task
.
value
)
{
return
record
;
return
record
;
}
}
current
Task
.
value
.
buttonList
.
forEach
((
item
)
=>
{
onlyForBtnPermission
Task
.
value
.
buttonList
.
forEach
((
item
)
=>
{
record
[
item
.
code
]
=
item
.
show
;
record
[
item
.
code
]
=
item
.
show
;
});
});
return
record
;
return
record
;
});
});
// 是否显示 `其他` 按钮
const
showButtonOther
=
computed
(()
=>
{
const
moreCollections
=
new
Set
([
'addSign'
,
'subSign'
,
'transfer'
,
'trust'
]);
return
Object
.
keys
(
buttonPermissions
.
value
).
some
(
(
key
)
=>
moreCollections
.
has
(
key
)
&&
buttonPermissions
.
value
[
key
],
);
});
/**
* myself 我发起的
* readonly 只读 只用于查看
* approve 审批
* admin 流程监控 - 待办任务使用
*/
type
ApprovalType
=
'admin'
|
'approve'
|
'myself'
|
'readonly'
;
const
showFooter
=
computed
(()
=>
{
const
showFooter
=
computed
(()
=>
{
if
(
props
.
type
===
'readonly'
)
{
if
(
props
.
type
===
'readonly'
)
{
return
false
;
return
false
;
...
@@ -132,36 +86,34 @@ const currentFlowInfo = ref<FlowInfoResponse>();
...
@@ -132,36 +86,34 @@ const currentFlowInfo = ref<FlowInfoResponse>();
* card的loading状态
* card的loading状态
*/
*/
const
loading
=
ref
(
false
);
const
loading
=
ref
(
false
);
const
iframeLoaded
=
ref
(
false
);
const
iframeHeight
=
ref
(
300
);
useEventListener
(
'message'
,
(
event
)
=>
{
const
data
=
event
.
data
as
{
[
key
:
string
]:
any
;
type
:
string
};
if
(
!
isObject
(
data
))
return
;
/**
* iframe通信 加载完毕后才显示表单 解决卡顿问题
*/
if
(
data
.
type
===
'mounted'
)
{
iframeLoaded
.
value
=
true
;
}
/**
* 高度与表单高度保持一致
*/
if
(
data
.
type
===
'height'
)
{
const
height
=
data
.
height
;
iframeHeight
.
value
=
height
;
}
});
async
function
handleLoadInfo
(
task
:
TaskInfo
|
undefined
)
{
async
function
handleLoadInfo
(
task
:
TaskInfo
|
undefined
)
{
if
(
!
task
)
{
return
null
;
}
try
{
try
{
if
(
!
task
)
return
null
;
loading
.
value
=
true
;
loading
.
value
=
true
;
iframeLoaded
.
value
=
false
;
const
resp
=
await
flowInfo
(
task
.
businessId
);
currentFlowInfo
.
value
=
resp
;
const
taskResp
=
await
getTaskByTaskId
(
props
.
task
!
.
id
);
/**
currentTask
.
value
=
taskResp
;
* 不为审批不需要调用`getTaskByTaskId`接口
*/
if
(
props
.
type
!==
'approve'
)
{
const
flowResp
=
await
flowInfo
(
task
.
businessId
);
currentFlowInfo
.
value
=
flowResp
;
return
;
}
/**
* getTaskByTaskId主要为了获取按钮权限 目前没有其他功能
* 行数据(即props.task)获取的是没有按钮权限的
*/
const
[
flowResp
,
taskResp
]
=
await
Promise
.
all
([
flowInfo
(
task
.
businessId
),
getTaskByTaskId
(
task
.
id
),
]);
currentFlowInfo
.
value
=
flowResp
;
onlyForBtnPermissionTask
.
value
=
taskResp
;
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
error
(
error
);
console
.
error
(
error
);
}
finally
{
}
finally
{
...
@@ -171,217 +123,6 @@ async function handleLoadInfo(task: TaskInfo | undefined) {
...
@@ -171,217 +123,6 @@ async function handleLoadInfo(task: TaskInfo | undefined) {
watch
(()
=>
props
.
task
,
handleLoadInfo
);
watch
(()
=>
props
.
task
,
handleLoadInfo
);
onUnmounted
(()
=>
(
currentFlowInfo
.
value
=
undefined
));
// 进行中 可以撤销
const
revocable
=
computed
(()
=>
props
.
task
?.
flowStatus
===
'waiting'
);
async
function
handleCancel
()
{
Modal
.
confirm
({
title
:
'提示'
,
content
:
'确定要撤销该申请吗?'
,
centered
:
true
,
okButtonProps
:
{
danger
:
true
},
onOk
:
async
()
=>
{
await
cancelProcessApply
({
businessId
:
props
.
task
!
.
businessId
,
message
:
'申请人撤销流程!'
,
});
emit
(
'reload'
);
},
});
}
/**
* 是否可编辑/删除
*/
const
editableAndRemoveable
=
computed
(()
=>
{
if
(
!
props
.
task
)
{
return
false
;
}
return
[
'back'
,
'cancel'
,
'draft'
].
includes
(
props
.
task
.
flowStatus
);
});
const
router
=
useRouter
();
function
handleEdit
()
{
const
path
=
props
.
task
?.
formPath
;
if
(
path
)
{
router
.
push
({
path
,
query
:
{
id
:
props
.
task
!
.
businessId
}
});
}
}
function
handleRemove
()
{
Modal
.
confirm
({
title
:
'提示'
,
content
:
'确定删除该申请吗?'
,
centered
:
true
,
okButtonProps
:
{
danger
:
true
},
onOk
:
async
()
=>
{
await
deleteByInstanceIds
([
props
.
task
!
.
id
]);
emit
(
'reload'
);
},
});
}
/**
* 审批驳回
*/
const
[
RejectionModal
,
rejectionModalApi
]
=
useVbenModal
({
connectedComponent
:
approvalRejectionModal
,
});
function
handleRejection
()
{
rejectionModalApi
.
setData
({
taskId
:
props
.
task
?.
id
,
definitionId
:
props
.
task
?.
definitionId
,
nodeCode
:
props
.
task
?.
nodeCode
,
});
rejectionModalApi
.
open
();
}
/**
* 审批终止
*/
function
handleTermination
()
{
approveWithReasonModal
({
title
:
'审批终止'
,
description
:
'确定终止当前审批流程吗?'
,
onOk
:
async
(
reason
)
=>
{
await
terminationTask
({
taskId
:
props
.
task
!
.
id
,
comment
:
reason
});
emit
(
'reload'
);
},
});
}
/**
* 审批通过
*/
const
[
ApprovalModal
,
approvalModalApi
]
=
useVbenModal
({
connectedComponent
:
approvalModal
,
});
function
handleApproval
()
{
// 是否具有抄送权限
const
copyPermission
=
buttonPermissions
.
value
?.
copy
??
false
;
// 是否具有选人权限
const
assignPermission
=
buttonPermissions
.
value
?.
pop
??
false
;
approvalModalApi
.
setData
({
taskId
:
props
.
task
?.
id
,
copyPermission
,
assignPermission
,
});
approvalModalApi
.
open
();
}
/**
* TODO: 1提取公共函数 2原版是可以填写意见的(message参数)
*/
/**
* 委托
*/
const
[
DelegationModal
,
delegationModalApi
]
=
useVbenModal
({
connectedComponent
:
userSelectModal
,
});
function
handleDelegation
(
userList
:
User
[])
{
if
(
userList
.
length
===
0
)
return
;
const
current
=
userList
[
0
];
approveWithReasonModal
({
title
:
'委托'
,
description
:
`确定委托给[
${
current
?.
nickName
}]
吗
?
`,
onOk: async (reason) => {
await taskOperation(
{ taskId: props.task!.id, userId: current!.userId, message: reason },
'delegateTask',
);
emit('reload');
},
});
}
/**
* 转办
*/
const [TransferModal, transferModalApi] = useVbenModal({
connectedComponent: userSelectModal,
});
function handleTransfer(userList: User[]) {
if (userList.length === 0) return;
const current = userList[0];
approveWithReasonModal({
title: '转办',
description: `
确定转办给
[
$
{
current
?.
nickName
}]
吗
?
`,
onOk: async (reason) => {
await taskOperation(
{ taskId: props.task!.id, userId: current!.userId, message: reason },
'transferTask',
);
emit('reload');
},
});
}
const [AddSignatureModal, addSignatureModalApi] = useVbenModal({
connectedComponent: userSelectModal,
});
function handleAddSignature(userList: User[]) {
if (userList.length === 0) return;
const userIds = userList.map((user) => user.userId);
Modal.confirm({
title: '提示',
content: '确认加签吗?',
centered: true,
onOk: async () => {
await taskOperation({ taskId: props.task!.id, userIds }, 'addSignature');
emit('reload');
},
});
}
const [ReductionSignatureModal, reductionSignatureModalApi] = useVbenModal({
connectedComponent: userSelectModal,
});
function handleReductionSignature(userList: User[]) {
if (userList.length === 0) return;
const userIds = userList.map((user) => user.userId);
Modal.confirm({
title: '提示',
content: '确认减签吗?',
centered: true,
onOk: async () => {
await taskOperation(
{ taskId: props.task!.id, userIds },
'reductionSignature',
);
emit('reload');
},
});
}
// 流程干预
const [FlowInterfereModal, flowInterfereModalApi] = useVbenModal({
connectedComponent: flowInterfereModal,
});
function handleFlowInterfere() {
flowInterfereModalApi.setData({ taskId: props.task?.id });
flowInterfereModalApi.open();
}
// 修改办理人
const [UpdateAssigneeModal, updateAssigneeModalApi] = useVbenModal({
connectedComponent: userSelectModal,
});
function handleUpdateAssignee(userList: User[]) {
if (userList.length === 0) return;
const current = userList[0];
if (!current) return;
Modal.confirm({
title: '修改办理人',
content: `
确定修改办理人为
$
{
current
?.
nickName
}
吗
?
`,
centered: true,
onOk: async () => {
await updateAssignee([props.task!.id], current.userId);
emit('reload');
},
});
}
/**
/**
* 不加legacy在本地开发没有问题
* 不加legacy在本地开发没有问题
* 打包后在一些设备会无法复制 使用legacy来保证兼容性
* 打包后在一些设备会无法复制 使用legacy来保证兼容性
...
@@ -408,6 +149,7 @@ async function handleCopy(text: string) {
...
@@ -408,6 +149,7 @@ async function handleCopy(text: string) {
<CopyOutlined
class=
"cursor-pointer"
@
click=
"handleCopy(task.id)"
/>
<CopyOutlined
class=
"cursor-pointer"
@
click=
"handleCopy(task.id)"
/>
</div>
</div>
</
template
>
</
template
>
<
template
#
extra
>
<
template
#
extra
>
<Button
size=
"small"
@
click=
"() => handleLoadInfo(task)"
>
<Button
size=
"small"
@
click=
"() => handleLoadInfo(task)"
>
<div
class=
"flex items-center justify-center"
>
<div
class=
"flex items-center justify-center"
>
...
@@ -415,6 +157,7 @@ async function handleCopy(text: string) {
...
@@ -415,6 +157,7 @@ async function handleCopy(text: string) {
</div>
</div>
</Button>
</Button>
</
template
>
</
template
>
<div
class=
"flex flex-col gap-5 p-4"
>
<div
class=
"flex flex-col gap-5 p-4"
>
<div
class=
"flex flex-col gap-3"
>
<div
class=
"flex flex-col gap-3"
>
<div
class=
"flex items-center gap-2"
>
<div
class=
"flex items-center gap-2"
>
...
@@ -427,19 +170,24 @@ async function handleCopy(text: string) {
...
@@ -427,19 +170,24 @@ async function handleCopy(text: string) {
/>
/>
</div>
</div>
</div>
</div>
<div
class=
"flex items-center gap-2"
>
<div
class=
"flex items-center gap-2"
>
<VbenAvatar
<VbenAvatar
:alt=
"task?.createByName ?? ''"
:alt=
"task?.createByName ?? ''"
class=
"bg-primary size-[28px] rounded-full text-white"
class=
"bg-primary size-[28px] rounded-full text-white"
src=
""
src=
""
/>
/>
<span>
{{ task.createByName }}
</span>
<span>
{{ task.createByName }}
</span>
<div
class=
"flex items-center opacity-50"
>
<div
class=
"flex items-center opacity-50"
>
<div
class=
"flex items-center gap-1"
>
<div
class=
"flex items-center gap-1"
>
<span
class=
"icon-[bxs--category-alt] size-[16px]"
></span>
<span
class=
"icon-[bxs--category-alt] size-[16px]"
></span>
流程分类: {{ task.categoryName }}
流程分类: {{ task.categoryName }}
</div>
</div>
<Divider
type=
"vertical"
/>
<Divider
type=
"vertical"
/>
<div
class=
"flex items-center gap-1"
>
<div
class=
"flex items-center gap-1"
>
<span
class=
"icon-[mdi--clock-outline] size-[16px]"
></span>
<span
class=
"icon-[mdi--clock-outline] size-[16px]"
></span>
提交时间: {{ task.createTime }}
提交时间: {{ task.createTime }}
...
@@ -447,154 +195,32 @@ async function handleCopy(text: string) {
...
@@ -447,154 +195,32 @@ async function handleCopy(text: string) {
</div>
</div>
</div>
</div>
</div>
</div>
<Tabs
v-if=
"currentFlowInfo"
class=
"flex-1"
>
<Tabs
v-if=
"currentFlowInfo"
class=
"flex-1"
>
<TabPane
key=
"1"
tab=
"审批详情"
>
<TabPane
key=
"1"
tab=
"审批详情"
>
<ApprovalDetails
<ApprovalDetails
:current-flow-info=
"currentFlowInfo"
:current-flow-info=
"currentFlowInfo"
:iframe-loaded=
"iframeLoaded"
:iframe-height=
"iframeHeight"
:task=
"task"
:task=
"task"
/>
/>
</TabPane>
</TabPane>
<TabPane
key=
"2"
tab=
"审批流程图"
>
<TabPane
key=
"2"
tab=
"审批流程图"
>
<FlowPreview
:instance-id=
"currentFlowInfo.instanceId"
/>
<FlowPreview
:instance-id=
"currentFlowInfo.instanceId"
/>
</TabPane>
</TabPane>
</Tabs>
</Tabs>
</div>
</div>
<!-- 固定底部 -->
<!-- 固定底部 占位高度 -->
<div
class=
"h-[58px]"
></div>
<div
class=
"h-[58px]"
></div>
<
div
<
FlowActions
v-if=
"showFooter"
v-if=
"showFooter"
:class=
"
:type=
"type"
cn(
:task=
"task"
'absolute bottom-0 left-0',
:button-permissions=
"buttonPermissions"
'border-t-solid border-t-[1px]',
@
reload=
"$emit('reload')"
'bg-background w-full p-3',
/>
)
"
>
<div
class=
"flex justify-end"
>
<Space
v-if=
"type === 'myself'"
>
<Button
v-if=
"revocable"
danger
ghost
type=
"primary"
:icon=
"h(RollbackOutlined)"
@
click=
"handleCancel"
>
撤销申请
</Button>
<Button
type=
"primary"
ghost
v-if=
"editableAndRemoveable"
:icon=
"h(EditOutlined)"
@
click=
"handleEdit"
>
重新编辑
</Button>
<Button
v-if=
"editableAndRemoveable"
danger
ghost
type=
"primary"
:icon=
"h(EditOutlined)"
@
click=
"handleRemove"
>
删除
</Button>
</Space>
<Space
v-if=
"type === 'approve'"
>
<Button
type=
"primary"
ghost
:icon=
"h(CheckOutlined)"
@
click=
"handleApproval"
>
通过
</Button>
<Button
v-if=
"buttonPermissions?.termination"
danger
ghost
type=
"primary"
:icon=
"h(ExclamationCircleOutlined)"
@
click=
"handleTermination"
>
终止
</Button>
<Button
v-if=
"buttonPermissions?.back"
danger
ghost
type=
"primary"
:icon=
"h(ArrowLeftOutlined)"
@
click=
"handleRejection"
>
驳回
</Button>
<Dropdown
:get-popup-container=
"getPopupContainer"
placement=
"bottomRight"
>
<
template
#
overlay
>
<Menu>
<MenuItem
v-if=
"buttonPermissions?.trust"
key=
"1"
@
click=
"() => delegationModalApi.open()"
>
<UserOutlined
class=
"mr-2"
/>
委托
</MenuItem>
<MenuItem
v-if=
"buttonPermissions?.transfer"
key=
"2"
@
click=
"() => transferModalApi.open()"
>
<RollbackOutlined
class=
"mr-2"
/>
转办
</MenuItem>
<MenuItem
v-if=
"showMultiActions && buttonPermissions?.addSign"
key=
"3"
@
click=
"() => addSignatureModalApi.open()"
>
<UsergroupAddOutlined
class=
"mr-2"
/>
加签
</MenuItem>
<MenuItem
v-if=
"showMultiActions && buttonPermissions?.subSign"
key=
"4"
@
click=
"() => reductionSignatureModalApi.open()"
>
<UsergroupDeleteOutlined
class=
"mr-2"
/>
减签
</MenuItem>
</Menu>
</
template
>
<Button
v-if=
"showButtonOther"
:icon=
"h(MenuOutlined)"
>
其他
</Button>
</Dropdown>
<ApprovalModal
@
complete=
"$emit('reload')"
/>
<RejectionModal
@
complete=
"$emit('reload')"
/>
<DelegationModal
mode=
"single"
@
finish=
"handleDelegation"
/>
<TransferModal
mode=
"single"
@
finish=
"handleTransfer"
/>
<AddSignatureModal
mode=
"multiple"
@
finish=
"handleAddSignature"
/>
<ReductionSignatureModal
mode=
"multiple"
@
finish=
"handleReductionSignature"
/>
</Space>
<Space
v-if=
"type === 'admin'"
>
<Button
@
click=
"handleFlowInterfere"
>
流程干预
</Button>
<Button
@
click=
"() => updateAssigneeModalApi.open()"
>
修改办理人
</Button>
<FlowInterfereModal
@
complete=
"$emit('reload')"
/>
<UpdateAssigneeModal
mode=
"single"
@
finish=
"handleUpdateAssignee"
/>
</Space>
</div>
</div>
</Card>
</Card>
<slot
v-else
name=
"empty"
>
<slot
v-else
name=
"empty"
>
<Fallback
title=
"点击左侧选择"
/>
<Fallback
title=
"点击左侧选择"
/>
</slot>
</slot>
...
...
apps/web-antd/src/views/workflow/components/approval-timeline-item.vue
View file @
8d4896cf
...
@@ -45,11 +45,8 @@ onMounted(async () => {
...
@@ -45,11 +45,8 @@ onMounted(async () => {
}));
}));
});
});
/**
* 这里无法处理昵称中带,的情况
*/
const
isMultiplePerson
=
computed
(
const
isMultiplePerson
=
computed
(
()
=>
props
.
item
.
approve
Name
?.
split
(
','
).
length
>
1
,
()
=>
props
.
item
.
approve
r
?.
split
(
','
).
length
>
1
,
);
);
</
script
>
</
script
>
...
@@ -87,6 +84,7 @@ const isMultiplePerson = computed(
...
@@ -87,6 +84,7 @@ const isMultiplePerson = computed(
</div>
</div>
<div
:class=
"cn('mt-2 flex flex-wrap gap-2')"
v-if=
"isMultiplePerson"
>
<div
:class=
"cn('mt-2 flex flex-wrap gap-2')"
v-if=
"isMultiplePerson"
>
<!-- 如果昵称中带, 这里的处理是不准确的 -->
<div
<div
:class=
"cn('bg-foreground/5 flex items-center rounded-full', 'p-1')"
:class=
"cn('bg-foreground/5 flex items-center rounded-full', 'p-1')"
v-for=
"(name, index) in item.approveName.split(',')"
v-for=
"(name, index) in item.approveName.split(',')"
...
...
apps/web-antd/src/views/workflow/components/approval-timeline.vue
View file @
8d4896cf
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
type
{
Flow
}
from
'#/api/workflow/instance/model'
;
import
type
{
Flow
}
from
'#/api/workflow/instance/model'
;
import
{
Timeline
}
from
'ant-design-vue'
;
import
{
Empty
,
Timeline
}
from
'ant-design-vue'
;
import
ApprovalTimelineItem
from
'./approval-timeline-item.vue'
;
import
ApprovalTimelineItem
from
'./approval-timeline-item.vue'
;
const
props
=
defineProps
<
{
interface
Props
{
list
:
Flow
[];
list
:
Flow
[];
}
>
();
}
defineProps
<
Props
>
();
</
script
>
</
script
>
<
template
>
<
template
>
<Timeline
v-if=
"props.list.length > 0"
>
<Timeline
v-if=
"list.length > 0"
>
<ApprovalTimelineItem
<ApprovalTimelineItem
v-for=
"item in list"
:key=
"item.id"
:item=
"item"
/>
v-for=
"item in props.list"
:key=
"item.id"
:item=
"item"
/>
</Timeline>
</Timeline>
<Empty
v-else
/>
</
template
>
</
template
>
apps/web-antd/src/views/workflow/components/flow-preview.vue
View file @
8d4896cf
<!-- 流程图预览组件 -->
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
useAppConfig
}
from
'@vben/hooks'
;
import
{
useAppConfig
}
from
'@vben/hooks'
;
import
{
stringify
}
from
'@vben/request'
;
import
{
stringify
}
from
'@vben/request'
;
...
@@ -7,7 +9,14 @@ import { useWarmflowIframe } from './hook';
...
@@ -7,7 +9,14 @@ import { useWarmflowIframe } from './hook';
defineOptions
({
name
:
'FlowPreview'
});
defineOptions
({
name
:
'FlowPreview'
});
const
props
=
defineProps
<
{
instanceId
:
number
}
>
();
const
props
=
defineProps
<
Props
>
();
interface
Props
{
/**
* 流程实例ID
*/
instanceId
:
number
;
}
const
{
clientId
}
=
useAppConfig
(
import
.
meta
.
env
,
import
.
meta
.
env
.
PROD
);
const
{
clientId
}
=
useAppConfig
(
import
.
meta
.
env
,
import
.
meta
.
env
.
PROD
);
...
@@ -21,6 +30,7 @@ const params = {
...
@@ -21,6 +30,7 @@ const params = {
/**
/**
* iframe地址
* iframe地址
* 后端地址 + 固定flow地址拼接
*/
*/
const
url
=
`
${
import
.
meta
.
env
.
VITE_GLOB_API_URL
}
/warm-flow-ui/index.html?
${
stringify
(
params
)}
`
;
const
url
=
`
${
import
.
meta
.
env
.
VITE_GLOB_API_URL
}
/warm-flow-ui/index.html?
${
stringify
(
params
)}
`
;
...
@@ -28,5 +38,9 @@ const { iframeRef } = useWarmflowIframe();
...
@@ -28,5 +38,9 @@ const { iframeRef } = useWarmflowIframe();
</
script
>
</
script
>
<
template
>
<
template
>
<iframe
ref=
"iframeRef"
:src=
"url"
class=
"h-[500px] w-full border"
></iframe>
<iframe
ref=
"iframeRef"
:src=
"url"
class=
"h-[600px] w-full rounded-[6px] border"
></iframe>
</
template
>
</
template
>
apps/web-antd/src/views/workflow/components/hook.ts
View file @
8d4896cf
...
@@ -10,20 +10,26 @@ export function useWarmflowIframe() {
...
@@ -10,20 +10,26 @@ export function useWarmflowIframe() {
const
iframeRef
=
useTemplateRef
<
HTMLIFrameElement
>
(
'iframeRef'
);
const
iframeRef
=
useTemplateRef
<
HTMLIFrameElement
>
(
'iframeRef'
);
const
{
isDark
}
=
usePreferences
();
const
{
isDark
}
=
usePreferences
();
async
function
iframeLoadEvent
()
{
/**
* TODO: 这里可以优化 因为拿不到内部vue的mount状态
*/
await
new
Promise
((
resolve
)
=>
setTimeout
(
resolve
,
500
));
const
theme
=
isDark
.
value
?
'theme-dark'
:
'theme-light'
;
iframeRef
.
value
?.
contentWindow
?.
postMessage
({
type
:
theme
});
}
onMounted
(()
=>
{
onMounted
(()
=>
{
/**
/**
* load只是iframe加载完 而非vue加载完
* load只是iframe加载完 而非vue加载完
*/
*/
iframeRef
.
value
?.
addEventListener
(
'load'
,
async
()
=>
{
iframeRef
.
value
?.
addEventListener
(
'load'
,
iframeLoadEvent
);
/**
* TODO: 这里可以优化 因为拿不到内部vue的mount状态
*/
await
new
Promise
((
resolve
)
=>
setTimeout
(
resolve
,
500
));
const
theme
=
isDark
.
value
?
'theme-dark'
:
'theme-light'
;
iframeRef
.
value
?.
contentWindow
?.
postMessage
({
type
:
theme
});
});
});
});
// onBeforeUnmount(() => {
// iframeRef.value?.removeEventListener('load', iframeLoadEvent);
// });
// 监听主题切换 通知iframe切换
// 监听主题切换 通知iframe切换
watch
(
isDark
,
(
dark
)
=>
{
watch
(
isDark
,
(
dark
)
=>
{
if
(
!
iframeRef
.
value
)
{
if
(
!
iframeRef
.
value
)
{
...
...
apps/web-antd/src/views/workflow/components/type.d.ts
0 → 100644
View file @
8d4896cf
export
{};
/**
* myself 我发起的
* readonly 只读 只用于查看
* approve 审批(我的待办)
* admin 流程监控 - 待办任务使用
*/
export
type
ApprovalType
=
'admin'
|
'approve'
|
'myself'
|
'readonly'
;
apps/web-antd/src/views/workflow/processInstance/index.vue
View file @
8d4896cf
...
@@ -149,9 +149,10 @@ const [InstanceVariableModal, instanceVariableModalApi] = useVbenModal({
...
@@ -149,9 +149,10 @@ const [InstanceVariableModal, instanceVariableModalApi] = useVbenModal({
connectedComponent
:
instanceVariableModal
,
connectedComponent
:
instanceVariableModal
,
});
});
function
handleVariable
(
row
:
Recordable
<
any
>
)
{
function
handleVariable
(
row
:
Recordable
<
any
>
)
{
instanceVariableModalApi
.
setData
({
record
:
row
.
variable
});
instanceVariableModalApi
.
setData
({
instanceId
:
row
.
id
});
instanceVariableModalApi
.
open
();
instanceVariableModalApi
.
open
();
}
}
const
[
FlowInfoModal
,
flowInfoModalApi
]
=
useVbenModal
({
const
[
FlowInfoModal
,
flowInfoModalApi
]
=
useVbenModal
({
connectedComponent
:
flowInfoModal
,
connectedComponent
:
flowInfoModal
,
});
});
...
...
apps/web-antd/src/views/workflow/processInstance/instance-invalid-modal.vue
View file @
8d4896cf
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts
x
"
>
import
{
useVbenModal
}
from
'@vben/common-ui
'
;
import
{
ref
}
from
'vue
'
;
import
{
cloneDeep
}
from
'lodash-es'
;
import
{
JsonPreview
,
useVbenModal
}
from
'@vben/common-ui'
;
import
{
cn
,
getPopupContainer
}
from
'@vben/utils'
;
import
{
message
,
Modal
,
Tag
}
from
'ant-design-vue'
;
import
{
useVbenForm
}
from
'#/adapter/form'
;
import
{
useVbenForm
}
from
'#/adapter/form'
;
import
{
workflowInstanceInvalid
}
from
'#/api/workflow/instance/index
'
;
import
{
instanceVariable
,
updateFlowVariable
}
from
'#/api/workflow/instance
'
;
const
emit
=
defineEmits
<
{
reload
:
[]
}
>
();
interface
ModalData
{
/**
* 变量 json字符串
*/
record
:
string
;
instanceId
:
string
;
}
const
data
=
ref
({});
const
[
BasicModal
,
modalApi
]
=
useVbenModal
({
const
[
BasicModal
,
modalApi
]
=
useVbenModal
({
onConfirm
:
handleSubmit
,
title
:
'流程变量'
,
onCancel
:
handleCancel
,
fullscreenButton
:
false
,
fullscreenButton
:
false
,
title
:
'作废原因'
,
footer
:
false
,
onOpenChange
:
async
(
visible
)
=>
{
if
(
!
visible
)
{
data
.
value
=
{};
return
null
;
}
modalApi
.
modalLoading
(
true
);
await
loadData
();
modalApi
.
modalLoading
(
false
);
},
});
});
const
[
BasicForm
,
formApi
]
=
useVbenForm
({
const
fieldTypeColors
=
{
string
:
'cyan'
,
number
:
'blue'
,
boolean
:
'orange'
,
object
:
'purple'
,
};
function
getFieldTypeColor
(
fieldType
:
string
)
{
return
(
fieldTypeColors
[
fieldType
as
keyof
typeof
fieldTypeColors
]
??
'default'
);
}
async
function
loadData
()
{
const
{
instanceId
}
=
modalApi
.
getData
()
as
ModalData
;
const
resp
=
await
instanceVariable
(
instanceId
);
const
jsonObj
=
JSON
.
parse
(
resp
.
variable
);
data
.
value
=
jsonObj
;
// 表单
const
objEntry
=
Object
.
entries
(
jsonObj
);
interface
OptionsType
{
label
:
string
;
value
:
string
;
fieldType
:
string
;
}
formApi
.
updateSchema
([
{
fieldName
:
'key'
,
componentProps
:
{
options
:
objEntry
.
map
(
([
key
,
value
])
=>
({
label
:
key
,
value
:
key
,
fieldType
:
typeof
value
,
})
as
OptionsType
,
),
},
renderComponentContent
:
()
=>
({
option
:
(
option
:
OptionsType
)
=>
(
<
div
>
{
option
.
label
}
<
Tag
class
=
"ml-1"
color
=
{
getFieldTypeColor
(
option
.
fieldType
)}
>
{
option
.
fieldType
}
<
/Tag
>
<
/div
>
),
}),
},
]);
}
const
[
Form
,
formApi
]
=
useVbenForm
({
commonConfig
:
{
commonConfig
:
{
formItemClass
:
'col-span-2'
,
componentProps
:
{
componentProps
:
{
class
:
'w-full'
,
class
:
'w-full'
,
allowClear
:
true
,
},
},
labelWidth
:
80
,
labelWidth
:
80
,
},
},
layout
:
'vertical'
,
schema
:
[
schema
:
[
{
{
fieldName
:
'comment'
,
fieldName
:
'key'
,
label
:
'作废原因'
,
component
:
'Select'
,
component
:
'Textarea'
,
label
:
'变量名称'
,
rules
:
'selectRequired'
,
componentProps
:
{
getPopupContainer
,
},
},
{
fieldName
:
'valueType'
,
component
:
'Select'
,
label
:
'变量类型'
,
rules
:
'selectRequired'
,
componentProps
:
{
getPopupContainer
,
options
:
[
{
label
:
'string'
,
value
:
'string'
,
},
{
label
:
'boolean | number | object (使用JSON.parse)'
,
value
:
'object'
,
},
],
},
},
{
fieldName
:
'value'
,
component
:
'Input'
,
label
:
'变量值'
,
rules
:
'required'
,
},
},
],
],
showDefaultActions
:
false
,
resetButtonOptions
:
{
wrapperClass
:
'grid-cols-2'
,
show
:
false
,
},
submitButtonOptions
:
{
content
:
'修改'
,
},
handleSubmit
:
async
(
values
)
=>
{
console
.
log
(
values
);
Modal
.
confirm
({
title
:
'修改流程变量'
,
content
:
'确认修改流程变量吗?'
,
centered
:
true
,
okButtonProps
:
{
danger
:
true
,
},
onOk
:
async
()
=>
{
await
handleSubmit
(
values
);
},
});
},
});
});
async
function
handleCancel
()
{
async
function
handleSubmit
(
values
:
any
)
{
modalApi
.
close
();
await
formApi
.
resetForm
();
}
async
function
handleSubmit
()
{
try
{
try
{
modalApi
.
modalLoading
(
true
);
modalApi
.
lock
(
true
);
const
{
valid
}
=
await
formApi
.
validate
();
if
(
!
valid
)
{
const
{
instanceId
}
=
modalApi
.
getData
()
as
ModalData
;
return
;
let
transformValue
=
values
.
value
;
if
(
values
.
valueType
!==
'string'
)
{
try
{
transformValue
=
JSON
.
parse
(
values
.
value
);
}
catch
(
error
)
{
console
.
error
(
error
);
if
(
error
instanceof
Error
)
{
message
.
error
(
error
.
message
);
}
throw
error
;
}
}
}
const
data
=
cloneDeep
(
await
formApi
.
getValues
());
data
.
id
=
modalApi
.
getData
().
id
;
// 修改
await
workflowInstanceInvalid
(
data
as
any
);
const
requestData
=
{
emit
(
'reload'
);
instanceId
,
handleCancel
();
key
:
values
.
key
,
value
:
transformValue
,
};
await
updateFlowVariable
(
requestData
);
await
formApi
.
resetForm
();
// 查询修改后的
const
resp
=
await
instanceVariable
(
instanceId
);
const
jsonObj
=
JSON
.
parse
(
resp
.
variable
);
data
.
value
=
jsonObj
;
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
error
(
error
);
console
.
error
(
error
);
}
finally
{
}
finally
{
modalApi
.
modalLoading
(
false
);
modalApi
.
lock
(
false
);
}
}
}
}
</
script
>
</
script
>
<
template
>
<
template
>
<BasicModal>
<BasicModal>
<BasicForm
/>
<div
:class=
"cn('min-h-[400px] overflow-y-auto border', 'rounded-[4px] p-2')"
>
<JsonPreview
:data=
"data"
/>
</div>
<!--
<div
class=
"mt-2 break-all text-sm font-medium text-orange-500"
>
需要支持变量类型需要更改后端代码(原版只支持string类型)
<div>
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowVariableBo.java
</div>
将value的类型改为Object才能使用
</div>
-->
<Form
class=
"mt-2"
/>
</BasicModal>
</BasicModal>
</
template
>
</
template
>
packages/@core/ui-kit/menu-ui/src/components/menu.vue
View file @
8d4896cf
...
@@ -495,7 +495,7 @@ $namespace: vben;
...
@@ -495,7 +495,7 @@ $namespace: vben;
&
.is-rounded
{
&
.is-rounded
{
--menu-item-margin-x
:
8px
;
--menu-item-margin-x
:
8px
;
--menu-item-collapse-margin-x
:
6px
;
--menu-item-collapse-margin-x
:
6px
;
--menu-item-radius
:
8
px
;
--menu-item-radius
:
6
px
;
}
}
&
.is-horizontal
:not
(
.is-rounded
)
{
&
.is-horizontal
:not
(
.is-rounded
)
{
...
...
packages/effects/access/src/directive.ts
View file @
8d4896cf
...
@@ -12,15 +12,13 @@ function isAccessible(
...
@@ -12,15 +12,13 @@ function isAccessible(
el
:
Element
,
el
:
Element
,
binding
:
DirectiveBinding
<
string
|
string
[]
>
,
binding
:
DirectiveBinding
<
string
|
string
[]
>
,
)
{
)
{
const
{
accessMode
,
hasAccessByCodes
,
hasAccessByRoles
}
=
useAccess
();
const
{
hasAccessByCodes
,
hasAccessByRoles
}
=
useAccess
();
const
value
=
binding
.
value
;
const
value
=
binding
.
value
;
if
(
!
value
)
return
;
if
(
!
value
)
return
;
const
authMethod
=
const
authMethod
=
accessMode
.
value
===
'frontend'
&&
binding
.
arg
===
'role'
binding
.
arg
===
'role'
?
hasAccessByRoles
:
hasAccessByCodes
;
?
hasAccessByRoles
:
hasAccessByCodes
;
const
values
=
Array
.
isArray
(
value
)
?
value
:
[
value
];
const
values
=
Array
.
isArray
(
value
)
?
value
:
[
value
];
...
...
packages/effects/access/src/use-access.ts
View file @
8d4896cf
...
@@ -17,6 +17,10 @@ function useAccess() {
...
@@ -17,6 +17,10 @@ function useAccess() {
*/
*/
function
hasAccessByRoles
(
roles
:
string
[])
{
function
hasAccessByRoles
(
roles
:
string
[])
{
const
userRoleSet
=
new
Set
(
userStore
.
userRoles
);
const
userRoleSet
=
new
Set
(
userStore
.
userRoles
);
// 超管的角色
if
(
userRoleSet
.
has
(
'superadmin'
))
{
return
true
;
}
const
intersection
=
roles
.
filter
((
item
)
=>
userRoleSet
.
has
(
item
));
const
intersection
=
roles
.
filter
((
item
)
=>
userRoleSet
.
has
(
item
));
return
intersection
.
length
>
0
;
return
intersection
.
length
>
0
;
}
}
...
...
packages/effects/plugins/src/vxe-table/style.css
View file @
8d4896cf
...
@@ -133,3 +133,8 @@ TODO: 最后一条数据hover/check仍会显示边框
...
@@ -133,3 +133,8 @@ TODO: 最后一条数据hover/check仍会显示边框
border-radius
:
var
(
--vxe-ui-table-border-radius
)
border-radius
:
var
(
--vxe-ui-table-border-radius
)
var
(
--vxe-ui-table-border-radius
)
0
0
;
var
(
--vxe-ui-table-border-radius
)
0
0
;
}
}
/* modal/drawer里使用列配置 重置列弹窗被遮挡 */
.vxe-dynamics--modal
>
.vxe-modal--wrapper
{
z-index
:
calc
(
var
(
--popup-z-index
)
+
1
)
!important
;
}
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