Add developer profile page
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
import FitFullscreen from '@/components/common/FitFullscreen.tsx'
|
||||
import '@/assets/css/pages/system/tools/execute.scss'
|
||||
import { base64ToFiles, base64ToStr, IMPORT_MAP_FILE_NAME } from '@/components/Playground/files.ts'
|
||||
import { IImportMap } from '@/components/Playground/shared.ts'
|
||||
import compiler from '@/components/Playground/compiler.ts'
|
||||
import { DATABASE_NO_RECORD_FOUND, DATABASE_SELECT_SUCCESS } from '@/constants/common.constants.ts'
|
||||
import { r_sys_tool_get_one } from '@/services/system.tsx'
|
||||
import Card from '@/components/common/Card.tsx'
|
||||
import { DATABASE_NO_RECORD_FOUND, DATABASE_SELECT_SUCCESS } from '@/constants/common.constants'
|
||||
import { r_sys_tool_get_one } from '@/services/system'
|
||||
import FitFullscreen from '@/components/common/FitFullscreen'
|
||||
import Card from '@/components/common/Card'
|
||||
import Playground from '@/components/Playground'
|
||||
import compiler from '@/components/Playground/compiler'
|
||||
import { IImportMap } from '@/components/Playground/shared'
|
||||
import { base64ToFiles, base64ToStr, IMPORT_MAP_FILE_NAME } from '@/components/Playground/files'
|
||||
|
||||
const Execute = () => {
|
||||
const navigate = useNavigate()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ChangeEvent, KeyboardEvent } from 'react'
|
||||
import Icon from '@ant-design/icons'
|
||||
import {
|
||||
r_sys_tool_delete,
|
||||
r_sys_tool_get,
|
||||
@@ -15,16 +16,15 @@ import {
|
||||
DATABASE_SELECT_SUCCESS,
|
||||
DATABASE_UPDATE_SUCCESS,
|
||||
TOOL_NOT_UNDER_REVIEW
|
||||
} from '@/constants/common.constants.ts'
|
||||
import FlexBox from '@/components/common/FlexBox.tsx'
|
||||
import Card from '@/components/common/Card.tsx'
|
||||
import FitFullscreen from '@/components/common/FitFullscreen.tsx'
|
||||
import HideScrollbar from '@/components/common/HideScrollbar.tsx'
|
||||
import Icon from '@ant-design/icons'
|
||||
import compiler from '@/components/Playground/compiler.ts'
|
||||
import { base64ToFiles, IMPORT_MAP_FILE_NAME, strToBase64 } from '@/components/Playground/files.ts'
|
||||
import { IImportMap } from '@/components/Playground/shared.ts'
|
||||
import Permission from '@/components/common/Permission.tsx'
|
||||
} from '@/constants/common.constants'
|
||||
import FlexBox from '@/components/common/FlexBox'
|
||||
import Card from '@/components/common/Card'
|
||||
import FitFullscreen from '@/components/common/FitFullscreen'
|
||||
import HideScrollbar from '@/components/common/HideScrollbar'
|
||||
import compiler from '@/components/Playground/compiler'
|
||||
import { IImportMap } from '@/components/Playground/shared'
|
||||
import { base64ToFiles, IMPORT_MAP_FILE_NAME, strToBase64 } from '@/components/Playground/files'
|
||||
import Permission from '@/components/common/Permission'
|
||||
|
||||
const Tools = () => {
|
||||
const navigate = useNavigate()
|
||||
|
||||
@@ -6,7 +6,7 @@ import { base64ToFiles } from '@/components/Playground/files'
|
||||
import Playground from '@/components/Playground'
|
||||
import FitFullscreen from '@/components/common/FitFullscreen'
|
||||
import Card from '@/components/common/Card'
|
||||
import { getLoginStatus } from '@/util/auth.tsx'
|
||||
import { getLoginStatus } from '@/util/auth'
|
||||
|
||||
const Source = () => {
|
||||
const navigate = useNavigate()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DetailedHTMLProps, HTMLAttributes, MouseEvent, ReactNode, UIEvent, useState } from 'react'
|
||||
import { DetailedHTMLProps, HTMLAttributes, MouseEvent, ReactNode, UIEvent } from 'react'
|
||||
import VanillaTilt, { TiltOptions } from 'vanilla-tilt'
|
||||
import Icon from '@ant-design/icons'
|
||||
import '@/assets/css/pages/tools/store.scss'
|
||||
@@ -56,6 +56,11 @@ const CommonCard = ({
|
||||
url && navigate(url)
|
||||
}
|
||||
|
||||
const handleOnClickAuthor = (e: MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation()
|
||||
navigate(authorUsername)
|
||||
}
|
||||
|
||||
const handleOnSourceBtnClick = (e: MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation()
|
||||
navigate(`/source/${authorUsername}/${toolId}`)
|
||||
@@ -78,12 +83,12 @@ const CommonCard = ({
|
||||
<div className={'tool-id'}>{`ID: ${toolId}`}</div>
|
||||
{toolDesc && <div className={'tool-desc'}>{`简介:${toolDesc}`}</div>}
|
||||
</div>
|
||||
<div className={'author'}>
|
||||
<div className={'author'} onClick={handleOnClickAuthor}>
|
||||
<div className={'avatar'}>
|
||||
<AntdAvatar
|
||||
src={
|
||||
<AntdImage
|
||||
preview={{ mask: <Icon component={IconOxygenEye}></Icon> }}
|
||||
preview={false}
|
||||
src={`data:image/png;base64,${authorAvatar}`}
|
||||
alt={'Avatar'}
|
||||
/>
|
||||
@@ -139,11 +144,13 @@ const Store = () => {
|
||||
const scrollTopRef = useRef(0)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [currentPage, setCurrentPage] = useState(0)
|
||||
const [hasNextPage, setHasNextPage] = useState(true)
|
||||
const [hasNextPage, setHasNextPage] = useState(false)
|
||||
const [toolData, setToolData] = useState<ToolVo[]>([])
|
||||
const [hideSearch, setHideSearch] = useState(false)
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
|
||||
const handleOnSearch = (value: string) => {
|
||||
setSearchValue(value)
|
||||
getTool(1, value)
|
||||
}
|
||||
|
||||
@@ -160,7 +167,7 @@ const Store = () => {
|
||||
if (isLoading) {
|
||||
return
|
||||
}
|
||||
getTool(currentPage + 1, '')
|
||||
getTool(currentPage + 1, searchValue)
|
||||
}
|
||||
|
||||
const getTool = (page: number, searchValue: string) => {
|
||||
@@ -177,7 +184,10 @@ const Store = () => {
|
||||
switch (response.code) {
|
||||
case DATABASE_SELECT_SUCCESS:
|
||||
setCurrentPage(response.data!.current)
|
||||
if (response.data!.current === response.data!.pages) {
|
||||
if (
|
||||
response.data!.current === response.data!.pages ||
|
||||
response.data!.total === 0
|
||||
) {
|
||||
setHasNextPage(false)
|
||||
} else {
|
||||
setHasNextPage(true)
|
||||
@@ -220,6 +230,7 @@ const Store = () => {
|
||||
/>
|
||||
</div>
|
||||
<FlexBox direction={'horizontal'} className={'root-content'}>
|
||||
{!toolData.length && <div className={'no-tool'}>未找到任何工具</div>}
|
||||
{toolData?.map((value) => (
|
||||
<CommonCard
|
||||
key={value.id}
|
||||
|
||||
295
src/pages/Tools/User.tsx
Normal file
295
src/pages/Tools/User.tsx
Normal file
@@ -0,0 +1,295 @@
|
||||
import { DetailedHTMLProps, HTMLAttributes, MouseEvent, ReactNode } from 'react'
|
||||
import VanillaTilt, { TiltOptions } from 'vanilla-tilt'
|
||||
import Icon from '@ant-design/icons'
|
||||
import '@/assets/css/pages/tools/user.scss'
|
||||
import {
|
||||
COLOR_BACKGROUND,
|
||||
DATABASE_NO_RECORD_FOUND,
|
||||
DATABASE_SELECT_SUCCESS
|
||||
} from '@/constants/common.constants'
|
||||
import { r_sys_user_info_get_basic } from '@/services/system'
|
||||
import { r_tool_store_get_by_username } from '@/services/tool'
|
||||
import FitFullscreen from '@/components/common/FitFullscreen'
|
||||
import HideScrollbar from '@/components/common/HideScrollbar'
|
||||
import Card from '@/components/common/Card'
|
||||
import FlexBox from '@/components/common/FlexBox'
|
||||
|
||||
interface CommonCardProps
|
||||
extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
|
||||
icon: ReactNode
|
||||
toolName: string
|
||||
toolId: string
|
||||
toolDesc: string
|
||||
options?: TiltOptions
|
||||
url: string
|
||||
authorUsername: string
|
||||
ver: string
|
||||
}
|
||||
|
||||
const CommonCard = ({
|
||||
style,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
ref,
|
||||
icon,
|
||||
toolName,
|
||||
toolId,
|
||||
toolDesc,
|
||||
options = {
|
||||
reverse: true,
|
||||
max: 8,
|
||||
glare: true,
|
||||
['max-glare']: 0.3,
|
||||
scale: 1.03
|
||||
},
|
||||
url,
|
||||
authorUsername,
|
||||
ver,
|
||||
...props
|
||||
}: CommonCardProps) => {
|
||||
const navigate = useNavigate()
|
||||
const cardRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
cardRef.current && VanillaTilt.init(cardRef.current, options)
|
||||
}, [options])
|
||||
|
||||
const handleCardOnClick = () => {
|
||||
url && navigate(url)
|
||||
}
|
||||
|
||||
const handleOnSourceBtnClick = (e: MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation()
|
||||
navigate(`/source/${authorUsername}/${toolId}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card
|
||||
style={{ overflow: 'visible', ...style }}
|
||||
ref={cardRef}
|
||||
{...props}
|
||||
onClick={handleCardOnClick}
|
||||
>
|
||||
<FlexBox className={'common-card'}>
|
||||
<div className={'icon'}>{icon}</div>
|
||||
<div className={'version'}>
|
||||
<AntdTag>V{ver}</AntdTag>
|
||||
</div>
|
||||
<div className={'info'}>
|
||||
<div className={'tool-name'}>{toolName}</div>
|
||||
<div className={'tool-id'}>{`ID: ${toolId}`}</div>
|
||||
{toolDesc && <div className={'tool-desc'}>{`简介:${toolDesc}`}</div>}
|
||||
</div>
|
||||
<div className={'operation'}>
|
||||
<AntdTooltip title={'源码'}>
|
||||
<Icon component={IconOxygenCode} onClick={handleOnSourceBtnClick} />
|
||||
</AntdTooltip>
|
||||
</div>
|
||||
</FlexBox>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
interface LoadMoreCardProps {
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
const LoadMoreCard = ({ onClick }: LoadMoreCardProps) => {
|
||||
const cardRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
cardRef.current &&
|
||||
VanillaTilt.init(cardRef.current, {
|
||||
reverse: true,
|
||||
max: 8,
|
||||
glare: true,
|
||||
['max-glare']: 0.3,
|
||||
scale: 1.03
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Card style={{ overflow: 'visible' }} ref={cardRef} onClick={onClick}>
|
||||
<FlexBox className={'load-more-card'}>
|
||||
<div className={'icon'}>
|
||||
<Icon component={IconOxygenMore} />{' '}
|
||||
</div>
|
||||
<div className={'text'}>加载更多</div>
|
||||
</FlexBox>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
const User = () => {
|
||||
const { username } = useParams()
|
||||
const navigate = useNavigate()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [userWithInfoVo, setUserWithInfoVo] = useState<UserWithInfoVo>()
|
||||
const [isLoadingTools, setIsLoadingTools] = useState(false)
|
||||
const [currentPage, setCurrentPage] = useState(0)
|
||||
const [hasNextPage, setHasNextPage] = useState(false)
|
||||
const [toolData, setToolData] = useState<ToolVo[]>([])
|
||||
|
||||
const handleOnCopyToClipboard = (username?: string) => {
|
||||
return username
|
||||
? () => {
|
||||
void navigator.clipboard
|
||||
.writeText(new URL(`/store/${username}`, location.href).href)
|
||||
.then(() => {
|
||||
void message.success('已复制到剪切板')
|
||||
})
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
|
||||
const getProfile = () => {
|
||||
if (isLoading) {
|
||||
return
|
||||
}
|
||||
setIsLoading(true)
|
||||
void message.loading({ content: '加载中', key: 'LOADING', duration: 0 })
|
||||
|
||||
void r_sys_user_info_get_basic(username!)
|
||||
.then((res) => {
|
||||
const response = res.data
|
||||
switch (response.code) {
|
||||
case DATABASE_SELECT_SUCCESS:
|
||||
setUserWithInfoVo(response.data!)
|
||||
getTool(1)
|
||||
break
|
||||
case DATABASE_NO_RECORD_FOUND:
|
||||
void message.warning('用户不存在')
|
||||
setTimeout(() => {
|
||||
navigate(-1)
|
||||
}, 3000)
|
||||
break
|
||||
default:
|
||||
void message.error('获取失败请稍后重试')
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false)
|
||||
void message.destroy('LOADING')
|
||||
})
|
||||
}
|
||||
|
||||
const handleOnLoadMore = () => {
|
||||
if (isLoading) {
|
||||
return
|
||||
}
|
||||
getTool(currentPage + 1)
|
||||
}
|
||||
|
||||
const getTool = (page: number) => {
|
||||
if (isLoadingTools) {
|
||||
return
|
||||
}
|
||||
setIsLoadingTools(true)
|
||||
void message.loading({ content: '加载工具列表中', key: 'LOADING', duration: 0 })
|
||||
|
||||
void r_tool_store_get_by_username(username!, { currentPage: page })
|
||||
.then((res) => {
|
||||
const response = res.data
|
||||
|
||||
switch (response.code) {
|
||||
case DATABASE_SELECT_SUCCESS:
|
||||
setCurrentPage(response.data!.current)
|
||||
if (
|
||||
response.data!.current === response.data!.pages ||
|
||||
response.data!.total === 0
|
||||
) {
|
||||
setHasNextPage(false)
|
||||
} else {
|
||||
setHasNextPage(true)
|
||||
}
|
||||
if (response.data!.current === 1) {
|
||||
setToolData(response.data!.records)
|
||||
} else {
|
||||
setToolData([...toolData, ...response.data!.records])
|
||||
}
|
||||
break
|
||||
default:
|
||||
void message.error('加载失败,请稍后重试')
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoadingTools(false)
|
||||
message.destroy('LOADING')
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getProfile()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<FitFullscreen data-component={'tools-store-user'}>
|
||||
<HideScrollbar
|
||||
isShowVerticalScrollbar
|
||||
autoHideWaitingTime={1000}
|
||||
className={'root-content'}
|
||||
>
|
||||
<Card className={'root-box'}>
|
||||
<FlexBox className={'info'} direction={'horizontal'}>
|
||||
<div className={'avatar-box'}>
|
||||
<AntdAvatar
|
||||
src={
|
||||
<img
|
||||
src={`data:image/png;base64,${userWithInfoVo?.userInfo.avatar}`}
|
||||
alt={'Avatar'}
|
||||
/>
|
||||
}
|
||||
size={144}
|
||||
style={{
|
||||
background: COLOR_BACKGROUND,
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
className={'avatar'}
|
||||
/>
|
||||
</div>
|
||||
<FlexBox className={'info-name'}>
|
||||
<div className={'nickname'}>
|
||||
{userWithInfoVo?.userInfo.nickname}
|
||||
</div>
|
||||
<a
|
||||
className={'url'}
|
||||
onClick={handleOnCopyToClipboard(userWithInfoVo?.username)}
|
||||
>
|
||||
{userWithInfoVo?.username &&
|
||||
new URL(`/store/${userWithInfoVo.username}`, location.href)
|
||||
.href}
|
||||
<Icon component={IconOxygenCopy} />
|
||||
</a>
|
||||
</FlexBox>
|
||||
</FlexBox>
|
||||
<FlexBox direction={'horizontal'} className={'tools'}>
|
||||
{!toolData.length && (
|
||||
<div className={'no-tool'}>该开发者暂未发布任何工具</div>
|
||||
)}
|
||||
{toolData?.map((value) => (
|
||||
<CommonCard
|
||||
key={value.id}
|
||||
icon={
|
||||
<img
|
||||
src={`data:image/svg+xml;base64,${value.icon}`}
|
||||
alt={'Icon'}
|
||||
/>
|
||||
}
|
||||
toolName={value.name}
|
||||
toolId={value.toolId}
|
||||
toolDesc={value.description}
|
||||
url={`/view/${value.author.username}/${value.toolId}`}
|
||||
authorUsername={value.author.username}
|
||||
ver={value.ver}
|
||||
/>
|
||||
))}
|
||||
{hasNextPage && <LoadMoreCard onClick={handleOnLoadMore} />}
|
||||
</FlexBox>
|
||||
</Card>
|
||||
</HideScrollbar>
|
||||
</FitFullscreen>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default User
|
||||
@@ -266,14 +266,14 @@ const User = () => {
|
||||
href={
|
||||
userWithPowerInfoVo?.username &&
|
||||
new URL(
|
||||
`/view/${userWithPowerInfoVo.username}`,
|
||||
`/store/${userWithPowerInfoVo.username}`,
|
||||
location.href
|
||||
).href
|
||||
}
|
||||
>
|
||||
{userWithPowerInfoVo?.username &&
|
||||
new URL(
|
||||
`/view/${userWithPowerInfoVo.username}`,
|
||||
`/store/${userWithPowerInfoVo.username}`,
|
||||
location.href
|
||||
).href}
|
||||
<Icon component={IconOxygenShare} />
|
||||
|
||||
Reference in New Issue
Block a user