mirror of https://gitee.com/answerdev/answer.git
feat: add brand upload component
This commit is contained in:
parent
c918ae4c48
commit
acd263a9db
|
@ -915,6 +915,7 @@ ui:
|
||||||
general: General
|
general: General
|
||||||
interface: Interface
|
interface: Interface
|
||||||
smtp: SMTP
|
smtp: SMTP
|
||||||
|
branding: Branding
|
||||||
dashboard:
|
dashboard:
|
||||||
title: Dashboard
|
title: Dashboard
|
||||||
welcome: Welcome to Answer Admin!
|
welcome: Welcome to Answer Admin!
|
||||||
|
@ -1109,3 +1110,5 @@ ui:
|
||||||
msg: SMTP authentication cannot be empty.
|
msg: SMTP authentication cannot be empty.
|
||||||
'yes': 'Yes'
|
'yes': 'Yes'
|
||||||
'no': 'No'
|
'no': 'No'
|
||||||
|
branding:
|
||||||
|
page_title: Branding
|
||||||
|
|
|
@ -54,7 +54,12 @@ export const ADMIN_NAV_MENUS = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'settings',
|
name: 'settings',
|
||||||
child: [{ name: 'general' }, { name: 'interface' }, { name: 'smtp' }],
|
child: [
|
||||||
|
{ name: 'general' },
|
||||||
|
{ name: 'interface' },
|
||||||
|
{ name: 'branding' },
|
||||||
|
{ name: 'smtp' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { FC } from 'react';
|
||||||
|
import { ButtonGroup, Button } from 'react-bootstrap';
|
||||||
|
|
||||||
|
import { Icon, UploadImg } from '@/components';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type: string;
|
||||||
|
imgPath: string;
|
||||||
|
uploadCallback: (data: FormData) => Promise<any>;
|
||||||
|
deleteCallback: (type: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Index: FC<Props> = ({
|
||||||
|
type,
|
||||||
|
imgPath,
|
||||||
|
uploadCallback,
|
||||||
|
deleteCallback,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="d-flex">
|
||||||
|
<div className="bg-gray-300 upload-img-wrap me-2 d-flex align-items-center justify-content-center">
|
||||||
|
<img src={imgPath} alt="" height={100} />
|
||||||
|
</div>
|
||||||
|
<ButtonGroup vertical className="fit-content">
|
||||||
|
<UploadImg type={type} upload={uploadCallback} className="mb-0">
|
||||||
|
<Icon name="cloud-upload" />
|
||||||
|
</UploadImg>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outline-secondary"
|
||||||
|
onClick={() => deleteCallback(type)}>
|
||||||
|
<Icon name="trash" />
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Index;
|
|
@ -3,10 +3,12 @@ import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
type: string;
|
type: string;
|
||||||
|
className?: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
upload: (data: FormData) => Promise<any>;
|
upload: (data: FormData) => Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Index: React.FC<IProps> = ({ type, upload }) => {
|
const Index: React.FC<IProps> = ({ type, upload, children, className }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [status, setStatus] = useState(false);
|
const [status, setStatus] = useState(false);
|
||||||
|
|
||||||
|
@ -35,8 +37,8 @@ const Index: React.FC<IProps> = ({ type, upload }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label className="mb-2 btn btn-outline-secondary uploadBtn">
|
<label className={`btn btn-outline-secondary uploadBtn ${className}`}>
|
||||||
{status ? t('upload_img.loading') : t('upload_img.name')}
|
{children || (status ? t('upload_img.loading') : t('upload_img.name'))}
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
className="d-none"
|
className="d-none"
|
||||||
|
|
|
@ -25,6 +25,7 @@ import Empty from './Empty';
|
||||||
import BaseUserCard from './BaseUserCard';
|
import BaseUserCard from './BaseUserCard';
|
||||||
import FollowingTags from './FollowingTags';
|
import FollowingTags from './FollowingTags';
|
||||||
import QueryGroup from './QueryGroup';
|
import QueryGroup from './QueryGroup';
|
||||||
|
import BrandUpload from './BrandUpload';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Avatar,
|
Avatar,
|
||||||
|
@ -55,5 +56,6 @@ export {
|
||||||
FollowingTags,
|
FollowingTags,
|
||||||
htmlRender,
|
htmlRender,
|
||||||
QueryGroup,
|
QueryGroup,
|
||||||
|
BrandUpload,
|
||||||
};
|
};
|
||||||
export type { EditorRef };
|
export type { EditorRef };
|
||||||
|
|
|
@ -915,6 +915,7 @@ ui:
|
||||||
general: General
|
general: General
|
||||||
interface: Interface
|
interface: Interface
|
||||||
smtp: SMTP
|
smtp: SMTP
|
||||||
|
branding: Branding
|
||||||
dashboard:
|
dashboard:
|
||||||
title: Dashboard
|
title: Dashboard
|
||||||
welcome: Welcome to Answer Admin!
|
welcome: Welcome to Answer Admin!
|
||||||
|
@ -1109,3 +1110,5 @@ ui:
|
||||||
msg: SMTP authentication cannot be empty.
|
msg: SMTP authentication cannot be empty.
|
||||||
'yes': 'Yes'
|
'yes': 'Yes'
|
||||||
'no': 'No'
|
'no': 'No'
|
||||||
|
branding:
|
||||||
|
page_title: Branding
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
@import '~bootstrap/scss/bootstrap';
|
@import '~bootstrap/scss/bootstrap';
|
||||||
@import '~bootstrap-icons';
|
@import '~bootstrap-icons';
|
||||||
|
|
||||||
|
.bg-gray-300 {
|
||||||
|
background-color: $gray-300;
|
||||||
|
}
|
||||||
|
|
||||||
.focus {
|
.focus {
|
||||||
color: $input-focus-color !important;
|
color: $input-focus-color !important;
|
||||||
background-color: $input-focus-bg !important;
|
background-color: $input-focus-bg !important;
|
||||||
|
@ -143,6 +147,7 @@ a {
|
||||||
|
|
||||||
.fit-content {
|
.fit-content {
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
|
width: fit-content;
|
||||||
flex: none;
|
flex: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,3 +215,8 @@ a {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.upload-img-wrap {
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 128px;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { FC, memo, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { BrandUpload } from '@/components';
|
||||||
|
|
||||||
|
const Index: FC = () => {
|
||||||
|
const { t } = useTranslation('translation', {
|
||||||
|
keyPrefix: 'admin.branding',
|
||||||
|
});
|
||||||
|
|
||||||
|
const [img, setImg] = useState(
|
||||||
|
'https://image-static.segmentfault.com/405/057/4050570037-636c7b0609a49',
|
||||||
|
);
|
||||||
|
|
||||||
|
const imgUpload = (file: any) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
console.log(file);
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-4">{t('page_title')}</h3>
|
||||||
|
<BrandUpload
|
||||||
|
type="logo"
|
||||||
|
imgPath={img}
|
||||||
|
uploadCallback={imgUpload}
|
||||||
|
deleteCallback={() => {
|
||||||
|
console.log('delete');
|
||||||
|
setImg('');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(Index);
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { FC, FormEvent, useEffect, useState } from 'react';
|
import { FC, FormEvent, useEffect, useState } from 'react';
|
||||||
import { Form, Button, Image, Stack } from 'react-bootstrap';
|
import { Form, Button, Image, Stack } from 'react-bootstrap';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
@ -187,7 +187,7 @@ const Interface: FC = () => {
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="d-inline-flex">
|
<div className="d-inline-flex">
|
||||||
<UploadImg type="logo" upload={imgUpload} />
|
<UploadImg type="logo" upload={imgUpload} className="mb-2" />
|
||||||
</div>
|
</div>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Form.Text as="div" className="text-muted">
|
<Form.Text as="div" className="text-muted">
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { Container, Row, Col } from 'react-bootstrap';
|
import { Container, Row, Col } from 'react-bootstrap';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Outlet } from 'react-router-dom';
|
import { Outlet, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import { AccordionNav, AdminHeader, PageTitle } from '@/components';
|
import { AccordionNav, AdminHeader, PageTitle } from '@/components';
|
||||||
import { ADMIN_NAV_MENUS } from '@/common/constants';
|
import { ADMIN_NAV_MENUS } from '@/common/constants';
|
||||||
|
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
|
const formPaths = ['general', 'smtp', 'interface', 'branding'];
|
||||||
|
|
||||||
const Dashboard: FC = () => {
|
const Dashboard: FC = () => {
|
||||||
const { t } = useTranslation('translation', { keyPrefix: 'page_title' });
|
const { t } = useTranslation('translation', { keyPrefix: 'page_title' });
|
||||||
|
const { pathname } = useLocation();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageTitle title={t('admin')} />
|
<PageTitle title={t('admin')} />
|
||||||
|
@ -19,7 +22,7 @@ const Dashboard: FC = () => {
|
||||||
<Col lg={2}>
|
<Col lg={2}>
|
||||||
<AccordionNav menus={ADMIN_NAV_MENUS} />
|
<AccordionNav menus={ADMIN_NAV_MENUS} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col lg={10}>
|
<Col lg={formPaths.find((v) => pathname.includes(v)) ? 6 : 10}>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -364,7 +364,11 @@ const Index: React.FC = () => {
|
||||||
className="me-3 rounded"
|
className="me-3 rounded"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<UploadImg type="avatar" upload={avatarUpload} />
|
<UploadImg
|
||||||
|
type="avatar"
|
||||||
|
upload={avatarUpload}
|
||||||
|
className="mb-2"
|
||||||
|
/>
|
||||||
<div>
|
<div>
|
||||||
<Form.Text className="text-muted mt-0">
|
<Form.Text className="text-muted mt-0">
|
||||||
<Trans i18nKey="settings.profile.avatar.text">
|
<Trans i18nKey="settings.profile.avatar.text">
|
||||||
|
|
|
@ -251,6 +251,10 @@ const routes: RouteNode[] = [
|
||||||
path: 'smtp',
|
path: 'smtp',
|
||||||
page: 'pages/Admin/Smtp',
|
page: 'pages/Admin/Smtp',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'branding',
|
||||||
|
page: 'pages/Admin/Branding',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue