فهرست منبع

新增作业列表和发起作业页面

pm 5 ماه پیش
والد
کامیت
033f19674b
3فایلهای تغییر یافته به همراه2139 افزوده شده و 68 حذف شده
  1. 15 0
      src/api/WorkJob.ts
  2. 1812 41
      src/components/IsolationWork.tsx
  3. 312 27
      src/components/ProcessDesigner.tsx

+ 15 - 0
src/api/WorkJob.ts

@@ -28,6 +28,16 @@ export interface UpdateWorkJobParam {
   [key: string]: any;
 }
 
+// 发起作业参数
+export interface InsertWorkflowWorkParam {
+  name: string; // 作业名称
+  type: string; // 作业分类
+  designId: number; // 流程设计ID
+  description: string; // 作业内容/描述
+  urgencyLevel?: string; // 紧急程度
+  [key: string]: any;
+}
+
 // 分页参数类型
 export interface PageParam {
   pageNo?: number;
@@ -70,4 +80,9 @@ export const workJobApi = {
     const idsStr = ids.join(',');
     return axiosInstance.delete(`/iscs/work-job/deleteWorkJobList?ids=${idsStr}`);
   },
+  
+  // 发起作业(创建流程作业)
+  insertWorkflowWork: (data: InsertWorkflowWorkParam) => {
+    return axiosInstance.post('/iscs/workflow-work/insertWorkflowWork', data);
+  },
 };

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1812 - 41
src/components/IsolationWork.tsx


+ 312 - 27
src/components/ProcessDesigner.tsx

@@ -131,14 +131,15 @@ import {
   FireOutlined as FireIconOutlined,
   ThunderboltOutlined as ThunderboltIconOutlined,
 } from '@ant-design/icons';
-import { Button, Input, Select, Checkbox, Tabs, Modal, Dropdown, Popover, message } from 'antd';
+import { Button, Input, Select, Checkbox, Tabs, Modal, Dropdown, Popover, message, Card, Alert, InputNumber, Radio, DatePicker, Form as AntdForm, Cascader, Upload, Switch } from 'antd';
 import type { MenuProps } from 'antd';
 import { toast } from 'sonner';
 import { workflowDesignApi, WorkflowDesignVO } from '../api/WorkflowDesign';
 import { userApi } from '../api/user';
 import { UserVO } from '../types';
 import { segregationPointApi, SegregationPointVO } from '../api/spm';
