服务模版

This commit is contained in:
‘Liammcl’
2024-12-21 15:31:52 +08:00
parent aa5918aacf
commit 3812c9b7e9
7 changed files with 1678 additions and 26 deletions

View File

@@ -1,43 +1,375 @@
import React from 'react';
import { Card, Table, Button } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import { Card, Table, Button, Space, Input, message, Popconfirm } from "antd";
import { EyeOutlined } from "@ant-design/icons";
import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { supabase } from "@/config/supabase";
const ServicePage = () => {
const [loading, setLoading] = useState(false);
const [data, setData] = useState([]);
const navigate = useNavigate();
const [editingKey, setEditingKey] = useState('');
// 获取服务模板列表
const fetchServices = async () => {
try {
setLoading(true);
const { data: services, error } = await supabase
.from("resources")
.select("*")
.eq("type", "serviceTemplate")
.order("created_at", { ascending: false });
if (error) throw error;
setData(services);
} catch (error) {
console.error("获取服务模板失败:", error);
message.error("获取服务模板失败");
} finally {
setLoading(false);
}
};
// 添加保存编辑项目的方法
const handleSaveItem = async (record, sectionIndex, itemIndex) => {
try {
setLoading(true);
const newData = [...data];
const targetService = newData.find(item => item.id === record.serviceId);
targetService.attributes.sections[sectionIndex].items[itemIndex] = record;
// 计算新的总金额
const newTotalAmount = targetService.attributes.sections.reduce((total, section) => {
return total + section.items.reduce((sectionTotal, item) =>
sectionTotal + (item.price * item.quantity), 0);
}, 0);
targetService.attributes.totalAmount = newTotalAmount;
const { error } = await supabase
.from('resources')
.update({ attributes: targetService.attributes })
.eq('id', targetService.id);
if (error) throw error;
setData(newData);
setEditingKey('');
message.success('更新成功');
} catch (error) {
console.error('保存失败:', error);
message.error('保存失败');
} finally {
setLoading(false);
}
};
// 添加删除项目的方法
const handleDeleteItem = async (record) => {
try {
setLoading(true);
const newData = [...data];
const targetService = newData.find(item => item.id === record.serviceId);
// 删除指定项目
targetService.attributes.sections[record.sectionIndex].items.splice(record.itemIndex, 1);
// 重新计算总金额
const newTotalAmount = targetService.attributes.sections.reduce((total, section) => {
return total + section.items.reduce((sectionTotal, item) =>
sectionTotal + (item.price * item.quantity), 0);
}, 0);
targetService.attributes.totalAmount = newTotalAmount;
const { error } = await supabase
.from('resources')
.update({ attributes: targetService.attributes })
.eq('id', targetService.id);
if (error) throw error;
setData(newData);
message.success('删除成功');
} catch (error) {
console.error('删除失败:', error);
message.error('删除失败');
} finally {
setLoading(false);
}
};
// 主表格列定义
const columns = [
{
title: '服务名称',
dataIndex: 'name',
key: 'name',
title: "模板名称",
dataIndex: ["attributes", "templateName"],
key: "templateName",
className: "min-w-[200px]",
},
{
title: '服务类型',
dataIndex: 'type',
key: 'type',
title: "描述",
dataIndex: ["attributes", "description"],
key: "description",
},
{
title: '价格',
dataIndex: 'price',
key: 'price',
title: "总金额",
dataIndex: ["attributes", "totalAmount"],
key: "totalAmount",
className: "min-w-[150px] text-right",
render: (amount) => (
<span className="text-blue-600 font-medium">
¥
{amount?.toLocaleString("zh-CN", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}) || "0.00"}
</span>
),
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
title: "操作",
key: "action",
render: (_, record) => (
<Space>
<Button
type="link"
icon={<EyeOutlined />}
onClick={() =>
navigate(`/company/serviceTemplateInfo/${record.id}`)
}
>
查看
</Button>
</Space>
),
},
];
return (
<Card
title="服务管理"
extra={
<Button type="primary" icon={<PlusOutlined />}>
新增服务
</Button>
// 子表格列定义
const itemColumns = [
{
title: "项目名称",
dataIndex: "name",
key: "name",
width: "40%",
render: (text, record) => {
const isEditing = record.key === editingKey;
return isEditing ? (
<Input
value={text}
onChange={e => {
const newData = [...data];
const targetService = newData.find(item => item.id === record.serviceId);
const targetItem = targetService.attributes.sections[record.sectionIndex].items[record.itemIndex];
targetItem.name = e.target.value;
setData(newData);
}}
/>
) : text;
}
>
<Table columns={columns} dataSource={[]} rowKey="id" />
</Card>
},
{
title: "单价",
dataIndex: "price",
key: "price",
width: "20%",
render: (price, record) => {
const isEditing = record.key === editingKey;
return isEditing ? (
<Input
type="number"
value={price}
onChange={e => {
const newData = [...data];
const targetService = newData.find(item => item.id === record.serviceId);
const targetItem = targetService.attributes.sections[record.sectionIndex].items[record.itemIndex];
targetItem.price = Number(e.target.value);
setData(newData);
}}
/>
) : (
<span className="text-gray-600">
¥{price?.toLocaleString("zh-CN", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})}
</span>
);
}
},
{
title: "数量",
dataIndex: "quantity",
key: "quantity",
width: "20%",
render: (quantity, record) => {
const isEditing = record.key === editingKey;
return isEditing ? (
<Input
type="number"
value={quantity}
onChange={e => {
const newData = [...data];
const targetService = newData.find(item => item.id === record.serviceId);
const targetItem = targetService.attributes.sections[record.sectionIndex].items[record.itemIndex];
targetItem.quantity = Number(e.target.value);
setData(newData);
}}
/>
) : <span className="text-gray-600">{quantity}</span>;
}
},
{
title: "小计",
key: "subtotal",
width: "20%",
render: (_, record) => (
<span className="text-blue-600 font-medium">
¥
{((record.price || 0) * (record.quantity || 0)).toLocaleString(
"zh-CN",
{
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}
)}
</span>
),
},
{
title: '操作',
key: 'action',
width: "150px",
render: (_, record) => {
const isEditing = record.key === editingKey;
return isEditing ? (
<Space>
<Button
type="link"
size="small"
className="text-green-600"
onClick={() => handleSaveItem(record, record.sectionIndex, record.itemIndex)}
>
保存
</Button>
<Button
type="link"
size="small"
className="text-gray-600"
onClick={() => setEditingKey('')}
>
取消
</Button>
</Space>
) : (
<Space>
<Button
type="link"
size="small"
className="text-blue-600"
disabled={editingKey !== ''}
onClick={() => setEditingKey(record.key)}
>
编辑
</Button>
<Popconfirm
title="删除确认"
description="确定要删除这个项目吗?"
okText="确定"
cancelText="取消"
okButtonProps={{ danger: true }}
onConfirm={() => handleDeleteItem(record)}
>
<Button
type="link"
size="small"
className="text-red-600"
disabled={editingKey !== ''}
>
删除
</Button>
</Popconfirm>
</Space>
);
}
}
];
// 修改 expandedRowRender 以支持编辑
const expandedRowRender = (record) => (
<div className="bg-gray-50 p-4 rounded-lg">
{record.attributes.sections?.map((section, sectionIndex) => (
<Card
key={sectionIndex}
className="mb-4 shadow-sm"
title={
<div className="flex items-center justify-between">
<span className="text-lg font-medium">{section.sectionName}</span>
<span className="text-blue-600 font-medium">
总计: ¥
{section.items
.reduce(
(total, item) => total + item.price * item.quantity,
0
)
.toLocaleString("zh-CN", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})}
</span>
</div>
}
>
<Table
columns={itemColumns}
dataSource={section.items.map((item, itemIndex) => ({
...item,
key: `${sectionIndex}-${itemIndex}`,
serviceId: record.id,
sectionIndex: sectionIndex,
itemIndex: itemIndex
}))}
pagination={false}
className="rounded-lg overflow-hidden"
/>
</Card>
))}
</div>
);
useEffect(() => {
fetchServices();
}, []);
return (
<div className="min-h-screen bg-gray-100 ">
<Card
title={<span className="text-xl font-medium">服务模版管理</span>}
className="shadow-lg rounded-lg"
extra={
<Button
type="primary"
onClick={() => navigate("/company/serviceTemplateInfo")}
>
新建模版
</Button>
}
>
<Table
columns={columns}
dataSource={data}
loading={loading}
rowKey="id"
expandable={{
expandedRowRender,
}}
className="rounded-lg overflow-hidden"
rowClassName="hover:bg-gray-50 transition-colors"
/>
</Card>
</div>
);
};
export default ServicePage;
export default ServicePage;