feat: 创建博客支持slug字段了

This commit is contained in:
2025-12-27 13:05:07 +08:00
parent a2e8ddebca
commit 58b7f592fe
4 changed files with 71 additions and 3 deletions

View File

@@ -5,6 +5,9 @@ export class CreateBlogDto {
@IsString()
title: string;
@IsString()
slug: string;// 允许空串但如果为空则需要手动设置为null防止数据库唯一键冲突
@IsString()
description: string;

View File

@@ -57,6 +57,9 @@ export class BlogService {
.digest('hex');
}
}
if (typeof blog.slug === 'string' && blog.slug.trim().length === 0) {
blog.slug = null;
}
const newBlog = this.blogRepository.create(blog);
return this.blogRepository.save(newBlog);

View File

@@ -27,12 +27,37 @@ export default function AddBlog({ children, onRefresh }: AddBlogProps) {
const [open, setOpen] = useState(false);
const [blog, setBlog] = useState({
title: "",
slug: "",
description: "",
contentUrl: "",
permissions: [] as BlogPermission[],
password: "",
});
const handleCopyShareURL = () => {
const slug = blog.slug.trim();
if (slug.length === 0) {
return toast.warning('请先填写Slug')
}
let url = `${window.location.origin}/blog/${slug}`;
const password = blog.password.trim();
if (blog.permissions.includes(BlogPermission.ByPassword)) {
if (password.length === 0) {
return toast.warning('开启了密码保护但没有填写有效的密码无法生成有效URL')
} else {
url += `?p=${blog.password.trim()}`;
}
}
navigator.clipboard.writeText(url).then(() => {
toast.success('复制成功');
}, () => {
toast.error('复制失败,请手动复制');
});
};
const handleSubmit = async () => {
try {
const res = await AdminAPI.createBlog({
@@ -44,6 +69,7 @@ export default function AddBlog({ children, onRefresh }: AddBlogProps) {
toast.success("添加成功");
setBlog({
title: '',
slug: '',
description: '',
contentUrl: '',
permissions: [],
@@ -92,6 +118,17 @@ export default function AddBlog({ children, onRefresh }: AddBlogProps) {
onChange={(e) => setBlog({ ...blog, description: e.target.value })}
/>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="slug" className="text-right">
Slug
</Label>
<Input
id="slug"
className="col-span-3"
value={blog.slug}
onChange={(e) => setBlog({ ...blog, slug: e.target.value })}
/>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="contentUrl" className="text-right">
URL
@@ -136,9 +173,14 @@ export default function AddBlog({ children, onRefresh }: AddBlogProps) {
</div>
}
</div>
<DialogFooter>
<Button type="button" variant='secondary' onClick={() => setOpen(false)}></Button>
<Button type="button" onClick={handleSubmit}></Button>
<DialogFooter >
<div className="flex justify-between w-full">
<Button type="button" variant='outline' onClick={handleCopyShareURL}></Button>
<div>
<Button type="button" variant='secondary' onClick={() => setOpen(false)}></Button>
<Button type="button" onClick={handleSubmit}></Button>
</div>
</div>
</DialogFooter>
</DialogContent>
</Dialog>

View File

@@ -3,6 +3,7 @@ import { clientFetch } from "../client";
import { Blog } from "@/lib/types/blog";
import { BlogPermission } from "@/lib/types/Blog.Permission.enum";
import { Role } from "@/lib/types/role";
import { APIError } from "../common";
export interface UserEntity {
userId: string;
@@ -87,9 +88,28 @@ export async function updateResource(id: string, data: UpdateResourceParams) {
interface CreateBlogParams {
title: string;
description: string;
slug: string;
contentUrl: string;
permissions: BlogPermission[];
password: string;
}
export async function createBlog(data: CreateBlogParams) {
data.title = data.title.trim()
data.description = data.description.trim()
data.slug = data.slug.trim()
data.contentUrl = data.contentUrl.trim()
data.password = data.password.trim()
if (data.title.length === 0) {
throw new APIError('标题不得为空')
}
if (data.description.length === 0) {
throw new APIError('描述不得为空')
}
if (data.contentUrl.length === 0) {
throw new APIError('文章链接不得为空')
}
return clientFetch<Blog>('/api/admin/web/blog', {
method: 'POST',
body: JSON.stringify(data)