-import { getFormPage, FormVO } from '../api/bpm/form';
+import { getFormPage, getForm, FormVO } from '../api/bpm/form';
+import { setConfAndFields2, FormCreateData } from '../utils/formCreate';
 
 // 节点配置
 const nodeConfigs = [
@@ -694,6 +695,29 @@ export default function ProcessDesigner() {
   const [workflowDetail, setWorkflowDetail] = useState<WorkflowDesignVO | null>(null);
   const [loadingDetail, setLoadingDetail] = useState(false);
   
+  // 表单预览相关状态
+  const [formPreviewVisible, setFormPreviewVisible] = useState(false);
+  const [formPreviewData, setFormPreviewData] = useState<FormVO | null>(null);
+  const [formPreviewLoading, setFormPreviewLoading] = useState(false);
+  const [formPreviewDetailData, setFormPreviewDetailData] = useState<FormCreateData>({
+    rule: [],
+    option: {}
+  });
+  const formPreviewForm = AntdForm.useForm()[0];
+  
+  const defaultFormConfig = {
+    name: '',
+    labelPosition: 'right',
+    formSize: 'middle',
+    labelSuffix: '',
+    labelWidth: 100,
+    hideRequiredMark: false,
+    showValidationError: true,
+    inlineValidation: false,
+    showSubmitButton: false,
+    showResetButton: false,
+  };
+  
   // 角色用户列表
   const [drawerUsers, setDrawerUsers] = useState<UserVO[]>([]); // 负责人(jtdrawer)
   const [lockerUsers, setLockerUsers] = useState<UserVO[]>([]); // 上锁人(jtlocker)
@@ -1476,7 +1500,7 @@ export default function ProcessDesigner() {
   const handleSave = async () => {
     try {
       // 构建导出数据(与导出JSON逻辑相同)
-      const adjacency: Record<string, { incoming: string[]; outgoing: string[] }> = {};
+      const adjacency: Record<string, { parentUuid: string[]; childrenUuid: string[] }> = {};
       const nodeIdMap = new Map<string, string>();
       nodes.forEach(n => {
         nodeIdMap.set(n.id, n.id);
@@ -1484,15 +1508,15 @@ export default function ProcessDesigner() {
 
       nodes.forEach(n => {
         const uuid = nodeIdMap.get(n.id) || n.id;
-        adjacency[uuid] = adjacency[uuid] || { incoming: [], outgoing: [] };
+        adjacency[uuid] = adjacency[uuid] || { parentUuid: [], childrenUuid: [] };
       });
       edges.forEach(e => {
         const sourceUuid = nodeIdMap.get(e.source) || e.source;
         const targetUuid = nodeIdMap.get(e.target) || e.target;
-        if (!adjacency[sourceUuid]) adjacency[sourceUuid] = { incoming: [], outgoing: [] };
-        if (!adjacency[targetUuid]) adjacency[targetUuid] = { incoming: [], outgoing: [] };
-        adjacency[sourceUuid].outgoing.push(targetUuid);
-        adjacency[targetUuid].incoming.push(sourceUuid);
+        if (!adjacency[sourceUuid]) adjacency[sourceUuid] = { parentUuid: [], childrenUuid: [] };
+        if (!adjacency[targetUuid]) adjacency[targetUuid] = { parentUuid: [], childrenUuid: [] };
+        adjacency[sourceUuid].childrenUuid.push(targetUuid);
+        adjacency[targetUuid].parentUuid.push(sourceUuid);
       });
 
       const exportData = {
@@ -1587,18 +1611,18 @@ export default function ProcessDesigner() {
     });
 
     // adjacency使用uuid作为key
-    const adjacency: Record<string, { incoming: string[]; outgoing: string[] }> = {};
+    const adjacency: Record<string, { parentUuid: string[]; childrenUuid: string[] }> = {};
     nodes.forEach(n => {
       const uuid = nodeIdMap.get(n.id) || n.id;
-      adjacency[uuid] = adjacency[uuid] || { incoming: [], outgoing: [] };
+      adjacency[uuid] = adjacency[uuid] || { parentUuid: [], childrenUuid: [] };
     });
     edges.forEach(e => {
       const sourceUuid = nodeIdMap.get(e.source) || e.source;
       const targetUuid = nodeIdMap.get(e.target) || e.target;
-      if (!adjacency[sourceUuid]) adjacency[sourceUuid] = { incoming: [], outgoing: [] };
-      if (!adjacency[targetUuid]) adjacency[targetUuid] = { incoming: [], outgoing: [] };
-      adjacency[sourceUuid].outgoing.push(targetUuid);
-      adjacency[targetUuid].incoming.push(sourceUuid);
+      if (!adjacency[sourceUuid]) adjacency[sourceUuid] = { parentUuid: [], childrenUuid: [] };
+      if (!adjacency[targetUuid]) adjacency[targetUuid] = { parentUuid: [], childrenUuid: [] };
+      adjacency[sourceUuid].childrenUuid.push(targetUuid);
+      adjacency[targetUuid].parentUuid.push(sourceUuid);
     });
 
     const exportData = {
@@ -2301,19 +2325,47 @@ export default function ProcessDesigner() {
                             <label className="block text-sm font-medium text-gray-700 mb-2">
                               提交表单 <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
                             </label>
-                            <Select
-                              value={nodeConfig.submitForm || undefined}
-                              onChange={(value) =>
-                                setNodeConfig({ ...nodeConfig, submitForm: value || '' })
-                              }
-                              placeholder="请选择提交表单"
-                              className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!h-10"
-                              allowClear
-                            >
-                              {formList.map(form => (
-                                <Select.Option key={form.id} value={form.id}>{form.name}</Select.Option>
-                              ))}
-                            </Select>
+                            <div className="flex gap-2">
+                              <Select
+                                value={nodeConfig.submitForm || undefined}
+                                onChange={(value) =>
+                                  setNodeConfig({ ...nodeConfig, submitForm: value || '' })
+                                }
+                                placeholder="请选择提交表单"
+                                className="flex-1 [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!h-10"
+                                allowClear
+                              >
+                                {formList.map(form => (
+                                  <Select.Option key={form.id} value={form.id}>{form.name}</Select.Option>
+                                ))}
+                              </Select>
+                              <Button
+                                icon={<EyeOutlined />}
+                                onClick={async () => {
+                                  if (!nodeConfig.submitForm) {
+                                    message.warning('请先选择表单');
+                                    return;
+                                  }
+                                  setFormPreviewLoading(true);
+                                  try {
+                                    const formData = await getForm(Number(nodeConfig.submitForm));
+                                    setFormPreviewData(formData);
+                                    // 解析表单配置和字段
+                                    setConfAndFields2(setFormPreviewDetailData, formData.conf, formData.fields);
+                                    setFormPreviewVisible(true);
+                                  } catch (error: any) {
+                                    message.error(error?.message || '获取表单详情失败');
+                                  } finally {
+                                    setFormPreviewLoading(false);
+                                  }
+                                }}
+                                disabled={!nodeConfig.submitForm}
+                                loading={formPreviewLoading}
+                                className="flex-shrink-0"
+                              >
+                                预览
+                              </Button>
+                            </div>
                           </div>
 
                           {/* 隔离/方案 和 解除隔离 节点特有的字段 */}
@@ -2834,6 +2886,239 @@ export default function ProcessDesigner() {
             className="font-mono text-xs"
           />
         </div>
+        </Modal>
+
+      {/* 表单预览Modal */}
+      <Modal
+        open={formPreviewVisible}
+        title={`预览表单 - ${formPreviewData?.name || ''}`}
+        onCancel={() => {
+          setFormPreviewVisible(false);
+          setFormPreviewData(null);
+          setFormPreviewDetailData({ rule: [], option: {} });
+        }}
+        footer={[
+          <Button key="close" onClick={() => {
+            setFormPreviewVisible(false);
+            setFormPreviewData(null);
+            setFormPreviewDetailData({ rule: [], option: {} });
+          }}>
+            关闭
+          </Button>
+        ]}
+        width={800}
+        style={{ top: 20 }}
+        styles={{ body: { maxHeight: 'calc(90vh - 120px)', overflowY: 'auto', overflowX: 'hidden' } }}
+      >
+        <div className="p-4">
+          {(() => {
+            const formConfig = formPreviewDetailData.option?.formConfig || defaultFormConfig;
+            const layoutColumns = formConfig.layoutColumns || 1;
+            
+            // 渲染字段预览(支持嵌套结构)
+            const renderFieldPreview = (field: any, parentSpanStyle?: React.CSSProperties): React.ReactNode => {
+              const spanStyle = parentSpanStyle || (layoutColumns > 1 ? { gridColumn: `span ${Math.min(layoutColumns, field.span || 1)}` } : undefined);
+
+              // 处理容器类型(card 和 grid)
+              if (field.type === 'card') {
+                const children = field.children || [];
+                const cardTitle = field.cardTitle || field.label || '卡片容器';
+                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') {
+                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}
+                    />
+                  </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}
+                        help={field.hint}
+                      >
+                        <Input.TextArea 
+                          placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请输入'} 
+                          rows={4}
+                          disabled
+                        />
+                      </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}
+                        help={field.hint}
+                      >
+                        <InputNumber style={{ width: '100%' }} placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请输入'} disabled />
+                      </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}
+                        help={field.hint}
+                      >
+                        <Select placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请选择'} disabled>
+                          {(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}
+                        help={field.hint}
+                      >
+                        <DatePicker style={{ width: '100%' }} placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请选择日期'} disabled />
+                      </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}
+                        valuePropName="checked"
+                      >
+                        <Switch 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}
+                        help={field.hint}
+                      >
+                        <Radio.Group 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}
+                        help={field.hint}
+                      >
+                        <Checkbox.Group disabled>
+                          {(field.options || []).map((opt: any, idx: number) => (
+                            <Checkbox key={idx} value={opt.value}>{opt.label}</Checkbox>
+                          ))}
+                        </Checkbox.Group>
+                      </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}
+                        help={field.hint}
+                      >
+                        <Input 
+                          type={field.inputType || 'text'}
+                          placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请输入'}
+                          disabled
+                        />
+                      </AntdForm.Item>
+                    </div>
+                  );
+              }
+            };
+
+            return (
+              <AntdForm
+                form={formPreviewForm}
+                layout={formConfig.labelPosition === 'top' ? 'vertical' : 'horizontal'}
+                size={formConfig.formSize || 'middle'}
+                labelCol={formConfig.labelPosition !== 'top' ? { span: formConfig.labelWidth ? Math.floor(formConfig.labelWidth / 8) : 6 } : undefined}
+                wrapperCol={formConfig.labelPosition !== 'top' ? { span: 24 - (formConfig.labelWidth ? Math.floor(formConfig.labelWidth / 8) : 6) } : undefined}
+              >
+                <div style={layoutColumns > 1 ? { display: 'grid', gridTemplateColumns: `repeat(${layoutColumns}, minmax(0, 1fr))`, gap: '16px' } : {}}>
+                  {(formPreviewDetailData.rule || []).map((field: any) => renderFieldPreview(field))}
+                </div>
+              </AntdForm>
+            );
+          })()}
+        </div>
       </Modal>
     </div>
   );

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است