mirror of
https://github.com/wgh136/nysoure.git
synced 2025-09-27 12:17:24 +00:00
Add AboutPage component and integrate sorting options in resource retrieval
This commit is contained in:
@@ -10,6 +10,7 @@ import ManagePage from "./pages/manage_page.tsx";
|
|||||||
import TaggedResourcesPage from "./pages/tagged_resources_page.tsx";
|
import TaggedResourcesPage from "./pages/tagged_resources_page.tsx";
|
||||||
import UserPage from "./pages/user_page.tsx";
|
import UserPage from "./pages/user_page.tsx";
|
||||||
import EditResourcePage from "./pages/edit_resource_page.tsx";
|
import EditResourcePage from "./pages/edit_resource_page.tsx";
|
||||||
|
import AboutPage from "./pages/about_page.tsx";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
@@ -26,6 +27,7 @@ export default function App() {
|
|||||||
<Route path={"/tag/:tag"} element={<TaggedResourcesPage/>}/>
|
<Route path={"/tag/:tag"} element={<TaggedResourcesPage/>}/>
|
||||||
<Route path={"/user/:username"} element={<UserPage/>}/>
|
<Route path={"/user/:username"} element={<UserPage/>}/>
|
||||||
<Route path={"/resource/edit/:rid"} element={<EditResourcePage/>}/>
|
<Route path={"/resource/edit/:rid"} element={<EditResourcePage/>}/>
|
||||||
|
<Route path={"/about"} element={<AboutPage/>}/>
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
@@ -319,6 +319,15 @@ export const i18nData = {
|
|||||||
"Set the description of the tag.": "设置标签的描述。",
|
"Set the description of the tag.": "设置标签的描述。",
|
||||||
"Use markdown format.": "使用Markdown格式。",
|
"Use markdown format.": "使用Markdown格式。",
|
||||||
"Tag: ": "标签: ",
|
"Tag: ": "标签: ",
|
||||||
|
|
||||||
|
// 添加排序选项翻译
|
||||||
|
"Select a Order": "选择排序方式",
|
||||||
|
"Latest": "最新",
|
||||||
|
"Oldest": "最早",
|
||||||
|
"Most Viewed": "浏览最多",
|
||||||
|
"Least Viewed": "浏览最少",
|
||||||
|
"Most Downloaded": "下载最多",
|
||||||
|
"Least Downloaded": "下载最少",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"zh-TW": {
|
"zh-TW": {
|
||||||
@@ -480,6 +489,15 @@ export const i18nData = {
|
|||||||
"Set the description of the tag.": "設置標籤的描述。",
|
"Set the description of the tag.": "設置標籤的描述。",
|
||||||
"Use markdown format.": "使用Markdown格式。",
|
"Use markdown format.": "使用Markdown格式。",
|
||||||
"Tag: ": "標籤: ",
|
"Tag: ": "標籤: ",
|
||||||
|
|
||||||
|
// 添加排序选项翻译
|
||||||
|
"Select a Order": "選擇排序方式",
|
||||||
|
"Latest": "最新",
|
||||||
|
"Oldest": "最早",
|
||||||
|
"Most Viewed": "瀏覽最多",
|
||||||
|
"Least Viewed": "瀏覽最少",
|
||||||
|
"Most Downloaded": "下載最多",
|
||||||
|
"Least Downloaded": "下載最少",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -84,6 +84,9 @@ article {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, system-ui;
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, system-ui;
|
||||||
}
|
}
|
||||||
|
.line-break {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a.no-underline {
|
a.no-underline {
|
||||||
|
@@ -126,3 +126,12 @@ export interface ServerConfig {
|
|||||||
server_description: string;
|
server_description: string;
|
||||||
site_info: string;
|
site_info: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum RSort {
|
||||||
|
TimeAsc = 0,
|
||||||
|
TimeDesc = 1,
|
||||||
|
ViewsAsc = 2,
|
||||||
|
ViewsDesc = 3,
|
||||||
|
DownloadsAsc = 4,
|
||||||
|
DownloadsDesc = 5,
|
||||||
|
}
|
||||||
|
@@ -14,7 +14,7 @@ import {
|
|||||||
UserWithToken,
|
UserWithToken,
|
||||||
Comment,
|
Comment,
|
||||||
CommentWithResource,
|
CommentWithResource,
|
||||||
ServerConfig
|
ServerConfig, RSort
|
||||||
} from "./models.ts";
|
} from "./models.ts";
|
||||||
|
|
||||||
class Network {
|
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 {
|
try {
|
||||||
const response = await axios.get(`${this.apiBaseUrl}/resource`, {
|
const response = await axios.get(`${this.apiBaseUrl}/resource`, {
|
||||||
params: {
|
params: {
|
||||||
page
|
page,
|
||||||
|
sort,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return response.data
|
return response.data
|
||||||
|
10
frontend/src/pages/about_page.tsx
Normal file
10
frontend/src/pages/about_page.tsx
Normal 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>
|
||||||
|
}
|
@@ -2,36 +2,63 @@ import {useEffect, useState} from "react";
|
|||||||
import ResourcesView from "../components/resources_view.tsx";
|
import ResourcesView from "../components/resources_view.tsx";
|
||||||
import {network} from "../network/network.ts";
|
import {network} from "../network/network.ts";
|
||||||
import { app } from "../app.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 {useTranslation} from "react-i18next";
|
||||||
|
import {useNavigate} from "react-router";
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = app.appName;
|
document.title = app.appName;
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
const [order, setOrder] = useState(RSort.TimeAsc)
|
||||||
|
|
||||||
const {t} = useTranslation()
|
const {t} = useTranslation()
|
||||||
|
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
{
|
<div className={"flex p-4 items-center"}>
|
||||||
app.siteInfo && <div className={"mt-4 px-4"}>
|
<select value={order} className="select w-52 select-info" onInput={(e) => {
|
||||||
<div className="collapse collapse-arrow bg-base-100 border border-base-300" onClick={() => setIsCollapsed(!isCollapsed)}>
|
const value = e.currentTarget.value;
|
||||||
<input type="radio" name="my-accordion-2" checked={isCollapsed} style={{
|
if (value === "0") {
|
||||||
"cursor": "pointer",
|
setOrder(RSort.TimeAsc);
|
||||||
}}/>
|
} else if (value === "1") {
|
||||||
<div className="collapse-title font-semibold cursor-pointer">{t("About this site")}</div>
|
setOrder(RSort.TimeDesc);
|
||||||
<article className="collapse-content text-sm cursor-auto" onClick={(e) => {
|
} else if (value === "2") {
|
||||||
e.stopPropagation();
|
setOrder(RSort.ViewsDesc);
|
||||||
}}>
|
} else if (value === "3") {
|
||||||
<Markdown>
|
setOrder(RSort.ViewsAsc);
|
||||||
{app.siteInfo}
|
} else if (value === "4") {
|
||||||
</Markdown>
|
setOrder(RSort.DownloadsDesc);
|
||||||
</article>
|
} else if (value === "5") {
|
||||||
|
setOrder(RSort.DownloadsAsc);
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<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>
|
</div>
|
||||||
</div>
|
</Button>
|
||||||
}
|
</div>
|
||||||
<ResourcesView storageKey={"home_page"} loader={(page) => network.getResources(page)}></ResourcesView>
|
<ResourcesView
|
||||||
|
key={`home_page_${order}`}
|
||||||
|
storageKey={`home_page_${order}`}
|
||||||
|
loader={(page) => network.getResources(page, order)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
@@ -101,7 +101,19 @@ func handleListResources(c fiber.Ctx) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return model.NewRequestError("Invalid page number")
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -37,7 +37,7 @@ func GetResourceByID(id uint) (model.Resource, error) {
|
|||||||
return r, nil
|
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
|
// Retrieve a list of resources with pagination
|
||||||
var resources []model.Resource
|
var resources []model.Resource
|
||||||
var total int64
|
var total int64
|
||||||
@@ -46,7 +46,25 @@ func GetResourceList(page, pageSize int) ([]model.Resource, int, error) {
|
|||||||
return nil, 0, err
|
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
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
12
server/model/sort.go
Normal file
12
server/model/sort.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type RSort uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
RSortTimeAsc RSort = iota
|
||||||
|
RSortTimeDesc
|
||||||
|
RSortViewsAsc
|
||||||
|
RSortViewsDesc
|
||||||
|
RSortDownloadsAsc
|
||||||
|
RSortDownloadsDesc
|
||||||
|
)
|
@@ -125,8 +125,8 @@ func GetResource(id uint, host string) (*model.ResourceDetailView, error) {
|
|||||||
return &v, nil
|
return &v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetResourceList(page int) ([]model.ResourceView, int, error) {
|
func GetResourceList(page int, sort model.RSort) ([]model.ResourceView, int, error) {
|
||||||
resources, totalPages, err := dao.GetResourceList(page, pageSize)
|
resources, totalPages, err := dao.GetResourceList(page, pageSize, sort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user