完成编辑网站功能

This commit is contained in:
宇阳
2024-08-11 19:24:50 +08:00
parent 3978c6dee0
commit 148f106240
9 changed files with 80 additions and 169 deletions

View File

@@ -42,7 +42,6 @@ function App() {
<ConfigProvider
theme={{
token: {
// Seed Token影响范围大
colorPrimary: '#727cf5',
},
}}

View File

@@ -1,5 +1,5 @@
import Request from '@/utils/request'
import { System, Web, Layout } from '@/types/system'
import { System, Web, Layout } from '@/types/project'
// 获取系统配置信息
export const getSystemDataAPI = () => Request<System>("GET", "/project/system")

View File

@@ -1,5 +1,5 @@
import Request from '@/utils/request'
import { account, editUser, Login, UserInfo } from '@/types/user'
import { account, EditUser, Login, UserInfo } from '@/types/user'
// 登录
export const loginDataAPI = (data: Login) => Request<account>("POST", "/user/login", data)
@@ -11,4 +11,4 @@ export const getUserDataAPI = (id?: number) => Request<UserInfo>("GET", `/user/$
export const editUserDataAPI = (data: UserInfo) => Request<UserInfo>("PATCH", "/user", data)
// 修改管理员密码
export const editAdminPassAPI = (data: editUser) => Request<UserInfo>("PATCH", "/user/admin", data)
export const editAdminPassAPI = (data: EditUser) => Request<UserInfo>("PATCH", "/user/admin", data)

View File

@@ -12,34 +12,25 @@ const LoginPage = () => {
const store = useUserStore();
const navigate = useNavigate();
const location = useLocation();
const returnUrl = new URLSearchParams(location.search).get('returnUrl') || '/home';
const returnUrl = new URLSearchParams(location.search).get('returnUrl') || '/';
const onSubmit = async () => {
try {
const values = await form.validateFields();
const { data } = await loginDataAPI(values);
const values = await form.validateFields();
const { data } = await loginDataAPI(values);
// 将用户信息和token保存起来
store.setUser(data.user);
store.setToken(data.token);
// 将用户信息和token保存起来
store.setUser(data.user);
store.setToken(data.token);
notification.success({
message: 'Success',
description: `Hello ${data.user.name} 欢迎回来 🎉`,
});
notification.success({
message: '🎉🎉🎉',
description: `Hello ${data.user.name} 欢迎回来`,
});
navigate(returnUrl);
} catch (error) {
console.error('Failed to login:', error);
notification.error({
message: 'Error',
description: 'Failed to login. Please try again.',
});
}
navigate(returnUrl);
};
useEffect(()=>{
useEffect(() => {
},)
@@ -53,17 +44,15 @@ const LoginPage = () => {
<Form
form={form}
size='large'
layout="vertical"
onFinish={onSubmit}
className='pt-5 px-10'
size='large'
>
<Form.Item
name="username"
label="账号"
rules={[
{ required: true, message: '请输入账号' }
]}
rules={[{ required: true, message: '请输入账号' }]}
>
<Input prefix={<UserOutlined />} placeholder="请输入用户名" />
</Form.Item>
@@ -71,20 +60,14 @@ const LoginPage = () => {
<Form.Item
name="password"
label="密码"
rules={[
{ required: true, message: '请输入密码' }
]}
rules={[{ required: true, message: '请输入密码' }]}
>
<Input.Password
prefix={<LockOutlined />}
type={isPassVisible ? 'text' : 'password'}
placeholder="请输入密码"
iconRender={visible =>
visible ? (
<EyeOutlined onClick={() => setIsPassVisible(!isPassVisible)} />
) : (
<EyeInvisibleOutlined onClick={() => setIsPassVisible(!isPassVisible)} />
)
visible ? <EyeOutlined onClick={() => setIsPassVisible(!isPassVisible)} /> : <EyeInvisibleOutlined onClick={() => setIsPassVisible(!isPassVisible)} />
}
/>
</Form.Item>

View File

@@ -2,51 +2,38 @@ import { useState, useEffect } from 'react';
import { notification, Divider, Input, Alert, Button, Spin } from 'antd';
import { PictureOutlined, LoadingOutlined } from '@ant-design/icons';
import { editLayoutDataAPI, getLayoutDataAPI } from '@/api/System';
import 'tailwindcss/tailwind.css';
interface Layout {
isArticleLayout: string;
rightSidebar: string[];
swiperImage: string;
swiperText: string[];
}
import { Layout } from '@/types/system';
const LayoutPage = () => {
const [loading, setLoading] = useState<boolean>(false);
const [tempSwiperText, setTempSwiperText] = useState<string>('');
const [layout, setLayout] = useState<Layout>({
isArticleLayout: 'classics',
rightSidebar: ['author', 'hotArticle', 'randomArticle', 'newComments'],
swiperImage: 'https://bu.dusays.com/2023/11/10/654e2cf6055b0.jpg',
swiperText: ['这是一段文本', '这是第二段文本'],
});
const [swiperText, setSwiperText] = useState<string>('');
const onSidebar = (select: string) => {
setLayout((prevLayout) => {
const is = prevLayout.rightSidebar.includes(select);
const newRightSidebar = is
? prevLayout.rightSidebar.filter((item) => item !== select)
: [...prevLayout.rightSidebar, select];
const [layout, setLayout] = useState<Layout>({} as Layout);
return { ...prevLayout, rightSidebar: newRightSidebar };
});
const onSidebar = (value: string) => {
const rightSidebar = JSON.parse(layout.rightSidebar || '[]');
const index = rightSidebar.indexOf(value);
index > -1 ? rightSidebar.splice(index, 1) : rightSidebar.push(value)
setLayout({ ...layout, rightSidebar: JSON.stringify(rightSidebar) });
};
const getLayoutData = async () => {
setLoading(true);
const { data } = await getLayoutDataAPI();
setLayout(data);
setTempSwiperText(data.swiperText.join('\n'));
const swiperText = JSON.parse(data.swiperText)
setSwiperText(swiperText.join('\n'));
setLoading(false);
};
// useEffect(() => {
// getLayoutData();
// }, []);
useEffect(() => {
getLayoutData();
}, []);
const editLayoutData = async () => {
setLoading(true);
const updatedLayout = { ...layout, swiperText: tempSwiperText.split('\n') };
const updatedLayout = { ...layout, swiperText: JSON.stringify(swiperText.split('\n')) };
await editLayoutDataAPI(updatedLayout);
notification.success({
message: '成功',
@@ -79,8 +66,8 @@ const LayoutPage = () => {
<Divider orientation="left"></Divider>
<div className="mb-8">
<Input.TextArea
value={tempSwiperText}
onChange={(e) => setTempSwiperText(e.target.value)}
value={swiperText}
onChange={(e) => setSwiperText(e.target.value)}
autoSize={{ minRows: 2, maxRows: 4 }}
/>
<Alert message="以换行分隔,每行表示一段文本" type="info" className="mt-2" />
@@ -89,14 +76,10 @@ const LayoutPage = () => {
<Divider orientation="left"></Divider>
<div className="sidebar flex mb-8">
{['author', 'hotArticle', 'randomArticle', 'newComments'].map((item) => (
<div
key={item}
className={`item flex flex-col items-center p-4 m-4 border-2 rounded cursor-pointer ${layout.rightSidebar.includes(item) ? 'border-primary' : 'border-[#eee]'}`}
onClick={() => onSidebar(item)}
>
<div key={item} className={`item flex flex-col items-center p-4 m-4 border-2 rounded cursor-pointer ${layout.rightSidebar && JSON.parse(layout.rightSidebar).includes(item) ? 'border-primary' : 'border-[#eee]'}`} onClick={() => onSidebar(item)}>
<img src={`${getFile(item)}`} alt="" className="h-52 mb-4 rounded" />
<p className={`text-center ${layout.rightSidebar.includes(item) ? 'text-primary' : ''}`}>
<p className={`text-center ${layout.rightSidebar && JSON.parse(layout.rightSidebar).includes(item) ? 'text-primary' : ''}`}>
{item === 'author' ? '作者信息模块' : item === 'hotArticle' ? '文章推荐模块' : item === 'randomArticle' ? '随机文章模块' : '最新评论模块'}
</p>
</div>
@@ -106,13 +89,9 @@ const LayoutPage = () => {
<Divider orientation="left"></Divider>
<div className="article flex">
{['classics', 'card', 'waterfall'].map((item) => (
<div
key={item}
className={`item flex flex-col items-center p-4 m-4 border-2 rounded cursor-pointer ${layout.isArticleLayout === item ? 'border-primary' : 'border-[#eee]'
}`}
onClick={() => setLayout({ ...layout, isArticleLayout: item })}
>
<div key={item} onClick={() => setLayout({ ...layout, isArticleLayout: item })} className={`item flex flex-col items-center p-4 m-4 border-2 rounded cursor-pointer ${layout.isArticleLayout === item ? 'border-primary' : 'border-[#eee]'}`}>
<img src={`${getFile(item)}`} alt="" className="h-52 mb-4 rounded" />
<p className={`text-center ${layout.isArticleLayout === item ? 'text-primary' : ''}`}>
{item === 'classics' ? '经典布局' : item === 'card' ? '卡片布局' : '瀑布流布局'}
</p>
@@ -120,9 +99,7 @@ const LayoutPage = () => {
))}
</div>
<Button type="primary" size="large" className="w-full mt-4" onClick={editLayoutData}>
</Button>
<Button type="primary" size="large" className="w-full mt-4" onClick={editLayoutData}></Button>
</Spin>
</div>
);

View File

@@ -1,49 +1,33 @@
import { useEffect, useState } from 'react';
import { Form, Input, Button, notification } from 'antd';
import { Form, Input, Button, message } from 'antd';
import { getWebDataAPI, editWebDataAPI } from '@/api/System';
import { Web } from './type'
import { Web } from '@/types/project'
const WebPage = () => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [web, setWeb] = useState<Web>({
url: '',
title: '',
subhead: '',
light_logo: '',
dark_logo: '',
description: '',
keyword: '',
favicon: '',
footer: '',
social: '',
covers: []
});
const [web, setWeb] = useState<Web>({} as Web);
const [tempCovers, setTempCovers] = useState<string>("");
useEffect(() => {
const fetchData = async () => {
setLoading(true);
const { data } = await getWebDataAPI();
data.social = JSON.stringify(data.social);
setTempCovers(data.covers.join("\n"));
setWeb(data);
form.setFieldsValue(data);
setLoading(false);
};
fetchData();
}, [form]);
const onFinish = async (values: Web) => {
setLoading(true);
values.covers = tempCovers.split("\n");
await editWebDataAPI(values);
const getWebData = async () => {
const { data } = await getWebDataAPI();
setTempCovers(data.covers);
setWeb(data);
form.setFieldsValue(data);
setLoading(false);
notification.success({
message: '成功',
description: '🎉编辑网站成功',
});
};
useEffect(() => {
setLoading(true);
getWebData();
}, []);
const onSubmit = async (values: Web) => {
setLoading(true);
// values.covers = tempCovers.split("\n");
await editWebDataAPI(values);
message.success("🎉 编辑网站成功");
getWebData();
};
return (
@@ -54,17 +38,14 @@ const WebPage = () => {
form={form}
size='large'
layout="vertical"
onFinish={onFinish}
onFinish={onSubmit}
initialValues={web}
className="w-5/12 mx-auto"
>
<Form.Item
label="网站名称"
name="title"
rules={[
{ required: true, message: '网站名称不能为空' },
{ min: 1, max: 10, message: '网站名称限制在1 ~ 10个字符' }
]}
rules={[{ required: true, message: '网站名称不能为空' }]}
>
<Input placeholder="Thrive" />
</Form.Item>
@@ -72,10 +53,7 @@ const WebPage = () => {
<Form.Item
label="网站副标题"
name="subhead"
rules={[
{ required: true, message: '网站副标题不能为空' },
{ min: 1, max: 50, message: '网站副标题限制在1 ~ 50个字符' }
]}
rules={[{ required: true, message: '网站副标题不能为空' }]}
>
<Input placeholder="花有重开日, 人无再少年" />
</Form.Item>
@@ -98,7 +76,7 @@ const WebPage = () => {
<Form.Item
label="光亮主题LOGO"
name="light_logo"
name="lightLogo"
rules={[{ required: true, message: '网站LOGO不能为空' }]}
>
<Input placeholder="https://liuyuyang.net/logo.png" />
@@ -106,7 +84,7 @@ const WebPage = () => {
<Form.Item
label="暗黑主题LOGO"
name="dark_logo"
name="darkLogo"
rules={[{ required: true, message: '网站LOGO不能为空' }]}
>
<Input placeholder="https://liuyuyang.net/logo.png" />
@@ -115,10 +93,7 @@ const WebPage = () => {
<Form.Item
label="网站描述"
name="description"
rules={[
{ required: true, message: '网站描述不能为空' },
{ min: 5, max: 300, message: '网站描述限制在5 ~ 300个字符' }
]}
rules={[{ required: true, message: '网站描述不能为空' }]}
>
<Input placeholder="记录前端、Python、Java点点滴滴" />
</Form.Item>
@@ -137,10 +112,8 @@ const WebPage = () => {
rules={[{ required: true, message: '网站随机封面不能为空' }]}
>
<Input.TextArea
value={tempCovers}
onChange={(e) => setTempCovers(e.target.value)}
autoSize={{ minRows: 2, maxRows: 10 }}
placeholder="Please input"
placeholder="随机文章封面"
/>
</Form.Item>
@@ -150,28 +123,21 @@ const WebPage = () => {
rules={[{ required: true, message: '社交网站不能为空' }]}
>
<Input.TextArea
value={web.social}
onChange={(e) => setWeb({ ...web, social: e.target.value })}
autoSize={{ minRows: 2, maxRows: 10 }}
placeholder="Please input"
placeholder="社交账号"
/>
</Form.Item>
<Form.Item
label="底部信息"
name="footer"
rules={[
{ required: true, message: '网站底部信息不能为空' },
{ min: 10, max: 300, message: '网站底部信息限制在10 ~ 300个字符' }
]}
rules={[{ required: true, message: '网站底部信息不能为空' }]}
>
<Input placeholder="记录前端、Python、Java点点滴滴" />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" loading={loading} block>
</Button>
<Button type="primary" htmlType="submit" loading={loading} block></Button>
</Form.Item>
</Form>
</div>

View File

@@ -1,14 +0,0 @@
export interface Web {
url: string,
title: string,
subhead: string,
favicon: string,
light_logo: string,
dark_logo: string,
description: string,
keyword: string,
footer: string,
// font: string,
social: string,
covers: string[]
}

View File

@@ -68,7 +68,7 @@ const TagManagement: React.FC = () => {
<Card className="mt-2">
<Spin spinning={loading}>
<div className="w-8/12 flex justify-between px-8 mx-auto">
<div className="w-10/12 flex justify-between px-8 mx-auto">
<div className="flex flex-col w-[40%]">
<h2 className="text-xl pb-4 text-center">{title}</h2>
@@ -91,7 +91,7 @@ const TagManagement: React.FC = () => {
<h2 className="text-xl pb-4 text-center"></h2>
<Table dataSource={list} rowKey="id">
<Table.Column title="ID" dataIndex="id" />
<Table.Column title="ID" dataIndex="id" align="center" />
<Table.Column title="名称" dataIndex="name" align="center" />
<Table.Column
title="操作"

View File

@@ -31,14 +31,14 @@ export interface Web {
title: string,
subhead: string,
favicon: string,
light_logo: string,
dark_logo: string,
lightLogo: string,
darkLogo: string,
description: string,
keyword: string,
footer: string,
// font: string,
social: string,
covers: string[]
covers: string
}
export type ArticleLayout = "classics" | "card" | "waterfall" | ""
@@ -46,8 +46,8 @@ export type RightSidebar = "author" | "hotArticle" | "randomArticle" | "newComme
// 布局配置
export interface Layout {
isArticleLayout: ArticleLayout,
rightSidebar: RightSidebar[],
isArticleLayout: string,
rightSidebar: string,
swiperImage: string,
swiperText: string[]
swiperText: string
}