From df0aa520cafdc4f25880d759fb20675d9a5077ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=98Liammcl=E2=80=99?= Date: Sun, 22 Dec 2024 12:33:02 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=8D=A2schema=EF=BC=8C=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E7=AE=A1=E7=90=8650%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/supabase.js | 2 +- src/pages/auth/Login.jsx | 1 - src/pages/company/service/detail/index.jsx | 38 +-- src/pages/company/service/index.jsx | 372 +++++++++++++++++---- 4 files changed, 322 insertions(+), 91 deletions(-) diff --git a/src/config/supabase.js b/src/config/supabase.js index 5f7b4fa..6a073da 100644 --- a/src/config/supabase.js +++ b/src/config/supabase.js @@ -21,7 +21,7 @@ export const createSupabase = () => { detectSessionInUrl: false, }, db: { - schema: 'limq' + schema: 'limq_dev' } } ); diff --git a/src/pages/auth/Login.jsx b/src/pages/auth/Login.jsx index 88f9b5e..654dcc3 100644 --- a/src/pages/auth/Login.jsx +++ b/src/pages/auth/Login.jsx @@ -22,7 +22,6 @@ const Login = () => { const handleGoogleLogin = async () => { try { await signInWithGoogle(); - navigate("/"); } catch (error) { console.error("Google login error:", error); } diff --git a/src/pages/company/service/detail/index.jsx b/src/pages/company/service/detail/index.jsx index 01a00c2..aaadb18 100644 --- a/src/pages/company/service/detail/index.jsx +++ b/src/pages/company/service/detail/index.jsx @@ -30,9 +30,10 @@ const ServiceForm = () => { const isEdit = location.search.includes("edit=true"); const [editingSectionIndex, setEditingSectionIndex] = useState(null); const [availableSections, setAvailableSections] = useState([]); - const [formValues, setFormValues] = useState({}); - const [sectionNameForm] = Form.useForm(); - + const [formValues, setFormValues] = useState({ + sections: [{ items: [{}] }], + currency: "CNY" + }); useEffect(() => { if (id) { fetchServiceTemplate(); @@ -50,11 +51,13 @@ const ServiceForm = () => { .single(); if (error) throw error; - form.setFieldsValue({ + const formData = { templateName: data.attributes.templateName, description: data.attributes.description, sections: data.attributes.sections, - }); + }; + form.setFieldsValue(formData); + setFormValues(formData); } catch (error) { console.error("获取服务模版失败:", error); message.error("获取服务模版失败"); @@ -96,6 +99,7 @@ const ServiceForm = () => { templateName: values.templateName, description: values.description, sections: values.sections, + category: values.category || [], // 添加 category 字段 totalAmount, }, }; @@ -279,7 +283,7 @@ const ServiceForm = () => { disabled={id && !isEdit} onValuesChange={handleValuesChange} > -
+
基本信息 @@ -327,23 +331,12 @@ const ServiceForm = () => {
-
+
报价明细 - {(!id || isEdit) && ( - diff --git a/src/pages/company/service/index.jsx b/src/pages/company/service/index.jsx index 35438ad..7d1282d 100644 --- a/src/pages/company/service/index.jsx +++ b/src/pages/company/service/index.jsx @@ -1,9 +1,11 @@ -import { Card, Table, Button, Space, Input, message, Popconfirm } from "antd"; -import { EyeOutlined } from "@ant-design/icons"; +import { Card, Table, Button, Space, Input, message, Popconfirm, Form, Tag, Typography } from "antd"; +import { DownOutlined, UpOutlined,EyeOutlined,EditOutlined } from "@ant-design/icons"; import { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { supabase } from "@/config/supabase"; +const { Paragraph } = Typography; + const ServicePage = () => { const [loading, setLoading] = useState(false); const [data, setData] = useState([]); @@ -30,32 +32,72 @@ const ServicePage = () => { } }; - // 添加保存编辑项目的方法 - const handleSaveItem = async (record, sectionIndex, itemIndex) => { + // 修改保存逻辑 + const handleSaveItem = async (record) => { try { setLoading(true); - const newData = [...data]; - const targetService = newData.find(item => item.id === record.serviceId); - targetService.attributes.sections[sectionIndex].items[itemIndex] = record; + const { serviceId, sectionIndex, 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 currentService = data.find(item => item.id === serviceId); + if (!currentService) { + throw new Error('Service not found'); + } + // 创建新的 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 .from('resources') - .update({ attributes: targetService.attributes }) - .eq('id', targetService.id); + .update({ + type: "serviceTemplate", + attributes: newAttributes + }) + .eq('id', serviceId); if (error) throw error; - setData(newData); + // 更新本地状态 + setData(prevData => prevData.map(item => + item.id === serviceId + ? { ...item, attributes: newAttributes } + : item + )); + setEditingKey(''); - message.success('更新成功'); + message.success('保存成功'); } catch (error) { console.error('保存失败:', error); message.error('保存失败'); @@ -68,28 +110,73 @@ const ServicePage = () => { const handleDeleteItem = async (record) => { try { setLoading(true); - const newData = [...data]; - const targetService = newData.find(item => item.id === record.serviceId); + const { serviceId, sectionIndex, itemIndex } = record; - // 删除指定项目 - 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'); - // 重新计算总金额 - 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; + // 创建新的 attributes 对象,避免直接修改状态 + const newAttributes = { + ...targetService.attributes, + sections: targetService.attributes.sections.map((section, secIdx) => { + if (secIdx === sectionIndex) { + return { + ...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 .from('resources') - .update({ attributes: targetService.attributes }) - .eq('id', targetService.id); + .update({ + attributes: newAttributes + }) + .eq('id', serviceId); 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('删���失败'); + } 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('删除成功'); } catch (error) { console.error('删除失败:', error); @@ -107,10 +194,65 @@ const ServicePage = () => { key: "templateName", className: "min-w-[200px]", }, + { + title: "分类", + dataIndex: ["attributes", "category"], + key: "category", + render: (category) => { + if (!category || !Array.isArray(category)) return null; + return ( + + {category.map((cat, index) => ( + + {cat} + + ))} + + ) + }} + className="mb-0" + > + + {category.map((cat, index) => ( + + {cat} + + ))} + + + ); + }, + }, { title: "描述", dataIndex: ["attributes", "description"], key: "description", + width: 300, + render: (text) => ( + + {text} + + ) }, { title: "总金额", @@ -118,7 +260,7 @@ const ServicePage = () => { key: "totalAmount", className: "min-w-[150px] text-right", render: (amount) => ( - + ¥ {amount?.toLocaleString("zh-CN", { minimumFractionDigits: 2, @@ -130,17 +272,40 @@ const ServicePage = () => { { title: "操作", key: "action", + fixed: 'right', + width: 220, render: (_, record) => ( - + e.stopPropagation()}> + + handleDeleteService(record.id)} + > + + ), }, @@ -152,28 +317,71 @@ const ServicePage = () => { title: "项目名称", dataIndex: "name", key: "name", - width: "40%", + width: "25%", render: (text, record) => { const isEditing = record.key === editingKey; return isEditing ? ( - { - 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); - }} - /> + + { + 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; } }, + { + title: "描述", + dataIndex: "description", + key: "description", + width: "25%", + render: (text, record) => { + const isEditing = record.key === editingKey; + return isEditing ? ( + + { + 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); + }} + /> + + ) : ( + + {text} + + ); + } + }, { title: "单价", dataIndex: "price", key: "price", - width: "20%", + width: "15%", render: (price, record) => { const isEditing = record.key === editingKey; return isEditing ? ( @@ -202,7 +410,7 @@ const ServicePage = () => { title: "数量", dataIndex: "quantity", key: "quantity", - width: "20%", + width: "15%", render: (quantity, record) => { const isEditing = record.key === editingKey; return isEditing ? ( @@ -296,21 +504,24 @@ const ServicePage = () => { } ]; - // 修改 expandedRowRender 以支持编辑 + // 修改 expandedRowRender 函数,确保 key 的唯一性 const expandedRowRender = (record) => ( -
+
{record.attributes.sections?.map((section, sectionIndex) => ( - {section.sectionName} - +
+ + {section.sectionName || `服务类型 ${sectionIndex + 1}`} + + 总计: ¥ {section.items .reduce( - (total, item) => total + item.price * item.quantity, + (total, item) => + total + (Number(item.price) || 0) * (Number(item.quantity) || 0), 0 ) .toLocaleString("zh-CN", { @@ -320,18 +531,26 @@ const ServicePage = () => {
} + 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)' + }} > ({ ...item, - key: `${sectionIndex}-${itemIndex}`, + key: `${record.id}-${sectionIndex}-${itemIndex}`, serviceId: record.id, - sectionIndex: sectionIndex, - itemIndex: itemIndex + sectionIndex, + itemIndex }))} pagination={false} className="rounded-lg overflow-hidden" + rowClassName="hover:bg-blue-50/50 dark:hover:bg-blue-900/20 transition-colors" /> ))} @@ -343,10 +562,10 @@ const ServicePage = () => { }, []); return ( -
+
服务模版管理} - className="shadow-lg rounded-lg" + className="h-full w-full overflow-auto" extra={