服务模版
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user