任务模块

This commit is contained in:
‘Liammcl’
2024-12-28 19:23:54 +08:00
parent b7802abb2d
commit 4c9601b949
9 changed files with 1109 additions and 181 deletions

View File

@@ -1,18 +1,217 @@
import React from 'react';
import { Form, Card } from 'antd';
import React, { useState, useEffect } from 'react';
import { Form, Card, Input, Select, message,Button } from 'antd';
import { supabaseService } from '@/hooks/supabaseService';
import {ArrowLeftOutlined}from '@ant-design/icons'
import TaskList from '@/components/TaskList';
const TYPE = 'task'
const TaskTemplate = ({ id, isView, onCancel,isEdit }) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [formValues, setFormValues] = useState({
sections: [{ items: [{}] }],
});
const [categories, setCategories] = useState([]);
useEffect(() => {
if (id) {
fetchServiceTemplate();
}
fetchCategories();
}, [id]);
const fetchServiceTemplate = async () => {
try {
setLoading(true);
const { data } = await supabaseService.select('resources', {
filter: {
id: { eq: id },
type: { eq: 'serviceTemplate' },
'attributes->>template_type': { eq: TYPE }
}
});
if (data?.[0]) {
const formData = {
templateName: data[0].attributes.templateName,
description: data[0].attributes.description,
category: data[0].attributes.category?.map(v => v.id) || [],
sections: data[0].attributes.sections || [{ items: [{}] }],
currency: data[0].attributes.currency || "CNY",
};
form.setFieldsValue(formData);
setFormValues(formData);
}
} catch (error) {
console.error("获取服务模版失败:", error);
message.error("获取服务模版失败");
} finally {
setLoading(false);
}
};
const fetchCategories = async () => {
try {
const { data } = await supabaseService.select('resources', {
filter: {
type: { eq: 'categories' },
'attributes->>template_type': { in: `(${TYPE},common)` }
},
order: {
column: 'created_at',
ascending: false
}
});
const formattedCategories = (data || []).map(category => ({
value: category.id,
label: category.attributes.name
}));
setCategories(formattedCategories);
} catch (error) {
message.error('获取分类数据失败');
console.error(error);
}
};
const handleValuesChange = (changedValues, allValues) => {
setFormValues(allValues);
};
const onFinish = async (values) => {
try {
setLoading(true);
const categoryData = values.category.map(categoryId => {
const category = categories.find(c => c.value === categoryId);
return {
id: categoryId,
name: category.label
};
});
const serviceData = {
type: "serviceTemplate",
attributes: {
template_type: TYPE,
templateName: values.templateName,
description: values.description,
sections: values.sections,
category: categoryData,
},
};
if (id) {
await supabaseService.update('resources',
{ id },
serviceData
);
} else {
// 新增
await supabaseService.insert('resources', serviceData);
}
message.success("保存成功");
onCancel();
} catch (error) {
console.error("保存失败:", error);
message.error("保存失败");
} finally {
setLoading(false);
}
};
const TaskTemplate = ({ form, id, isView }) => {
return (
<Form
form={form}
layout="vertical"
disabled={isView}
>
{/* 任务模板特有的字段和组件 */}
<Card>
{/* 任务步骤、检查项等内容 */}
</Card>
</Form>
<>
<Form
form={form}
onFinish={onFinish}
onValuesChange={handleValuesChange}
layout="vertical"
variant="filled"
disabled={isView}
>
<Card
className="shadow-sm rounded-lg mb-6"
type="inner"
title={
<span className="flex items-center space-x-2 text-gray-700">
<span className="w-1 h-4 bg-blue-500 rounded-full" />
<span>基本信息</span>
</span>
}
bordered={false}
>
<div className="grid grid-cols-2 gap-6">
<Form.Item
label="模版名称"
name="templateName"
rules={[{ required: true, message: "请输入模版名称" }]}
>
<Input placeholder="请输入模版名称" />
</Form.Item>
<Form.Item
label="模版分类"
name="category"
rules={[{ required: true, message: "请选择或输入分类" }]}
>
<Select
placeholder="请选择或输入分类"
showSearch
allowClear
mode="tags"
options={categories}
loading={loading}
/>
</Form.Item>
<Form.Item
label="模版描述"
name="description"
className="col-span-2"
>
<Input.TextArea rows={4} placeholder="请输入模版描述" />
</Form.Item>
</div>
</Card>
<Card
className="shadow-sm rounded-lg"
type="inner"
title={
<span className="flex items-center space-x-2 text-gray-700">
<span className="w-1 h-4 bg-blue-500 rounded-full" />
<span>服务明细</span>
</span>
}
bordered={false}
>
<TaskList
type={TYPE}
form={form}
isView={isView}
formValues={formValues}
onValuesChange={handleValuesChange}
/>
</Card>
</Form>
<div className="flex justify-end pt-4 space-x-4">
<Button
icon={<ArrowLeftOutlined />}
onClick={onCancel}
>
返回
</Button>
{!isView && (
<Button
type="primary"
onClick={() => form.submit()}
loading={loading}
>
保存
</Button>
)}
</div>
</>
);
};

