提交 54da6b43 authored 作者: liuluyu's avatar liuluyu

更新计划管理页面样式、参数和流程

上级 fd7edf9b
import { defineStore } from 'pinia';
import { reactive, ref } from 'vue';
export const usePlanFormStore = defineStore('planFormStore', () => {
// 表单提交回调函数
const submitCallback = ref<(() => Promise<void>) | null>(null);
// 表单数据缓存
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;
};
// 执行表单提交
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 updateFormDataCache = (data: any) => {
Object.assign(formDataCache, data);
};
// 清除表单缓存
const clearFormCache = () => {
Object.assign(formDataCache, {
id: '',
executeStatus: '',
actualStartTime: null,
actualEndTime: null,
executeEcord: '',
attachments: [],
});
formLoadingState.lastLoadedId = '';
};
// 设置加载状态
const setLoadingState = (isLoading: boolean) => {
formLoadingState.isLoading = isLoading;
};
// 设置最后加载的ID
const setLastLoadedId = (id: string) => {
formLoadingState.lastLoadedId = id;
};
return {
submitCallback,
formDataCache,
formLoadingState,
registerSubmitCallback,
executeSubmit,
updateFormDataCache,
clearFormCache,
setLoadingState,
setLastLoadedId,
};
});
...@@ -34,11 +34,7 @@ ...@@ -34,11 +34,7 @@
@change="handleChange" @change="handleChange"
v-if="shownextNodeNameSele" v-if="shownextNodeNameSele"
> >
<a-select-option <a-select-option v-for="item in tableOptions" :key="item.id" :value="item.value">
v-for="item in tableOptions"
:key="item.id"
:value="item.value"
>
{{ item.name }} {{ item.name }}
</a-select-option> </a-select-option>
</a-select> </a-select>
...@@ -62,8 +58,8 @@ ...@@ -62,8 +58,8 @@
<div v-show="formTp == 1" class="form-wrapper"> <div v-show="formTp == 1" class="form-wrapper">
<FlowInnerForm ref="refCruInnerForm" :key="formKey" style="width: 100%" /> <FlowInnerForm ref="refCruInnerForm" :key="formKey" style="width: 100%" />
</div> </div>
<div v-show="formTp == 2" class="iframe-container" style="background-color: red; height: 470px"> <div v-show="formTp == 2" class="iframe-container" style="height: 470px">
<iFrame :src="formUrl" class="responsive-iframe" style="width: 100%; height: 100%" /> <iFrame :key="formKey" :src="formUrl" class="responsive-iframe" style="width: 100%; height: 100%" />
</div> </div>
</div> </div>
</a-card> </a-card>
...@@ -216,7 +212,7 @@ ...@@ -216,7 +212,7 @@
// API // API
import { flowRecord } from '/@/components/Process/api/finished'; import { flowRecord } from '/@/components/Process/api/finished';
import { flowXmlAndNode } from '/@/components/Process/api/definition'; import { flowXmlAndNode } from '/@/components/Process/api/definition';
import { complete, flowTaskForm, getNextFlowNode,getMyTaskFlow} from '/@/components/Process/api/todo'; import { complete, flowTaskForm, getNextFlowNode, getMyTaskFlow } from '/@/components/Process/api/todo';
import { flowTaskInfo } from '/@/components/Process/api/process'; import { flowTaskInfo } from '/@/components/Process/api/process';
// 组件 // 组件
...@@ -241,7 +237,6 @@ ...@@ -241,7 +237,6 @@
const formKey = ref(0); const formKey = ref(0);
const showApprovalUi = ref(true); const showApprovalUi = ref(true);
const flowData = ref<any>({}); const flowData = ref<any>({});
const flowRecordList = ref<any[]>([]); const flowRecordList = ref<any[]>([]);
const nextNodeName = ref(''); const nextNodeName = ref('');
...@@ -333,6 +328,7 @@ ...@@ -333,6 +328,7 @@
placeholder: '请选择接收人', placeholder: '请选择接收人',
mode: 'multiple', mode: 'multiple',
}, },
rules: [{ required: true, message: '请选择接收人' }],
}, },
{ {
label: '接收角色', label: '接收角色',
...@@ -387,13 +383,8 @@ ...@@ -387,13 +383,8 @@
await nextTick(); await nextTick();
if (!refCruInnerForm.value) {
throw new Error('表单组件未找到');
}
let { taskId } = taskForm; let { taskId } = taskForm;
if (!taskId) { if (!taskId) {
// try to resolve taskId by business id from workFlowData // try to resolve taskId by business id from workFlowData
const dataId = workFlowData.value?.dataId || workFlowData.value?.id || workFlowData.value?.businessId; const dataId = workFlowData.value?.dataId || workFlowData.value?.id || workFlowData.value?.businessId;
...@@ -416,17 +407,32 @@ ...@@ -416,17 +407,32 @@
const resData = await flowTaskForm({ taskId }); const resData = await flowTaskForm({ taskId });
const { flowForm = {} } = resData; const { flowForm = {} } = resData;
formTp.value = flowForm.formTp; // normalize form type to number for consistent comparisons
const tp = Number(flowForm.formTp);
formTp.value = tp;
if (flowForm.formTp == '1') { if (tp === 1) {
// ensure the inner form component is recreated before initializing
formKey.value++;
await nextTick();
if (!refCruInnerForm.value) {
// wait a tick more in case component hasn't mounted
await nextTick();
}
if (refCruInnerForm.value && typeof refCruInnerForm.value.iniData === 'function') {
await refCruInnerForm.value.iniData(flowForm); await refCruInnerForm.value.iniData(flowForm);
} else { } else {
let formValues = resData.formValues; throw new Error('内嵌表单组件未就绪');
}
} else {
let formValues = resData.formValues || {};
let formUrlparval = formValues.dataId; let formUrlparval = formValues.dataId;
let formUrlpar = formValues.dataName; let formUrlpar = formValues.dataName || 'id';
const connector = flowForm.formUrl.includes('?') ? '&' : '?'; const connector = flowForm.formUrl && flowForm.formUrl.includes('?') ? '&' : '?';
formUrl.value = flowForm.formUrl + connector + formUrlpar + '=' + formUrlparval; formUrl.value = (flowForm.formUrl || '') + connector + formUrlpar + '=' + formUrlparval;
//formUrl.value = flowForm.formUrl + '?' + formUrlpar + '=' + formUrlparval; // force iframe reload when src is set/changed
formKey.value++;
await nextTick();
console.error('表单url:', formUrl.value); console.error('表单url:', formUrl.value);
} }
formError.value = false; formError.value = false;
...@@ -517,7 +523,7 @@ ...@@ -517,7 +523,7 @@
values: {}, values: {},
}; };
//找到选择的下一步节点 //找到选择的下一步节点
if(nextNodenum.value>1){ if (nextNodenum.value > 1) {
submitData.values['userTaskid'] = nextNodeNameSelevue.value; submitData.values['userTaskid'] = nextNodeNameSelevue.value;
} }
...@@ -527,12 +533,11 @@ ...@@ -527,12 +533,11 @@
return false; return false;
} }
const formData = await validate(); const formData = await validate();
Object.assign(submitData, formData); Object.assign(submitData, formData);
submitData.comment = submitData.comment || ''; submitData.comment = submitData.comment || '';
//alert("dd"+isFixed.value); //alert("dd"+isFixed.value);
// if (!isFixed.value) { // if (!isFixed.value) {
//alert("dd"+userType.value); //alert("dd"+userType.value);
if (userType.value === 'user') { if (userType.value === 'user') {
...@@ -583,24 +588,21 @@ ...@@ -583,24 +588,21 @@
const handleChange = (value: string) => { const handleChange = (value: string) => {
console.log(`selected ${value}`); console.log(`selected ${value}`);
nextNodeNameSelevue.value=value; nextNodeNameSelevue.value = value;
console.log(" nextNodeNameSelevue.value ",nextNodeNameSelevue.value); console.log(' nextNodeNameSelevue.value ', nextNodeNameSelevue.value);
if(value==0){ if (value == 0) {
ChangeSelectNodeAfter(res0); ChangeSelectNodeAfter(res0);
} }
if(value==1){ if (value == 1) {
ChangeSelectNodeAfter(res1); ChangeSelectNodeAfter(res1);
} }
if(value==2){ if (value == 2) {
ChangeSelectNodeAfter(res2); ChangeSelectNodeAfter(res2);
} }
}; };
function ChangeSelectNodeAfter(res) { function ChangeSelectNodeAfter(res) {
isFixed.value = res.dataType == 'fixed' ? true : false; isFixed.value = res.dataType == 'fixed' ? true : false;
if (isFixed.value && !isApproval.value) { if (isFixed.value && !isApproval.value) {
showApprovalUi.value; showApprovalUi.value;
...@@ -609,8 +611,6 @@ ...@@ -609,8 +611,6 @@
nextNode.value = res; nextNode.value = res;
nextNodeName.value = res.userTask?.name || ''; nextNodeName.value = res.userTask?.name || '';
//alert(""+res.dataType);
if (res.type === 'role') { if (res.type === 'role') {
removeSchemaByFiled('checkSendUser'); removeSchemaByFiled('checkSendUser');
if (res.dataType === 'fixed' && res.userTask?.candidateGroups) { if (res.dataType === 'fixed' && res.userTask?.candidateGroups) {
...@@ -626,16 +626,13 @@ ...@@ -626,16 +626,13 @@
}); });
} }
} }
} }
const iniData = async (data: any) => { const iniData = async (data: any) => {
try { try {
// 设置工作流数据 // 设置工作流数据
workFlowData.value = data; workFlowData.value = data;
console.log("why iniData",data); console.log('why iniData', data);
// 更新任务表单 // 更新任务表单
Object.assign(taskForm, { Object.assign(taskForm, {
...@@ -658,58 +655,49 @@ ...@@ -658,58 +655,49 @@
if (taskForm.taskId) { if (taskForm.taskId) {
const reslist = await getNextFlowNode({ taskId: taskForm.taskId }); const reslist = await getNextFlowNode({ taskId: taskForm.taskId });
console.log('getNextFlowNode reslist ', reslist);
console.log("getNextFlowNode reslist ",reslist);
if (reslist == null) { if (reslist == null) {
// 到达流程结束节点:设置展示状态但不要提前返回,仍需加载表单和流转记录
nextNodeName.value = '结束'; nextNodeName.value = '结束';
removeSchemaByFiled('checkSendRole'); removeSchemaByFiled('checkSendRole');
removeSchemaByFiled('checkSendUser'); removeSchemaByFiled('checkSendUser');
showApprovalUi.value = isApproval.value; showApprovalUi.value = isApproval.value;
shownextNodeNameSele.value =false; shownextNodeNameSele.value = false;
shownextNodeNametext.value =true; shownextNodeNametext.value = true;
return false; // 不再 return,这样后续仍会继续加载流转记录和表单
}else{ } else {
shownextNodeNameSele.value =true; shownextNodeNameSele.value = true;
shownextNodeNametext.value =false; shownextNodeNametext.value = false;
nextNodenum.value=reslist.length;
nextNodenum.value = reslist.length;
//why工作流修改 //why工作流修改
// 使用 for 循环赋值 // 使用 for 循环赋值
for (let i = 0; i <reslist.length; i++) { for (let i = 0; i < reslist.length; i++) {
let resb = reslist[i]; let resb = reslist[i];
tableOptions.value.push({ tableOptions.value.push({
id: `${i}`, id: `${i}`,
name: `${resb.userTask.name}`, name: `${resb.userTask.name}`,
value: `${i}` value: `${i}`,
}); });
} }
nextNodeNameSele.value = tableOptions.value[0].name; nextNodeNameSele.value = tableOptions.value[0].name;
// tableOptions.value = res.userTaskList;
const res = reslist[0]; // 获取第一行数据 const res = reslist[0]; // 获取第一行数据
console.log("res ",res); console.log('res ', res);
ChangeSelectNodeAfter(res); ChangeSelectNodeAfter(res);
if(reslist.length==1){ if (reslist.length == 1) {
res0.value = reslist[0]; res0.value = reslist[0];
} }
if(reslist.length==2){ if (reslist.length == 2) {
res0.value = reslist[0]; res0.value = reslist[0];
res1.value = reslist[1]; res1.value = reslist[1];
} }
if(reslist.length==3){ if (reslist.length == 3) {
res0.value = reslist[0]; res0.value = reslist[0];
res1.value = reslist[1]; res1.value = reslist[1];
res2.value = reslist[2]; res2.value = reslist[2];
} }
} }
} }
...@@ -732,7 +720,28 @@ ...@@ -732,7 +720,28 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
/* ==================== 柔和中性风格 - CSS 变量 ==================== */
.execute-form-container {
background: var(--color-bg-white);
min-height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif;
}
.app-container { .app-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;
height: 100vh; height: 100vh;
margin: 0; margin: 0;
padding: 0; padding: 0;
......
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { BasicColumn, FormSchema } from '/@/components/Table'; import { BasicColumn, FormSchema } from '/@/components/Table';
import { render } from '/@/utils/common/renderUtils'; 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[] = [ export const columns: BasicColumn[] = [
{ {
title: '计划名称', title: '计划名称',
align: 'left', align: 'left',
dataIndex: 'projectName', dataIndex: 'projectName',
width: 200, width: 180,
ellipsis: true, ellipsis: true,
}, },
{ {
title: '类型', title: '计划类型',
align: 'center', align: 'center',
dataIndex: 'projectTypeName', dataIndex: 'projectTypeName',
width: 120, width: 120,
ellipsis: true, ellipsis: true,
}, },
// { {
// title: '执行部门', title: '执行部门',
// align: 'center', align: 'center',
// dataIndex: 'execDepName', dataIndex: 'execDepName',
// width: 150, width: 140,
// ellipsis: true, ellipsis: true,
// }, },
{ {
title: '负责人', title: '负责人',
align: 'center', align: 'center',
...@@ -32,19 +68,39 @@ export const columns: BasicColumn[] = [ ...@@ -32,19 +68,39 @@ export const columns: BasicColumn[] = [
ellipsis: true, ellipsis: true,
}, },
{ {
title: '计划开始日期', title: '优先级',
align: 'center', align: 'center',
dataIndex: 'planStartDate', dataIndex: 'priority',
width: 130, width: 80,
ellipsis: true, ellipsis: true,
customRender: ({ text }) => { customRender: ({ text }) => {
return !text ? '-' : text.length > 10 ? text.substr(0, 10) : text; const priorityMap = {
'1': '高',
'2': '中',
'3': '低',
};
return priorityMap[text] || '-';
}, },
}, },
{ {
title: '计划结束日期', title: '执行规则',
align: 'center', 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, width: 130,
ellipsis: true, ellipsis: true,
customRender: ({ text }) => { customRender: ({ text }) => {
...@@ -52,22 +108,17 @@ export const columns: BasicColumn[] = [ ...@@ -52,22 +108,17 @@ export const columns: BasicColumn[] = [
}, },
}, },
{ {
title: '执行规则', title: '计划结束日期',
align: 'center', align: 'center',
dataIndex: 'exeRule', dataIndex: 'planEndDate',
width: 100, width: 130,
ellipsis: true, ellipsis: true,
customRender: ({ text }) => { customRender: ({ text }) => {
const ruleMap = { return !text ? '-' : text.length > 10 ? text.substr(0, 10) : text;
1: '每发生',
2: '周期性',
3: '一次性',
};
return ruleMap[text] || '-';
}, },
}, },
{ {
title: '状态', title: '计划状态',
align: 'center', align: 'center',
dataIndex: 'statusName', dataIndex: 'statusName',
width: 100, width: 100,
...@@ -91,7 +142,8 @@ export const columns: BasicColumn[] = [ ...@@ -91,7 +142,8 @@ export const columns: BasicColumn[] = [
ellipsis: false, ellipsis: false,
}, },
]; ];
//查询数据
// 查询表单数据
export const searchFormSchema: FormSchema[] = [ export const searchFormSchema: FormSchema[] = [
{ {
label: '计划名称', label: '计划名称',
...@@ -100,7 +152,7 @@ export const searchFormSchema: FormSchema[] = [ ...@@ -100,7 +152,7 @@ export const searchFormSchema: FormSchema[] = [
colProps: { span: 6 }, colProps: { span: 6 },
}, },
{ {
label: '类型', label: '计划类型',
field: 'projectType', field: 'projectType',
component: 'Select', component: 'Select',
colProps: { span: 6 }, colProps: { span: 6 },
...@@ -112,12 +164,58 @@ export const searchFormSchema: FormSchema[] = [ ...@@ -112,12 +164,58 @@ export const searchFormSchema: FormSchema[] = [
{ {
label: '执行部门', label: '执行部门',
field: 'execDepCode', field: 'execDepCode',
component: 'Input', component: 'Select',
colProps: { span: 6 }, 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[] = [ export const formSchema: FormSchema[] = [
{ {
label: '计划名称', label: '计划名称',
...@@ -126,11 +224,11 @@ export const formSchema: FormSchema[] = [ ...@@ -126,11 +224,11 @@ export const formSchema: FormSchema[] = [
colProps: { lg: 12 }, colProps: { lg: 12 },
itemProps: { labelCol: { xs: { span: 24 }, sm: { span: 6 } }, wrapperCol: { xs: { span: 24 }, sm: { span: 18 } } }, itemProps: { labelCol: { xs: { span: 24 }, sm: { span: 6 } }, wrapperCol: { xs: { span: 24 }, sm: { span: 18 } } },
dynamicRules: ({ model, schema }) => { dynamicRules: ({ model, schema }) => {
return [{ required: true, message: '请输入项目名称!' }]; return [{ required: true, message: '请输入计划名称!' }];
}, },
}, },
{ {
label: '类型', label: '计划类型',
field: 'projectType', field: 'projectType',
component: 'JCategorySelect', component: 'JCategorySelect',
componentProps: { componentProps: {
...@@ -140,10 +238,29 @@ export const formSchema: FormSchema[] = [ ...@@ -140,10 +238,29 @@ export const formSchema: FormSchema[] = [
colProps: { lg: 12 }, colProps: { lg: 12 },
itemProps: { labelCol: { xs: { span: 24 }, sm: { span: 6 } }, wrapperCol: { xs: { span: 24 }, sm: { span: 18 } } }, itemProps: { labelCol: { xs: { span: 24 }, sm: { span: 6 } }, wrapperCol: { xs: { span: 24 }, sm: { span: 18 } } },
dynamicRules: ({ model, schema }) => { 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: '计划开始日期', label: '计划开始日期',
field: 'planStartDate', field: 'planStartDate',
...@@ -151,7 +268,7 @@ export const formSchema: FormSchema[] = [ ...@@ -151,7 +268,7 @@ export const formSchema: FormSchema[] = [
colProps: { lg: 12 }, colProps: { lg: 12 },
itemProps: { labelCol: { xs: { span: 24 }, sm: { span: 6 } }, wrapperCol: { xs: { span: 24 }, sm: { span: 18 } } }, itemProps: { labelCol: { xs: { span: 24 }, sm: { span: 6 } }, wrapperCol: { xs: { span: 24 }, sm: { span: 18 } } },
dynamicRules: ({ model, schema }) => { dynamicRules: ({ model, schema }) => {
return [{ required: false, message: '请选择计划开始日期!' }]; return [{ required: true, message: '请选择计划开始日期!' }];
}, },
}, },
{ {
...@@ -161,26 +278,81 @@ export const formSchema: FormSchema[] = [ ...@@ -161,26 +278,81 @@ export const formSchema: FormSchema[] = [
colProps: { lg: 12 }, colProps: { lg: 12 },
itemProps: { labelCol: { xs: { span: 24 }, sm: { span: 6 } }, wrapperCol: { xs: { span: 24 }, sm: { span: 18 } } }, itemProps: { labelCol: { xs: { span: 24 }, sm: { span: 6 } }, wrapperCol: { xs: { span: 24 }, sm: { span: 18 } } },
dynamicRules: ({ model, schema }) => { dynamicRules: ({ model, schema }) => {
return [{ required: false, message: '请选择计划结束日期!' }]; return [{ required: true, message: '请选择计划结束日期!' }];
}, },
}, },
{ {
label: '依据', label: '优先级',
field: 'planBasis', field: 'priority',
component: 'Input', // 使用基础组件类型 component: 'Select',
slot: 'planBasis', colProps: { lg: 12 },
itemProps: { itemProps: { labelCol: { xs: { span: 24 }, sm: { span: 6 } }, wrapperCol: { xs: { span: 24 }, sm: { span: 18 } } },
labelCol: { xs: { span: 24 }, sm: { span: 3 } }, componentProps: {
wrapperCol: { xs: { span: 24 }, sm: { span: 21 } }, options: priorityOptions,
placeholder: '请选择优先级',
}, },
}, },
{ {
label: '要求', label: '执行规则',
field: 'planRequest', field: 'exeRule',
component: 'InputTextArea', 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 } } }, 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 }) => { 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[] = [ ...@@ -193,30 +365,13 @@ export const formSchema: FormSchema[] = [
return [{ required: true, message: '请输入交付物!' }]; return [{ required: true, message: '请输入交付物!' }];
}, },
}, },
// { {
// label: '审核类型', 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: '描述',
field: 'projectDesc', field: 'projectDesc',
component: 'InputTextArea', component: 'InputTextArea',
itemProps: { labelCol: { xs: { span: 24 }, sm: { span: 3 } }, wrapperCol: { xs: { span: 24 }, sm: { span: 21 } } }, itemProps: { labelCol: { xs: { span: 24 }, sm: { span: 3 } }, wrapperCol: { xs: { span: 24 }, sm: { span: 21 } } },
dynamicRules: ({ model, schema }) => { dynamicRules: ({ model, schema }) => {
return [{ required: false, message: '请输入项目描述!' }]; return [{ required: false, message: '请输入备注说明!' }];
}, },
}, },
{ {
...@@ -233,7 +388,7 @@ export const formSchema: FormSchema[] = [ ...@@ -233,7 +388,7 @@ export const formSchema: FormSchema[] = [
itemProps: { labelCol: { xs: { span: 24 }, sm: { span: 3 } }, wrapperCol: { xs: { span: 24 }, sm: { span: 21 } } }, itemProps: { labelCol: { xs: { span: 24 }, sm: { span: 3 } }, wrapperCol: { xs: { span: 24 }, sm: { span: 21 } } },
show: false, show: false,
}, },
// TODO 主键隐藏字段,目前写死为ID // 主键隐藏字段
{ {
label: '', label: '',
field: 'id', field: 'id',
......
<template> <template>
<div> <div class="plan-management-page">
<div class="jeecg-basic-table-form-container" @keyup.enter="searchQuery"> <!-- 页面头部区域 -->
<a-form :model="queryParam" :label-col="labelCol" :wrapper-col="wrapperCol"> <!-- <div class="page-header">
<a-row :gutter="24"> <div class="header-content">
<a-col :lg="6"> <div class="header-left">
<a-form-item :label="searchFormSchema[0].label"> <h1 class="page-title">计划编制管理</h1>
<JInput placeholder="请输入" v-model:value="queryParam[searchFormSchema[0].field]" /> <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-form-item>
</a-col> </a-col>
<a-col :lg="6"> <a-col :xl="5" :lg="8" :md="12" :sm="24">
<a-form-item :label="searchFormSchema[1].label"> <a-form-item label="计划类型">
<JSearchSelect placeholder="请输入" v-model:value="queryParam[searchFormSchema[1].field]" dict="projecttype" /> <JSearchSelect placeholder="请选择类型" v-model:value="queryParam['projectType']" dict="projecttype" />
</a-form-item> </a-form-item>
</a-col> </a-col>
<!-- <a-col :lg="6"> <a-col :xl="5" :lg="8" :md="12" :sm="24">
<a-form-item :label="searchFormSchema[2].label"> <a-form-item label="执行部门">
<JSelectDept placeholder="请输入" v-model:value="jSelectDeptVal" :multiple="false" @change="updateJSelectDept" /> <JSelectDept placeholder="请选择执行部门" v-model:value="queryParam['execDepCode']" />
</a-form-item> </a-form-item>
</a-col> --> </a-col>
<a-col :lg="6"> <a-col :xl="4" :lg="8" :md="12" :sm="24">
<a-form-item> <a-form-item label="计划状态">
<a-space :size="5"> <a-select
<a-button type="primary" preIcon="ant-design:search-outlined" @click="searchQuery">查询</a-button> v-model:value="queryParam['status']"
<a-button type="primary" preIcon="ant-design:reload-outlined" @click="searchReset">重置</a-button> 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-space>
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
</a-form> </a-form>
</div>
<!--引用表格--> <!-- 表格区域 -->
<BasicTable @register="registerTable" :rowSelection="rowSelection"> <div class="table-section">
<!--插槽:table标题--> <BasicTable @register="registerTable" :rowSelection="rowSelection" class="flat-table">
<!-- 插槽:table标题 -->
<template #tableTitle> <template #tableTitle>
<a-button type="primary" @click="handleAdd" preIcon="ant-design:plus-outlined"> 新建</a-button> <div class="table-toolbar">
<a-button v-show="showUpBtn" type="primary" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button> <a-button type="primary" @click="handleAdd">新建计划</a-button>
<j-upload-button v-show="showUpBtn" type="primary" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-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"> <a-dropdown v-if="selectedRowKeys.length > 0 && showUpBtn">
<template #overlay> <template #overlay>
<a-menu> <a-menu>
<a-menu-item key="1" @click="batchHandleDelete"> <a-menu-item key="1" @click="batchHandleDelete" class="danger-item">
<Icon icon="ant-design:delete-outlined" /> <Icon icon="ant-design:delete-outlined" />
删除 批量删除
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
</template> </template>
<a-button <a-button>
>批量操作 已选 {{ selectedRowKeys.length }} 项
<Icon icon="mdi:chevron-down" /> <Icon icon="mdi:chevron-down" />
</a-button> </a-button>
</a-dropdown> </a-dropdown>
</div>
</template> </template>
<!--操作栏--> <!-- 操作栏 -->
<template #action="{ record }"> <template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" /> <TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
</template> </template>
<!--字段回显插槽--> <!-- 字段回显插槽 -->
<template #htmlSlot="{ text }"> <template #htmlSlot="{ text }">
<div v-html="text"></div> <div v-html="text"></div>
</template> </template>
<template #fileSlot="{ text }"> <template #fileSlot="{ text }">
<span v-if="!text" style="font-size: 12px; font-style: italic">无文件</span> <span v-if="!text" class="no-file">无文件</span>
<a-button v-else :ghost="true" type="primary" preIcon="ant-design:download-outlined" size="small" @click="downloadFile(text)" <a-button v-else :ghost="true" type="primary" size="small" @click="downloadFile(text)">下载</a-button>
>下载</a-button
>
</template> </template>
</BasicTable> </BasicTable>
</div>
<!-- 表单区域 --> <!-- 表单区域 -->
<StPlanManModal @register="registerModal" @success="handleSuccess" :showSelectorBtn="true" /> <StPlanManModal @register="registerModal" @success="handleSuccess" :showSelectorBtn="true" />
<StPlanManFlowModal ref="refStPlanManFlow" /> <StPlanManFlowModal ref="refStPlanManFlow" />
</div> </div>
<!-- 待办抽屉 --> <!-- 待办抽屉 -->
<div v-if="isShowDrawer"> <div v-if="isShowDrawer">
<a-drawer <a-drawer destroyOnClose v-model:open="isShowDrawer" class="flat-drawer" title="待办任务" placement="right" width="90%">
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"
>
<template #extra> <template #extra>
<div style="float: right"> <div class="drawer-tags">
<a-tag style="margin-left: 10px">发起人:{{ startUser }}</a-tag> <span class="tag">发起人: {{ startUser }}</span>
<a-tag>任务节点:{{ taskName }}</a-tag> <span class="tag">任务节点: {{ taskName }}</span>
</div> </div>
</template> </template>
<TodoIndex v-if="isShowDrawer" ref="refTodoIndex" @callback="handleSuccess" /> <TodoIndex v-if="isShowDrawer" ref="refTodoIndex" @callback="handleSuccess" />
</a-drawer> </a-drawer>
</div> </div>
<!-- 流程详情抽屉 --> <!-- 流程详情抽屉 -->
<div v-if="isShowDetailDrawer"> <div v-if="isShowDetailDrawer">
<a-drawer <a-drawer destroyOnClose v-model:open="isShowDetailDrawer" class="flat-drawer" title="流程详情" placement="right" width="90%">
destroyOnClose
v-model:open="isShowDetailDrawer"
class="custom-class"
root-class-name="root-class-name"
:root-style="{ color: 'blue' }"
title="流程详情111"
placement="right"
width="90%"
style="margin: 0px; padding: 0px"
>
<template #extra> <template #extra>
<div style="float: right">
<a-button type="text" @click="handleDetailDrawerClose">关闭</a-button> <a-button type="text" @click="handleDetailDrawerClose">关闭</a-button>
</div>
</template> </template>
<Detail ref="refDetail" /> <Detail ref="refDetail" />
</a-drawer> </a-drawer>
...@@ -136,6 +208,7 @@ ...@@ -136,6 +208,7 @@
import { todoList, getMyTaskFlow } from '/@/components/Process/api/todo'; import { todoList, getMyTaskFlow } from '/@/components/Process/api/todo';
// 引入详情组件 // 引入详情组件
import Detail from '../../flowable/task/myProcess/components/Detail.vue'; import Detail from '../../flowable/task/myProcess/components/Detail.vue';
const refTodoIndex = ref(); const refTodoIndex = ref();
const isShowDrawer = ref(false); const isShowDrawer = ref(false);
const startUser = ref<string>(''); const startUser = ref<string>('');
...@@ -147,10 +220,12 @@ ...@@ -147,10 +220,12 @@
// 流程详情抽屉相关 // 流程详情抽屉相关
const refDetail = ref(); const refDetail = ref();
const isShowDetailDrawer = ref(false); const isShowDetailDrawer = ref(false);
//注册model
// 注册model
const [registerModal, { openModal }] = useModal(); const [registerModal, { openModal }] = useModal();
const jSelectDeptVal = ref([]); const jSelectDeptVal = ref([]);
//注册table数据
// 注册table数据
const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({ const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({
tableProps: { tableProps: {
title: '计划编制', title: '计划编制',
...@@ -159,7 +234,6 @@ ...@@ -159,7 +234,6 @@
canResize: false, canResize: false,
useSearchForm: false, useSearchForm: false,
formConfig: { formConfig: {
//labelWidth: 120,
schemas: searchFormSchema, schemas: searchFormSchema,
autoSubmitOnEnter: true, autoSubmitOnEnter: true,
showAdvancedButton: true, showAdvancedButton: true,
...@@ -167,7 +241,7 @@ ...@@ -167,7 +241,7 @@
fieldMapToTime: [], fieldMapToTime: [],
}, },
actionColumn: { actionColumn: {
width: 240, width: 280,
fixed: 'right', fixed: 'right',
}, },
showTableSetting: false, showTableSetting: false,
...@@ -186,15 +260,17 @@ ...@@ -186,15 +260,17 @@
}); });
const [registerTable, { setProps, reload }, { rowSelection, selectedRowKeys }] = tableContext; const [registerTable, { setProps, reload }, { rowSelection, selectedRowKeys }] = tableContext;
const labelCol = reactive({ const labelCol = reactive({
xs: { span: 24 }, xs: { span: 24 },
sm: { span: 8 }, sm: { span: 6 },
}); });
const wrapperCol = reactive({ const wrapperCol = reactive({
xs: { span: 24 }, xs: { span: 24 },
sm: { span: 16 }, sm: { span: 18 },
}); });
const queryParam = reactive({}); const queryParam = reactive({});
function initParam() { function initParam() {
const schemas = unref(searchFormSchema); const schemas = unref(searchFormSchema);
schemas.forEach((item) => { schemas.forEach((item) => {
...@@ -205,6 +281,7 @@ ...@@ -205,6 +281,7 @@
jSelectDeptVal.value = []; jSelectDeptVal.value = [];
} }
initParam(); initParam();
function updateJSelectDept(val) { function updateJSelectDept(val) {
if (val.length > 0) { if (val.length > 0) {
queryParam[searchFormSchema[2].field] = val[0]; queryParam[searchFormSchema[2].field] = val[0];
...@@ -213,14 +290,17 @@ ...@@ -213,14 +290,17 @@
queryParam[searchFormSchema[2].field] = ''; queryParam[searchFormSchema[2].field] = '';
} }
} }
function searchQuery() { function searchQuery() {
setProps({ searchInfo: toRaw(queryParam) }); setProps({ searchInfo: toRaw(queryParam) });
reload(); reload();
} }
function searchReset() { function searchReset() {
initParam(); initParam();
reload(); reload();
} }
/** /**
* 新增事件 * 新增事件
*/ */
...@@ -230,6 +310,7 @@ ...@@ -230,6 +310,7 @@
showFooter: true, showFooter: true,
}); });
} }
/** /**
* 编辑事件 * 编辑事件
*/ */
...@@ -240,6 +321,7 @@ ...@@ -240,6 +321,7 @@
showFooter: true, showFooter: true,
}); });
} }
/** /**
* 详情 * 详情
*/ */
...@@ -250,6 +332,7 @@ ...@@ -250,6 +332,7 @@
showFooter: false, showFooter: false,
}); });
} }
/** /**
* 流转记录 * 流转记录
*/ */
...@@ -263,9 +346,6 @@ ...@@ -263,9 +346,6 @@
try { try {
const myTaskFlow = await getMyTaskFlow({ deploymentId: record.deployId, dataId: record.id }); const myTaskFlow = await getMyTaskFlow({ deploymentId: record.deployId, dataId: record.id });
console.log('获取流程任务信息:', myTaskFlow);
// 提取任务流信息,支持多层返回结构(data/result/直接对象)
const taskData = pickStartResult(myTaskFlow); const taskData = pickStartResult(myTaskFlow);
if (!taskData || !taskData.taskId) { if (!taskData || !taskData.taskId) {
...@@ -273,19 +353,14 @@ ...@@ -273,19 +353,14 @@
return; return;
} }
// 若未指定是否审批,则默认展示审批意见(保持与发起流程一致)
if (taskData.nodeisApprove == null) { if (taskData.nodeisApprove == null) {
taskData.nodeisApprove = true; taskData.nodeisApprove = true;
} }
// 保存缓存
taskCache.set(String(dataId), taskData); taskCache.set(String(dataId), taskData);
// 打开流程详情抽屉并初始化数据
isShowDetailDrawer.value = true; isShowDetailDrawer.value = true;
await nextTick(); await nextTick();
if (refDetail.value) { if (refDetail.value) {
// 使用获取到的taskData,确保包含所有必要参数
refDetail.value.iniData({ refDetail.value.iniData({
...record, ...record,
...taskData, ...taskData,
...@@ -301,7 +376,6 @@ ...@@ -301,7 +376,6 @@
} }
async function findTodoTaskByProcInsId(procInsId: string) { async function findTodoTaskByProcInsId(procInsId: string) {
// 启动流程后,待办任务可能存在短暂延迟,做一个轻量重试
for (let i = 0; i < 3; i++) { for (let i = 0; i < 3; i++) {
const ret = await todoList({ pageNum: 1, pageSize: 10, procInsId }); const ret = await todoList({ pageNum: 1, pageSize: 10, procInsId });
const records = ret?.records || []; const records = ret?.records || [];
...@@ -313,12 +387,10 @@ ...@@ -313,12 +387,10 @@
} }
function pickStartResult(res: any) { function pickStartResult(res: any) {
// defHttp 返回形态在不同后端/拦截器下可能是 data/result/直接对象,这里做一次兜底
const base = res?.data ?? res?.result ?? res; const base = res?.data ?? res?.result ?? res;
return base || {}; return base || {};
} }
// 发起任务:点击后若流程未启动则先启动,再在抽屉中完成流程操作
async function handleTodo(record: Recordable) { async function handleTodo(record: Recordable) {
const dataId = record.id || record.dataId || record.businessId; const dataId = record.id || record.dataId || record.businessId;
if (!dataId) { if (!dataId) {
...@@ -326,13 +398,11 @@ ...@@ -326,13 +398,11 @@
return; return;
} }
// 优先使用本地缓存(用于刚启动后列表未回写 taskId/procInsId 的场景)
const cached = taskCache.get(String(dataId)); const cached = taskCache.get(String(dataId));
if (cached && !record.taskId) { if (cached && !record.taskId) {
record = Object.assign({}, record, cached); record = Object.assign({}, record, cached);
} }
// 流程未启动时,先启动流程
const needStartFlow = const needStartFlow =
!record.taskId && !record.procInsId && (record['bpmStatus'] == null || record['bpmStatus'] == '' || record['bpmStatus'] == '1'); !record.taskId && !record.procInsId && (record['bpmStatus'] == null || record['bpmStatus'] == '' || record['bpmStatus'] == '1');
...@@ -340,27 +410,14 @@ ...@@ -340,27 +410,14 @@
try { try {
const formData = { dataId, dataName: 'id' }; const formData = { dataId, dataName: 'id' };
const startResRaw = await definitionStartByDeployId(record.deployId, formData); 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); const startRes = pickStartResult(startResRaw);
let payload: any = Object.assign({}, record, startRes); let payload: any = Object.assign({}, record, startRes);
//console.log("definitionStartByDeployId 返回值-payload ",payload);
// 若启动接口未返回 taskId,则尝试通过 procInsId 从待办列表反查
if (!payload.taskId && payload.procInsId) { if (!payload.taskId && payload.procInsId) {
//console.log("definitionStartByDeployId 返回值-payload -1");
const todoRow = await findTodoTaskByProcInsId(payload.procInsId); const todoRow = await findTodoTaskByProcInsId(payload.procInsId);
if (todoRow?.taskId) { if (todoRow?.taskId) {
payload = Object.assign({}, record, startRes, todoRow); payload = Object.assign({}, record, startRes, todoRow);
} }
} else {
//console.log("definitionStartByDeployId 返回值-payload -2");
} }
if (!payload.taskId) { if (!payload.taskId) {
...@@ -379,11 +436,9 @@ ...@@ -379,11 +436,9 @@
console.error('启动流程或获取任务失败:', e); console.error('启动流程或获取任务失败:', e);
message.error('操作失败,请重试'); message.error('操作失败,请重试');
} }
//console.log("definitionStartByDeployId 返回值-payload- return");
return; return;
} }
console.log('流程已启动:优先使用 record.taskId,否则尝试用 procInsId 从待办列表反查');
// 流程已启动:优先使用 record.taskId,否则尝试用 procInsId 从待办列表反查
try { try {
let payload: any = { ...record }; let payload: any = { ...record };
...@@ -412,7 +467,6 @@ ...@@ -412,7 +467,6 @@
} }
} }
// 待办任务:
async function handleTodoDb(record: Recordable) { async function handleTodoDb(record: Recordable) {
const dataId = record.id; const dataId = record.id;
const deployId = record.deployId; const deployId = record.deployId;
...@@ -423,9 +477,6 @@ ...@@ -423,9 +477,6 @@
try { try {
const myTaskFlow = await getMyTaskFlow({ deploymentId: record.deployId, dataId: record.id }); const myTaskFlow = await getMyTaskFlow({ deploymentId: record.deployId, dataId: record.id });
console.log('获取流程任务信息:', myTaskFlow);
// 提取任务流信息,支持多层返回结构(data/result/直接对象)
const taskData = pickStartResult(myTaskFlow); const taskData = pickStartResult(myTaskFlow);
if (!taskData || !taskData.taskId) { if (!taskData || !taskData.taskId) {
...@@ -433,15 +484,11 @@ ...@@ -433,15 +484,11 @@
return; return;
} }
// 若未指定是否审批,则默认展示审批意见(保持与发起流程一致)
if (taskData.nodeisApprove == null) { if (taskData.nodeisApprove == null) {
taskData.nodeisApprove = true; taskData.nodeisApprove = true;
} }
// 保存缓存
taskCache.set(String(dataId), taskData); taskCache.set(String(dataId), taskData);
// 打开抽屉并初始化待办任务数据
startUser.value = taskData.startUserName || taskData.startUser || ''; startUser.value = taskData.startUserName || taskData.startUser || '';
taskName.value = taskData.taskName || taskData.currentTaskName || ''; taskName.value = taskData.taskName || taskData.currentTaskName || '';
...@@ -460,12 +507,14 @@ ...@@ -460,12 +507,14 @@
async function handleDelete(record) { async function handleDelete(record) {
await deleteOne({ id: record.id }, handleSuccess); await deleteOne({ id: record.id }, handleSuccess);
} }
/** /**
* 批量删除事件 * 批量删除事件
*/ */
async function batchHandleDelete() { async function batchHandleDelete() {
await batchDelete({ ids: selectedRowKeys.value }, handleSuccess); await batchDelete({ ids: selectedRowKeys.value }, handleSuccess);
} }
/** /**
* 成功回调 * 成功回调
*/ */
...@@ -479,6 +528,7 @@ ...@@ -479,6 +528,7 @@
function handleDetailDrawerClose() { function handleDetailDrawerClose() {
isShowDetailDrawer.value = false; isShowDetailDrawer.value = false;
} }
/** /**
* 操作栏 * 操作栏
*/ */
...@@ -523,7 +573,6 @@ ...@@ -523,7 +573,6 @@
if (record['bpmStatus'] == null || record['bpmStatus'] == '') return true; if (record['bpmStatus'] == null || record['bpmStatus'] == '') return true;
else return false; else return false;
}, },
// disabled: record['planFlag'] != '0' && record['planFlag'] != '2' && record['planFlag'] != '9',
popConfirm: { popConfirm: {
title: '是否确认删除该计划项', title: '是否确认删除该计划项',
confirm: handleDelete.bind(null, record), confirm: handleDelete.bind(null, record),
...@@ -531,6 +580,7 @@ ...@@ -531,6 +580,7 @@
}, },
]; ];
} }
/** /**
* 下拉操作栏 * 下拉操作栏
*/ */
...@@ -539,4 +589,352 @@ ...@@ -539,4 +589,352 @@
} }
</script> </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>
<template> <template>
<div style="background-color: #fff; padding: 100px"> <div class="execute-form-container">
<a-form ref="formRef" :model="formData" :label-col="{ span: 4 }" :wrapper-col="{ span: 8 }"> <!-- 表单头部 -->
<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="执行状态" prop="executeStatus"> <a-form-item label="执行状态" name="executeStatus">
<a-select v-model:value="formData.executeStatus" placeholder="请选择执行状态"> <a-select v-model:value="formData.executeStatus" placeholder="请选择执行状态">
<a-select-option value="0">未开始</a-select-option> <a-select-option value="0">
<a-select-option value="1">进行中</a-select-option> <span class="status-option">
<a-select-option value="2">已完成</a-select-option> <span class="status-indicator pending"></span>
<a-select-option value="3">已暂停</a-select-option> 未开始
</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-select>
</a-form-item> </a-form-item>
<!-- 实际开始时间 --> <!-- 时间选择区域 -->
<a-form-item label="实际开始时间" prop="actualStartTime"> <div class="form-row">
<a-date-picker v-model="formData.actualStartTime" type="datetime" placeholder="选择时间"></a-date-picker> <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>
<!-- 实际结束时间 --> <a-form-item label="实际结束时间" name="actualEndTime" class="form-item-half" :rules="endDateRules">
<a-form-item label="实际结束时间" prop="actualEndTime"> <a-date-picker v-model:value="formData.actualEndTime" placeholder="选择时间" format="YYYY-MM-DD"></a-date-picker>
<a-date-picker v-model="formData.actualEndTime" type="datetime" placeholder="选择时间"></a-date-picker>
</a-form-item> </a-form-item>
</div>
<!-- 执行记录 --> <!-- 执行记录 -->
<a-form-item label="执行记录" prop="executeRecord"> <a-form-item label="执行记录" name="executeEcord">
<a-textarea v-model="formData.executeRecord" :rows="4" placeholder="请输入执行记录"></a-textarea> <a-textarea v-model:value="formData.executeEcord" :rows="3" placeholder="请输入执行记录和详细说明..." show-count :maxlength="500" />
</a-form-item> </a-form-item>
<!-- 附件 --> <!-- 附件上传 -->
<a-form-item label="附件" prop="attachments"> <a-form-item label="相关附件" name="attachments">
<!-- <JUpload v-model:value="formModel.fileUploadPath" desText="支持扩展名: .rar .zip .doc .docx .pdf .jpg..." :disabled="isDetail" /> --> <JUpload v-model:value="formData.attachments" desText="支持扩展名: .rar .zip .doc .docx .pdf .jpg..." />
</a-form-item> </a-form-item>
</a-form>
</div>
<!-- 操作按钮 --> <!-- 表单底部按钮 -->
<a-form-item :wrapper-col="{ offset: 6 }"> <div class="form-footer">
<a-space :size="12">
<a-button @click="resetForm">重置</a-button>
<a-button type="primary" @click="submitForm">保存</a-button> <a-button type="primary" @click="submitForm">保存</a-button>
<a-button style="margin-left: 40px" @click="resetForm">重置</a-button> </a-space>
</a-form-item> </div>
</a-form>
</div> </div>
</template> </template>
<script setup> <script setup lang="ts">
import { ref } from 'vue'; 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 JUpload from '/@/components/Form/src/jeecg/components/JUpload/JUpload.vue';
import { usePlanFormStore } from '/@/store/modules/planFormStore';
const route = useRoute();
const formRef = ref(); 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: '', executeStatus: '',
actualStartTime: '', actualStartTime: null as any,
actualEndTime: '', actualEndTime: null as any,
executeRecord: '', executeEcord: '',
attachments: [], attachments: [] as any[],
}); });
const submitForm = () => { // 监听formData变化,用于调试
formRef.value.validate((valid) => {
if (valid) { watch(
console.log('Form submitted:', formData.value); () => 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 = () => { const resetForm = () => {
formRef.value.resetFields(); 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> </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>
<template> <template>
<div> <div>
<vxe-drawer <vxe-drawer
...@@ -9,21 +8,40 @@ ...@@ -9,21 +8,40 @@
width="100%" width="100%"
height="100%" height="100%"
:loading="loading" :loading="loading"
class="flat-flow-drawer"
> >
<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 <iframe
id="iframeId" id="iframeId"
ref="iframeRef" ref="iframeRef"
:src="frmUrl" :src="frmUrl"
frameborder="0" frameborder="0"
style="width: 100%; height: 100%;" class="flow-iframe"
@load="handleIframeLoad"
></iframe> ></iframe>
</div>
</div>
</vxe-drawer> </vxe-drawer>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted,nextTick } from "vue"; import { ref, onMounted, nextTick } from "vue";
import { useUserStoreWithOut } from "/@/store/modules/user"; import { useUserStoreWithOut } from "/@/store/modules/user";
import { defHttp } from '/@/utils/http/axios'; import { defHttp } from '/@/utils/http/axios';
import { getToken } from '/@/utils/auth'; import { getToken } from '/@/utils/auth';
...@@ -35,18 +53,190 @@ const loading = ref(false); ...@@ -35,18 +53,190 @@ const loading = ref(false);
const pageTilte = ref(""); const pageTilte = ref("");
const iframeRef = ref<HTMLIFrameElement>(); const iframeRef = ref<HTMLIFrameElement>();
const handleIframeLoad = () => {
loading.value = false;
};
const iniPage = async (data) => { const iniPage = async (data) => {
pageTilte.value = data.projectName; pageTilte.value = data.projectName;
showPopup.value = true; 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}`; 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'; const setSourctUrl = '/api/jflow/setCCWorkId';
await defHttp.get({ await defHttp.get({
url: setSourctUrl, url: setSourctUrl,
params: {"targetId":data.id,"targetKey":"targetKey","token":getToken()}, params: {"targetId":data.id,"targetKey":"targetKey","token":getToken()},
}); });
} }
// 暴露方法 // 暴露方法
defineExpose({ iniPage }); defineExpose({ iniPage });
</script> </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>
<template> <template>
<div style="background-color: #fff; padding: 100px"> <div class="plan-form-container">
<a-spin :spinning="loading"> <a-spin :spinning="loading">
<!-- 表单头部 -->
<div class="form-header">
<h2 class="form-title">计划详情</h2>
<p class="form-subtitle">查看和编辑计划信息</p>
</div>
<!-- 表单内容 -->
<div class="form-body">
<BasicForm @register="registerForm"> <BasicForm @register="registerForm">
<template #planBasis="{ model, field }"> <template #planBasis="{ model, field }">
<div v-if="model[field]"> <div class="basis-container" v-if="model[field]">
<div v-if="isValidJson(model[field])"> <div v-if="isValidJson(model[field])" class="basis-tags">
<a-tag <a-tag
v-for="item in safeJsonParse(model[field])" v-for="item in safeJsonParse(model[field])"
@click="viewBasisDetail(item)" @click="viewBasisDetail(item)"
:key="item.id" :key="item.id"
style="margin-bottom: 8px; cursor: pointer" class="basis-tag"
> >
{{ item.name }} {{ item.name }}
</a-tag> </a-tag>
</div> </div>
<a-alert v-else type="warning" :message="`无效的数据格式: ${model[field]}`" /> <a-alert v-else type="warning" :message="`无效的数据格式: ${model[field]}`" class="basis-alert" />
</div>
<div v-else class="basis-empty">
<a-empty description="暂无依据数据" :image-style="{ height: '40px' }" />
</div> </div>
<a-empty v-else description="暂无数据" />
</template> </template>
</BasicForm> </BasicForm>
</a-spin> </div>
<div style="width: 100%; text-align: center; margin-top: 24px" v-if="!formDisabled"> <!-- 操作按钮 -->
<a-space> <div class="form-footer" v-if="!formDisabled">
<a-button @click="submitForm" type="primary" :loading="submitting" pre-icon="ant-design:check">提 交</a-button> <a-space :size="12">
<a-button @click="handleReset">重 置</a-button> <a-button @click="handleReset">重置</a-button>
<a-button @click="submitForm" type="primary" :loading="submitting">提交</a-button>
</a-space> </a-space>
</div> </div>
</a-spin>
<AuditInnerDetailDrawer ref="auditInnerDetailDrawerRef" :visible="showDetailDrawer" :basis="selectedBasis" @close="handleDrawerClose" /> <AuditInnerDetailDrawer ref="auditInnerDetailDrawerRef" :visible="showDetailDrawer" :basis="selectedBasis" @close="handleDrawerClose" />
</div> </div>
...@@ -125,29 +137,12 @@ ...@@ -125,29 +137,12 @@
const initFormData = async () => { const initFormData = async () => {
try { try {
loading.value = true; loading.value = true;
//const { cctoken, WorkID } = getUrlParams();
//console.log('Token:', cctoken, 'WorkID:', WorkID);
const timestamp = new Date().getTime(); 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; let tid = toRaw(route.query).id;
console.log('tid:', tid); console.log('tid:', tid);
//setAuthCache(TOKEN_KEY, token);
//console.log('tid:', tid, 'token:', token);
const queryByIdUrl = '/plan.main/stPlanMan/queryById'; const queryByIdUrl = '/plan.main/stPlanMan/queryById';
const data = await defHttp.get({ const data = await defHttp.get({
url: queryByIdUrl, url: queryByIdUrl,
...@@ -169,14 +164,216 @@ ...@@ -169,14 +164,216 @@
loading.value = false; 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(() => { onMounted(() => {
initFormData(); initFormData();
}); });
</script> </script>
<style scoped> <style scoped lang="less">
.ant-tag { /* ==================== 柔和中性风格 - CSS 变量 ==================== */
margin-right: 8px; .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; 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> </style>
...@@ -3,37 +3,157 @@ ...@@ -3,37 +3,157 @@
v-bind="$attrs" v-bind="$attrs"
@register="registerModal" @register="registerModal"
destroyOnClose destroyOnClose
title="发起计划"
:width="1000" :width="1000"
@ok="handleSubmit" @ok="handleSubmit"
@cancel="handleCance" @cancel="handleCance"
:centered="true" :centered="true"
:title="'发起计划'"
> >
<a-form :model="formModel" ref="formRef" style="overflow-x: hidden" :disabled="isDetail"> <div class="modal-body">
<a-row gutter="8"> <a-form ref="formRef" :model="formModel" class="styled-form" :disabled="isDetail">
<!-- 第一行:计划名称 + 类型 -->
<a-row :gutter="16">
<a-col :span="12"> <a-col :span="12">
<a-form-item label="计划名称" name="projectName" :label-col="{ style: { width: '120px' } }" :wrapperCol="{ span: 16 }"> <a-form-item
<a-input v-model:value="formModel.projectName" allow-clear :disabled="isDetail" /> 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-form-item>
</a-col> </a-col>
<a-col :span="12"> <a-col :span="12">
<a-form-item label="类型" name="projectType" :label-col="{ style: { width: '120px' } }" :wrapperCol="{ span: 16 }"> <a-form-item
<JCategorySelect pcode="B09" :value="formModel.projectType" :disabled="isDetail" @change="changeJCategory" /> 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-form-item>
</a-col> </a-col>
</a-row>
<!-- 第二行:执行部门 + 负责人 -->
<a-row :gutter="16">
<a-col :span="12"> <a-col :span="12">
<a-form-item label="计划开始日期" name="planStartDate" :label-col="{ style: { width: '120px' } }" :wrapperCol="{ span: 16 }"> <a-form-item label="执行部门" name="execDepCode" :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" /> <JSelectDept v-model:value="formModel.execDepCode" placeholder="请选择执行部门" :disabled="isDetail" />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="12"> <a-col :span="12">
<a-form-item label="计划结束日期" name="planEndDate" :label-col="{ style: { width: '120px' } }" :wrapperCol="{ span: 16 }"> <a-form-item label="负责人" name="headId" :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" /> <JSearchSelectDuty v-model:value="formModel.headId" placeholder="请选择负责人" :disabled="isDetail" />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="24"> </a-row>
<a-form-item label="依据" :label-col="{ style: { width: '120px' } }" :wrapperCol="{ span: 16 }"> <!-- 第三行:开始日期 + 结束日期 -->
<div> <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"
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 <a-tag
v-for="item in formModel.basisList" v-for="item in formModel.basisList"
:key="item.id" :key="item.id"
...@@ -41,45 +161,53 @@ ...@@ -41,45 +161,53 @@
@close="removeBasis(item.id)" @close="removeBasis(item.id)"
@click="viewBasisDetail(item)" @click="viewBasisDetail(item)"
:disabled="isDetail" :disabled="isDetail"
class="basis-tag"
> >
{{ item.name }} {{ item.name }}
</a-tag> </a-tag>
<a-button type="link" v-if="showSelectorBtn" @click="openBasisSelector" :disabled="isDetail">添加依据</a-button> <a-button type="dashed" v-if="showSelectorBtn && !isDetail" @click="openBasisSelector" class="btn-add-basis"> + 添加依据 </a-button>
</div> </div>
</a-form-item> </a-form-item>
</a-col> <!-- 计划要求 -->
<a-col :span="24"> <a-row :gutter="16">
<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-col :span="12">
<a-form-item label="交付物" name="planDeliverable" :label-col="{ style: { width: '120px' } }" :wrapperCol="{ span: 16 }"> <a-form-item
<a-input v-model:value="formModel.planDeliverable" allow-clear :disabled="isDetail" /> label="交付物"
</a-form-item> name="planDeliverable"
</a-col> :rules="[{ required: true, message: '请输入交付物' }]"
<a-col :span="24"> :label-col="{ span: 6 }"
<a-form-item label="描述" name="projectDesc" :label-col="{ style: { width: '120px' } }" :wrapperCol="{ span: 16 }"> :wrapper-col="{ span: 18 }"
<a-textarea v-model:value="formModel.projectDesc" rows="3" allow-clear :disabled="isDetail" /> >
<a-input
v-model:value="formModel.planDeliverable"
allow-clear
:disabled="isDetail"
placeholder="请输入需提交的交付物,如报告、台账等"
/>
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="24"> <a-col :span="24">
<a-form-item label="执行规则" name="exeRule" :label-col="{ style: { width: '120px' } }" :wrapperCol="{ span: 16 }"> <a-form-item label="备注说明" name="projectDesc" :label-col="{ span: 3 }" :wrapper-col="{ span: 21 }">
<vxe-radio-group v-model="formModel.exeRule" :options="exeRules" /> <a-textarea v-model:value="formModel.projectDesc" :rows="3" allow-clear :disabled="isDetail" placeholder="请输入补充说明..." />
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="24"> <a-col :span="24">
<a-form-item label="相关附件" name="fileUploadPath" :label-col="{ style: { width: '120px' } }" :wrapperCol="{ span: 16 }"> <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" /> <JUpload v-model:value="formModel.fileUploadPath" desText="支持扩展名: .rar .zip .doc .docx .pdf .jpg..." :disabled="isDetail" />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="0"> </a-row>
<a-form-item name="id"> <!-- 相关附件 -->
<!-- 隐藏字段 -->
<a-form-item name="id" style="display: none">
<a-input v-model:value="formModel.id" type="hidden" /> <a-input v-model:value="formModel.id" type="hidden" />
</a-form-item> </a-form-item>
</a-col>
</a-row>
</a-form> </a-form>
</div>
<!-- 选择依据组件 --> <!-- 选择依据组件 -->
<BasicCtrlSelector ref="selectorRef" @click="handleSelectorClick" /> <BasicCtrlSelector ref="selectorRef" @click="handleSelectorClick" />
<!-- 依据详情抽屉 --> <!-- 依据详情抽屉 -->
...@@ -97,12 +225,13 @@ ...@@ -97,12 +225,13 @@
import { list } from '/@/views/newlib/components/api/AuditInnerCtrl.api'; import { list } from '/@/views/newlib/components/api/AuditInnerCtrl.api';
import { listAll } from '/@/views/newlib/components/api/AuditInnerCtrlItem.api'; import { listAll } from '/@/views/newlib/components/api/AuditInnerCtrlItem.api';
import { saveOrUpdate } from '../StPlanMan.api'; import { saveOrUpdate } from '../StPlanMan.api';
import AuditInnerDetailDrawer from '/@/views/newlib/components/modal/AuditInnerDetailDrawer.vue'; import AuditInnerDetailDrawer from '/@/views/newlib/components/modal/AuditInnerDetailDrawer.vue';
import BasicCtrlSelector from '/@/components/BasicCtrlSelector.vue';
defineProps<{ defineProps<{
showSelectorBtn?: boolean; showSelectorBtn?: boolean;
}>(); }>();
const AuditInnerDetailDrawerRef = ref(); const AuditInnerDetailDrawerRef = ref();
const showDetailDrawer = ref(false); const showDetailDrawer = ref(false);
const selectedBasis = ref<any>(null); const selectedBasis = ref<any>(null);
...@@ -116,6 +245,7 @@ ...@@ -116,6 +245,7 @@
AuditInnerDetailDrawerRef.value?.open(data); AuditInnerDetailDrawerRef.value?.open(data);
showDetailDrawer.value = true; showDetailDrawer.value = true;
}; };
// Emits声明 // Emits声明
const emit = defineEmits(['register', 'success', 'selector-click']); const emit = defineEmits(['register', 'success', 'selector-click']);
const isUpdate = ref(true); const isUpdate = ref(true);
...@@ -123,16 +253,20 @@ ...@@ -123,16 +253,20 @@
const visible = ref(false); const visible = ref(false);
const formRef = ref(); const formRef = ref();
const exeRules = ref([ // const exeRules = ref([
{ value: 1, label: '每发生' }, // { value: 1, label: '事件触发' },
{ value: 2, label: '周期性' }, // { value: 2, label: '周期执行' },
{ value: 3, label: '一次性' }, // { value: 3, label: '一次性执行' },
]); // ]);
const formModel = reactive({ const formModel = reactive({
projectName: '', projectName: '',
projectType: '', projectType: '',
execDepCode: '',
execDepName: '',
headId: '',
headName: '',
priority: '2',
planRequest: '', planRequest: '',
planDeliverable: '', planDeliverable: '',
planStartDate: '', planStartDate: '',
...@@ -142,13 +276,24 @@ ...@@ -142,13 +276,24 @@
projectDesc: '', projectDesc: '',
fileUploadPath: '', fileUploadPath: '',
planBasis: '', planBasis: '',
exeRule:1, exeRule: 1,
triggerEventName: '',
exePeriod: undefined,
firstExecDate: '',
completionRate: 0,
statusName: '',
id: '', id: '',
}); });
const resetForm = () => { const resetForm = () => {
Object.assign(formModel, { Object.assign(formModel, {
projectName: '', projectName: '',
projectType: '', projectType: '',
execDepCode: '',
execDepName: '',
headId: '',
headName: '',
priority: '2',
planRequest: '', planRequest: '',
planDeliverable: '', planDeliverable: '',
planStartDate: '', planStartDate: '',
...@@ -158,10 +303,16 @@ ...@@ -158,10 +303,16 @@
projectDesc: '', projectDesc: '',
fileUploadPath: '', fileUploadPath: '',
planBasis: '', planBasis: '',
exeRule:1, exeRule: 1,
triggerEventName: '',
exePeriod: undefined,
firstExecDate: '',
completionRate: 0,
statusName: '',
id: '', id: '',
}); });
}; };
//表单赋值 //表单赋值
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
setModalProps({ confirmLoading: false, showCancelBtn: !!data?.showFooter, showOkBtn: !!data?.showFooter }); setModalProps({ confirmLoading: false, showCancelBtn: !!data?.showFooter, showOkBtn: !!data?.showFooter });
...@@ -184,10 +335,6 @@ ...@@ -184,10 +335,6 @@
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
const valids = await formRef.value.validate(); 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) { if (formModel.basisList) {
valids['planBasis'] = JSON.stringify(formModel.basisList); valids['planBasis'] = JSON.stringify(formModel.basisList);
...@@ -205,7 +352,7 @@ ...@@ -205,7 +352,7 @@
setModalProps({ confirmLoading: false }); setModalProps({ confirmLoading: false });
} }
}; };
// const basisSelectMode = ref<'flat' | 'nested'>('flat');
const selectorRef = ref(); const selectorRef = ref();
const basisSelectorConfig = { const basisSelectorConfig = {
title: '选择依据', title: '选择依据',
...@@ -222,7 +369,6 @@ ...@@ -222,7 +369,6 @@
'a', 'a',
{ {
onClick: async () => { onClick: async () => {
// 加载二级数据并更新组件中的表格
const detailData = await listAll({ checkid: record.id }); const detailData = await listAll({ checkid: record.id });
selectorRef.value.setDetail({ selectorRef.value.setDetail({
title: '详细内容', title: '详细内容',
...@@ -278,22 +424,180 @@ ...@@ -278,22 +424,180 @@
const changeJCategory = (val, obj) => { const changeJCategory = (val, obj) => {
formModel['projectType'] = val; formModel['projectType'] = val;
}; };
const formatTp = (row) => { const formatTp = (row) => {
return row.tp == 1 ? '工具要求' : row.tp == 2 ? '记录要求' : '制度要求'; return row.tp == 1 ? '工具要求' : row.tp == 2 ? '记录要求' : '制度要求';
}; };
const formatComeFrom = (row) => { const formatComeFrom = (row) => {
return row.comeFrom == 1 ? '自建' : row.tp == 2 ? '合规库' : '内控制度'; return row.comeFrom == 1 ? '自建' : row.tp == 2 ? '合规库' : '内控制度';
}; };
const formatExeRules = (row) => { const formatExeRules = (row) => {
return row.comeFrom == 1 ? '每发生' : row.tp == 2 ? '周期性' : '一次性'; return row.comeFrom == 1 ? '一次性' : row.tp == 2 ? '周期执行' : '事件触发';
}; };
</script> </script>
<style scoped> <style scoped lang="less">
.ant-tag { /* ==================== 弹窗主体 ==================== */
.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; cursor: pointer;
margin-right: 4px; margin: 0;
margin-bottom: 4px; 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> </style>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论