This commit is contained in:
Pan Qiancheng 2024-10-19 15:26:01 +08:00
commit c37ba84ea3
32 changed files with 663 additions and 113 deletions

View File

@ -0,0 +1,38 @@
import React from 'react';
import { Viewer } from '@bytemd/react';
import gfm from '@bytemd/plugin-gfm';
import breaks from '@bytemd/plugin-breaks';
import frontmatter from '@bytemd/plugin-frontmatter';
import gemoji from '@bytemd/plugin-gemoji';
import highlight from '@bytemd/plugin-highlight';
import 'katex/dist/katex.css';
import 'highlight.js/styles/default.css';
import math from '@bytemd/plugin-math';
import mediumZoom from '@bytemd/plugin-medium-zoom';
import mermaid from '@bytemd/plugin-mermaid';
// 引入基础css
import 'bytemd/dist/index.min.css';
import "./style.css"
const plugins = [
gfm(),
breaks(),
frontmatter(),
gemoji(),
highlight(),
math(),
mediumZoom(),
mermaid(),
];
const MdViewer = ({ md }: { md: string }) => {
return (
<Viewer
value={md}
plugins={plugins}
/>
);
}
export default MdViewer;

View File

@ -1,3 +1,11 @@
.bytemd {
height: 100%;
}
.markdown-body {
width: 100%;
}
img {
max-width: 100%;
}

View File

