This commit is contained in:
2026-07-14 16:12:29 +08:00
parent 91ffce9f36
commit 35aa9d2d13
22 changed files with 764 additions and 119 deletions
+18
View File
@@ -0,0 +1,18 @@
import { route } from "preact-router"
const Dashboard = () => {
return (
<div class="app-dashboard">
<div class="dashboard-sidebar">
<a href="/dashboard/datasets" onClick={(e) => { e.preventDefault(); route("/dashboard/datasets") }}></a>
<a href="/dashboard/testsets" onClick={(e) => { e.preventDefault(); route("/dashboard/testsets") }}></a>
<a href="/dashboard/logs" onClick={(e) => { e.preventDefault(); route("/dashboard/logs") }}></a>
</div>
<div class="dashboard-content">
{/* 子页面内容通过 Router 渲染到这里 */}
</div>
</div>
)
}
export default Dashboard
+6 -4
View File
@@ -1,9 +1,11 @@
export const Footer = () => {
return (
<footer class="h-[120px] bg-black border-t border-border py-8 px-4 md:px-6 lg:px-8">
<div class="max-w-6xl mx-auto text-center">
<p class="text-text/50 text-sm">Made with for cat lovers everywhere</p>
</div>
<footer class="app-footer">
<span>https://pytorch.org/</span>
<span>|</span>
<span>https://wails.io/</span>
<span>|</span>
<span>https://preactjs.com/</span>
</footer>
)
}
+104
View File
@@ -0,0 +1,104 @@
import { useState, useEffect } from 'preact/hooks'
import Pagination from './Pagination'
const History = () => {
const [page, setPage] = useState<number>(1)
const [total, setTotal] = useState<number>(50)
const handlePageChange = (page: number) => {
// setPage(page)
// loadTranscript(currentNav, currentOpt, page).then(res => {
// if (res && res.code === 0) {
// setData(res.data.data)
// setTotal(res.data.total)
// }
// }).catch(e => message.error(e.toString()))
}
return (
<div class="app-history">
<div class="history-card">
<div class="card-left">
<img src="../assets/images/0012.jpg" />
</div>
<div class="card-right">
<div class="right-top">
<h3>Maine Coon</h3>
<span>Jan 15, 2024</span>
</div>
<div class="right-overview">
The Maine Coon is a large, friendly cat breed known for its tufted ears.
</div>
</div>
</div>
<div class="history-card">
<div class="card-left">
<img src="../assets/images/0012.jpg" />
</div>
<div class="card-right">
<div class="right-top">
<h3>Maine Coon</h3>
<span>Jan 15, 2024</span>
</div>
<div class="right-overview">
The Maine Coon is a large, friendly cat breed known for its tufted ears.
</div>
</div>
</div>
<div class="history-card">
<div class="card-left">
<img src="../assets/images/0012.jpg" />
</div>
<div class="card-right">
<div class="right-top">
<h3>Maine Coon</h3>
<span>Jan 15, 2024</span>
</div>
<div class="right-overview">
The Maine Coon is a large, friendly cat breed known for its tufted ears.
</div>
</div>
</div>
<div class="history-card">
<div class="card-left">
<img src="../assets/images/0012.jpg" />
</div>
<div class="card-right">
<div class="right-top">
<h3>Maine Coon</h3>
<span>Jan 15, 2024</span>
</div>
<div class="right-overview">
The Maine Coon is a large, friendly cat breed known for its tufted ears.
</div>
</div>
</div>
<div class="history-card">
<div class="card-left">
<img src="../assets/images/0012.jpg" />
</div>
<div class="card-right">
<div class="right-top">
<h3>Maine Coon</h3>
<span>Jan 15, 2024</span>
</div>
<div class="right-overview">
The Maine Coon is a large, friendly cat breed known for its tufted ears.
</div>
</div>
</div>
<div class="history-pagination">
<Pagination page={page} pagesize={5} total={total} onChange={handlePageChange} />
</div>
</div>
)
}
export default History
+33 -32
View File
@@ -1,39 +1,38 @@
export const Main = () => {
const Main = () => {
return (
<main class="h-[648px] pt-28 pb-16 px-4 md:px-6 lg:px-8">
<div class="max-w-6xl mx-auto">
{/* <div class="text-center mb-12 animate-slide-up">
<h1 class="font-heading font-bold text-4xl md:text-5xl lg:text-6xl text-text mb-4">
Discover Your Cat's <span class="text-primary">Breed</span>
<main class="app-main">
<div class="main-container">
<div class="title">
<h1>
<span></span><span></span>
</h1>
<p class="font-body text-lg md:text-xl text-text/70 max-w-2xl mx-auto">
Upload a photo of your furry friend and let our AI identify their breed in seconds
<p>
AI不仅认出它<span></span>
</p>
</div> */}
{/* <div class="max-w-2xl mx-auto mb-12 animate-slide-up" style="animation-delay: 0.1s;">
<i></i>
</div>
<div class="upload">
<div
id="dropZone"
class="drop-zone bg-white rounded-3xl border-2 border-dashed border-border p-12 text-center cursor-pointer"
class="upload-zone"
>
<div id="uploadContent">
<svg class="w-16 h-16 mx-auto mb-4 text-secondary animate-pulse-slow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 16.5V9.75m0 0l3 3m-3-3l-3 3M6.75 19.5a4.5 4.5 0 01-1.41-8.775 5.25 5.25 0 0110.233-2.33 3 3 0 013.758 3.848A3.752 3.752 0 0118 19.5H6.75z" />
</svg>
<p class="font-heading font-semibold text-xl text-text mb-2">Drop your cat's photo here</p>
<p class="text-text/60 mb-4">or click to browse</p>
<p class="text-sm text-text/50">Supports JPG, PNG up to 10MB</p>
</div>
<div id="previewContent" class="hidden">
<img id="previewImage" class="max-h-64 mx-auto rounded-2xl mb-4 object-cover" alt="Cat preview" />
<button id="removeBtn" class="text-red-500 hover:text-red-600 font-medium transition-colors duration-200 cursor-pointer">
Remove photo
</button>
</div>
<input type="file" id="fileInput" class="hidden" accept="image/*" />
<div id="uploadContent">
<svg class="w-16 h-16 mx-auto mb-4 text-secondary animate-pulse-slow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 16.5V9.75m0 0l3 3m-3-3l-3 3M6.75 19.5a4.5 4.5 0 01-1.41-8.775 5.25 5.25 0 0110.233-2.33 3 3 0 013.758 3.848A3.752 3.752 0 0118 19.5H6.75z" />
</svg>
<p class="font-heading font-semibold text-xl text-text mb-2"></p>
<p class="text-sm text-text/50">JPGPNGJPEG2MB</p>
</div>
{/* <div id="previewContent" class="hidden">
<img id="previewImage" class="max-h-64 mx-auto rounded-2xl mb-4 object-cover" alt="Cat preview" />
<button id="removeBtn" class="text-red-500 hover:text-red-600 font-medium transition-colors duration-200 cursor-pointer">
Remove photo
</button>
</div>
<input type="file" id="fileInput" class="hidden" accept="image/*" /> */}
</div>
<button
{/* <button
id="detectBtn"
class="hidden w-full mt-4 bg-cta hover:bg-orange-600 text-white font-heading font-semibold text-lg py-4 rounded-2xl transition-colors duration-200 cursor-pointer flex items-center justify-center gap-3"
>
@@ -41,8 +40,8 @@ export const Main = () => {
<path stroke-linecap="round" stroke-linejoin="round" d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456z" />
</svg>
Detect Breed
</button>
</div> */}
</button> */}
</div>
{/* <div id="loadingState" class="hidden max-w-2xl mx-auto mb-12">
<div class="bg-white rounded-3xl border border-border p-8 text-center">
@@ -60,7 +59,7 @@ export const Main = () => {
<h2 class="font-heading font-bold text-2xl text-text">Detection Complete!</h2>
</div>
<div id="primaryResult" class="bg-white rounded-3xl border border-border p-6 md:p-8 mb-6 animate-slide-up">
<div id="primaryResult" class="bg-white rounded-3xl border border-border p-6 md:p-8 mb-6">
<div class="flex flex-col md:flex-row gap-6 md:gap-8">
<div class="flex-shrink-0">
<img id="resultImage" class="w-full md:w-48 h-48 rounded-2xl object-cover" alt="Cat photo" />
@@ -141,4 +140,6 @@ export const Main = () => {
</div>
</main>
)
}
}
export default Main
+17 -16
View File
@@ -1,21 +1,22 @@
export const Navbar = () => {
const svgPath = "M12 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-4.5 4c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm9 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-9.5 3.5c-1.38 0-2.5 1.12-2.5 2.5s1.12 2.5 2.5 2.5 2.5-1.12 2.5-2.5-1.12-2.5-2.5-2.5zm11 0c-1.38 0-2.5 1.12-2.5 2.5s1.12 2.5 2.5 2.5 2.5-1.12 2.5-2.5-1.12-2.5-2.5-2.5zM12 18c-2.21 0-4 1.79-4 4h8c0-2.21-1.79-4-4-4z"
const handleClick = () => window.open('https://git.leonstack.com/Leon/Collage-Images', '_blank')
return (
<nav class="fixed top-4 left-4 right-4 z-50 animate-fade-in">
<div class="max-w-6xl mx-auto bg-white backdrop-blur-md rounded-2xl shadow-lg border border-border px-6 py-4">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3 cursor-pointer">
<svg class="w-8 h-8 text-primary" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-4.5 4c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm9 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-9.5 3.5c-1.38 0-2.5 1.12-2.5 2.5s1.12 2.5 2.5 2.5 2.5-1.12 2.5-2.5-1.12-2.5-2.5-2.5zm11 0c-1.38 0-2.5 1.12-2.5 2.5s1.12 2.5 2.5 2.5 2.5-1.12 2.5-2.5-1.12-2.5-2.5-2.5zM12 18c-2.21 0-4 1.79-4 4h8c0-2.21-1.79-4-4-4z"/>
</svg>
<span class="font-heading font-semibold text-xl text-text">MeowVision</span>
</div>
<div class="flex items-center gap-6">
<a href="#" class="text-text/70 hover:text-primary transition-colors duration-200 font-medium cursor-pointer"></a>
<a href="#" class="text-text/70 hover:text-primary transition-colors duration-200 font-medium cursor-pointer"></a>
<button class="bg-primary hover:bg-blue-600 text-white px-5 py-2.5 rounded-xl font-semibold transition-colors duration-200 cursor-pointer">
</button>
</div>
<nav class="app-navbar animate-fade-in">
<div class="navbar-container">
<div class="container-left">
<svg class="icon" viewBox="0 0 24 24" fill="currentColor">
<path d={svgPath} />
</svg>
<span class="font-heading font-semibold text-xl text-text">miao</span>
</div>
<div class="container-right">
<a href="/"></a>
<a href="/history"></a>
<a href="/dashboard"></a>
<button onClick={handleClick}></button>
</div>
</div>
</nav>
+214
View File
@@ -0,0 +1,214 @@
import { useState, useEffect } from 'preact/hooks'
interface IPagination {
total: number
onChange: (page: number) => void
page: number
pagesize: number
}
const Pagination = ({
total,
onChange,
page,
pagesize,
}: IPagination) => {
const [value, setValue] = useState<string>('1')
const [num, setNum] = useState<number>(1)
const [seqNums, setSeqNums] = useState<[number, number][]>([])
useEffect(() => {
setValue('1')
setNum(1)
}, [])
useEffect(() => {
if (page) {
setValue(page.toString());
let nums: [number, number][] = []
for (let i = 1; i < total / pagesize + 1; i++) {
if (i === 1) {
nums.push([1, 1])
} else {
nums.push([i, 0])
}
}
setSeqNums(nums)
calSeqNums(nums, page)
}
}, [total, page])
const handleChange = (e: Event) => {
const target = e.target as HTMLInputElement
setValue(target.value)
}
const handleEnterPress = (e: KeyboardEvent) => {
if (e.key === 'Enter') {
if (parseInt(value) <= seqNums.length && parseInt(value) > 0) {
handleChangeNum(parseInt(value))
} else if (parseInt(value) >= seqNums.length) {
handleChangeNum(seqNums.length)
} else if (parseInt(value) < seqNums.length) {
handleChangeNum(1)
} else {
handleChangeNum(1)
}
}
}
const calSeqNums = (seqNums: [number, number][], num: number) => {
const seqNumsTemp = seqNums.map((item): [number, number] =>
item[0] === num ? [item[0], 1] : [item[0], 0],
)
setSeqNums(seqNumsTemp)
setNum(num)
}
const handleChangeNum = (val: number|string) => {
if (typeof val === 'number') {
setValue(String(val))
if (val !== num) onChange(val)
calSeqNums(seqNums, val)
} else if (typeof val === 'string') {
if (val === 'pre') {
if (num > 1) {
calSeqNums(seqNums, num - 1)
setValue(String(num - 1))
onChange(num - 1)
}
} else if (val === 'next') {
if (num < seqNums.length) {
calSeqNums(seqNums, num + 1)
setValue(String(num + 1))
onChange(num + 1)
}
}
}
}
const calPaginationDom = () => {
return seqNums.map((item) => (
<span
key={item[0]}
className={`pagination-num${
item[1] === 1 ? ' pagination-num-selected' : ''
}`}
onClick={() => handleChangeNum(item[0])}
>
{item[0]}
</span>
))
}
const calPaginationDomEtc = () => {
return (
<>
{num < 5 && (
<>
{seqNums.slice(0, 5).map((item) => (
<span
key={item[0]}
className={`pagination-num${
item[1] === 1 ? ' pagination-num-selected' : ''
}`}
onClick={() => handleChangeNum(item[0])}
>
{item[0]}
</span>
))}
<span className="pagination-etc"> ... </span>
<span
className="pagination-num"
onClick={() => handleChangeNum(seqNums.length)}
>
{seqNums.length}
</span>
</>
)}
{num >= 5 && num <= seqNums.length - 4 && (
<>
<span className="pagination-num" onClick={() => handleChangeNum(1)}>
1
</span>
<span className="pagination-etc"> ... </span>
{seqNums.slice(num - 3, num + 2).map((item) => (
<span
key={item[0]}
className={`pagination-num${
item[1] === 1 ? ' pagination-num-selected' : ''
}`}
onClick={() => handleChangeNum(item[0])}
>
{item[0]}
</span>
))}
<span className="pagination-etc"> ... </span>
<span
className="pagination-num"
onClick={() => handleChangeNum(seqNums.length)}
>
{seqNums.length}
</span>
</>
)}
{num > seqNums.length - 4 && (
<>
<span
key={1}
className="pagination-num"
onClick={() => handleChangeNum(1)}
>
1
</span>
<span className="pagination-etc"> ... </span>
{seqNums.slice(seqNums.length - 5, seqNums.length).map((item) => (
<span
key={item[0]}
className={`pagination-num${
item[1] === 1 ? ' pagination-num-selected' : ''
}`}
onClick={() => handleChangeNum(item[0])}
>
{item[0]}
</span>
))}
</>
)}
</>
)
}
return (
<div className="div-pagination">
<div className="div-pagination-core unselect">
<span
className="pagination-icon"
onClick={() => handleChangeNum('pre')}
>
{/* <img src="../static/images/page_left.svg" alt="" /> */}
</span>
{seqNums.length < 10 ? calPaginationDom() : calPaginationDomEtc()}
<span
className="pagination-icon"
onClick={() => handleChangeNum('next')}
>
{/* <img src="../static/images/page_right.svg" alt="" /> */}
</span>
<span className="pagination-word" style={{ marginLeft: 16 }}>
</span>
<input
value={value}
onChange={handleChange}
onKeyDown={handleEnterPress}
/>
<span className="pagination-word"></span>
</div>
</div>
)
}
export default Pagination
@@ -0,0 +1,7 @@
const TestsetManagement = () => {
return (
<div></div>
)
}
export default TestsetManagement
@@ -0,0 +1,7 @@
const TrainsetManagement = () => {
return (
<div></div>
)
}
export default TrainsetManagement