实现添加博客
This commit is contained in:
@@ -13,6 +13,7 @@ import { AdminUserRoleController } from './controller/admin-user-role.controller
|
|||||||
import { AdminWebResourceController } from './controller/web/admin-web-resource.controller';
|
import { AdminWebResourceController } from './controller/web/admin-web-resource.controller';
|
||||||
import { AdminWebBlogController } from './controller/web/admin-web-blog.controller';
|
import { AdminWebBlogController } from './controller/web/admin-web-blog.controller';
|
||||||
import { ResourceModule } from 'src/resource/resource.module';
|
import { ResourceModule } from 'src/resource/resource.module';
|
||||||
|
import { BlogModule } from 'src/blog/blog.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -22,6 +23,7 @@ import { ResourceModule } from 'src/resource/resource.module';
|
|||||||
UserModule,
|
UserModule,
|
||||||
RoleModule,
|
RoleModule,
|
||||||
ResourceModule,
|
ResourceModule,
|
||||||
|
BlogModule,
|
||||||
],
|
],
|
||||||
controllers: [
|
controllers: [
|
||||||
AdminController,
|
AdminController,
|
||||||
|
|||||||
@@ -1,6 +1,45 @@
|
|||||||
import { Controller } from "@nestjs/common";
|
import { Body, Controller, Delete, Get, Param, ParseUUIDPipe, Post, Put } from "@nestjs/common";
|
||||||
|
import { CreateBlogDto } from "src/admin/dto/admin-web/create-blog.dto";
|
||||||
|
import { BlogService } from "src/blog/blog.service";
|
||||||
|
|
||||||
@Controller('/admin/web/blog')
|
@Controller('/admin/web/blog')
|
||||||
export class AdminWebBlogController {
|
export class AdminWebBlogController {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly adminWebBlogService: BlogService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
async list() {
|
||||||
|
return this.adminWebBlogService.list();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
async create(
|
||||||
|
@Body() dto: CreateBlogDto,
|
||||||
|
) {
|
||||||
|
return this.adminWebBlogService.create(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put(':id')
|
||||||
|
async update(
|
||||||
|
@Param('id', new ParseUUIDPipe({ version: '4' })) id: string,
|
||||||
|
@Body() dto: CreateBlogDto,
|
||||||
|
) {
|
||||||
|
return this.adminWebBlogService.update(id, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
async get(
|
||||||
|
@Param('id', new ParseUUIDPipe({ version: '4' })) id: string,
|
||||||
|
) {
|
||||||
|
return this.adminWebBlogService.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
async remove(
|
||||||
|
@Param('id', new ParseUUIDPipe({ version: '4' })) id: string,
|
||||||
|
) {
|
||||||
|
return this.adminWebBlogService.remove(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -26,14 +26,16 @@ export class AdminWebResourceController {
|
|||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
async update(
|
async update(
|
||||||
@Param('id') id: string,
|
@Param('id', new ParseUUIDPipe({ version: '4' })) id: string,
|
||||||
@Body() data: CreateResourceDto
|
@Body() data: CreateResourceDto
|
||||||
) {
|
) {
|
||||||
return this.resourceService.update(id, data);
|
return this.resourceService.update(id, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
async delete(@Param('id') id: string) {
|
async delete(
|
||||||
|
@Param('id', new ParseUUIDPipe({ version: '4' })) id: string,
|
||||||
|
) {
|
||||||
return this.resourceService.delete(id);
|
return this.resourceService.delete(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
12
tone-page-server/src/admin/dto/admin-web/create-blog.dto.ts
Normal file
12
tone-page-server/src/admin/dto/admin-web/create-blog.dto.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { IsString } from "class-validator";
|
||||||
|
|
||||||
|
export class CreateBlogDto {
|
||||||
|
@IsString()
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
contentUrl: string;
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import { Blog } from './entity/Blog.entity';
|
|||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([Blog])],
|
imports: [TypeOrmModule.forFeature([Blog])],
|
||||||
controllers: [BlogController],
|
controllers: [BlogController],
|
||||||
providers: [BlogService]
|
providers: [BlogService],
|
||||||
|
exports: [BlogService],
|
||||||
})
|
})
|
||||||
export class BlogModule { }
|
export class BlogModule { }
|
||||||
|
|||||||
@@ -15,9 +15,28 @@ export class BlogService {
|
|||||||
return this.blogRepository.find({
|
return this.blogRepository.find({
|
||||||
where: { deletedAt: null },
|
where: { deletedAt: null },
|
||||||
order: {
|
order: {
|
||||||
publishAt: 'DESC',
|
createdAt: 'DESC',
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async create(blog: Partial<Blog>) {
|
||||||
|
const newBlog = this.blogRepository.create(blog);
|
||||||
|
return this.blogRepository.save(newBlog);
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: string, blog: Partial<Blog>) {
|
||||||
|
await this.blogRepository.update(id, blog);
|
||||||
|
return this.blogRepository.findOneBy({ id });
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(id: string) {
|
||||||
|
const blog = await this.blogRepository.findOneBy({ id });
|
||||||
|
if (!blog) return null;
|
||||||
|
return this.blogRepository.softRemove(blog);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findById(id: string) {
|
||||||
|
return this.blogRepository.findOneBy({ id });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,6 @@ export class Blog {
|
|||||||
@Column()
|
@Column()
|
||||||
contentUrl: string;
|
contentUrl: string;
|
||||||
|
|
||||||
@Column({ precision: 3 })
|
|
||||||
publishAt: Date;
|
|
||||||
|
|
||||||
@CreateDateColumn({ precision: 3 })
|
@CreateDateColumn({ precision: 3 })
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog"
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { AdminApi } from "@/lib/api";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
interface AddBlogProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
onRefresh: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AddBlog({ children, onRefresh }: AddBlogProps) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [blog, setBlog] = useState({
|
||||||
|
title: "",
|
||||||
|
description: "",
|
||||||
|
contentUrl: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
const res = await AdminApi.web.blog.create({
|
||||||
|
...blog,
|
||||||
|
});
|
||||||
|
if (res) {
|
||||||
|
setOpen(false);
|
||||||
|
onRefresh();
|
||||||
|
toast.success("添加成功");
|
||||||
|
} else {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.error((error as Error).message || "添加失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
{children}
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="sm:max-w-[425px]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>添加博客</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="grid gap-4 py-4">
|
||||||
|
<div className="grid grid-cols-4 items-center gap-4">
|
||||||
|
<Label htmlFor="title" className="text-right">
|
||||||
|
标题
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="title"
|
||||||
|
className="col-span-3"
|
||||||
|
value={blog.title}
|
||||||
|
onChange={(e) => setBlog({ ...blog, title: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-4 items-center gap-4">
|
||||||
|
<Label htmlFor="description" className="text-right">
|
||||||
|
描述
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="description"
|
||||||
|
className="col-span-3"
|
||||||
|
value={blog.description}
|
||||||
|
onChange={(e) => setBlog({ ...blog, description: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-4 items-center gap-4">
|
||||||
|
<Label htmlFor="contentUrl" className="text-right">
|
||||||
|
文章URL
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="contentUrl"
|
||||||
|
className="col-span-3"
|
||||||
|
value={blog.contentUrl}
|
||||||
|
onChange={(e) => setBlog({ ...blog, contentUrl: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button type="button" variant='secondary' onClick={() => setOpen(false)}>取消</Button>
|
||||||
|
<Button type="button" onClick={handleSubmit}>保存</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -8,41 +8,58 @@ import {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table"
|
} from "@/components/ui/table"
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
|
||||||
|
import { Blog } from "@/lib/types/blog"
|
||||||
|
|
||||||
interface BlogTableProps {
|
interface BlogTableProps {
|
||||||
blogs: {
|
blogs: Blog[],
|
||||||
|
error?: string,
|
||||||
}[]
|
onRefresh?: () => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BlogTable({ blogs }: BlogTableProps) {
|
export default function BlogTable({ blogs, error, onRefresh }: BlogTableProps) {
|
||||||
return (
|
return (
|
||||||
<Table>
|
<Table>
|
||||||
<TableCaption>A list of your recent invoices.</TableCaption>
|
{
|
||||||
|
error && (
|
||||||
|
<TableCaption>{error}</TableCaption>
|
||||||
|
)
|
||||||
|
}
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead className="w-[100px]">Invoice</TableHead>
|
<TableHead className="w-[100px]">Id</TableHead>
|
||||||
<TableHead>Status</TableHead>
|
<TableHead>标题</TableHead>
|
||||||
<TableHead>Method</TableHead>
|
<TableHead>描述</TableHead>
|
||||||
<TableHead className="text-right">Amount</TableHead>
|
<TableHead>文章URL</TableHead>
|
||||||
|
<TableHead className="text-right">操作</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{/* {invoices.map((invoice) => (
|
{blogs.map((blog) => (
|
||||||
<TableRow key={invoice.invoice}>
|
<TableRow key={blog.id}>
|
||||||
<TableCell className="font-medium">{invoice.invoice}</TableCell>
|
<TableCell className="font-medium">
|
||||||
<TableCell>{invoice.paymentStatus}</TableCell>
|
<TooltipProvider>
|
||||||
<TableCell>{invoice.paymentMethod}</TableCell>
|
<Tooltip>
|
||||||
<TableCell className="text-right">{invoice.totalAmount}</TableCell>
|
<TooltipTrigger asChild>
|
||||||
|
<div className="max-w-[100px] overflow-hidden text-ellipsis">{blog.id}</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{blog.id}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{blog.title}</TableCell>
|
||||||
|
<TableCell>{blog.description}</TableCell>
|
||||||
|
<TableCell>{blog.contentUrl}</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
{/* <ResourceEdit id={resource.id} onRefresh={() => onRefresh()}>
|
||||||
|
<Button variant={'outline'} size={'sm'}>编辑</Button>
|
||||||
|
</ResourceEdit> */}
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))} */}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
<TableFooter>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell colSpan={3}>Total</TableCell>
|
|
||||||
<TableCell className="text-right">$2,500.00</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableFooter>
|
|
||||||
</Table>
|
</Table>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,21 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useBlogList } from "@/hooks/admin/web/blog/use-blog-list"
|
||||||
|
import BlogTable from "./components/BlogTable"
|
||||||
|
import AddBlog from "./components/AddBlog";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
|
const { blogs, error, isLoading, refresh } = useBlogList();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<div>
|
||||||
|
<AddBlog onRefresh={refresh}>
|
||||||
|
<Button>添加博客</Button>
|
||||||
|
</AddBlog>
|
||||||
|
</div>
|
||||||
|
<BlogTable blogs={blogs || []} onRefresh={refresh} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
24
tone-page-web/hooks/admin/web/blog/use-blog-list.ts
Normal file
24
tone-page-web/hooks/admin/web/blog/use-blog-list.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { AdminApi } from "@/lib/api";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
|
export function useBlogList() {
|
||||||
|
const { data, error, isLoading, mutate } = useSWR(
|
||||||
|
['/admin/web/blog'],
|
||||||
|
() => AdminApi.web.blog.list()
|
||||||
|
)
|
||||||
|
|
||||||
|
const refresh = useCallback(() => {
|
||||||
|
return mutate()
|
||||||
|
}, [mutate])
|
||||||
|
|
||||||
|
return {
|
||||||
|
blogs: data,
|
||||||
|
error,
|
||||||
|
isLoading,
|
||||||
|
mutate,
|
||||||
|
refresh,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import fetcher from "@/lib/api/fetcher";
|
import fetcher from "@/lib/api/fetcher";
|
||||||
|
import { Blog } from "@/lib/types/blog";
|
||||||
|
|
||||||
export async function list() {
|
export async function list() {
|
||||||
return fetcher('/api/admin/web/blog')
|
return fetcher<Blog[]>('/api/admin/web/blog')
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,6 @@
|
|||||||
export type BlogPermission =
|
|
||||||
'public' |
|
|
||||||
'password' |
|
|
||||||
'listed';
|
|
||||||
|
|
||||||
export interface Blog {
|
export interface Blog {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
publish_at: string;
|
contentUrl: string;
|
||||||
permissions: BlogPermission[];
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user