Feat: 新增评论组件,细化通知组件样式,引入环境变量,替换图标组件
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
import { useState, type ChangeEvent, type SubmitEvent } from "react";
|
||||
import { RiLoader5Fill } from "@remixicon/react";
|
||||
|
||||
import { add } from "~/components/ui/notice/utils";
|
||||
import { useIsLogin } from "~/composables/use-is-login";
|
||||
import { clsn } from "~/utils";
|
||||
import { getAuthorInfo, setAuthorInfo } from "~/utils/comment";
|
||||
|
||||
export interface CommentFormSubmitValues {
|
||||
name: string;
|
||||
email: string;
|
||||
site: string;
|
||||
content: string;
|
||||
root?: string;
|
||||
parent?: string;
|
||||
}
|
||||
|
||||
interface CommentFormProps {
|
||||
className?: string;
|
||||
parent?: string;
|
||||
root?: string;
|
||||
onSubmit: (values: CommentFormSubmitValues) => Promise<unknown>;
|
||||
}
|
||||
|
||||
export default function CommentForm({ className, parent, root, onSubmit }: CommentFormProps) {
|
||||
const isLogin = useIsLogin();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [input, setInput] = useState(() => ({
|
||||
...getAuthorInfo(),
|
||||
content: "",
|
||||
}));
|
||||
|
||||
const onChange = (ev: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const { name, value } = ev.target;
|
||||
setInput((prev) => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
const onFormSubmit = (ev: SubmitEvent) => {
|
||||
ev.preventDefault();
|
||||
|
||||
if (!isLogin && (!input.name || !input.email || !input.content)) {
|
||||
add({
|
||||
title: "评论失败",
|
||||
type: "error",
|
||||
content: "缺少必填项",
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!input.content) {
|
||||
add({
|
||||
title: "评论失败",
|
||||
type: "error",
|
||||
content: "缺少必填项",
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setAuthorInfo({
|
||||
name: input.name,
|
||||
email: input.email,
|
||||
site: input.site,
|
||||
});
|
||||
|
||||
setLoading(true);
|
||||
|
||||
onSubmit({
|
||||
...input,
|
||||
root,
|
||||
parent,
|
||||
}).then(() => {
|
||||
setInput((prev) => ({ ...prev, content: "" }));
|
||||
setLoading(false);
|
||||
}).catch(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const inputClass = "w-full px-3 py-2.5 rounded-xl bg-white border-4 border-gray-200 focus:border-pink-400 outline-none transition-colors placeholder:text-neutral-400";
|
||||
const labelClass = "block text-sm font-medium mb-2 text-neutral-500";
|
||||
|
||||
if (isLogin) {
|
||||
return (
|
||||
<form className={clsn("mb-8", className)} onSubmit={onFormSubmit}>
|
||||
<div className="mb-4">
|
||||
<h3 className="text-pink-400 font-bold text-lg mb-1">欢迎回来!</h3>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<textarea
|
||||
name="content"
|
||||
className={clsn(inputClass, "min-h-[7em] resize-y")}
|
||||
placeholder="评论"
|
||||
rows={3}
|
||||
required
|
||||
value={input.content}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="inline-flex items-center justify-center px-6 py-3 rounded-xl bg-pink-400 text-white font-medium hover:bg-pink-500 transition-colors cursor-pointer disabled:opacity-60 disabled:cursor-not-allowed"
|
||||
disabled={loading}
|
||||
>
|
||||
{loading && <RiLoader5Fill className="animate-spin mr-2" />}
|
||||
提交
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<form className={clsn("mb-8", className)} onSubmit={onFormSubmit}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<label className={labelClass}>昵称</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
className={inputClass}
|
||||
placeholder="园友"
|
||||
required
|
||||
value={input.name}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className={labelClass}>邮箱</label>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
className={inputClass}
|
||||
placeholder="user@example.com"
|
||||
required
|
||||
value={input.email}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className={labelClass}>站点</label>
|
||||
<input
|
||||
type="url"
|
||||
name="site"
|
||||
className={inputClass}
|
||||
placeholder="https://example.com"
|
||||
value={input.site}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 md:h-full">
|
||||
<div className="flex flex-col flex-1 min-h-0">
|
||||
<label className={labelClass}>评论</label>
|
||||
<textarea
|
||||
name="content"
|
||||
className={clsn(inputClass, "md:flex-1 resize-y")}
|
||||
placeholder="注意言行举止,来做评论排行榜的前十名吧~"
|
||||
rows={3}
|
||||
required
|
||||
value={input.content}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full inline-flex items-center justify-center px-6 py-2.5 rounded-xl border-4 border-transparent bg-pink-400 text-white font-medium hover:bg-pink-500 transition-colors cursor-pointer disabled:opacity-60 disabled:cursor-not-allowed"
|
||||
disabled={loading}
|
||||
>
|
||||
{loading && <RiLoader5Fill className="animate-spin mr-2" />}
|
||||
提交
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user