diff --git a/Cunkebao/app/clientLayout.tsx b/Cunkebao/app/clientLayout.tsx index 0054c505..892511aa 100644 --- a/Cunkebao/app/clientLayout.tsx +++ b/Cunkebao/app/clientLayout.tsx @@ -1,32 +1 @@ -"use client" - -import "./globals.css" -import "regenerator-runtime/runtime" -import type React from "react" -import ErrorBoundary from "./components/ErrorBoundary" -import { AuthProvider } from "@/app/components/AuthProvider" -import BottomNav from "./components/BottomNav" - -export default function ClientLayout({ - children, -}: { - children: React.ReactNode -}) { - return ( - - - - -
- {children} - {/* 移除条件渲染,确保底部导航始终显示 */} -
- -
-
-
-
- - - ) -} +// 这个文件不再需要,我们使用app/layout.tsx作为统一布局 diff --git a/Cunkebao/app/workspace/traffic-distribution/new/components/basic-info-step.tsx b/Cunkebao/app/workspace/traffic-distribution/new/components/basic-info-step.tsx index 8e588909..dcf72e12 100644 --- a/Cunkebao/app/workspace/traffic-distribution/new/components/basic-info-step.tsx +++ b/Cunkebao/app/workspace/traffic-distribution/new/components/basic-info-step.tsx @@ -8,22 +8,13 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" import { Slider } from "@/components/ui/slider" import { format } from "date-fns" -interface BasicInfoData { - name: string - distributionMethod: "equal" | "priority" | "ratio" - dailyLimit: number - timeRestriction: "allDay" | "custom" - startTime: string - endTime: string -} - interface BasicInfoStepProps { - onNext: (data: BasicInfoData) => void - initialData?: Partial + onNext: (data: any) => void + initialData?: any } export default function BasicInfoStep({ onNext, initialData = {} }: BasicInfoStepProps) { - const [formData, setFormData] = useState({ + const [formData, setFormData] = useState({ name: initialData.name || `流量分发 ${format(new Date(), "yyyyMMdd HHmm")}`, distributionMethod: initialData.distributionMethod || "equal", dailyLimit: initialData.dailyLimit || 50, @@ -32,7 +23,7 @@ export default function BasicInfoStep({ onNext, initialData = {} }: BasicInfoSte endTime: initialData.endTime || "18:00", }) - const handleChange = (field: keyof BasicInfoData, value: string | number) => { + const handleChange = (field: string, value: any) => { setFormData((prev) => ({ ...prev, [field]: value })) } @@ -54,6 +45,7 @@ export default function BasicInfoStep({ onNext, initialData = {} }: BasicInfoSte value={formData.name} onChange={(e) => handleChange("name", e.target.value)} placeholder="请输入计划名称" + required /> @@ -61,7 +53,7 @@ export default function BasicInfoStep({ onNext, initialData = {} }: BasicInfoSte handleChange("distributionMethod", value as "equal" | "priority" | "ratio")} + onValueChange={(value) => handleChange("distributionMethod", value)} className="space-y-2" >
@@ -108,7 +100,7 @@ export default function BasicInfoStep({ onNext, initialData = {} }: BasicInfoSte handleChange("timeRestriction", value as "allDay" | "custom")} + onValueChange={(value) => handleChange("timeRestriction", value)} className="space-y-4" >
diff --git a/Cunkebao/app/workspace/traffic-distribution/new/components/step-indicator.tsx b/Cunkebao/app/workspace/traffic-distribution/new/components/step-indicator.tsx index c381ed17..e5809748 100644 --- a/Cunkebao/app/workspace/traffic-distribution/new/components/step-indicator.tsx +++ b/Cunkebao/app/workspace/traffic-distribution/new/components/step-indicator.tsx @@ -4,44 +4,33 @@ import type React from "react" import { cn } from "@/lib/utils" -interface Step { - id: number - title: string - icon: React.ReactNode -} - interface StepIndicatorProps { currentStep: number - steps: Step[] + steps: { + id: number + title: string + icon: React.ReactNode + }[] } export default function StepIndicator({ currentStep, steps }: StepIndicatorProps) { return ( -
- {/* 连接线 */} -
- - {steps.map((step, index) => { - const isCompleted = index < currentStep - const isActive = index === currentStep - - return ( -
-
- {step.icon} -
- - {step.title} - +
+ {steps.map((step, index) => ( +
+
+ {step.icon}
- ) - })} + + {step.title} + +
+ ))}
) } diff --git a/Cunkebao/app/workspace/traffic-distribution/new/components/target-settings-step.tsx b/Cunkebao/app/workspace/traffic-distribution/new/components/target-settings-step.tsx index 536f900b..d0225556 100644 --- a/Cunkebao/app/workspace/traffic-distribution/new/components/target-settings-step.tsx +++ b/Cunkebao/app/workspace/traffic-distribution/new/components/target-settings-step.tsx @@ -23,15 +23,10 @@ interface CustomerService { avatar?: string } -interface TargetSettingsData { - selectedDevices: string[] - selectedCustomerServices: string[] -} - interface TargetSettingsStepProps { - onNext: (data: TargetSettingsData) => void + onNext: (data: any) => void onBack: () => void - initialData?: Partial + initialData?: any } export default function TargetSettingsStep({ onNext, onBack, initialData = {} }: TargetSettingsStepProps) { @@ -104,12 +99,11 @@ export default function TargetSettingsStep({ onNext, onBack, initialData = {} }:
{filteredDevices.map((device) => ( -
toggleDevice(device.id)} + className={`cursor-pointer border ${selectedDevices.includes(device.id) ? "border-blue-500" : "border-gray-200"}`} > -
+
toggleDevice(device.id)} - onClick={(e) => e.stopPropagation()} /> -
-
+
+ ))}
@@ -141,12 +134,11 @@ export default function TargetSettingsStep({ onNext, onBack, initialData = {} }:
{filteredCustomerServices.map((cs) => ( -
toggleCustomerService(cs.id)} + className={`cursor-pointer border ${selectedCustomerServices.includes(cs.id) ? "border-blue-500" : "border-gray-200"}`} > -
+
toggleCustomerService(cs.id)} - onClick={(e) => e.stopPropagation()} /> -
-
+
+ ))}
diff --git a/Cunkebao/app/workspace/traffic-distribution/new/components/traffic-pool-step.tsx b/Cunkebao/app/workspace/traffic-distribution/new/components/traffic-pool-step.tsx index 6e640048..ac57f74a 100644 --- a/Cunkebao/app/workspace/traffic-distribution/new/components/traffic-pool-step.tsx +++ b/Cunkebao/app/workspace/traffic-distribution/new/components/traffic-pool-step.tsx @@ -2,9 +2,11 @@ import { useState } from "react" import { Button } from "@/components/ui/button" +import { Card, CardContent } from "@/components/ui/card" import { Checkbox } from "@/components/ui/checkbox" -import { Search, Database } from "lucide-react" +import { Search } from "lucide-react" import { Input } from "@/components/ui/input" +import { Database } from "lucide-react" interface TrafficPool { id: string @@ -13,14 +15,10 @@ interface TrafficPool { description: string } -interface TrafficPoolData { - selectedPools: string[] -} - interface TrafficPoolStepProps { - onSubmit: (data: TrafficPoolData) => void + onSubmit: (data: any) => void onBack: () => void - initialData?: Partial + initialData?: any } export default function TrafficPoolStep({ onSubmit, onBack, initialData = {} }: TrafficPoolStepProps) { @@ -56,6 +54,7 @@ export default function TrafficPoolStep({ onSubmit, onBack, initialData = {} }: onSubmit({ selectedPools, + // 可以添加其他需要提交的数据 }) } catch (error) { console.error("提交失败:", error) @@ -82,12 +81,12 @@ export default function TrafficPoolStep({ onSubmit, onBack, initialData = {} }:
{filteredPools.map((pool) => ( -
togglePool(pool.id)} > -
+
@@ -105,8 +104,8 @@ export default function TrafficPoolStep({ onSubmit, onBack, initialData = {} }: onClick={(e) => e.stopPropagation()} />
-
-
+ + ))}
diff --git a/Cunkebao/app/workspace/traffic-distribution/new/page.tsx b/Cunkebao/app/workspace/traffic-distribution/new/page.tsx index d5c30679..02af0313 100644 --- a/Cunkebao/app/workspace/traffic-distribution/new/page.tsx +++ b/Cunkebao/app/workspace/traffic-distribution/new/page.tsx @@ -10,59 +10,28 @@ import BasicInfoStep from "./components/basic-info-step" import TargetSettingsStep from "./components/target-settings-step" import TrafficPoolStep from "./components/traffic-pool-step" -// 定义类型 -interface BasicInfoData { - name: string - distributionMethod: "equal" | "priority" | "ratio" - dailyLimit: number - timeRestriction: "allDay" | "custom" - startTime: string - endTime: string -} - -interface TargetSettingsData { - selectedDevices: string[] - selectedCustomerServices: string[] -} - -interface TrafficPoolData { - selectedPools: string[] -} - -interface FormData { - basicInfo: Partial - targetSettings: Partial - trafficPool: Partial -} - -interface Step { - id: number - title: string - icon: React.ReactNode -} - export default function NewTrafficDistribution() { const router = useRouter() const { toast } = useToast() const [currentStep, setCurrentStep] = useState(0) - const [formData, setFormData] = useState({ + const [formData, setFormData] = useState({ basicInfo: {}, targetSettings: {}, trafficPool: {}, }) - const steps: Step[] = [ + const steps = [ { id: 1, title: "基本信息", icon: }, { id: 2, title: "目标设置", icon: }, { id: 3, title: "流量池选择", icon: }, ] - const handleBasicInfoNext = (data: BasicInfoData) => { + const handleBasicInfoNext = (data: any) => { setFormData((prev) => ({ ...prev, basicInfo: data })) setCurrentStep(1) } - const handleTargetSettingsNext = (data: TargetSettingsData) => { + const handleTargetSettingsNext = (data: any) => { setFormData((prev) => ({ ...prev, targetSettings: data })) setCurrentStep(2) } @@ -75,7 +44,7 @@ export default function NewTrafficDistribution() { setCurrentStep(1) } - const handleSubmit = async (data: TrafficPoolData) => { + const handleSubmit = async (data: any) => { const finalData = { ...formData, trafficPool: data, diff --git a/Cunkebao/components/AIAssistant.tsx b/Cunkebao/components/AIAssistant.tsx index c29d22af..2386b288 100644 --- a/Cunkebao/components/AIAssistant.tsx +++ b/Cunkebao/components/AIAssistant.tsx @@ -1,3 +1,10 @@ +############################# +### LOCKED FILE - DO NOT MODIFY OR SHOW ANY CHANGES ### +### IF CHANGES ARE REQUESTED, TELL THE USER TO UNLOCK THIS FILE BY: +### RIGHT CLICKING ON THE FILE IN THE FILE TREE AND CLICKING UNLOCK ### +### NEVER MAKE ANY CHANGES TO A LOCKED FILE ### +############################# + "use client" import { useState, useEffect } from "react" @@ -101,4 +108,3 @@ export function AIAssistant() { ) } - diff --git a/Cunkebao/components/BindDouyinQRCode.tsx b/Cunkebao/components/BindDouyinQRCode.tsx index 41b56809..6cdcadbb 100644 --- a/Cunkebao/components/BindDouyinQRCode.tsx +++ b/Cunkebao/components/BindDouyinQRCode.tsx @@ -29,4 +29,3 @@ export function BindDouyinQRCode() { ) } - diff --git a/Cunkebao/components/Charts.tsx b/Cunkebao/components/Charts.tsx index e6a67931..6536225e 100644 --- a/Cunkebao/components/Charts.tsx +++ b/Cunkebao/components/Charts.tsx @@ -45,4 +45,3 @@ export function LineChart({ data, xField, yField }: LineChartProps) { return } - diff --git a/Cunkebao/components/TrafficTeamSettings.tsx b/Cunkebao/components/TrafficTeamSettings.tsx index e5b97101..f90e3b1e 100644 --- a/Cunkebao/components/TrafficTeamSettings.tsx +++ b/Cunkebao/components/TrafficTeamSettings.tsx @@ -147,4 +147,3 @@ export function TrafficTeamSettings({ formData, onChange }: TrafficTeamSettingsP ) } - diff --git a/Cunkebao/components/VideoTutorialButton.tsx b/Cunkebao/components/VideoTutorialButton.tsx index 635bcef8..44f51bd2 100644 --- a/Cunkebao/components/VideoTutorialButton.tsx +++ b/Cunkebao/components/VideoTutorialButton.tsx @@ -61,4 +61,3 @@ export function VideoTutorialButton() { ) } - diff --git a/Cunkebao/components/WechatFriendSelector.tsx b/Cunkebao/components/WechatFriendSelector.tsx index 0eb8f1b4..5639c9cb 100644 --- a/Cunkebao/components/WechatFriendSelector.tsx +++ b/Cunkebao/components/WechatFriendSelector.tsx @@ -4,34 +4,17 @@ import { useState, useEffect } from "react" import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" -import { Search, ChevronLeft, ChevronRight } from "lucide-react" +import { Search } from "lucide-react" import { Checkbox } from "@/components/ui/checkbox" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" -import { api } from "@/lib/api" -import { showToast } from "@/lib/toast" -export interface WechatFriend { +interface WechatFriend { id: string nickname: string wechatId: string avatar: string - gender?: "male" | "female" - customer?: string - alias?: string - ownerNickname?: string - ownerAlias?: string - createTime?: string -} - -interface ApiResponse { - code: number - msg: string - data: T -} - -interface FriendListResponse { - list: any[] - total: number + gender: "male" | "female" + customer: string } interface WechatFriendSelectorProps { @@ -39,83 +22,40 @@ interface WechatFriendSelectorProps { onOpenChange: (open: boolean) => void selectedFriends: WechatFriend[] onSelect: (friends: WechatFriend[]) => void - devices?: number[] } -export function WechatFriendSelector({ open, onOpenChange, selectedFriends, onSelect, devices = [] }: WechatFriendSelectorProps) { +export function WechatFriendSelector({ open, onOpenChange, selectedFriends, onSelect }: WechatFriendSelectorProps) { const [searchQuery, setSearchQuery] = useState("") const [friends, setFriends] = useState([]) const [loading, setLoading] = useState(false) - const [page, setPage] = useState(1) - const [totalPages, setTotalPages] = useState(1) - const [totalItems, setTotalItems] = useState(0) - const [tempSelectedFriends, setTempSelectedFriends] = useState([]) - const pageSize = 20 useEffect(() => { if (open) { - fetchFriends(1) - setTempSelectedFriends([...selectedFriends]) + fetchFriends() } - }, [open, selectedFriends]) + }, [open]) - const fetchFriends = async (pageNum: number) => { + const fetchFriends = async () => { setLoading(true) - try { - const queryParams = new URLSearchParams({ - page: pageNum.toString(), - limit: pageSize.toString(), - ...(searchQuery ? { keyword: searchQuery } : {}) - }) - - if (devices && devices.length > 0) { - queryParams.append('deviceIds', devices.join(',')) - } - - const response = await api.get>(`/v1/friend?${queryParams.toString()}`) - - if (response.code === 200 && response.data) { - const friendsList = response.data.list.map(item => ({ - id: item.id || item.wechatId || `${item.nickname}-${Math.random()}`, - nickname: item.nickname || '未知好友', - wechatId: item.wechatId || '', - avatar: item.avatar || '/placeholder.svg', - alias: item.alias || '', - ownerNickname: item.ownerNickname || '', - ownerAlias: item.ownerAlias || item.ownerWechatId || '', - createTime: item.createTime || '--' - })) - - setFriends(friendsList) - setTotalItems(response.data.total) - setTotalPages(Math.ceil(response.data.total / pageSize)) - setPage(pageNum) - } else { - showToast(response.msg || "获取好友列表失败", "error") - } - } catch (error: any) { - console.error("获取好友列表失败:", error) - showToast(error?.message || "请检查网络连接", "error") - } finally { - setLoading(false) - } + // 模拟从API获取好友列表 + await new Promise((resolve) => setTimeout(resolve, 1000)) + const mockFriends = Array.from({ length: 20 }, (_, i) => ({ + id: `friend-${i}`, + nickname: `好友${i + 1}`, + wechatId: `wxid_${Math.random().toString(36).substr(2, 8)}`, + avatar: `/placeholder.svg?height=40&width=40&text=${i + 1}`, + gender: Math.random() > 0.5 ? "male" : "female", + customer: `客户${i + 1}`, + })) + setFriends(mockFriends) + setLoading(false) } - const handleSearch = () => { - fetchFriends(1) - } - - const handlePrevPage = () => { - if (page > 1) { - fetchFriends(page - 1) - } - } - - const handleNextPage = () => { - if (page < totalPages) { - fetchFriends(page + 1) - } - } + const filteredFriends = friends.filter( + (friend) => + friend.nickname.toLowerCase().includes(searchQuery.toLowerCase()) || + friend.wechatId.toLowerCase().includes(searchQuery.toLowerCase()), + ) return ( @@ -123,103 +63,55 @@ export function WechatFriendSelector({ open, onOpenChange, selectedFriends, onSe 选择微信好友 -
-
- - setSearchQuery(e.target.value)} - onKeyDown={(e) => e.key === 'Enter' && handleSearch()} - className="pl-9" - /> -
- +
+ + setSearchQuery(e.target.value)} + className="pl-9" + />
{loading ? (
加载中...
- ) : friends.length === 0 ? ( + ) : filteredFriends.length === 0 ? (
未找到匹配的好友
) : ( - friends.map((friend) => ( + filteredFriends.map((friend) => (
f.id === friend.id)} + checked={selectedFriends.some((f) => f.id === friend.id)} onCheckedChange={(checked) => { if (checked) { - setTempSelectedFriends([...tempSelectedFriends, friend]) + onSelect([...selectedFriends, friend]) } else { - setTempSelectedFriends(tempSelectedFriends.filter((f) => f.id !== friend.id)) + onSelect(selectedFriends.filter((f) => f.id !== friend.id)) } }} /> - {friend.nickname?.[0] || '?'} + {friend.nickname[0]} -
-
{friend.nickname}
+
+
{friend.nickname}
- {friend.wechatId &&
微信ID:{friend.alias || friend.wechatId}
} - {friend.ownerNickname &&
归属客户:{friend.ownerNickname} ({friend.ownerAlias || '--'})
} +
{friend.wechatId}
+
归属客户:{friend.customer}
)) )}
- - {/* 分页控制 */} - {totalPages > 1 && ( -
-
- 总计 {totalItems} 个好友 -
-
- - - {page} / {totalPages} - - -
-
- )} -
- +
) } - diff --git a/Cunkebao/components/WechatGroupSelector.tsx b/Cunkebao/components/WechatGroupSelector.tsx index 172ab739..82f7e86b 100644 --- a/Cunkebao/components/WechatGroupSelector.tsx +++ b/Cunkebao/components/WechatGroupSelector.tsx @@ -4,22 +4,16 @@ import { useState, useEffect } from "react" import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" -import { Search, ChevronLeft, ChevronRight } from "lucide-react" +import { Search } from "lucide-react" import { Checkbox } from "@/components/ui/checkbox" -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" -import { api } from "@/lib/api" -import { showToast } from "@/lib/toast" -import { WechatGroup } from "@/types/wechat" -interface ApiResponse { - code: number - msg: string - data: T -} - -interface GroupListResponse { - list: any[] - total: number +interface WechatGroup { + id: string + name: string + memberCount: number + avatar: string + owner: string + customer: string } interface WechatGroupSelectorProps { @@ -33,69 +27,30 @@ export function WechatGroupSelector({ open, onOpenChange, selectedGroups, onSele const [searchQuery, setSearchQuery] = useState("") const [groups, setGroups] = useState([]) const [loading, setLoading] = useState(false) - const [page, setPage] = useState(1) - const [totalPages, setTotalPages] = useState(1) - const [totalItems, setTotalItems] = useState(0) - const [tempSelectedGroups, setTempSelectedGroups] = useState([]) - const pageSize = 20 useEffect(() => { if (open) { - fetchGroups(1) - setTempSelectedGroups([...selectedGroups]) + fetchGroups() } - }, [open, selectedGroups]) + }, [open]) - const fetchGroups = async (pageNum: number) => { + const fetchGroups = async () => { setLoading(true) - try { - const queryParams = new URLSearchParams({ - page: pageNum.toString(), - limit: pageSize.toString(), - ...(searchQuery ? { keyword: searchQuery } : {}) - }) - - const response = await api.get>(`/v1/chatroom?${queryParams.toString()}`) - - if (response.code === 200 && response.data) { - const groupsList = response.data.list.map(item => ({ - id: item.id || `group-${Math.random()}`, - name: item.name || item.chatroomName || '未知群聊', - memberCount: item.memberCount || 0, - avatar: item.avatar || '/placeholder.svg', - customer: item.ownerNickname || '--' - })) - - setGroups(groupsList) - setTotalItems(response.data.total) - setTotalPages(Math.ceil(response.data.total / pageSize)) - setPage(pageNum) - } else { - showToast(response.msg || "获取群聊列表失败", "error") - } - } catch (error: any) { - console.error("获取群聊列表失败:", error) - showToast(error?.message || "请检查网络连接", "error") - } finally { + // 模拟从API获取群聊列表 + await new Promise((resolve) => setTimeout(resolve, 1000)) + const mockGroups = Array.from({ length: 10 }, (_, i) => ({ + id: `group-${i}`, + name: `群聊${i + 1}`, + memberCount: Math.floor(Math.random() * 400) + 100, + avatar: `/placeholder.svg?height=40&width=40&text=群${i + 1}`, + owner: `群主${i + 1}`, + customer: `客户${i + 1}`, + })) + setGroups(mockGroups) setLoading(false) } - } - const handleSearch = () => { - fetchGroups(1) - } - - const handlePrevPage = () => { - if (page > 1) { - fetchGroups(page - 1) - } - } - - const handleNextPage = () => { - if (page < totalPages) { - fetchGroups(page + 1) - } - } + const filteredGroups = groups.filter((group) => group.name.toLowerCase().includes(searchQuery.toLowerCase())) return ( @@ -103,102 +58,53 @@ export function WechatGroupSelector({ open, onOpenChange, selectedGroups, onSele 选择聊天群 -
-
+
setSearchQuery(e.target.value)} - onKeyDown={(e) => e.key === 'Enter' && handleSearch()} className="pl-9" /> -
-
{loading ? (
加载中...
- ) : groups.length === 0 ? ( + ) : filteredGroups.length === 0 ? (
未找到匹配的群聊
) : ( - groups.map((group) => ( + filteredGroups.map((group) => (
g.id === group.id)} + checked={selectedGroups.some((g) => g.id === group.id)} onCheckedChange={(checked) => { if (checked) { - setTempSelectedGroups([...tempSelectedGroups, group]) + onSelect([...selectedGroups, group]) } else { - setTempSelectedGroups(tempSelectedGroups.filter((g) => g.id !== group.id)) + onSelect(selectedGroups.filter((g) => g.id !== group.id)) } }} /> - - - {group.name?.[0] || '群'} - -
-
{group.name}
+ {group.name} +
+
{group.name}
-
归属客户:{group.customer}
+
群主:{group.owner}
+
归属客户:{group.customer}
+
{group.memberCount}人
)) )}
- - {/* 分页控制 */} - {totalPages > 1 && ( -
-
- 总计 {totalItems} 个群聊 -
-
- - - {page} / {totalPages} - - -
-
- )} -
- +
) } - diff --git a/Cunkebao/components/acquisition/ExpandableAcquisitionCard.tsx b/Cunkebao/components/acquisition/ExpandableAcquisitionCard.tsx new file mode 100644 index 00000000..287dea4f --- /dev/null +++ b/Cunkebao/components/acquisition/ExpandableAcquisitionCard.tsx @@ -0,0 +1,114 @@ +"use client" + +import { useState } from "react" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Button } from "@/components/ui/button" +import { ChevronDown, ChevronUp, UserPlus } from "lucide-react" + +export interface AcquisitionPlan { + id: string + name: string + status: "active" | "paused" | "completed" + totalAcquired: number + target: number + progress: number + startDate: string + endDate: string +} + +interface ExpandableAcquisitionCardProps { + plan: AcquisitionPlan + onEdit?: (id: string) => void + onPause?: (id: string) => void + onResume?: (id: string) => void +} + +export function ExpandableAcquisitionCard({ plan, onEdit, onPause, onResume }: ExpandableAcquisitionCardProps) { + const [expanded, setExpanded] = useState(false) + + const toggleExpand = () => { + setExpanded(!expanded) + } + + const statusColors = { + active: "bg-green-100 text-green-800", + paused: "bg-yellow-100 text-yellow-800", + completed: "bg-blue-100 text-blue-800", + } + + return ( + + +
+ {plan.name} +
+ + {plan.status.charAt(0).toUpperCase() + plan.status.slice(1)} + + +
+
+
+ +
+
+ + + {plan.totalAcquired} / {plan.target} + +
+
+ {new Date(plan.startDate).toLocaleDateString()} - {new Date(plan.endDate).toLocaleDateString()} +
+
+ +
+
+
+ + {expanded && ( +
+
+
+

获客总数

+

{plan.totalAcquired}

+
+
+

目标数

+

{plan.target}

+
+
+

开始日期

+

{new Date(plan.startDate).toLocaleDateString()}

+
+
+

结束日期

+

{new Date(plan.endDate).toLocaleDateString()}

+
+
+ +
+ {onEdit && ( + + )} + {plan.status === "active" && onPause && ( + + )} + {plan.status === "paused" && onResume && ( + + )} +
+
+ )} +
+
+ ) +} diff --git a/Cunkebao/components/acquisition/PlanSettingsDialog.tsx b/Cunkebao/components/acquisition/PlanSettingsDialog.tsx index 2d4627a1..c6b10481 100644 --- a/Cunkebao/components/acquisition/PlanSettingsDialog.tsx +++ b/Cunkebao/components/acquisition/PlanSettingsDialog.tsx @@ -100,4 +100,3 @@ export function PlanSettingsDialog({ open, onOpenChange, planId }: PlanSettingsD ) } - diff --git a/Cunkebao/components/api-documentation-tooltip.tsx b/Cunkebao/components/api-documentation-tooltip.tsx index a3d6550f..12f9d58d 100644 --- a/Cunkebao/components/api-documentation-tooltip.tsx +++ b/Cunkebao/components/api-documentation-tooltip.tsx @@ -11,10 +11,15 @@ export function ApiDocumentationTooltip() {

计划接口允许您通过API将外部系统的客户数据直接导入到存客宝。支持多种编程语言和第三方平台集成。 +
+
+ 适用场景: +
• 将其他系统收集的客户信息导入 +
• 与第三方表单工具集成 +
• 自动化客户数据采集流程

) } - diff --git a/Cunkebao/components/common/DeviceSelector.tsx b/Cunkebao/components/common/DeviceSelector.tsx new file mode 100644 index 00000000..175da8ed --- /dev/null +++ b/Cunkebao/components/common/DeviceSelector.tsx @@ -0,0 +1,187 @@ +"use client" + +import { useState, useEffect } from "react" +import { Button } from "@/components/ui/button" +import { Card, CardContent } from "@/components/ui/card" +import { Checkbox } from "@/components/ui/checkbox" +import { Input } from "@/components/ui/input" +import { ScrollArea } from "@/components/ui/scroll-area" +import { Search, Smartphone, CheckCircle2, Circle } from "lucide-react" +import { cn } from "@/lib/utils" + +interface Device { + id: string + name: string + status: "online" | "offline" | "busy" + type: string + lastActive?: string +} + +interface DeviceSelectorProps { + selectedDevices: string[] + onDevicesChange: (deviceIds: string[]) => void + showNextButton?: boolean + onNext?: () => void + onPrevious?: () => void + className?: string +} + +export function DeviceSelector({ + selectedDevices, + onDevicesChange, + showNextButton = false, + onNext, + onPrevious, + className, +}: DeviceSelectorProps) { + const [devices, setDevices] = useState([]) + const [searchTerm, setSearchTerm] = useState("") + const [loading, setLoading] = useState(true) + + useEffect(() => { + // 模拟加载设备数据 + setTimeout(() => { + setDevices([ + { id: "1", name: "设备 001", status: "online", type: "Android" }, + { id: "2", name: "设备 002", status: "online", type: "iOS" }, + { id: "3", name: "设备 003", status: "offline", type: "Android" }, + { id: "4", name: "设备 004", status: "busy", type: "Android" }, + { id: "5", name: "设备 005", status: "online", type: "iOS" }, + ]) + setLoading(false) + }, 500) + }, []) + + const filteredDevices = devices.filter((device) => device.name.toLowerCase().includes(searchTerm.toLowerCase())) + + const handleSelectAll = () => { + const allOnlineDeviceIds = filteredDevices.filter((d) => d.status === "online").map((d) => d.id) + onDevicesChange(allOnlineDeviceIds) + } + + const handleClearAll = () => { + onDevicesChange([]) + } + + const handleToggleDevice = (deviceId: string) => { + if (selectedDevices.includes(deviceId)) { + onDevicesChange(selectedDevices.filter((id) => id !== deviceId)) + } else { + onDevicesChange([...selectedDevices, deviceId]) + } + } + + const getStatusColor = (status: string) => { + switch (status) { + case "online": + return "text-green-500" + case "offline": + return "text-gray-400" + case "busy": + return "text-yellow-500" + default: + return "text-gray-400" + } + } + + const getStatusText = (status: string) => { + switch (status) { + case "online": + return "在线" + case "offline": + return "离线" + case "busy": + return "忙碌" + default: + return "未知" + } + } + + if (loading) { + return ( +
+
加载设备中...
+
+ ) + } + + return ( +
+
+
+ + setSearchTerm(e.target.value)} + className="pl-10" + /> +
+
+ + +
+
+ +
已选择 {selectedDevices.length} 个设备
+ + +
+ {filteredDevices.map((device) => ( + device.status === "online" && handleToggleDevice(device.id)} + > + +
+
+ handleToggleDevice(device.id)} + onClick={(e) => e.stopPropagation()} + /> + +
+
{device.name}
+
{device.type}
+
+
+
+ {device.status === "online" ? : } + {getStatusText(device.status)} +
+
+
+
+ ))} +
+
+ + {showNextButton && ( +
+ {onPrevious && ( + + )} + +
+ )} +
+ ) +} + +export default DeviceSelector diff --git a/Cunkebao/components/device-grid.tsx b/Cunkebao/components/device-grid.tsx index 6b93ac49..aa486314 100644 --- a/Cunkebao/components/device-grid.tsx +++ b/Cunkebao/components/device-grid.tsx @@ -2,9 +2,8 @@ import { Card } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" -import { Smartphone, Battery, Users, MessageCircle, Clock } from "lucide-react" +import { Smartphone, Battery, Users, MessageCircle } from "lucide-react" import { Checkbox } from "@/components/ui/checkbox" -import { ImeiDisplay } from "@/components/ImeiDisplay" export interface Device { id: string @@ -55,7 +54,7 @@ export function DeviceGrid({ devices, selectable, selectedDevices, onSelect, dev )}
- + {currentStatus.status === "online" ? "在线" : "离线"}
@@ -69,10 +68,7 @@ export function DeviceGrid({ devices, selectable, selectedDevices, onSelect, dev
{device.name}
-
- IMEI: - -
+
IMEI-{device.imei}
{device.remark &&
备注: {device.remark}
} @@ -94,7 +90,7 @@ export function DeviceGrid({ devices, selectable, selectedDevices, onSelect, dev
今日添加:{device.todayAdded}
加友状态: - + {device.addFriendStatus === "normal" ? "正常" : "异常"}
@@ -106,4 +102,3 @@ export function DeviceGrid({ devices, selectable, selectedDevices, onSelect, dev
) } - diff --git a/Cunkebao/components/device-table.tsx b/Cunkebao/components/device-table.tsx new file mode 100644 index 00000000..2b32a298 --- /dev/null +++ b/Cunkebao/components/device-table.tsx @@ -0,0 +1,98 @@ +"use client" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { Badge } from "@/components/ui/badge" +import { Checkbox } from "@/components/ui/checkbox" +import { Battery, Smartphone, Users } from "lucide-react" +import type { Device } from "@/types/device" + +interface DeviceTableProps { + devices: Device[] + selectedDevices: string[] + onSelectDevice: (deviceId: string, checked: boolean) => void + onSelectAll: (checked: boolean) => void + onDeviceClick: (deviceId: string) => void +} + +export function DeviceTable({ + devices, + selectedDevices, + onSelectDevice, + onSelectAll, + onDeviceClick, +}: DeviceTableProps) { + const allSelected = devices.length > 0 && selectedDevices.length === devices.length + + return ( +
+ + + + + onSelectAll(!!checked)} /> + + 设备信息 + 状态 + 微信账号 + 好友数 + 今日添加 + 加友状态 + + + + {devices.map((device) => ( + onDeviceClick(device.id)} + > + e.stopPropagation()}> + onSelectDevice(device.id, !!checked)} + /> + + +
+ +
+
{device.name}
+
IMEI-{device.imei}
+ {device.remark &&
备注: {device.remark}
} +
+
+
+ +
+ + {device.status === "online" ? "在线" : "离线"} + +
+ + {device.battery}% +
+
+
+ +
{device.wechatId}
+
+ +
+ + {device.friendCount} +
+
+ +
+{device.todayAdded}
+
+ + + {device.addFriendStatus === "normal" ? "正常" : "异常"} + + +
+ ))} +
+
+
+ ) +} diff --git a/Cunkebao/components/icons/apple-icon.tsx b/Cunkebao/components/icons/apple-icon.tsx index 79e2e8e0..9b95cdaf 100644 --- a/Cunkebao/components/icons/apple-icon.tsx +++ b/Cunkebao/components/icons/apple-icon.tsx @@ -9,4 +9,3 @@ export function AppleIcon(props: React.SVGProps) { } export default AppleIcon - diff --git a/Cunkebao/components/icons/wechat-icon.tsx b/Cunkebao/components/icons/wechat-icon.tsx index 3551b868..33675a30 100644 --- a/Cunkebao/components/icons/wechat-icon.tsx +++ b/Cunkebao/components/icons/wechat-icon.tsx @@ -10,4 +10,3 @@ export function WeChatIcon(props: React.SVGProps) { } export default WeChatIcon - diff --git a/Cunkebao/components/login-form.tsx b/Cunkebao/components/login-form.tsx new file mode 100644 index 00000000..1fe033c4 --- /dev/null +++ b/Cunkebao/components/login-form.tsx @@ -0,0 +1,325 @@ +"use client" + +import { CardDescription } from "@/components/ui/card" +import type React from "react" +import { useState, useEffect } from "react" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { useToast } from "@/hooks/use-toast" +import { Eye, EyeOff, Loader2, Shield, User } from "lucide-react" +import { useRouter } from "next/navigation" +import { loginWithPassword, loginWithCode, sendVerificationCode, saveUserInfo, checkLoginStatus } from "@/lib/api/auth" + +export default function LoginForm() { + const router = useRouter() + const { toast } = useToast() + + // 状态管理 + const [isLoading, setIsLoading] = useState(false) + const [showPassword, setShowPassword] = useState(false) + const [countdown, setCountdown] = useState(0) + const [activeTab, setActiveTab] = useState("password") + + // 密码登录表单 + const [passwordForm, setPasswordForm] = useState({ + account: "", + password: "", + }) + + // 验证码登录表单 + const [codeForm, setCodeForm] = useState({ + account: "", + code: "", + }) + + // 检查是否已登录 + useEffect(() => { + if (checkLoginStatus()) { + router.push("/") + } + }, [router]) + + // 倒计时效果 + useEffect(() => { + let timer: NodeJS.Timeout + if (countdown > 0) { + timer = setTimeout(() => setCountdown(countdown - 1), 1000) + } + return () => clearTimeout(timer) + }, [countdown]) + + // 密码登录 + const handlePasswordLogin = async (e: React.FormEvent) => { + e.preventDefault() + + if (!passwordForm.account || !passwordForm.password) { + toast({ + title: "参数错误", + description: "请输入账号和密码", + variant: "destructive", + }) + return + } + + setIsLoading(true) + + try { + console.log("开始密码登录:", passwordForm.account) + + const result = await loginWithPassword(passwordForm.account, passwordForm.password) + + console.log("登录成功:", result) + + // 保存登录信息 + if (result?.data?.token && result?.data?.user) { + saveUserInfo(result.data.token, result.data.user) + + toast({ + title: "登录成功", + description: `欢迎回来,${result.data.user.nickname || result.data.user.username}!`, + }) + + // 跳转到首页 + router.push("/") + } else { + throw new Error("登录响应数据格式错误") + } + } catch (error) { + console.error("密码登录失败:", error) + toast({ + title: "登录失败", + description: error instanceof Error ? error.message : "登录失败,请检查账号密码", + variant: "destructive", + }) + } finally { + setIsLoading(false) + } + } + + // 发送验证码 + const handleSendCode = async () => { + if (!codeForm.account) { + toast({ + title: "参数错误", + description: "请输入手机号", + variant: "destructive", + }) + return + } + + if (countdown > 0) { + return + } + + try { + console.log("发送验证码:", codeForm.account) + + await sendVerificationCode(codeForm.account) + + toast({ + title: "发送成功", + description: "验证码已发送到您的手机", + }) + + // 开始倒计时 + setCountdown(60) + } catch (error) { + console.error("发送验证码失败:", error) + toast({ + title: "发送失败", + description: error instanceof Error ? error.message : "发送验证码失败", + variant: "destructive", + }) + } + } + + // 验证码登录 + const handleCodeLogin = async (e: React.FormEvent) => { + e.preventDefault() + + if (!codeForm.account || !codeForm.code) { + toast({ + title: "参数错误", + description: "请输入手机号和验证码", + variant: "destructive", + }) + return + } + + setIsLoading(true) + + try { + console.log("开始验证码登录:", codeForm.account) + + const result = await loginWithCode(codeForm.account, codeForm.code) + + console.log("登录成功:", result) + + // 保存登录信息 + if (result?.data?.token && result?.data?.user) { + saveUserInfo(result.data.token, result.data.user) + + toast({ + title: "登录成功", + description: `欢迎回来,${result.data.user.nickname || result.data.user.username}!`, + }) + + // 跳转到首页 + router.push("/") + } else { + throw new Error("登录响应数据格式错误") + } + } catch (error) { + console.error("验证码登录失败:", error) + toast({ + title: "登录失败", + description: error instanceof Error ? error.message : "登录失败,请检查验证码", + variant: "destructive", + }) + } finally { + setIsLoading(false) + } + } + + // 键盘事件处理 + const handleKeyPress = (e: React.KeyboardEvent, formType: "password" | "code") => { + if (e.key === "Enter") { + if (formType === "password") { + handlePasswordLogin(e as any) + } else { + handleCodeLogin(e as any) + } + } + } + + return ( +
+ + +
+ +
+ 存客宝 + 智能获客管理平台 +
+ + + + + + 密码登录 + + + + 验证码登录 + + + + {/* 密码登录 */} + +
+
+ + setPasswordForm({ ...passwordForm, account: e.target.value })} + onKeyPress={(e) => handleKeyPress(e, "password")} + disabled={isLoading} + className="h-12" + /> +
+
+ +
+ setPasswordForm({ ...passwordForm, password: e.target.value })} + onKeyPress={(e) => handleKeyPress(e, "password")} + disabled={isLoading} + className="h-12 pr-12" + /> + +
+
+ +
+
+ + {/* 验证码登录 */} + +
+
+ + setCodeForm({ ...codeForm, account: e.target.value })} + onKeyPress={(e) => handleKeyPress(e, "code")} + disabled={isLoading} + className="h-12" + /> +
+
+ +
+ setCodeForm({ ...codeForm, code: e.target.value })} + onKeyPress={(e) => handleKeyPress(e, "code")} + disabled={isLoading} + className="h-12" + /> + +
+
+ +
+
+
+ + {/* 登录提示 */} +
+