View File

@@ -224,8 +224,295 @@ const ServicePage = () => {
}
};
// 子表格列定义
const table_warp=(record,section)=>{
switch(record.attributes.template_type){
case 'quotation':
return [
{
title: "名称",
dataIndex: "name",
key: "name",
width: "15%",
render: (text, item, index) => {
const isEditing = editingKey === `${record.id}-${section.key}-${index}`;
return isEditing ? (
<Input
defaultValue={text}
onChange={(e) => {
setEditingItem((prev) => ({
...prev || item,
name: e.target.value
}));
}}
/>
) : (
<span>{text}</span>
);
},
},
{
title: "状态",
dataIndex: "unit",
key: "unit",
width: "10%",
render: (text, item, index) => {
const isEditing = editingKey === `${record.id}-${section.key}-${index}`;
return isEditing ? (
<Input
defaultValue={text}
onChange={(e) => {
setEditingItem((prev) => ({
...prev || item,
unit: e.target.value
}));
}}
/>
) : (
<span>{text}</span>
);
},
},
{
title: "单价",
dataIndex: "price",
key: "price",
width: "10%",
render: (text, item, index) => {
const isEditing = editingKey === `${record.id}-${section.key}-${index}`;
return isEditing ? (
<InputNumber
defaultValue={text}
onChange={(value) => {
setEditingItem((prev) => ({
...prev || item,
price: value
}));
}}
/>
) : (
<span>{text}</span>
);
},
},
{
title: "数量",
dataIndex: "quantity",
key: "quantity",
width: "10%",
render: (text, item, index) => {
const isEditing = editingKey === `${record.id}-${section.key}-${index}`;
return isEditing ? (
<InputNumber
defaultValue={text}
onChange={(value) => {
setEditingItem((prev) => ({
...prev || item,
quantity: value
}));
}}
/>
) : (
<span>{text}</span>
);
},
},
{
title: "描述",
dataIndex: "description",
key: "description",
render: (text, item, index) => {
const isEditing = editingKey === `${record.id}-${section.key}-${index}`;
return isEditing ? (
<Input
defaultValue={text}
onChange={(e) => {
setEditingItem((prev) => ({
...prev || item,
description: e.target.value
}));
}}
/>
) : (
<span>{text}</span>
);
},
},
{
title: "操作",
key: "action",
width: 150,
render: (_, item, index) => {
const isEditing = editingKey === `${record.id}-${section.key}-${index}`;
return (
<Space>
{isEditing ? (
<>
<SaveOutlined
className="text-green-600 cursor-pointer"
onClick={() => handleSave(record, section.key, index)}
/>
<CloseOutlined
className="text-gray-600 cursor-pointer"
onClick={() => {
setEditingKey("");
setEditingItem(null);
}}
/>
</>
) : (
<>
<EditOutlined
className="text-blue-600 cursor-pointer"
onClick={() => {
setEditingKey(`${record.id}-${section.key}-${index}`);
setEditingItem(item); // 初始化编辑项
}}
/>
<Popconfirm
title="确定要删除这一项吗?"
onConfirm={() => handleDeleteItem(record, section.key, index)}
>
<DeleteOutlined className="text-red-500 cursor-pointer" />
</Popconfirm>
</>
)}
</Space>
);
},
},
]
case 'task':
return [
{
title: "名称",
dataIndex: "name",
key: "name",
width: "15%",
render: (text, item, index) => {
const isEditing = editingKey === `${record.id}-${section.key}-${index}`;
return isEditing ? (
<Input
defaultValue={text}
onChange={(e) => {
setEditingItem((prev) => ({
...prev || item,
name: e.target.value
}));
}}
/>
) : (
<span>{text}</span>
);
},
},
{
title: "执行状态",
dataIndex: "unit",
key: "unit",
render: (text, item, index) => {
const isEditing = editingKey === `${record.id}-${section.key}-${index}`;
return isEditing ? (
<Input
defaultValue={text}
onChange={(e) => {
setEditingItem((prev) => ({
...prev || item,
unit: e.target.value
}));
}}
/>
) : (
<span>{text}</span>
);
},
},
{
title: "开始时间",
dataIndex: "timeRange",
key: "startTime",
render: (timeRange) => timeRange?.[0] || '-',
},
{
title: "结束时间",
dataIndex: "timeRange",
key: "endTime",
render: (timeRange) => timeRange?.[1] || '-',
},
{
title: "描述",
dataIndex: "description",
key: "description",
render: (text, item, index) => {
const isEditing = editingKey === `${record.id}-${section.key}-${index}`;
return isEditing ? (
<Input
defaultValue={text}
onChange={(e) => {
setEditingItem((prev) => ({
...prev || item,
description: e.target.value
}));
}}
/>
) : (
<span>{text}</span>
);
},
},
{
title: "操作",
key: "action",
render: (_, item, index) => {
const isEditing = editingKey === `${record.id}-${section.key}-${index}`;
return (
<Space>
{isEditing ? (
<>
<SaveOutlined
className="text-green-600 cursor-pointer"
onClick={() => handleSave(record, section.key, index)}
/>
<CloseOutlined
className="text-gray-600 cursor-pointer"
onClick={() => {
setEditingKey("");
setEditingItem(null);
}}
/>
</>
) : (
<>
{/* <EditOutlined
className="text-blue-600 cursor-pointer"
onClick={() => {
setEditingKey(`${record.id}-${section.key}-${index}`);
setEditingItem(item); // 初始化编辑项
}}
/> */}
<Popconfirm
title="确定要删除这一项吗?"
onConfirm={() => handleDeleteItem(record, section.key, index)}
>
<DeleteOutlined className="text-red-500 cursor-pointer" />
</Popconfirm>
</>
)}
</Space>
);
},
},
]
case "project":
default:[]
}
}
const expandedRowRender = (record) => {
return (
<div className="bg-gray-50 dark:bg-gray-600 p-4 rounded-lg">
{record.attributes.sections.map((section) => (
@@ -240,164 +527,12 @@ const ServicePage = () => {
</Popconfirm>
</div>
<Table
scroll={{x:true}}
dataSource={section.items}
pagination={false}
size="small"
className="nested-items-table border-t border-gray-100"
columns={[
{
title: "名称",
dataIndex: "name",
key: "name",
width: "15%",
render: (text, item, index) => {
const isEditing = editingKey === `${record.id}-${section.key}-${index}`;
return isEditing ? (
<Input
defaultValue={text}
onChange={(e) => {
setEditingItem((prev) => ({
...prev || item,
name: e.target.value
}));
}}
/>
) : (
<span>{text}</span>
);
},
},
{
title: "单位",
dataIndex: "unit",
key: "unit",
width: "10%",
render: (text, item, index) => {
const isEditing = editingKey === `${record.id}-${section.key}-${index}`;
return isEditing ? (
<Input
defaultValue={text}
onChange={(e) => {
setEditingItem((prev) => ({
...prev || item,
unit: e.target.value
}));
}}
/>
) : (
<span>{text}</span>
);
},
},
{
title: "单价",
dataIndex: "price",
key: "price",
width: "10%",
render: (text, item, index) => {
const isEditing = editingKey === `${record.id}-${section.key}-${index}`;
return isEditing ? (
<InputNumber
defaultValue={text}
onChange={(value) => {
setEditingItem((prev) => ({
...prev || item,
price: value
}));
}}
/>
) : (
<span>{text}</span>
);
},
},
{
title: "数量",
dataIndex: "quantity",
key: "quantity",
width: "10%",
render: (text, item, index) => {
const isEditing = editingKey === `${record.id}-${section.key}-${index}`;
return isEditing ? (
<InputNumber
defaultValue={text}
onChange={(value) => {
setEditingItem((prev) => ({
...prev || item,
quantity: value
}));
}}
/>
) : (
<span>{text}</span>
);
},
},
{
title: "描述",
dataIndex: "description",
key: "description",
render: (text, item, index) => {
const isEditing = editingKey === `${record.id}-${section.key}-${index}`;
return isEditing ? (
<Input
defaultValue={text}
onChange={(e) => {
setEditingItem((prev) => ({
...prev || item,
description: e.target.value
}));
}}
/>
) : (
<span>{text}</span>
);
},
},
{
title: "操作",
key: "action",
width: 150,
render: (_, item, index) => {
const isEditing = editingKey === `${record.id}-${section.key}-${index}`;
return (
<Space>
{isEditing ? (
<>
<SaveOutlined
className="text-green-600 cursor-pointer"
onClick={() => handleSave(record, section.key, index)}
/>
<CloseOutlined
className="text-gray-600 cursor-pointer"
onClick={() => {
setEditingKey("");
setEditingItem(null);
}}
/>
</>
) : (
<>
<EditOutlined
className="text-blue-600 cursor-pointer"
onClick={() => {
setEditingKey(`${record.id}-${section.key}-${index}`);
setEditingItem(item); // 初始化编辑项
}}
/>
<Popconfirm
title="确定要删除这一项吗?"
onConfirm={() => handleDeleteItem(record, section.key, index)}
>
<DeleteOutlined className="text-red-500 cursor-pointer" />
</Popconfirm>
</>
)}
</Space>
);
},
},
]}
columns={table_warp(record,section)}
/>
</div>
))}
@@ -411,7 +546,7 @@ const ServicePage = () => {
const path = id ? `${basePath}/${id}` : basePath;
if (mode === "create") {
navigate(`${basePath}`);
navigate(`${basePath}?type=${type}`);
} else {
navigate(`${basePath}/${id}?type=${type}&${mode}=true`);
}
@@ -589,7 +724,6 @@ const ServicePage = () => {
className="rounded-lg"
/>
{/* 添加模板类型选择弹窗 */}
<TemplateTypeModal
open={isModalOpen}
onClose={() => setIsModalOpen(false)}