完成编辑网站功能
This commit is contained in:
@@ -42,7 +42,6 @@ function App() {
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
token: {
|
||||
// Seed Token,影响范围大
|
||||
colorPrimary: '#727cf5',
|
||||
},
|
||||
}}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
14
src/pages/Setup/components/Web/type.d.ts
vendored
14
src/pages/Setup/components/Web/type.d.ts
vendored
@@ -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[]
|
||||
}
|
||||
@@ -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="操作"
|
||||
|
||||
12
src/types/System.d.ts → src/types/project.d.ts
vendored
12
src/types/System.d.ts → src/types/project.d.ts
vendored
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user