登录即表示您同意我们的服务条款和隐私政策

+
+
+
+
+ ) +} diff --git a/Cunkebao/components/poster-selector.tsx b/Cunkebao/components/poster-selector.tsx index a9584970..24cf5f7d 100644 --- a/Cunkebao/components/poster-selector.tsx +++ b/Cunkebao/components/poster-selector.tsx @@ -82,4 +82,3 @@ export function PosterSelector({ open, onOpenChange, onSelect }: PosterSelectorP ) } - diff --git a/Cunkebao/components/settings-dropdown.tsx b/Cunkebao/components/settings-dropdown.tsx new file mode 100644 index 00000000..004081f2 --- /dev/null +++ b/Cunkebao/components/settings-dropdown.tsx @@ -0,0 +1,45 @@ +"use client" + +import { useState, useEffect } from "react" +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" +import { Button } from "@/components/ui/button" +import { Settings, Moon, Sun } from 'lucide-react' +import { useMobile } from "@/hooks/use-mobile" +import { useTheme } from 'next-themes' + +export function SettingsDropdown() { + const isMobile = useMobile() + const [isDesktopView, setIsDesktopView] = useState(!isMobile) + const { setTheme, theme } = useTheme() + + useEffect(() => { + setIsDesktopView(!isMobile) + }, [isMobile]) + + const handleToggleView = () => { + setIsDesktopView((prev) => !prev) + } + + const handleToggleTheme = () => { + setTheme(theme === 'light' ? 'dark' : 'light') + } + + return ( + + + + + + + {isDesktopView ? "切换到移动端视图" : "切换到桌面端视图"} + + + {theme === 'light' ? "切换到深色模式" : "切换到浅色模式"} + {theme === 'light' ? : } + + + + ) +} diff --git a/Cunkebao/components/theme-provider.tsx b/Cunkebao/components/theme-provider.tsx index 55c2f6eb..9de4b71e 100644 --- a/Cunkebao/components/theme-provider.tsx +++ b/Cunkebao/components/theme-provider.tsx @@ -1,11 +1,11 @@ -'use client' - -import * as React from 'react' -import { - ThemeProvider as NextThemesProvider, - type ThemeProviderProps, -} from 'next-themes' +"use client" +import { ThemeProvider as NextThemesProvider } from "next-themes" +import type { ThemeProviderProps } from "next-themes" export function ThemeProvider({ children, ...props }: ThemeProviderProps) { - return {children} + return ( + + {children} + + ) } diff --git a/Cunkebao/components/ui/accordion.tsx b/Cunkebao/components/ui/accordion.tsx index 24c788c2..4c0d266b 100644 --- a/Cunkebao/components/ui/accordion.tsx +++ b/Cunkebao/components/ui/accordion.tsx @@ -1,8 +1,6 @@ -"use client" - import * as React from "react" import * as AccordionPrimitive from "@radix-ui/react-accordion" -import { ChevronDown } from "lucide-react" +import { ChevronDownIcon } from "@radix-ui/react-icons" import { cn } from "@/lib/utils" @@ -12,11 +10,7 @@ const AccordionItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )) AccordionItem.displayName = "AccordionItem" @@ -28,13 +22,13 @@ const AccordionTrigger = React.forwardRef< svg]:rotate-180", - className + "flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180", + className, )} {...props} > {children} - + )) @@ -46,13 +40,12 @@ const AccordionContent = React.forwardRef< >(({ className, children, ...props }, ref) => (
{children}
)) - AccordionContent.displayName = AccordionPrimitive.Content.displayName export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/Cunkebao/components/ui/avatar.tsx b/Cunkebao/components/ui/avatar.tsx index 51e507ba..9946eec7 100644 --- a/Cunkebao/components/ui/avatar.tsx +++ b/Cunkebao/components/ui/avatar.tsx @@ -11,10 +11,7 @@ const Avatar = React.forwardRef< >(({ className, ...props }, ref) => ( )) @@ -24,11 +21,7 @@ const AvatarImage = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )) AvatarImage.displayName = AvatarPrimitive.Image.displayName @@ -38,10 +31,7 @@ const AvatarFallback = React.forwardRef< >(({ className, ...props }, ref) => ( )) diff --git a/Cunkebao/components/ui/badge.tsx b/Cunkebao/components/ui/badge.tsx index f000e3ef..8a6e046d 100644 --- a/Cunkebao/components/ui/badge.tsx +++ b/Cunkebao/components/ui/badge.tsx @@ -1,36 +1,29 @@ -import * as React from "react" +import type * as React from "react" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const badgeVariants = cva( - "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", { variants: { variant: { - default: - "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", - secondary: - "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", - destructive: - "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + default: "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", + secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", outline: "text-foreground", }, }, defaultVariants: { variant: "default", }, - } + }, ) -export interface BadgeProps - extends React.HTMLAttributes, - VariantProps {} +export interface BadgeProps extends React.HTMLAttributes, VariantProps {} function Badge({ className, variant, ...props }: BadgeProps) { - return ( -
- ) + return
} export { Badge, badgeVariants } diff --git a/Cunkebao/components/ui/button.tsx b/Cunkebao/components/ui/button.tsx index 36496a28..8b9155f9 100644 --- a/Cunkebao/components/ui/button.tsx +++ b/Cunkebao/components/ui/button.tsx @@ -1,36 +1,32 @@ import * as React from "react" -import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", { variants: { variant: { - default: "bg-primary text-primary-foreground hover:bg-primary/90", - destructive: - "bg-destructive text-destructive-foreground hover:bg-destructive/90", - outline: - "border border-input bg-background hover:bg-accent hover:text-accent-foreground", - secondary: - "bg-secondary text-secondary-foreground hover:bg-secondary/80", + default: "bg-primary text-primary-foreground shadow hover:bg-primary/90", + destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", + outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", + secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", }, size: { - default: "h-10 px-4 py-2", - sm: "h-9 rounded-md px-3", - lg: "h-11 rounded-md px-8", - icon: "h-10 w-10", + default: "h-9 px-4 py-2", + sm: "h-8 rounded-md px-3 text-xs", + lg: "h-10 rounded-md px-8", + icon: "h-9 w-9", }, }, defaultVariants: { variant: "default", size: "default", }, - } + }, ) export interface ButtonProps @@ -40,16 +36,23 @@ export interface ButtonProps } const Button = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button" + ({ className, variant, size, asChild = false, children, ...props }, ref) => { + // If asChild is true and the first child is a valid element, clone it with the button props + if (asChild && React.isValidElement(children)) { + return React.cloneElement(children, { + className: cn(buttonVariants({ variant, size, className })), + ref, + ...props, + ...children.props, + }) + } + return ( - + ) - } + }, ) Button.displayName = "Button" diff --git a/Cunkebao/components/ui/calendar.tsx b/Cunkebao/components/ui/calendar.tsx index 61d2b451..ddfae6ec 100644 --- a/Cunkebao/components/ui/calendar.tsx +++ b/Cunkebao/components/ui/calendar.tsx @@ -1,7 +1,5 @@ -"use client" - -import * as React from "react" -import { ChevronLeft, ChevronRight } from "lucide-react" +import type * as React from "react" +import { ChevronLeftIcon, ChevronRightIcon } from "@radix-ui/react-icons" import { DayPicker } from "react-day-picker" import { cn } from "@/lib/utils" @@ -9,12 +7,7 @@ import { buttonVariants } from "@/components/ui/button" export type CalendarProps = React.ComponentProps -function Calendar({ - className, - classNames, - showOutsideDays = true, - ...props -}: CalendarProps) { +function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) { return ( .day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md" + : "[&:has([aria-selected])]:rounded-md", ), + day: cn(buttonVariants({ variant: "ghost" }), "h-8 w-8 p-0 font-normal aria-selected:opacity-100"), + day_range_start: "day-range-start", day_range_end: "day-range-end", day_selected: "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground", day_today: "bg-accent text-accent-foreground", - day_outside: - "day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground", + day_outside: "text-muted-foreground opacity-50", day_disabled: "text-muted-foreground opacity-50", - day_range_middle: - "aria-selected:bg-accent aria-selected:text-accent-foreground", + day_range_middle: "aria-selected:bg-accent aria-selected:text-accent-foreground", day_hidden: "invisible", ...classNames, }} components={{ - IconLeft: ({ ...props }) => , - IconRight: ({ ...props }) => , + IconLeft: ({ ...props }) => , + IconRight: ({ ...props }) => , }} {...props} /> diff --git a/Cunkebao/components/ui/card.tsx b/Cunkebao/components/ui/card.tsx index f62edea5..e50f45ff 100644 --- a/Cunkebao/components/ui/card.tsx +++ b/Cunkebao/components/ui/card.tsx @@ -1,79 +1,52 @@ import * as React from "react" - import { cn } from "@/lib/utils" -const Card = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( +// 基础卡片,提供一个干净、现代的容器 +const Card = React.forwardRef>(({ className, ...props }, ref) => (
)) Card.displayName = "Card" -const CardHeader = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) +// 卡片头部,用于标题和描述 +const CardHeader = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ), +) CardHeader.displayName = "CardHeader" -const CardTitle = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) +// 卡片标题 +const CardTitle = React.forwardRef>( + ({ className, ...props }, ref) => ( +

