| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584 |
- import React, { useState, useEffect } from 'react';
- import { useNavigate } from 'react-router-dom';
- import { Eye, Search, RotateCcw } from 'lucide-react';
- import { Button, Space, Table as AntdTable, Input, message, Modal, Form as AntdForm, Card, Alert, Select, DatePicker, InputNumber, Switch, Radio, Checkbox, Cascader, Upload } from 'antd';
- import { UploadOutlined, LockOutlined, KeyOutlined } from '@ant-design/icons';
- import type { ColumnsType } from 'antd/es/table';
- import { toast } from 'sonner';
- import { myTaskApi, MyTaskVO, MyTaskPageParam, PageResponse, MyTaskNodeDetailVO, UpdateNodeApprovalParam } from '../api/mytask';
- import { dateFormatter } from '../utils/formatTime';
- import { DICT_TYPE, getDictLabel } from '../utils/dict';
- import { setConfAndFields2, FormCreateData } from '../utils/formCreate';
- export default function MyTask() {
- const navigate = useNavigate();
-
- const [loading, setLoading] = useState(true);
- const [list, setList] = useState<MyTaskVO[]>([]);
- const [total, setTotal] = useState(0);
- const [queryParams, setQueryParams] = useState<MyTaskPageParam>({
- pageNo: 1,
- pageSize: 10,
- key: '',
- });
- const [searchKey, setSearchKey] = useState('');
- const [approvalStatusDictList, setApprovalStatusDictList] = useState<any[]>([]);
-
- // 节点详情弹框相关状态
- const [detailVisible, setDetailVisible] = useState(false);
- const [detailLoading, setDetailLoading] = useState(false);
- const [detailData, setDetailData] = useState<MyTaskNodeDetailVO | null>(null);
- const [formData, setFormData] = useState<FormCreateData>({
- rule: [],
- option: {}
- });
- const [formLoading, setFormLoading] = useState(false); // 表单加载状态
- const [originalFields, setOriginalFields] = useState<string[]>([]); // 保存原始的 fields 数组(JSON 字符串数组)
- const [originalConf, setOriginalConf] = useState<string>(''); // 保存原始的 conf(JSON 字符串)
- const [detailForm] = AntdForm.useForm();
- const [approvalComment, setApprovalComment] = useState(''); // 审核意见
- const [approvalLoading, setApprovalLoading] = useState(false); // 审核操作loading状态
- const [submitLoading, setSubmitLoading] = useState(false); // 提交操作loading状态
-
-
- // 组件挂载时打印调试信息
- useEffect(() => {
- console.log('MyTask 组件已加载');
- }, []);
-
- // 监听弹框状态变化
- useEffect(() => {
- console.log('MyTask: detailVisible 状态变化', detailVisible, 'detailData:', detailData);
- }, [detailVisible, detailData]);
-
- // 获取审批状态字典
- const getApprovalStatusDictList = async () => {
- try {
- const { dictDataApi } = await import('../api/DictData');
- const response = await dictDataApi.getDictDataPage({
- pageNo: 1,
- pageSize: -1,
- dictType: 'approval_status',
- });
- const data = (response as any)?.data || response;
- const dictList = data?.list || [];
- setApprovalStatusDictList(dictList);
- console.log('MyTask: 获取审批状态字典成功', dictList);
- } catch (error: any) {
- console.error('获取审批状态字典失败:', error);
- }
- };
- useEffect(() => {
- getApprovalStatusDictList();
- }, []);
-
- /** 查询列表 */
- const getList = async (params?: MyTaskPageParam) => {
- const searchParams = params || queryParams;
- setLoading(true);
- try {
- console.log('MyTask: 开始获取我的任务列表', searchParams);
- const response = await myTaskApi.getMyWorkPage(searchParams);
- console.log('MyTask: 接口响应', response);
- const data = (response as any)?.data || response;
- const pageData = (data as PageResponse<MyTaskVO>);
- console.log('MyTask: 解析后的数据', pageData);
- setList(pageData.list || []);
- setTotal(pageData.total || 0);
- } catch (error: any) {
- console.error('MyTask: 获取我的任务列表失败', error);
- toast.error(error.message || '获取我的任务列表失败');
- setList([]);
- setTotal(0);
- } finally {
- setLoading(false);
- }
- };
- useEffect(() => {
- console.log('MyTask: useEffect 触发,queryParams:', queryParams);
- getList();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [queryParams.pageNo, queryParams.pageSize, queryParams.key]);
- // 搜索
- const handleSearch = () => {
- setQueryParams({
- ...queryParams,
- pageNo: 1,
- key: searchKey.trim() || undefined,
- });
- };
- // 重置
- const handleReset = () => {
- setSearchKey('');
- setQueryParams({
- ...queryParams,
- pageNo: 1,
- key: undefined,
- });
- };
- // 默认表单配置
- const defaultFormConfig = {
- name: '',
- labelPosition: 'right',
- formSize: 'middle',
- labelSuffix: '',
- labelWidth: 100,
- hideRequiredMark: false,
- showValidationError: true,
- inlineValidation: false,
- showSubmitButton: false,
- showResetButton: false,
- };
- // 渲染字段预览(支持嵌套结构)
- const renderFieldPreview = (field: any, parentSpanStyle?: React.CSSProperties): React.ReactNode => {
- const formConfig = formData.option?.formConfig || defaultFormConfig;
- const layoutColumns = formConfig.layoutColumns || 1;
- const spanStyle = parentSpanStyle || (layoutColumns > 1 ? { gridColumn: `span ${Math.min(layoutColumns, field.span || 1)}` } : undefined);
- // 处理容器类型(card 和 grid)
- if (field.type === 'card') {
- const children = field.children || [];
- // 优先使用 label(字段名称),如果没有则使用 cardTitle,最后使用默认值
- const cardTitle = field.label || field.cardTitle || '卡片容器';
- return (
- <div key={field.id} style={spanStyle} className="mb-4">
- <Card title={cardTitle} className="w-full">
- <div className="space-y-4">
- {children.map((child: any) => renderFieldPreview(child))}
- </div>
- </Card>
- </div>
- );
- }
- if (field.type === 'grid') {
- const gridColumns = field.gridColumns || 2;
- const children = field.children || [];
- return (
- <div key={field.id} style={spanStyle} className="mb-4">
- <div
- style={{
- display: 'grid',
- gridTemplateColumns: `repeat(${gridColumns}, minmax(0, 1fr))`,
- gap: '16px',
- }}
- >
- {children.map((child: any) => {
- const childSpanStyle = gridColumns > 1
- ? { gridColumn: `span ${Math.min(gridColumns, child.span || 1)}` }
- : undefined;
- return (
- <div key={child.id} style={childSpanStyle}>
- {renderFieldPreview(child)}
- </div>
- );
- })}
- </div>
- </div>
- );
- }
- // 处理 alert 类型
- if (field.type === 'alert') {
- const themeClass =
- field.alertTheme === 'dark'
- ? 'bg-gray-800 text-white border-gray-700'
- : '';
- return (
- <div key={field.id} className="mb-4" style={spanStyle}>
- <Alert
- message={field.alertTitle || field.label}
- description={field.alertDescription}
- type={field.alertType || 'info'}
- showIcon={field.alertShowIcon !== false}
- closable={field.alertClosable}
- closeText={field.alertCloseText}
- className={`${field.alertCentered ? 'text-center' : ''} ${themeClass}`}
- banner
- style={field.style}
- />
- {field.hint && <div className="text-xs text-gray-400 mt-1">{field.hint}</div>}
- </div>
- );
- }
- // 处理普通字段
- switch (field.type) {
- case 'textarea':
- return (
- <div key={field.id} style={spanStyle}>
- <AntdForm.Item
- label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
- name={field.name || field.field}
- required={field.required && !formConfig.hideRequiredMark}
- rules={field.required ? [{ required: true, message: field.requiredMessage || '请输入' }] : []}
- help={field.hint}
- >
- <Input.TextArea
- placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请输入'}
- rows={4}
- allowClear={field.showClear}
- maxLength={field.maxLength}
- readOnly={field.readOnly}
- disabled={field.disabled}
- size={field.size || 'middle'}
- />
- </AntdForm.Item>
- </div>
- );
- case 'password':
- return (
- <div key={field.id} style={spanStyle}>
- <AntdForm.Item
- label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
- name={field.name || field.field}
- required={field.required && !formConfig.hideRequiredMark}
- rules={field.required ? [{ required: true, message: field.requiredMessage || '请输入' }] : []}
- help={field.hint}
- >
- <Input.Password
- placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请输入'}
- allowClear={field.showClear}
- maxLength={field.maxLength}
- readOnly={field.readOnly}
- disabled={field.disabled}
- size={field.size || 'middle'}
- />
- </AntdForm.Item>
- </div>
- );
- case 'number':
- return (
- <div key={field.id} style={spanStyle}>
- <AntdForm.Item
- label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
- name={field.name || field.field}
- required={field.required && !formConfig.hideRequiredMark}
- rules={field.required ? [{ required: true, message: field.requiredMessage || '请输入' }] : []}
- help={field.hint}
- >
- <InputNumber
- style={{ width: '100%' }}
- placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请输入'}
- max={field.maxLength}
- readOnly={field.readOnly}
- disabled={field.disabled}
- size={field.size || 'middle'}
- />
- </AntdForm.Item>
- </div>
- );
- case 'select':
- return (
- <div key={field.id} style={spanStyle}>
- <AntdForm.Item
- label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
- name={field.name || field.field}
- required={field.required && !formConfig.hideRequiredMark}
- rules={field.required ? [{ required: true, message: field.requiredMessage || '请选择' }] : []}
- help={field.hint}
- >
- <Select
- placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请选择'}
- allowClear={field.showClear}
- disabled={field.disabled}
- size={field.size || 'middle'}
- >
- {(field.options || []).map((opt: any, idx: number) => (
- <Select.Option key={idx} value={opt.value}>{opt.label}</Select.Option>
- ))}
- </Select>
- </AntdForm.Item>
- </div>
- );
- case 'date':
- return (
- <div key={field.id} style={spanStyle}>
- <AntdForm.Item
- label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
- name={field.name || field.field}
- required={field.required && !formConfig.hideRequiredMark}
- rules={field.required ? [{ required: true, message: field.requiredMessage || '请选择日期' }] : []}
- help={field.hint}
- >
- <DatePicker
- style={{ width: '100%' }}
- placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请选择日期'}
- allowClear={field.showClear}
- disabled={field.disabled}
- size={field.size || 'middle'}
- />
- </AntdForm.Item>
- </div>
- );
- case 'daterange':
- return (
- <div key={field.id} style={spanStyle}>
- <AntdForm.Item
- label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
- name={field.name || field.field}
- required={field.required && !formConfig.hideRequiredMark}
- rules={field.required ? [{ required: true, message: field.requiredMessage || '请选择日期范围' }] : []}
- help={field.hint}
- >
- <DatePicker.RangePicker
- style={{ width: '100%' }}
- placeholder={Array.isArray(field.placeholder) ? field.placeholder as [string, string] : ['开始日期', '结束日期']}
- allowClear={field.showClear}
- disabled={field.disabled}
- size={field.size || 'middle'}
- />
- </AntdForm.Item>
- </div>
- );
- case 'datetime':
- return (
- <div key={field.id} style={spanStyle}>
- <AntdForm.Item
- label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
- name={field.name || field.field}
- required={field.required && !formConfig.hideRequiredMark}
- rules={field.required ? [{ required: true, message: field.requiredMessage || '请选择日期时间' }] : []}
- help={field.hint}
- >
- <DatePicker
- style={{ width: '100%' }}
- placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请选择日期时间'}
- allowClear={field.showClear}
- showTime={{ format: 'HH:mm:ss' }}
- format="YYYY-MM-DD HH:mm:ss"
- disabled={field.disabled}
- size={field.size || 'middle'}
- />
- </AntdForm.Item>
- </div>
- );
- case 'switch':
- return (
- <div key={field.id} style={spanStyle}>
- <AntdForm.Item
- label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
- name={field.name || field.field}
- required={field.required && !formConfig.hideRequiredMark}
- rules={field.required ? [{ required: true, message: field.requiredMessage || '请选择' }] : []}
- help={field.hint}
- valuePropName="checked"
- >
- <Switch disabled={field.disabled} />
- </AntdForm.Item>
- </div>
- );
- case 'radio':
- return (
- <div key={field.id} style={spanStyle}>
- <AntdForm.Item
- label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
- name={field.name || field.field}
- required={field.required && !formConfig.hideRequiredMark}
- rules={field.required ? [{ required: true, message: field.requiredMessage || '请选择' }] : []}
- help={field.hint}
- >
- <Radio.Group disabled={field.disabled}>
- {(field.options || []).map((opt: any, idx: number) => (
- <Radio key={idx} value={opt.value}>{opt.label}</Radio>
- ))}
- </Radio.Group>
- </AntdForm.Item>
- </div>
- );
- case 'checkbox':
- return (
- <div key={field.id} style={spanStyle}>
- <AntdForm.Item
- label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
- name={field.name || field.field}
- required={field.required && !formConfig.hideRequiredMark}
- rules={field.required ? [{ required: true, message: field.requiredMessage || '请选择' }] : []}
- help={field.hint}
- >
- <Checkbox.Group disabled={field.disabled}>
- {(field.options || []).map((opt: any, idx: number) => (
- <Checkbox key={idx} value={opt.value}>{opt.label}</Checkbox>
- ))}
- </Checkbox.Group>
- </AntdForm.Item>
- </div>
- );
- case 'cascader':
- return (
- <div key={field.id} style={spanStyle}>
- <AntdForm.Item
- label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
- name={field.name || field.field}
- required={field.required && !formConfig.hideRequiredMark}
- rules={field.required ? [{ required: true, message: field.requiredMessage || '请选择' }] : []}
- help={field.hint}
- >
- <Cascader
- placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请选择'}
- options={field.cascaderOptions || []}
- className="w-full"
- allowClear={field.showClear}
- disabled={field.disabled}
- size={field.size || 'middle'}
- />
- </AntdForm.Item>
- </div>
- );
- case 'upload':
- return (
- <div key={field.id} style={spanStyle}>
- <AntdForm.Item
- label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
- name={field.name || field.field}
- required={field.required && !formConfig.hideRequiredMark}
- rules={field.required ? [{ required: true, message: field.requiredMessage || '请上传' }] : []}
- help={field.hint}
- >
- <Upload
- disabled={field.disabled}
- maxCount={field.uploadType === 'single-image' ? 1 : field.maxCount || (field.uploadType === 'multiple-image' ? 9 : undefined)}
- accept={field.accept || (field.uploadType === 'single-image' || field.uploadType === 'multiple-image' ? 'image/*' : undefined)}
- listType={(field.uploadType === 'file' ? 'text' : 'picture-card') as 'text' | 'picture-card' | 'picture'}
- multiple={field.uploadType !== 'single-image'}
- >
- {field.uploadType === 'file' ? (
- <Button icon={<UploadOutlined />}>上传文件</Button>
- ) : (
- <div>
- <UploadOutlined />
- <div style={{ marginTop: 8 }}>上传</div>
- </div>
- )}
- </Upload>
- </AntdForm.Item>
- </div>
- );
- default:
- return (
- <div key={field.id} style={spanStyle}>
- <AntdForm.Item
- label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
- name={field.name || field.field}
- required={field.required && !formConfig.hideRequiredMark}
- rules={field.required ? [{ required: true, message: field.requiredMessage || '请输入' }] : []}
- help={field.hint}
- >
- <Input
- type={field.inputType || 'text'}
- placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请输入'}
- allowClear={field.showClear}
- maxLength={field.maxLength}
- readOnly={field.readOnly}
- disabled={field.disabled}
- size={field.size || 'middle'}
- />
- </AntdForm.Item>
- </div>
- );
- }
- };
- // 查看节点详情(节点详情弹框)
- const handleViewDetail = async (record: MyTaskVO) => {
- console.log('MyTask: handleViewDetail 被调用', record);
-
- if (!record.nodeId) {
- console.warn('MyTask: 节点ID不存在', record);
- message.warning('节点ID不存在');
- return;
- }
-
- setDetailLoading(true);
- setDetailVisible(true); // 先打开弹框显示加载状态
- console.log('MyTask: 弹框状态已设置为 true (加载中)');
-
- try {
- console.log('MyTask: 开始获取节点详情', { nodeId: record.nodeId });
- const response = await myTaskApi.getMyWorkNodeDetail(record.nodeId);
- console.log('MyTask: 节点详情响应', response);
-
- let data: any = response;
-
- // 直接从第一层 data.formId 获取 formId
- let extractedFormId: number | undefined = undefined;
- if (data?.formId !== undefined && data?.formId !== null) {
- const formIdValue = data.formId;
- if (typeof formIdValue === 'string') {
- const parsed = parseInt(formIdValue, 10);
- if (!isNaN(parsed)) {
- extractedFormId = parsed;
- console.log('MyTask: ✅ 从 data.formId (字符串) 提取到 formId', extractedFormId);
- }
- } else if (typeof formIdValue === 'number') {
- extractedFormId = formIdValue;
- console.log('MyTask: ✅ 从 data.formId (数字) 提取到 formId', extractedFormId);
- }
- }
-
- console.log('MyTask: formId 提取结果', {
- extractedFormId,
- '原始 data.formId': data?.formId,
- 'data.formId 类型': typeof data?.formId
- });
-
- if (extractedFormId === undefined) {
- console.warn('MyTask: ⚠️ 未能提取到 formId', {
- 'data.formId': data?.formId,
- 'data': data
- });
- }
-
- // 合并列表数据中的作业信息(作业名称、编号、负责人、时间等)
- // 直接使用第一层 data,不解析 data.data
- const detailDataWithWorkInfo: MyTaskNodeDetailVO = {
- ...(data || {}),
- workName: record.name || data?.workName || data?.name,
- orderNo: record.orderNo || data?.orderNo,
- workerUserName: record.workerUserName || data?.workerUserName,
- workTime: record.workTime || data?.workTime,
- // 确保 type 字段存在,使用第一层 data.type
- type: data?.type || data?.nodeType || '',
- nodeType: data?.nodeType || data?.type || '', // 同时设置 nodeType 作为兼容
- // 使用提取到的 formId
- formId: extractedFormId,
- };
-
- console.log('MyTask: 合并后的详情数据 - type 和 formId 字段', {
- 'data.type': data?.type,
- 'data.nodeType': data?.nodeType,
- 'data.formId': data?.formId,
- 'detailDataWithWorkInfo.type': detailDataWithWorkInfo.type,
- 'detailDataWithWorkInfo.nodeType': detailDataWithWorkInfo.nodeType,
- 'detailDataWithWorkInfo.formId': detailDataWithWorkInfo.formId,
- });
-
- console.log('MyTask: 合并后的详情数据', detailDataWithWorkInfo);
- console.log('MyTask: 节点类型', detailDataWithWorkInfo.type);
-
- // 先设置数据,确保弹框有内容显示
- setDetailData(detailDataWithWorkInfo);
- console.log('MyTask: detailData 已设置', detailDataWithWorkInfo);
-
- // 确保弹框是打开状态
- setDetailVisible(true);
- setDetailLoading(false);
- console.log('MyTask: 弹框状态设置为 true,loading 设置为 false');
-
- // 根据节点类型决定是否需要获取表单
- // isolation、releaseIsolation 和 returnLock 不需要表单,其他类型(review 和其他)需要根据 formId 获取表单
- const nodeType = detailDataWithWorkInfo.type || detailDataWithWorkInfo.nodeType || '';
- const isIsolation = nodeType === 'isolation' || nodeType === '隔离' || nodeType === '隔离/方案';
- const isReleaseIsolation = nodeType === 'releaseIsolation' || nodeType === '解除隔离';
- const isReturnLock = nodeType === 'returnLock';
-
- // 检查任务状态是否为"已通过"
- const isApproved = detailDataWithWorkInfo.approvalStatus === 'approved';
-
- // 获取 formId(可能为 0,所以需要明确检查 undefined 和 null)
- const formId = detailDataWithWorkInfo.formId;
- // formId 可能是数字(包括 0)或字符串,只要不是 undefined、null 或空字符串,就认为有 formId
- const hasFormId = formId !== undefined && formId !== null && formId !== '' && (typeof formId === 'number' || (typeof formId === 'string' && formId.trim() !== ''));
-
- // 检查是否有 formData(已提交的表单数据)
- const hasFormData = detailDataWithWorkInfo.formData && (
- (typeof detailDataWithWorkInfo.formData === 'string' && detailDataWithWorkInfo.formData.trim() !== '') ||
- (typeof detailDataWithWorkInfo.formData === 'object' && Object.keys(detailDataWithWorkInfo.formData).length > 0)
- );
-
- console.log('MyTask: 表单获取判断', {
- nodeType,
- isIsolation,
- isReleaseIsolation,
- isReturnLock,
- isApproved,
- hasFormData,
- formId,
- formIdType: typeof formId,
- hasFormId,
- '从formData获取': isApproved && hasFormData,
- '从formId获取': !isIsolation && !isReleaseIsolation && !isReturnLock && hasFormId && !(isApproved && hasFormData),
- 'detailDataWithWorkInfo': detailDataWithWorkInfo,
- });
-
- // 如果任务状态为"已通过"且有 formData,则从 formData 中解析表单结构
- if (isApproved && hasFormData) {
- console.log('MyTask: ✅ 任务已通过,从 formData 中解析表单结构');
- setFormLoading(true);
-
- try {
- // 解析 formData(可能是 JSON 字符串或对象)
- let parsedFormData: any;
- if (typeof detailDataWithWorkInfo.formData === 'string') {
- parsedFormData = JSON.parse(detailDataWithWorkInfo.formData);
- } else {
- parsedFormData = detailDataWithWorkInfo.formData;
- }
-
- console.log('MyTask: 解析后的 formData', parsedFormData);
-
- // 从 formData 中提取 conf 和 fields
- const conf = parsedFormData.conf;
- const fields = parsedFormData.fields;
-
- if (conf && fields) {
- // 保存原始的 conf 和 fields(JSON 字符串格式)
- const confString = typeof conf === 'string' ? conf : JSON.stringify(conf);
- const fieldsArray = Array.isArray(fields) ? fields : [];
- const fieldsStringArray = fieldsArray.map((field: any) => {
- return typeof field === 'string' ? field : JSON.stringify(field);
- });
-
- setOriginalConf(confString);
- setOriginalFields(fieldsStringArray);
- console.log('MyTask: 从 formData 保存原始表单数据', {
- conf: confString,
- fieldsCount: fieldsStringArray.length,
- fields: fieldsStringArray
- });
-
- // 解析 fields 中的每个字段,提取 value 值用于回显
- const formValues: any = {};
- fieldsStringArray.forEach((fieldString: string) => {
- try {
- const fieldObj = typeof fieldString === 'string' ? JSON.parse(fieldString) : fieldString;
- const fieldName = fieldObj.name || fieldObj.field;
- if (fieldName && fieldObj.value !== undefined && fieldObj.value !== null) {
- formValues[fieldName] = fieldObj.value;
- }
- } catch (e) {
- console.error('MyTask: 解析字段 JSON 失败', e, fieldString);
- }
- });
-
- console.log('MyTask: 从 formData 提取的表单值', formValues);
-
- // 设置表单配置和字段
- setConfAndFields2(setFormData, conf, fields);
- console.log('MyTask: 表单配置已设置(从 formData)');
-
- // 回填表单值
- setTimeout(() => {
- detailForm.setFieldsValue(formValues);
- console.log('MyTask: 表单数据已回填(从 formData)', formValues);
- }, 100);
- } else {
- console.warn('MyTask: formData 中缺少 conf 或 fields', { conf: !!conf, fields: !!fields });
- message.warning('表单数据不完整');
- }
- } catch (e) {
- console.error('MyTask: 解析 formData 失败', e);
- message.error('解析表单数据失败: ' + (e instanceof Error ? e.message : String(e)));
- } finally {
- setFormLoading(false);
- }
- }
- // 如果不是隔离类型,且有表单ID,且不是从 formData 获取,则从接口获取表单配置
- else if (!isIsolation && !isReleaseIsolation && !isReturnLock && hasFormId) {
- console.log('MyTask: ✅ 满足条件,开始获取表单', { formId, nodeType });
- // 重置表单数据
- setFormData({ rule: [], option: {} });
- setOriginalFields([]);
- setOriginalConf('');
- setFormLoading(true);
-
- // 立即调用接口,不使用 setTimeout,确保接口被调用
- (async () => {
- try {
- // 确保 formId 是数字类型
- const numericFormId = typeof formId === 'string' ? parseInt(formId, 10) : formId;
- if (isNaN(numericFormId)) {
- console.error('MyTask: formId 不是有效数字', formId);
- message.error('表单ID无效');
- setFormLoading(false);
- return;
- }
-
- console.log('MyTask: 🚀 开始调用表单接口', { formId: numericFormId, nodeType, url: `/bpm/form/get?id=${numericFormId}` });
- const FormApi = await import('../api/bpm/form');
- const formDetailResponse = await FormApi.getForm(numericFormId);
- console.log('MyTask: ✅ 表单接口调用成功,原始响应', formDetailResponse);
-
- // 处理响应数据(可能包含 code 和 data 包装)
- let formDetail: any = formDetailResponse;
- if (formDetailResponse && typeof formDetailResponse === 'object' && 'data' in formDetailResponse) {
- // 如果响应有 data 字段,使用 data
- formDetail = (formDetailResponse as any).data || formDetailResponse;
- }
-
- console.log('MyTask: 处理后的表单详情', formDetail);
-
- // 获取 conf 和 fields
- const conf = formDetail?.conf || formDetail?.formConfig;
- const fields = formDetail?.fields || formDetail?.formFields;
-
- console.log('MyTask: 表单配置和字段', {
- hasConf: !!conf,
- confType: typeof conf,
- hasFields: !!fields,
- fieldsType: Array.isArray(fields) ? 'array' : typeof fields,
- fieldsLength: Array.isArray(fields) ? fields.length : 0
- });
-
- // 解析表单配置和字段
- if (conf && fields) {
- // 保存原始的 conf 和 fields(JSON 字符串格式)
- const confString = typeof conf === 'string' ? conf : JSON.stringify(conf);
- const fieldsArray = Array.isArray(fields) ? fields : [];
- const fieldsStringArray = fieldsArray.map((field: any) => {
- return typeof field === 'string' ? field : JSON.stringify(field);
- });
-
- setOriginalConf(confString);
- setOriginalFields(fieldsStringArray);
- console.log('MyTask: 保存原始表单数据', {
- conf: confString,
- fieldsCount: fieldsStringArray.length,
- fields: fieldsStringArray
- });
-
- // setConfAndFields2 会自动处理 JSON 字符串解析
- setConfAndFields2(setFormData, conf, fields);
- console.log('MyTask: 表单配置已设置', {
- conf: typeof conf === 'string' ? 'JSON字符串' : '对象',
- fieldsCount: Array.isArray(fields) ? fields.length : 0
- });
-
- // 如果有表单数据值,回填到表单(在表单配置设置后)
- if (detailDataWithWorkInfo.formData) {
- try {
- const formValues = typeof detailDataWithWorkInfo.formData === 'string'
- ? JSON.parse(detailDataWithWorkInfo.formData)
- : detailDataWithWorkInfo.formData;
- // 延迟一下,确保表单字段已经渲染
- setTimeout(() => {
- detailForm.setFieldsValue(formValues);
- console.log('MyTask: 表单数据已回填', formValues);
- }, 100);
- } catch (e) {
- console.error('MyTask: 解析表单数据失败', e);
- }
- }
- } else {
- console.warn('MyTask: 表单详情缺少配置或字段', { conf: !!conf, fields: !!fields, formDetail });
- message.warning('表单配置不完整');
- }
- } catch (e) {
- console.error('MyTask: 获取表单详情失败', e);
- message.error('获取表单详情失败: ' + (e instanceof Error ? e.message : String(e)));
- // 即使获取表单失败,也继续显示弹框
- } finally {
- setFormLoading(false);
- }
- })();
- } else {
- setFormLoading(false);
- if (isIsolation || isReleaseIsolation || isReturnLock) {
- console.log('MyTask: ⚠️ 隔离类型节点,跳过表单配置获取', { nodeType, isIsolation, isReleaseIsolation, isReturnLock });
- } else if (!hasFormId) {
- console.warn('MyTask: ⚠️ 没有表单ID,跳过表单配置获取', {
- formId,
- formIdType: typeof formId,
- hasFormId,
- nodeType,
- 'detailDataWithWorkInfo': detailDataWithWorkInfo
- });
- message.warning(`节点类型 ${nodeType} 缺少表单ID,无法加载表单`);
- } else {
- console.warn('MyTask: ⚠️ 未知原因未获取表单', {
- nodeType,
- isIsolation,
- isReleaseIsolation,
- isReturnLock,
- hasFormId,
- formId
- });
- }
- }
-
- // 注意:表单数据回填已移到表单配置加载完成后,确保表单字段已渲染
-
- // 回显审核意见(如果有 approvalOpinion 字段,且不是 "pending")
- if (detailDataWithWorkInfo.approvalOpinion && detailDataWithWorkInfo.approvalOpinion !== 'pending') {
- setApprovalComment(detailDataWithWorkInfo.approvalOpinion);
- console.log('MyTask: 回显审核意见', detailDataWithWorkInfo.approvalOpinion);
- } else {
- setApprovalComment(''); // 重置审核意见(包括 pending 的情况)
- }
-
- setDetailLoading(false);
- console.log('MyTask: 弹框已打开', { detailVisible: true, detailData: detailDataWithWorkInfo });
- } catch (error: any) {
- console.error('MyTask: 获取节点详情失败', error);
- toast.error(error.message || '获取节点详情失败');
- // 即使接口失败,也显示弹框(显示错误信息)
- if (!detailData) {
- setDetailData({
- id: record.nodeId,
- nodeId: record.nodeId,
- workId: record.workId,
- workName: record.name,
- orderNo: record.orderNo,
- workerUserName: record.workerUserName,
- workTime: record.workTime,
- type: '',
- } as MyTaskNodeDetailVO);
- }
- setDetailVisible(true);
- } finally {
- setDetailLoading(false);
- }
- };
- // 获取任务状态显示文本(使用 approval_status 字典)
- const getTaskStatusText = (status: string | number | undefined): string => {
- if (!status) return '未知';
- const statusStr = String(status).toLowerCase();
- const statusItem = approvalStatusDictList.find(item => String(item.value).toLowerCase() === statusStr);
- if (statusItem) {
- return statusItem.label || statusItem.name || '未知';
- }
- // 如果没有找到字典值,使用默认映射
- const statusMap: Record<string, string> = {
- 'pending': '待审核',
- 'approved': '已通过',
- 'rejected': '已驳回',
- 'unaudited': '未审核',
- };
- return statusMap[statusStr] || '未知';
- };
- // 获取任务状态样式(审批状态)
- const getTaskStatusClassName = (status: string | number | undefined): string => {
- if (!status) return 'bg-gray-100 text-gray-600';
- const statusStr = String(status).toLowerCase();
- const statusMap: Record<string, string> = {
- 'pending': 'bg-yellow-100 text-yellow-700',
- 'approved': 'bg-green-100 text-green-700',
- 'rejected': 'bg-red-100 text-red-700',
- 'unaudited': 'bg-gray-100 text-gray-600',
- };
- return statusMap[statusStr] || 'bg-gray-100 text-gray-600';
- };
- // 表格列配置
- const columns: ColumnsType<MyTaskVO> = [
- {
- title: '作业编号',
- dataIndex: 'orderNo',
- width: 180,
- align: 'center',
- render: (text: string) => text || '-',
- },
- {
- title: '作业名称',
- dataIndex: 'name',
- width: 220,
- align: 'center',
- ellipsis: true,
- },
- {
- title: '负责人',
- dataIndex: 'workerUserName',
- width: 150,
- align: 'center',
- render: (text: string, record: MyTaskVO) => {
- // 优先使用 workerUserName,如果没有则尝试从其他字段获取
- return text || record.responsibleName || record.responsible || record.initiatorName || record.initiator || '-';
- },
- },
- {
- title: '当前任务',
- dataIndex: 'currentNodeName',
- width: 180,
- align: 'center',
- render: (text: string, record: MyTaskVO) => {
- // 优先使用 currentNodeName,如果没有则使用 currentNode
- return text || record.currentNode || '-';
- },
- },
- {
- title: '任务开始时间',
- dataIndex: 'workTime',
- width: 180,
- align: 'center',
- render: (text: string | number | Date | undefined, record: MyTaskVO) => {
- // 优先使用 workTime,如果没有则使用其他时间字段
- const time = text || record.taskStartTime || record.initiationTime || record.initiateTime;
- return time ? dateFormatter(time) : '-';
- },
- },
- {
- title: '任务状态',
- dataIndex: 'approvalStatus',
- width: 120,
- align: 'center',
- render: (status: string | number | undefined, record: MyTaskVO) => {
- // 优先使用 approvalStatus,如果没有则使用其他状态字段
- const taskStatus = status || record.taskStatus || record.status;
- return (
- <span
- className={`inline-flex px-3 py-1 rounded-lg text-xs ${getTaskStatusClassName(taskStatus)}`}
- >
- {getTaskStatusText(taskStatus)}
- </span>
- );
- },
- },
- {
- title: '操作',
- width: 120,
- align: 'center',
- fixed: 'right',
- render: (_: any, record: MyTaskVO) => (
- <Space size="small">
- <Button
- type="link"
- size="small"
- icon={<Eye className="w-4 h-4" />}
- onClick={(e) => {
- e.stopPropagation();
- console.log('MyTask: 查看详情按钮被点击', record);
- handleViewDetail(record);
- }}
- >
- 查看详情
- </Button>
- </Space>
- ),
- },
- ];
- // 计算分页数据
- const totalPages = Math.ceil(total / queryParams.pageSize) || 1;
- return (
- <div className="space-y-6">
- {/* 操作栏和表格容器 */}
- <div className="bg-white rounded-2xl border border-gray-200/50 shadow-sm overflow-hidden">
- {/* 查询与操作栏 */}
- <div className="p-4 lg:p-5 border-b border-gray-200/50">
- <div className="flex flex-wrap items-center gap-3">
- <Input
- value={searchKey}
- onChange={(e) => setSearchKey(e.target.value)}
- placeholder="请输入作业编号或者作业名称进行查询"
- allowClear
- className="flex-1"
- style={{ minWidth: 200 }}
- onPressEnter={handleSearch}
- />
- <Space size="small">
- <Button type="primary" icon={<Search className="w-4 h-4" />} onClick={handleSearch}>
- 搜索
- </Button>
- <Button icon={<RotateCcw className="w-4 h-4" />} onClick={handleReset}>
- 重置
- </Button>
- </Space>
- </div>
- </div>
- {/* 表格容器 */}
- <div className="overflow-hidden min-w-0">
- <AntdTable
- loading={loading}
- columns={columns}
- dataSource={list}
- rowKey={(record) => record.id || Math.random()}
- pagination={false}
- scroll={{ x: 'max-content' }}
- locale={{
- emptyText: '暂无数据',
- }}
- />
- </div>
- </div>
- {/* 分页 */}
- {total > 0 && (
- <div className="bg-white rounded-lg border border-gray-200 px-6 py-4">
- <div className="flex items-center justify-between">
- <div className="text-sm text-gray-600">
- 共 <span className="text-blue-600 font-medium">{total}</span> 条记录
- </div>
- <div className="flex gap-2">
- <Button
- onClick={() => setQueryParams({ ...queryParams, pageNo: queryParams.pageNo - 1 })}
- disabled={queryParams.pageNo <= 1}
- >
- 上一页
- </Button>
- <span className="px-4 py-2 text-sm text-gray-600 flex items-center">
- {queryParams.pageNo} / {totalPages}
- </span>
- <Button
- onClick={() => setQueryParams({ ...queryParams, pageNo: queryParams.pageNo + 1 })}
- disabled={queryParams.pageNo >= totalPages}
- >
- 下一页
- </Button>
- </div>
- </div>
- </div>
- )}
- {/* 详情弹框 */}
- <Modal
- title={null}
- open={detailVisible}
- onCancel={() => {
- console.log('MyTask: 弹框关闭');
- setDetailVisible(false);
- setDetailData(null);
- setFormData({ rule: [], option: {} });
- setFormLoading(false);
- setOriginalFields([]);
- setOriginalConf('');
- detailForm.resetFields();
- setApprovalComment('');
- }}
- footer={null}
- width={800}
- destroyOnClose
- confirmLoading={detailLoading}
- maskClosable={false}
- zIndex={1000}
- styles={{
- body: {
- minHeight: '500px',
- maxHeight: '600px',
- height: '600px',
- padding: 0,
- display: 'flex',
- flexDirection: 'column',
- overflow: 'hidden'
- }
- }}
- >
- {(() => {
- console.log('MyTask: Modal 内容渲染', { detailLoading, detailData, detailVisible });
- if (detailLoading) {
- return <div className="py-8 text-center">加载中...</div>;
- }
- if (detailData) {
- return (
- <div style={{
- display: 'flex',
- flexDirection: 'column',
- height: '100%',
- overflow: 'hidden'
- }}>
- {/* 标题区域 */}
- <div className="mb-4" style={{ padding: '20px 24px 0' }}>
- <h2 className="text-xl font-semibold text-gray-900 mb-2">
- {detailData.workName || detailData.name || '作业详情'}
- </h2>
- <div className="text-sm flex gap-4" style={{ color: '#898f9a' }}>
- <span>作业编号:{detailData.orderNo || '-'}</span>
- <span>负责人:{detailData.workerUserName || '-'}</span>
- <span>时间:{detailData.workTime ? dateFormatter(detailData.workTime) : '-'}</span>
- </div>
- </div>
- {/* 内容区域 - 可滚动 */}
- <div className="mt-6" style={{
- padding: '0 24px',
- flex: 1,
- overflowY: 'auto',
- minHeight: 0
- }}>
- {(() => {
- // 检查节点状态是否为已通过(approved),如果是则禁用所有输入和按钮
- const isApproved = detailData?.approvalStatus === 'approved';
-
- // 从 detailData.type 或 detailData.nodeType 获取节点类型
- const nodeType = String(detailData?.type || detailData?.nodeType || '').trim();
- console.log('MyTask: 渲染内容区域 - 节点类型', nodeType, 'detailData:', detailData);
-
- const isReview = nodeType === 'review';
- const isIsolation = nodeType === 'isolation';
- const isReleaseIsolation = nodeType === 'releaseIsolation';
- const isReturnLock = nodeType === 'returnLock';
-
- console.log('MyTask: 节点类型判断结果', {
- isReview,
- isIsolation,
- isReleaseIsolation,
- isReturnLock,
- nodeType,
- 'detailData.type': detailData.type,
- 'detailData.nodeType': detailData.nodeType,
- 'typeof detailData.type': typeof detailData.type,
- 'String(detailData.type)': String(detailData.type)
- });
- // 隔离/方案节点、解除隔离节点和还锁节点
- if (isIsolation || isReleaseIsolation || isReturnLock) {
- console.log('MyTask: ✅ 进入隔离节点渲染分支', { isIsolation, isReleaseIsolation, isReturnLock, nodeType });
- const isolationContent = (
- <div
- key="isolation-content"
- style={{
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- minHeight: '400px',
- padding: '40px 20px'
- }}
- >
- <Card
- style={{
- width: '100%',
- maxWidth: '500px',
- textAlign: 'center',
- borderRadius: '12px',
- boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
- border: '1px solid #e8e8e8'
- }}
- bodyStyle={{
- padding: '40px 30px'
- }}
- >
- <div style={{ marginBottom: '24px' }}>
- <LockOutlined
- style={{
- fontSize: '64px',
- color: '#1890ff',
- display: 'block'
- }}
- />
- </div>
- <div
- style={{
- fontSize: '20px',
- fontWeight: 500,
- color: '#333',
- lineHeight: '1.6'
- }}
- >
- 请前往锁控柜进行取锁,取钥匙操作
- </div>
- </Card>
- </div>
- );
- console.log('MyTask: 隔离节点内容已创建', isolationContent);
- return isolationContent;
- }
- // 审核类型节点 (review)
- if (isReview) {
- return (
- <div style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
- <div className="space-y-6" style={{ padding: '0 24px', flex: 1, overflowY: 'auto', minHeight: 0 }}>
- {/* 自定义表单 */}
- {formLoading ? (
- <div className="py-8 text-center text-gray-500">表单加载中...</div>
- ) : formData.rule && formData.rule.length > 0 ? (
- <div>
- {(() => {
- const formConfig = formData.option?.formConfig || defaultFormConfig;
- const layoutColumns = formConfig.layoutColumns || 1;
- const gridStyle = layoutColumns > 1 ? {
- display: 'grid',
- gridTemplateColumns: `repeat(${layoutColumns}, minmax(0, 1fr))`,
- gap: '12px',
- rowGap: '16px',
- } : undefined;
-
- return (
- <AntdForm
- form={detailForm}
- layout={formConfig.labelPosition === 'top' ? 'vertical' : formConfig.labelPosition === 'left' ? 'horizontal' : 'horizontal'}
- size={formConfig.formSize === 'default' ? 'middle' : formConfig.formSize}
- requiredMark={formConfig.hideRequiredMark ? false : undefined}
- labelCol={formConfig.labelWidth ? {
- style: {
- width: `${formConfig.labelWidth}px`,
- textAlign: formConfig.labelPosition === 'left' ? 'left' : formConfig.labelPosition === 'right' ? 'right' : 'left'
- }
- } : undefined}
- >
- <div
- style={gridStyle}
- className={layoutColumns === 1 ? 'space-y-4' : 'form-detail-grid'}
- >
- <style>{`
- .form-detail-grid .ant-form-item {
- margin-bottom: 12px;
- }
- `}</style>
- {(formData.rule || []).map((field: any) => {
- // 如果已通过,禁用所有字段
- const fieldWithDisabled = isApproved ? { ...field, disabled: true, readOnly: true } : field;
- return renderFieldPreview(fieldWithDisabled);
- })}
- </div>
- </AntdForm>
- );
- })()}
- </div>
- ) : (
- <div className="py-4 text-center text-gray-400 text-sm">暂无表单内容</div>
- )}
- {/* 审核意见 - label 和 textarea 同一行 */}
- <div className="flex items-start" style={{ gap: '16px' }}>
- <label
- className="text-sm font-medium text-gray-700 whitespace-nowrap pt-2"
- style={{
- width: defaultFormConfig.labelWidth ? `${defaultFormConfig.labelWidth - 20}px` : '80px',
- textAlign: defaultFormConfig.labelPosition === 'left' ? 'left' : defaultFormConfig.labelPosition === 'right' ? 'right' : 'left',
- flexShrink: 0
- }}
- >
- 审核意见
- </label>
- <div className="flex-1">
- <Input.TextArea
- rows={4}
- value={approvalComment}
- onChange={(e) => setApprovalComment(e.target.value)}
- placeholder="请输入审核意见"
- maxLength={500}
- showCount
- disabled={isApproved}
- readOnly={isApproved}
- />
- </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 = detailData.nodeId || detailData.id;
- if (!nodeId) {
- message.error('节点ID不存在');
- return;
- }
-
- // 如果有自定义表单,先校验表单
- if (formData.rule && formData.rule.length > 0) {
- try {
- // 校验表单所有字段
- await detailForm.validateFields();
- console.log('MyTask: 表单校验通过');
- } catch (error: any) {
- // 校验失败,显示错误信息
- console.error('MyTask: 表单校验失败', error);
- if (error.errorFields && error.errorFields.length > 0) {
- // 获取第一个错误字段的提示信息
- const firstError = error.errorFields[0];
- const errorMessage = firstError.errors?.[0] || '请完善表单内容';
- message.error(errorMessage);
- } else {
- message.error('请完善表单内容');
- }
- return; // 阻止提交
- }
- }
-
- setApprovalLoading(true);
- try {
- const params: UpdateNodeApprovalParam = {
- nodeId: nodeId,
- approvalStatus: 'rejected',
- approvalOpinion: approvalComment || undefined,
- };
-
- console.log('MyTask: 调用审核不通过接口', params);
- await myTaskApi.updateNodeApproval(params);
- message.success('审核不通过操作成功');
-
- // 关闭弹框
- setDetailVisible(false);
- setDetailData(null);
- setFormData({ rule: [], option: {} });
- setFormLoading(false);
- setOriginalFields([]);
- setOriginalConf('');
- detailForm.resetFields();
- setApprovalComment('');
-
- // 刷新列表
- getList();
- } catch (error: any) {
- console.error('MyTask: 审核不通过失败', error);
- message.error(error?.message || '审核不通过操作失败');
- } finally {
- setApprovalLoading(false);
- }
- }}
- >
- 审核不通过
- </Button>
- <Button
- type="primary"
- loading={approvalLoading}
- disabled={isApproved}
- onClick={async () => {
- const nodeId = detailData.nodeId || detailData.id;
- if (!nodeId) {
- message.error('节点ID不存在');
- return;
- }
-
- // 如果有自定义表单,先校验表单
- if (formData.rule && formData.rule.length > 0) {
- try {
- // 校验表单所有字段
- await detailForm.validateFields();
- console.log('MyTask: 表单校验通过');
- } catch (error: any) {
- // 校验失败,显示错误信息
- console.error('MyTask: 表单校验失败', error);
- if (error.errorFields && error.errorFields.length > 0) {
- // 获取第一个错误字段的提示信息
- const firstError = error.errorFields[0];
- const errorMessage = firstError.errors?.[0] || '请完善表单内容';
- message.error(errorMessage);
- } else {
- message.error('请完善表单内容');
- }
- return; // 阻止提交
- }
- }
-
- setApprovalLoading(true);
- try {
- const params: UpdateNodeApprovalParam = {
- nodeId: nodeId,
- approvalStatus: 'approved',
- approvalOpinion: approvalComment || undefined,
- };
-
- console.log('MyTask: 调用审核通过接口', params);
- await myTaskApi.updateNodeApproval(params);
- message.success('审核通过操作成功');
-
- // 关闭弹框
- setDetailVisible(false);
- setDetailData(null);
- setFormData({ rule: [], option: {} });
- setFormLoading(false);
- setOriginalFields([]);
- setOriginalConf('');
- detailForm.resetFields();
- setApprovalComment('');
-
- // 刷新列表
- getList();
- } catch (error: any) {
- console.error('MyTask: 审核通过失败', error);
- message.error(error?.message || '审核通过操作失败');
- } finally {
- setApprovalLoading(false);
- }
- }}
- >
- 审核通过
- </Button>
- </div>
- </div>
- );
- }
- // 其他节点类型(需要根据 formId 获取表单)
- return (
- <div style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
- <div className="space-y-6" style={{ padding: '0 24px', flex: 1, overflowY: 'auto', minHeight: 0 }}>
- {/* 自定义表单 */}
- {formLoading ? (
- <div className="py-8 text-center text-gray-500">表单加载中...</div>
- ) : formData.rule && formData.rule.length > 0 ? (
- <div>
- {(() => {
- const formConfig = formData.option?.formConfig || defaultFormConfig;
- const layoutColumns = formConfig.layoutColumns || 1;
- const gridStyle = layoutColumns > 1 ? {
- display: 'grid',
- gridTemplateColumns: `repeat(${layoutColumns}, minmax(0, 1fr))`,
- gap: '12px',
- rowGap: '16px',
- } : undefined;
-
- return (
- <AntdForm
- form={detailForm}
- layout={formConfig.labelPosition === 'top' ? 'vertical' : formConfig.labelPosition === 'left' ? 'horizontal' : 'horizontal'}
- size={formConfig.formSize === 'default' ? 'middle' : formConfig.formSize}
- requiredMark={formConfig.hideRequiredMark ? false : undefined}
- labelCol={formConfig.labelWidth ? {
- style: {
- width: `${formConfig.labelWidth}px`,
- textAlign: formConfig.labelPosition === 'left' ? 'left' : formConfig.labelPosition === 'right' ? 'right' : 'left'
- }
- } : undefined}
- >
- <div
- style={gridStyle}
- className={layoutColumns === 1 ? 'space-y-4' : 'form-detail-grid'}
- >
- <style>{`
- .form-detail-grid .ant-form-item {
- margin-bottom: 12px;
- }
- `}</style>
- {(formData.rule || []).map((field: any) => {
- // 如果已通过,禁用所有字段
- const fieldWithDisabled = isApproved ? { ...field, disabled: true, readOnly: true } : field;
- return renderFieldPreview(fieldWithDisabled);
- })}
- </div>
- </AntdForm>
- );
- })()}
- </div>
- ) : (
- <div className="py-4 text-center text-gray-400 text-sm">暂无表单内容</div>
- )}
- </div>
- {/* 底部按钮 */}
- <div
- className="flex justify-end gap-3"
- style={{
- padding: '16px 24px',
- borderTop: '1px solid #f0f0f0',
- flexShrink: 0
- }}
- >
- <Button
- onClick={() => {
- setDetailVisible(false);
- setDetailData(null);
- setFormData({ rule: [], option: {} });
- setOriginalFields([]);
- setOriginalConf('');
- detailForm.resetFields();
- }}
- >
- 取消
- </Button>
- <Button
- type="primary"
- loading={submitLoading}
- disabled={isApproved}
- onClick={async () => {
- try {
- // 验证表单
- const values = await detailForm.validateFields();
-
- // 获取节点ID
- const nodeId = detailData.nodeId || detailData.id;
- if (!nodeId) {
- message.error('节点ID不存在');
- return;
- }
-
- setSubmitLoading(true);
-
- // 将填写值添加到原始 fields 数组的每个 JSON 字符串中
- const fieldsWithValues = originalFields.map((fieldString: string) => {
- try {
- // 解析字段 JSON 字符串
- const fieldObj = JSON.parse(fieldString);
-
- // 获取字段名:优先使用 name(表单实际使用的字段名),其次使用 field
- const fieldName = fieldObj.name || fieldObj.field;
-
- // 获取填写值
- const fieldValue = values[fieldName];
-
- // 添加或更新 value 字段
- fieldObj.value = fieldValue !== undefined && fieldValue !== null ? fieldValue : '';
-
- // 转回 JSON 字符串
- return JSON.stringify(fieldObj);
- } catch (e) {
- console.error('MyTask: 解析字段 JSON 失败', e, fieldString);
- return fieldString; // 如果解析失败,返回原始字符串
- }
- });
-
- // 构建完整的表单数据对象
- const submitData = {
- id: detailData.formId || detailData.id,
- name: detailData.nodeName || '未知',
- conf: originalConf,
- fields: fieldsWithValues
- };
-
- // 将表单数据转换为 JSON 字符串
- const formDataString = JSON.stringify(submitData);
-
- // 调用接口
- const params: UpdateNodeApprovalParam = {
- nodeId: nodeId,
- approvalStatus: 'approved',
- approvalOpinion: undefined, // 非审核节点,不需要审批意见
- formData: formDataString
- };
-
- console.log('MyTask: 调用提交接口', params);
- await myTaskApi.updateNodeApproval(params);
- message.success('提交成功');
-
- // 关闭弹框
- setDetailVisible(false);
- setDetailData(null);
- setFormData({ rule: [], option: {} });
- setFormLoading(false);
- setOriginalFields([]);
- setOriginalConf('');
- detailForm.resetFields();
-
- // 刷新列表
- getList();
- } catch (error: any) {
- console.error('MyTask: 提交失败', error);
- if (error?.errorFields) {
- // 表单验证失败
- message.error('请填写完整的表单内容');
- } else {
- message.error(error?.message || '提交失败');
- }
- } finally {
- setSubmitLoading(false);
- }
- }}
- >
- 提交
- </Button>
- </div>
- </div>
- );
- })()}
- </div>
- </div>
- );
- }
- return <div className="py-8 text-center text-gray-500">暂无数据</div>;
- })()}
- </Modal>
- </div>
- );
- }
|