This commit is contained in:
xuqssq
2024-12-23 01:15:54 +08:00
parent df0aa520ca
commit feefbd7a5c
8 changed files with 592 additions and 120 deletions

View File

@@ -0,0 +1,277 @@
import React, { useState, useEffect } from "react";
import {
Form,
Input,
Button,
Space,
Card,
Table,
Typography,
message,
} from "antd";
import {
PlusOutlined,
ArrowLeftOutlined,
SaveOutlined,
} from "@ant-design/icons";
import { supabase } from "@/config/supabase";
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
import { v4 as uuidv4 } from "uuid";
const { Title } = Typography;
const ResourceTaskForm = () => {
const { id } = useParams();
const [searchParams] = useSearchParams();
const isEdit = searchParams.get("edit") === "true";
const isView = id && !isEdit;
const [form] = Form.useForm();
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const [dataSource, setDataSource] = useState([{ id: uuidv4(), name: '' }]);
const columns = [
{
title: "名称",
dataIndex: "name",
render: (_, record, index) => (
<Form.Item
name={["items", index, "name"]}
rules={[{ required: true, message: "请输入名称" }]}
style={{ margin: 0 }}
preserve={true}
>
<Input placeholder="请输入名称" maxLength={100} />
</Form.Item>
),
},
{
title: "操作",
width: 100,
render: (_, record, index) => (
<Button
type="link"
danger
disabled={dataSource.length === 1 || isView}
onClick={() => handleRemoveItem(record.id, index)}
>
删除
</Button>
),
},
];
const handleRemoveItem = (itemId, index) => {
// 获取当前所有表单项的值
const allFormValues = form.getFieldsValue();
const currentItems = allFormValues.items || [];
// 保留要删除项之外的所有项
const newItems = currentItems.filter((_, idx) => idx !== index);
// 更新 dataSource确保保留其他项的数据
const newDataSource = dataSource.filter((_, idx) => idx !== index);
// 同步更新 form 和 dataSource
setDataSource(newDataSource);
form.setFieldsValue({ items: newItems });
};
const handleAddItem = () => {
const newItem = { id: uuidv4(), name: '' };
// 获取当前表单值
const currentItems = form.getFieldValue('items') || [];
// 同步更新表单和 dataSource
setDataSource(prev => [...prev, newItem]);
form.setFieldsValue({
items: [...currentItems, newItem]
});
};
const fetchResourceTaskDetail = async () => {
try {
setLoading(true);
const { data, error } = await supabase
.from("resources")
.select("*")
.eq("id", id)
.single();
if (error) throw error;
if (data?.attributes) {
const items = data.attributes.items?.map(item => ({
...item,
id: item.id || uuidv4(),
name: item.name || ''
})) || [{ id: uuidv4(), name: '' }];
// 同步设置表单值和 dataSource
form.setFieldsValue({
taskName: data.attributes.taskName,
items,
});
setDataSource(items);
}
} catch (error) {
message.error("获取任务详情失败,请稍后重试");
console.error("获取任务详情失败:", error);
} finally {
setLoading(false);
}
};
useEffect(() => {
if (id) {
fetchResourceTaskDetail();
}
}, [id]);
const onFinish = async (values) => {
try {
setLoading(true);
// 验证任务项
const hasEmptyItems = values.items.some((item) => !item.name?.trim());
if (hasEmptyItems) {
message.error("请填写所有任务项的名称");
return;
}
const resourceTaskData = {
type: "task",
attributes: {
taskName: values.taskName.trim(),
items: values.items.map((item, index) => ({
id: dataSource[index].id,
name: item.name?.trim(),
})),
},
};
if (id) {
resourceTaskData.id = id;
}
const { error } = await supabase
.from("resources")
.upsert(resourceTaskData)
.select();
if (error) throw error;
message.success("保存成功");
navigate("/resource/task");
} catch (error) {
message.error("保存失败,请稍后重试");
console.error("保存失败:", error);
} finally {
setLoading(false);
}
};
return (
<div className="bg-gradient-to-b from-gray-50 to-white min-h-screen p-6">
<Card
className="shadow-lg rounded-lg border-0"
title={
<div className="flex justify-between items-center py-2">
<div className="flex items-center space-x-3">
<Title level={4} className="mb-0 text-gray-800">
{id ? (isEdit ? "编辑任务" : "查看任务") : "新建任务"}
</Title>
<span className="text-gray-400 text-sm">
{id
? isEdit
? "请修改任务信息"
: "任务详情"
: "请填写任务信息"}
</span>
</div>
<Space size="middle">
<Button
icon={<ArrowLeftOutlined />}
onClick={() => navigate("/resource/task")}
>
返回
</Button>
{!isView && (
<Button
type="primary"
icon={<SaveOutlined />}
onClick={() => form.submit()}
loading={loading}
>
保存
</Button>
)}
</Space>
</div>
}
>
<Form
form={form}
onFinish={onFinish}
layout="vertical"
initialValues={{
items: [{ id: uuidv4() }],
}}
disabled={isView || loading}
>
<Card
className="mb-4 shadow-sm rounded-lg"
title={
<div className="flex items-center">
<div className="w-1 h-4 bg-blue-500 rounded-full mr-2" />
<span>基本信息</span>
</div>
}
bordered={false}
>
<Form.Item
name="taskName"
label="任务名称"
rules={[{ required: true, message: "请输入任务名称" }]}
>
<Input placeholder="请输入任务名称" maxLength={100} />
</Form.Item>
</Card>
<Card
className="shadow-sm rounded-lg"
title={
<div className="flex items-center">
<div className="w-1 h-4 bg-green-500 rounded-full mr-2" />
<span>任务明细</span>
</div>
}
extra={
!isView && (
<Button
type="dashed"
icon={<PlusOutlined />}
onClick={handleAddItem}
disabled={loading}
>
新增一栏
</Button>
)
}
bordered={false}
>
<Table
dataSource={dataSource}
columns={columns}
pagination={false}
rowKey="id"
loading={loading}
/>
</Card>
</Form>
</Card>
</div>
);
};
export default ResourceTaskForm;