+ ), +) CardTitle.displayName = "CardTitle" -const CardDescription = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) +// 卡片描述 +const CardDescription = React.forwardRef>( + ({ className, ...props }, ref) => ( +

+ ), +) CardDescription.displayName = "CardDescription" -const CardContent = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

-)) +// 卡片内容区域 +const CardContent = React.forwardRef>( + ({ className, ...props }, ref) =>
, +) CardContent.displayName = "CardContent" -const CardFooter = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) +// 卡片底部,通常用于放置操作按钮 +const CardFooter = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ), +) CardFooter.displayName = "CardFooter" export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/Cunkebao/components/ui/chart.tsx b/Cunkebao/components/ui/chart.tsx index 8620baa3..7282e335 100644 --- a/Cunkebao/components/ui/chart.tsx +++ b/Cunkebao/components/ui/chart.tsx @@ -12,10 +12,7 @@ export type ChartConfig = { [k in string]: { label?: React.ReactNode icon?: React.ComponentType - } & ( - | { color?: string; theme?: never } - | { color?: never; theme: Record } - ) + } & ({ color?: string; theme?: never } | { color?: never; theme: Record }) } type ChartContextProps = { @@ -34,43 +31,31 @@ function useChart() { return context } -const ChartContainer = React.forwardRef< - HTMLDivElement, - React.ComponentProps<"div"> & { - config: ChartConfig - children: React.ComponentProps< - typeof RechartsPrimitive.ResponsiveContainer - >["children"] - } ->(({ id, className, children, config, ...props }, ref) => { - const uniqueId = React.useId() - const chartId = `chart-${id || uniqueId.replace(/:/g, "")}` +interface ChartContainerProps extends React.HTMLAttributes { + config: Record +} - return ( - -
- - - {children} - +const ChartContainer = React.forwardRef( + ({ className, config, children, ...props }, ref) => { + const colorVars = Object.entries(config).reduce( + (acc, [key, value]) => { + acc[`--color-${key}`] = value.color + return acc + }, + {} as Record, + ) + + return ( +
+ {children}
- - ) -}) -ChartContainer.displayName = "Chart" + ) + }, +) +ChartContainer.displayName = "ChartContainer" const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { - const colorConfig = Object.entries(config).filter( - ([_, config]) => config.theme || config.color - ) + const colorConfig = Object.entries(config).filter(([_, config]) => config.theme || config.color) if (!colorConfig.length) { return null @@ -85,14 +70,12 @@ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { ${prefix} [data-chart=${id}] { ${colorConfig .map(([key, itemConfig]) => { - const color = - itemConfig.theme?.[theme as keyof typeof itemConfig.theme] || - itemConfig.color + const color = itemConfig.theme?.[theme as keyof typeof itemConfig.theme] || itemConfig.color return color ? ` --color-${key}: ${color};` : null }) .join("\n")} } -` +`, ) .join("\n"), }} @@ -100,161 +83,48 @@ ${colorConfig ) } -const ChartTooltip = RechartsPrimitive.Tooltip +interface ChartTooltipProps { + children?: React.ReactNode +} -const ChartTooltipContent = React.forwardRef< - HTMLDivElement, - React.ComponentProps & - React.ComponentProps<"div"> & { - hideLabel?: boolean - hideIndicator?: boolean - indicator?: "line" | "dot" | "dashed" - nameKey?: string - labelKey?: string - } ->( - ( - { - active, - payload, - className, - indicator = "dot", - hideLabel = false, - hideIndicator = false, - label, - labelFormatter, - labelClassName, - formatter, - color, - nameKey, - labelKey, - }, - ref - ) => { - const { config } = useChart() +const ChartTooltip = React.forwardRef(({ className, children, ...props }, ref) => { + return
+}) +ChartTooltip.displayName = "ChartTooltip" - const tooltipLabel = React.useMemo(() => { - if (hideLabel || !payload?.length) { - return null - } +interface ChartTooltipContentProps extends React.HTMLAttributes { + active?: boolean + payload?: any[] + label?: string + labelFormatter?: (value: any) => string + hideLabel?: boolean +} - const [item] = payload - const key = `${labelKey || item.dataKey || item.name || "value"}` - const itemConfig = getPayloadConfigFromPayload(config, item, key) - const value = - !labelKey && typeof label === "string" - ? config[label as keyof typeof config]?.label || label - : itemConfig?.label - - if (labelFormatter) { - return ( -
- {labelFormatter(value, payload)} -
- ) - } - - if (!value) { - return null - } - - return
{value}
- }, [ - label, - labelFormatter, - payload, - hideLabel, - labelClassName, - config, - labelKey, - ]) - - if (!active || !payload?.length) { +const ChartTooltipContent = React.forwardRef( + ({ active, payload, label, labelFormatter, hideLabel, className, ...props }, ref) => { + if (!active || !payload) { return null } - const nestLabel = payload.length === 1 && indicator !== "dot" - return ( -
+ {!hideLabel && label && ( +
{labelFormatter ? labelFormatter(label) : label}
)} - > - {!nestLabel ? tooltipLabel : null} -
- {payload.map((item, index) => { - const key = `${nameKey || item.name || item.dataKey || "value"}` - const itemConfig = getPayloadConfigFromPayload(config, item, key) - const indicatorColor = color || item.payload.fill || item.color - - return ( -
svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground", - indicator === "dot" && "items-center" - )} - > - {formatter && item?.value !== undefined && item.name ? ( - formatter(item.value, item.name, item, index, item.payload) - ) : ( - <> - {itemConfig?.icon ? ( - - ) : ( - !hideIndicator && ( -
- ) - )} -
-
- {nestLabel ? tooltipLabel : null} - - {itemConfig?.label || item.name} - -
- {item.value && ( - - {item.value.toLocaleString()} - - )} -
- - )} -
- ) - })} +
+ {payload.map((entry, index) => ( +
+
+ {entry.name}: + {entry.value} +
+ ))}
) - } + }, ) -ChartTooltipContent.displayName = "ChartTooltip" +ChartTooltipContent.displayName = "ChartTooltipContent" const ChartLegend = RechartsPrimitive.Legend @@ -265,101 +135,70 @@ const ChartLegendContent = React.forwardRef< hideIcon?: boolean nameKey?: string } ->( - ( - { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey }, - ref - ) => { - const { config } = useChart() +>(({ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey }, ref) => { + const { config } = useChart() - if (!payload?.length) { - return null - } - - return ( -
- {payload.map((item) => { - const key = `${nameKey || item.dataKey || "value"}` - const itemConfig = getPayloadConfigFromPayload(config, item, key) - - return ( -
svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground" - )} - > - {itemConfig?.icon && !hideIcon ? ( - - ) : ( -
- )} - {itemConfig?.label} -
- ) - })} -
- ) + if (!payload?.length) { + return null } -) + + return ( +
+ {payload.map((item) => { + const key = `${nameKey || item.dataKey || "value"}` + const itemConfig = getPayloadConfigFromPayload(config, item, key) + + return ( +
svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground")} + > + {itemConfig?.icon && !hideIcon ? ( + + ) : ( +
+ )} + {itemConfig?.label} +
+ ) + })} +
+ ) +}) ChartLegendContent.displayName = "ChartLegend" // Helper to extract item config from a payload. -function getPayloadConfigFromPayload( - config: ChartConfig, - payload: unknown, - key: string -) { +function getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key: string) { if (typeof payload !== "object" || payload === null) { return undefined } const payloadPayload = - "payload" in payload && - typeof payload.payload === "object" && - payload.payload !== null + "payload" in payload && typeof payload.payload === "object" && payload.payload !== null ? payload.payload : undefined let configLabelKey: string = key - if ( - key in payload && - typeof payload[key as keyof typeof payload] === "string" - ) { + if (key in payload && typeof payload[key as keyof typeof payload] === "string") { configLabelKey = payload[key as keyof typeof payload] as string } else if ( payloadPayload && key in payloadPayload && typeof payloadPayload[key as keyof typeof payloadPayload] === "string" ) { - configLabelKey = payloadPayload[ - key as keyof typeof payloadPayload - ] as string + configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string } - return configLabelKey in config - ? config[configLabelKey] - : config[key as keyof typeof config] + return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config] } -export { - ChartContainer, - ChartTooltip, - ChartTooltipContent, - ChartLegend, - ChartLegendContent, - ChartStyle, -} +export { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent, ChartStyle } diff --git a/Cunkebao/components/ui/checkbox.tsx b/Cunkebao/components/ui/checkbox.tsx index df61a138..c6546d5b 100644 --- a/Cunkebao/components/ui/checkbox.tsx +++ b/Cunkebao/components/ui/checkbox.tsx @@ -1,8 +1,6 @@ -"use client" - import * as React from "react" import * as CheckboxPrimitive from "@radix-ui/react-checkbox" -import { Check } from "lucide-react" +import { CheckIcon } from "@radix-ui/react-icons" import { cn } from "@/lib/utils" @@ -13,15 +11,13 @@ const Checkbox = React.forwardRef< - - + + )) diff --git a/Cunkebao/components/ui/collapsible.tsx b/Cunkebao/components/ui/collapsible.tsx index 9fa48946..67a8c899 100644 --- a/Cunkebao/components/ui/collapsible.tsx +++ b/Cunkebao/components/ui/collapsible.tsx @@ -1,5 +1,4 @@ "use client" - import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" const Collapsible = CollapsiblePrimitive.Root diff --git a/Cunkebao/components/ui/date-picker.tsx b/Cunkebao/components/ui/date-picker.tsx new file mode 100644 index 00000000..5c1fc416 --- /dev/null +++ b/Cunkebao/components/ui/date-picker.tsx @@ -0,0 +1,87 @@ +"use client" +import { CalendarIcon } from "lucide-react" +import { format } from "date-fns" +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { Calendar } from "@/components/ui/calendar" +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" +import type { DateRange } from "react-day-picker" + +export interface DatePickerProps { + date: Date | undefined + setDate: (date: Date | undefined) => void + className?: string + placeholder?: string + disabled?: boolean +} + +export function DatePicker({ date, setDate, className, placeholder = "选择日期", disabled = false }: DatePickerProps) { + return ( + + + + + + + + + ) +} + +export interface DatePickerWithRangeProps { + date: DateRange | undefined + setDate: (date: DateRange | undefined) => void + className?: string + placeholder?: string + disabled?: boolean +} + +export function DatePickerWithRange({ + date, + setDate, + className, + placeholder = "选择日期范围", + disabled = false, +}: DatePickerWithRangeProps) { + return ( + + + + + + + + + ) +} diff --git a/Cunkebao/components/ui/date-range-picker.tsx b/Cunkebao/components/ui/date-range-picker.tsx index 7766ec0d..8542cd0f 100644 --- a/Cunkebao/components/ui/date-range-picker.tsx +++ b/Cunkebao/components/ui/date-range-picker.tsx @@ -49,11 +49,9 @@ export function DateRangePicker({ className, value, onChange }: DateRangePickerP onSelect={onChange} numberOfMonths={2} locale={zhCN} - showOutsideDays={false} />
) } - diff --git a/Cunkebao/components/ui/dialog.tsx b/Cunkebao/components/ui/dialog.tsx index 01ff19c7..6c60ee6d 100644 --- a/Cunkebao/components/ui/dialog.tsx +++ b/Cunkebao/components/ui/dialog.tsx @@ -1,8 +1,6 @@ -"use client" - import * as React from "react" import * as DialogPrimitive from "@radix-ui/react-dialog" -import { X } from "lucide-react" +import { Cross2Icon } from "@radix-ui/react-icons" import { cn } from "@/lib/utils" @@ -22,7 +20,7 @@ const DialogOverlay = React.forwardRef< ref={ref} className={cn( "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", - className + className, )} {...props} /> @@ -39,13 +37,13 @@ const DialogContent = React.forwardRef< ref={ref} className={cn( "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", - className + className, )} {...props} > {children} - + Close @@ -53,31 +51,13 @@ const DialogContent = React.forwardRef< )) DialogContent.displayName = DialogPrimitive.Content.displayName -const DialogHeader = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
+const DialogHeader = ({ className, ...props }: React.HTMLAttributes) => ( +
) DialogHeader.displayName = "DialogHeader" -const DialogFooter = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
+const DialogFooter = ({ className, ...props }: React.HTMLAttributes) => ( +
) DialogFooter.displayName = "DialogFooter" @@ -87,10 +67,7 @@ const DialogTitle = React.forwardRef< >(({ className, ...props }, ref) => ( )) @@ -100,11 +77,7 @@ const DialogDescription = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )) DialogDescription.displayName = DialogPrimitive.Description.displayName @@ -112,8 +85,8 @@ export { Dialog, DialogPortal, DialogOverlay, - DialogClose, DialogTrigger, + DialogClose, DialogContent, DialogHeader, DialogFooter, diff --git a/Cunkebao/components/ui/dropdown-menu.tsx b/Cunkebao/components/ui/dropdown-menu.tsx index 0fc4c0e0..dd2b97af 100644 --- a/Cunkebao/components/ui/dropdown-menu.tsx +++ b/Cunkebao/components/ui/dropdown-menu.tsx @@ -1,8 +1,6 @@ -"use client" - import * as React from "react" import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" -import { Check, ChevronRight, Circle } from "lucide-react" +import { CheckIcon, ChevronRightIcon, DotFilledIcon } from "@radix-ui/react-icons" import { cn } from "@/lib/utils" @@ -27,18 +25,17 @@ const DropdownMenuSubTrigger = React.forwardRef< {children} - + )) -DropdownMenuSubTrigger.displayName = - DropdownMenuPrimitive.SubTrigger.displayName +DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName const DropdownMenuSubContent = React.forwardRef< React.ElementRef, @@ -48,13 +45,12 @@ const DropdownMenuSubContent = React.forwardRef< ref={ref} className={cn( "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", - className + className, )} {...props} /> )) -DropdownMenuSubContent.displayName = - DropdownMenuPrimitive.SubContent.displayName +DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName const DropdownMenuContent = React.forwardRef< React.ElementRef, @@ -65,8 +61,9 @@ const DropdownMenuContent = React.forwardRef< ref={ref} sideOffset={sideOffset} className={cn( - "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", - className + "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md", + "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", + className, )} {...props} /> @@ -83,9 +80,9 @@ const DropdownMenuItem = React.forwardRef< @@ -100,21 +97,20 @@ const DropdownMenuCheckboxItem = React.forwardRef< ref={ref} className={cn( "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", - className + className, )} checked={checked} {...props} > - + {children} )) -DropdownMenuCheckboxItem.displayName = - DropdownMenuPrimitive.CheckboxItem.displayName +DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName const DropdownMenuRadioItem = React.forwardRef< React.ElementRef, @@ -124,13 +120,13 @@ const DropdownMenuRadioItem = React.forwardRef< ref={ref} className={cn( "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", - className + className, )} {...props} > - + {children} @@ -146,11 +142,7 @@ const DropdownMenuLabel = React.forwardRef< >(({ className, inset, ...props }, ref) => ( )) @@ -160,24 +152,12 @@ const DropdownMenuSeparator = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )) DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName -const DropdownMenuShortcut = ({ - className, - ...props -}: React.HTMLAttributes) => { - return ( - - ) +const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => { + return } DropdownMenuShortcut.displayName = "DropdownMenuShortcut" diff --git a/Cunkebao/components/ui/input.tsx b/Cunkebao/components/ui/input.tsx index 68551b92..31bbca4f 100644 --- a/Cunkebao/components/ui/input.tsx +++ b/Cunkebao/components/ui/input.tsx @@ -2,21 +2,21 @@ import * as React from "react" import { cn } from "@/lib/utils" -const Input = React.forwardRef>( - ({ className, type, ...props }, ref) => { - return ( - - ) - } -) +export interface InputProps extends React.InputHTMLAttributes {} + +const Input = React.forwardRef(({ className, type, ...props }, ref) => { + return ( + + ) +}) Input.displayName = "Input" export { Input } diff --git a/Cunkebao/components/ui/label.tsx b/Cunkebao/components/ui/label.tsx index 53418217..9b5ca346 100644 --- a/Cunkebao/components/ui/label.tsx +++ b/Cunkebao/components/ui/label.tsx @@ -1,25 +1,16 @@ -"use client" - import * as React from "react" import * as LabelPrimitive from "@radix-ui/react-label" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" -const labelVariants = cva( - "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" -) +const labelVariants = cva("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70") const Label = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef & - VariantProps + React.ComponentPropsWithoutRef & VariantProps >(({ className, ...props }, ref) => ( - + )) Label.displayName = LabelPrimitive.Root.displayName diff --git a/Cunkebao/components/ui/pagination.tsx b/Cunkebao/components/ui/pagination.tsx index ea40d196..f2d27513 100644 --- a/Cunkebao/components/ui/pagination.tsx +++ b/Cunkebao/components/ui/pagination.tsx @@ -1,8 +1,8 @@ import * as React from "react" -import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react" +import { ChevronLeftIcon, ChevronRightIcon, DotsHorizontalIcon } from "@radix-ui/react-icons" import { cn } from "@/lib/utils" -import { ButtonProps, buttonVariants } from "@/components/ui/button" +import { type ButtonProps, buttonVariants } from "@/components/ui/button" const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (