fix
This commit is contained in:
@@ -202,13 +202,13 @@ export default function ChatAIDrawer({ open, onClose, onExport }) {
|
|||||||
placeholder="请输入您的问题..."
|
placeholder="请输入您的问题..."
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="flex-1 rounded-lg border-gray-300 hover:border-blue-400 focus:border-blue-600 focus:shadow-blue-100"
|
className="flex-1 rounded-md "
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
htmlType="submit"
|
htmlType="submit"
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
className="rounded-lg bg-blue-600 hover:bg-blue-700"
|
className="rounded-md"
|
||||||
>
|
>
|
||||||
发送
|
发送
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ const Sidebar = ({ collapsed }) => {
|
|||||||
}, []);
|
}, []);
|
||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
|
const selectedKey = useMemo(() => {
|
||||||
|
return location.pathname;
|
||||||
|
}, [location.pathname]);
|
||||||
|
|
||||||
const handleMenuClick = ({ key }) => {
|
const handleMenuClick = ({ key }) => {
|
||||||
navigate(key);
|
navigate(key);
|
||||||
};
|
};
|
||||||
@@ -56,13 +60,17 @@ const Sidebar = ({ collapsed }) => {
|
|||||||
theme={isDarkMode ? "dark" : "light"}
|
theme={isDarkMode ? "dark" : "light"}
|
||||||
width={256}
|
width={256}
|
||||||
collapsedWidth={80}
|
collapsedWidth={80}
|
||||||
className={`app-sidebar ${collapsed ? "collapsed" : ""}`}
|
className={`app-sidebar ${collapsed ? "collapsed" : ""} overflow-auto`}
|
||||||
>
|
>
|
||||||
<Logo collapsed={collapsed} isDarkMode={isDarkMode} />
|
<Logo collapsed={collapsed} isDarkMode={isDarkMode} />
|
||||||
<Menu
|
<Menu
|
||||||
theme={isDarkMode ? "dark" : "light"}
|
theme={isDarkMode ? "dark" : "light"}
|
||||||
mode="inline"
|
mode="inline"
|
||||||
|
<<<<<<< HEAD
|
||||||
selectedKeys={getSelectedKeys}
|
selectedKeys={getSelectedKeys}
|
||||||
|
=======
|
||||||
|
selectedKeys={[selectedKey]}
|
||||||
|
>>>>>>> 8dccf8e554eefcfa832eca45be35be4dddad3cb8
|
||||||
defaultOpenKeys={defaultOpenKeys}
|
defaultOpenKeys={defaultOpenKeys}
|
||||||
items={menuClient}
|
items={menuClient}
|
||||||
onClick={handleMenuClick}
|
onClick={handleMenuClick}
|
||||||
|
|||||||
@@ -187,50 +187,44 @@ const SectionList = ({
|
|||||||
{availableSections.map((section) => (
|
{availableSections.map((section) => (
|
||||||
<div
|
<div
|
||||||
key={section.id}
|
key={section.id}
|
||||||
className="group relative bg-white rounded-lg shadow-sm border border-gray-200 hover:shadow-lg transition-all duration-300 cursor-pointer"
|
className="group relative bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 hover:shadow-lg transition-all duration-300 cursor-pointer"
|
||||||
onClick={() => handleUseTemplate(section, add)}
|
onClick={() => handleUseTemplate(section, add)}
|
||||||
>
|
>
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="text-center mb-4">
|
<div className="text-center mb-4">
|
||||||
<h3 className="text-lg font-medium group-hover:text-blue-500 transition-colors">
|
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 group-hover:text-blue-500 transition-colors">
|
||||||
{section.attributes.name}
|
{section.attributes.name}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="text-sm text-gray-500 mt-1">
|
<div className="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
||||||
{section.attributes.items?.length || 0} 个项目
|
{section.attributes.items?.length || 0} 个项目
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2 mt-4 border-t pt-4">
|
<div className="space-y-2 mt-4 border-t dark:border-gray-700 pt-4">
|
||||||
{(section.attributes.items || [])
|
{(section.attributes.items || []).slice(0, 3).map((item, index) => (
|
||||||
.slice(0, 3)
|
<div key={index} className="flex justify-between items-center">
|
||||||
.map((item, index) => (
|
<span className="text-sm text-gray-600 dark:text-gray-300 truncate flex-1">
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className="flex justify-between items-center"
|
|
||||||
>
|
|
||||||
<span className="text-sm text-gray-600 truncate flex-1">
|
|
||||||
{item.name}
|
{item.name}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-sm text-gray-500 ml-2">
|
<span className="text-sm text-gray-500 dark:text-gray-400 ml-2">
|
||||||
{formatExchangeRate(currentCurrency, item.price)}
|
{formatExchangeRate(currentCurrency, item.price)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{(section.attributes.items || []).length > 3 && (
|
{(section.attributes.items || []).length > 3 && (
|
||||||
<div className="text-sm text-gray-500 text-center">
|
<div className="text-sm text-gray-500 dark:text-gray-400 text-center">
|
||||||
还有 {section.attributes.items.length - 3} 个项目...
|
还有 {section.attributes.items.length - 3} 个项目...
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4 pt-4 border-t flex justify-between items-center">
|
<div className="mt-4 pt-4 border-t dark:border-gray-700 flex justify-between items-center">
|
||||||
<span className="text-sm text-gray-600">总金额</span>
|
<span className="text-sm text-gray-600 dark:text-gray-300">总金额</span>
|
||||||
<span className="text-base font-medium text-blue-500">
|
<span className="text-base font-medium text-blue-500 dark:text-blue-400">
|
||||||
{formatExchangeRate(
|
{formatExchangeRate(
|
||||||
currentCurrency,
|
currentCurrency,
|
||||||
(section.attributes.items || []).reduce(
|
(section.attributes.items || []).reduce(
|
||||||
(sum, item) =>
|
(sum, item) => sum + (item.price * (item.quantity || 1) || 0),
|
||||||
sum + (item.price * (item.quantity || 1) || 0),
|
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
@@ -241,12 +235,12 @@ const SectionList = ({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center mt-6">
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined />}
|
||||||
onClick={() => handleCreateCustom(add, fieldsLength)}
|
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"
|
className="bg-blue-600 dark:bg-blue-500 hover:bg-blue-700 dark:hover:bg-blue-600 border-0 shadow-md hover:shadow-lg transition-all duration-200 h-10 px-6 rounded-lg flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<span className="font-medium">自定义模块</span>
|
<span className="font-medium">自定义模块</span>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -256,7 +250,7 @@ const SectionList = ({
|
|||||||
<div className="flex flex-col items-center justify-center py-16 px-4">
|
<div className="flex flex-col items-center justify-center py-16 px-4">
|
||||||
<div className="w-48 h-48 mb-8">
|
<div className="w-48 h-48 mb-8">
|
||||||
<svg
|
<svg
|
||||||
className="w-full h-full text-gray-200"
|
className="w-full h-full text-gray-200 dark:text-gray-700"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
@@ -270,10 +264,10 @@ const SectionList = ({
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-medium text-gray-900 mb-2">
|
<h3 className="text-xl font-medium text-gray-900 dark:text-gray-100 mb-2">
|
||||||
暂无可用模板
|
暂无可用模板
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-500 text-center max-w-sm mb-8">
|
<p className="text-gray-500 dark:text-gray-400 text-center max-w-sm mb-8">
|
||||||
您可以选择创建一个自定义模块开始使用
|
您可以选择创建一个自定义模块开始使用
|
||||||
</p>
|
</p>
|
||||||
<Button
|
<Button
|
||||||
@@ -281,7 +275,7 @@ const SectionList = ({
|
|||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined />}
|
||||||
onClick={() => handleCreateCustom(add, fieldsLength)}
|
onClick={() => handleCreateCustom(add, fieldsLength)}
|
||||||
size="large"
|
size="large"
|
||||||
className="shadow-md hover:shadow-lg transition-shadow"
|
className="shadow-md hover:shadow-lg transition-shadow dark:bg-blue-500 dark:hover:bg-blue-600"
|
||||||
>
|
>
|
||||||
创建自定义模块
|
创建自定义模块
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ const SectionList = ({ form, isView, formValues, type }) => {
|
|||||||
{availableSections.map((section) => (
|
{availableSections.map((section) => (
|
||||||
<div
|
<div
|
||||||
key={section.id}
|
key={section.id}
|
||||||
className="group relative bg-white rounded-lg shadow-sm border border-gray-200 hover:shadow-lg transition-all duration-300 cursor-pointer"
|
className="group relative bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 hover:shadow-lg transition-all duration-300 cursor-pointer"
|
||||||
onClick={() => handleUseTemplate(section, add)}
|
onClick={() => handleUseTemplate(section, add)}
|
||||||
>
|
>
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
@@ -241,7 +241,7 @@ const SectionList = ({ form, isView, formValues, type }) => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center mt-6">
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined />}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from "react";
|
|||||||
import { Navigate, useLocation } from "react-router-dom";
|
import { Navigate, useLocation } from "react-router-dom";
|
||||||
import { useAuth } from "@/contexts/AuthContext";
|
import { useAuth } from "@/contexts/AuthContext";
|
||||||
|
|
||||||
const PUBLIC_PATHS = ['login', '404'];
|
const PUBLIC_PATHS = ['login', '404','home'];
|
||||||
export const ProtectedRoute = ({ children }) => {
|
export const ProtectedRoute = ({ children }) => {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -16,12 +16,11 @@ export const ProtectedRoute = ({ children }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!hasPermission) {
|
if (!hasPermission) {
|
||||||
return <Navigate to="/404" replace />;
|
return <Navigate to="/home" replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果用户未登录,重定向到登录页
|
|
||||||
return <Navigate to="/login" state={{ from: location }} replace />;
|
return <Navigate to="/login" state={{ from: location }} replace />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -215,20 +215,20 @@ export default function DifyChatDrawer({ open, onClose, onExport }) {
|
|||||||
<div ref={messagesEndRef} />
|
<div ref={messagesEndRef} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t dark:border-gray-700 bg-white dark:bg-gray-800 p-4">
|
<div className="border-t dark:border-gray-700 p-4">
|
||||||
<form onSubmit={handleSendMessage} className="flex gap-2">
|
<form onSubmit={handleSendMessage} className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
value={input}
|
value={input}
|
||||||
placeholder="请输入您的问题..."
|
placeholder="请输入您的问题..."
|
||||||
onChange={(e) => setInput(e.target.value)}
|
onChange={(e) => setInput(e.target.value)}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="flex-1 rounded-lg border-gray-300 dark:border-gray-600 hover:border-blue-400 dark:hover:border-blue-500 focus:border-blue-600 dark:focus:border-blue-500 focus:shadow-blue-100 dark:focus:shadow-blue-900 dark:bg-gray-700 dark:text-gray-200"
|
className="flex-1 rounded-md "
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
htmlType="submit"
|
htmlType="submit"
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
className="rounded-lg bg-blue-600 hover:bg-blue-700 dark:bg-blue-700 dark:hover:bg-blue-800"
|
className="rounded-md "
|
||||||
>
|
>
|
||||||
发送
|
发送
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ export const useResources = (initialPagination, initialSorter, type) => {
|
|||||||
|
|
||||||
setCurrentPagination(newPagination);
|
setCurrentPagination(newPagination);
|
||||||
setCurrentSorter(newSorter);
|
setCurrentSorter(newSorter);
|
||||||
console.log(params.searchQuery,'params.searchQuery');
|
|
||||||
|
|
||||||
const { data, total: newTotal } = await resourceService.getResources({
|
const { data, total: newTotal } = await resourceService.getResources({
|
||||||
page: newPagination.current,
|
page: newPagination.current,
|
||||||
pageSize: newPagination.pageSize,
|
pageSize: newPagination.pageSize,
|
||||||
@@ -77,8 +75,6 @@ export const useResources = (initialPagination, initialSorter, type) => {
|
|||||||
const deleteResource = async (id) => {
|
const deleteResource = async (id) => {
|
||||||
try {
|
try {
|
||||||
const data= await resourceService.deleteResource(id, type);
|
const data= await resourceService.deleteResource(id, type);
|
||||||
console.log(data,data.length,'data');
|
|
||||||
|
|
||||||
if(data?.length>0){
|
if(data?.length>0){
|
||||||
const newCurrent =
|
const newCurrent =
|
||||||
resources.length === 1 && currentPagination.current > 1
|
resources.length === 1 && currentPagination.current > 1
|
||||||
@@ -87,7 +83,7 @@ export const useResources = (initialPagination, initialSorter, type) => {
|
|||||||
await fetchResources({ current: newCurrent });
|
await fetchResources({ current: newCurrent });
|
||||||
message.success("删除成功");
|
message.success("删除成功");
|
||||||
}else{
|
}else{
|
||||||
throw new Error("no level");
|
throw new Error("暂无权限");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { supabase } from "@/config/supabase";
|
import { supabase } from "@/config/supabase";
|
||||||
|
import { message } from "antd";
|
||||||
class SupabaseService {
|
class SupabaseService {
|
||||||
async select(table, options = {}) {
|
async select(table, options = {}) {
|
||||||
try {
|
try {
|
||||||
@@ -60,12 +60,22 @@ class SupabaseService {
|
|||||||
.from(table)
|
.from(table)
|
||||||
.insert(data)
|
.insert(data)
|
||||||
.select();
|
.select();
|
||||||
|
if (error) {
|
||||||
|
if (error.code === '42501' || error.status === 403) {
|
||||||
|
throw new Error('暂无权限:您没有执行此操作的权限');
|
||||||
|
|
||||||
if (error) throw error;
|
}
|
||||||
|
throw new Error(error.message || '数据库操作失败');
|
||||||
|
}
|
||||||
|
if (result?.length === 0) {
|
||||||
|
throw new Error('暂无权限:您没有执行此操作的权限');
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error inserting into ${table}:`, error.message);
|
// 确保错误始终是 Error 对象
|
||||||
throw error;
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
console.error(`Insert error for table ${table}:`, errorMessage);
|
||||||
|
throw new Error(errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,22 +93,21 @@ class SupabaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { data, error } = await query.select();
|
const { data, error } = await query.select();
|
||||||
|
if(data.length===0) throw '暂无权限' ;
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error updating ${table}:`, error.message);
|
throw new Error('更新失败');
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通用 DELETE 请求
|
// 通用 DELETE 请求
|
||||||
async delete(table, match) {
|
async delete(table, match) {
|
||||||
try {
|
try {
|
||||||
const { error } = await supabase.from(table).delete().match(match);
|
const { data,error } = await supabase.from(table).delete().match(match).select();
|
||||||
|
if(data.length===0) throw '暂无权限';
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return true;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error deleting from ${table}:`, error.message);
|
console.error(`Error deleting from ${table}:`, error.message);
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -228,8 +228,7 @@ export default function ProjectDetail() {
|
|||||||
.select();
|
.select();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.error) throw result.error;
|
if (result.error||result.data.length===0) throw 'error';
|
||||||
|
|
||||||
message.success("保存成功");
|
message.success("保存成功");
|
||||||
navigate("/company/project");
|
navigate("/company/project");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -257,7 +256,7 @@ export default function ProjectDetail() {
|
|||||||
}, [id, templateId]);
|
}, [id, templateId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-gradient-to-b from-gray-50 to-white dark:from-gray-800 dark:to-gray-900/90 min-h-screen p-2">
|
<div className="bg-gradient-to-b min-h-screen p-2">
|
||||||
<Card
|
<Card
|
||||||
className="shadow-lg rounded-lg border-0"
|
className="shadow-lg rounded-lg border-0"
|
||||||
title={
|
title={
|
||||||
|
|||||||
@@ -268,7 +268,10 @@ const QuotationForm = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (result.error) throw result.error;
|
if (result.error) throw result.error;
|
||||||
|
if(result.length===0){
|
||||||
|
message.error('暂无权限');
|
||||||
|
return;
|
||||||
|
};
|
||||||
message.success("保存成功");
|
message.success("保存成功");
|
||||||
navigate("/company/quotation");
|
navigate("/company/quotation");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -359,7 +362,7 @@ const QuotationForm = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-gradient-to-b from-gray-50 to-white dark:from-gray-800 dark:to-gray-900/90 min-h-screen p-2">
|
<div className="bg-gradient-to-b min-h-screen p-2">
|
||||||
<Card
|
<Card
|
||||||
className="shadow-lg rounded-lg border-0"
|
className="shadow-lg rounded-lg border-0"
|
||||||
title={
|
title={
|
||||||
|
|||||||
@@ -405,7 +405,7 @@ useEffect(()=>{
|
|||||||
<Button
|
<Button
|
||||||
key="custom"
|
key="custom"
|
||||||
onClick={() => handleConfirm()}
|
onClick={() => handleConfirm()}
|
||||||
className=" gap-2 px-6 rounded-full hover:bg-gray-100"
|
className="gap-2 px-6 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 dark:text-gray-200"
|
||||||
>
|
>
|
||||||
<FileAddOutlined /> 自定义创建
|
<FileAddOutlined /> 自定义创建
|
||||||
</Button>,
|
</Button>,
|
||||||
@@ -414,7 +414,7 @@ useEffect(()=>{
|
|||||||
type="primary"
|
type="primary"
|
||||||
disabled={!selectedTemplateId}
|
disabled={!selectedTemplateId}
|
||||||
onClick={handleConfirm}
|
onClick={handleConfirm}
|
||||||
className="gap-2 px-6 rounded-full bg-blue-600 hover:bg-blue-700"
|
className="gap-2 px-6 rounded-full "
|
||||||
>
|
>
|
||||||
<AppstoreOutlined /> 使用选中模板
|
<AppstoreOutlined /> 使用选中模板
|
||||||
</Button>,
|
</Button>,
|
||||||
@@ -427,15 +427,15 @@ useEffect(()=>{
|
|||||||
<Spin size="large" />
|
<Spin size="large" />
|
||||||
</div>
|
</div>
|
||||||
) : templates.length === 0 ? (
|
) : templates.length === 0 ? (
|
||||||
<Empty description="暂无可用模板" />
|
<Empty description={<span className="dark:text-gray-400">暂无可用模板</span>} />
|
||||||
) : (
|
) : (
|
||||||
<div className="max-h-[600px] overflow-y-auto px-4 ">
|
<div className="max-h-[600px] overflow-y-auto px-4 ">
|
||||||
{getTemplatesByCategory().map((group, groupIndex) => (
|
{getTemplatesByCategory().map((group, groupIndex) => (
|
||||||
<div key={groupIndex} className="mb-10 last:mb-2">
|
<div key={groupIndex} className="mb-10 last:mb-2">
|
||||||
<div className="flex items-center gap-3 mb-6">
|
<div className="flex items-center gap-3 mb-6">
|
||||||
<h3 className="text-xl font-medium text-gray-900">
|
<h3 className="text-xl font-medium text-gray-900 dark:text-gray-100">
|
||||||
{group.name}
|
{group.name}
|
||||||
<span className="ml-2 text-sm text-gray-500">
|
<span className="ml-2 text-sm text-gray-500 dark:text-gray-400">
|
||||||
({group.templates.length})
|
({group.templates.length})
|
||||||
</span>
|
</span>
|
||||||
</h3>
|
</h3>
|
||||||
@@ -450,18 +450,18 @@ useEffect(()=>{
|
|||||||
relative p-6 rounded-xl cursor-pointer transition-all duration-200
|
relative p-6 rounded-xl cursor-pointer transition-all duration-200
|
||||||
${
|
${
|
||||||
selectedTemplateId === template.id
|
selectedTemplateId === template.id
|
||||||
? "ring-2 ring-blue-500 bg-blue-50/40"
|
? "ring-2 ring-blue-500 dark:ring-blue-400 bg-blue-50/40 dark:bg-blue-900/30"
|
||||||
: "hover:shadow-lg border border-gray-200 hover:border-blue-200"
|
: "hover:shadow-lg border border-gray-200 dark:border-gray-700 hover:border-blue-200 dark:hover:border-blue-700"
|
||||||
}
|
}
|
||||||
transform hover:-translate-y-1
|
transform hover:-translate-y-1 dark:bg-gray-800
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<div className="flex justify-between items-start gap-3 mb-4">
|
<div className="flex justify-between items-start gap-3 mb-4">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h4 className="text-lg font-medium text-gray-900 truncate">
|
<h4 className="text-lg font-medium text-gray-900 dark:text-gray-100 truncate">
|
||||||
{template.attributes.templateName}
|
{template.attributes.templateName}
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-sm text-gray-600 mt-2 line-clamp-2">
|
<p className="text-sm text-gray-600 dark:text-gray-400 mt-2 line-clamp-2">
|
||||||
{template.attributes.description || "暂无描述"}
|
{template.attributes.description || "暂无描述"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -475,16 +475,16 @@ useEffect(()=>{
|
|||||||
rounded-lg p-3 text-sm border
|
rounded-lg p-3 text-sm border
|
||||||
${
|
${
|
||||||
selectedTemplateId === template.id
|
selectedTemplateId === template.id
|
||||||
? "bg-white border-blue-200"
|
? "bg-white dark:bg-gray-700 border-blue-200 dark:border-blue-600"
|
||||||
: "bg-gray-50 border-gray-100"
|
: "bg-gray-50 dark:bg-gray-800 border-gray-100 dark:border-gray-700"
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="font-medium text-gray-700 truncate flex-1">
|
<span className="font-medium text-gray-700 dark:text-gray-200 truncate flex-1">
|
||||||
{section.sectionName}
|
{section.sectionName}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-gray-500 ml-2 text-xs px-2 py-0.5 bg-gray-100 rounded-full">
|
<span className="text-gray-500 dark:text-gray-400 ml-2 text-xs px-2 py-0.5 bg-gray-100 dark:bg-gray-600 rounded-full">
|
||||||
{section.items.length}项
|
{section.items.length}项
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -494,7 +494,7 @@ useEffect(()=>{
|
|||||||
|
|
||||||
{selectedTemplateId === template.id && (
|
{selectedTemplateId === template.id && (
|
||||||
<div className="absolute top-4 right-4">
|
<div className="absolute top-4 right-4">
|
||||||
<div className="w-6 h-6 bg-blue-600 rounded-full flex items-center justify-center shadow-md">
|
<div className="w-6 h-6 bg-blue-600 dark:bg-blue-500 rounded-full flex items-center justify-center shadow-md">
|
||||||
<svg
|
<svg
|
||||||
className="w-4 h-4 text-white"
|
className="w-4 h-4 text-white"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
|
|||||||
@@ -42,61 +42,70 @@ const QuotationPreview = () => {
|
|||||||
// PDF导出函数
|
// PDF导出函数
|
||||||
const exportPDF = async () => {
|
const exportPDF = async () => {
|
||||||
try {
|
try {
|
||||||
// 显示加载中
|
|
||||||
message.loading('正在生成PDF...', 0);
|
message.loading('正在生成PDF...', 0);
|
||||||
|
|
||||||
// 获取要导出的DOM元素
|
|
||||||
const element = document.getElementById('quotation-content');
|
const element = document.getElementById('quotation-content');
|
||||||
|
|
||||||
// 使用html2canvas将DOM转换为canvas
|
// A4 尺寸(单位:pt,1mm ≈ 2.83pt)
|
||||||
const canvas = await html2canvas(element, {
|
const a4Width = 595.28;
|
||||||
scale: 2, // 提高清晰度
|
const a4Height = 841.89;
|
||||||
useCORS: true, // 允许加载跨域图片
|
const margin = 40; // 页边距(pt)
|
||||||
logging: false, // 关闭日志
|
|
||||||
backgroundColor: '#ffffff' // 设置背景色
|
// 初始化 PDF
|
||||||
|
const pdf = new jsPDF({
|
||||||
|
unit: 'pt',
|
||||||
|
format: 'a4',
|
||||||
});
|
});
|
||||||
|
|
||||||
// 获取canvas的宽高
|
// 获取内容的总高度
|
||||||
const imgWidth = 210; // A4纸的宽度(mm)
|
let remainingContent = element.cloneNode(true);
|
||||||
const pageHeight = 297; // A4纸的高度(mm)
|
document.body.appendChild(remainingContent);
|
||||||
const imgHeight = canvas.height * imgWidth / canvas.width;
|
remainingContent.style.width = `${a4Width - 2 * margin}pt`;
|
||||||
const pdfWidth = imgWidth;
|
const totalHeight = remainingContent.offsetHeight;
|
||||||
const pdfHeight = imgHeight;
|
|
||||||
|
|
||||||
// 将canvas转为图片
|
// 计算需要的页数
|
||||||
const imgData = canvas.toDataURL('image/jpeg', 1.0);
|
const contentHeight = a4Height - 2 * margin;
|
||||||
|
const pageCount = Math.ceil(totalHeight / contentHeight);
|
||||||
|
|
||||||
// 初始化jsPDF
|
// 逐页处理内容
|
||||||
const pdf = new jsPDF('p', 'mm', 'a4');
|
for (let i = 0; i < pageCount; i++) {
|
||||||
|
if (i > 0) {
|
||||||
// 计算分页
|
|
||||||
let position = 0;
|
|
||||||
let currentPage = 1;
|
|
||||||
|
|
||||||
while (position < pdfHeight) {
|
|
||||||
// 添加图片到PDF
|
|
||||||
pdf.addImage(
|
|
||||||
imgData,
|
|
||||||
'JPEG',
|
|
||||||
0, // x坐标
|
|
||||||
position === 0 ? 0 : -position, // y坐标
|
|
||||||
pdfWidth, // 图片宽度
|
|
||||||
pdfHeight // 图片高度
|
|
||||||
);
|
|
||||||
|
|
||||||
position += pageHeight;
|
|
||||||
|
|
||||||
// 如果还有内容,添加新页面
|
|
||||||
if (position < pdfHeight) {
|
|
||||||
pdf.addPage();
|
pdf.addPage();
|
||||||
currentPage++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存PDF
|
// 创建当前页的容器
|
||||||
|
const pageDiv = document.createElement('div');
|
||||||
|
pageDiv.style.width = `${a4Width - 2 * margin}pt`;
|
||||||
|
pageDiv.style.height = `${contentHeight}pt`;
|
||||||
|
pageDiv.style.position = 'absolute';
|
||||||
|
pageDiv.style.top = '0';
|
||||||
|
pageDiv.style.left = '0';
|
||||||
|
pageDiv.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
// 克隆原始内容
|
||||||
|
const contentClone = element.cloneNode(true);
|
||||||
|
contentClone.style.transform = `translateY(-${i * contentHeight}pt)`;
|
||||||
|
pageDiv.appendChild(contentClone);
|
||||||
|
document.body.appendChild(pageDiv);
|
||||||
|
|
||||||
|
// 转换为图片并添加到 PDF
|
||||||
|
const canvas = await html2canvas(pageDiv, {
|
||||||
|
scale: 2,
|
||||||
|
useCORS: true,
|
||||||
|
logging: false,
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
});
|
||||||
|
|
||||||
|
const imgData = canvas.toDataURL('image/jpeg', 1.0);
|
||||||
|
pdf.addImage(imgData, 'JPEG', margin, margin, a4Width - 2 * margin, contentHeight);
|
||||||
|
|
||||||
|
// 清理临时元素
|
||||||
|
document.body.removeChild(pageDiv);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理克隆的元素
|
||||||
|
document.body.removeChild(remainingContent);
|
||||||
|
|
||||||
pdf.save(`${quotation.attributes.quataName || '报价单'}.pdf`);
|
pdf.save(`${quotation.attributes.quataName || '报价单'}.pdf`);
|
||||||
|
|
||||||
// 关闭加载提示
|
|
||||||
message.destroy();
|
message.destroy();
|
||||||
message.success('PDF导出成功!');
|
message.success('PDF导出成功!');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ const ProjectTemplate = ({ form, id, isView }) => {
|
|||||||
layout="vertical"
|
layout="vertical"
|
||||||
disabled={isView}
|
disabled={isView}
|
||||||
>
|
>
|
||||||
{/* 专案模板特有的字段和组件 */}
|
|
||||||
<Card>
|
<Card>
|
||||||
{/* 项目阶段、时间线等内容 */}
|
|
||||||
</Card>
|
</Card>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -103,21 +103,17 @@ const QuotationTemplate = ({ id, isView, onCancel,isEdit }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
// 更新
|
await supabaseService.update('resources', { id }, serviceData);
|
||||||
await supabaseService.update('resources',
|
|
||||||
{ id },
|
|
||||||
serviceData
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// 新增
|
|
||||||
await supabaseService.insert('resources', serviceData);
|
await supabaseService.insert('resources', serviceData);
|
||||||
}
|
}
|
||||||
|
|
||||||
message.success("保存成功");
|
message.success("保存成功");
|
||||||
onCancel();
|
onCancel();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
const errorMsg = error instanceof Error ? error.message : '保存失败';
|
||||||
console.error("保存失败:", error);
|
console.error("保存失败:", error);
|
||||||
message.error("保存失败");
|
message.error(errorMsg);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,15 +104,14 @@ const TaskTemplate = ({ id, isView, onCancel,isEdit }) => {
|
|||||||
serviceData
|
serviceData
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// 新增
|
|
||||||
await supabaseService.insert('resources', serviceData);
|
await supabaseService.insert('resources', serviceData);
|
||||||
}
|
}
|
||||||
|
|
||||||
message.success("保存成功");
|
message.success("保存成功");
|
||||||
onCancel();
|
onCancel();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
message.error('保存失败');
|
||||||
console.error("保存失败:", error);
|
console.error("保存失败:", error);
|
||||||
message.error("保存失败");
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import TaskTemplate from './components/TaskTemplate';
|
|||||||
|
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
|
|
||||||
// 模板类型配置
|
|
||||||
const TEMPLATE_CONFIG = {
|
const TEMPLATE_CONFIG = {
|
||||||
quotation: {
|
quotation: {
|
||||||
title: '报价单模板',
|
title: '报价单模板',
|
||||||
@@ -36,9 +35,8 @@ const ServiceForm = () => {
|
|||||||
if (!currentTemplate) {
|
if (!currentTemplate) {
|
||||||
return <div>无效的模板类型</div>;
|
return <div>无效的模板类型</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-gradient-to-b from-gray-50 to-white dark:from-gray-800 dark:to-gray-900/90 min-h-screen p-2" >
|
<div className="bg-gradient-to-b min-h-screen p-2" >
|
||||||
<Card
|
<Card
|
||||||
className="shadow-lg rounded-lg border-0"
|
className="shadow-lg rounded-lg border-0"
|
||||||
title={
|
title={
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ const ServicePage = () => {
|
|||||||
setData(services || []);
|
setData(services || []);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取服务模板失败:", error);
|
console.error("获取服务模板失败:", error);
|
||||||
message.error("获取服<EFBFBD><EFBFBD><EFBFBD>模板失败");
|
message.error("获取服务模板失败");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -127,7 +127,7 @@ const ServicePage = () => {
|
|||||||
fetchServices();
|
fetchServices();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("删除失败:", error);
|
console.error("删除失败:", error);
|
||||||
message.error("删除失败");
|
message.error(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -173,7 +173,7 @@ const ServicePage = () => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("保存失败:", error);
|
console.error("保存失败:", error);
|
||||||
message.error("保存失败");
|
message.error(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -263,7 +263,6 @@ const ServicePage = () => {
|
|||||||
title: "名称",
|
title: "名称",
|
||||||
dataIndex: "name",
|
dataIndex: "name",
|
||||||
key: "name",
|
key: "name",
|
||||||
width: "15%",
|
|
||||||
render: (text, item, index) => {
|
render: (text, item, index) => {
|
||||||
const isEditing =
|
const isEditing =
|
||||||
editingKey === `${record.id}-${section.key}-${index}`;
|
editingKey === `${record.id}-${section.key}-${index}`;
|
||||||
@@ -286,7 +285,6 @@ const ServicePage = () => {
|
|||||||
title: "单位",
|
title: "单位",
|
||||||
dataIndex: "unit",
|
dataIndex: "unit",
|
||||||
key: "unit",
|
key: "unit",
|
||||||
width: "10%",
|
|
||||||
render: (text, item, index) => {
|
render: (text, item, index) => {
|
||||||
const isEditing =
|
const isEditing =
|
||||||
editingKey === `${record.id}-${section.key}-${index}`;
|
editingKey === `${record.id}-${section.key}-${index}`;
|
||||||
@@ -320,7 +318,6 @@ const ServicePage = () => {
|
|||||||
title: "单价",
|
title: "单价",
|
||||||
dataIndex: "price",
|
dataIndex: "price",
|
||||||
key: "price",
|
key: "price",
|
||||||
width: "10%",
|
|
||||||
render: (text, item, index) => {
|
render: (text, item, index) => {
|
||||||
const isEditing =
|
const isEditing =
|
||||||
editingKey === `${record.id}-${section.key}-${index}`;
|
editingKey === `${record.id}-${section.key}-${index}`;
|
||||||
@@ -343,7 +340,6 @@ const ServicePage = () => {
|
|||||||
title: "数量",
|
title: "数量",
|
||||||
dataIndex: "quantity",
|
dataIndex: "quantity",
|
||||||
key: "quantity",
|
key: "quantity",
|
||||||
width: "10%",
|
|
||||||
render: (text, item, index) => {
|
render: (text, item, index) => {
|
||||||
const isEditing =
|
const isEditing =
|
||||||
editingKey === `${record.id}-${section.key}-${index}`;
|
editingKey === `${record.id}-${section.key}-${index}`;
|
||||||
@@ -714,7 +710,6 @@ const ServicePage = () => {
|
|||||||
title: "模板名称",
|
title: "模板名称",
|
||||||
dataIndex: ["attributes", "templateName"],
|
dataIndex: ["attributes", "templateName"],
|
||||||
key: "templateName",
|
key: "templateName",
|
||||||
className: "min-w-[200px]",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "模板类型",
|
title: "模板类型",
|
||||||
@@ -766,6 +761,7 @@ const ServicePage = () => {
|
|||||||
title: "创建时间",
|
title: "创建时间",
|
||||||
dataIndex: "created_at",
|
dataIndex: "created_at",
|
||||||
key: "created_at",
|
key: "created_at",
|
||||||
|
width: 220,
|
||||||
render: (text) => new Date(text).toLocaleString(),
|
render: (text) => new Date(text).toLocaleString(),
|
||||||
sorter: (a, b) => new Date(a.created_at) - new Date(b.created_at),
|
sorter: (a, b) => new Date(a.created_at) - new Date(b.created_at),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Form, Input, Select, Button, Space, Card, Typography } from 'antd';
|
import { Form, Input, Select, Button, Space, Card, Typography, message } from 'antd';
|
||||||
import { ArrowLeftOutlined, SaveOutlined } from '@ant-design/icons';
|
import { ArrowLeftOutlined, SaveOutlined } from '@ant-design/icons';
|
||||||
import { supabase } from '@/config/supabase';
|
import { supabase } from '@/config/supabase';
|
||||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||||
@@ -22,7 +22,6 @@ const SupplierForm = () => {
|
|||||||
.select('*')
|
.select('*')
|
||||||
.eq('id', id)
|
.eq('id', id)
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
@@ -74,10 +73,14 @@ const SupplierForm = () => {
|
|||||||
.insert([supplierData])
|
.insert([supplierData])
|
||||||
.select();
|
.select();
|
||||||
}
|
}
|
||||||
|
if(result.data.length===0){
|
||||||
|
message.error('暂无权限');
|
||||||
|
return;
|
||||||
|
};
|
||||||
if (result.error) throw result.error;
|
if (result.error) throw result.error;
|
||||||
navigate('/company/supplier');
|
navigate('/company/supplier');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
message.error('暂无权限');
|
||||||
console.error('保存失败:', error);
|
console.error('保存失败:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ export default function TaskForm() {
|
|||||||
}, [id, templateId]);
|
}, [id, templateId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-gradient-to-b from-gray-50 to-white dark:from-gray-800 dark:to-gray-900/90 min-h-screen p-2">
|
<div className="bg-gradient-to-b min-h-screen p-2">
|
||||||
<Card
|
<Card
|
||||||
className="shadow-lg rounded-lg border-0"
|
className="shadow-lg rounded-lg border-0"
|
||||||
title={
|
title={
|
||||||
|
|||||||
@@ -251,9 +251,7 @@ const TaskPage = () => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
const newAttributes = JSON.parse(JSON.stringify(record.attributes));
|
const newAttributes = JSON.parse(JSON.stringify(record.attributes));
|
||||||
|
|
||||||
// 修改任务名称,添加"副本"标识
|
|
||||||
newAttributes.taskName = `${newAttributes.taskName} (副本)`;
|
newAttributes.taskName = `${newAttributes.taskName} (副本)`;
|
||||||
|
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from("resources")
|
.from("resources")
|
||||||
.insert([
|
.insert([
|
||||||
@@ -261,10 +259,12 @@ const TaskPage = () => {
|
|||||||
type: "task",
|
type: "task",
|
||||||
attributes: newAttributes,
|
attributes: newAttributes,
|
||||||
},
|
},
|
||||||
]);
|
]).select()
|
||||||
|
if(!data||data?.length===0){
|
||||||
|
message.error('暂无权限');
|
||||||
|
return;
|
||||||
|
};
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
message.success("复制成功");
|
message.success("复制成功");
|
||||||
fetchTasks(); // 刷新列表
|
fetchTasks(); // 刷新列表
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -344,7 +344,7 @@ const TaskPage = () => {
|
|||||||
</Tag>
|
</Tag>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
className="h-full w-full overflow-auto dark:bg-gray-800 dark:border-gray-700"
|
className="h-full w-full overflow-auto "
|
||||||
extra={
|
extra={
|
||||||
<Space>
|
<Space>
|
||||||
<Select
|
<Select
|
||||||
@@ -423,7 +423,7 @@ const TaskPage = () => {
|
|||||||
type="primary"
|
type="primary"
|
||||||
disabled={!selectedTemplateId}
|
disabled={!selectedTemplateId}
|
||||||
onClick={handleConfirm}
|
onClick={handleConfirm}
|
||||||
className="gap-2 px-6 rounded-full bg-blue-600 hover:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-700"
|
className="gap-2 px-6 rounded-full "
|
||||||
>
|
>
|
||||||
<AppstoreOutlined /> 使用选中模板
|
<AppstoreOutlined /> 使用选中模板
|
||||||
</Button>,
|
</Button>,
|
||||||
|
|||||||
11
src/pages/home/index.jsx
Normal file
11
src/pages/home/index.jsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Home = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
首页
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Home;
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Outlet } from 'react-router-dom';
|
|
||||||
|
|
||||||
const MarketingCampaign = () => {
|
|
||||||
return <Outlet />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MarketingCampaign;
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Card, DatePicker } from 'antd';
|
|
||||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
|
||||||
|
|
||||||
const CampaignPerformance = () => {
|
|
||||||
const data = [
|
|
||||||
{ name: '1月', 点击率: 400, 转化率: 240 },
|
|
||||||
{ name: '2月', 点击率: 300, 转化率: 139 },
|
|
||||||
{ name: '3月', 点击率: 200, 转化率: 980 },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card title="活动成效分析">
|
|
||||||
<div className="mb-4">
|
|
||||||
<DatePicker.RangePicker />
|
|
||||||
</div>
|
|
||||||
<div style={{ width: '100%', height: 400 }}>
|
|
||||||
<ResponsiveContainer>
|
|
||||||
<LineChart data={data}>
|
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
|
||||||
<XAxis dataKey="name" />
|
|
||||||
<YAxis />
|
|
||||||
<Tooltip />
|
|
||||||
<Legend />
|
|
||||||
<Line type="monotone" dataKey="点击率" stroke="#8884d8" />
|
|
||||||
<Line type="monotone" dataKey="转化率" stroke="#82ca9d" />
|
|
||||||
</LineChart>
|
|
||||||
</ResponsiveContainer>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CampaignPerformance;
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Card, Table, Button, Tag } from 'antd';
|
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
|
||||||
|
|
||||||
const CampaignPlan = () => {
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: '计划名称',
|
|
||||||
dataIndex: 'name',
|
|
||||||
key: 'name',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '开始时间',
|
|
||||||
dataIndex: 'startTime',
|
|
||||||
key: 'startTime',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '结束时间',
|
|
||||||
dataIndex: 'endTime',
|
|
||||||
key: 'endTime',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '状态',
|
|
||||||
dataIndex: 'status',
|
|
||||||
key: 'status',
|
|
||||||
render: status => (
|
|
||||||
<Tag color={status === '进行中' ? 'green' : 'default'}>
|
|
||||||
{status}
|
|
||||||
</Tag>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
title="活动计划"
|
|
||||||
extra={
|
|
||||||
<Button type="primary" icon={<PlusOutlined />}>
|
|
||||||
新增计划
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Table columns={columns} dataSource={[]} rowKey="id" />
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CampaignPlan;
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Form, Input, DatePicker, Select } from 'antd';
|
|
||||||
import { STATUS } from '@/constants/status';
|
|
||||||
|
|
||||||
export const ProjectForm = ({ form }) => (
|
|
||||||
<Form form={form} layout="vertical">
|
|
||||||
<Form.Item
|
|
||||||
name="name"
|
|
||||||
label="专案名称"
|
|
||||||
rules={[{ required: true, message: '请输入专案名称' }]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
name="manager"
|
|
||||||
label="负责人"
|
|
||||||
rules={[{ required: true, message: '请选择负责人' }]}
|
|
||||||
>
|
|
||||||
<Select placeholder="请选择负责人" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
name="startDate"
|
|
||||||
label="开始日期"
|
|
||||||
rules={[{ required: true, message: '请选择开始日期' }]}
|
|
||||||
>
|
|
||||||
<DatePicker />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
name="status"
|
|
||||||
label="状态"
|
|
||||||
rules={[{ required: true, message: '请选择状态' }]}
|
|
||||||
>
|
|
||||||
<Select>
|
|
||||||
{Object.values(STATUS).map(status => (
|
|
||||||
<Select.Option key={status} value={status}>
|
|
||||||
{status}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import { Card, Modal, Form } from 'antd';
|
|
||||||
import { PageHeader } from '@/components/common/PageHeader';
|
|
||||||
import { BaseTable } from '@/components/common/BaseTable';
|
|
||||||
import { ProjectForm } from './components/ProjectForm';
|
|
||||||
import { useProjectData } from './hooks/useProjectData';
|
|
||||||
import { getStatusColumn, getDateColumn } from '@/utils/tableColumns';
|
|
||||||
|
|
||||||
const CampaignProject = () => {
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
const [visible, setVisible] = useState(false);
|
|
||||||
const { loading, data, pagination, loadData } = useProjectData();
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: '专案名称',
|
|
||||||
dataIndex: 'name',
|
|
||||||
key: 'name',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '负责人',
|
|
||||||
dataIndex: 'manager',
|
|
||||||
key: 'manager',
|
|
||||||
},
|
|
||||||
getDateColumn('开始日期', 'startDate'),
|
|
||||||
getStatusColumn(),
|
|
||||||
];
|
|
||||||
|
|
||||||
const handleAdd = () => {
|
|
||||||
setVisible(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
try {
|
|
||||||
const values = await form.validateFields();
|
|
||||||
console.log('Success:', values);
|
|
||||||
setVisible(false);
|
|
||||||
form.resetFields();
|
|
||||||
loadData();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<PageHeader
|
|
||||||
title="行销活动专案"
|
|
||||||
onAdd={handleAdd}
|
|
||||||
addButtonText="新增专案"
|
|
||||||
/>
|
|
||||||
<BaseTable
|
|
||||||
columns={columns}
|
|
||||||
dataSource={data}
|
|
||||||
loading={loading}
|
|
||||||
pagination={pagination}
|
|
||||||
onChange={loadData}
|
|
||||||
/>
|
|
||||||
<Modal
|
|
||||||
title="新增专案"
|
|
||||||
open={visible}
|
|
||||||
onOk={handleSubmit}
|
|
||||||
onCancel={() => setVisible(false)}
|
|
||||||
>
|
|
||||||
<ProjectForm form={form} />
|
|
||||||
</Modal>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CampaignProject;
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Outlet } from 'react-router-dom';
|
|
||||||
|
|
||||||
const Communication = () => {
|
|
||||||
return <Outlet />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Communication;
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Card, Timeline, Button } from 'antd';
|
|
||||||
import { ClockCircleOutlined } from '@ant-design/icons';
|
|
||||||
|
|
||||||
const CommunicationJourney = () => {
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
title="沟通历程"
|
|
||||||
extra={
|
|
||||||
<Button type="primary">导出记录</Button>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Timeline
|
|
||||||
mode="alternate"
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
children: '创建营销活动 2023-10-25 10:00:00',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
children: '发送邮件通知 2023-10-25 10:30:00',
|
|
||||||
color: 'green',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
dot: <ClockCircleOutlined style={{ fontSize: '16px' }} />,
|
|
||||||
children: '客户查看邮件 2023-10-25 11:00:00',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color: 'red',
|
|
||||||
children: '系统提醒跟进 2023-10-25 14:00:00',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
children: '完成跟进 2023-10-25 16:00:00',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CommunicationJourney;
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Card, Table, Button, Input } from 'antd';
|
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
|
||||||
|
|
||||||
const { Search } = Input;
|
|
||||||
|
|
||||||
const CommunicationList = () => {
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: '名单名称',
|
|
||||||
dataIndex: 'name',
|
|
||||||
key: 'name',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '联系人数量',
|
|
||||||
dataIndex: 'contactCount',
|
|
||||||
key: 'contactCount',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '创建日期',
|
|
||||||
dataIndex: 'createdAt',
|
|
||||||
key: 'createdAt',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '更新日期',
|
|
||||||
dataIndex: 'updatedAt',
|
|
||||||
key: 'updatedAt',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
title="名单管理"
|
|
||||||
extra={
|
|
||||||
<Button type="primary" icon={<PlusOutlined />}>
|
|
||||||
新增名单
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="mb-4">
|
|
||||||
<Search placeholder="搜索名单" allowClear enterButton />
|
|
||||||
</div>
|
|
||||||
<Table columns={columns} dataSource={[]} rowKey="id" />
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CommunicationList;
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Card, Tabs, Button } from 'antd';
|
|
||||||
import { SendOutlined } from '@ant-design/icons';
|
|
||||||
|
|
||||||
const CommunicationPreview = () => {
|
|
||||||
const items = [
|
|
||||||
{
|
|
||||||
key: '1',
|
|
||||||
label: '邮件预览',
|
|
||||||
children: (
|
|
||||||
<div className="p-4 border rounded">
|
|
||||||
<h2>邮件主题</h2>
|
|
||||||
<div className="mt-4">邮件内容预览区域</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: '2',
|
|
||||||
label: '短信预览',
|
|
||||||
children: (
|
|
||||||
<div className="p-4 border rounded">
|
|
||||||
<div className="mt-4">短信内容预览区域</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
title="消息预览"
|
|
||||||
extra={
|
|
||||||
<Button type="primary" icon={<SendOutlined />}>
|
|
||||||
发送测试
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Tabs items={items} />
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CommunicationPreview;
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Outlet } from 'react-router-dom';
|
|
||||||
|
|
||||||
const MarketingCenter = () => {
|
|
||||||
return <Outlet />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MarketingCenter;
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Card, Table, Button } from 'antd';
|
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
|
||||||
|
|
||||||
const TemplatePage = () => {
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: '模版名称',
|
|
||||||
dataIndex: 'name',
|
|
||||||
key: 'name',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '类型',
|
|
||||||
dataIndex: 'type',
|
|
||||||
key: 'type',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '创建日期',
|
|
||||||
dataIndex: 'createdAt',
|
|
||||||
key: 'createdAt',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '状态',
|
|
||||||
dataIndex: 'status',
|
|
||||||
key: 'status',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
title="模版管理"
|
|
||||||
extra={
|
|
||||||
<Button type="primary" icon={<PlusOutlined />}>
|
|
||||||
新增模版
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Table columns={columns} dataSource={[]} rowKey="id" />
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TemplatePage;
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Card, App, Table, Button, Modal, Form, Select, message, Input, Divider } from 'antd';
|
import { Card, App, Table, Button, Modal, Form, Select, message, Input, Divider, Popconfirm } from 'antd';
|
||||||
import { PlusOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons';
|
import { PlusOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons';
|
||||||
import { supabase } from '@/config/supabase';
|
import { supabase } from '@/config/supabase';
|
||||||
|
|
||||||
@@ -366,23 +366,31 @@ export default function PermissionManagement() {
|
|||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'operation',
|
key: 'operation',
|
||||||
|
fixed: 'right',
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<div className="space-x-2">
|
<div className="space-x-2">
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="link"
|
||||||
icon={<EditOutlined />}
|
icon={<EditOutlined />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setModalType('edit');
|
setModalType('edit');
|
||||||
form.setFieldsValue(record);
|
form.setFieldsValue(record);
|
||||||
setModalVisible(true);
|
setModalVisible(true);
|
||||||
}}
|
}}
|
||||||
/>
|
>编辑</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title="确认删除"
|
||||||
|
description="确定要删除这条权限记录吗?"
|
||||||
|
okText="确认"
|
||||||
|
cancelText="取消"
|
||||||
|
onConfirm={() => handleDelete(record.id)}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="link"
|
||||||
danger
|
danger
|
||||||
icon={<DeleteOutlined />}
|
icon={<DeleteOutlined />}
|
||||||
onClick={() => handleDelete(record.id)}
|
>删除</Button>
|
||||||
/>
|
</Popconfirm>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -515,7 +523,7 @@ export default function PermissionManagement() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<App>
|
<App>
|
||||||
<Card title="权限管理" bordered={false}>
|
<Card title="权限管理" >
|
||||||
<RoleHeader
|
<RoleHeader
|
||||||
onAdd={() => {
|
onAdd={() => {
|
||||||
setModalType('add');
|
setModalType('add');
|
||||||
@@ -531,12 +539,14 @@ export default function PermissionManagement() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
|
scroll={{ x: true}}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={permissions}
|
dataSource={permissions}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={pagination}
|
pagination={pagination}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
className='w-full'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ export const TeamTable = ({ tableLoading,pagination,dataSource, onTableChange,on
|
|||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
width: 180,
|
width: 180,
|
||||||
|
fixed: 'right',
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
const editable = isEditing(record);
|
const editable = isEditing(record);
|
||||||
return editable ? (
|
return editable ? (
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ const TeamManagement = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<App>
|
<App>
|
||||||
<Card title="团队管理" bordered={false}>
|
<Card title="团队管理" >
|
||||||
<TeamHeader onSearch={handleSearch} onAdd={handleAdd} />
|
<TeamHeader onSearch={handleSearch} onAdd={handleAdd} />
|
||||||
<TeamTable
|
<TeamTable
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
|||||||
@@ -68,8 +68,8 @@ const AppRoutes = () => {
|
|||||||
<Route
|
<Route
|
||||||
path="/login"
|
path="/login"
|
||||||
element={
|
element={
|
||||||
user?.id && user.menukeys?.includes('company/serviceTemplate') ? (
|
user?.id ? (
|
||||||
<Navigate to="/company/serviceTemplate" replace />
|
<Navigate to="/home" replace />
|
||||||
) : (
|
) : (
|
||||||
<Login />
|
<Login />
|
||||||
)
|
)
|
||||||
@@ -84,10 +84,9 @@ const AppRoutes = () => {
|
|||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{/* 默认重定向到服务管理页面 */}
|
|
||||||
<Route
|
<Route
|
||||||
index
|
index
|
||||||
element={<Navigate to="/company/serviceTemplate" replace />}
|
element={<Navigate to="/home" replace />}
|
||||||
/>
|
/>
|
||||||
{renderRoutes(allRoutes)}
|
{renderRoutes(allRoutes)}
|
||||||
<Route path="*" element={<NotFound />} />
|
<Route path="*" element={<NotFound />} />
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
import { lazy } from "react";
|
import { lazy } from "react";
|
||||||
|
|
||||||
// 所有可用的路由配置
|
|
||||||
export const allRoutes = [
|
export const allRoutes = [
|
||||||
|
{
|
||||||
|
path: "/home",
|
||||||
|
component: lazy(() => import("@/pages/home")),
|
||||||
|
name: "首页",
|
||||||
|
hidden: true,
|
||||||
|
key: "home",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "dashboard",
|
path: "dashboard",
|
||||||
component: lazy(() => import("@/pages/Dashboard")),
|
component: lazy(() => import("@/pages/Dashboard")),
|
||||||
@@ -76,13 +82,23 @@ export const allRoutes = [
|
|||||||
key: "company/quotaInfo",
|
key: "company/quotaInfo",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
<<<<<<< HEAD
|
||||||
path: "quotaInfo/preview/:id?", // 添加可选的 id 参数
|
path: "quotaInfo/preview/:id?", // 添加可选的 id 参数
|
||||||
|
=======
|
||||||
|
path: "quotaInfo/preview/:id?",
|
||||||
|
>>>>>>> 8dccf8e554eefcfa832eca45be35be4dddad3cb8
|
||||||
hidden: true,
|
hidden: true,
|
||||||
component: lazy(() => import("@/pages/company/quotation/view")),
|
component: lazy(() => import("@/pages/company/quotation/view")),
|
||||||
name: "报价单预览",
|
name: "报价单预览",
|
||||||
icon: "file",
|
icon: "file",
|
||||||
|
<<<<<<< HEAD
|
||||||
key:'company/quotaInfo/preview'
|
key:'company/quotaInfo/preview'
|
||||||
},
|
},
|
||||||
|
=======
|
||||||
|
key: "company/quotaInfo/preview",
|
||||||
|
},
|
||||||
|
|
||||||
|
>>>>>>> 8dccf8e554eefcfa832eca45be35be4dddad3cb8
|
||||||
{
|
{
|
||||||
path: "project",
|
path: "project",
|
||||||
component: lazy(() => import("@/pages/company/project")),
|
component: lazy(() => import("@/pages/company/project")),
|
||||||
@@ -119,7 +135,10 @@ export const allRoutes = [
|
|||||||
name: "服务模版详情",
|
name: "服务模版详情",
|
||||||
icon: "container",
|
icon: "container",
|
||||||
key: "company/serviceTemplateInfo",
|
key: "company/serviceTemplateInfo",
|
||||||
|
<<<<<<< HEAD
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> 8dccf8e554eefcfa832eca45be35be4dddad3cb8
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "supplier",
|
path: "supplier",
|
||||||
@@ -157,7 +176,11 @@ export const allRoutes = [
|
|||||||
component: lazy(() => import("@/pages/company/service/itemsManange")),
|
component: lazy(() => import("@/pages/company/service/itemsManange")),
|
||||||
name: "资源类型",
|
name: "资源类型",
|
||||||
icon: "container",
|
icon: "container",
|
||||||
|
<<<<<<< HEAD
|
||||||
key:'company/templateItemManage'
|
key:'company/templateItemManage'
|
||||||
|
=======
|
||||||
|
key: "company/templateItem",
|
||||||
|
>>>>>>> 8dccf8e554eefcfa832eca45be35be4dddad3cb8
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
|
// Light mode colors
|
||||||
--primary-color: #1677ff;
|
--primary-color: #1677ff;
|
||||||
--primary-gradient: linear-gradient(45deg, #1677ff, #36cff0);
|
--primary-gradient: linear-gradient(45deg, #1677ff, #36cff0);
|
||||||
--success-color: #52c41a;
|
--success-color: #52c41a;
|
||||||
@@ -12,6 +13,25 @@
|
|||||||
--error-color: #ff4d4f;
|
--error-color: #ff4d4f;
|
||||||
--font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
|
--font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
|
||||||
'Noto Sans', sans-serif;
|
'Noto Sans', sans-serif;
|
||||||
|
|
||||||
|
// Google-style scrollbar colors (Light mode)
|
||||||
|
--scrollbar-thumb: rgba(0, 0, 0, .2);
|
||||||
|
--scrollbar-thumb-hover: rgba(0, 0, 0, .4);
|
||||||
|
--scrollbar-track: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark {
|
||||||
|
// Dark mode colors
|
||||||
|
--primary-color: #4f9eff; // 更亮的蓝色适配暗色模式
|
||||||
|
--primary-gradient: linear-gradient(45deg, #4f9eff, #60deff);
|
||||||
|
--success-color: #6ede3b;
|
||||||
|
--warning-color: #ffc53d;
|
||||||
|
--error-color: #ff7875;
|
||||||
|
|
||||||
|
// Google-style scrollbar colors (Dark mode)
|
||||||
|
--scrollbar-thumb: rgba(255, 255, 255, .2);
|
||||||
|
--scrollbar-thumb-hover: rgba(255, 255, 255, .4);
|
||||||
|
--scrollbar-track: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -152,42 +172,36 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 滚动条基础样式 */
|
/* 自定义滚动条样式 */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
/* 垂直滚动条宽度 */
|
|
||||||
height: 8px;
|
height: 8px;
|
||||||
/* 水平滚动条高度 */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 亮色模式滚动条样式 */
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
@apply bg-gray-100;
|
background: var(--scrollbar-track);
|
||||||
/* 轨道背景色 */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
@apply bg-gray-300 rounded-full hover:bg-gray-400 transition-colors;
|
background-color: var(--scrollbar-thumb);
|
||||||
/* 滑块样式 */
|
border-radius: 4px;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 暗色模式滚动条样式 */
|
::-webkit-scrollbar-thumb:hover {
|
||||||
.dark {
|
background-color: var(--scrollbar-thumb-hover);
|
||||||
::-webkit-scrollbar-track {
|
|
||||||
@apply bg-gray-800;
|
|
||||||
/* 暗色模式轨道背景 */
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
@apply bg-gray-600 hover:bg-gray-500;
|
|
||||||
/* 暗色模式滑块样式 */
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Firefox 滚动条样式 */
|
/* Firefox 滚动条样式 */
|
||||||
* {
|
* {
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
scrollbar-color: theme('colors.gray.300') theme('colors.gray.100');
|
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
* {
|
||||||
|
scrollbar-color: rgba(255, 255, 255, 0.2) transparent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark * {
|
.dark * {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ const generateMenuItems = (routes, menuKeys = [], parentPath = "") => {
|
|||||||
const icon = route.icon && <ColorIcon icon={getAntIcon(route.icon)} />;
|
const icon = route.icon && <ColorIcon icon={getAntIcon(route.icon)} />;
|
||||||
|
|
||||||
const menuItem = {
|
const menuItem = {
|
||||||
key: route.key, // 使用 key 而不是 fullPath
|
key: fullPath,
|
||||||
icon,
|
icon,
|
||||||
label: route.name,
|
label: route.name,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user