更换schema,服务管理50%
This commit is contained in:
@@ -21,7 +21,7 @@ export const createSupabase = () => {
|
|||||||
detectSessionInUrl: false,
|
detectSessionInUrl: false,
|
||||||
},
|
},
|
||||||
db: {
|
db: {
|
||||||
schema: 'limq'
|
schema: 'limq_dev'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ const Login = () => {
|
|||||||
const handleGoogleLogin = async () => {
|
const handleGoogleLogin = async () => {
|
||||||
try {
|
try {
|
||||||
await signInWithGoogle();
|
await signInWithGoogle();
|
||||||
navigate("/");
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Google login error:", error);
|
console.error("Google login error:", error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,9 +30,10 @@ const ServiceForm = () => {
|
|||||||
const isEdit = location.search.includes("edit=true");
|
const isEdit = location.search.includes("edit=true");
|
||||||
const [editingSectionIndex, setEditingSectionIndex] = useState(null);
|
const [editingSectionIndex, setEditingSectionIndex] = useState(null);
|
||||||
const [availableSections, setAvailableSections] = useState([]);
|
const [availableSections, setAvailableSections] = useState([]);
|
||||||
const [formValues, setFormValues] = useState({});
|
const [formValues, setFormValues] = useState({
|
||||||
const [sectionNameForm] = Form.useForm();
|
sections: [{ items: [{}] }],
|
||||||
|
currency: "CNY"
|
||||||
|
});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (id) {
|
if (id) {
|
||||||
fetchServiceTemplate();
|
fetchServiceTemplate();
|
||||||
@@ -50,11 +51,13 @@ const ServiceForm = () => {
|
|||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
form.setFieldsValue({
|
const formData = {
|
||||||
templateName: data.attributes.templateName,
|
templateName: data.attributes.templateName,
|
||||||
description: data.attributes.description,
|
description: data.attributes.description,
|
||||||
sections: data.attributes.sections,
|
sections: data.attributes.sections,
|
||||||
});
|
};
|
||||||
|
form.setFieldsValue(formData);
|
||||||
|
setFormValues(formData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取服务模版失败:", error);
|
console.error("获取服务模版失败:", error);
|
||||||
message.error("获取服务模版失败");
|
message.error("获取服务模版失败");
|
||||||
@@ -96,6 +99,7 @@ const ServiceForm = () => {
|
|||||||
templateName: values.templateName,
|
templateName: values.templateName,
|
||||||
description: values.description,
|
description: values.description,
|
||||||
sections: values.sections,
|
sections: values.sections,
|
||||||
|
category: values.category || [], // 添加 category 字段
|
||||||
totalAmount,
|
totalAmount,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -279,7 +283,7 @@ const ServiceForm = () => {
|
|||||||
disabled={id && !isEdit}
|
disabled={id && !isEdit}
|
||||||
onValuesChange={handleValuesChange}
|
onValuesChange={handleValuesChange}
|
||||||
>
|
>
|
||||||
<div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 mb-6">
|
<div className="bg-white dark:bg-gray-800 p-2 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 mb-6">
|
||||||
<Title level={5} className="mb-4 dark:text-gray-200">
|
<Title level={5} className="mb-4 dark:text-gray-200">
|
||||||
基本信息
|
基本信息
|
||||||
</Title>
|
</Title>
|
||||||
@@ -327,23 +331,12 @@ const ServiceForm = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
|
<div className="bg-white dark:bg-gray-800 p-2 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<Title level={5} className="!mb-0 dark:text-gray-200">
|
<Title level={5} className="!mb-0 dark:text-gray-200">
|
||||||
报价明细
|
报价明细
|
||||||
</Title>
|
</Title>
|
||||||
{(!id || isEdit) && (
|
|
||||||
<Select
|
|
||||||
style={{ width: 200 }}
|
|
||||||
placeholder="选择已有小节"
|
|
||||||
onChange={handleAddExistingSection}
|
|
||||||
options={availableSections.map((section) => ({
|
|
||||||
value: section.id,
|
|
||||||
label: section.attributes.sectionName,
|
|
||||||
}))}
|
|
||||||
className="w-48"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Form.List name="sections">
|
<Form.List name="sections">
|
||||||
@@ -448,12 +441,7 @@ const ServiceForm = () => {
|
|||||||
{...itemField}
|
{...itemField}
|
||||||
name={[itemField.name, "name"]}
|
name={[itemField.name, "name"]}
|
||||||
className="!mb-0"
|
className="!mb-0"
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: "请输入服务项目名称",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<Input placeholder="服务项目名称" />
|
<Input placeholder="服务项目名称" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { Card, Table, Button, Space, Input, message, Popconfirm } from "antd";
|
import { Card, Table, Button, Space, Input, message, Popconfirm, Form, Tag, Typography } from "antd";
|
||||||
import { EyeOutlined } from "@ant-design/icons";
|
import { DownOutlined, UpOutlined,EyeOutlined,EditOutlined } from "@ant-design/icons";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { supabase } from "@/config/supabase";
|
import { supabase } from "@/config/supabase";
|
||||||
|
|
||||||
|
const { Paragraph } = Typography;
|
||||||
|
|
||||||
const ServicePage = () => {
|
const ServicePage = () => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [data, setData] = useState([]);
|
const [data, setData] = useState([]);
|
||||||
@@ -30,32 +32,72 @@ const ServicePage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加保存编辑项目的方法
|
// 修改保存逻辑
|
||||||
const handleSaveItem = async (record, sectionIndex, itemIndex) => {
|
const handleSaveItem = async (record) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const newData = [...data];
|
const { serviceId, sectionIndex, itemIndex } = record;
|
||||||
const targetService = newData.find(item => item.id === record.serviceId);
|
|
||||||
targetService.attributes.sections[sectionIndex].items[itemIndex] = record;
|
|
||||||
|
|
||||||
// 计算新的总金额
|
// 找到当前服务模板
|
||||||
const newTotalAmount = targetService.attributes.sections.reduce((total, section) => {
|
const currentService = data.find(item => item.id === serviceId);
|
||||||
return total + section.items.reduce((sectionTotal, item) =>
|
if (!currentService) {
|
||||||
sectionTotal + (item.price * item.quantity), 0);
|
throw new Error('Service not found');
|
||||||
}, 0);
|
}
|
||||||
|
|
||||||
targetService.attributes.totalAmount = newTotalAmount;
|
|
||||||
|
|
||||||
|
// 创建新的 attributes 对象,避免直接修改状态
|
||||||
|
const newAttributes = {
|
||||||
|
...currentService.attributes,
|
||||||
|
templateName: currentService.attributes.templateName,
|
||||||
|
description: currentService.attributes.description,
|
||||||
|
category: currentService.attributes.category || [], // 确保 category 字段存在
|
||||||
|
sections: currentService.attributes.sections.map((section, secIdx) => {
|
||||||
|
if (secIdx === sectionIndex) {
|
||||||
|
return {
|
||||||
|
...section,
|
||||||
|
items: section.items.map((item, itemIdx) => {
|
||||||
|
if (itemIdx === itemIndex) {
|
||||||
|
return {
|
||||||
|
name: record.name,
|
||||||
|
price: Number(record.price) || 0,
|
||||||
|
quantity: Number(record.quantity) || 0,
|
||||||
|
description: record.description || '',
|
||||||
|
unit: record.unit || ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return section;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重新计算总金额
|
||||||
|
newAttributes.totalAmount = newAttributes.sections.reduce((total, section) => {
|
||||||
|
return total + section.items.reduce((sectionTotal, item) =>
|
||||||
|
sectionTotal + ((Number(item.price) || 0) * (Number(item.quantity) || 0)), 0);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
// 调用 supabase 更新数据
|
||||||
const { error } = await supabase
|
const { error } = await supabase
|
||||||
.from('resources')
|
.from('resources')
|
||||||
.update({ attributes: targetService.attributes })
|
.update({
|
||||||
.eq('id', targetService.id);
|
type: "serviceTemplate",
|
||||||
|
attributes: newAttributes
|
||||||
|
})
|
||||||
|
.eq('id', serviceId);
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
setData(newData);
|
// 更新本地状态
|
||||||
|
setData(prevData => prevData.map(item =>
|
||||||
|
item.id === serviceId
|
||||||
|
? { ...item, attributes: newAttributes }
|
||||||
|
: item
|
||||||
|
));
|
||||||
|
|
||||||
setEditingKey('');
|
setEditingKey('');
|
||||||
message.success('更新成功');
|
message.success('保存成功');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('保存失败:', error);
|
console.error('保存失败:', error);
|
||||||
message.error('保存失败');
|
message.error('保存失败');
|
||||||
@@ -68,28 +110,73 @@ const ServicePage = () => {
|
|||||||
const handleDeleteItem = async (record) => {
|
const handleDeleteItem = async (record) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const newData = [...data];
|
const { serviceId, sectionIndex, itemIndex } = record;
|
||||||
const targetService = newData.find(item => item.id === record.serviceId);
|
|
||||||
|
|
||||||
// 删除指定项目
|
// 找到目标服务
|
||||||
targetService.attributes.sections[record.sectionIndex].items.splice(record.itemIndex, 1);
|
const targetService = data.find(item => item.id === serviceId);
|
||||||
|
if (!targetService) throw new Error('Service not found');
|
||||||
|
|
||||||
// 重新计算总金额
|
// 创建新的 attributes 对象,避免直接修改状态
|
||||||
const newTotalAmount = targetService.attributes.sections.reduce((total, section) => {
|
const newAttributes = {
|
||||||
return total + section.items.reduce((sectionTotal, item) =>
|
...targetService.attributes,
|
||||||
sectionTotal + (item.price * item.quantity), 0);
|
sections: targetService.attributes.sections.map((section, secIdx) => {
|
||||||
}, 0);
|
if (secIdx === sectionIndex) {
|
||||||
|
return {
|
||||||
targetService.attributes.totalAmount = newTotalAmount;
|
...section,
|
||||||
|
items: section.items.filter((_, idx) => idx !== itemIndex)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return section;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重新计算总金额
|
||||||
|
newAttributes.totalAmount = newAttributes.sections.reduce((total, section) => {
|
||||||
|
return total + section.items.reduce((sectionTotal, item) =>
|
||||||
|
sectionTotal + ((Number(item.price) || 0) * (Number(item.quantity) || 0)), 0);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
// 调用 supabase 更新数据
|
||||||
const { error } = await supabase
|
const { error } = await supabase
|
||||||
.from('resources')
|
.from('resources')
|
||||||
.update({ attributes: targetService.attributes })
|
.update({
|
||||||
.eq('id', targetService.id);
|
attributes: newAttributes
|
||||||
|
})
|
||||||
|
.eq('id', serviceId);
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
setData(newData);
|
// 更新本地状态
|
||||||
|
setData(prevData => prevData.map(item =>
|
||||||
|
item.id === serviceId
|
||||||
|
? { ...item, attributes: newAttributes }
|
||||||
|
: item
|
||||||
|
));
|
||||||
|
|
||||||
|
message.success('删除成功');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除失败:', error);
|
||||||
|
message.error('删<><E588A0><EFBFBD>失败');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加删除服务模板的方法
|
||||||
|
const handleDeleteService = async (serviceId) => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
// 调用 supabase 删除数据
|
||||||
|
const { error } = await supabase
|
||||||
|
.from('resources')
|
||||||
|
.delete()
|
||||||
|
.eq('id', serviceId);
|
||||||
|
|
||||||
|
if (error) throw error;
|
||||||
|
|
||||||
|
// 更新本地状态
|
||||||
|
setData(prevData => prevData.filter(item => item.id !== serviceId));
|
||||||
message.success('删除成功');
|
message.success('删除成功');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('删除失败:', error);
|
console.error('删除失败:', error);
|
||||||
@@ -107,10 +194,65 @@ const ServicePage = () => {
|
|||||||
key: "templateName",
|
key: "templateName",
|
||||||
className: "min-w-[200px]",
|
className: "min-w-[200px]",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "分类",
|
||||||
|
dataIndex: ["attributes", "category"],
|
||||||
|
key: "category",
|
||||||
|
render: (category) => {
|
||||||
|
if (!category || !Array.isArray(category)) return null;
|
||||||
|
return (
|
||||||
|
<Paragraph
|
||||||
|
ellipsis={{
|
||||||
|
rows: 1,
|
||||||
|
tooltip: (
|
||||||
|
<Space size={[0, 8]} wrap>
|
||||||
|
{category.map((cat, index) => (
|
||||||
|
<Tag
|
||||||
|
size="small"
|
||||||
|
key={`${cat}-${index}`}
|
||||||
|
color="blue"
|
||||||
|
className="px-2 py-1 rounded-md"
|
||||||
|
>
|
||||||
|
{cat}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
className="mb-0"
|
||||||
|
>
|
||||||
|
<Space size={[0, 8]} nowrap>
|
||||||
|
{category.map((cat, index) => (
|
||||||
|
<Tag
|
||||||
|
size="small"
|
||||||
|
key={`${cat}-${index}`}
|
||||||
|
color="blue"
|
||||||
|
className="px-2 py-1 rounded-md"
|
||||||
|
>
|
||||||
|
{cat}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
</Paragraph>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "描述",
|
title: "描述",
|
||||||
dataIndex: ["attributes", "description"],
|
dataIndex: ["attributes", "description"],
|
||||||
key: "description",
|
key: "description",
|
||||||
|
width: 300,
|
||||||
|
render: (text) => (
|
||||||
|
<Paragraph
|
||||||
|
ellipsis={{
|
||||||
|
rows: 2,
|
||||||
|
tooltip: text
|
||||||
|
}}
|
||||||
|
className="mb-0"
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</Paragraph>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "总金额",
|
title: "总金额",
|
||||||
@@ -118,7 +260,7 @@ const ServicePage = () => {
|
|||||||
key: "totalAmount",
|
key: "totalAmount",
|
||||||
className: "min-w-[150px] text-right",
|
className: "min-w-[150px] text-right",
|
||||||
render: (amount) => (
|
render: (amount) => (
|
||||||
<span className="text-blue-600 font-medium">
|
<span style={{ color: '#f50', fontWeight: 'bold' }}>
|
||||||
¥
|
¥
|
||||||
{amount?.toLocaleString("zh-CN", {
|
{amount?.toLocaleString("zh-CN", {
|
||||||
minimumFractionDigits: 2,
|
minimumFractionDigits: 2,
|
||||||
@@ -130,17 +272,40 @@ const ServicePage = () => {
|
|||||||
{
|
{
|
||||||
title: "操作",
|
title: "操作",
|
||||||
key: "action",
|
key: "action",
|
||||||
|
fixed: 'right',
|
||||||
|
width: 220,
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<Space>
|
<Space onClick={e => e.stopPropagation()}>
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
icon={<EyeOutlined />}
|
icon={<EyeOutlined />}
|
||||||
onClick={() =>
|
onClick={() => navigate(`/company/serviceTemplateInfo/${record.id}`)}
|
||||||
navigate(`/company/serviceTemplateInfo/${record.id}`)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
查看
|
查看
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
icon={<EditOutlined />}
|
||||||
|
onClick={() => navigate(`/company/serviceTemplateInfo/${record.id}?edit=true`)}
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title="删除确认"
|
||||||
|
description="确定要删除这个服务模板吗?此操作不可恢复。"
|
||||||
|
okText="确定"
|
||||||
|
cancelText="取消"
|
||||||
|
okButtonProps={{ danger: true }}
|
||||||
|
onConfirm={() => handleDeleteService(record.id)}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
danger
|
||||||
|
className="text-red-600"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
</Space>
|
</Space>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -152,28 +317,71 @@ const ServicePage = () => {
|
|||||||
title: "项目名称",
|
title: "项目名称",
|
||||||
dataIndex: "name",
|
dataIndex: "name",
|
||||||
key: "name",
|
key: "name",
|
||||||
width: "40%",
|
width: "25%",
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
const isEditing = record.key === editingKey;
|
const isEditing = record.key === editingKey;
|
||||||
return isEditing ? (
|
return isEditing ? (
|
||||||
<Input
|
<Form.Item
|
||||||
value={text}
|
key={`name-${record.key}`}
|
||||||
onChange={e => {
|
name={[record.sectionIndex, "items", record.itemIndex, "name"]}
|
||||||
const newData = [...data];
|
className="!mb-0"
|
||||||
const targetService = newData.find(item => item.id === record.serviceId);
|
>
|
||||||
const targetItem = targetService.attributes.sections[record.sectionIndex].items[record.itemIndex];
|
<Input
|
||||||
targetItem.name = e.target.value;
|
value={text}
|
||||||
setData(newData);
|
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);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
) : text;
|
) : text;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "描述",
|
||||||
|
dataIndex: "description",
|
||||||
|
key: "description",
|
||||||
|
width: "25%",
|
||||||
|
render: (text, record) => {
|
||||||
|
const isEditing = record.key === editingKey;
|
||||||
|
return isEditing ? (
|
||||||
|
<Form.Item
|
||||||
|
name={[record.sectionIndex, "items", record.itemIndex, "description"]}
|
||||||
|
className="!mb-0"
|
||||||
|
>
|
||||||
|
<Input.TextArea
|
||||||
|
rows={1}
|
||||||
|
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.description = e.target.value;
|
||||||
|
setData(newData);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
) : (
|
||||||
|
<Paragraph
|
||||||
|
ellipsis={{
|
||||||
|
rows: 2,
|
||||||
|
tooltip: text
|
||||||
|
}}
|
||||||
|
className="mb-0"
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</Paragraph>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "单价",
|
title: "单价",
|
||||||
dataIndex: "price",
|
dataIndex: "price",
|
||||||
key: "price",
|
key: "price",
|
||||||
width: "20%",
|
width: "15%",
|
||||||
render: (price, record) => {
|
render: (price, record) => {
|
||||||
const isEditing = record.key === editingKey;
|
const isEditing = record.key === editingKey;
|
||||||
return isEditing ? (
|
return isEditing ? (
|
||||||
@@ -202,7 +410,7 @@ const ServicePage = () => {
|
|||||||
title: "数量",
|
title: "数量",
|
||||||
dataIndex: "quantity",
|
dataIndex: "quantity",
|
||||||
key: "quantity",
|
key: "quantity",
|
||||||
width: "20%",
|
width: "15%",
|
||||||
render: (quantity, record) => {
|
render: (quantity, record) => {
|
||||||
const isEditing = record.key === editingKey;
|
const isEditing = record.key === editingKey;
|
||||||
return isEditing ? (
|
return isEditing ? (
|
||||||
@@ -296,21 +504,24 @@ const ServicePage = () => {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// 修改 expandedRowRender 以支持编辑
|
// 修改 expandedRowRender 函数,确保 key 的唯一性
|
||||||
const expandedRowRender = (record) => (
|
const expandedRowRender = (record) => (
|
||||||
<div className="bg-gray-50 p-4 rounded-lg">
|
<div className="bg-white dark:bg-gray-800 p-4 rounded-lg">
|
||||||
{record.attributes.sections?.map((section, sectionIndex) => (
|
{record.attributes.sections?.map((section, sectionIndex) => (
|
||||||
<Card
|
<Card
|
||||||
key={sectionIndex}
|
key={`${record.id}-section-${sectionIndex}`}
|
||||||
className="mb-4 shadow-sm"
|
className="mb-4 shadow-sm border-blue-100 dark:border-blue-900/30 hover:shadow-md transition-shadow duration-300"
|
||||||
title={
|
title={
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between py-2">
|
||||||
<span className="text-lg font-medium">{section.sectionName}</span>
|
<span className="text-lg font-medium text-blue-600 dark:text-blue-400">
|
||||||
<span className="text-blue-600 font-medium">
|
{section.sectionName || `服务类型 ${sectionIndex + 1}`}
|
||||||
|
</span>
|
||||||
|
<span className="text-blue-600 dark:text-blue-400 font-medium">
|
||||||
总计: ¥
|
总计: ¥
|
||||||
{section.items
|
{section.items
|
||||||
.reduce(
|
.reduce(
|
||||||
(total, item) => total + item.price * item.quantity,
|
(total, item) =>
|
||||||
|
total + (Number(item.price) || 0) * (Number(item.quantity) || 0),
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
.toLocaleString("zh-CN", {
|
.toLocaleString("zh-CN", {
|
||||||
@@ -320,18 +531,26 @@ const ServicePage = () => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
headStyle={{
|
||||||
|
background: 'rgba(59, 130, 246, 0.05)',
|
||||||
|
borderBottom: '1px solid rgba(59, 130, 246, 0.1)'
|
||||||
|
}}
|
||||||
|
bodyStyle={{
|
||||||
|
background: 'rgba(59, 130, 246, 0.02)'
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Table
|
<Table
|
||||||
columns={itemColumns}
|
columns={itemColumns}
|
||||||
dataSource={section.items.map((item, itemIndex) => ({
|
dataSource={section.items.map((item, itemIndex) => ({
|
||||||
...item,
|
...item,
|
||||||
key: `${sectionIndex}-${itemIndex}`,
|
key: `${record.id}-${sectionIndex}-${itemIndex}`,
|
||||||
serviceId: record.id,
|
serviceId: record.id,
|
||||||
sectionIndex: sectionIndex,
|
sectionIndex,
|
||||||
itemIndex: itemIndex
|
itemIndex
|
||||||
}))}
|
}))}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
className="rounded-lg overflow-hidden"
|
className="rounded-lg overflow-hidden"
|
||||||
|
rowClassName="hover:bg-blue-50/50 dark:hover:bg-blue-900/20 transition-colors"
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
@@ -343,10 +562,10 @@ const ServicePage = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-100 ">
|
<div className="min-h-screen">
|
||||||
<Card
|
<Card
|
||||||
title={<span className="text-xl font-medium">服务模版管理</span>}
|
title={<span className="text-xl font-medium">服务模版管理</span>}
|
||||||
className="shadow-lg rounded-lg"
|
className="h-full w-full overflow-auto"
|
||||||
extra={
|
extra={
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -361,11 +580,36 @@ const ServicePage = () => {
|
|||||||
dataSource={data}
|
dataSource={data}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
|
scroll={{ x: true }}
|
||||||
expandable={{
|
expandable={{
|
||||||
expandedRowRender,
|
expandedRowRender,
|
||||||
|
expandRowByClick: true,
|
||||||
|
expandIcon: ({ expanded, onExpand, record }) => (
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onExpand(record, e);
|
||||||
|
}}
|
||||||
|
icon={expanded ? <UpOutlined /> : <DownOutlined />}
|
||||||
|
className={`
|
||||||
|
transition-all duration-300
|
||||||
|
${expanded
|
||||||
|
? 'text-blue-600'
|
||||||
|
: 'text-gray-400 hover:text-blue-600'
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
rowExpandable: record => record.attributes.sections?.length > 0,
|
||||||
}}
|
}}
|
||||||
className="rounded-lg overflow-hidden"
|
className="rounded-lg overflow-hidden"
|
||||||
rowClassName="hover:bg-gray-50 transition-colors"
|
rowClassName={(record, index) => `
|
||||||
|
cursor-pointer transition-colors
|
||||||
|
${index % 2 === 0 ? 'bg-white dark:bg-gray-800' : 'bg-gray-50 dark:bg-gray-750'}
|
||||||
|
hover:bg-blue-50 dark:hover:bg-blue-900/20
|
||||||
|
`}
|
||||||
|
expandedRowClassName={() => 'bg-blue-50/50 dark:bg-blue-900/10'}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user