@ -2,25 +2,119 @@ import React from 'react';
import Essays from './essays';
import { useHomeContext } from './context/homeContext';
import EssaysByLabel from './essays/byLabel';
import Styles from './styles.module.less';
import LabelList from './labels';
import LabelsByCategory from './labels/byCategory';
import useFeatures from '@project/hooks/useFeatures';
import { DoubleRightOutlined } from '@ant-design/icons';
const FrontendBody = () => {
const { selectedLabelId, selectedCategoryId, searchParam } =
useHomeContext();
const {
selectedLabelId,
selectedCategoryId,
searchParam,
setSearchParam,
setSelectedCategoryId,
setSelectedLabelId,
} = useHomeContext();
const features = useFeatures();
return (
<div>
{!selectedLabelId ? (
<Essays
oakPath='essays'
categoryId={selectedCategoryId!}
searchParam={searchParam}
></Essays>
) : (
<EssaysByLabel
oakPath='essaysByLabel'
oakId={selectedLabelId!}
></EssaysByLabel>
)}
<div className={Styles.body}>
<div className={Styles.left}>
<div className={Styles.labels}>
{!selectedCategoryId ? (
<LabelList
oakPath='labels'
labelClassName={Styles.label}
></LabelList>
) : (
<LabelsByCategory
oakPath='labelsByCategory'
oakId={selectedCategoryId!}
labelClassName={Styles.label}
></LabelsByCategory>
)}
<div className={Styles.blank}></div>
</div>
</div>
<div className={Styles.right}>
<div className={Styles.topFilters}>
{/* 如果什么都没筛选 */}
{!selectedCategoryId &&
!selectedLabelId &&
!searchParam.searchText && (
<div className={Styles.all}>
{features.locales.t('allessay')}
<DoubleRightOutlined />
</div>
)}
{selectedCategoryId && (
<div
className={Styles.category}
onClick={() => {
if (selectedLabelId) {
setSelectedLabelId(null);
return;
}
setSelectedCategoryId(null);
}}
>
{features.locales.t('essay:attr.category')}
<DoubleRightOutlined />
</div>
)}
{selectedLabelId && (
<div
className={Styles.label}
onClick={() => {
setSelectedLabelId(null);
}}
>
{features.locales.t('label:name')}
<DoubleRightOutlined />
</div>
)}
{searchParam.searchText && (
<div
className={Styles.search}
onClick={() => {
setSearchParam({
searchText: '',
});
}}
>
<div className={Styles.content}>
{/* 类型 */}
<div className={Styles.type}>
{features.locales.t(searchParam.searchType)}
</div>
{/* 关键字 */}
<div className={Styles.keyword}>
{searchParam.searchText}
</div>
</div>
<DoubleRightOutlined />
</div>
)}
</div>
<div className={Styles.content}>
{!selectedLabelId ? (
<Essays
oakPath='essays'
categoryId={selectedCategoryId!}
searchParam={searchParam}
></Essays>
) : (
<EssaysByLabel
oakPath='essaysByLabel'
oakId={selectedLabelId!}
categoryId={selectedCategoryId!}
></EssaysByLabel>
)}
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,7 @@
import React from "react";
const FrontendFooter = () => {
return <h1>Footer</h1>;
}
export default FrontendFooter;

View File

@ -1,34 +1,13 @@
import React from 'react';
import LabelList from './labels';
import Styles from './styles.module.less';
import CategoryList from './categories';
import { useHomeContext } from './context/homeContext';
import LabelsByCategory from './labels/byCategory';
const FrontendHeader = () => {
const { selectedCategoryId } = useHomeContext();
return (
<>
<div className={Styles.categories}>
<CategoryList oakPath='categories'></CategoryList>
</div>
<div className={Styles.labels}>
{!selectedCategoryId ? (
<LabelList
oakPath='labels'
labelClassName={Styles.label}
></LabelList>
) : (
<LabelsByCategory
oakPath='labelsByCategory'
oakId={selectedCategoryId!}
labelClassName={Styles.label}
></LabelsByCategory>
)}
<div className={Styles.blank}></div>
</div>
</>
<div className={Styles.categories}>
<CategoryList oakPath='categories'></CategoryList>
</div>
);
};

View File

@ -5,22 +5,16 @@ import Styles from './styles.module.less';
const CatrgoryItem = (props: {
item: RowWithActions<EntityDict, 'category'>;
navigateTo: (params: { url: string; oakId: string }) => void;
t: (key: string) => string;
onClick?: () => void;
selected?: boolean;
}) => {
const { item, navigateTo, t, selected } = props;
const { item, selected } = props;
return (
<div
className={Styles.category}
className={`${Styles.category} ${selected ? Styles.selected : ''}`}
onClick={() => {
props.onClick && props.onClick();
}}
style={{
backgroundColor: selected ? '#f0f0f0' : 'white',
border: selected ? '1px solid #f0f0f0' : '1px solid #f0f0f0',
}}
>
{item.name}
</div>

View File

@ -8,4 +8,17 @@
cursor: pointer;
transition: all 0.3s;
height: 20px;
border: 1px solid #e8e8e8;
min-width: 50px;
&:hover {
background: #e6f3ff;
color: #1890ff;
}
}
.selected {
background: #57aeff;
color: white;
border: 1px solid #aed8ff;
}

View File

@ -8,12 +8,7 @@
.title {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
margin-bottom: 10px;
font-size: 20px;
transition: all 0.3s;
&:hover {

View File

@ -2,7 +2,6 @@ import React from 'react';
import { EntityDict } from '@project/oak-app-domain';
import { RowWithActions, WebComponentProps } from 'oak-frontend-base';
import { useHomeContext } from '../context/homeContext';
import { Empty } from 'antd';
import CatrgoryItem from './item';
import Styles from './styles.module.less';
@ -17,22 +16,25 @@ const CategoryList = (
>
) => {
const { list } = props.data;
const { t, navigateTo } = props.methods;
const { t } = props.methods;
const { selectedCategoryId, setSelectedCategoryId } = useHomeContext();
return (
<>
{list && list.length ? (
<div className={Styles.list}>
<div className={Styles.title}>
<div
className={Styles.title}
onClick={() => {
setSelectedCategoryId(null);
}}
>
{t('categories')} ({list.length})
</div>
{list.map((item) => {
return (
<CatrgoryItem
item={item}
navigateTo={navigateTo}
t={t}
key={item.id}
onClick={() => {
if (selectedCategoryId === item.id) {
@ -47,7 +49,6 @@ const CategoryList = (
})}
</div>
) : (
// <Empty description={t('common::noData')} />
<div>{t('noCategaries')}</div>
)}
</>

View File

@ -1,7 +1,7 @@
import React from 'react';
import { createContext } from 'react';
type SearchType = 'title' | 'content';
export type SearchType = 'title' | 'content';
type HomeContextType = {
// label selection
@ -11,6 +11,8 @@ type HomeContextType = {
selectedCategoryId: string | undefined;
setSelectedCategoryId: (categoryId: string | null) => void;
// search
showSeachInput: boolean;
setShowSeachInput: (show: boolean) => void;
searchParam: {
searchType: SearchType;
searchText: string;
@ -57,6 +59,14 @@ export const HomeProvider: React.FC<{
};
// search
const [showSeachInput, _setShowSeachInput] = React.useState(false);
const setShowSeachInput = (show: boolean) => {
!show &&
setSearchParam({
searchText: '',
});
_setShowSeachInput(show);
};
const [searchParam, _setSearchParam] = React.useState<{
searchType: SearchType;
searchText: string;
@ -87,6 +97,8 @@ export const HomeProvider: React.FC<{
setSelectedLabelId,
selectedCategoryId,
setSelectedCategoryId,
showSeachInput,
setShowSeachInput,
searchParam,
setSearchParam,
}}

View File

@ -17,6 +17,8 @@ export default OakComponent({
},
},
formData({ data }) {
const categoryId = this.props.categoryId;
return {
list: data.essayLabels$label
?.map((item) => {
@ -39,7 +41,11 @@ export default OakComponent({
...item,
cover: '',
};
}),
})
// 只能在这里做筛选,不知道有没有更好的做法
.filter(
(item) => !categoryId || item.categoryId === categoryId
),
};
},
});

View File

@ -4,6 +4,8 @@ import { RowWithActions, WebComponentProps } from 'oak-frontend-base';
import Styles from './styles.module.less';
import dayjs from 'dayjs';
import useFeatures from '@project/hooks/useFeatures';
import { HighlightWords } from '@project/utils/dom-utils';
import { SearchType } from '../../context/homeContext';
const EssayItem = (props: {
item: RowWithActions<EntityDict, 'essay'> & {
@ -11,8 +13,12 @@ const EssayItem = (props: {
};
navigateTo: (params: { url: string; oakId: string }) => void;
t: (key: string) => string;
searchParam?: {
searchType: SearchType;
searchText: string;
};
}) => {
const { item, navigateTo, t } = props;
const { item, navigateTo, t, searchParam } = props;
const features = useFeatures();
// features.extraFile.
@ -39,8 +45,34 @@ const EssayItem = (props: {
)}
</div>
<div className={Styles.body}>
<div className={Styles.title}>{item.title}</div>
<div className={Styles.content}>{item.summary}</div>
<div className={Styles.title}>
{item.isTop && (
<div className={Styles.top}>
{t('common::top')}
</div>
)}
{searchParam &&
searchParam.searchType === 'title' ? (
<HighlightWords
string={item.title!}
words={[searchParam.searchText]}
></HighlightWords>
) : (
item.title
)}
</div>
<div className={Styles.content}>
{/* {item.summary} */}
{searchParam &&
searchParam.searchType === 'content' ? (
<HighlightWords
string={item.summary!}
words={[searchParam.searchText]}
></HighlightWords>
) : (
item.summary
)}
</div>
{/* labels */}
<div className={Styles.labels}>
{item.essayLabels$essay?.map((essayLabel) => {

View File

@ -35,6 +35,14 @@
font-weight: 600;
color: #333;
margin-bottom: 10px;
display: flex;
.top {
background-color: rgb(246, 190, 190);
padding: 0 5px;
margin-right: 5px;
border-radius: 5px;
}
}
.content {

View File

@ -3,6 +3,7 @@ import { EntityDict } from '@project/oak-app-domain';
import { RowWithActions, WebComponentProps } from 'oak-frontend-base';
import EssayItem from './item';
import { Empty } from 'antd';
import { SearchType } from '../context/homeContext';
const Essays = (
props: WebComponentProps<
@ -13,10 +14,14 @@ const Essays = (
list: (RowWithActions<EntityDict, 'essay'> & {
cover: string;
})[];
searchParam: {
searchType: SearchType;
searchText: string;
};
}
>
) => {
const { list } = props.data;
const { list, searchParam } = props.data;
const { t, navigateTo } = props.methods;
return (
@ -30,6 +35,7 @@ const Essays = (
navigateTo={navigateTo}
t={t}
key={item.id}
searchParam={searchParam}
/>
);
})}

View File

@ -22,7 +22,7 @@ const LabelsByCategory = (
<>
{list && (
<div className={Styles.labels}>
<div className={Styles.title}>{t('labelsByCategory')}</div>
<div className={Styles.title}>{t('labelsByCategory') + `(${list.length})`}</div>
<div className={Styles.list}>
<LabelLists
list={list}

View File

@ -21,6 +21,7 @@ export default OakComponent({
formData({ data }) {
return {
list: data,
count: data.length,
};
},
});

View File

@ -1,8 +1,6 @@
.labels {
width: 100%;
.title {
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 1rem;
}
.list {

View File

@ -12,17 +12,18 @@ const LabelList = (
{
list: RowWithActions<EntityDict, 'label'>[];
labelClassName?: string;
count: number;
}
>
) => {
const { list, oakFullpath, labelClassName } = props.data;
const { list, oakFullpath, labelClassName, count } = props.data;
const { t } = props.methods;
return (
<>
{list && (
<div className={Styles.labels}>
<div className={Styles.title}>{t('labelsAll')}</div>
<div className={Styles.title}>{t('labelsAll') + `(${count})`}</div>
<div className={Styles.list}>
<LabelLists
list={list as EntityDict['label']['Schema'][]}

View File

@ -0,0 +1,5 @@
{
"title": "标题包含",
"content": "内容包含",
"allessay": "全部文章"
}

View File

@ -1,21 +1,86 @@
.labels {
display: flex;
margin-top: 20px;
padding: 20px;
.body {
display: grid;
grid-template-columns: 1fr 3fr;
gap: 20px;
overflow: auto;
height: 100%;
.label {
transition: all 0.3s ease;
cursor: pointer;
white-space: nowrap;
.left {
// 最小宽度
min-width: 300px;
// 圆角阴影半透明
background: rgba(255, 255, 255, 0.8);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
border-radius: 10px;
padding: 10px;
backdrop-filter: blur(10px);
height: 100%;
&:hover {
transform: scale(1.05);
.labels {
display: flex;
padding: 20px;
gap: 20px;
overflow: auto;
// 圆角边框背景
border-radius: 10px;
background: rgba(244, 244, 244, 0.8);
.label {
transition: all 0.3s ease;
cursor: pointer;
white-space: nowrap;
&:hover {
transform: scale(1.05);
}
}
.blank {
flex: 1;
}
}
}
.blank {
flex: 1;
.right {
// 圆角阴影半透明
background: rgba(255, 255, 255, 0.8);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
border-radius: 10px;
padding: 20px;
backdrop-filter: blur(10px);
.topFilters {
display: flex;
gap: 10px;
margin-bottom: 15px;
.category,
.label,
.search,
.all {
display: flex;
align-items: center;
gap: 5px;
padding: 5px 10px;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background: rgba(0, 0, 0, 0.1);
}
}
.search {
.content {
flex-direction: column;
.type {
font-size: 12px;
}
.keyword {
font-size: 12px;
}
}
}
}
}
}
}

View File

@ -116,6 +116,16 @@ const i18ns: I18n[] = [
"tips": "请选择模式"
}
},
{
id: "aa6631e469935d0fb5c556e96dcc4379",
namespace: "new-demo-p-frontend-home",
language: "zh-CN",
module: "new-demo",
position: "src/pages/frontend/home",
data: {
"pageTitle": "首页"
}
},
{
id: "fd2b72a085371d814256e3e315beba4b",
namespace: "new-demo-p-frontend-login",
@ -232,6 +242,18 @@ const i18ns: I18n[] = [
"labelsAll": "全部标签"
}
},
{
id: "4b10fe11458024730c7c9c6f4115f807",
namespace: "new-demo-c-frontend-home",
language: "zh-CN",
module: "new-demo",
position: "src/components/frontend/home",
data: {
"title": "标题包含",
"content": "内容包含",
"allessay": "全部文章"
}
},
{
id: "66807ab52114b7a250380d6a509aea19",
namespace: "new-demo-l-common",
@ -289,7 +311,8 @@ const i18ns: I18n[] = [
"enter": "请输入",
"change": "修改",
"finish": "完成",
"creator": "创建人"
"creator": "创建人",
"top": "置顶"
}
},
{
@ -1904,7 +1927,11 @@ const i18ns: I18n[] = [
"console": "控制台",
"logout": "退出",
"close": "关闭",
"login": "登录"
"login": "登录",
"searchPlaceholder": "搜索文章",
"title": "标题",
"content": "内容",
"search": "搜索"
}
},
{
@ -1957,7 +1984,11 @@ const i18ns: I18n[] = [
"console": "控制台",
"logout": "退出",
"close": "关闭",
"login": "登录"
"login": "登录",
"searchPlaceholder": "搜索文章",
"title": "标题",
"content": "内容",
"search": "搜索"
}
},
{

View File

@ -49,5 +49,6 @@
"enter": "请输入",
"change": "修改",
"finish": "完成",
"creator": "创建人"
"creator": "创建人",
"top": "置顶"
}

View File

@ -5,8 +5,22 @@ export default OakComponent({
isList: false,
projection: essayProjection,
formData({ data }) {
const fileIndex = data?.extraFile$entity?.findIndex(
(item) => item.tag1 === 'cover'
);
if (fileIndex !== undefined && fileIndex > -1) {
const url = this.features.extraFile.getUrl(
data!.extraFile$entity![fileIndex]
);
return {
item: data,
// 获取封面的url地址
cover: url,
};
}
return {
item: data,
cover: '',
};
},
});

View File

@ -0,0 +1,118 @@
.essay {
min-height: calc(100vh - 60px);
display: flex;
flex-direction: column;
.top {
position: relative;
width: 100%;
height: 300px;
.backgroundImage {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
// 短边缩放,左右可以留白
object-fit: contain;
z-index: 1;
}
.backgroundfilter {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
// 模糊效果
filter: blur(10px);
z-index: 0;
}
.title {
position: absolute;
bottom: 20px;
left: 20%;
font-size: 30px;
font-weight: 600;
color: #ffffff;
// 翻转色
mix-blend-mode: difference;
padding: 10px;
border-radius: 20px;
backdrop-filter: blur(5px);
z-index: 2;
}
.noCover {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 40px;
font-weight: 600;
color: #ffffff;
mix-blend-mode: difference;
padding: 10px;
border-radius: 20px;
backdrop-filter: blur(5px);
filter: blur(2px);
}
}
.viewer {
width: 60%; // 设置宽度为60%这样左右各留20%
min-width: 400px; // 最小宽度300px
margin-left: auto; // 自动左边距
margin-right: auto; // 自动右边距
padding: 20px;
// 背景色和边框样式
background-color: #ffffff;
border-radius: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
z-index: 3;
.btns {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
}
.blank {
flex: 1;
}
.footer {
background-color: #fff;
margin-top: 20px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
}
.scrollTopButton {
position: fixed;
bottom: 100px;
right: 60px;
background-color: #007bff;
color: white;
border: none;
border-radius: 50%;
width: 50px;
height: 50px;
font-size: 24px;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
transition: opacity 0.3s;
opacity: 0.7;
&:hover {
opacity: 1;
}
}

View File

@ -1,6 +1,11 @@
import React, { useState, useEffect } from 'react';
import { EntityDict } from '@project/oak-app-domain';
import { RowWithActions, WebComponentProps } from 'oak-frontend-base';
import React from 'react';
import Styles from './styles.module.less';
import MdViewer from '@project/components/common/byteMD/MdViewer';
import FrontendFooter from '@project/components/frontend/home/FrontendFooter';
import { Button } from 'antd';
import { VerticalAlignTopOutlined } from '@ant-design/icons';
const EssayDetails = (
props: WebComponentProps<
@ -9,12 +14,83 @@ const EssayDetails = (
false,
{
item: RowWithActions<EntityDict, 'essay'>;
cover: string;
}
>
) => {
const { item, cover } = props.data;
const [showScrollTop, setShowScrollTop] = useState(false);
useEffect(() => {
const handleScroll = () => {
if (window.scrollY > 300) {
setShowScrollTop(true);
} else {
setShowScrollTop(false);
}
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth',
});
};
return (
<div>
<h1>Essay Details</h1>
<div className={Styles.essay}>
{item && (
<>
<div className={Styles.top}>
{cover ? (
<>
<img
className={Styles.backgroundfilter}
src={cover}
alt={item.title}
></img>
<img
className={Styles.backgroundImage}
src={cover}
alt={item.title}
></img>
</>
) : (
<div className={Styles.noCover}>{item.title}</div>
)}
<div className={Styles.title}>{item.title}</div>
</div>
{/* viewer */}
<div className={Styles.viewer}>
{/* 操作按钮,返回 */}
<div className={Styles.btns}>
<Button
className={Styles.back}
onClick={() => window.history.back()}
>
</Button>
</div>
<MdViewer md={item.content!} />
</div>
<div className={Styles.blank}></div>
</>
)}
<div className={Styles.footer}>
<FrontendFooter />
</div>
{showScrollTop && (
<button
className={Styles.scrollTopButton}
onClick={scrollToTop}
>
<VerticalAlignTopOutlined />
</button>
)}
</div>
);
};

View File

@ -0,0 +1,3 @@
{
"pageTitle": "首页"
}

View File

@ -1,5 +1,4 @@
.body {
padding-top: 60px;
background-color: #f0f2f5;
min-height: calc(100vh - 60px);
display: flex;
@ -13,9 +12,7 @@
.content {
flex: 1;
padding: 20px;
background-color: #fff;
margin-top: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.footer {

View File

@ -3,8 +3,20 @@ import React from 'react';
import Styles from './styles.module.less';
import FrontendBody from '@project/components/frontend/home/FrontendBody';
import { useHomeContext } from '@project/components/frontend/home/context/homeContext';
import FrontendFooter from '@project/components/frontend/home/FrontendFooter';
const Home = () => {
const { setShowSeachInput } = useHomeContext();
// 只有在这个页面的时候才会显示输入框,否则隐藏
React.useEffect(() => {
setShowSeachInput(true);
return () => {
setShowSeachInput(false);
};
}, []);
return (
<div className={Styles.body}>
<div className={Styles.header}>
@ -14,7 +26,7 @@ const Home = () => {
<FrontendBody />
</div>
<div className={Styles.footer}>
<h1>Footer</h1>
<FrontendFooter />
</div>
</div>
);

29
src/utils/dom-utils.ts Normal file
View File

@ -0,0 +1,29 @@
import React, { useMemo } from 'react';
export const HighlightWords = ({
string,
words,
}: {
string: string;
words: string[];
}) => {
const highlightedElements = useMemo(() => {
const reg = new RegExp(words.join('|'), 'g');
const token = string.replace(reg, '#@$&#');
const elements = token.split('#').map((x, index) =>
x[0] === '@'
? React.createElement(
'mark',
{
key: index,
className: 'highlight',
},
x.slice(1)
)
: x
);
return elements;
}, [string, words]);
return React.createElement('span', null, ...highlightedElements);
};

View File

@ -20,7 +20,7 @@ export default function render() {
});
}, []);
const { searchParam, setSearchParam } = useHomeContext();
const { searchParam, setSearchParam, showSeachInput } = useHomeContext();
const selectBefore = (
<Select
@ -67,23 +67,27 @@ export default function render() {
{features.locales.t('welcome')} {user?.name || user?.nickname}
</div> */}
</div>
<div className={Styles.search} >
<div className={Styles.search}>
{/* 搜索框 */}
<Space.Compact className={Styles.input}>
<Input
addonBefore={selectBefore}
placeholder={features.locales.t('searchPlaceholder')}
value={searchParam.searchText}
onChange={(e) => {
setSearchParam({
searchText: e.target.value,
});
}}
/>
<Button type='primary' className={Styles.btn}>
{features.locales.t('search')}
</Button>
</Space.Compact>
{showSeachInput && (
<Space.Compact className={Styles.input}>
<Input
addonBefore={selectBefore}
placeholder={features.locales.t(
'searchPlaceholder'
)}
value={searchParam.searchText}
onChange={(e) => {
setSearchParam({
searchText: e.target.value,
});
}}
/>
<Button type='primary' className={Styles.btn}>
{features.locales.t('search')}
</Button>
</Space.Compact>
)}
</div>
<div className={Styles.right}>
{user ? (

View File

@ -1,12 +1,12 @@
.header {
height: 60px;
// background-color: #6661f5;
// 背景透明,模糊
background-color: rgba(255, 255, 255, 0.3);
backdrop-filter: blur(10px);
display: flex;
flex-direction: row;
align-items: center;
z-index: 100;
// header固定在最上方
position: fixed;
@ -65,7 +65,7 @@
.input {
width: 30%;
min-width: 300px;
min-width: 500px;
}
.btn {

View File

@ -1,3 +1,5 @@
.body {
background-color: #f0f2f5;
padding-top: 60px;
min-height: calc(100vh - 60px);
}