Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
Z
zrch-risk-39
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
Administrator
zrch-risk-39
Commits
4c79503d
提交
4c79503d
authored
4月 08, 2026
作者:
liuluyu
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
计划管理样式更新
上级
6a232359
隐藏空白字符变更
内嵌
并排
正在显示
7 个修改的文件
包含
2245 行增加
和
462 行删除
+2245
-462
planFormStore.ts
zrch-risk-client-39/src/store/modules/planFormStore.ts
+65
-54
StPlanMan.data.ts
zrch-risk-client-39/src/views/project/plan/StPlanMan.data.ts
+223
-68
StPlanManList.vue
zrch-risk-client-39/src/views/project/plan/StPlanManList.vue
+538
-140
StPlanExcuteForm.vue
...39/src/views/project/plan/components/StPlanExcuteForm.vue
+580
-52
StPlanManFlowModal.vue
.../src/views/project/plan/components/StPlanManFlowModal.vue
+200
-10
StPlanManForm.vue
...nt-39/src/views/project/plan/components/StPlanManForm.vue
+243
-46
StPlanManModal.vue
...t-39/src/views/project/plan/components/StPlanManModal.vue
+396
-92
没有找到文件。
zrch-risk-client-39/src/store/modules/planFormStore.ts
浏览文件 @
4c79503d
/**
* 计划执行表单状态管理
* 用于在不同页面之间共享表单保存方法
*/
import
{
defineStore
}
from
'pinia'
;
import
{
ref
}
from
'vue'
;
import
{
re
active
,
re
f
}
from
'vue'
;
export
const
usePlanFormStore
=
defineStore
(
'planForm'
,
()
=>
{
// 存储表单的 ref 回调
const
submitCallback
=
ref
<
()
=>
Promise
<
any
>
|
null
>
(
null
);
const
formData
=
ref
<
any
>
(
null
);
export
const
usePlanFormStore
=
defineStore
(
'planFormStore'
,
()
=>
{
// 表单提交回调函数
const
submitCallback
=
ref
<
(()
=>
Promise
<
void
>
)
|
null
>
(
null
);
/**
* 注册表单提交回调(在 StPlanExcuteForm 中调用)
* @param callback - 表单的 submitForm 方法
*/
const
registerSubmitCallback
=
(
callback
:
()
=>
Promise
<
any
>
)
=>
{
// 表单数据缓存
const
formDataCache
=
reactive
({
id
:
''
,
executeStatus
:
''
,
actualStartTime
:
null
,
actualEndTime
:
null
,
executeEcord
:
''
,
attachments
:
[]
as
any
[],
});
// 表单加载状态
const
formLoadingState
=
reactive
({
isLoading
:
false
,
isError
:
false
,
errorMessage
:
''
,
lastLoadedId
:
''
,
});
// 注册表单提交回调
const
registerSubmitCallback
=
(
callback
:
()
=>
Promise
<
void
>
)
=>
{
submitCallback
.
value
=
callback
;
console
.
log
(
'[PlanFormStore] 已注册表单保存回调'
);
};
/**
* 注册表单数据(用于跨页面访问)
* @param data - 表单数据
*/
const
setFormData
=
(
data
:
any
)
=>
{
formData
.
value
=
data
;
// 执行表单提交
const
executeSubmit
=
async
()
=>
{
if
(
submitCallback
.
value
)
{
try
{
formLoadingState
.
isError
=
false
;
formLoadingState
.
errorMessage
=
''
;
await
submitCallback
.
value
();
}
catch
(
error
:
any
)
{
formLoadingState
.
isError
=
true
;
formLoadingState
.
errorMessage
=
error
?.
message
||
'表单提交失败'
;
throw
error
;
}
}
};
/**
* 获取表单数据
*/
const
getFormData
=
()
=>
{
return
formData
.
value
;
// 更新表单数据缓存
const
updateFormDataCache
=
(
data
:
any
)
=>
{
Object
.
assign
(
formDataCache
,
data
);
};
/**
* 执行表单保存(在 TodoIndex 中调用)
*/
const
submitPlanForm
=
async
()
=>
{
if
(
!
submitCallback
.
value
)
{
console
.
warn
(
'[PlanFormStore] 未注册表单保存回调'
);
return
null
;
}
// 清除表单缓存
const
clearFormCache
=
()
=>
{
Object
.
assign
(
formDataCache
,
{
id
:
''
,
executeStatus
:
''
,
actualStartTime
:
null
,
actualEndTime
:
null
,
executeEcord
:
''
,
attachments
:
[],
});
formLoadingState
.
lastLoadedId
=
''
;
};
try
{
console
.
log
(
'[PlanFormStore] 开始执行表单保存...'
);
const
result
=
await
submitCallback
.
value
();
console
.
log
(
'[PlanFormStore] 表单保存成功:'
,
result
);
return
result
;
}
catch
(
error
)
{
console
.
error
(
'[PlanFormStore] 表单保存失败:'
,
error
);
throw
error
;
}
// 设置加载状态
const
setLoadingState
=
(
isLoading
:
boolean
)
=>
{
formLoadingState
.
isLoading
=
isLoading
;
};
/**
* 清空回调(可选)
*/
const
clearCallback
=
()
=>
{
submitCallback
.
value
=
null
;
console
.
log
(
'[PlanFormStore] 已清空表单保存回调'
);
// 设置最后加载的ID
const
setLastLoadedId
=
(
id
:
string
)
=>
{
formLoadingState
.
lastLoadedId
=
id
;
};
return
{
submitCallback
,
formData
,
formDataCache
,
formLoadingState
,
registerSubmitCallback
,
setFormData
,
getFormData
,
submitPlanForm
,
clearCallback
,
executeSubmit
,
updateFormDataCache
,
clearFormCache
,
setLoadingState
,
setLastLoadedId
,
};
});
zrch-risk-client-39/src/views/project/plan/StPlanMan.data.ts
浏览文件 @
4c79503d
import
{
message
}
from
'ant-design-vue'
;
import
{
BasicColumn
,
FormSchema
}
from
'/@/components/Table'
;
import
{
render
}
from
'/@/utils/common/renderUtils'
;
//列表数据
// ==================== 执行规则选项 ====================
export
const
exeRuleOptions
=
[
{
value
:
1
,
label
:
'一次性'
},
{
value
:
2
,
label
:
'周期执行'
},
{
value
:
3
,
label
:
'事件触发'
},
];
// ==================== 执行周期选项 ====================
export
const
exePeriodOptions
=
[
{
value
:
'daily'
,
label
:
'每日'
},
{
value
:
'weekly'
,
label
:
'每周'
},
{
value
:
'monthly'
,
label
:
'每月'
},
{
value
:
'quarterly'
,
label
:
'每季度'
},
{
value
:
'halfyear'
,
label
:
'每半年'
},
{
value
:
'yearly'
,
label
:
'每年'
},
];
// ==================== 优先级选项 ====================
export
const
priorityOptions
=
[
{
value
:
'1'
,
label
:
'高'
},
{
value
:
'2'
,
label
:
'中'
},
{
value
:
'3'
,
label
:
'低'
},
];
// ==================== 计划状态选项 ====================
export
const
planStatusOptions
=
[
{
value
:
'0'
,
label
:
'草稿'
},
{
value
:
'1'
,
label
:
'审批中'
},
{
value
:
'2'
,
label
:
'已通过'
},
{
value
:
'3'
,
label
:
'已拒绝'
},
{
value
:
'4'
,
label
:
'执行中'
},
{
value
:
'5'
,
label
:
'已完成'
},
{
value
:
'6'
,
label
:
'已作废'
},
];
// 列表数据
export
const
columns
:
BasicColumn
[]
=
[
{
title
:
'计划名称'
,
align
:
'left'
,
dataIndex
:
'projectName'
,
width
:
20
0
,
width
:
18
0
,
ellipsis
:
true
,
},
{
title
:
'类型'
,
title
:
'
计划
类型'
,
align
:
'center'
,
dataIndex
:
'projectTypeName'
,
width
:
120
,
ellipsis
:
true
,
},
//
{
//
title: '执行部门',
//
align: 'center',
//
dataIndex: 'execDepName',
// width: 15
0,
//
ellipsis: true,
//
},
{
title
:
'执行部门'
,
align
:
'center'
,
dataIndex
:
'execDepName'
,
width
:
14
0
,
ellipsis
:
true
,
},
{
title
:
'负责人'
,
align
:
'center'
,
...
...
@@ -32,19 +68,39 @@ export const columns: BasicColumn[] = [
ellipsis
:
true
,
},
{
title
:
'
计划开始日期
'
,
title
:
'
优先级
'
,
align
:
'center'
,
dataIndex
:
'p
lanStartDate
'
,
width
:
13
0
,
dataIndex
:
'p
riority
'
,
width
:
8
0
,
ellipsis
:
true
,
customRender
:
({
text
})
=>
{
return
!
text
?
'-'
:
text
.
length
>
10
?
text
.
substr
(
0
,
10
)
:
text
;
const
priorityMap
=
{
'1'
:
'高'
,
'2'
:
'中'
,
'3'
:
'低'
,
};
return
priorityMap
[
text
]
||
'-'
;
},
},
{
title
:
'
计划结束日期
'
,
title
:
'
执行规则
'
,
align
:
'center'
,
dataIndex
:
'planEndDate'
,
dataIndex
:
'exeRule'
,
width
:
100
,
ellipsis
:
true
,
customRender
:
({
text
})
=>
{
const
ruleMap
=
{
1
:
'事件触发'
,
2
:
'周期执行'
,
3
:
'一次性执行'
,
};
return
ruleMap
[
text
]
||
'-'
;
},
},
{
title
:
'计划开始日期'
,
align
:
'center'
,
dataIndex
:
'planStartDate'
,
width
:
130
,
ellipsis
:
true
,
customRender
:
({
text
})
=>
{
...
...
@@ -52,22 +108,17 @@ export const columns: BasicColumn[] = [
},
},
{
title
:
'
执行规则
'
,
title
:
'
计划结束日期
'
,
align
:
'center'
,
dataIndex
:
'
exeRul
e'
,
width
:
1
0
0
,
dataIndex
:
'
planEndDat
e'
,
width
:
1
3
0
,
ellipsis
:
true
,
customRender
:
({
text
})
=>
{
const
ruleMap
=
{
1
:
'每发生'
,
2
:
'周期性'
,
3
:
'一次性'
,
};
return
ruleMap
[
text
]
||
'-'
;
return
!
text
?
'-'
:
text
.
length
>
10
?
text
.
substr
(
0
,
10
)
:
text
;
},
},
{
title
:
'状态'
,
title
:
'
计划
状态'
,
align
:
'center'
,
dataIndex
:
'statusName'
,
width
:
100
,
...
...
@@ -91,7 +142,8 @@ export const columns: BasicColumn[] = [
ellipsis
:
false
,
},
];
//查询数据
// 查询表单数据
export
const
searchFormSchema
:
FormSchema
[]
=
[
{
label
:
'计划名称'
,
...
...
@@ -100,7 +152,7 @@ export const searchFormSchema: FormSchema[] = [
colProps
:
{
span
:
6
},
},
{
label
:
'类型'
,
label
:
'
计划
类型'
,
field
:
'projectType'
,
component
:
'Select'
,
colProps
:
{
span
:
6
},
...
...
@@ -112,12 +164,58 @@ export const searchFormSchema: FormSchema[] = [
{
label
:
'执行部门'
,
field
:
'execDepCode'
,
component
:
'
Inpu
t'
,
component
:
'
Selec
t'
,
colProps
:
{
span
:
6
},
componentProps
:
{
allowClear
:
true
,
placeholder
:
'请选择执行部门'
,
},
},
{
label
:
'计划状态'
,
field
:
'status'
,
component
:
'Select'
,
colProps
:
{
span
:
6
},
componentProps
:
{
allowClear
:
true
,
placeholder
:
'请选择状态'
,
options
:
planStatusOptions
,
},
},
{
label
:
'计划日期'
,
field
:
'planDateRange'
,
component
:
'RangePicker'
,
colProps
:
{
span
:
8
},
componentProps
:
{
placeholder
:
[
'开始日期'
,
'结束日期'
],
},
},
{
label
:
'优先级'
,
field
:
'priority'
,
component
:
'Select'
,
colProps
:
{
span
:
5
},
componentProps
:
{
allowClear
:
true
,
placeholder
:
'请选择优先级'
,
options
:
priorityOptions
,
},
},
{
label
:
'执行规则'
,
field
:
'exeRule'
,
component
:
'Select'
,
colProps
:
{
span
:
5
},
componentProps
:
{
allowClear
:
true
,
placeholder
:
'请选择执行规则'
,
options
:
exeRuleOptions
,
},
},
];
//表单数据
//
表单数据
export
const
formSchema
:
FormSchema
[]
=
[
{
label
:
'计划名称'
,
...
...
@@ -126,11 +224,11 @@ export const formSchema: FormSchema[] = [
colProps
:
{
lg
:
12
},
itemProps
:
{
labelCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
6
}
},
wrapperCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
18
}
}
},
dynamicRules
:
({
model
,
schema
})
=>
{
return
[{
required
:
true
,
message
:
'请输入
项目
名称!'
}];
return
[{
required
:
true
,
message
:
'请输入
计划
名称!'
}];
},
},
{
label
:
'类型'
,
label
:
'
计划
类型'
,
field
:
'projectType'
,
component
:
'JCategorySelect'
,
componentProps
:
{
...
...
@@ -140,10 +238,29 @@ export const formSchema: FormSchema[] = [
colProps
:
{
lg
:
12
},
itemProps
:
{
labelCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
6
}
},
wrapperCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
18
}
}
},
dynamicRules
:
({
model
,
schema
})
=>
{
return
[{
required
:
true
,
message
:
'请选择项目类型!'
}];
return
[{
required
:
true
,
message
:
'请选择计划类型!'
}];
},
},
{
label
:
'执行部门'
,
field
:
'execDepCode'
,
component
:
'JSelectDept'
,
colProps
:
{
lg
:
12
},
itemProps
:
{
labelCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
6
}
},
wrapperCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
18
}
}
},
dynamicRules
:
({
model
,
schema
})
=>
{
return
[{
required
:
false
,
message
:
'请选择执行部门!'
}];
},
},
{
label
:
'负责人'
,
field
:
'headId'
,
component
:
'JSearchSelectDuty'
,
colProps
:
{
lg
:
12
},
itemProps
:
{
labelCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
6
}
},
wrapperCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
18
}
}
},
dynamicRules
:
({
model
,
schema
})
=>
{
return
[{
required
:
false
,
message
:
'请选择负责人!'
}];
},
},
{
label
:
'计划开始日期'
,
field
:
'planStartDate'
,
...
...
@@ -151,7 +268,7 @@ export const formSchema: FormSchema[] = [
colProps
:
{
lg
:
12
},
itemProps
:
{
labelCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
6
}
},
wrapperCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
18
}
}
},
dynamicRules
:
({
model
,
schema
})
=>
{
return
[{
required
:
fals
e
,
message
:
'请选择计划开始日期!'
}];
return
[{
required
:
tru
e
,
message
:
'请选择计划开始日期!'
}];
},
},
{
...
...
@@ -161,26 +278,81 @@ export const formSchema: FormSchema[] = [
colProps
:
{
lg
:
12
},
itemProps
:
{
labelCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
6
}
},
wrapperCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
18
}
}
},
dynamicRules
:
({
model
,
schema
})
=>
{
return
[{
required
:
fals
e
,
message
:
'请选择计划结束日期!'
}];
return
[{
required
:
tru
e
,
message
:
'请选择计划结束日期!'
}];
},
},
{
label
:
'依据'
,
field
:
'planBasis'
,
component
:
'Input'
,
// 使用基础组件类型
slot
:
'planBasis'
,
itemProps
:
{
labelCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
3
}
},
wrapperCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
21
}
},
label
:
'优先级'
,
field
:
'priority'
,
component
:
'Select'
,
colProps
:
{
lg
:
12
},
itemProps
:
{
labelCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
6
}
},
wrapperCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
18
}
}
},
componentProps
:
{
options
:
priorityOptions
,
placeholder
:
'请选择优先级'
,
},
},
{
label
:
'要求'
,
field
:
'planRequest'
,
component
:
'InputTextArea'
,
label
:
'执行规则'
,
field
:
'exeRule'
,
component
:
'RadioButtonGroup'
,
colProps
:
{
lg
:
12
},
itemProps
:
{
labelCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
6
}
},
wrapperCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
18
}
}
},
componentProps
:
{
options
:
exeRuleOptions
,
},
dynamicRules
:
({
model
,
schema
})
=>
{
return
[{
required
:
true
,
message
:
'请选择执行规则!'
}];
},
},
{
label
:
'触发事件名称'
,
field
:
'triggerEventName'
,
component
:
'Input'
,
colProps
:
{
lg
:
24
},
itemProps
:
{
labelCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
3
}
},
wrapperCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
21
}
}
},
dynamicShow
:
({
model
})
=>
{
return
model
.
exeRule
===
1
;
},
dynamicRules
:
({
model
,
schema
})
=>
{
return
model
.
exeRule
===
1
?
[{
required
:
true
,
message
:
'请填写触发事件名称!'
}]
:
[];
},
},
{
label
:
'执行周期'
,
field
:
'exePeriod'
,
component
:
'Select'
,
colProps
:
{
lg
:
12
},
itemProps
:
{
labelCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
6
}
},
wrapperCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
18
}
}
},
componentProps
:
{
options
:
exePeriodOptions
,
placeholder
:
'请选择执行周期'
,
},
dynamicShow
:
({
model
})
=>
{
return
model
.
exeRule
===
2
;
},
dynamicRules
:
({
model
,
schema
})
=>
{
return
[{
required
:
false
,
message
:
'请输入要求!'
}];
return
model
.
exeRule
===
2
?
[{
required
:
true
,
message
:
'请选择执行周期!'
}]
:
[];
},
},
{
label
:
'首次执行日期'
,
field
:
'firstExecDate'
,
component
:
'DatePicker'
,
colProps
:
{
lg
:
12
},
itemProps
:
{
labelCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
6
}
},
wrapperCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
18
}
}
},
dynamicShow
:
({
model
})
=>
{
return
model
.
exeRule
===
2
;
},
},
{
label
:
'计划依据'
,
field
:
'planBasis'
,
component
:
'Input'
,
slot
:
'planBasis'
,
itemProps
:
{
labelCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
3
}
},
wrapperCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
21
}
},
},
},
{
...
...
@@ -193,30 +365,13 @@ export const formSchema: FormSchema[] = [
return
[{
required
:
true
,
message
:
'请输入交付物!'
}];
},
},
// {
// label: '审核类型',
// field: 'executeType',
// component: 'Select',
// componentProps: {
// options: [
// { label: '流程A', value: 1 },
// { label: '流程B', value: 2 },
// { label: '流程C', value: 3 },
// ],
// },
// colProps: { lg: 12 },
// itemProps: { labelCol: { xs: { span: 24 }, sm: { span: 6 } }, wrapperCol: { xs: { span: 24 }, sm: { span: 18 } } },
// dynamicRules: ({ model, schema }) => {
// return [{ required: false, message: '请选择执行规则!' }];
// },
// },
{
label
:
'描述'
,
{
label
:
'备注说明'
,
field
:
'projectDesc'
,
component
:
'InputTextArea'
,
itemProps
:
{
labelCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
3
}
},
wrapperCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
21
}
}
},
dynamicRules
:
({
model
,
schema
})
=>
{
return
[{
required
:
false
,
message
:
'请输入
项目描述
!'
}];
return
[{
required
:
false
,
message
:
'请输入
备注说明
!'
}];
},
},
{
...
...
@@ -233,7 +388,7 @@ export const formSchema: FormSchema[] = [
itemProps
:
{
labelCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
3
}
},
wrapperCol
:
{
xs
:
{
span
:
24
},
sm
:
{
span
:
21
}
}
},
show
:
false
,
},
//
TODO 主键隐藏字段,目前写死为ID
//
主键隐藏字段
{
label
:
''
,
field
:
'id'
,
...
...
zrch-risk-client-39/src/views/project/plan/StPlanManList.vue
浏览文件 @
4c79503d
<
template
>
<div>
<div
class=
"jeecg-basic-table-form-container"
@
keyup
.
enter=
"searchQuery"
>
<a-form
:model=
"queryParam"
:label-col=
"labelCol"
:wrapper-col=
"wrapperCol"
>
<a-row
:gutter=
"24"
>
<a-col
:lg=
"6"
>
<a-form-item
:label=
"searchFormSchema[0].label"
>
<JInput
placeholder=
"请输入"
v-model:value=
"queryParam[searchFormSchema[0].field]"
/>
</a-form-item>
</a-col>
<a-col
:lg=
"6"
>
<a-form-item
:label=
"searchFormSchema[1].label"
>
<JSearchSelect
placeholder=
"请输入"
v-model:value=
"queryParam[searchFormSchema[1].field]"
dict=
"projecttype"
/>
</a-form-item>
</a-col>
<!--
<a-col
:lg=
"6"
>
<a-form-item
:label=
"searchFormSchema[2].label"
>
<JSelectDept
placeholder=
"请输入"
v-model:value=
"jSelectDeptVal"
:multiple=
"false"
@
change=
"updateJSelectDept"
/>
</a-form-item>
</a-col>
-->
<a-col
:lg=
"6"
>
<a-form-item>
<a-space
:size=
"5"
>
<a-button
type=
"primary"
preIcon=
"ant-design:search-outlined"
@
click=
"searchQuery"
>
查询
</a-button>
<a-button
type=
"primary"
preIcon=
"ant-design:reload-outlined"
@
click=
"searchReset"
>
重置
</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
<!--引用表格-->
<BasicTable
@
register=
"registerTable"
:rowSelection=
"rowSelection"
>
<!--插槽:table标题-->
<template
#
tableTitle
>
<a-button
type=
"primary"
@
click=
"handleAdd"
preIcon=
"ant-design:plus-outlined"
>
新建
</a-button>
<a-button
v-show=
"showUpBtn"
type=
"primary"
preIcon=
"ant-design:export-outlined"
@
click=
"onExportXls"
>
导出
</a-button>
<j-upload-button
v-show=
"showUpBtn"
type=
"primary"
preIcon=
"ant-design:import-outlined"
@
click=
"onImportXls"
>
导入
</j-upload-button>
<a-dropdown
v-if=
"selectedRowKeys.length > 0 && showUpBtn"
>
<template
#
overlay
>
<a-menu>
<a-menu-item
key=
"1"
@
click=
"batchHandleDelete"
>
<Icon
icon=
"ant-design:delete-outlined"
/>
删除
</a-menu-item>
</a-menu>
</
template
>
<a-button
>
批量操作
<Icon
icon=
"mdi:chevron-down"
/>
</a-button>
</a-dropdown>
</template>
<!--操作栏-->
<
template
#
action=
"{ record }"
>
<TableAction
:actions=
"getTableAction(record)"
:dropDownActions=
"getDropDownAction(record)"
/>
</
template
>
<!--字段回显插槽-->
<
template
#
htmlSlot=
"{ text }"
>
<div
v-html=
"text"
></div>
</
template
>
<
template
#
fileSlot=
"{ text }"
>
<span
v-if=
"!text"
style=
"font-size: 12px; font-style: italic"
>
无文件
</span>
<a-button
v-else
:ghost=
"true"
type=
"primary"
preIcon=
"ant-design:download-outlined"
size=
"small"
@
click=
"downloadFile(text)"
>
下载
</a-button
>
</
template
>
</BasicTable>
<div
class=
"plan-management-page"
>
<!-- 页面头部区域 -->
<!--
<div
class=
"page-header"
>
<div
class=
"header-content"
>
<div
class=
"header-left"
>
<h1
class=
"page-title"
>
计划编制管理
</h1>
<p
class=
"page-desc"
>
统一管理和追踪所有业务计划的编制与审批流程
</p>
</div>
<div
class=
"header-stats"
>
<div
class=
"stat-item"
>
<span
class=
"stat-value"
>
--
</span>
<span
class=
"stat-label"
>
计划总数
</span>
</div>
<div
class=
"stat-item warning"
>
<span
class=
"stat-value"
>
--
</span>
<span
class=
"stat-label"
>
待处理
</span>
</div>
<div
class=
"stat-item success"
>
<span
class=
"stat-value"
>
--
</span>
<span
class=
"stat-label"
>
已完成
</span>
</div>
</div>
</div>
</div>
-->
<!-- 主内容区 -->
<div
class=
"main-content"
>
<!-- 搜索区域 -->
<div
class=
"search-section"
@
keyup
.
enter=
"searchQuery"
>
<div
class=
"section-header"
>
<span
class=
"section-title"
>
筛选条件
</span>
</div>
<a-form
:model=
"queryParam"
:label-col=
"labelCol"
:wrapper-col=
"wrapperCol"
class=
"search-form"
>
<a-row
:gutter=
"16"
>
<a-col
:xl=
"5"
:lg=
"8"
:md=
"12"
:sm=
"24"
>
<a-form-item
label=
"计划名称"
>
<JInput
placeholder=
"请输入计划名称"
v-model:value=
"queryParam['projectName']"
/>
</a-form-item>
</a-col>
<a-col
:xl=
"5"
:lg=
"8"
:md=
"12"
:sm=
"24"
>
<a-form-item
label=
"计划类型"
>
<JSearchSelect
placeholder=
"请选择类型"
v-model:value=
"queryParam['projectType']"
dict=
"projecttype"
/>
</a-form-item>
</a-col>
<a-col
:xl=
"5"
:lg=
"8"
:md=
"12"
:sm=
"24"
>
<a-form-item
label=
"执行部门"
>
<JSelectDept
placeholder=
"请选择执行部门"
v-model:value=
"queryParam['execDepCode']"
/>
</a-form-item>
</a-col>
<a-col
:xl=
"4"
:lg=
"8"
:md=
"12"
:sm=
"24"
>
<a-form-item
label=
"计划状态"
>
<a-select
v-model:value=
"queryParam['status']"
placeholder=
"请选择状态"
allow-clear
:options=
"[
{ label: '草稿', value: '0' },
{ label: '审批中', value: '1' },
{ label: '已通过', value: '2' },
{ label: '已拒绝', value: '3' },
{ label: '执行中', value: '4' },
{ label: '已完成', value: '5' },
{ label: '已作废', value: '6' },
]"
/>
</a-form-item>
</a-col>
<a-col
:xl=
"5"
:lg=
"8"
:md=
"12"
:sm=
"24"
>
<a-form-item
label=
"计划日期"
>
<a-range-picker
v-model:value=
"queryParam['planDateRange']"
value-format=
"YYYY-MM-DD"
:placeholder=
"['开始日期', '结束日期']"
style=
"width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
<a-row
:gutter=
"16"
>
<a-col
:xl=
"5"
:lg=
"8"
:md=
"12"
:sm=
"24"
>
<a-form-item
label=
"优先级"
>
<a-select
v-model:value=
"queryParam['priority']"
placeholder=
"请选择优先级"
allow-clear
:options=
"[
{ label: '高', value: '1' },
{ label: '中', value: '2' },
{ label: '低', value: '3' },
]"
/>
</a-form-item>
</a-col>
<a-col
:xl=
"5"
:lg=
"8"
:md=
"12"
:sm=
"24"
>
<a-form-item
label=
"执行规则"
>
<a-select
v-model:value=
"queryParam['exeRule']"
placeholder=
"请选择执行规则"
allow-clear
:options=
"[
{ label: '一次性', value: '1' },
{ label: '周期执行', value: '2' },
{ label: '事件触发', value: '3' },
]"
/>
</a-form-item>
</a-col>
<a-col
:xl=
"14"
:lg=
"16"
:md=
"24"
:sm=
"24"
>
<a-form-item
class=
"search-btn-group"
>
<a-space
:size=
"8"
>
<a-button
type=
"primary"
@
click=
"searchQuery"
>
查询
</a-button>
<a-button
@
click=
"searchReset"
>
重置
</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
</div>
<!-- 表格区域 -->
<div
class=
"table-section"
>
<BasicTable
@
register=
"registerTable"
:rowSelection=
"rowSelection"
class=
"flat-table"
>
<!-- 插槽:table标题 -->
<template
#
tableTitle
>
<div
class=
"table-toolbar"
>
<a-button
type=
"primary"
@
click=
"handleAdd"
>
新建计划
</a-button>
<a-button
v-show=
"showUpBtn"
@
click=
"onExportXls"
>
导出
</a-button>
<j-upload-button
v-show=
"showUpBtn"
@
click=
"onImportXls"
>
导入
</j-upload-button>
<a-dropdown
v-if=
"selectedRowKeys.length > 0 && showUpBtn"
>
<template
#
overlay
>
<a-menu>
<a-menu-item
key=
"1"
@
click=
"batchHandleDelete"
class=
"danger-item"
>
<Icon
icon=
"ant-design:delete-outlined"
/>
批量删除
</a-menu-item>
</a-menu>
</
template
>
<a-button>
已选 {{ selectedRowKeys.length }} 项
<Icon
icon=
"mdi:chevron-down"
/>
</a-button>
</a-dropdown>
</div>
</template>
<!-- 操作栏 -->
<
template
#
action=
"{ record }"
>
<TableAction
:actions=
"getTableAction(record)"
:dropDownActions=
"getDropDownAction(record)"
/>
</
template
>
<!-- 字段回显插槽 -->
<
template
#
htmlSlot=
"{ text }"
>
<div
v-html=
"text"
></div>
</
template
>
<
template
#
fileSlot=
"{ text }"
>
<span
v-if=
"!text"
class=
"no-file"
>
无文件
</span>
<a-button
v-else
:ghost=
"true"
type=
"primary"
size=
"small"
@
click=
"downloadFile(text)"
>
下载
</a-button>
</
template
>
</BasicTable>
</div>
<!-- 表单区域 -->
<StPlanManModal
@
register=
"registerModal"
@
success=
"handleSuccess"
:showSelectorBtn=
"true"
/>
<StPlanManFlowModal
ref=
"refStPlanManFlow"
/>
</div>
<!-- 待办抽屉 -->
<div
v-if=
"isShowDrawer"
>
<a-drawer
destroyOnClose
v-model:open=
"isShowDrawer"
class=
"custom-class"
root-class-name=
"root-class-name"
:root-style=
"{ color: 'blue' }"
title=
"待办任务"
placement=
"right"
width=
"90%"
style=
"margin: 0px; padding: 0px"
>
<a-drawer
destroyOnClose
v-model:open=
"isShowDrawer"
class=
"flat-drawer"
title=
"待办任务"
placement=
"right"
width=
"90%"
>
<
template
#
extra
>
<div
style=
"float: right
"
>
<
a-tag
style=
"margin-left: 10px"
>
发起人:
{{
startUser
}}
</a-tag
>
<
a-tag>
任务节点:
{{
taskName
}}
</a-tag
>
<div
class=
"drawer-tags
"
>
<
span
class=
"tag"
>
发起人:
{{
startUser
}}
</span
>
<
span
class=
"tag"
>
任务节点:
{{
taskName
}}
</span
>
</div>
</
template
>
<TodoIndex
v-if=
"isShowDrawer"
ref=
"refTodoIndex"
@
callback=
"handleSuccess"
/>
</a-drawer>
</div>
<!-- 流程详情抽屉 -->
<div
v-if=
"isShowDetailDrawer"
>
<a-drawer
destroyOnClose
v-model:open=
"isShowDetailDrawer"
class=
"custom-class"
root-class-name=
"root-class-name"
:root-style=
"{ color: 'blue' }"
title=
"流程详情"
placement=
"right"
width=
"90%"
style=
"margin: 0px; padding: 0px"
>
<a-drawer
destroyOnClose
v-model:open=
"isShowDetailDrawer"
class=
"flat-drawer"
title=
"流程详情"
placement=
"right"
width=
"90%"
>
<
template
#
extra
>
<div
style=
"float: right"
>
<a-button
type=
"text"
@
click=
"handleDetailDrawerClose"
>
关闭
</a-button>
</div>
<a-button
type=
"text"
@
click=
"handleDetailDrawerClose"
>
关闭
</a-button>
</
template
>
<Detail
ref=
"refDetail"
/>
</a-drawer>
...
...
@@ -136,6 +208,7 @@
import
{
todoList
,
getMyTaskFlow
}
from
'/@/components/Process/api/todo'
;
// 引入详情组件
import
Detail
from
'../../flowable/task/myProcess/components/Detail.vue'
;
const
refTodoIndex
=
ref
();
const
isShowDrawer
=
ref
(
false
);
const
startUser
=
ref
<
string
>
(
''
);
...
...
@@ -147,10 +220,12 @@
// 流程详情抽屉相关
const
refDetail
=
ref
();
const
isShowDetailDrawer
=
ref
(
false
);
//注册model
// 注册model
const
[
registerModal
,
{
openModal
}]
=
useModal
();
const
jSelectDeptVal
=
ref
([]);
//注册table数据
// 注册table数据
const
{
prefixCls
,
tableContext
,
onExportXls
,
onImportXls
}
=
useListPage
({
tableProps
:
{
title
:
'计划编制'
,
...
...
@@ -159,7 +234,6 @@
canResize
:
false
,
useSearchForm
:
false
,
formConfig
:
{
//labelWidth: 120,
schemas
:
searchFormSchema
,
autoSubmitOnEnter
:
true
,
showAdvancedButton
:
true
,
...
...
@@ -167,7 +241,7 @@
fieldMapToTime
:
[],
},
actionColumn
:
{
width
:
2
4
0
,
width
:
2
8
0
,
fixed
:
'right'
,
},
showTableSetting
:
false
,
...
...
@@ -186,15 +260,17 @@
});
const
[
registerTable
,
{
setProps
,
reload
},
{
rowSelection
,
selectedRowKeys
}]
=
tableContext
;
const
labelCol
=
reactive
({
xs
:
{
span
:
24
},
sm
:
{
span
:
8
},
sm
:
{
span
:
6
},
});
const
wrapperCol
=
reactive
({
xs
:
{
span
:
24
},
sm
:
{
span
:
1
6
},
sm
:
{
span
:
1
8
},
});
const
queryParam
=
reactive
({});
function
initParam
()
{
const
schemas
=
unref
(
searchFormSchema
);
schemas
.
forEach
((
item
)
=>
{
...
...
@@ -205,6 +281,7 @@
jSelectDeptVal
.
value
=
[];
}
initParam
();
function
updateJSelectDept
(
val
)
{
if
(
val
.
length
>
0
)
{
queryParam
[
searchFormSchema
[
2
].
field
]
=
val
[
0
];
...
...
@@ -213,14 +290,17 @@
queryParam
[
searchFormSchema
[
2
].
field
]
=
''
;
}
}
function
searchQuery
()
{
setProps
({
searchInfo
:
toRaw
(
queryParam
)
});
reload
();
}
function
searchReset
()
{
initParam
();
reload
();
}
/**
* 新增事件
*/
...
...
@@ -230,6 +310,7 @@
showFooter
:
true
,
});
}
/**
* 编辑事件
*/
...
...
@@ -240,6 +321,7 @@
showFooter
:
true
,
});
}
/**
* 详情
*/
...
...
@@ -250,6 +332,7 @@
showFooter
:
false
,
});
}
/**
* 流转记录
*/
...
...
@@ -263,9 +346,6 @@
try
{
const
myTaskFlow
=
await
getMyTaskFlow
({
deploymentId
:
record
.
deployId
,
dataId
:
record
.
id
});
console
.
log
(
'获取流程任务信息:'
,
myTaskFlow
);
// 提取任务流信息,支持多层返回结构(data/result/直接对象)
const
taskData
=
pickStartResult
(
myTaskFlow
);
if
(
!
taskData
||
!
taskData
.
taskId
)
{
...
...
@@ -273,19 +353,14 @@
return
;
}
// 若未指定是否审批,则默认展示审批意见(保持与发起流程一致)
if
(
taskData
.
nodeisApprove
==
null
)
{
taskData
.
nodeisApprove
=
true
;
}
// 保存缓存
taskCache
.
set
(
String
(
dataId
),
taskData
);
// 打开流程详情抽屉并初始化数据
isShowDetailDrawer
.
value
=
true
;
await
nextTick
();
if
(
refDetail
.
value
)
{
// 使用获取到的taskData,确保包含所有必要参数
refDetail
.
value
.
iniData
({
...
record
,
...
taskData
,
...
...
@@ -301,7 +376,6 @@
}
async
function
findTodoTaskByProcInsId
(
procInsId
:
string
)
{
// 启动流程后,待办任务可能存在短暂延迟,做一个轻量重试
for
(
let
i
=
0
;
i
<
3
;
i
++
)
{
const
ret
=
await
todoList
({
pageNum
:
1
,
pageSize
:
10
,
procInsId
});
const
records
=
ret
?.
records
||
[];
...
...
@@ -313,12 +387,10 @@
}
function
pickStartResult
(
res
:
any
)
{
// defHttp 返回形态在不同后端/拦截器下可能是 data/result/直接对象,这里做一次兜底
const
base
=
res
?.
data
??
res
?.
result
??
res
;
return
base
||
{};
}
// 发起任务:点击后若流程未启动则先启动,再在抽屉中完成流程操作
async
function
handleTodo
(
record
:
Recordable
)
{
const
dataId
=
record
.
id
||
record
.
dataId
||
record
.
businessId
;
if
(
!
dataId
)
{
...
...
@@ -326,13 +398,11 @@
return
;
}
// 优先使用本地缓存(用于刚启动后列表未回写 taskId/procInsId 的场景)
const
cached
=
taskCache
.
get
(
String
(
dataId
));
if
(
cached
&&
!
record
.
taskId
)
{
record
=
Object
.
assign
({},
record
,
cached
);
}
// 流程未启动时,先启动流程
const
needStartFlow
=
!
record
.
taskId
&&
!
record
.
procInsId
&&
(
record
[
'bpmStatus'
]
==
null
||
record
[
'bpmStatus'
]
==
''
||
record
[
'bpmStatus'
]
==
'1'
);
...
...
@@ -340,27 +410,14 @@
try
{
const
formData
=
{
dataId
,
dataName
:
'id'
};
const
startResRaw
=
await
definitionStartByDeployId
(
record
.
deployId
,
formData
);
//console.log("definitionStartByDeployId 返回值",startResRaw);
/**definitionStartByDeployId 返回值
{procInsId: 'e962d600-1e88-11f1-8c5b-9a8d469af623',
executionId: 'e962fd16-1e88-11f1-8c5b-9a8d469af623',
instanceId: 'e962d600-1e88-11f1-8c5b-9a8d469af623',
deployId: '7fc9bc36-0591-11f1-9cb1-9a8d469af623',
taskId: 'e962fd1a-1e88-11f1-8c5b-9a8d469af623'}
*/
const
startRes
=
pickStartResult
(
startResRaw
);
let
payload
:
any
=
Object
.
assign
({},
record
,
startRes
);
//console.log("definitionStartByDeployId 返回值-payload ",payload);
// 若启动接口未返回 taskId,则尝试通过 procInsId 从待办列表反查
if
(
!
payload
.
taskId
&&
payload
.
procInsId
)
{
//console.log("definitionStartByDeployId 返回值-payload -1");
const
todoRow
=
await
findTodoTaskByProcInsId
(
payload
.
procInsId
);
if
(
todoRow
?.
taskId
)
{
payload
=
Object
.
assign
({},
record
,
startRes
,
todoRow
);
}
}
else
{
//console.log("definitionStartByDeployId 返回值-payload -2");
}
if
(
!
payload
.
taskId
)
{
...
...
@@ -379,11 +436,9 @@
console
.
error
(
'启动流程或获取任务失败:'
,
e
);
message
.
error
(
'操作失败,请重试'
);
}
//console.log("definitionStartByDeployId 返回值-payload- return");
return
;
}
console
.
log
(
'流程已启动:优先使用 record.taskId,否则尝试用 procInsId 从待办列表反查'
);
// 流程已启动:优先使用 record.taskId,否则尝试用 procInsId 从待办列表反查
try
{
let
payload
:
any
=
{
...
record
};
...
...
@@ -412,7 +467,6 @@
}
}
// 待办任务:
async
function
handleTodoDb
(
record
:
Recordable
)
{
const
dataId
=
record
.
id
;
const
deployId
=
record
.
deployId
;
...
...
@@ -423,9 +477,6 @@
try
{
const
myTaskFlow
=
await
getMyTaskFlow
({
deploymentId
:
record
.
deployId
,
dataId
:
record
.
id
});
console
.
log
(
'获取流程任务信息:'
,
myTaskFlow
);
// 提取任务流信息,支持多层返回结构(data/result/直接对象)
const
taskData
=
pickStartResult
(
myTaskFlow
);
if
(
!
taskData
||
!
taskData
.
taskId
)
{
...
...
@@ -433,15 +484,11 @@
return
;
}
// 若未指定是否审批,则默认展示审批意见(保持与发起流程一致)
if
(
taskData
.
nodeisApprove
==
null
)
{
taskData
.
nodeisApprove
=
true
;
}
// 保存缓存
taskCache
.
set
(
String
(
dataId
),
taskData
);
// 打开抽屉并初始化待办任务数据
startUser
.
value
=
taskData
.
startUserName
||
taskData
.
startUser
||
''
;
taskName
.
value
=
taskData
.
taskName
||
taskData
.
currentTaskName
||
''
;
...
...
@@ -460,12 +507,14 @@
async
function
handleDelete
(
record
)
{
await
deleteOne
({
id
:
record
.
id
},
handleSuccess
);
}
/**
* 批量删除事件
*/
async
function
batchHandleDelete
()
{
await
batchDelete
({
ids
:
selectedRowKeys
.
value
},
handleSuccess
);
}
/**
* 成功回调
*/
...
...
@@ -479,6 +528,7 @@
function
handleDetailDrawerClose
()
{
isShowDetailDrawer
.
value
=
false
;
}
/**
* 操作栏
*/
...
...
@@ -523,7 +573,6 @@
if
(
record
[
'bpmStatus'
]
==
null
||
record
[
'bpmStatus'
]
==
''
)
return
true
;
else
return
false
;
},
// disabled: record['planFlag'] != '0' && record['planFlag'] != '2' && record['planFlag'] != '9',
popConfirm
:
{
title
:
'是否确认删除该计划项'
,
confirm
:
handleDelete
.
bind
(
null
,
record
),
...
...
@@ -531,6 +580,7 @@
},
];
}
/**
* 下拉操作栏
*/
...
...
@@ -539,4 +589,352 @@
}
</
script
>
<
style
scoped
></
style
>
<
style
scoped
lang=
"less"
>
/* ==================== 柔和中性风格 - CSS 变量 ==================== */
.plan-management-page {
--color-primary: #3b5bdb;
--color-primary-hover: #364fc7;
--color-primary-light: #e8ecfd;
--color-accent: #3b5bdb;
--color-success: #2f9e44;
--color-success-light: #ebfbee;
--color-warning: #e67700;
--color-warning-light: #fff9db;
--color-error: #c92a2a;
--color-text-primary: #1c1c1e;
--color-text-secondary: #555770;
--color-text-muted: #a0a3b1;
--color-border: #e4e4e9;
--color-border-strong: #c8c8d0;
--color-bg-page: #f5f5f7;
--color-bg-white: #ffffff;
--color-bg-subtle: #fafafa;
--color-bg-section: #f0f0f4;
--radius: 6px;
min-height: 100vh;
background: var(--color-bg-page);
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif;
}
/* ==================== 页面头部 ==================== */
.page-header {
background: var(--color-bg-white);
border-bottom: 1px solid var(--color-border);
padding: 20px 32px;
}
.header-content {
max-width: 100%;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 20px;
}
.header-left {
flex: 1;
border-left: 3px solid var(--color-primary);
padding-left: 14px;
}
.page-title {
font-size: 18px;
font-weight: 600;
color: var(--color-text-primary);
margin: 0 0 3px 0;
letter-spacing: -0.2px;
}
.page-desc {
font-size: 13px;
color: var(--color-text-muted);
margin: 0;
}
.header-stats {
display: flex;
gap: 8px;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 10px 20px;
background: var(--color-bg-section);
border-radius: var(--radius);
min-width: 80px;
.stat-value {
font-size: 18px;
font-weight: 700;
color: var(--color-text-primary);
line-height: 1.2;
}
.stat-label {
font-size: 11px;
color: var(--color-text-muted);
margin-top: 3px;
white-space: nowrap;
}
&.warning .stat-value {
color: var(--color-warning);
}
&.success .stat-value {
color: var(--color-success);
}
}
/* ==================== 主内容区 ==================== */
.main-content {
max-width: 100%;
margin: 0 auto;
padding: 20px 32px;
display: flex;
flex-direction: column;
gap: 12px;
}
/* ==================== 搜索区域 ==================== */
.search-section {
background: var(--color-bg-white);
border: 1px solid var(--color-border);
border-radius: var(--radius);
}
.section-header {
padding: 11px 16px;
border-bottom: 1px solid var(--color-border);
display: flex;
align-items: center;
gap: 8px;
&::before {
content: '';
display: inline-block;
width: 3px;
height: 14px;
background: var(--color-primary);
border-radius: 2px;
flex-shrink: 0;
}
}
.section-title {
font-size: 13px;
font-weight: 600;
color: var(--color-text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.search-form {
padding: 16px 16px 4px;
}
.search-btn-group {
margin-bottom: 0;
}
/* ==================== 表格区域 ==================== */
.table-section {
background: var(--color-bg-white);
border: 1px solid var(--color-border);
border-radius: var(--radius);
}
.table-toolbar {
display: flex;
gap: 8px;
padding: 12px 0;
flex-wrap: wrap;
}
.flat-table {
:deep(.ant-table) {
border-radius: 0;
font-size: 13px;
}
:deep(.ant-table-thead > tr > th) {
background: var(--color-bg-section);
border-bottom: 1px solid var(--color-border-strong);
font-weight: 600;
font-size: 12px;
color: var(--color-text-secondary);
padding: 10px 16px;
text-transform: uppercase;
letter-spacing: 0.3px;
}
:deep(.ant-table-tbody > tr > td) {
padding: 11px 16px;
border-bottom: 1px solid var(--color-border);
color: var(--color-text-primary);
}
:deep(.ant-table-tbody > tr:hover > td) {
background: #f8f8fc;
}
:deep(.ant-pagination) {
margin: 14px 16px;
}
:deep(.ant-table-row-selected > td) {
background: var(--color-primary-light) !important;
}
}
.no-file {
color: var(--color-text-muted);
font-size: 12px;
}
.danger-item {
color: var(--color-error);
}
/* ==================== 抽屉样式 ==================== */
.flat-drawer {
:deep(.ant-drawer-header) {
background: var(--color-bg-white);
border-bottom: 1px solid var(--color-border);
padding: 14px 24px;
}
:deep(.ant-drawer-title) {
font-size: 15px;
font-weight: 600;
color: var(--color-text-primary);
}
:deep(.ant-drawer-body) {
padding: 0;
background: var(--color-bg-page);
}
}
.drawer-tags {
display: flex;
gap: 8px;
}
.tag {
font-size: 12px;
color: var(--color-text-secondary);
padding: 3px 10px;
background: var(--color-bg-section);
border-radius: 3px;
border: 1px solid var(--color-border);
white-space: nowrap;
}
/* ==================== 表单控件 ==================== */
:deep(.ant-input),
:deep(.ant-select-selector),
:deep(.ant-picker) {
border-radius: var(--radius) !important;
border-color: var(--color-border) !important;
box-shadow: none !important;
font-size: 13px;
&:hover {
border-color: var(--color-border-strong) !important;
}
&:focus,
&.ant-input-focused {
border-color: var(--color-primary) !important;
box-shadow: 0 0 0 2px var(--color-primary-light) !important;
}
}
:deep(.ant-select-focused .ant-select-selector) {
border-color: var(--color-primary) !important;
box-shadow: 0 0 0 2px var(--color-primary-light) !important;
}
/* ==================== 按钮样式 ==================== */
:deep(.ant-btn) {
border-radius: var(--radius);
font-weight: 500;
font-size: 13px;
box-shadow: none;
&.ant-btn-primary {
background: var(--color-primary);
border-color: var(--color-primary);
&:hover {
background: var(--color-primary-hover);
border-color: var(--color-primary-hover);
}
}
&.ant-btn-default {
border-color: var(--color-border-strong);
color: var(--color-text-secondary);
background: var(--color-bg-white);
&:hover {
border-color: var(--color-primary);
color: var(--color-primary);
background: var(--color-primary-light);
}
}
}
/* ==================== 表单项 ==================== */
:deep(.ant-form-item) {
margin-bottom: 16px;
}
:deep(.ant-form-item-label > label) {
font-size: 13px;
font-weight: 500;
color: var(--color-text-secondary);
}
/* ==================== 复选框 ==================== */
:deep(.ant-checkbox-checked .ant-checkbox-inner) {
background: var(--color-primary);
border-color: var(--color-primary);
}
/* ==================== 响应式 ==================== */
@media (max-width: 768px) {
.page-header {
padding: 16px;
}
.header-content {
flex-direction: column;
align-items: flex-start;
}
.header-stats {
width: 100%;
justify-content: space-between;
}
.stat-item {
flex: 1;
padding: 8px 12px;
min-width: unset;
}
.main-content {
padding: 12px 16px;
}
.table-toolbar {
flex-wrap: wrap;
}
}
</
style
>
zrch-risk-client-39/src/views/project/plan/components/StPlanExcuteForm.vue
浏览文件 @
4c79503d
<
template
>
<div
style=
"background-color: #fff; padding: 100px"
>
<a-form
ref=
"formRef"
:model=
"formData"
:label-col=
"
{ span: 4 }" :wrapper-col="{ span: 8 }">
<!-- 执行状态 -->
<a-form-item
label=
"执行状态"
prop=
"executeStatus"
>
<a-select
v-model:value=
"formData.executeStatus"
placeholder=
"请选择执行状态"
>
<a-select-option
value=
"0"
>
未开始
</a-select-option>
<a-select-option
value=
"1"
>
进行中
</a-select-option>
<a-select-option
value=
"2"
>
已完成
</a-select-option>
<a-select-option
value=
"3"
>
已暂停
</a-select-option>
</a-select>
</a-form-item>
<!-- 实际开始时间 -->
<a-form-item
label=
"实际开始时间"
prop=
"actualStartTime"
>
<a-date-picker
v-model=
"formData.actualStartTime"
type=
"datetime"
placeholder=
"选择时间"
></a-date-picker>
</a-form-item>
<!-- 实际结束时间 -->
<a-form-item
label=
"实际结束时间"
prop=
"actualEndTime"
>
<a-date-picker
v-model=
"formData.actualEndTime"
type=
"datetime"
placeholder=
"选择时间"
></a-date-picker>
</a-form-item>
<!-- 执行记录 -->
<a-form-item
label=
"执行记录"
prop=
"executeRecord"
>
<a-textarea
v-model=
"formData.executeRecord"
:rows=
"4"
placeholder=
"请输入执行记录"
></a-textarea>
</a-form-item>
<!-- 附件 -->
<a-form-item
label=
"附件"
prop=
"attachments"
>
<!--
<JUpload
v-model:value=
"formModel.fileUploadPath"
desText=
"支持扩展名: .rar .zip .doc .docx .pdf .jpg..."
:disabled=
"isDetail"
/>
-->
</a-form-item>
<!-- 操作按钮 -->
<a-form-item
:wrapper-col=
"
{ offset: 6 }">
<div
class=
"execute-form-container"
>
<!-- 表单头部 -->
<div
class=
"form-header"
>
<h2
class=
"form-title"
>
执行信息录入
</h2>
<p
class=
"form-subtitle"
>
更新计划执行状态和相关记录
</p>
</div>
<!-- 表单主体 -->
<div
class=
"form-body"
>
<a-form
ref=
"formRef"
:model=
"formData"
layout=
"vertical"
class=
"styled-form"
>
<!-- 执行状态 -->
<a-form-item
label=
"执行状态"
name=
"executeStatus"
>
<a-select
v-model:value=
"formData.executeStatus"
placeholder=
"请选择执行状态"
>
<a-select-option
value=
"0"
>
<span
class=
"status-option"
>
<span
class=
"status-indicator pending"
></span>
未开始
</span>
</a-select-option>
<a-select-option
value=
"1"
>
<span
class=
"status-option"
>
<span
class=
"status-indicator processing"
></span>
进行中
</span>
</a-select-option>
<a-select-option
value=
"2"
>
<span
class=
"status-option"
>
<span
class=
"status-indicator success"
></span>
已完成
</span>
</a-select-option>
<a-select-option
value=
"3"
>
<span
class=
"status-option"
>
<span
class=
"status-indicator paused"
></span>
已暂停
</span>
</a-select-option>
</a-select>
</a-form-item>
<!-- 时间选择区域 -->
<div
class=
"form-row"
>
<a-form-item
label=
"实际开始时间"
name=
"actualStartTime"
class=
"form-item-half"
>
<a-date-picker
v-model:value=
"formData.actualStartTime"
placeholder=
"选择时间"
format=
"YYYY-MM-DD"
></a-date-picker>
</a-form-item>
<a-form-item
label=
"实际结束时间"
name=
"actualEndTime"
class=
"form-item-half"
:rules=
"endDateRules"
>
<a-date-picker
v-model:value=
"formData.actualEndTime"
placeholder=
"选择时间"
format=
"YYYY-MM-DD"
></a-date-picker>
</a-form-item>
</div>
<!-- 执行记录 -->
<a-form-item
label=
"执行记录"
name=
"executeEcord"
>
<a-textarea
v-model:value=
"formData.executeEcord"
:rows=
"3"
placeholder=
"请输入执行记录和详细说明..."
show-count
:maxlength=
"500"
/>
</a-form-item>
<!-- 附件上传 -->
<a-form-item
label=
"相关附件"
name=
"attachments"
>
<JUpload
v-model:value=
"formData.attachments"
desText=
"支持扩展名: .rar .zip .doc .docx .pdf .jpg..."
/>
</a-form-item>
</a-form>
</div>
<!-- 表单底部按钮 -->
<div
class=
"form-footer"
>
<a-space
:size=
"12"
>
<a-button
@
click=
"resetForm"
>
重置
</a-button>
<a-button
type=
"primary"
@
click=
"submitForm"
>
保存
</a-button>
<a-button
style=
"margin-left: 40px"
@
click=
"resetForm"
>
重置
</a-button>
</a-form-item>
</a-form>
</a-space>
</div>
</div>
</
template
>
<
script
setup
>
import
{
ref
}
from
'vue'
;
<
script
setup
lang=
"ts"
>
import
{
ref
,
reactive
,
onMounted
,
watch
,
nextTick
}
from
'vue'
;
import
{
message
}
from
'ant-design-vue'
;
import
dayjs
from
'dayjs'
;
import
{
saveOrUpdate
}
from
'../StPlanMan.api'
;
import
{
defHttp
}
from
'/@/utils/http/axios'
;
import
{
useRoute
}
from
'vue-router'
;
import
JUpload
from
'/@/components/Form/src/jeecg/components/JUpload/JUpload.vue'
;
import
{
usePlanFormStore
}
from
'/@/store/modules/planFormStore'
;
const
route
=
useRoute
();
const
formRef
=
ref
();
const
formData
=
ref
({
const
loading
=
ref
(
false
);
const
submitting
=
ref
(
false
);
const
planId
=
ref
(
''
);
const
planFormStore
=
usePlanFormStore
();
const
isInitialized
=
ref
(
false
);
// const formData = ref({
// id: '',
// executeStatus: '',
// actualStartTime: null,
// actualEndTime: null,
// executeEcord: '',
// attachments: [],
// });
const
formData
=
reactive
({
id
:
''
,
executeStatus
:
''
,
actualStartTime
:
''
,
actualEndTime
:
''
,
execute
Re
cord
:
''
,
attachments
:
[],
actualStartTime
:
null
as
any
,
actualEndTime
:
null
as
any
,
execute
E
cord
:
''
,
attachments
:
[]
as
any
[]
,
});
const
submitForm
=
()
=>
{
formRef
.
value
.
validate
((
valid
)
=>
{
if
(
valid
)
{
console
.
log
(
'Form submitted:'
,
formData
.
value
);
// 提交逻辑
// 监听formData变化,用于调试
watch
(
()
=>
formData
,
(
newVal
)
=>
{
console
.
log
(
'[v0] formData 响应式更新触发:'
,
JSON
.
stringify
(
newVal
));
},
{
deep
:
true
,
immediate
:
true
}
);
// 安全解析JSON
const
safeJsonParse
=
(
str
)
=>
{
if
(
!
str
)
return
[];
if
(
Array
.
isArray
(
str
))
return
str
;
try
{
return
JSON
.
parse
(
str
);
}
catch
(
e
)
{
return
[];
}
};
// 初始化表单数据
const
initFormData
=
async
(
id
?:
string
)
=>
{
try
{
// 检查是否已正在加载,避免重复请求
if
(
loading
.
value
&&
planId
.
value
===
(
id
||
route
.
query
.
id
))
{
console
.
log
(
'[v0] 表单已在加载中,跳过重复初始化'
);
return
;
}
});
loading
.
value
=
true
;
planFormStore
.
setLoadingState
(
true
);
// 从参数或URL获取id
const
targetId
=
id
||
(
route
.
query
.
id
as
string
);
if
(
!
targetId
)
{
message
.
warning
(
'未获取到计划ID'
);
console
.
warn
(
'[v0] 未获取到计划ID, id参数:'
,
id
,
'路由ID:'
,
route
.
query
.
id
);
return
;
}
console
.
log
(
'[v0] 开始初始化表单,ID:'
,
targetId
);
planId
.
value
=
targetId
;
planFormStore
.
setLastLoadedId
(
targetId
);
// 根据id查询现有数据
const
timestamp
=
new
Date
().
getTime
();
const
queryUrl
=
'/plan.main/stPlanMan/queryById'
;
const
data
=
await
defHttp
.
get
({
url
:
queryUrl
,
params
:
{
id
:
targetId
,
_t
:
timestamp
},
});
// 使用查询到的数据初始化表单,只保留执行相关字段
if
(
data
)
{
const
newFormData
=
{
id
:
data
.
id
||
''
,
executeStatus
:
data
.
executeStatus
||
''
,
actualStartTime
:
data
.
actualStartTime
?
dayjs
(
data
.
actualStartTime
)
:
null
,
actualEndTime
:
data
.
actualEndTime
?
dayjs
(
data
.
actualEndTime
)
:
null
,
executeEcord
:
data
.
executeEcord
||
''
,
// 安全解析附件字段(可能是字符串或数组)
attachments
:
safeJsonParse
(
data
.
attachments
),
};
// 直接替换整个 ref 值,确保触发响应式更新
Object
.
assign
(
formData
,
newFormData
);
// 强制等待 DOM 更新
await
nextTick
();
planFormStore
.
updateFormDataCache
(
newFormData
);
isInitialized
.
value
=
true
;
console
.
log
(
'[v0] 表单初始化成功,数据:'
,
formData
);
// Object.assign(formData, newFormData);
// planFormStore.updateFormDataCache(newFormData);
// isInitialized.value = true;
// console.log('[v0] 表单初始化成功,数据:', newFormData);
}
}
catch
(
error
:
any
)
{
console
.
error
(
'初始化表单数据失败:'
,
error
);
message
.
error
(
'初始化表单数据失败'
);
planFormStore
.
formLoadingState
.
isError
=
true
;
planFormStore
.
formLoadingState
.
errorMessage
=
error
?.
message
||
'加载失败'
;
}
finally
{
loading
.
value
=
false
;
planFormStore
.
setLoadingState
(
false
);
// 确保表单在下一个更新周期中可用
await
nextTick
();
}
};
// 结束日期验证规则
const
endDateRules
=
[
{
validator
:
(
rule
,
value
)
=>
{
if
(
!
value
)
{
return
Promise
.
resolve
();
}
if
(
!
formData
.
actualStartTime
)
{
return
Promise
.
resolve
();
}
// 比较日期:结束日期不能早于开始日期
const
endTime
=
dayjs
(
value
);
const
startTime
=
dayjs
(
formData
.
actualStartTime
);
if
(
endTime
.
isBefore
(
startTime
,
'day'
))
{
return
Promise
.
reject
(
new
Error
(
'结束日期不能早于开始日期'
));
}
return
Promise
.
resolve
();
},
trigger
:
'change'
,
},
];
const
submitForm
=
async
()
=>
{
try
{
// 验证表单
try
{
await
formRef
.
value
.
validate
();
}
catch
(
validateError
)
{
return
;
}
if
(
!
formData
.
id
)
{
message
.
error
(
'缺少计划ID,无法保存'
);
return
;
}
submitting
.
value
=
true
;
planFormStore
.
setLoadingState
(
true
);
// 格式化日期为字符串(仅保留日期,不含时分秒)
const
formatDate
=
(
date
)
=>
{
if
(
!
date
)
return
''
;
// 如果是Dayjs对象
if
(
date
&&
typeof
date
.
format
===
'function'
)
{
return
date
.
format
(
'YYYY-MM-DD'
);
}
// 如果是Date对象
if
(
date
instanceof
Date
)
{
const
year
=
date
.
getFullYear
();
const
month
=
String
(
date
.
getMonth
()
+
1
).
padStart
(
2
,
'0'
);
const
day
=
String
(
date
.
getDate
()).
padStart
(
2
,
'0'
);
return
`
${
year
}
-
${
month
}
-
${
day
}
`
;
}
// 如果已是字符串
return
String
(
date
);
};
// 准备提交数据,将数组类型转换为字符串,日期格式化
const
submitData
=
{
id
:
formData
.
id
,
executeStatus
:
formData
.
executeStatus
||
''
,
actualStartTime
:
formatDate
(
formData
.
actualStartTime
),
actualEndTime
:
formatDate
(
formData
.
actualEndTime
),
executeEcord
:
formData
.
executeEcord
||
''
,
// 将附件数组转换为JSON字符串(如果是数组)或保留原值(如果已是字符串)
attachments
:
Array
.
isArray
(
formData
.
attachments
)
?
JSON
.
stringify
(
formData
.
attachments
)
:
formData
.
attachments
||
''
,
};
// 调用 saveOrUpdate 接口,isUpdate=true表示执行更新操作
const
response
=
await
saveOrUpdate
(
submitData
,
true
);
if
(
response
)
{
message
.
success
(
'保存成功'
);
// 更新缓存
planFormStore
.
updateFormDataCache
(
formData
);
console
.
log
(
response
);
}
}
catch
(
error
:
any
)
{
console
.
error
(
'保存失败:'
,
error
);
message
.
error
(
error
?.
message
||
'保存失败,请检查表单输入'
);
}
finally
{
submitting
.
value
=
false
;
planFormStore
.
setLoadingState
(
false
);
}
};
const
resetForm
=
()
=>
{
formRef
.
value
.
resetFields
();
// 重置为初始化的数据
initFormData
();
};
// 1 接收 id 2 保存调用 StPlanMan.api 的 saveOrUpdate
// 组件挂载时初始化表单
onMounted
(
async
()
=>
{
// 等待 iframe DOM 完全渲染
await
nextTick
();
// 从 URL 直接解析 id 参数(iframe 场景)
const
urlParams
=
new
URLSearchParams
(
window
.
location
.
search
);
const
idFromUrl
=
urlParams
.
get
(
'id'
);
const
targetId
=
idFromUrl
||
(
route
.
query
.
id
as
string
);
console
.
log
(
'[v0] StPlanExcuteForm mounted - idFromUrl:'
,
idFromUrl
,
'routeId:'
,
route
.
query
.
id
,
'finalId:'
,
targetId
);
// 如果从 URL 获取到 ID,直接初始化
if
(
idFromUrl
)
{
initFormData
(
idFromUrl
);
}
else
if
(
targetId
)
{
// 否则使用路由参数
initFormData
(
targetId
);
}
else
{
// 都没有则延迟检查,确保 URL 参数已加载
await
new
Promise
((
resolve
)
=>
setTimeout
(
resolve
,
100
));
const
delayedUrlParams
=
new
URLSearchParams
(
window
.
location
.
search
);
const
delayedIdFromUrl
=
delayedUrlParams
.
get
(
'id'
);
if
(
delayedIdFromUrl
)
{
initFormData
(
delayedIdFromUrl
);
}
}
planFormStore
.
registerSubmitCallback
(
submitForm
);
console
.
log
(
'[StPlanExcuteForm] 已向 store 注册表单保存回调'
);
});
// 监听路由参数变化
watch
(
()
=>
route
.
query
.
id
,
(
newId
)
=>
{
if
(
newId
)
{
// 重置初始化标志,确保新的ID会被加载
isInitialized
.
value
=
false
;
initFormData
(
newId
as
string
);
}
}
);
// 监听store中的加载状态变化
watch
(
()
=>
planFormStore
.
formLoadingState
.
lastLoadedId
,
(
newId
)
=>
{
// 当store中的lastLoadedId变化时,无论当前状态如何,都重新初始化表单
if
(
newId
)
{
// 重置初始化标志,确保表单可以重新初始化
isInitialized
.
value
=
false
;
initFormData
(
newId
);
}
}
);
// 暴露方法给外部组件调用
defineExpose
({
submitForm
,
resetForm
,
initFormData
,
isInitialized
,
});
</
script
>
<
style
scoped
></
style
>
<
style
scoped
lang=
"less"
>
/* ==================== 柔和中性风格 - CSS 变量 ==================== */
.execute-form-container {
--color-primary: #3b5bdb;
--color-primary-hover: #364fc7;
--color-primary-light: #e8ecfd;
--color-success: #2f9e44;
--color-warning: #e67700;
--color-error: #c92a2a;
--color-text-primary: #1c1c1e;
--color-text-secondary: #555770;
--color-text-muted: #a0a3b1;
--color-border: #e4e4e9;
--color-border-strong: #c8c8d0;
--color-bg-page: #f5f5f7;
--color-bg-white: #ffffff;
--color-bg-section: #f0f0f4;
--radius: 6px;
background: var(--color-bg-white);
min-height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif;
}
/* ==================== 表单头部 ==================== */
.form-header {
background: var(--color-bg-white);
border-bottom: 1px solid var(--color-border);
padding: 20px 32px;
border-left: 3px solid var(--color-primary);
padding-left: 28px;
}
.form-title {
font-size: 18px;
font-weight: 600;
color: var(--color-text-primary);
margin: 0 0 3px 0;
letter-spacing: -0.2px;
}
.form-subtitle {
font-size: 13px;
color: var(--color-text-muted);
margin: 0;
}
/* ==================== 表单主体 ==================== */
.form-body {
padding: 24px 32px;
max-width: 800px;
margin: 0 auto;
}
.styled-form {
display: flex;
flex-direction: column;
gap: 8px;
}
.form-row {
display: flex;
gap: 24px;
.form-item-half {
flex: 1;
}
}
/* 状态选项样式 */
.status-option {
display: flex;
align-items: center;
gap: 8px;
}
.status-indicator {
width: 8px;
height: 8px;
border-radius: 2px;
&.pending {
background: var(--color-text-muted);
}
&.processing {
background: var(--color-primary);
}
&.success {
background: var(--color-success);
}
&.paused {
background: var(--color-warning);
}
}
/* 上传区域样式 */
.upload-area {
border: 1px dashed var(--color-border-dark);
border-radius: var(--radius);
padding: 32px;
background: var(--color-bg-gray);
cursor: pointer;
transition: border-color 0.15s;
&:hover {
border-color: var(--color-primary);
}
}
.upload-content {
text-align: center;
}
.upload-text {
font-size: 14px;
color: var(--color-text-secondary);
margin: 0 0 4px 0;
}
.upload-hint {
font-size: 12px;
color: var(--color-text-muted);
margin: 0;
}
/* ==================== 表单底部按钮 ==================== */
.form-footer {
padding: 14px 32px;
background: var(--color-bg-section);
border-top: 1px solid var(--color-border);
display: flex;
justify-content: flex-end;
}
/* ==================== 上传区域 ==================== */
.upload-area {
border: 1px dashed var(--color-border-strong);
border-radius: var(--radius);
padding: 28px;
background: var(--color-bg-section);
cursor: pointer;
transition: border-color 0.15s;
&:hover {
border-color: var(--color-primary);
}
}
/* ==================== 表单控件样式覆盖 ==================== */
:deep(.ant-form-item) {
margin-bottom: 18px;
}
:deep(.ant-form-item-label > label) {
font-size: 13px;
font-weight: 500;
color: var(--color-text-secondary);
}
:deep(.ant-input),
:deep(.ant-select-selector),
:deep(.ant-picker),
:deep(.ant-input-textarea textarea) {
border-radius: var(--radius) !important;
border-color: var(--color-border) !important;
font-size: 13px;
box-shadow: none !important;
&:hover {
border-color: var(--color-border-strong) !important;
}
&:focus,
&.ant-input-focused {
border-color: var(--color-primary) !important;
box-shadow: 0 0 0 2px var(--color-primary-light) !important;
}
}
:deep(.ant-select-focused .ant-select-selector),
:deep(.ant-picker-focused) {
border-color: var(--color-primary) !important;
box-shadow: 0 0 0 2px var(--color-primary-light) !important;
}
/* ==================== 按钮样式 ==================== */
:deep(.ant-btn) {
border-radius: var(--radius);
font-weight: 500;
font-size: 13px;
height: 34px;
padding: 0 18px;
box-shadow: none;
&.ant-btn-primary {
background: var(--color-primary);
border-color: var(--color-primary);
&:hover {
background: var(--color-primary-hover);
border-color: var(--color-primary-hover);
}
}
&.ant-btn-default {
border-color: var(--color-border-strong);
color: var(--color-text-secondary);
background: var(--color-bg-white);
&:hover {
border-color: var(--color-primary);
color: var(--color-primary);
background: var(--color-primary-light);
}
}
}
/* ==================== 响应式设计 ==================== */
@media (max-width: 768px) {
.form-header {
padding: 16px 20px;
}
.form-body {
padding: 20px;
}
.form-row {
flex-direction: column;
gap: 0;
}
.form-footer {
padding: 12px 20px;
}
}
</
style
>
zrch-risk-client-39/src/views/project/plan/components/StPlanManFlowModal.vue
浏览文件 @
4c79503d
<
template
>
<div>
<vxe-drawer
...
...
@@ -9,21 +8,40 @@
width=
"100%"
height=
"100%"
:loading=
"loading"
class=
"flat-flow-drawer"
>
<iframe
id=
"iframeId"
ref=
"iframeRef"
:src=
"frmUrl"
frameborder=
"0"
style=
"width: 100%; height: 100%;"
></iframe>
<template
#
header
>
<div
class=
"drawer-header"
>
<h3
class=
"drawer-title"
>
{{
pageTilte
}}
</h3>
<span
class=
"drawer-subtitle"
>
计划审批流程
</span>
</div>
</
template
>
<div
class=
"drawer-body"
>
<div
class=
"iframe-container"
>
<div
v-if=
"loading"
class=
"loading-overlay"
>
<div
class=
"loading-content"
>
<a-spin
size=
"large"
/>
<span
class=
"loading-text"
>
加载流程中...
</span>
</div>
</div>
<iframe
id=
"iframeId"
ref=
"iframeRef"
:src=
"frmUrl"
frameborder=
"0"
class=
"flow-iframe"
@
load=
"handleIframeLoad"
></iframe>
</div>
</div>
</vxe-drawer>
</div>
</template>
<
script
lang=
"ts"
setup
>
import
{
ref
,
onMounted
,
nextTick
}
from
"vue"
;
import
{
ref
,
onMounted
,
nextTick
}
from
"vue"
;
import
{
useUserStoreWithOut
}
from
"/@/store/modules/user"
;
import
{
defHttp
}
from
'/@/utils/http/axios'
;
import
{
getToken
}
from
'/@/utils/auth'
;
...
...
@@ -35,18 +53,190 @@ const loading = ref(false);
const
pageTilte
=
ref
(
""
);
const
iframeRef
=
ref
<
HTMLIFrameElement
>
();
const
handleIframeLoad
=
()
=>
{
loading
.
value
=
false
;
};
const
iniPage
=
async
(
data
)
=>
{
pageTilte
.
value
=
data
.
projectName
;
showPopup
.
value
=
true
;
loading
.
value
=
true
;
frmUrl
.
value
=
`
${
import
.
meta
.
env
.
VITE_APP_JFLOW_CORE_ADDR
}
/#/WF/MyFlow?FlowNo=087&Token=
${
user
.
getJflowToken
}
&tid=
${
data
.
id
}
`
;
const
setSourctUrl
=
'/api/jflow/setCCWorkId'
;
await
defHttp
.
get
({
url
:
setSourctUrl
,
params
:
{
"targetId"
:
data
.
id
,
"targetKey"
:
"targetKey"
,
"token"
:
getToken
()},
});
}
// 暴露方法
defineExpose
({
iniPage
});
</
script
>
<
style
scoped
lang=
"less"
>
/* ==================== 柔和中性风格 - CSS 变量 ==================== */
.flat-flow-drawer {
--color-primary: #3b5bdb;
--color-primary-hover: #364fc7;
--color-primary-light: #e8ecfd;
--color-success: #2f9e44;
--color-warning: #e67700;
--color-error: #c92a2a;
--color-text-primary: #1c1c1e;
--color-text-secondary: #555770;
--color-text-muted: #a0a3b1;
--color-border: #e4e4e9;
--color-border-strong: #c8c8d0;
--color-bg-page: #f5f5f7;
--color-bg-white: #ffffff;
--color-bg-section: #f0f0f4;
--radius: 6px;
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif;
}
/* ==================== 抽屉头部样式 ==================== */
.drawer-header {
display: flex;
align-items: baseline;
gap: 10px;
border-left: 3px solid var(--color-primary);
padding-left: 10px;
}
.drawer-title {
font-size: 16px;
font-weight: 600;
color: var(--color-text-primary);
margin: 0;
letter-spacing: -0.2px;
}
.drawer-subtitle {
font-size: 12px;
color: var(--color-text-muted);
}
/* ==================== 抽屉主体样式 ==================== */
.drawer-body {
height: 100%;
background: var(--color-bg-page);
padding: 12px;
}
.iframe-container {
position: relative;
width: 100%;
height: 100%;
background: var(--color-bg-white);
border: 1px solid var(--color-border);
border-radius: var(--radius);
overflow: hidden;
}
.flow-iframe {
width: 100%;
height: 100%;
border: none;
}
/* ==================== 加载状态 ==================== */
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.95);
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
.loading-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
}
.loading-text {
font-size: 14px;
color: var(--color-text-secondary);
}
/* ==================== VXE Drawer 样式覆盖 ==================== */
:deep(.vxe-drawer--wrapper) {
.vxe-drawer--header {
background: var(--color-bg-section);
border-bottom: 1px solid var(--color-border);
padding: 14px 24px;
}
.vxe-drawer--body {
padding: 0;
background: var(--color-bg-page);
}
.vxe-drawer--footer {
background: var(--color-bg-white);
border-top: 1px solid var(--color-border);
padding: 12px 24px;
}
.vxe-button {
border-radius: var(--radius);
font-weight: 500;
font-size: 13px;
box-shadow: none;
&.type--primary {
background: var(--color-primary);
border-color: var(--color-primary);
&:hover {
background: var(--color-primary-hover);
border-color: var(--color-primary-hover);
}
}
&.type--default {
border-color: var(--color-border-strong);
color: var(--color-text-secondary);
background: var(--color-bg-white);
&:hover {
border-color: var(--color-primary);
color: var(--color-primary);
background: var(--color-primary-light);
}
}
}
}
/* ==================== 响应式设计 ==================== */
@media (max-width: 768px) {
.drawer-header {
flex-direction: column;
gap: 4px;
}
.drawer-title {
font-size: 16px;
}
.drawer-body {
padding: 12px;
}
:deep(.vxe-drawer--wrapper) {
.vxe-drawer--header {
padding: 12px 16px;
}
.vxe-drawer--footer {
padding: 10px 16px;
}
}
}
</
style
>
zrch-risk-client-39/src/views/project/plan/components/StPlanManForm.vue
浏览文件 @
4c79503d
<
template
>
<div
style=
"background-color: #fff; padding: 100px
"
>
<div
class=
"plan-form-container
"
>
<a-spin
:spinning=
"loading"
>
<BasicForm
@
register=
"registerForm"
>
<template
#
planBasis=
"
{ model, field }">
<div
v-if=
"model[field]"
>
<div
v-if=
"isValidJson(model[field])"
>
<a-tag
v-for=
"item in safeJsonParse(model[field])"
@
click=
"viewBasisDetail(item)"
:key=
"item.id"
style=
"margin-bottom: 8px; cursor: pointer"
>
{{
item
.
name
}}
</a-tag>
<!-- 表单头部 -->
<div
class=
"form-header"
>
<h2
class=
"form-title"
>
计划详情
</h2>
<p
class=
"form-subtitle"
>
查看和编辑计划信息
</p>
</div>
<!-- 表单内容 -->
<div
class=
"form-body"
>
<BasicForm
@
register=
"registerForm"
>
<template
#
planBasis=
"
{ model, field }">
<div
class=
"basis-container"
v-if=
"model[field]"
>
<div
v-if=
"isValidJson(model[field])"
class=
"basis-tags"
>
<a-tag
v-for=
"item in safeJsonParse(model[field])"
@
click=
"viewBasisDetail(item)"
:key=
"item.id"
class=
"basis-tag"
>
{{
item
.
name
}}
</a-tag>
</div>
<a-alert
v-else
type=
"warning"
:message=
"`无效的数据格式: $
{model[field]}`" class="basis-alert" />
</div>
<
a-alert
v-else
type=
"warning"
:message=
"`无效的数据格式: $
{model[field]}`" /
>
</div
>
<a-empty
v-else
description=
"暂无数据"
/
>
</
template
>
</BasicForm>
</a-spin
>
<
div
v-else
class=
"basis-empty"
>
<a-empty
description=
"暂无依据数据"
:image-style=
"
{ height: '40px' }" /
>
</div
>
</
template
>
</BasicForm>
</div
>
<div
style=
"width: 100%; text-align: center; margin-top: 24px"
v-if=
"!formDisabled"
>
<a-space>
<a-button
@
click=
"submitForm"
type=
"primary"
:loading=
"submitting"
pre-icon=
"ant-design:check"
>
提 交
</a-button>
<a-button
@
click=
"handleReset"
>
重 置
</a-button>
</a-space>
</div>
<!-- 操作按钮 -->
<div
class=
"form-footer"
v-if=
"!formDisabled"
>
<a-space
:size=
"12"
>
<a-button
@
click=
"handleReset"
>
重置
</a-button>
<a-button
@
click=
"submitForm"
type=
"primary"
:loading=
"submitting"
>
提交
</a-button>
</a-space>
</div>
</a-spin>
<AuditInnerDetailDrawer
ref=
"auditInnerDetailDrawerRef"
:visible=
"showDetailDrawer"
:basis=
"selectedBasis"
@
close=
"handleDrawerClose"
/>
</div>
...
...
@@ -125,29 +137,12 @@
const
initFormData
=
async
()
=>
{
try
{
loading
.
value
=
true
;
//const { cctoken, WorkID } = getUrlParams();
//console.log('Token:', cctoken, 'WorkID:', WorkID);
const
timestamp
=
new
Date
().
getTime
();
/**
const gettokeyUrl = '/api/jflow/getCCWorkTokenAndTid';
const {tid,token} = await defHttp.get({
url: gettokeyUrl,
params: {
"targetKey":"targetKey",
"flowToken":cctoken,
"WorkID":WorkID,
"_t": timestamp},
});
*/
let
tid
=
toRaw
(
route
.
query
).
id
;
console
.
log
(
'tid:'
,
tid
);
//setAuthCache(TOKEN_KEY, token);
//console.log('tid:', tid, 'token:', token);
const
queryByIdUrl
=
'/plan.main/stPlanMan/queryById'
;
const
data
=
await
defHttp
.
get
({
url
:
queryByIdUrl
,
...
...
@@ -169,14 +164,216 @@
loading
.
value
=
false
;
}
};
const
handleReset
=
()
=>
{
resetFields
();
};
const
submitForm
=
async
()
=>
{
try
{
submitting
.
value
=
true
;
await
validate
();
const
values
=
getFieldsValue
();
await
saveOrUpdate
(
values
,
true
);
createMessage
.
success
(
'提交成功'
);
}
catch
(
error
)
{
console
.
error
(
'提交失败:'
,
error
);
}
finally
{
submitting
.
value
=
false
;
}
};
onMounted
(()
=>
{
initFormData
();
});
</
script
>
<
style
scoped
>
.ant-tag
{
margin-right
:
8px
;
cursor
:
pointer
;
<
style
scoped
lang=
"less"
>
/* ==================== 柔和中性风格 - CSS 变量 ==================== */
.plan-form-container {
--color-primary: #3b5bdb;
--color-primary-hover: #364fc7;
--color-primary-light: #e8ecfd;
--color-success: #2f9e44;
--color-warning: #e67700;
--color-error: #c92a2a;
--color-text-primary: #1c1c1e;
--color-text-secondary: #555770;
--color-text-muted: #a0a3b1;
--color-border: #e4e4e9;
--color-border-strong: #c8c8d0;
--color-bg-page: #f5f5f7;
--color-bg-white: #ffffff;
--color-bg-section: #f0f0f4;
--radius: 6px;
background: var(--color-bg-white);
min-height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif;
}
/* ==================== 表单头部 ==================== */
.form-header {
background: var(--color-bg-white);
border-bottom: 1px solid var(--color-border);
padding: 20px 32px;
border-left: 3px solid var(--color-primary);
padding-left: 28px;
}
.form-title {
font-size: 18px;
font-weight: 600;
color: var(--color-text-primary);
margin: 0 0 3px 0;
letter-spacing: -0.2px;
}
.form-subtitle {
font-size: 13px;
color: var(--color-text-muted);
margin: 0;
}
/* ==================== 表单主体 ==================== */
.form-body {
padding: 24px 32px;
background: var(--color-bg-white);
}
/* 依据标签样式 */
.basis-container {
padding: 8px 0;
}
.basis-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.basis-tag {
display: inline-flex;
align-items: center;
padding: 4px 10px;
background: var(--color-primary-light);
border: 1px solid var(--color-primary);
border-radius: var(--radius);
color: var(--color-primary);
font-size: 13px;
cursor: pointer;
transition: background 0.15s;
&:hover {
background: #d0d9f8;
}
}
.basis-alert {
border-radius: var(--radius);
}
.basis-empty {
padding: 16px;
background: var(--color-bg-section);
border-radius: var(--radius);
border: 1px dashed var(--color-border-strong);
}
/* ==================== 表单底部按钮 ==================== */
.form-footer {
padding: 14px 32px;
background: var(--color-bg-section);
border-top: 1px solid var(--color-border);
display: flex;
justify-content: flex-end;
}
/* ==================== 表单控件样式覆盖 ==================== */
:deep(.ant-form-item) {
margin-bottom: 18px;
}
:deep(.ant-form-item-label > label) {
font-size: 13px;
font-weight: 500;
color: var(--color-text-secondary);
}
:deep(.ant-input),
:deep(.ant-select-selector),
:deep(.ant-picker),
:deep(.ant-input-textarea textarea) {
border-radius: var(--radius) !important;
border-color: var(--color-border) !important;
font-size: 13px;
box-shadow: none !important;
&:hover {
border-color: var(--color-border-strong) !important;
}
&:focus,
&.ant-input-focused {
border-color: var(--color-primary) !important;
box-shadow: 0 0 0 2px var(--color-primary-light) !important;
}
}
:deep(.ant-select-focused .ant-select-selector) {
border-color: var(--color-primary) !important;
box-shadow: 0 0 0 2px var(--color-primary-light) !important;
}
/* ==================== 按钮样式 ==================== */
:deep(.ant-btn) {
border-radius: var(--radius);
font-weight: 500;
font-size: 13px;
height: 34px;
padding: 0 18px;
box-shadow: none;
&.ant-btn-primary {
background: var(--color-primary);
border-color: var(--color-primary);
&:hover {
background: var(--color-primary-hover);
border-color: var(--color-primary-hover);
}
}
&.ant-btn-default {
border-color: var(--color-border-strong);
color: var(--color-text-secondary);
background: var(--color-bg-white);
&:hover {
border-color: var(--color-primary);
color: var(--color-primary);
background: var(--color-primary-light);
}
}
}
/* ==================== 加载状态 ==================== */
:deep(.ant-spin-container) {
min-height: 400px;
}
/* ==================== 响应式设计 ==================== */
@media (max-width: 768px) {
.form-header {
padding: 16px 20px;
}
.form-body {
padding: 20px;
}
.form-footer {
padding: 12px 20px;
}
}
</
style
>
zrch-risk-client-39/src/views/project/plan/components/StPlanManModal.vue
浏览文件 @
4c79503d
...
...
@@ -3,83 +3,211 @@
v-bind=
"$attrs"
@
register=
"registerModal"
destroyOnClose
title=
"发起计划"
:width=
"1000"
@
ok=
"handleSubmit"
@
cancel=
"handleCance"
:centered=
"true"
:title=
"'发起计划'"
>
<a-form
:model=
"formModel"
ref=
"formRef"
style=
"overflow-x: hidden"
:disabled=
"isDetail"
>
<a-row
gutter=
"8"
>
<a-col
:span=
"12"
>
<a-form-item
label=
"计划名称"
name=
"projectName"
:label-col=
"
{ style: { width: '120px' } }" :wrapperCol="{ span: 16 }">
<a-input
v-model:value=
"formModel.projectName"
allow-clear
:disabled=
"isDetail"
/>
</a-form-item>
</a-col>
<a-col
:span=
"12"
>
<a-form-item
label=
"类型"
name=
"projectType"
:label-col=
"
{ style: { width: '120px' } }" :wrapperCol="{ span: 16 }">
<JCategorySelect
pcode=
"B09"
:value=
"formModel.projectType"
:disabled=
"isDetail"
@
change=
"changeJCategory"
/>
</a-form-item>
</a-col>
<a-col
:span=
"12"
>
<a-form-item
label=
"计划开始日期"
name=
"planStartDate"
:label-col=
"
{ style: { width: '120px' } }" :wrapperCol="{ span: 16 }">
<a-date-picker
v-model:value=
"formModel.planStartDate"
value-format=
"YYYY-MM-DD"
style=
"width: 100%"
:disabled=
"isDetail"
/>
</a-form-item>
</a-col>
<a-col
:span=
"12"
>
<a-form-item
label=
"计划结束日期"
name=
"planEndDate"
:label-col=
"
{ style: { width: '120px' } }" :wrapperCol="{ span: 16 }">
<a-date-picker
v-model:value=
"formModel.planEndDate"
value-format=
"YYYY-MM-DD"
style=
"width: 100%"
:disabled=
"isDetail"
/>
</a-form-item>
</a-col>
<a-col
:span=
"24"
>
<a-form-item
label=
"依据"
:label-col=
"
{ style: { width: '120px' } }" :wrapperCol="{ span: 16 }">
<div>
<a-tag
v-for=
"item in formModel.basisList"
:key=
"item.id"
closable
@
close=
"removeBasis(item.id)"
@
click=
"viewBasisDetail(item)"
<div
class=
"modal-body"
>
<a-form
ref=
"formRef"
:model=
"formModel"
class=
"styled-form"
:disabled=
"isDetail"
>
<!-- 第一行:计划名称 + 类型 -->
<a-row
:gutter=
"16"
>
<a-col
:span=
"12"
>
<a-form-item
label=
"计划名称"
name=
"projectName"
:label-col=
"
{ span: 6 }"
:wrapper-col="{ span: 18 }"
:rules="[{ required: true, message: '请输入计划名称' }]"
>
<a-input
v-model:value=
"formModel.projectName"
allow-clear
placeholder=
"请输入计划名称"
:disabled=
"isDetail"
/>
</a-form-item>
</a-col>
<a-col
:span=
"12"
>
<a-form-item
label=
"计划类型"
name=
"projectType"
:label-col=
"
{ span: 6 }"
:wrapper-col="{ span: 18 }"
:rules="[{ required: true, message: '请选择计划类型' }]"
>
<JCategorySelect
pcode=
"B09"
:value=
"formModel.projectType"
:disabled=
"isDetail"
@
change=
"changeJCategory"
placeholder=
"请选择类型"
/>
</a-form-item>
</a-col>
</a-row>
<!-- 第二行:执行部门 + 负责人 -->
<a-row
:gutter=
"16"
>
<a-col
:span=
"12"
>
<a-form-item
label=
"执行部门"
name=
"execDepCode"
:label-col=
"
{ span: 6 }" :wrapper-col="{ span: 18 }">
<JSelectDept
v-model:value=
"formModel.execDepCode"
placeholder=
"请选择执行部门"
:disabled=
"isDetail"
/>
</a-form-item>
</a-col>
<a-col
:span=
"12"
>
<a-form-item
label=
"负责人"
name=
"headId"
:label-col=
"
{ span: 6 }" :wrapper-col="{ span: 18 }">
<JSearchSelectDuty
v-model:value=
"formModel.headId"
placeholder=
"请选择负责人"
:disabled=
"isDetail"
/>
</a-form-item>
</a-col>
</a-row>
<!-- 第三行:开始日期 + 结束日期 -->
<a-row
:gutter=
"16"
>
<a-col
:span=
"12"
>
<a-form-item
label=
"计划开始日期"
name=
"planStartDate"
:label-col=
"
{ span: 6 }" :wrapper-col="{ span: 18 }">
<a-date-picker
v-model:value=
"formModel.planStartDate"
value-format=
"YYYY-MM-DD"
style=
"width: 100%"
:disabled=
"isDetail"
>
{{
item
.
name
}}
</a-tag>
<a-button
type=
"link"
v-if=
"showSelectorBtn"
@
click=
"openBasisSelector"
:disabled=
"isDetail"
>
添加依据
</a-button>
</div>
</a-form-item>
</a-col>
<a-col
:span=
"24"
>
<a-form-item
label=
"要求"
name=
"planRequest"
:label-col=
"
{ style: { width: '120px' } }" :wrapperCol="{ span: 16 }">
<a-textarea
v-model:value=
"formModel.planRequest"
rows=
"3"
allow-clear
:disabled=
"isDetail"
/>
</a-form-item>
</a-col>
<a-col
:span=
"12"
>
<a-form-item
label=
"交付物"
name=
"planDeliverable"
:label-col=
"
{ style: { width: '120px' } }" :wrapperCol="{ span: 16 }">
<a-input
v-model:value=
"formModel.planDeliverable"
allow-clear
:disabled=
"isDetail"
/>
</a-form-item>
</a-col>
<a-col
:span=
"24"
>
<a-form-item
label=
"描述"
name=
"projectDesc"
:label-col=
"
{ style: { width: '120px' } }" :wrapperCol="{ span: 16 }">
<a-textarea
v-model:value=
"formModel.projectDesc"
rows=
"3"
allow-clear
:disabled=
"isDetail"
/>
</a-form-item>
</a-col>
<a-col
:span=
"24"
>
<a-form-item
label=
"执行规则"
name=
"exeRule"
:label-col=
"
{ style: { width: '120px' } }" :wrapperCol="{ span: 16 }">
<vxe-radio-group
v-model=
"formModel.exeRule"
:options=
"exeRules"
/>
</a-form-item>
</a-col>
<a-col
:span=
"24"
>
<a-form-item
label=
"相关附件"
name=
"fileUploadPath"
:label-col=
"
{ style: { width: '120px' } }" :wrapperCol="{ span: 16 }">
<JUpload
v-model:value=
"formModel.fileUploadPath"
desText=
"支持扩展名: .rar .zip .doc .docx .pdf .jpg..."
:disabled=
"isDetail"
/>
</a-form-item>
</a-col>
<a-col
:span=
"0"
>
<a-form-item
name=
"id"
>
<a-input
v-model:value=
"formModel.id"
type=
"hidden"
/>
</a-form-item>
</a-col>
</a-row>
</a-form>
placeholder=
"选择开始日期"
/>
</a-form-item>
</a-col>
<a-col
:span=
"12"
>
<a-form-item
label=
"计划结束日期"
name=
"planEndDate"
:label-col=
"
{ span: 6 }" :wrapper-col="{ span: 18 }">
<a-date-picker
v-model:value=
"formModel.planEndDate"
value-format=
"YYYY-MM-DD"
style=
"width: 100%"
:disabled=
"isDetail"
placeholder=
"选择结束日期"
/>
</a-form-item>
</a-col>
</a-row>
<!-- 第四行:优先级 + 执行规则 -->
<a-row
:gutter=
"16"
>
<a-col
:span=
"12"
>
<a-form-item
label=
"优先级"
name=
"priority"
:label-col=
"
{ span: 6 }" :wrapper-col="{ span: 18 }">
<a-select
v-model:value=
"formModel.priority"
placeholder=
"请选择优先级"
:disabled=
"isDetail"
:options=
"[
{ label: '高', value: '1' },
{ label: '中', value: '2' },
{ label: '低', value: '3' },
]"
/>
</a-form-item>
</a-col>
<a-col
:span=
"12"
>
<a-form-item
label=
"执行规则"
name=
"exeRule"
:label-col=
"
{ span: 6 }" :wrapper-col="{ span: 18 }">
<a-radio-group
v-model:value=
"formModel.exeRule"
:disabled=
"isDetail"
>
<a-radio-button
:value=
"1"
>
一次性
</a-radio-button>
<a-radio-button
:value=
"2"
>
周期执行
</a-radio-button>
<a-radio-button
:value=
"3"
>
事件触发
</a-radio-button>
</a-radio-group>
</a-form-item>
</a-col>
</a-row>
<!-- 事件触发:填写触发事件名称 -->
<a-row
:gutter=
"16"
v-if=
"formModel.exeRule === 3"
>
<a-col
:span=
"12"
>
<a-form-item
label=
"触发事件名称"
name=
"triggerEventName"
:label-col=
"
{ span: 6 }"
:wrapper-col="{ span: 18 }"
:rules="[{ required: true, message: '请填写触发该计划执行的事件名称' }]"
>
<a-input
v-model:value=
"formModel.triggerEventName"
allow-clear
:disabled=
"isDetail"
placeholder=
"如:发现安全隐患、设备故障上报等"
/>
</a-form-item>
</a-col>
</a-row>
<!-- 周期执行:选择执行周期 -->
<a-row
:gutter=
"16"
v-if=
"formModel.exeRule === 2"
>
<a-col
:span=
"12"
>
<a-form-item
label=
"执行周期"
name=
"exePeriod"
:label-col=
"
{ span: 6 }"
:wrapper-col="{ span: 18 }"
:rules="[{ required: true, message: '请选择执行周期' }]"
>
<a-select
v-model:value=
"formModel.exePeriod"
placeholder=
"请选择执行周期"
:disabled=
"isDetail"
:options=
"[
{ label: '每日', value: 'daily' },
{ label: '每周', value: 'weekly' },
{ label: '每月', value: 'monthly' },
{ label: '每季度', value: 'quarterly' },
{ label: '每半年', value: 'halfyear' },
{ label: '每年', value: 'yearly' },
]"
/>
</a-form-item>
</a-col>
<a-col
:span=
"12"
>
<a-form-item
label=
"首次执行日期"
name=
"firstExecDate"
:label-col=
"
{ span: 6 }" :wrapper-col="{ span: 18 }">
<a-date-picker
v-model:value=
"formModel.firstExecDate"
value-format=
"YYYY-MM-DD"
style=
"width: 100%"
:disabled=
"isDetail"
placeholder=
"选择首次执行日期"
/>
</a-form-item>
</a-col>
</a-row>
<!-- 计划依据 -->
<a-form-item
label=
"计划依据"
name=
"planBasis"
:label-col=
"
{ span: 3 }" :wrapper-col="{ span: 21 }">
<div
class=
"basis-tags-container"
>
<a-tag
v-for=
"item in formModel.basisList"
:key=
"item.id"
closable
@
close=
"removeBasis(item.id)"
@
click=
"viewBasisDetail(item)"
:disabled=
"isDetail"
class=
"basis-tag"
>
{{
item
.
name
}}
</a-tag>
<a-button
type=
"dashed"
v-if=
"showSelectorBtn && !isDetail"
@
click=
"openBasisSelector"
class=
"btn-add-basis"
>
+ 添加依据
</a-button>
</div>
</a-form-item>
<!-- 计划要求 -->
<a-row
:gutter=
"16"
>
<a-col
:span=
"12"
>
<a-form-item
label=
"交付物"
name=
"planDeliverable"
:rules=
"[
{ required: true, message: '请输入交付物' }]"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 18 }"
>
<a-input
v-model:value=
"formModel.planDeliverable"
allow-clear
:disabled=
"isDetail"
placeholder=
"请输入需提交的交付物,如报告、台账等"
/>
</a-form-item>
</a-col>
</a-row>
<a-row
:gutter=
"16"
>
<a-col
:span=
"24"
>
<a-form-item
label=
"备注说明"
name=
"projectDesc"
:label-col=
"
{ span: 3 }" :wrapper-col="{ span: 21 }">
<a-textarea
v-model:value=
"formModel.projectDesc"
:rows=
"3"
allow-clear
:disabled=
"isDetail"
placeholder=
"请输入补充说明..."
/>
</a-form-item>
</a-col>
</a-row>
<a-row
:gutter=
"16"
>
<a-col
:span=
"24"
>
<a-form-item
label=
"相关附件"
name=
"fileUploadPath"
:label-col=
"
{ span: 3 }" :wrapper-col="{ span: 21 }">
<JUpload
v-model:value=
"formModel.fileUploadPath"
desText=
"支持扩展名: .rar .zip .doc .docx .pdf .jpg..."
:disabled=
"isDetail"
/>
</a-form-item>
</a-col>
</a-row>
<!-- 相关附件 -->
<!-- 隐藏字段 -->
<a-form-item
name=
"id"
style=
"display: none"
>
<a-input
v-model:value=
"formModel.id"
type=
"hidden"
/>
</a-form-item>
</a-form>
</div>
<!-- 选择依据组件 -->
<BasicCtrlSelector
ref=
"selectorRef"
@
click=
"handleSelectorClick"
/>
<!-- 依据详情抽屉 -->
...
...
@@ -97,12 +225,13 @@
import
{
list
}
from
'/@/views/newlib/components/api/AuditInnerCtrl.api'
;
import
{
listAll
}
from
'/@/views/newlib/components/api/AuditInnerCtrlItem.api'
;
import
{
saveOrUpdate
}
from
'../StPlanMan.api'
;
import
AuditInnerDetailDrawer
from
'/@/views/newlib/components/modal/AuditInnerDetailDrawer.vue'
;
import
BasicCtrlSelector
from
'/@/components/BasicCtrlSelector.vue'
;
defineProps
<
{
showSelectorBtn
?:
boolean
;
}
>
();
const
AuditInnerDetailDrawerRef
=
ref
();
const
showDetailDrawer
=
ref
(
false
);
const
selectedBasis
=
ref
<
any
>
(
null
);
...
...
@@ -116,6 +245,7 @@
AuditInnerDetailDrawerRef
.
value
?.
open
(
data
);
showDetailDrawer
.
value
=
true
;
};
// Emits声明
const
emit
=
defineEmits
([
'register'
,
'success'
,
'selector-click'
]);
const
isUpdate
=
ref
(
true
);
...
...
@@ -123,16 +253,20 @@
const
visible
=
ref
(
false
);
const
formRef
=
ref
();
const
exeRules
=
ref
([
{
value
:
1
,
label
:
'每发生'
},
{
value
:
2
,
label
:
'周期性'
},
{
value
:
3
,
label
:
'一次性'
},
]);
// const exeRules = ref([
// { value: 1, label: '事件触发' },
// { value: 2, label: '周期执行' },
// { value: 3, label: '一次性执行' },
// ]);
const
formModel
=
reactive
({
projectName
:
''
,
projectType
:
''
,
execDepCode
:
''
,
execDepName
:
''
,
headId
:
''
,
headName
:
''
,
priority
:
'2'
,
planRequest
:
''
,
planDeliverable
:
''
,
planStartDate
:
''
,
...
...
@@ -142,13 +276,24 @@
projectDesc
:
''
,
fileUploadPath
:
''
,
planBasis
:
''
,
exeRule
:
1
,
exeRule
:
1
,
triggerEventName
:
''
,
exePeriod
:
undefined
,
firstExecDate
:
''
,
completionRate
:
0
,
statusName
:
''
,
id
:
''
,
});
const
resetForm
=
()
=>
{
Object
.
assign
(
formModel
,
{
projectName
:
''
,
projectType
:
''
,
execDepCode
:
''
,
execDepName
:
''
,
headId
:
''
,
headName
:
''
,
priority
:
'2'
,
planRequest
:
''
,
planDeliverable
:
''
,
planStartDate
:
''
,
...
...
@@ -158,10 +303,16 @@
projectDesc
:
''
,
fileUploadPath
:
''
,
planBasis
:
''
,
exeRule
:
1
,
exeRule
:
1
,
triggerEventName
:
''
,
exePeriod
:
undefined
,
firstExecDate
:
''
,
completionRate
:
0
,
statusName
:
''
,
id
:
''
,
});
};
//表单赋值
const
[
registerModal
,
{
setModalProps
,
closeModal
}]
=
useModalInner
(
async
(
data
)
=>
{
setModalProps
({
confirmLoading
:
false
,
showCancelBtn
:
!!
data
?.
showFooter
,
showOkBtn
:
!!
data
?.
showFooter
});
...
...
@@ -184,10 +335,6 @@
const
handleSubmit
=
async
()
=>
{
try
{
const
valids
=
await
formRef
.
value
.
validate
();
//const idList = formModel.basisList?.map(item => item.id);
//if(idList) {
// valids["planBasis"] = JSON.stringify(formModel.basisList)
//}
if
(
formModel
.
basisList
)
{
valids
[
'planBasis'
]
=
JSON
.
stringify
(
formModel
.
basisList
);
...
...
@@ -205,7 +352,7 @@
setModalProps
({
confirmLoading
:
false
});
}
};
// const basisSelectMode = ref
<
'flat'
|
'nested'
>
(
'flat'
);
const
selectorRef
=
ref
();
const
basisSelectorConfig
=
{
title
:
'选择依据'
,
...
...
@@ -222,7 +369,6 @@
'a'
,
{
onClick
:
async
()
=>
{
// 加载二级数据并更新组件中的表格
const
detailData
=
await
listAll
({
checkid
:
record
.
id
});
selectorRef
.
value
.
setDetail
({
title
:
'详细内容'
,
...
...
@@ -278,22 +424,180 @@
const
changeJCategory
=
(
val
,
obj
)
=>
{
formModel
[
'projectType'
]
=
val
;
};
const
formatTp
=
(
row
)
=>
{
return
row
.
tp
==
1
?
'工具要求'
:
row
.
tp
==
2
?
'记录要求'
:
'制度要求'
;
};
const
formatComeFrom
=
(
row
)
=>
{
return
row
.
comeFrom
==
1
?
'自建'
:
row
.
tp
==
2
?
'合规库'
:
'内控制度'
;
};
const
formatExeRules
=
(
row
)
=>
{
return
row
.
comeFrom
==
1
?
'
每发生'
:
row
.
tp
==
2
?
'周期性'
:
'一次性
'
;
return
row
.
comeFrom
==
1
?
'
一次性'
:
row
.
tp
==
2
?
'周期执行'
:
'事件触发
'
;
};
</
script
>
<
style
scoped
>
.ant-tag
{
<
style
scoped
lang=
"less"
>
/* ==================== 弹窗主体 ==================== */
.modal-body {
max-height: 70vh;
// overflow: hidden;
}
.styled-form {
display: flex;
flex-direction: column;
gap: 4px;
overflow-x: hidden;
}
/* ==================== 表单项样式 ==================== */
:deep(.ant-form-item) {
margin-bottom: 16px;
}
:deep(.ant-form-item-label > label) {
font-size: 13px;
font-weight: 500;
}
/* ==================== 依据标签样式 ==================== */
.basis-tags-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
min-height: 32px;
}
.basis-tag {
display: inline-flex;
align-items: center;
padding: 4px 10px;
background: var(--color-primary-light);
border: 1px solid var(--color-primary);
border-radius: var(--radius);
color: var(--color-primary);
font-size: 13px;
cursor: pointer;
margin-right
:
4px
;
margin-bottom
:
4px
;
margin: 0;
transition: background 0.15s;
&:hover {
background: #d0d9f8;
}
:deep(.ant-tag-close-icon) {
color: var(--color-primary);
margin-left: 6px;
&:hover {
color: var(--color-error);
}
}
}
.btn-add-basis {
display: inline-flex;
align-items: center;
height: 30px;
border-radius: var(--radius);
border-color: var(--color-border-strong);
color: var(--color-text-secondary);
font-size: 13px;
&:hover {
border-color: var(--color-primary);
color: var(--color-primary);
background: var(--color-primary-light);
}
}
/* ==================== 单选按钮组样式 ==================== */
.styled-radio-group {
:deep(.ant-radio-button-wrapper) {
border-radius: 0;
border-color: var(--color-border);
&:first-child {
border-radius: var(--radius) 0 0 var(--radius);
}
&:last-child {
border-radius: 0 var(--radius) var(--radius) 0;
}
&:hover {
color: var(--color-primary);
}
&.ant-radio-button-wrapper-checked {
background: var(--color-primary);
border-color: var(--color-primary);
color: #ffffff;
}
}
}
/* ==================== 弹窗全局样式覆盖 ==================== */
:deep(.ant-modal-header) {
padding: 14px 24px;
border-bottom: 1px solid var(--color-border);
background: var(--color-bg-section);
}
:deep(.ant-modal-title) {
font-size: 15px;
font-weight: 600;
color: var(--color-text-primary);
}
:deep(.ant-modal-body) {
padding: 20px 24px;
background: var(--color-bg-white);
}
:deep(.ant-modal-footer) {
padding: 12px 24px;
border-top: 1px solid var(--color-border);
background: var(--color-bg-section);
}
:deep(.ant-modal-footer .ant-btn) {
border-radius: var(--radius);
height: 34px;
padding: 0 18px;
font-weight: 500;
font-size: 13px;
box-shadow: none;
}
:deep(.ant-modal-footer .ant-btn-primary) {
background: var(--color-primary);
border-color: var(--color-primary);
&:hover {
background: var(--color-primary-hover);
border-color: var(--color-primary-hover);
}
}
:deep(.ant-modal-footer .ant-btn-default) {
border-color: var(--color-border-strong);
color: var(--color-text-secondary);
background: var(--color-bg-white);
&:hover {
border-color: var(--color-primary);
color: var(--color-primary);
background: var(--color-primary-light);
}
}
/* ==================== 响应式设计 ==================== */
@media (max-width: 768px) {
.modal-body {
max-height: 60vh;
}
}
</
style
>
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论