mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-27 21:20:29 +08:00
profile page
This commit is contained in:
parent
27f2f70f1b
commit
eb90aa2819
@ -44,7 +44,7 @@ const App: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="w-full h-[100vh] flex">
|
<div className="w-full h-[100vh] flex">
|
||||||
<div className="side w-[250px] h-full overflow-y-auto no-scrollbar">
|
<div className="side w-[250px] h-full overflow-y-auto no-scrollbar">
|
||||||
<div className="sticky top-0 z-40 backdrop-blur h-[48px]">
|
<div className="sticky top-0 z-40 backdrop-blur bg-background/70 h-[48px]">
|
||||||
<div className="flex justify-between p-2">
|
<div className="flex justify-between p-2">
|
||||||
<h3 className="select-none text-lg font-bold leading-[32px]">Mihomo Party</h3>
|
<h3 className="select-none text-lg font-bold leading-[32px]">Mihomo Party</h3>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -10,7 +10,7 @@ interface Props {
|
|||||||
const BasePage: React.FC<Props> = (props) => {
|
const BasePage: React.FC<Props> = (props) => {
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full overflow-y-auto custom-scrollbar">
|
<div className="w-full h-full overflow-y-auto custom-scrollbar">
|
||||||
<div className="sticky top-0 z-40 h-[48px] w-full backdrop-blur">
|
<div className="sticky top-0 z-40 h-[48px] w-full backdrop-blur bg-background/70">
|
||||||
<div className="p-2 flex justify-between">
|
<div className="p-2 flex justify-between">
|
||||||
<div className="select-none title h-full text-lg leading-[32px]">{props.title}</div>
|
<div className="select-none title h-full text-lg leading-[32px]">{props.title}</div>
|
||||||
<div className="header h-full">{props.header}</div>
|
<div className="header h-full">{props.header}</div>
|
||||||
|
|||||||
41
src/renderer/src/components/profiles/profile-item.tsx
Normal file
41
src/renderer/src/components/profiles/profile-item.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { Button, Card, CardBody, CardFooter, Progress } from '@nextui-org/react'
|
||||||
|
import { calcPercent, calcTraffic } from '@renderer/utils/calc'
|
||||||
|
import React from 'react'
|
||||||
|
import { IoMdRefresh } from 'react-icons/io'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
info: IProfileItem
|
||||||
|
isCurrent: boolean
|
||||||
|
onClick: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProfileItem: React.FC<Props> = (props) => {
|
||||||
|
const { info, onClick, isCurrent } = props
|
||||||
|
const extra = info?.extra
|
||||||
|
const usage = (extra?.upload ?? 0) + (extra?.download ?? 0)
|
||||||
|
const total = extra?.total ?? 0
|
||||||
|
return (
|
||||||
|
<Card fullWidth isPressable onPress={onClick} className={isCurrent ? 'bg-primary' : ''}>
|
||||||
|
<CardBody>
|
||||||
|
<div className="flex justify-between h-[32px]">
|
||||||
|
<h3 className="select-none text-ellipsis whitespace-nowrap overflow-hidden text-md font-bold leading-[32px]">
|
||||||
|
{info?.name}
|
||||||
|
</h3>
|
||||||
|
<Button isIconOnly size="sm" variant="light" color="default">
|
||||||
|
<IoMdRefresh color="default" className="text-[24px]" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardBody>
|
||||||
|
<CardFooter className="pt-1">
|
||||||
|
<Progress
|
||||||
|
classNames={{ indicator: 'bg-foreground', label: 'select-none' }}
|
||||||
|
label={extra ? `${calcTraffic(usage)}/${calcTraffic(total)}` : undefined}
|
||||||
|
value={calcPercent(extra?.upload, extra?.download, extra?.total)}
|
||||||
|
className="max-w-md"
|
||||||
|
/>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProfileItem
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { Button, Card, CardBody, CardFooter, Progress } from '@nextui-org/react'
|
import { Button, Card, CardBody, CardFooter, Progress } from '@nextui-org/react'
|
||||||
import { useProfileConfig } from '@renderer/hooks/use-profile'
|
import { useProfileConfig } from '@renderer/hooks/use-profile'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
import { calcTraffic } from '@renderer/utils/calc'
|
import { calcTraffic, calcPercent } from '@renderer/utils/calc'
|
||||||
import { IoMdRefresh } from 'react-icons/io'
|
import { IoMdRefresh } from 'react-icons/io'
|
||||||
|
|
||||||
const ProfileCard: React.FC = () => {
|
const ProfileCard: React.FC = () => {
|
||||||
@ -50,14 +50,3 @@ const ProfileCard: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default ProfileCard
|
export default ProfileCard
|
||||||
|
|
||||||
function calcPercent(
|
|
||||||
upload: number | undefined,
|
|
||||||
download: number | undefined,
|
|
||||||
total: number | undefined
|
|
||||||
): number {
|
|
||||||
if (upload === undefined || download === undefined || total === undefined) {
|
|
||||||
return 100
|
|
||||||
}
|
|
||||||
return Math.round(((upload + download) / total) * 100)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import { Button, Input } from '@nextui-org/react'
|
import { Button, Input } from '@nextui-org/react'
|
||||||
import BasePage from '@renderer/components/base/base-page'
|
import BasePage from '@renderer/components/base/base-page'
|
||||||
|
import ProfileItem from '@renderer/components/profiles/profile-item'
|
||||||
import { useProfileConfig } from '@renderer/hooks/use-profile'
|
import { useProfileConfig } from '@renderer/hooks/use-profile'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { MdContentPaste } from 'react-icons/md'
|
import { MdContentPaste } from 'react-icons/md'
|
||||||
|
|
||||||
const Profiles: React.FC = () => {
|
const Profiles: React.FC = () => {
|
||||||
const { profileConfig, addProfileItem } = useProfileConfig()
|
const { profileConfig, addProfileItem } = useProfileConfig()
|
||||||
|
const { current, items } = profileConfig || {}
|
||||||
const [importing, setImporting] = useState(false)
|
const [importing, setImporting] = useState(false)
|
||||||
const [url, setUrl] = useState('')
|
const [url, setUrl] = useState('')
|
||||||
|
|
||||||
@ -22,7 +24,7 @@ const Profiles: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<BasePage title="订阅">
|
<BasePage title="订阅">
|
||||||
<div className="flex m-2">
|
<div className="sticky top-[48px] z-40 backdrop-blur bg-background/70 flex p-2">
|
||||||
<Input
|
<Input
|
||||||
variant="bordered"
|
variant="bordered"
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
@ -48,7 +50,16 @@ const Profiles: React.FC = () => {
|
|||||||
导入
|
导入
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{JSON.stringify(profileConfig)}
|
<div className="grid sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-2 mx-2">
|
||||||
|
{items?.map((item) => (
|
||||||
|
<ProfileItem
|
||||||
|
key={item.id}
|
||||||
|
isCurrent={item.id === current}
|
||||||
|
info={item}
|
||||||
|
onClick={() => {}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</BasePage>
|
</BasePage>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,3 +17,14 @@ export function calcTraffic(bit: number): string {
|
|||||||
bit /= 1024
|
bit /= 1024
|
||||||
return `${bit.toFixed(2)} YB`
|
return `${bit.toFixed(2)} YB`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function calcPercent(
|
||||||
|
upload: number | undefined,
|
||||||
|
download: number | undefined,
|
||||||
|
total: number | undefined
|
||||||
|
): number {
|
||||||
|
if (upload === undefined || download === undefined || total === undefined) {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
return Math.round(((upload + download) / total) * 100)
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user