View File

@@ -0,0 +1,166 @@
import React, { useEffect, useState } from "react";
import {
Card,
Table,
Button,
message,
Popconfirm,
Tag,
Space,
Tooltip,
} from "antd";
import {
PlusOutlined,
EditOutlined,
DeleteOutlined,
EyeOutlined,
CopyOutlined,
} from "@ant-design/icons";
import { useResources } from "@/hooks/resource/useResource";
import { useNavigate } from "react-router-dom";
const ResourceTask = () => {
const navigate = useNavigate();
const [pagination, setPagination] = useState({ current: 1, pageSize: 10 });
const [sorter, setSorter] = useState({
field: "created_at",
order: "descend",
});
const {
resources: quotations,
loading,
total,
fetchResources: fetchQuotations,
deleteResource: deleteQuotation,
} = useResources(pagination, sorter, "task");
useEffect(() => {
fetchQuotations();
}, []);
const handleTableChange = (pagination, filters, sorter) => {
setPagination(pagination);
setSorter(sorter);
fetchQuotations({
current: pagination.current,
pageSize: pagination.pageSize,
field: sorter.field,
order: sorter.order,
});
};
const handleDelete = async (id) => {
try {
await deleteQuotation(id);
message.success("删除成功");
fetchQuotations();
} catch (error) {
message.error("删除失败:" + error.message);
}
};
const columns = [
{
title: "任务名称",
dataIndex: ["attributes", "taskName"],
key: "taskName",
ellipsis: true,
},
{
title: "创建日期",
dataIndex: "created_at",
key: "created_at",
sorter: true,
render: (text) => (
<span>
{new Date(text).toLocaleString("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
})}
</span>
),
},
{
title: "操作",
width: 250,
key: "action",
render: (_, record) => (
<Space size={0}>
<Button
size="small"
type="link"
icon={<EyeOutlined />}
onClick={() => navigate(`/resource/task/edit/${record.id}`)}
>
查看
</Button>
<Button
size="small"
type="link"
icon={<EditOutlined />}
onClick={() =>
navigate(`/resource/task/edit/${record.id}?edit=true`)
}
>
编辑
</Button>
<Popconfirm
title="确定要删除这个任务吗?"
description="删除后将无法恢复!"
onConfirm={() => handleDelete(record.id)}
okText="确定"
cancelText="取消"
okButtonProps={{ danger: true }}
>
<Button size="small" type="link" danger icon={<DeleteOutlined />}>
删除
</Button>
</Popconfirm>
</Space>
),
},
];
return (
<Card
title={
<Space>
<span>任务管理</span>
<Tag color="blue">{total} 个任务</Tag>
</Space>
}
className="h-full w-full overflow-auto"
extra={
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => navigate("/resource/task/edit")}
>
新增任务
</Button>
}
>
<Table
columns={columns}
dataSource={quotations}
rowKey="id"
loading={loading}
onChange={handleTableChange}
pagination={{
...pagination,
total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total} 条记录`,
}}
/>
</Card>
);
};
export default ResourceTask;