|
|
@@ -1,10 +1,10 @@
|
|
|
import React, { useState, useEffect, useMemo } from 'react';
|
|
|
import { CheckCircle, Eye, ShoppingCart, HelpCircle, PlayCircle, Clock, AlertCircle, Rocket } from 'lucide-react';
|
|
|
import { LineChart, Line, AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts';
|
|
|
-import { Card, Button, Table, Tag, message, Modal, Form as AntdForm, Input, Select, DatePicker, InputNumber, Switch, Radio, Checkbox, Cascader, Upload, Alert } from 'antd';
|
|
|
+import { Card, Button, Table, Tag, message, Modal, Form as AntdForm, Input, Select, DatePicker, InputNumber, Switch, Radio, Checkbox, Cascader, Upload, Alert, Space } from 'antd';
|
|
|
import { UploadOutlined, LockOutlined, CheckCircleOutlined } from '@ant-design/icons';
|
|
|
import { executorHomeApi, ExecutorNodeCountVO, ExecutorNodeVO, ExecutorDayCompletedVO } from '../api/executorHome';
|
|
|
-import { myTaskApi, MyTaskNodeDetailVO } from '../api/mytask';
|
|
|
+import { myTaskApi, MyTaskNodeDetailVO, UpdateNodeApprovalParam } from '../api/mytask';
|
|
|
import { formatDateWithFormat, dateFormatter } from '../utils/formatTime';
|
|
|
import { toast } from 'sonner';
|
|
|
import { useNavigate } from 'react-router-dom';
|
|
|
@@ -178,6 +178,8 @@ export default function ExecutorDashboard() {
|
|
|
const [originalConf, setOriginalConf] = useState<string>('');
|
|
|
const [taskDetailForm] = AntdForm.useForm();
|
|
|
const [approvalComment, setApprovalComment] = useState('');
|
|
|
+ const [approvalLoading, setApprovalLoading] = useState(false);
|
|
|
+ const [submitLoading, setSubmitLoading] = useState(false);
|
|
|
|
|
|
// 获取字典数据
|
|
|
const fetchDictData = async () => {
|
|
|
@@ -1432,16 +1434,38 @@ export default function ExecutorDashboard() {
|
|
|
{
|
|
|
title: t('cockpit.action'),
|
|
|
key: 'action',
|
|
|
- width: 100,
|
|
|
+ width: 120,
|
|
|
render: (_: any, record: ExecutorNodeVO) => {
|
|
|
+ const statusText = getTaskStatusText(record.approvalStatus);
|
|
|
+ const statusTextLower = statusText.toLowerCase();
|
|
|
+ const isInProgress = statusTextLower.includes('进行中') || statusTextLower.includes('待审核') || statusTextLower.includes('pending') || statusTextLower.includes('执行中') || statusTextLower.includes('in progress');
|
|
|
+ const isCompleted = statusTextLower.includes('已完成') || statusTextLower.includes('已通过') || statusTextLower.includes('approved') || statusTextLower.includes('完成') || statusTextLower.includes('执行完成') || statusTextLower.includes('completed');
|
|
|
+ const buttonText = isInProgress ? t('form.handleNow') : (isCompleted ? t('form.viewDetail') : t('form.viewDetail'));
|
|
|
return (
|
|
|
- <Button
|
|
|
- type="primary"
|
|
|
- size="small"
|
|
|
- onClick={() => handleViewTask(record)}
|
|
|
- >
|
|
|
- 查看
|
|
|
- </Button>
|
|
|
+ <Space size="small">
|
|
|
+ <Button
|
|
|
+ type="link"
|
|
|
+ size="small"
|
|
|
+ icon={<Eye className="w-4 h-4" style={{ color: '#000000' }} />}
|
|
|
+ onClick={(e) => {
|
|
|
+ e.stopPropagation();
|
|
|
+ e.preventDefault();
|
|
|
+ handleViewTask(record);
|
|
|
+ }}
|
|
|
+ style={{ color: '#000000' }}
|
|
|
+ className="transition-colors hover:text-[#1677ff] hover:underline"
|
|
|
+ onMouseEnter={(e) => {
|
|
|
+ e.currentTarget.style.color = '#1677ff';
|
|
|
+ e.currentTarget.querySelector('svg')?.setAttribute('style', 'color: #1677ff');
|
|
|
+ }}
|
|
|
+ onMouseLeave={(e) => {
|
|
|
+ e.currentTarget.style.color = '#000000';
|
|
|
+ e.currentTarget.querySelector('svg')?.setAttribute('style', 'color: #000000');
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {buttonText}
|
|
|
+ </Button>
|
|
|
+ </Space>
|
|
|
);
|
|
|
},
|
|
|
},
|
|
|
@@ -1633,6 +1657,12 @@ export default function ExecutorDashboard() {
|
|
|
onCancel={() => {
|
|
|
setTaskDetailVisible(false);
|
|
|
setTaskDetailData(null);
|
|
|
+ setFormData({ rule: [], option: {} });
|
|
|
+ setFormLoading(false);
|
|
|
+ setOriginalFields([]);
|
|
|
+ setOriginalConf('');
|
|
|
+ taskDetailForm.resetFields();
|
|
|
+ setApprovalComment('');
|
|
|
}}
|
|
|
footer={null}
|
|
|
width={1000}
|
|
|
@@ -1858,14 +1888,14 @@ export default function ExecutorDashboard() {
|
|
|
flexShrink: 0
|
|
|
}}
|
|
|
>
|
|
|
- 审核意见
|
|
|
+ {t('form.reviewComment')}
|
|
|
</label>
|
|
|
<div className="flex-1">
|
|
|
<Input.TextArea
|
|
|
rows={4}
|
|
|
value={approvalComment}
|
|
|
onChange={(e) => setApprovalComment(e.target.value)}
|
|
|
- placeholder={t('cockpit.auditOpinionPlaceholder')}
|
|
|
+ placeholder={t('form.reviewCommentPlaceholder')}
|
|
|
maxLength={500}
|
|
|
showCount
|
|
|
disabled={isApproved}
|
|
|
@@ -1874,7 +1904,168 @@ export default function ExecutorDashboard() {
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- {/* 底部按钮 - 审核节点不显示按钮(只读模式) */}
|
|
|
+ {/* 底部按钮 - 审核通过/不通过 */}
|
|
|
+ <div
|
|
|
+ className="flex justify-end gap-3"
|
|
|
+ style={{
|
|
|
+ padding: '16px 24px',
|
|
|
+ borderTop: '1px solid #f0f0f0',
|
|
|
+ flexShrink: 0
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Button
|
|
|
+ danger
|
|
|
+ loading={approvalLoading}
|
|
|
+ disabled={isApproved}
|
|
|
+ onClick={async () => {
|
|
|
+ const nodeId = taskDetailData.nodeId ?? taskDetailData.id;
|
|
|
+ if (nodeId == null) {
|
|
|
+ message.warning(t('cockpit.nodeIdNotExist'));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const numNodeId = typeof nodeId === 'number' ? nodeId : Number(nodeId);
|
|
|
+ if (isNaN(numNodeId)) {
|
|
|
+ message.warning(t('cockpit.nodeIdInvalid'));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (formData.rule && formData.rule.length > 0) {
|
|
|
+ try {
|
|
|
+ await taskDetailForm.validateFields();
|
|
|
+ } catch (err: any) {
|
|
|
+ if (err?.errorFields?.length) {
|
|
|
+ message.error(err.errorFields[0]?.errors?.[0] || '请完善表单内容');
|
|
|
+ } else {
|
|
|
+ message.error('请完善表单内容');
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ setApprovalLoading(true);
|
|
|
+ try {
|
|
|
+ let formDataString: string | undefined;
|
|
|
+ if (formData.rule?.length && originalFields.length > 0) {
|
|
|
+ const values = taskDetailForm.getFieldsValue();
|
|
|
+ const fieldsWithValues = originalFields.map((fieldString: string) => {
|
|
|
+ try {
|
|
|
+ const fieldObj = JSON.parse(fieldString);
|
|
|
+ const fieldName = fieldObj.name || fieldObj.field;
|
|
|
+ const fieldValue = values[fieldName];
|
|
|
+ fieldObj.value = fieldValue !== undefined && fieldValue !== null ? fieldValue : '';
|
|
|
+ return JSON.stringify(fieldObj);
|
|
|
+ } catch (e) {
|
|
|
+ return fieldString;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ formDataString = JSON.stringify({
|
|
|
+ id: taskDetailData.formId ?? taskDetailData.id,
|
|
|
+ name: taskDetailData.nodeName || taskDetailData.workName || '未知',
|
|
|
+ conf: originalConf,
|
|
|
+ fields: fieldsWithValues
|
|
|
+ });
|
|
|
+ }
|
|
|
+ const params: UpdateNodeApprovalParam = {
|
|
|
+ nodeId: numNodeId,
|
|
|
+ approvalStatus: 'rejected',
|
|
|
+ approvalOpinion: approvalComment || undefined,
|
|
|
+ formData: formDataString,
|
|
|
+ };
|
|
|
+ await myTaskApi.updateNodeApproval(params);
|
|
|
+ message.success(t('form.reviewRejectSuccess'));
|
|
|
+ setTaskDetailVisible(false);
|
|
|
+ setTaskDetailData(null);
|
|
|
+ setFormData({ rule: [], option: {} });
|
|
|
+ setFormLoading(false);
|
|
|
+ setOriginalFields([]);
|
|
|
+ setOriginalConf('');
|
|
|
+ taskDetailForm.resetFields();
|
|
|
+ setApprovalComment('');
|
|
|
+ fetchAllData();
|
|
|
+ } catch (error: any) {
|
|
|
+ message.error(error?.message || t('form.reviewRejectFailed'));
|
|
|
+ } finally {
|
|
|
+ setApprovalLoading(false);
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {t('form.reviewReject')}
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ loading={approvalLoading}
|
|
|
+ disabled={isApproved}
|
|
|
+ onClick={async () => {
|
|
|
+ const nodeId = taskDetailData.nodeId ?? taskDetailData.id;
|
|
|
+ if (nodeId == null) {
|
|
|
+ message.warning(t('cockpit.nodeIdNotExist'));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const numNodeId = typeof nodeId === 'number' ? nodeId : Number(nodeId);
|
|
|
+ if (isNaN(numNodeId)) {
|
|
|
+ message.warning(t('cockpit.nodeIdInvalid'));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (formData.rule && formData.rule.length > 0) {
|
|
|
+ try {
|
|
|
+ await taskDetailForm.validateFields();
|
|
|
+ } catch (err: any) {
|
|
|
+ if (err?.errorFields?.length) {
|
|
|
+ message.error(err.errorFields[0]?.errors?.[0] || '请完善表单内容');
|
|
|
+ } else {
|
|
|
+ message.error('请完善表单内容');
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ setApprovalLoading(true);
|
|
|
+ try {
|
|
|
+ let formDataString: string | undefined;
|
|
|
+ if (formData.rule?.length && originalFields.length > 0) {
|
|
|
+ const values = taskDetailForm.getFieldsValue();
|
|
|
+ const fieldsWithValues = originalFields.map((fieldString: string) => {
|
|
|
+ try {
|
|
|
+ const fieldObj = JSON.parse(fieldString);
|
|
|
+ const fieldName = fieldObj.name || fieldObj.field;
|
|
|
+ const fieldValue = values[fieldName];
|
|
|
+ fieldObj.value = fieldValue !== undefined && fieldValue !== null ? fieldValue : '';
|
|
|
+ return JSON.stringify(fieldObj);
|
|
|
+ } catch (e) {
|
|
|
+ return fieldString;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ formDataString = JSON.stringify({
|
|
|
+ id: taskDetailData.formId ?? taskDetailData.id,
|
|
|
+ name: taskDetailData.nodeName || taskDetailData.workName || '未知',
|
|
|
+ conf: originalConf,
|
|
|
+ fields: fieldsWithValues
|
|
|
+ });
|
|
|
+ }
|
|
|
+ const params: UpdateNodeApprovalParam = {
|
|
|
+ nodeId: numNodeId,
|
|
|
+ approvalStatus: 'approved',
|
|
|
+ approvalOpinion: approvalComment || undefined,
|
|
|
+ formData: formDataString,
|
|
|
+ };
|
|
|
+ await myTaskApi.updateNodeApproval(params);
|
|
|
+ message.success(t('form.reviewApproveSuccess'));
|
|
|
+ setTaskDetailVisible(false);
|
|
|
+ setTaskDetailData(null);
|
|
|
+ setFormData({ rule: [], option: {} });
|
|
|
+ setFormLoading(false);
|
|
|
+ setOriginalFields([]);
|
|
|
+ setOriginalConf('');
|
|
|
+ taskDetailForm.resetFields();
|
|
|
+ setApprovalComment('');
|
|
|
+ fetchAllData();
|
|
|
+ } catch (error: any) {
|
|
|
+ message.error(error?.message || t('form.reviewApproveFailed'));
|
|
|
+ } finally {
|
|
|
+ setApprovalLoading(false);
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {t('form.reviewApprove')}
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
);
|
|
|
}
|
|
|
@@ -1975,7 +2166,98 @@ export default function ExecutorDashboard() {
|
|
|
</div>
|
|
|
)}
|
|
|
</div>
|
|
|
- {/* 底部按钮 - 其他节点类型不显示按钮(只读模式) */}
|
|
|
+ {/* 底部按钮 - 取消 + 提交/完成 */}
|
|
|
+ <div
|
|
|
+ className="flex justify-end gap-3"
|
|
|
+ style={{
|
|
|
+ padding: '16px 24px',
|
|
|
+ borderTop: '1px solid #f0f0f0',
|
|
|
+ flexShrink: 0
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Button
|
|
|
+ onClick={() => {
|
|
|
+ setTaskDetailVisible(false);
|
|
|
+ setTaskDetailData(null);
|
|
|
+ setFormData({ rule: [], option: {} });
|
|
|
+ setOriginalFields([]);
|
|
|
+ setOriginalConf('');
|
|
|
+ taskDetailForm.resetFields();
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {t('common.cancel')}
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ loading={submitLoading}
|
|
|
+ disabled={isApproved}
|
|
|
+ onClick={async () => {
|
|
|
+ const nodeId = taskDetailData.nodeId ?? taskDetailData.id;
|
|
|
+ if (nodeId == null) {
|
|
|
+ message.warning(t('cockpit.nodeIdNotExist'));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const numNodeId = typeof nodeId === 'number' ? nodeId : Number(nodeId);
|
|
|
+ if (isNaN(numNodeId)) {
|
|
|
+ message.warning(t('cockpit.nodeIdInvalid'));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ setSubmitLoading(true);
|
|
|
+ try {
|
|
|
+ const values = formData.rule?.length ? await taskDetailForm.validateFields() : {};
|
|
|
+ const fieldsWithValues = originalFields.map((fieldString: string) => {
|
|
|
+ try {
|
|
|
+ const fieldObj = JSON.parse(fieldString);
|
|
|
+ const fieldName = fieldObj.name || fieldObj.field;
|
|
|
+ const fieldValue = values[fieldName];
|
|
|
+ fieldObj.value = fieldValue !== undefined && fieldValue !== null ? fieldValue : '';
|
|
|
+ return JSON.stringify(fieldObj);
|
|
|
+ } catch (e) {
|
|
|
+ return fieldString;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ const submitData = {
|
|
|
+ id: taskDetailData.formId ?? taskDetailData.id,
|
|
|
+ name: taskDetailData.nodeName || taskDetailData.workName || '未知',
|
|
|
+ conf: originalConf,
|
|
|
+ fields: fieldsWithValues
|
|
|
+ };
|
|
|
+ const formDataString = JSON.stringify(submitData);
|
|
|
+ const params: UpdateNodeApprovalParam = {
|
|
|
+ nodeId: numNodeId,
|
|
|
+ approvalStatus: 'approved',
|
|
|
+ approvalOpinion: undefined,
|
|
|
+ formData: formDataString
|
|
|
+ };
|
|
|
+ await myTaskApi.updateNodeApproval(params);
|
|
|
+ const nodeType = String(taskDetailData?.type || taskDetailData?.nodeType || '').trim();
|
|
|
+ const isComplete = nodeType === 'complete';
|
|
|
+ message.success(isComplete ? t('form.completeSuccess') : t('common.submit') + t('common.success'));
|
|
|
+ setTaskDetailVisible(false);
|
|
|
+ setTaskDetailData(null);
|
|
|
+ setFormData({ rule: [], option: {} });
|
|
|
+ setFormLoading(false);
|
|
|
+ setOriginalFields([]);
|
|
|
+ setOriginalConf('');
|
|
|
+ taskDetailForm.resetFields();
|
|
|
+ fetchAllData();
|
|
|
+ } catch (error: any) {
|
|
|
+ if (error?.errorFields) {
|
|
|
+ message.error('请填写完整的表单内容');
|
|
|
+ } else {
|
|
|
+ message.error(error?.message || '提交失败');
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ setSubmitLoading(false);
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {(() => {
|
|
|
+ const nodeType = String(taskDetailData?.type || taskDetailData?.nodeType || '').trim();
|
|
|
+ return nodeType === 'complete' ? t('form.complete') : t('common.submit');
|
|
|
+ })()}
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
);
|
|
|
})()}
|