feat:分类模块,单位模块完成
This commit is contained in:
17
src/constants/typeEnum.js
Normal file
17
src/constants/typeEnum.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export const TEMPLATE_TYPES = [
|
||||||
|
{
|
||||||
|
value: 'quotation',
|
||||||
|
label: '报价单模板',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'project',
|
||||||
|
label: '专案模板',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'task',
|
||||||
|
label: '任务模板',
|
||||||
|
},{
|
||||||
|
value: 'common',
|
||||||
|
label: '通用',
|
||||||
|
}
|
||||||
|
];
|
||||||
@@ -1,336 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { Table, Button, Form, Input, Space, message, Popconfirm, Drawer, Select, Tabs, Badge, Radio } from 'antd';
|
|
||||||
import { PlusOutlined, FileTextOutlined, ProjectOutlined, CheckSquareOutlined } from '@ant-design/icons';
|
|
||||||
import { supabaseService } from '@/hooks/supabaseService';
|
|
||||||
|
|
||||||
const CategoryDrawer = () => {
|
|
||||||
const [data, setData] = useState([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [editingKey, setEditingKey] = useState('');
|
|
||||||
const [drawerVisible, setDrawerVisible] = useState(false);
|
|
||||||
const [activeType, setActiveType] = useState('quotation'); // 当前选中的类型
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
|
|
||||||
// 模板类型配置
|
|
||||||
const TEMPLATE_TYPES = [
|
|
||||||
{
|
|
||||||
value: 'quotation',
|
|
||||||
label: '报价单模板',
|
|
||||||
icon: <FileTextOutlined className="text-blue-500" />,
|
|
||||||
color: 'blue',
|
|
||||||
description: '用于创建标准化的报价单'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'project',
|
|
||||||
label: '专案模板',
|
|
||||||
icon: <ProjectOutlined className="text-green-500" />,
|
|
||||||
color: 'green',
|
|
||||||
description: '用于创建项目流程模板'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'task',
|
|
||||||
label: '任务模板',
|
|
||||||
icon: <CheckSquareOutlined className="text-purple-500" />,
|
|
||||||
color: 'purple',
|
|
||||||
description: '用于创建标准任务模板'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// 获取分类数据
|
|
||||||
const fetchCategories = async (type = activeType) => {
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const { data: categories } = await supabaseService.select('resources', {
|
|
||||||
filter: {
|
|
||||||
type: { eq: 'categories' },
|
|
||||||
'attributes->>type': { eq: type }
|
|
||||||
},
|
|
||||||
order: {
|
|
||||||
column: 'created_at',
|
|
||||||
ascending: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setData(categories || []);
|
|
||||||
} catch (error) {
|
|
||||||
message.error('获取分类数据失败');
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (drawerVisible) {
|
|
||||||
fetchCategories();
|
|
||||||
}
|
|
||||||
}, [drawerVisible, activeType]);
|
|
||||||
|
|
||||||
// 切换模板类型
|
|
||||||
const handleTypeChange = (type) => {
|
|
||||||
setActiveType(type);
|
|
||||||
setEditingKey(''); // 清除编辑状态
|
|
||||||
form.resetFields(); // 重置表单
|
|
||||||
};
|
|
||||||
|
|
||||||
// 新增分类
|
|
||||||
const handleAdd = (type) => {
|
|
||||||
const newData = {
|
|
||||||
id: Date.now().toString(),
|
|
||||||
attributes: {
|
|
||||||
name: '',
|
|
||||||
type: type // 默认类型
|
|
||||||
},
|
|
||||||
isNew: true
|
|
||||||
};
|
|
||||||
setData([newData, ...data]);
|
|
||||||
setEditingKey(newData.id);
|
|
||||||
form.setFieldsValue(newData.attributes);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 保存分类数据
|
|
||||||
const handleSave = async (record) => {
|
|
||||||
try {
|
|
||||||
const values = await form.validateFields();
|
|
||||||
if (record.isNew) {
|
|
||||||
await supabaseService.insert('resources', {
|
|
||||||
type: 'categories',
|
|
||||||
attributes: {
|
|
||||||
name: values.name,
|
|
||||||
type: values.type
|
|
||||||
},
|
|
||||||
schema_version: 1
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 更新
|
|
||||||
await supabaseService.update('resources',
|
|
||||||
{ id: record.id },
|
|
||||||
{
|
|
||||||
attributes: {
|
|
||||||
name: values.name,
|
|
||||||
type: values.type
|
|
||||||
},
|
|
||||||
updated_at: new Date().toISOString()
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
message.success('保存成功');
|
|
||||||
setEditingKey('');
|
|
||||||
fetchCategories();
|
|
||||||
} catch (error) {
|
|
||||||
message.error('保存失败');
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 删除分类
|
|
||||||
const handleDelete = async (record) => {
|
|
||||||
try {
|
|
||||||
await supabaseService.delete('resources', { id: record.id });
|
|
||||||
message.success('删除成功');
|
|
||||||
fetchCategories();
|
|
||||||
} catch (error) {
|
|
||||||
message.error('删除失败');
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: '分类名称',
|
|
||||||
dataIndex: ['attributes', 'name'],
|
|
||||||
render: (text, record) => {
|
|
||||||
const isEditing = record.id === editingKey;
|
|
||||||
return isEditing ? (
|
|
||||||
<Form.Item
|
|
||||||
name="name"
|
|
||||||
style={{ margin: 0 }}
|
|
||||||
rules={[{ required: true, message: '请输入分类名称!' }]}
|
|
||||||
>
|
|
||||||
<Input placeholder="请输入分类名称" />
|
|
||||||
</Form.Item>
|
|
||||||
) : (
|
|
||||||
<span className="text-gray-600">{text}</span>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '模板类型',
|
|
||||||
dataIndex: ['attributes', 'type'],
|
|
||||||
render: (text, record) => {
|
|
||||||
const isEditing = record.id === editingKey;
|
|
||||||
return isEditing ? (
|
|
||||||
<Form.Item
|
|
||||||
name="type"
|
|
||||||
style={{ margin: 0 }}
|
|
||||||
rules={[{ required: true, message: '请选择模板类型!' }]}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
options={TEMPLATE_TYPES}
|
|
||||||
placeholder="请选择模板类型"
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
) : (
|
|
||||||
<span className="text-gray-600">
|
|
||||||
{TEMPLATE_TYPES.find(t => t.value === text)?.label || text}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '操作',
|
|
||||||
render: (_, record) => {
|
|
||||||
const isEditing = record.id === editingKey;
|
|
||||||
return isEditing ? (
|
|
||||||
<Space>
|
|
||||||
<Button
|
|
||||||
type="link"
|
|
||||||
className="text-green-600 hover:text-green-500"
|
|
||||||
onClick={() => handleSave(record)}
|
|
||||||
>
|
|
||||||
保存
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="link"
|
|
||||||
className="text-gray-600 hover:text-gray-500"
|
|
||||||
onClick={() => {
|
|
||||||
setEditingKey('');
|
|
||||||
if (record.isNew) {
|
|
||||||
setData(data.filter(item => item.id !== record.id));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
取消
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
) : (
|
|
||||||
<Space>
|
|
||||||
<Button
|
|
||||||
type="link"
|
|
||||||
className="text-blue-600 hover:text-blue-500"
|
|
||||||
disabled={editingKey !== ''}
|
|
||||||
onClick={() => {
|
|
||||||
setEditingKey(record.id);
|
|
||||||
form.setFieldsValue({
|
|
||||||
name: record.attributes.name,
|
|
||||||
type: record.attributes.type
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
编辑
|
|
||||||
</Button>
|
|
||||||
<Popconfirm
|
|
||||||
title="确认<E7A1AE><E8AEA4><EFBFBD>除"
|
|
||||||
description="确定要删除这个分类吗?"
|
|
||||||
onConfirm={() => handleDelete(record)}
|
|
||||||
okText="确定"
|
|
||||||
cancelText="取消"
|
|
||||||
okButtonProps={{
|
|
||||||
className: "bg-red-500 hover:bg-red-600 border-red-500"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type="link"
|
|
||||||
className="text-red-600 hover:text-red-500"
|
|
||||||
disabled={editingKey !== ''}
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</Button>
|
|
||||||
</Popconfirm>
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
onClick={() => setDrawerVisible(true)}
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
>
|
|
||||||
分类管理
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Drawer
|
|
||||||
title="分类管理"
|
|
||||||
placement="right"
|
|
||||||
width={800}
|
|
||||||
onClose={() => setDrawerVisible(false)}
|
|
||||||
open={drawerVisible}
|
|
||||||
className="category-drawer"
|
|
||||||
>
|
|
||||||
<div className="p-6">
|
|
||||||
{/* 类型选择器 */}
|
|
||||||
<div className="mb-6">
|
|
||||||
<Radio.Group
|
|
||||||
value={activeType}
|
|
||||||
onChange={(e) => handleTypeChange(e.target.value)}
|
|
||||||
className="flex space-x-4"
|
|
||||||
>
|
|
||||||
{TEMPLATE_TYPES.map(type => (
|
|
||||||
<Radio.Button
|
|
||||||
key={type.value}
|
|
||||||
value={type.value}
|
|
||||||
className={`flex items-center px-4 py-2 ${
|
|
||||||
activeType === type.value ? `bg-${type.color}-50 border-${type.color}-500` : ''
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<span className="flex items-center space-x-2">
|
|
||||||
{type.icon}
|
|
||||||
<span>{type.label}</span>
|
|
||||||
<Badge
|
|
||||||
count={data.filter(item => item.attributes.type === type.value).length}
|
|
||||||
className={`bg-${type.color}-100 text-${type.color}-600`}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</Radio.Button>
|
|
||||||
))}
|
|
||||||
</Radio.Group>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 当前类型说明 */}
|
|
||||||
<div className="mb-4 p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
|
||||||
<h3 className={`text-${TEMPLATE_TYPES.find(t => t.value === activeType)?.color}-500 text-lg font-medium mb-2 flex items-center`}>
|
|
||||||
{TEMPLATE_TYPES.find(t => t.value === activeType)?.icon}
|
|
||||||
<span className="ml-2">{TEMPLATE_TYPES.find(t => t.value === activeType)?.label}</span>
|
|
||||||
</h3>
|
|
||||||
<p className="text-gray-500 dark:text-gray-400">
|
|
||||||
{TEMPLATE_TYPES.find(t => t.value === activeType)?.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 新增按钮 */}
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
onClick={() => handleAdd(activeType)}
|
|
||||||
className="mb-4"
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
>
|
|
||||||
新增分类
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{/* 分类列表 */}
|
|
||||||
<Form form={form}>
|
|
||||||
<Table
|
|
||||||
scroll={{ x: true }}
|
|
||||||
columns={columns}
|
|
||||||
dataSource={data}
|
|
||||||
rowKey="id"
|
|
||||||
loading={loading}
|
|
||||||
pagination={{
|
|
||||||
pageSize: 10,
|
|
||||||
showTotal: (total) => `共 ${total} 条`,
|
|
||||||
}}
|
|
||||||
className="bg-white dark:bg-gray-800 rounded-lg shadow"
|
|
||||||
/>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
</Drawer>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CategoryDrawer;
|
|
||||||
@@ -58,7 +58,7 @@ const QuotationTemplate = ({ id, isView, onCancel,isEdit }) => {
|
|||||||
const { data } = await supabaseService.select('resources', {
|
const { data } = await supabaseService.select('resources', {
|
||||||
filter: {
|
filter: {
|
||||||
type: { eq: 'categories' },
|
type: { eq: 'categories' },
|
||||||
'attributes->>type': { eq: 'quotation' }
|
'attributes->>template_type': { eq: 'quotation' }
|
||||||
},
|
},
|
||||||
order: {
|
order: {
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ const ServiceForm = () => {
|
|||||||
<TemplateComponent
|
<TemplateComponent
|
||||||
id={id}
|
id={id}
|
||||||
isView={isView}
|
isView={isView}
|
||||||
onCancel={() => navigate("/company/serviceTeamplate")}
|
onCancel={() => navigate("/company/serviceTemplate")}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ const ServicePage = () => {
|
|||||||
filter: {
|
filter: {
|
||||||
type: { eq: "serviceTemplate" },
|
type: { eq: "serviceTemplate" },
|
||||||
...(selectedType
|
...(selectedType
|
||||||
? { "attributes->>type": { eq: selectedType } }
|
? { "attributes->>template_type": { eq: selectedType } }
|
||||||
: {}),
|
: {}),
|
||||||
},
|
},
|
||||||
order: {
|
order: {
|
||||||
|
|||||||
288
src/pages/company/service/itemsManange/classify/index.jsx
Normal file
288
src/pages/company/service/itemsManange/classify/index.jsx
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Table, Button, Form, Input, Space, message, Popconfirm, Drawer, Select, Segmented, Badge } from 'antd';
|
||||||
|
import { PlusOutlined, FileTextOutlined, ProjectOutlined, CheckSquareOutlined } from '@ant-design/icons';
|
||||||
|
import { supabaseService } from '@/hooks/supabaseService';
|
||||||
|
const Classify = ({activeType,typeList}) => {
|
||||||
|
const [data, setData] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [editingKey, setEditingKey] = useState('');
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [filterType, setFilterType] = useState('all'); // 'all', 'common', 'current'
|
||||||
|
|
||||||
|
const fetchCategories = async (type = activeType, filterTypeValue = filterType) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
let filterCondition;
|
||||||
|
|
||||||
|
switch (filterTypeValue) {
|
||||||
|
case 'common':
|
||||||
|
filterCondition = { eq: 'common' };
|
||||||
|
break;
|
||||||
|
case 'current':
|
||||||
|
filterCondition = { eq: type };
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
filterCondition = { in: `(${type},common)` };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: categories } = await supabaseService.select('resources', {
|
||||||
|
filter: {
|
||||||
|
'type': { eq: 'categories' },
|
||||||
|
'attributes->>template_type': filterCondition
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
column: 'created_at',
|
||||||
|
ascending: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setData(categories || []);
|
||||||
|
} catch (error) {
|
||||||
|
message.error('获取分类数据失败');
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchCategories(activeType, filterType);
|
||||||
|
}, [activeType]);
|
||||||
|
|
||||||
|
|
||||||
|
// 新增分类
|
||||||
|
const handleAdd = (type) => {
|
||||||
|
const newData = {
|
||||||
|
id: Date.now().toString(),
|
||||||
|
attributes: {
|
||||||
|
name: '',
|
||||||
|
template_type: type
|
||||||
|
},
|
||||||
|
isNew: true
|
||||||
|
};
|
||||||
|
setData([newData, ...data]);
|
||||||
|
setEditingKey(newData.id);
|
||||||
|
form.setFieldsValue(newData.attributes);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 保存分类数据
|
||||||
|
const handleSave = async (record) => {
|
||||||
|
try {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
if (record.isNew) {
|
||||||
|
await supabaseService.insert('resources', {
|
||||||
|
type: 'categories',
|
||||||
|
attributes: {
|
||||||
|
name: values.name,
|
||||||
|
template_type: values.template_type
|
||||||
|
},
|
||||||
|
schema_version: 1
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await supabaseService.update('resources',
|
||||||
|
{ id: record.id },
|
||||||
|
{
|
||||||
|
attributes: {
|
||||||
|
name: values.name,
|
||||||
|
template_type: values.template_type
|
||||||
|
},
|
||||||
|
updated_at: new Date().toISOString()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
message.success('保存成功');
|
||||||
|
setEditingKey('');
|
||||||
|
fetchCategories();
|
||||||
|
} catch (error) {
|
||||||
|
message.error('保存失败');
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除分类
|
||||||
|
const handleDelete = async (record) => {
|
||||||
|
try {
|
||||||
|
await supabaseService.delete('resources', { id: record.id });
|
||||||
|
message.success('删除成功');
|
||||||
|
fetchCategories();
|
||||||
|
} catch (error) {
|
||||||
|
message.error('删除失败');
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '分类名称',
|
||||||
|
dataIndex: ['attributes', 'name'],
|
||||||
|
render: (text, record) => {
|
||||||
|
const isEditing = record.id === editingKey;
|
||||||
|
return isEditing ? (
|
||||||
|
<Form.Item
|
||||||
|
name="name"
|
||||||
|
style={{ margin: 0 }}
|
||||||
|
rules={[{ required: true, message: '请输入分类名称!' }]}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder="请输入分类名称"
|
||||||
|
className="rounded-md"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
) : (
|
||||||
|
<span className="text-gray-700">{text}</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '模板类型',
|
||||||
|
dataIndex: ['attributes', 'template_type'],
|
||||||
|
render: (text, record) => {
|
||||||
|
const isEditing = record.id === editingKey;
|
||||||
|
return isEditing ? (
|
||||||
|
<Form.Item
|
||||||
|
name="template_type"
|
||||||
|
style={{ margin: 0 }}
|
||||||
|
rules={[{ required: true, message: '请选择模板类型!' }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
options={typeList}
|
||||||
|
placeholder="请选择模板类型"
|
||||||
|
className="w-full rounded-md"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
) : (
|
||||||
|
<span className="text-gray-700 px-2 py-1 bg-gray-100 rounded-full text-sm">
|
||||||
|
{typeList.find(t => t.value === text)?.label || text}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
render: (_, record) => {
|
||||||
|
const isEditing = record.id === editingKey;
|
||||||
|
return isEditing ? (
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
className="text-green-600 hover:text-green-500 font-medium"
|
||||||
|
onClick={() => handleSave(record)}
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
className="text-gray-600 hover:text-gray-500 font-medium"
|
||||||
|
onClick={() => {
|
||||||
|
setEditingKey('');
|
||||||
|
if (record.isNew) {
|
||||||
|
setData(data.filter(item => item.id !== record.id));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
) : (
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
className="text-blue-600 hover:text-blue-500 font-medium"
|
||||||
|
disabled={editingKey !== ''}
|
||||||
|
onClick={() => {
|
||||||
|
setEditingKey(record.id);
|
||||||
|
form.setFieldsValue({
|
||||||
|
name: record.attributes.name,
|
||||||
|
template_type: record.attributes.template_type
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title="确认删除"
|
||||||
|
description="确定要删除这个分类吗?"
|
||||||
|
onConfirm={() => handleDelete(record)}
|
||||||
|
okText="确定"
|
||||||
|
cancelText="取消"
|
||||||
|
okButtonProps={{
|
||||||
|
className: "bg-red-500 hover:bg-red-600 border-red-500"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
className="text-red-600 hover:text-red-500 font-medium"
|
||||||
|
disabled={editingKey !== ''}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-6 bg-gray-50">
|
||||||
|
<div className="bg-white rounded-lg shadow-sm mb-6 p-4">
|
||||||
|
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => handleAdd(activeType)}
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
className="bg-blue-600 hover:bg-blue-700 border-0 shadow-sm h-10"
|
||||||
|
>
|
||||||
|
新增分类
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Segmented
|
||||||
|
value={filterType}
|
||||||
|
onChange={(value) => {
|
||||||
|
setFilterType(value);
|
||||||
|
fetchCategories(activeType, value);
|
||||||
|
}}
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
label: '全部',
|
||||||
|
value: 'all',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '仅当前类型',
|
||||||
|
value: 'current',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '仅通用类型',
|
||||||
|
value: 'common',
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
className="bg-gray-50 p-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white rounded-lg shadow-sm">
|
||||||
|
<Form form={form}>
|
||||||
|
<Table
|
||||||
|
scroll={{ x: true }}
|
||||||
|
columns={columns}
|
||||||
|
dataSource={data}
|
||||||
|
rowKey="id"
|
||||||
|
loading={loading}
|
||||||
|
pagination={{
|
||||||
|
pageSize: 10,
|
||||||
|
showTotal: (total) => `共 ${total} 条`,
|
||||||
|
className: "px-4"
|
||||||
|
}}
|
||||||
|
className="rounded-lg"
|
||||||
|
/>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Classify;
|
||||||
69
src/pages/company/service/itemsManange/index.jsx
Normal file
69
src/pages/company/service/itemsManange/index.jsx
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import React, { useState, useMemo } from 'react';
|
||||||
|
import { Tabs, Card, Button, Table, Modal, Form, Input, Space, message, InputNumber, Select } from 'antd';
|
||||||
|
import { FileTextOutlined, ProjectOutlined, CheckSquareOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
|
import { supabase } from '@/config/supabase';
|
||||||
|
import Classify from './classify'
|
||||||
|
import Sections from './sections'
|
||||||
|
import Unit from './unit'
|
||||||
|
import {TEMPLATE_TYPES as typelist}from '@/constants/typeEnum'
|
||||||
|
|
||||||
|
const ResourceManagement = () => {
|
||||||
|
const [activeType, setActiveType] = useState('quotation');
|
||||||
|
|
||||||
|
|
||||||
|
// 模板类型配置
|
||||||
|
const TEMPLATE_TYPES = [
|
||||||
|
{
|
||||||
|
key: 'quotation',
|
||||||
|
label: '报价单模板',
|
||||||
|
icon: <FileTextOutlined className="text-blue-500" />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'project',
|
||||||
|
label: '专案模板',
|
||||||
|
icon: <ProjectOutlined className="text-green-500" />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'task',
|
||||||
|
label: '任务模板',
|
||||||
|
icon: <CheckSquareOutlined className="text-purple-500" />,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
const filterOption =useMemo(()=>{
|
||||||
|
return typelist.filter(v=>v.value===activeType||v.value==='common')
|
||||||
|
},[activeType])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-6 bg-gray-50 min-h-screen">
|
||||||
|
<Tabs
|
||||||
|
activeKey={activeType}
|
||||||
|
onChange={setActiveType}
|
||||||
|
type="card"
|
||||||
|
className="bg-white rounded-lg shadow-sm"
|
||||||
|
items={TEMPLATE_TYPES.map(type => ({
|
||||||
|
key: type.key,
|
||||||
|
label: (
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
{type.icon}
|
||||||
|
<span>{type.label}</span>
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
children: (
|
||||||
|
<div className="p-6">
|
||||||
|
<Card>
|
||||||
|
<Classify typeList={filterOption} activeType={activeType} setActiveType={setActiveType} />
|
||||||
|
<Unit typeList={filterOption} activeType={activeType} />
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResourceManagement;
|
||||||
@@ -146,7 +146,6 @@ const SectionManagement = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Drawer 中表格的列定义
|
|
||||||
const drawerColumns = [
|
const drawerColumns = [
|
||||||
{
|
{
|
||||||
title: '项目名称',
|
title: '项目名称',
|
||||||
285
src/pages/company/service/itemsManange/unit/index.jsx
Normal file
285
src/pages/company/service/itemsManange/unit/index.jsx
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Table, Button, Form, Input, Space, message, Popconfirm, Select, Segmented } from 'antd';
|
||||||
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
|
import { supabaseService } from '@/hooks/supabaseService';
|
||||||
|
|
||||||
|
const UnitManagement = ({ activeType, typeList }) => {
|
||||||
|
const [data, setData] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [editingKey, setEditingKey] = useState('');
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [filterType, setFilterType] = useState('all');
|
||||||
|
|
||||||
|
const fetchUnits = async (type = activeType, filterTypeValue = filterType) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
let filterCondition;
|
||||||
|
|
||||||
|
switch (filterTypeValue) {
|
||||||
|
case 'common':
|
||||||
|
filterCondition = { eq: 'common' };
|
||||||
|
break;
|
||||||
|
case 'current':
|
||||||
|
filterCondition = { eq: type };
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
filterCondition = { in: `(${type},common)` };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: units } = await supabaseService.select('resources', {
|
||||||
|
filter: {
|
||||||
|
'type': { eq: 'units' },
|
||||||
|
'attributes->>template_type': filterCondition
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
column: 'created_at',
|
||||||
|
ascending: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setData(units || []);
|
||||||
|
} catch (error) {
|
||||||
|
message.error('获取单位数据失败');
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchUnits(activeType, filterType);
|
||||||
|
}, [activeType]);
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
const newData = {
|
||||||
|
id: Date.now().toString(),
|
||||||
|
attributes: {
|
||||||
|
name: '',
|
||||||
|
template_type: activeType
|
||||||
|
},
|
||||||
|
isNew: true
|
||||||
|
};
|
||||||
|
setData([newData, ...data]);
|
||||||
|
setEditingKey(newData.id);
|
||||||
|
form.setFieldsValue(newData.attributes);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = async (record) => {
|
||||||
|
try {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
if (record.isNew) {
|
||||||
|
await supabaseService.insert('resources', {
|
||||||
|
type: 'units',
|
||||||
|
attributes: {
|
||||||
|
name: values.name,
|
||||||
|
template_type: values.template_type
|
||||||
|
},
|
||||||
|
schema_version: 1
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await supabaseService.update('resources',
|
||||||
|
{ id: record.id },
|
||||||
|
{
|
||||||
|
attributes: {
|
||||||
|
name: values.name,
|
||||||
|
template_type: values.template_type
|
||||||
|
},
|
||||||
|
updated_at: new Date().toISOString()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
message.success('保存成功');
|
||||||
|
setEditingKey('');
|
||||||
|
fetchUnits();
|
||||||
|
} catch (error) {
|
||||||
|
message.error('保存失败');
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (record) => {
|
||||||
|
try {
|
||||||
|
await supabaseService.delete('resources', { id: record.id });
|
||||||
|
message.success('删除成功');
|
||||||
|
fetchUnits();
|
||||||
|
} catch (error) {
|
||||||
|
message.error('删除失败');
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '单位名称',
|
||||||
|
dataIndex: ['attributes', 'name'],
|
||||||
|
render: (text, record) => {
|
||||||
|
const isEditing = record.id === editingKey;
|
||||||
|
return isEditing ? (
|
||||||
|
<Form.Item
|
||||||
|
name="name"
|
||||||
|
style={{ margin: 0 }}
|
||||||
|
rules={[{ required: true, message: '请输入单位名称!' }]}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder="请输入单位名称"
|
||||||
|
className="rounded-md"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
) : (
|
||||||
|
<span className="text-gray-700">{text}</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '模板类型',
|
||||||
|
dataIndex: ['attributes', 'template_type'],
|
||||||
|
render: (text, record) => {
|
||||||
|
const isEditing = record.id === editingKey;
|
||||||
|
return isEditing ? (
|
||||||
|
<Form.Item
|
||||||
|
name="template_type"
|
||||||
|
style={{ margin: 0 }}
|
||||||
|
rules={[{ required: true, message: '请选择模板类型!' }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
options={typeList}
|
||||||
|
placeholder="请选择模板类型"
|
||||||
|
className="w-full rounded-md"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
) : (
|
||||||
|
<span className="text-gray-700 px-2 py-1 bg-gray-100 rounded-full text-sm">
|
||||||
|
{typeList.find(t => t.value === text)?.label || text}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
render: (_, record) => {
|
||||||
|
const isEditing = record.id === editingKey;
|
||||||
|
return isEditing ? (
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
className="text-green-600 hover:text-green-500 font-medium"
|
||||||
|
onClick={() => handleSave(record)}
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
className="text-gray-600 hover:text-gray-500 font-medium"
|
||||||
|
onClick={() => {
|
||||||
|
setEditingKey('');
|
||||||
|
if (record.isNew) {
|
||||||
|
setData(data.filter(item => item.id !== record.id));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
) : (
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
className="text-blue-600 hover:text-blue-500 font-medium"
|
||||||
|
disabled={editingKey !== ''}
|
||||||
|
onClick={() => {
|
||||||
|
setEditingKey(record.id);
|
||||||
|
form.setFieldsValue({
|
||||||
|
name: record.attributes.name,
|
||||||
|
template_type: record.attributes.template_type
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title="确认删除"
|
||||||
|
description="确定要删除这个单位吗?"
|
||||||
|
onConfirm={() => handleDelete(record)}
|
||||||
|
okText="确定"
|
||||||
|
cancelText="取消"
|
||||||
|
okButtonProps={{
|
||||||
|
className: "bg-red-500 hover:bg-red-600 border-red-500"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
className="text-red-600 hover:text-red-500 font-medium"
|
||||||
|
disabled={editingKey !== ''}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-6 bg-gray-50">
|
||||||
|
<div className="bg-white rounded-lg shadow-sm mb-6 p-4">
|
||||||
|
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={handleAdd}
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
className="bg-blue-600 hover:bg-blue-700 border-0 shadow-sm h-10"
|
||||||
|
>
|
||||||
|
新增单位
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Segmented
|
||||||
|
value={filterType}
|
||||||
|
onChange={(value) => {
|
||||||
|
setFilterType(value);
|
||||||
|
fetchUnits(activeType, value);
|
||||||
|
}}
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
label: '全部',
|
||||||
|
value: 'all',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '仅当前类型',
|
||||||
|
value: 'current',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '仅通用类型',
|
||||||
|
value: 'common',
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
className="bg-gray-50 p-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white rounded-lg shadow-sm">
|
||||||
|
<Form form={form}>
|
||||||
|
<Table
|
||||||
|
scroll={{ x: true }}
|
||||||
|
columns={columns}
|
||||||
|
dataSource={data}
|
||||||
|
rowKey="id"
|
||||||
|
loading={loading}
|
||||||
|
pagination={{
|
||||||
|
pageSize: 10,
|
||||||
|
showTotal: (total) => `共 ${total} 条`,
|
||||||
|
className: "px-4"
|
||||||
|
}}
|
||||||
|
className="rounded-lg"
|
||||||
|
/>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UnitManagement;
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { Table, Button, Modal, Form, Input, Space, message } from 'antd';
|
|
||||||
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons';
|
|
||||||
|
|
||||||
const ServiceType = () => {
|
|
||||||
const [data, setData] = useState([]);
|
|
||||||
const [visible, setVisible] = useState(false);
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
const [editingId, setEditingId] = useState(null);
|
|
||||||
|
|
||||||
// 表格列定义
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: '服务类型',
|
|
||||||
dataIndex: 'serviceType',
|
|
||||||
key: 'serviceType',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '操作',
|
|
||||||
key: 'action',
|
|
||||||
render: (_, record) => (
|
|
||||||
<Space>
|
|
||||||
<Button type="link" onClick={() => handleEdit(record)}>
|
|
||||||
编辑
|
|
||||||
</Button>
|
|
||||||
<Button type="link" danger onClick={() => handleDelete(record.id)}>
|
|
||||||
删除
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// 处理添加/编辑表单提交
|
|
||||||
const handleSubmit = async (values) => {
|
|
||||||
try {
|
|
||||||
if (editingId) {
|
|
||||||
// 编辑操作
|
|
||||||
// await updateServiceType({ ...values, id: editingId });
|
|
||||||
message.success('更新成功');
|
|
||||||
} else {
|
|
||||||
// 添加操作
|
|
||||||
// await addServiceType(values);
|
|
||||||
message.success('添加成功');
|
|
||||||
}
|
|
||||||
setVisible(false);
|
|
||||||
form.resetFields();
|
|
||||||
// 重新加载数据
|
|
||||||
// loadData();
|
|
||||||
} catch (error) {
|
|
||||||
message.error('操作失败');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 编辑操作
|
|
||||||
const handleEdit = (record) => {
|
|
||||||
setEditingId(record.id);
|
|
||||||
form.setFieldsValue({
|
|
||||||
serviceType: record.serviceType,
|
|
||||||
details: record.details || [],
|
|
||||||
});
|
|
||||||
setVisible(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 删除操作
|
|
||||||
const handleDelete = async (id) => {
|
|
||||||
try {
|
|
||||||
// await deleteServiceType(id);
|
|
||||||
message.success('删除成功');
|
|
||||||
// 重新加载数据
|
|
||||||
// loadData();
|
|
||||||
} catch (error) {
|
|
||||||
message.error('删除失败');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
onClick={() => {
|
|
||||||
setEditingId(null);
|
|
||||||
form.resetFields();
|
|
||||||
setVisible(true);
|
|
||||||
}}
|
|
||||||
style={{ marginBottom: 16 }}
|
|
||||||
>
|
|
||||||
<PlusOutlined /> 新增服务类型
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Table scroll={{ x: true }} columns={columns} dataSource={data} rowKey="id" />
|
|
||||||
|
|
||||||
<Modal
|
|
||||||
title={editingId ? '编辑服务类型' : '新增服务类型'}
|
|
||||||
open={visible}
|
|
||||||
onOk={() => form.submit()}
|
|
||||||
onCancel={() => {
|
|
||||||
setVisible(false);
|
|
||||||
form.resetFields();
|
|
||||||
}}
|
|
||||||
width={800}
|
|
||||||
>
|
|
||||||
<Form form={form} onFinish={handleSubmit}>
|
|
||||||
<Form.Item
|
|
||||||
label="服务类型"
|
|
||||||
name="serviceType"
|
|
||||||
rules={[{ required: true, message: '请输入服务类型' }]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.List name="details">
|
|
||||||
{(fields, { add, remove }) => (
|
|
||||||
<>
|
|
||||||
{fields.map(({ key, name, ...restField }) => (
|
|
||||||
<Space key={key} align="baseline">
|
|
||||||
<Form.Item
|
|
||||||
{...restField}
|
|
||||||
name={[name, 'projectDetail']}
|
|
||||||
rules={[{ required: true, message: '请输入项目明细' }]}
|
|
||||||
>
|
|
||||||
<Input placeholder="项目明细" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
{...restField}
|
|
||||||
name={[name, 'description']}
|
|
||||||
rules={[{ required: true, message: '请输入描述' }]}
|
|
||||||
>
|
|
||||||
<Input placeholder="描述" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
{...restField}
|
|
||||||
name={[name, 'quantity']}
|
|
||||||
rules={[{ required: true, message: '请输入数量' }]}
|
|
||||||
>
|
|
||||||
<Input placeholder="数量" type="number" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
{...restField}
|
|
||||||
name={[name, 'unit']}
|
|
||||||
rules={[{ required: true, message: '请输入单位' }]}
|
|
||||||
>
|
|
||||||
<Input placeholder="单位" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
{...restField}
|
|
||||||
name={[name, 'price']}
|
|
||||||
rules={[{ required: true, message: '请输入单价' }]}
|
|
||||||
>
|
|
||||||
<Input placeholder="单价" type="number" />
|
|
||||||
</Form.Item>
|
|
||||||
<DeleteOutlined onClick={() => remove(name)} />
|
|
||||||
</Space>
|
|
||||||
))}
|
|
||||||
<Form.Item>
|
|
||||||
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
|
|
||||||
添加项目明细
|
|
||||||
</Button>
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Form.List>
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ServiceType;
|
|
||||||
@@ -1,261 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { Table, Button, Drawer, Form, Input, Space, message, Popconfirm } from 'antd';
|
|
||||||
import { PlusOutlined, DeleteOutlined, SaveOutlined, CloseOutlined } from '@ant-design/icons';
|
|
||||||
import { supabase } from '@/config/supabase';
|
|
||||||
|
|
||||||
const UnitManagement = () => {
|
|
||||||
const [data, setData] = useState([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [drawerVisible, setDrawerVisible] = useState(false);
|
|
||||||
const [editingKey, setEditingKey] = useState('');
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
|
|
||||||
// 获取单位数据
|
|
||||||
const fetchUnits = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const { data: units, error } = await supabase
|
|
||||||
.from('resources')
|
|
||||||
.select('*')
|
|
||||||
.eq('type', 'units')
|
|
||||||
.order('created_at', { ascending: false });
|
|
||||||
|
|
||||||
if (error) throw error;
|
|
||||||
setData(units || []);
|
|
||||||
} catch (error) {
|
|
||||||
message.error('获取单位数据失败');
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (drawerVisible) {
|
|
||||||
fetchUnits();
|
|
||||||
}
|
|
||||||
}, [drawerVisible]);
|
|
||||||
|
|
||||||
// 新增<E696B0><E5A29E><EFBFBD>位
|
|
||||||
const handleAdd = () => {
|
|
||||||
const newData = {
|
|
||||||
id: Date.now().toString(),
|
|
||||||
attributes: { name: '' },
|
|
||||||
isNew: true
|
|
||||||
};
|
|
||||||
setData([newData, ...data]);
|
|
||||||
setEditingKey(newData.id);
|
|
||||||
form.setFieldsValue(newData.attributes);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 保存单位数据
|
|
||||||
const handleSave = async (record) => {
|
|
||||||
try {
|
|
||||||
const values = await form.validateFields();
|
|
||||||
if (record.isNew) {
|
|
||||||
// 新增
|
|
||||||
const { error } = await supabase
|
|
||||||
.from('resources')
|
|
||||||
.insert([{
|
|
||||||
type: 'units',
|
|
||||||
attributes: {
|
|
||||||
name: values.name
|
|
||||||
},
|
|
||||||
schema_version: 1
|
|
||||||
}]);
|
|
||||||
|
|
||||||
if (error) throw error;
|
|
||||||
} else {
|
|
||||||
// 更新
|
|
||||||
const { error } = await supabase
|
|
||||||
.from('resources')
|
|
||||||
.update({
|
|
||||||
attributes: {
|
|
||||||
name: values.name
|
|
||||||
},
|
|
||||||
updated_at: new Date().toISOString()
|
|
||||||
})
|
|
||||||
.eq('id', record.id);
|
|
||||||
|
|
||||||
if (error) throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
message.success('保存成功');
|
|
||||||
setEditingKey('');
|
|
||||||
fetchUnits();
|
|
||||||
} catch (error) {
|
|
||||||
message.error('保存失败');
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 删除单位
|
|
||||||
const handleDelete = async (id) => {
|
|
||||||
try {
|
|
||||||
const { error } = await supabase
|
|
||||||
.from('resources')
|
|
||||||
.delete()
|
|
||||||
.eq('id', id);
|
|
||||||
|
|
||||||
if (error) throw error;
|
|
||||||
message.success('删除成功');
|
|
||||||
fetchUnits();
|
|
||||||
} catch (error) {
|
|
||||||
message.error('删除失败');
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 修改取消编辑的处理方法
|
|
||||||
const handleCancel = (record) => {
|
|
||||||
setEditingKey('');
|
|
||||||
if (record.isNew) {
|
|
||||||
// 如果是新增的记录,直接从数据中移除
|
|
||||||
setData(data.filter(item => item.id !== record.id));
|
|
||||||
}
|
|
||||||
form.resetFields();
|
|
||||||
};
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: '单位名称',
|
|
||||||
dataIndex: ['attributes', 'name'],
|
|
||||||
render: (text, record) => {
|
|
||||||
const isEditing = record.id === editingKey;
|
|
||||||
return isEditing ? (
|
|
||||||
<Form.Item
|
|
||||||
name="name"
|
|
||||||
rules={[{ required: true, message: '请输入单位名称!' }]}
|
|
||||||
style={{ margin: 0 }}
|
|
||||||
initialValue={text}
|
|
||||||
>
|
|
||||||
<Input placeholder="请输入单位名称" />
|
|
||||||
</Form.Item>
|
|
||||||
) : (
|
|
||||||
text
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '操作',
|
|
||||||
render: (_, record) => {
|
|
||||||
const isEditing = record.id === editingKey;
|
|
||||||
return isEditing ? (
|
|
||||||
<Space>
|
|
||||||
<Button
|
|
||||||
type="link"
|
|
||||||
icon={<SaveOutlined />}
|
|
||||||
onClick={() => handleSave(record)}
|
|
||||||
className="text-green-600 hover:text-green-500"
|
|
||||||
>
|
|
||||||
保存
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="link"
|
|
||||||
icon={<CloseOutlined />}
|
|
||||||
onClick={() => handleCancel(record)}
|
|
||||||
className="text-gray-600 hover:text-gray-500"
|
|
||||||
>
|
|
||||||
取消
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
) : (
|
|
||||||
<Space>
|
|
||||||
<Button
|
|
||||||
type="link"
|
|
||||||
disabled={editingKey !== ''}
|
|
||||||
onClick={() => {
|
|
||||||
setEditingKey(record.id);
|
|
||||||
form.setFieldsValue(record.attributes);
|
|
||||||
}}
|
|
||||||
className="text-blue-600 hover:text-blue-500 disabled:text-gray-400"
|
|
||||||
>
|
|
||||||
编辑
|
|
||||||
</Button>
|
|
||||||
<Popconfirm
|
|
||||||
title="确定要删除吗?"
|
|
||||||
onConfirm={() => handleDelete(record.id)}
|
|
||||||
okButtonProps={{
|
|
||||||
className: "bg-red-500 hover:bg-red-600 border-red-500"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type="link"
|
|
||||||
danger
|
|
||||||
disabled={editingKey !== ''}
|
|
||||||
icon={<DeleteOutlined />}
|
|
||||||
className="text-red-600 hover:text-red-500 disabled:text-gray-400"
|
|
||||||
/>
|
|
||||||
</Popconfirm>
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
onClick={() => setDrawerVisible(true)}
|
|
||||||
className="flex items-center"
|
|
||||||
>
|
|
||||||
单位管理
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Drawer
|
|
||||||
title={
|
|
||||||
<span className="text-lg font-medium text-gray-800 dark:text-gray-200">
|
|
||||||
单位管理
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
placement="right"
|
|
||||||
width={600}
|
|
||||||
onClose={() => {
|
|
||||||
setDrawerVisible(false);
|
|
||||||
setEditingKey('');
|
|
||||||
form.resetFields();
|
|
||||||
}}
|
|
||||||
open={drawerVisible}
|
|
||||||
className="dark:bg-gray-800"
|
|
||||||
>
|
|
||||||
<div className="flex flex-col h-full">
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
onClick={handleAdd}
|
|
||||||
disabled={editingKey !== ''}
|
|
||||||
className="mb-4 w-32 flex items-center justify-center"
|
|
||||||
>
|
|
||||||
新增单位
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Form form={form} className="flex-1">
|
|
||||||
<Table
|
|
||||||
scroll={{ x: true }}
|
|
||||||
dataSource={data}
|
|
||||||
columns={columns}
|
|
||||||
rowKey="id"
|
|
||||||
loading={loading}
|
|
||||||
pagination={{
|
|
||||||
pageSize: 10,
|
|
||||||
className: "dark:text-gray-300"
|
|
||||||
}}
|
|
||||||
className="bg-white dark:bg-gray-800 rounded-lg shadow-sm"
|
|
||||||
rowClassName={(record) =>
|
|
||||||
`transition-colors ${
|
|
||||||
record.id === editingKey
|
|
||||||
? 'bg-blue-50 dark:bg-blue-900/20'
|
|
||||||
: 'hover:bg-gray-50 dark:hover:bg-gray-700/50'
|
|
||||||
}`
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
</Drawer>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default UnitManagement;
|
|
||||||
@@ -15,7 +15,7 @@ const NotFound = () => {
|
|||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
key="home"
|
key="home"
|
||||||
onClick={() => navigate('/company/serviceTeamplate')}
|
onClick={() => navigate('/company/serviceTemplate')}
|
||||||
>
|
>
|
||||||
返回首页
|
返回首页
|
||||||
</Button>,
|
</Button>,
|
||||||
|
|||||||
@@ -599,9 +599,7 @@ const StorageManager = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen bg-gray-50">
|
<div className="flex h-screen bg-gray-50">
|
||||||
{/* 左侧文件列表 */}
|
|
||||||
<div className="w-1/3 p-4 flex flex-col space-y-4">
|
<div className="w-1/3 p-4 flex flex-col space-y-4">
|
||||||
{/* 上传区域 */}
|
|
||||||
<div className="bg-white rounded-lg shadow-sm overflow-hidden">
|
<div className="bg-white rounded-lg shadow-sm overflow-hidden">
|
||||||
<Dragger
|
<Dragger
|
||||||
{...uploadProps}
|
{...uploadProps}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ const AppRoutes = () => {
|
|||||||
<Route
|
<Route
|
||||||
path="/login"
|
path="/login"
|
||||||
element={
|
element={
|
||||||
user ? <Navigate to="/company/serviceTeamplate" replace /> : <Login />
|
user ? <Navigate to="/company/serviceTemplate" replace /> : <Login />
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -45,16 +45,15 @@ const companyRoutes = [
|
|||||||
icon: "file",
|
icon: "file",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "serviceTeamplate",
|
path: "serviceTemplate",
|
||||||
component: lazy(() => import("@/pages/company/service")),
|
component: lazy(() => import("@/pages/company/service")),
|
||||||
name: "服务管理",
|
name: "服务管理",
|
||||||
icon: "container",
|
icon: "container",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "serviceType",
|
path: "templateItemManage",
|
||||||
hidden: true,
|
component: lazy(() => import("@/pages/company/service/itemsManange")),
|
||||||
component: lazy(() => import("@/pages/company/service/serviceType")),
|
name: "资源类型",
|
||||||
name: "类型管理",
|
|
||||||
icon: "container",
|
icon: "container",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user