Add AboutPage component and integrate sorting options in resource retrieval

This commit is contained in:
2025-05-25 11:27:24 +08:00
parent b610a5db9d
commit 6e78707d9e
11 changed files with 139 additions and 27 deletions

View File

@@ -10,6 +10,7 @@ import ManagePage from "./pages/manage_page.tsx";
import TaggedResourcesPage from "./pages/tagged_resources_page.tsx";
import UserPage from "./pages/user_page.tsx";
import EditResourcePage from "./pages/edit_resource_page.tsx";
import AboutPage from "./pages/about_page.tsx";
export default function App() {
return (
@@ -26,6 +27,7 @@ export default function App() {
<Route path={"/tag/:tag"} element={<TaggedResourcesPage/>}/>
<Route path={"/user/:username"} element={<UserPage/>}/>
<Route path={"/resource/edit/:rid"} element={<EditResourcePage/>}/>
<Route path={"/about"} element={<AboutPage/>}/>
</Route>
</Routes>
</BrowserRouter>

View File

@@ -319,6 +319,15 @@ export const i18nData = {
"Set the description of the tag.": "设置标签的描述。",
"Use markdown format.": "使用Markdown格式。",
"Tag: ": "标签: ",
// 添加排序选项翻译
"Select a Order": "选择排序方式",
"Latest": "最新",
"Oldest": "最早",
"Most Viewed": "浏览最多",
"Least Viewed": "浏览最少",
"Most Downloaded": "下载最多",
"Least Downloaded": "下载最少",
}
},
"zh-TW": {
@@ -480,6 +489,15 @@ export const i18nData = {
"Set the description of the tag.": "設置標籤的描述。",
"Use markdown format.": "使用Markdown格式。",
"Tag: ": "標籤: ",
// 添加排序选项翻译
"Select a Order": "選擇排序方式",
"Latest": "最新",
"Oldest": "最早",
"Most Viewed": "瀏覽最多",
"Least Viewed": "瀏覽最少",
"Most Downloaded": "下載最多",
"Least Downloaded": "下載最少",
}
}
}

View File

@@ -84,6 +84,9 @@ article {
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, system-ui;
}
.line-break {
white-space: pre-wrap;
}
}
a.no-underline {

View File

@@ -126,3 +126,12 @@ export interface ServerConfig {
server_description: string;
site_info: string;
}
export enum RSort {
TimeAsc = 0,
TimeDesc = 1,
ViewsAsc = 2,
ViewsDesc = 3,
DownloadsAsc = 4,
DownloadsDesc = 5,
}

View File

@@ -14,7 +14,7 @@ import {
UserWithToken,
Comment,
CommentWithResource,
ServerConfig
ServerConfig, RSort
} from "./models.ts";
class Network {
@@ -409,11 +409,12 @@ class Network {
}
}
async getResources(page: number): Promise<PageResponse<Resource>> {
async getResources(page: number, sort: RSort): Promise<PageResponse<Resource>> {
try {
const response = await axios.get(`${this.apiBaseUrl}/resource`, {
params: {
page
page,
sort,
}
})
return response.data

View File

@@ -0,0 +1,10 @@
import Markdown from "react-markdown";
import {app} from "../app.ts";
export default function AboutPage() {
return <article className={"p-4"}>
<Markdown>
{app.siteInfo}
</Markdown>
</article>
}

View File

@@ -2,36 +2,63 @@ import {useEffect, useState} from "react";
import ResourcesView from "../components/resources_view.tsx";
import {network} from "../network/network.ts";
import { app } from "../app.ts";
import Markdown from "react-markdown";
import {RSort} from "../network/models.ts";
import Button from "../components/button.tsx";
import {MdInfoOutline} from "react-icons/md";
import {useTranslation} from "react-i18next";
import {useNavigate} from "react-router";
export default function HomePage() {
useEffect(() => {
document.title = app.appName;
}, [])
const [isCollapsed, setIsCollapsed] = useState(false);
const [order, setOrder] = useState(RSort.TimeAsc)
const {t} = useTranslation()
const navigate = useNavigate()
return <>
{
app.siteInfo && <div className={"mt-4 px-4"}>
<div className="collapse collapse-arrow bg-base-100 border border-base-300" onClick={() => setIsCollapsed(!isCollapsed)}>
<input type="radio" name="my-accordion-2" checked={isCollapsed} style={{
"cursor": "pointer",
}}/>
<div className="collapse-title font-semibold cursor-pointer">{t("About this site")}</div>
<article className="collapse-content text-sm cursor-auto" onClick={(e) => {
e.stopPropagation();
}}>
<Markdown>
{app.siteInfo}
</Markdown>
</article>
</div>
</div>
<div className={"flex p-4 items-center"}>
<select value={order} className="select w-52 select-info" onInput={(e) => {
const value = e.currentTarget.value;
if (value === "0") {
setOrder(RSort.TimeAsc);
} else if (value === "1") {
setOrder(RSort.TimeDesc);
} else if (value === "2") {
setOrder(RSort.ViewsDesc);
} else if (value === "3") {
setOrder(RSort.ViewsAsc);
} else if (value === "4") {
setOrder(RSort.DownloadsDesc);
} else if (value === "5") {
setOrder(RSort.DownloadsAsc);
}
<ResourcesView storageKey={"home_page"} loader={(page) => network.getResources(page)}></ResourcesView>
}}>
<option disabled>{t("Select a Order")}</option>
<option value={0}>{t("Latest")}</option>
<option value={1}>{t("Oldest")}</option>
<option value={2}>{t("Most Viewed")}</option>
<option value={3}>{t("Least Viewed")}</option>
<option value={4}>{t("Most Downloaded")}</option>
<option value={5}>{t("Least Downloaded")}</option>
</select>
<span className={"flex-1"}/>
<Button onClick={() => {
navigate("/about");
}}>
<div className={"flex items-center"}>
<MdInfoOutline size={24} className={"inline-block mr-2"}/>
<span>{t("About this site")}</span>
</div>
</Button>
</div>
<ResourcesView
key={`home_page_${order}`}
storageKey={`home_page_${order}`}
loader={(page) => network.getResources(page, order)}
/>
</>
}

View File

@@ -101,7 +101,19 @@ func handleListResources(c fiber.Ctx) error {
if err != nil {
return model.NewRequestError("Invalid page number")
}
resources, maxPage, err := service.GetResourceList(page)
sortStr := c.Query("sort")
if sortStr == "" {
sortStr = "0"
}
sortInt, err := strconv.Atoi(sortStr)
if err != nil {
return model.NewRequestError("Invalid sort parameter")
}
if sortInt < 0 || sortInt > 5 {
return model.NewRequestError("Sort parameter out of range")
}
sort := model.RSort(sortInt)
resources, maxPage, err := service.GetResourceList(page, sort)
if err != nil {
return err
}

View File

@@ -37,7 +37,7 @@ func GetResourceByID(id uint) (model.Resource, error) {
return r, nil
}
func GetResourceList(page, pageSize int) ([]model.Resource, int, error) {
func GetResourceList(page, pageSize int, sort model.RSort) ([]model.Resource, int, error) {
// Retrieve a list of resources with pagination
var resources []model.Resource
var total int64
@@ -46,7 +46,25 @@ func GetResourceList(page, pageSize int) ([]model.Resource, int, error) {
return nil, 0, err
}
if err := db.Offset((page - 1) * pageSize).Limit(pageSize).Preload("User").Preload("Images").Preload("Tags").Order("created_at DESC").Find(&resources).Error; err != nil {
order := ""
switch sort {
case model.RSortTimeAsc:
order = "created_at ASC"
case model.RSortTimeDesc:
order = "created_at DESC"
case model.RSortViewsAsc:
order = "views ASC"
case model.RSortViewsDesc:
order = "views DESC"
case model.RSortDownloadsAsc:
order = "downloads ASC"
case model.RSortDownloadsDesc:
order = "downloads DESC"
default:
order = "created_at DESC" // Default sort order
}
if err := db.Offset((page - 1) * pageSize).Limit(pageSize).Preload("User").Preload("Images").Preload("Tags").Order(order).Find(&resources).Error; err != nil {
return nil, 0, err
}

12
server/model/sort.go Normal file
View File

@@ -0,0 +1,12 @@
package model
type RSort uint8
const (
RSortTimeAsc RSort = iota
RSortTimeDesc
RSortViewsAsc
RSortViewsDesc
RSortDownloadsAsc
RSortDownloadsDesc
)

View File

@@ -125,8 +125,8 @@ func GetResource(id uint, host string) (*model.ResourceDetailView, error) {
return &v, nil
}
func GetResourceList(page int) ([]model.ResourceView, int, error) {
resources, totalPages, err := dao.GetResourceList(page, pageSize)
func GetResourceList(page int, sort model.RSort) ([]model.ResourceView, int, error) {
resources, totalPages, err := dao.GetResourceList(page, pageSize, sort)
if err != nil {
return nil, 0, err
}