diff --git a/src/components/SectionList/index.jsx b/src/components/SectionList/index.jsx
index aea1352..e329549 100644
--- a/src/components/SectionList/index.jsx
+++ b/src/components/SectionList/index.jsx
@@ -1,26 +1,47 @@
-import React, { useState, useEffect, useMemo } from 'react';
-import { Form, Input, InputNumber, Button, Card, Typography, Modal, message, Divider, Select } from 'antd';
-import { PlusOutlined, DeleteOutlined, EditOutlined, CheckOutlined, CloseOutlined } from '@ant-design/icons';
-import { v4 as uuidv4 } from 'uuid';
+import React, { useState, useEffect, useMemo } from "react";
+import {
+ Form,
+ Input,
+ InputNumber,
+ Button,
+ Card,
+ Typography,
+ Modal,
+ message,
+ Divider,
+ Select,
+} from "antd";
+import {
+ PlusOutlined,
+ DeleteOutlined,
+ EditOutlined,
+ CheckOutlined,
+ CloseOutlined,
+} from "@ant-design/icons";
+import { v4 as uuidv4 } from "uuid";
import { supabase } from "@/config/supabase";
import { supabaseService } from "@/hooks/supabaseService";
const { Text } = Typography;
-const SectionList = ({
+const SectionList = ({
form,
isView,
formValues,
type,
- currentCurrency = 'CNY'
+ currentCurrency = "TWD",
}) => {
const [editingSectionIndex, setEditingSectionIndex] = useState(null);
- const [editingSectionName, setEditingSectionName] = useState('');
+ const [editingSectionName, setEditingSectionName] = useState("");
const [templateModalVisible, setTemplateModalVisible] = useState(false);
const [availableSections, setAvailableSections] = useState([]);
const [loading, setLoading] = useState(false);
const [units, setUnits] = useState([]);
const [loadingUnits, setLoadingUnits] = useState(false);
+ const CURRENCY_SYMBOLS = {
+ CNY: "¥",
+ TWD: "NT$",
+ USD: "$",
+ };
- // 内部计算方法
const calculateItemAmount = (quantity, price) => {
const safeQuantity = Number(quantity) || 0;
const safePrice = Number(price) || 0;
@@ -36,19 +57,13 @@ const SectionList = ({
};
const formatCurrency = (amount) => {
- const CURRENCY_SYMBOLS = {
- CNY: "¥",
- TWD: "NT$",
- USD: "$",
- };
const safeAmount = Number(amount) || 0;
- return `${CURRENCY_SYMBOLS[currentCurrency] || ""}${safeAmount.toLocaleString("zh-CN", {
+ return `${CURRENCY_SYMBOLS[currentCurrency] || "NT$"}${safeAmount.toLocaleString("zh-TW", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})}`;
};
- // 获取可用的小节模板
const fetchAvailableSections = async () => {
try {
setLoading(true);
@@ -56,6 +71,7 @@ const SectionList = ({
.from("resources")
.select("*")
.eq("type", "sections")
+ .eq("attributes->>template_type", [type])
.order("created_at", { ascending: false });
if (error) throw error;
@@ -68,195 +84,235 @@ const SectionList = ({
}
};
- // 处理小节名称编辑
const handleSectionNameEdit = (sectionIndex, initialValue) => {
setEditingSectionIndex(sectionIndex);
- setEditingSectionName(initialValue || '');
+ setEditingSectionName(initialValue || "");
};
const handleSectionNameSave = () => {
if (!editingSectionName.trim()) {
- message.error('请输入小节名称');
+ message.error("请输入小节名称");
return;
}
- const sections = form.getFieldValue('sections');
+ const sections = form.getFieldValue("sections");
const newSections = [...sections];
newSections[editingSectionIndex] = {
...newSections[editingSectionIndex],
- sectionName: editingSectionName.trim()
+ sectionName: editingSectionName.trim(),
};
- form.setFieldValue('sections', newSections);
+ form.setFieldValue("sections", newSections);
setEditingSectionIndex(null);
- setEditingSectionName('');
+ setEditingSectionName("");
};
- // 添加项目
const handleAddItem = (add) => {
add({
key: uuidv4(),
- name: '',
- description: '',
+ name: "",
+ description: "",
quantity: 1,
price: 0,
- unit: ''
+ unit: "",
});
};
- // 处理使用模板
const handleUseTemplate = (template, add) => {
const newSection = {
key: uuidv4(),
sectionName: template.attributes.name,
- items: (template.attributes.items || []).map(item => ({
+ items: (template.attributes.items || []).map((item) => ({
key: uuidv4(),
- name: item.name || '',
- description: item.description || '',
+ name: item.name || "",
+ description: item.description || "",
price: item.price || 0,
quantity: item.quantity || 1,
- unit: item.unit || '',
+ unit: item.unit || "",
})),
};
add(newSection);
setTemplateModalVisible(false);
- message.success('套用模版成功');
+ message.success("套用模版成功");
};
- // 处理创建自定义小节
const handleCreateCustom = (add, fieldsLength) => {
add({
key: uuidv4(),
sectionName: `服务类型 ${fieldsLength + 1}`,
- items: [{
- key: uuidv4(),
- name: '',
- description: '',
- quantity: 1,
- price: 0,
- unit: ''
- }]
+ items: [
+ {
+ key: uuidv4(),
+ name: "",
+ description: "",
+ quantity: 1,
+ price: 0,
+ unit: "",
+ },
+ ],
});
setTemplateModalVisible(false);
};
- // 获取单位列表
const fetchUnits = async () => {
setLoadingUnits(true);
try {
- const { data: units } = await supabaseService.select('resources', {
+ const { data: units } = await supabaseService.select("resources", {
filter: {
- 'type': { eq: 'units' },
- 'attributes->>template_type': { in: `(${type},common)` }
+ type: { eq: "units" },
+ "attributes->>template_type": { in: `(${type},common)` },
},
order: {
- column: 'created_at',
- ascending: false
- }
+ column: "created_at",
+ ascending: false,
+ },
});
setUnits(units || []);
} catch (error) {
- message.error('获取单位列表失败');
+ message.error("获取单位列表失败");
console.error(error);
} finally {
setLoadingUnits(false);
}
};
- // 在组件加载时获取单位列表
useEffect(() => {
fetchUnits();
}, []);
- // 新增单位
const handleAddUnit = async (unitName) => {
try {
- const { error } = await supabase
- .from('resources')
- .insert([{
- type: 'units',
+ const { error } = await supabase.from("resources").insert([
+ {
+ type: "units",
attributes: {
- name: unitName
+ name: unitName,
+ template_type: type,
},
- schema_version: 1
- }]);
+ schema_version: 1,
+ },
+ ]);
if (error) throw error;
- message.success('新增单位成功');
+ message.success("新增单位成功");
fetchUnits();
return true;
} catch (error) {
- message.error('新增单位失败');
+ message.error("新增单位失败");
console.error(error);
return false;
}
};
- // 模板选择弹窗内容
const renderTemplateModalContent = (add, fieldsLength) => (
-
- {availableSections.map(section => (
-
handleUseTemplate(section, add)}
- >
-
-
-
- {section.attributes.name}
-
-
- {section.attributes.items?.length || 0} 个项目
+ {availableSections.length > 0 ? (
+
+
+ {availableSections.map((section) => (
+
handleUseTemplate(section, add)}
+ >
+
+
+
+ {section.attributes.name}
+
+
+ {section.attributes.items?.length || 0} 个项目
+
+
+
+
+ {(section.attributes.items || [])
+ .slice(0, 3)
+ .map((item, index) => (
+
+
+ {item.name}
+
+
+ {formatCurrency(item.price)}
+
+
+ ))}
+ {(section.attributes.items || []).length > 3 && (
+
+ 还有 {section.attributes.items.length - 3} 个项目...
+
+ )}
+
+
+
+ 总金额
+
+ {formatCurrency(
+ (section.attributes.items || []).reduce(
+ (sum, item) =>
+ sum + (item.price * (item.quantity || 1) || 0),
+ 0
+ )
+ )}
+
+
-
-
- {(section.attributes.items || []).slice(0, 3).map((item, index) => (
-
- {item.name}
- {formatCurrency(item.price)}
-
- ))}
- {(section.attributes.items || []).length > 3 && (
-
- 还有 {section.attributes.items.length - 3} 个项目...
-
- )}
-
-
-
- 总金额
-
- {formatCurrency(
- (section.attributes.items || []).reduce(
- (sum, item) => sum + (item.price * (item.quantity || 1) || 0),
- 0
- )
- )}
-
-
-
+ ))}
- ))}
-
-
-
-
- }
- onClick={() => handleCreateCustom(add, fieldsLength)}
- className="w-1/3 border-2"
- >
- 自定义模块
-
-
+
+ }
+ onClick={() => handleCreateCustom(add, fieldsLength)}
+ className="bg-blue-600 hover:bg-blue-700 border-0 shadow-md hover:shadow-lg transition-all duration-200 h-10 px-6 rounded-lg flex items-center gap-2"
+ >
+ 自定义模块
+
+
+
+ ) : (
+
+
+
+ 暂无可用模板
+
+
+ 您可以选择创建一个自定义模块开始使用
+
+
}
+ onClick={() => handleCreateCustom(add, fieldsLength)}
+ size="large"
+ className="shadow-md hover:shadow-lg transition-shadow"
+ >
+ 创建自定义模块
+
+
+ )}
);
- // 修改项目小计的计算,将其封装为 memo 组件
const ItemSubtotal = React.memo(({ quantity, price, currentCurrency }) => {
const subtotal = useMemo(() => {
const safeQuantity = Number(quantity) || 0;
@@ -273,7 +329,6 @@ const SectionList = ({
);
});
- // 修改小节总计的计算,将其封装为 memo 组件
const SectionTotal = React.memo(({ items, currentCurrency }) => {
const total = useMemo(() => {
if (!Array.isArray(items)) return 0;
@@ -281,7 +336,7 @@ const SectionList = ({
if (!item) return sum;
const safeQuantity = Number(item.quantity) || 0;
const safePrice = Number(item.price) || 0;
- return sum + (safeQuantity * safePrice);
+ return sum + safeQuantity * safePrice;
}, 0);
}, [items]);
@@ -302,11 +357,11 @@ const SectionList = ({
{(fields, { add, remove }) => (
<>
-
+
{fields.map((field, sectionIndex) => (
@@ -315,7 +370,9 @@ const SectionList = ({
setEditingSectionName(e.target.value)}
+ onChange={(e) =>
+ setEditingSectionName(e.target.value)
+ }
onPressEnter={handleSectionNameSave}
autoFocus
className="w-48"
@@ -331,7 +388,7 @@ const SectionList = ({
icon={
}
onClick={() => {
setEditingSectionIndex(null);
- setEditingSectionName('');
+ setEditingSectionName("");
}}
className="text-red-500"
/>
@@ -340,17 +397,26 @@ const SectionList = ({
- {form.getFieldValue(['sections', sectionIndex, 'sectionName'])
- || `服务类型 ${sectionIndex + 1}`}
+ {form.getFieldValue([
+ "sections",
+ sectionIndex,
+ "sectionName",
+ ]) || `服务类型 ${sectionIndex + 1}`}
{!isView && (
}
- onClick={() => handleSectionNameEdit(
- sectionIndex,
- form.getFieldValue(['sections', sectionIndex, 'sectionName'])
- )}
+ onClick={() =>
+ handleSectionNameEdit(
+ sectionIndex,
+ form.getFieldValue([
+ "sections",
+ sectionIndex,
+ "sectionName",
+ ])
+ )
+ }
className="text-gray-400 hover:text-blue-500"
/>
)}
@@ -368,11 +434,9 @@ const SectionList = ({
}
>
- {/* 项目列表 */}
{(itemFields, { add: addItem, remove: removeItem }) => (
<>
- {/* 表头 */}
项目明细
描述/备注
@@ -383,7 +447,6 @@ const SectionList = ({
- {/* 项目列表 */}
{itemFields.map((itemField, itemIndex) => (
({
+ style={{ minWidth: "120px" }}
+ options={units.map((unit) => ({
label: unit.attributes.name,
- value: unit.attributes.name
+ value: unit.attributes.name,
}))}
onDropdownVisibleChange={(open) => {
if (open) fetchUnits();
@@ -423,48 +487,32 @@ const SectionList = ({
dropdownRender={(menu) => (
<>
{menu}
-
-
- }
- block
- onClick={(e) => {
- e.stopPropagation();
- Modal.confirm({
- title: '新增单位',
- content: (
- {
- Modal.confirm.update({
- okButtonProps: {
- disabled: !e.target.value.trim()
- }
- });
- }}
- ref={(input) => {
- if (input) {
- setTimeout(() => input.focus(), 100);
- }
- }}
- />
- ),
- onOk: async (close) => {
- const unitName = document.querySelector('.ant-modal-content input').value.trim();
- if (await handleAddUnit(unitName)) {
- const currentItems = form.getFieldValue(['sections', field.name, 'items']);
- currentItems[itemField.name].unit = unitName;
- form.setFieldValue(['sections', field.name, 'items'], currentItems);
- close();
- }
- }
- });
+
+
+ }
+ onSearch={async (value) => {
+ if (!value.trim()) return;
+ if (
+ await handleAddUnit(value.trim())
+ ) {
+ const currentItems =
+ form.getFieldValue([
+ "sections",
+ field.name,
+ "items",
+ ]);
+ currentItems[itemField.name].unit =
+ value.trim();
+ form.setFieldValue(
+ ["sections", field.name, "items"],
+ currentItems
+ );
+ }
}}
- >
- 新增单位
-
-
+ />
+
>
)}
/>
@@ -474,18 +522,34 @@ const SectionList = ({
name={[itemField.name, "quantity"]}
className="!mb-0"
>
-
+
-
+
-
{!isView && itemFields.length > 1 && (
@@ -511,7 +575,7 @@ const SectionList = ({
)}
-
@@ -555,4 +619,5 @@ const SectionList = ({
);
};
-export default SectionList;
\ No newline at end of file
+
+export default SectionList;
diff --git a/src/pages/company/quotation/detail/index.jsx b/src/pages/company/quotation/detail/index.jsx
index 13bcb72..e7a564a 100644
--- a/src/pages/company/quotation/detail/index.jsx
+++ b/src/pages/company/quotation/detail/index.jsx
@@ -18,16 +18,12 @@ import {
ArrowLeftOutlined,
SaveOutlined,
DeleteOutlined,
- CloseOutlined,
- EditOutlined,
- CheckOutlined,
} from "@ant-design/icons";
import { supabase } from "@/config/supabase";
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
import { v4 as uuidv4 } from "uuid";
-
-const { TextArea } = Input;
-const { Title, Text } = Typography;
+import SectionList from '@/components/SectionList'
+const { Title } = Typography;
// 添加货币符号映射
const CURRENCY_SYMBOLS = {
@@ -47,7 +43,7 @@ const QuotationForm = () => {
const [dataSource, setDataSource] = useState([{ id: Date.now() }]);
const [totalAmount, setTotalAmount] = useState(0);
const [loading, setLoading] = useState(false);
- const [currentCurrency, setCurrentCurrency] = useState("CNY");
+ const [currentCurrency, setCurrentCurrency] = useState("TWD");
const [customers, setCustomers] = useState([]);
const [selectedCustomers, setSelectedCustomers] = useState([]);
const [formValues, setFormValues] = useState({});
@@ -737,304 +733,28 @@ const QuotationForm = () => {
- {/* 报价单细卡片 */}
-
- {(fields, { add, remove }) => (
- <>
-
- {fields.map((field, sectionIndex) => (
-
-
- {editingSectionIndex === sectionIndex ? (
-
-
- setEditingSectionName(e.target.value)
- }
- onPressEnter={handleSectionNameSave}
- autoFocus
- className="w-48"
- />
- }
- onClick={handleSectionNameSave}
- className="text-green-500 hover:text-green-600"
- />
- }
- onClick={handleSectionNameCancel}
- className="text-red-500 hover:text-red-600"
- />
-
- ) : (
-
-
-
- {form.getFieldValue([
- "sections",
- sectionIndex,
- "sectionName",
- ]) || `服务类�� ${sectionIndex + 1}`}
-
- {(!id || isEdit) && (
- }
- onClick={() =>
- handleSectionNameEdit(
- sectionIndex,
- form.getFieldValue([
- "sections",
- sectionIndex,
- "sectionName",
- ]) || `服务类型 ${sectionIndex + 1}`
- )
- }
- className="text-gray-400 hover:text-blue-500"
- />
- )}
-
- )}
-
-
- }
- >
-
- {(itemFields, { add: addItem, remove: removeItem }) => (
- <>
- {/* 表头 */}
-
-
项目明细
-
描述/备注
-
单位
-
数量
-
单价
-
小计
-
操作
-
- {itemFields.map((itemField, itemIndex) => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {formatCurrency(
- calculateItemAmount(
- formValues?.sections?.[sectionIndex]
- ?.items?.[itemIndex]?.quantity,
- formValues?.sections?.[sectionIndex]
- ?.items?.[itemIndex]?.price
- )
- )}
-
-
- {!isView && (
-
}
- onClick={() => removeItem(itemField.name)}
- className="flex items-center justify-center"
- />
- )}
-
- ))}
-
- {!isView && (
-
- )}
-
-
-
- 小计总额:
-
- {formatCurrency(
- calculateSectionTotal(
- formValues?.sections?.[sectionIndex]
- ?.items
- )
- )}
-
-
-
- >
- )}
-
-
- ))}
-
-
- {/* Add section button */}
- {!isView && (
-
- }
- className="w-1/3 border-2 hover:border-blue-400 hover:text-blue-500"
- >
- 新建小节
-
-
- )}
-
- {/* 总金额统计 */}
-
-
- 税前总计:
-
- {formatCurrency(calculateTotalAmount(formValues?.sections))}
-
-
-
-
-
税率:
-
- {
- const value = e.target.value.replace(/[^\d]/g, '');
- setTaxRate(Number(value) || 0);
- }}
- />
-
-
-
-
- 税后总计:
-
- {formatCurrency(afterTaxAmount)}
-
-
-
-
-
折扣价:
-
- {
- const value = e.target.value.replace(/[^\d]/g, '');
- setDiscount(Number(value) || 0);
- }}
- />
-
-
-
-
- 最终金额:
-
- {formatCurrency(discount || afterTaxAmount)}
-
-
-
-
-
-
- 补充说明
-
- }
- bordered={false}
- >
-
-
-
-
- >
- )}
-
+
+
+ 服务明细
+
+ }
+ bordered={false}
+ >
+
+
-
- 选择小节模版
-
- }
- open={templateModalVisible}
- onCancel={() => setTemplateModalVisible(false)}
- footer={null}
- width={800}
- className="dark:bg-gray-800"
- closeIcon={
-
- }
- >
- {renderTemplateModalContent()}
-
+
);
};
diff --git a/src/pages/company/quotation/index.jsx b/src/pages/company/quotation/index.jsx
index 33a42c7..e64eb6e 100644
--- a/src/pages/company/quotation/index.jsx
+++ b/src/pages/company/quotation/index.jsx
@@ -354,7 +354,8 @@ const QuotationPage = () => {
使用选中模板
,
]}
- width={800}
+ width={900}
+ className="template-modal dark:bg-gray-800"
>
{loading ? (
@@ -363,63 +364,74 @@ const QuotationPage = () => {
) : templates.length === 0 ? (
) : (
-
+
{getTemplatesByCategory().map((group, groupIndex) => (
-
-
-
-
+
+
+
{group.name}
-
+
({group.templates.length})
-
+
{group.templates.map(template => (
handleTemplateSelect(template.id)}
+ className={`
+ relative p-4 rounded-xl cursor-pointer transition-all duration-200
+ ${
+ selectedTemplateId === template.id
+ ? 'ring-2 ring-blue-500 bg-blue-50/40 dark:bg-blue-900/40'
+ : 'hover:bg-gray-50 dark:hover:bg-gray-700/50 border border-gray-200 dark:border-gray-700 shadow-sm hover:shadow-md'
+ }
+ dark:bg-gray-800
+ `}
>
-
+
-
+
{template.attributes.templateName}
-
+
{template.attributes.description || '暂无描述'}
-
+
¥{template.attributes.totalAmount?.toLocaleString()}
-
+
-
+
{template.attributes.sections.map((section, index) => (
-
+
{section.sectionName}
-
+
{section.items.length}项
))}
+
+ {selectedTemplateId === template.id && (
+
+ )}
))}
diff --git a/src/pages/company/service/detail/components/QuotationTemplate.jsx b/src/pages/company/service/detail/components/QuotationTemplate.jsx
index 95172b7..52f48ce 100644
--- a/src/pages/company/service/detail/components/QuotationTemplate.jsx
+++ b/src/pages/company/service/detail/components/QuotationTemplate.jsx
@@ -31,7 +31,6 @@ const QuotationTemplate = ({ id, isView, onCancel,isEdit }) => {
'attributes->>template_type': { eq: TYPE }
}
});
- console.log(data,'data');
if (data?.[0]) {
const formData = {
diff --git a/src/pages/company/service/detail/index.jsx b/src/pages/company/service/detail/index.jsx
index 23b2dfb..81d37ed 100644
--- a/src/pages/company/service/detail/index.jsx
+++ b/src/pages/company/service/detail/index.jsx
@@ -38,7 +38,7 @@ const ServiceForm = () => {
}
return (
-
+
{
onChange={setActiveType}
type="card"
className="bg-white rounded-lg shadow-sm"
- items={TEMPLATE_TYPES.map(type => ({
- key: type.key,
- label: (
-
- {type.icon}
- {type.label}
-
- ),
- children: (
-
-
-
-
-
-
- )
- }))}
+ items={TEMPLATE_TYPES.map(type => {
+ return ({
+ key: type.key,
+ label: (
+
+ {type.icon}
+ {type.label}
+
+ ),
+ children: (
+
+
+
+
+
+
+
+ )
+ });
+ })}
/>
diff --git a/src/pages/company/service/itemsManange/sections/index.jsx b/src/pages/company/service/itemsManange/sections/index.jsx
index 0ec1d74..64afcaf 100644
--- a/src/pages/company/service/itemsManange/sections/index.jsx
+++ b/src/pages/company/service/itemsManange/sections/index.jsx
@@ -1,127 +1,127 @@
import React, { useState, useEffect } from 'react';
-import { Table, Button, Drawer, Modal, Form, Input, InputNumber, Space, message, Popconfirm, Select } from 'antd';
-import { PlusOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons';
-import { supabase } from '@/config/supabase';
+import {
+ Table,
+ Button,
+ Form,
+ Input,
+ Space,
+ message,
+ Popconfirm,
+ Select,
+ Segmented,
+ InputNumber,
+ Card,
+ Typography
+} from 'antd';
+import { PlusOutlined, DeleteOutlined } from '@ant-design/icons';
+import { supabaseService } from '@/hooks/supabaseService';
+import { v4 as uuidv4 } from 'uuid';
-const SectionManagement = () => {
+const { Text } = Typography;
+
+const SectionsManagement = ({ activeType = 'quotation', typeList }) => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
- const [drawerVisible, setDrawerVisible] = useState(false);
- const [modalVisible, setModalVisible] = useState(false);
- const [editingRecord, setEditingRecord] = useState(null);
+ const [editingKey, setEditingKey] = useState('');
const [form] = Form.useForm();
- const [units, setUnits] = useState([]);
+ const [filterType, setFilterType] = useState('all');
- // 获取子模块数据
- const fetchSections = async () => {
+ const fetchSections = async (type = activeType, filterTypeValue = filterType) => {
setLoading(true);
try {
- const { data: sections, error } = await supabase
- .from('resources')
- .select('*')
- .eq('type', 'sections');
+ let filterCondition;
+
+ switch (filterTypeValue) {
+ case 'current':
+ filterCondition = { eq: type };
+ break;
+ default:
+ filterCondition = { eq: type };
+ }
- if (error) throw error;
+ const { data: sections } = await supabaseService.select('resources', {
+ filter: {
+ 'type': { eq: 'sections' },
+ 'attributes->>template_type': filterCondition
+ },
+ order: {
+ column: 'created_at',
+ ascending: false
+ }
+ });
+
setData(sections || []);
} catch (error) {
- message.error('获取子模块数据失败');
+ message.error('获取模块数据失败');
console.error(error);
} finally {
setLoading(false);
}
};
- // 获取单位数据
- const fetchUnits = async () => {
- try {
- const { data: unitsData, error } = await supabase
- .from('resources')
- .select('*')
- .eq('type', 'units')
- .order('created_at', { ascending: false });
-
- if (error) throw error;
- setUnits(unitsData || []);
- } catch (error) {
- message.error('获取单位数据失败');
- console.error(error);
- }
- };
-
useEffect(() => {
- if (drawerVisible) {
- fetchSections();
- }
- fetchUnits();
- }, [drawerVisible]);
+ fetchSections(activeType, filterType);
+ }, [activeType]);
- // 打开新增/编辑模态框
- const showModal = async (record = null) => {
- setModalVisible(true);
- setEditingRecord(record);
-
- if (record) {
- try {
- const { data: section, error } = await supabase
- .from('resources')
- .select('*')
- .eq('id', record.id)
- .single();
-
- if (error) throw error;
-
- form.setFieldsValue({
- name: section.attributes.name,
- items: section.attributes.items
- });
- } catch (error) {
- message.error('获取子模块详情失败');
- console.error(error);
- }
- } else {
- form.setFieldsValue({
+ const handleAdd = () => {
+ const newData = {
+ id: Date.now().toString(),
+ attributes: {
name: '',
- items: [{ name: '', description: '', price: 0, quantity: 1, unit: '' }]
- });
- }
+ template_type: activeType,
+ items: [{
+ key: uuidv4(),
+ name: '',
+ description: '',
+ quantity: 1,
+ price: 0,
+ unit: ''
+ }]
+ },
+ isNew: true
+ };
+ setData([newData, ...data]);
+ setEditingKey(newData.id);
+ form.setFieldsValue(newData.attributes);
};
- // 保存子模块数据
- const handleSave = async (values) => {
+ const handleSave = async (record) => {
try {
- if (editingRecord) {
- // 更新
- const { error } = await supabase
- .from('resources')
- .update({
+ const values = await form.validateFields();
+ const items = form.getFieldValue(['items']) || [];
+
+ // 验证items数组
+ if (!items.length || !items.some(item => item.name)) {
+ message.error('请至少添加一个有效的服务项目');
+ return;
+ }
+
+ if (record.isNew) {
+ await supabaseService.insert('resources', {
+ type: 'sections',
+ attributes: {
+ name: values.name,
+ template_type: activeType,
+ items: items.filter(item => item.name), // 只保存有名称的项目
+ },
+ schema_version: 1
+ });
+ } else {
+ await supabaseService.update('resources',
+ { id: record.id },
+ {
attributes: {
name: values.name,
- items: values.items
+ template_type: activeType,
+ items: items.filter(item => item.name),
},
updated_at: new Date().toISOString()
- })
- .eq('id', editingRecord.id);
-
- if (error) throw error;
- } else {
- // 新增
- const { error } = await supabase
- .from('resources')
- .insert([{
- type: 'sections',
- attributes: {
- name: values.name,
- items: values.items
- },
- schema_version: 1
- }]);
-
- if (error) throw error;
+ }
+ );
}
message.success('保存成功');
- setModalVisible(false);
- form.resetFields();
+ setEditingKey('');
fetchSections();
} catch (error) {
message.error('保存失败');
@@ -129,15 +129,9 @@ const SectionManagement = () => {
}
};
- // 删除子模块
- const handleDelete = async (id) => {
+ const handleDelete = async (record) => {
try {
- const { error } = await supabase
- .from('resources')
- .delete()
- .eq('id', id);
-
- if (error) throw error;
+ await supabaseService.delete('resources', { id: record.id });
message.success('删除成功');
fetchSections();
} catch (error) {
@@ -146,290 +140,226 @@ const SectionManagement = () => {
}
};
- const drawerColumns = [
+ const columns = [
{
- title: '项目名称',
- dataIndex: 'name',
- },
- {
- title: '描述',
- dataIndex: 'description',
- },
- {
- title: '单价',
- dataIndex: 'price',
- render: (price) => `¥${price}`
- },
- {
- title: '数量',
- dataIndex: 'quantity',
- },
- {
- title: '单位',
- dataIndex: 'unit',
- }
- ];
-
- // 添加模态框内表格列定义
- const modalColumns = [
- {
- title: '项目名称',
- dataIndex: 'name',
- render: (_, __, index) => (
-
-
-
- )
- },
- {
- title: '描述',
- dataIndex: 'description',
- render: (_, __, index) => (
-
-
-
- )
- },
- {
- title: '单价',
- dataIndex: 'price',
- render: (_, __, index) => (
-
-
-
- )
- },
- {
- title: '数量',
- dataIndex: 'quantity',
- render: (_, __, index) => (
-
-
-
- )
- },
- {
- title: '单位',
- dataIndex: 'unit',
- render: (_, __, index) => (
-
-
- )
- },
- {
- title: '操作',
- render: (_, __, index, { remove }) => (
- }
- onClick={() => remove(index)}
- />
- )
- }
- ];
-
- return (
-
-
}
- onClick={() => setDrawerVisible(true)}
- className="flex items-center"
- >
- 子模块管理
-
-
-
- 子模块管理
-
- }
- placement="right"
- width={1000}
- onClose={() => setDrawerVisible(false)}
- open={drawerVisible}
- className="dark:bg-gray-800"
- >
-
-
}
- onClick={() => showModal()}
- className="mb-4 w-32 flex items-center justify-center"
- >
- 新增子模块
-
-
-
- {data.map((section) => (
-
-
-
- {section.attributes.name}
-
-
- }
- onClick={() => showModal(section)}
- className="text-blue-600 hover:text-blue-500"
- >
- 编辑
-
- handleDelete(section.id)}
- okButtonProps={{
- className: "bg-red-500 hover:bg-red-600 border-red-500"
- }}
- >
- }
- className="text-red-600 hover:text-red-500"
- />
-
-
-
-
-
`${section.id}-${index}`}
- className="border dark:border-gray-700 rounded-lg"
- rowClassName="hover:bg-gray-50 dark:hover:bg-gray-700/50"
- size="small"
- />
-
-
- ))}
-
-
-
-
- {
- setModalVisible(false);
- setEditingRecord(null);
- form.resetFields();
- }}
- footer={null}
- width={1200}
- destroyOnClose={true}
- >
-
-
+
-
-
- {(fields, { add, remove }) => (
-
-
({
- ...col,
- render: (...args) => col.render(...args, { remove })
- }))}
- pagination={false}
- rowKey="key"
- className="mb-4"
- />
-
+ ) : (
+
{text}
+ );
+ },
+ },
+ {
+ title: '服务项目',
+ dataIndex: ['attributes', 'items'],
+ render: (items, record) => {
+ const isEditing = record.id === editingKey;
+
+ if (isEditing) {
+ return (
+
+ {(fields, { add, remove }) => (
+
-
- )}
-
+ )}
+
+ );
+ }
-
-
-