diff --git a/src/assets/css/constants.scss b/src/assets/css/constants.scss index 50786e0..923b3e5 100644 --- a/src/assets/css/constants.scss +++ b/src/assets/css/constants.scss @@ -17,4 +17,5 @@ $SIZE_ICON_XS: 16px; $SIZE_ICON_SM: 20px; $SIZE_ICON_MD: 24px; $SIZE_ICON_LG: 32px; -$SIZE_ICON_XL: 64px; \ No newline at end of file +$SIZE_ICON_XL: 64px; +$SIZE_ICON_XXL: 96px; \ No newline at end of file diff --git a/src/assets/css/pages/tools/index.scss b/src/assets/css/pages/tools/index.scss index 9ee637a..000ce1e 100644 --- a/src/assets/css/pages/tools/index.scss +++ b/src/assets/css/pages/tools/index.scss @@ -20,8 +20,8 @@ align-items: center; > * { - flex: 0 0 auto; display: block; + flex: 0 0 auto; } .version-select { diff --git a/src/assets/css/pages/tools/source.scss b/src/assets/css/pages/tools/source.scss new file mode 100644 index 0000000..eba55ab --- /dev/null +++ b/src/assets/css/pages/tools/source.scss @@ -0,0 +1,8 @@ +[data-component=tools-source] { + padding: 30px; + + .card-box { + width: 100%; + height: 100%; + } +} \ No newline at end of file diff --git a/src/assets/css/pages/tools/store.scss b/src/assets/css/pages/tools/store.scss new file mode 100644 index 0000000..eccb0d2 --- /dev/null +++ b/src/assets/css/pages/tools/store.scss @@ -0,0 +1,117 @@ +@use '@/assets/css/constants' as constants; + +[data-component=tools-store] { + .root-content { + padding: 30px; + gap: 20px; + flex-wrap: wrap; + justify-content: flex-start; + + > .card-box { + width: 180px; + height: 290px; + flex: 0 0 auto; + cursor: pointer; + + .common-card { + width: 100%; + height: 100%; + text-align: center; + align-items: center; + + > * { + display: block; + flex: 0 0 auto; + } + + + .icon { + display: flex; + padding-top: 40px; + padding-bottom: 20px; + color: constants.$production-color; + font-size: constants.$SIZE_ICON_XL; + justify-content: center; + + img { + width: constants.$SIZE_ICON_XL; + } + } + + .version { + position: absolute; + left: 10px; + top: 10px; + } + + .info { + padding-top: 20px; + + .tool-name { + font-weight: bolder; + font-size: 1.6em; + } + + .tool-desc { + margin-top: 10px; + color: constants.$font-secondary-color; + } + } + + .author { + display: flex; + margin-top: auto; + flex-direction: row; + justify-content: end; + padding-bottom: 10px; + gap: 10px; + + .avatar { + > * { + width: 24px; + height: 24px; + } + } + + .author-name { + display: flex; + align-items: center; + } + } + + .operation { + position: absolute; + top: 6px; + right: 12px; + font-size: 1.6em; + + >*:hover { + color: constants.$font-secondary-color; + } + } + } + + .load-more-card { + width: 100%; + height: 100%; + text-align: center; + align-items: center; + + .icon { + display: flex; + font-size: constants.$SIZE_ICON_XXL; + color: constants.$production-color; + align-items: center; + transform: translateY(-20px); + } + + .text { + position: absolute; + top: 60%; + font-size: 1.2em; + font-weight: bolder; + } + } + } + } +} \ No newline at end of file diff --git a/src/assets/svg/code.svg b/src/assets/svg/code.svg new file mode 100644 index 0000000..256aba2 --- /dev/null +++ b/src/assets/svg/code.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/more.svg b/src/assets/svg/more.svg new file mode 100644 index 0000000..289f2a6 --- /dev/null +++ b/src/assets/svg/more.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/pages/Tools/Source.tsx b/src/pages/Tools/Source.tsx new file mode 100644 index 0000000..399bf51 --- /dev/null +++ b/src/pages/Tools/Source.tsx @@ -0,0 +1,89 @@ +import '@/assets/css/pages/tools/source.scss' +import { DATABASE_NO_RECORD_FOUND, DATABASE_SELECT_SUCCESS } from '@/constants/common.constants' +import { r_tool_detail } from '@/services/tool' +import { IFiles } from '@/components/Playground/shared' +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' + +const Source = () => { + const navigate = useNavigate() + const { username, toolId, ver } = useParams() + const [loading, setLoading] = useState(false) + const [files, setFiles] = useState({}) + const [selectedFileName, setSelectedFileName] = useState('') + + const render = (toolVo: ToolVo) => { + try { + setFiles(base64ToFiles(toolVo.source.data!)) + setSelectedFileName(toolVo.entryPoint) + } catch (e) { + void message.error('载入工具失败') + } + } + + const getTool = () => { + if (loading) { + return + } + setLoading(true) + void message.loading({ content: '加载中……', key: 'LOADING', duration: 0 }) + + void r_tool_detail(username!, toolId!, ver || 'latest') + .then((res) => { + const response = res.data + switch (response.code) { + case DATABASE_SELECT_SUCCESS: + render(response.data!) + break + case DATABASE_NO_RECORD_FOUND: + void message.error('未找到指定工具') + setTimeout(() => { + navigate(-1) + }, 3000) + break + default: + void message.error('获取工具信息失败,请稍后重试') + } + }) + .finally(() => { + setLoading(false) + message.destroy('LOADING') + }) + } + + useEffect(() => { + if (username === '!' && !getLoginStatus()) { + setTimeout(() => { + navigate(-1) + }, 3000) + return + } + if (username !== '!' && ver) { + navigate(`/source/${username}/${toolId}`) + return + } + if (username === '!' && !ver) { + navigate(`/source/!/${toolId}/latest`) + return + } + getTool() + }, []) + + return ( + + + + + + ) +} + +export default Source diff --git a/src/pages/Tools/Store.tsx b/src/pages/Tools/Store.tsx index 04ce990..6683e48 100644 --- a/src/pages/Tools/Store.tsx +++ b/src/pages/Tools/Store.tsx @@ -1,18 +1,20 @@ -import { DetailedHTMLProps, HTMLAttributes, ReactNode, useEffect, useState } from 'react' +import { DetailedHTMLProps, HTMLAttributes, MouseEvent, ReactNode } from 'react' import VanillaTilt, { TiltOptions } from 'vanilla-tilt' -import Card from '@/components/common/Card.tsx' -import FlexBox from '@/components/common/FlexBox.tsx' import Icon from '@ant-design/icons' -import { COLOR_BACKGROUND, DATABASE_SELECT_SUCCESS } from '@/constants/common.constants.ts' -import FitFullscreen from '@/components/common/FitFullscreen.tsx' -import HideScrollbar from '@/components/common/HideScrollbar.tsx' -import { r_tool_store_get } from '@/services/tool.tsx' +import '@/assets/css/pages/tools/store.scss' +import { COLOR_BACKGROUND, DATABASE_SELECT_SUCCESS } from '@/constants/common.constants' +import { r_tool_store_get } from '@/services/tool' +import Card from '@/components/common/Card' +import FlexBox from '@/components/common/FlexBox' +import FitFullscreen from '@/components/common/FitFullscreen' +import HideScrollbar from '@/components/common/HideScrollbar' interface CommonCardProps extends DetailedHTMLProps, HTMLDivElement> { icon: ReactNode toolName: string toolId: string + toolDesc: string options?: TiltOptions url: string authorName: string @@ -28,6 +30,7 @@ const CommonCard = ({ icon, toolName, toolId, + toolDesc, options = { reverse: true, max: 8, @@ -53,6 +56,11 @@ const CommonCard = ({ url && navigate(url) } + const handleOnSourceBtnClick = (e: MouseEvent) => { + e.stopPropagation() + navigate(`/source/${authorUsername}/${toolId}`) + } + return (
{icon}
-
{ver}
+
+ V{ver} +
{toolName}
{`ID: ${toolId}`}
+ {toolDesc &&
{`简介:${toolDesc}`}
}
@@ -84,6 +95,41 @@ const CommonCard = ({
{authorName}
+
+ + + +
+ + + ) +} + +interface LoadMoreCardProps { + onClick: () => void +} + +const LoadMoreCard = ({ onClick }: LoadMoreCardProps) => { + const cardRef = useRef(null) + + useEffect(() => { + cardRef.current && + VanillaTilt.init(cardRef.current, { + reverse: true, + max: 8, + glare: true, + ['max-glare']: 0.3, + scale: 1.03 + }) + }, []) + + return ( + + +
+ {' '} +
+
加载更多
) @@ -91,24 +137,31 @@ const CommonCard = ({ const Store = () => { const [isLoading, setIsLoading] = useState(false) - const [currentPage, setCurrentPage] = useState(1) + const [currentPage, setCurrentPage] = useState(0) const [hasNextPage, setHasNextPage] = useState(true) - const [toolData, setToolData] = useState() + const [toolData, setToolData] = useState([]) - const getTool = () => { + const getTool = (page: number) => { if (isLoading) { return } setIsLoading(true) void message.loading({ content: '加载工具列表中', key: 'LOADING', duration: 0 }) - void r_tool_store_get({ currentPage }) + void r_tool_store_get({ currentPage: page }) .then((res) => { const response = res.data switch (response.code) { case DATABASE_SELECT_SUCCESS: - setToolData(response.data?.records) + setCurrentPage(page) + setToolData([...toolData, ...response.data!.records]) + if (response.data?.current === response.data?.pages) { + setHasNextPage(false) + } + break + default: + void message.error('加载失败,请稍后重试') } }) .finally(() => { @@ -117,13 +170,20 @@ const Store = () => { }) } + const handleOnLoadMore = () => { + if (isLoading) { + return + } + getTool(currentPage + 1) + } + useEffect(() => { - getTool() + getTool(1) }, []) return ( <> - + {toolData?.map((value) => ( @@ -136,13 +196,15 @@ const Store = () => { } toolName={value.name} toolId={value.toolId} - url={''} + toolDesc={value.description} + url={`/view/${value.author.username}/${value.toolId}`} authorName={value.author.userInfo.nickname} authorAvatar={value.author.userInfo.avatar} authorUsername={value.author.username} ver={value.ver} /> ))} + {hasNextPage && } diff --git a/src/pages/Tools/View.tsx b/src/pages/Tools/View.tsx index 7f15644..2e1c0f6 100644 --- a/src/pages/Tools/View.tsx +++ b/src/pages/Tools/View.tsx @@ -16,22 +16,32 @@ const View = () => { const [compiledCode, setCompiledCode] = useState('') const render = (toolVo: ToolVo) => { - try { - const baseDist = base64ToStr(toolVo.base.dist.data!) - const files = base64ToFiles(toolVo.source.data!) - const importMap = JSON.parse(files[IMPORT_MAP_FILE_NAME].value) as IImportMap + if (username === '!') { + try { + const baseDist = base64ToStr(toolVo.base.dist.data!) + const files = base64ToFiles(toolVo.source.data!) + const importMap = JSON.parse(files[IMPORT_MAP_FILE_NAME].value) as IImportMap - void compiler - .compile(files, importMap, toolVo.entryPoint) - .then((result) => { - const output = result.outputFiles[0].text - setCompiledCode(`${output}\n${baseDist}`) - }) - .catch((reason) => { - void message.error(`编译失败:${reason}`) - }) - } catch (e) { - void message.error('载入工具失败') + void compiler + .compile(files, importMap, toolVo.entryPoint) + .then((result) => { + const output = result.outputFiles[0].text + setCompiledCode(`${output}\n${baseDist}`) + }) + .catch((reason) => { + void message.error(`编译失败:${reason}`) + }) + } catch (e) { + void message.error('载入工具失败') + } + } else { + try { + const baseDist = base64ToStr(toolVo.base.dist.data!) + const dist = base64ToStr(toolVo.dist.data!) + setCompiledCode(`${dist}\n${baseDist}`) + } catch (e) { + void message.error('载入工具失败') + } } } diff --git a/src/pages/Tools/index.tsx b/src/pages/Tools/index.tsx index b27f0dd..d7b3a39 100644 --- a/src/pages/Tools/index.tsx +++ b/src/pages/Tools/index.tsx @@ -36,6 +36,7 @@ interface CommonCardProps url?: string onOpen?: () => void onEdit?: () => void + onSource?: () => void onPublish?: () => void onCancelReview?: () => void onDelete?: () => void @@ -58,6 +59,7 @@ const CommonCard = ({ url, onOpen, onEdit, + onSource, onPublish, onCancelReview, onDelete, @@ -102,6 +104,11 @@ const CommonCard = ({
)} + {onSource && ( + + 源码 + + )} {onCancelReview && ( 取消审核 @@ -140,15 +147,23 @@ const ToolCard = ({ tools, onDelete, onUpgrade, onSubmit, onCancel }: ToolCardPr } const handleOnEditTool = () => { - if (selectedTool.publish === '0' && ['NONE', 'REJECT'].includes(selectedTool.review)) { + if (['NONE', 'REJECT'].includes(selectedTool.review)) { return () => { navigate(`/edit/${tools[0].toolId}`) } } } + const handleOnSourceTool = () => { + if (selectedTool.review === 'PASS') { + return () => { + navigate(`/source/!/${selectedTool.toolId}/${selectedTool.ver}`) + } + } + } + const handleOnPublishTool = () => { - if (selectedTool.publish === '0' && ['NONE', 'REJECT'].includes(selectedTool.review)) { + if (['NONE', 'REJECT'].includes(selectedTool.review)) { return () => { onSubmit?.(selectedTool) } @@ -156,7 +171,7 @@ const ToolCard = ({ tools, onDelete, onUpgrade, onSubmit, onCancel }: ToolCardPr } const handleOnCancelReview = () => { - if (selectedTool.publish === '0' && selectedTool.review === 'PROCESSING') { + if (selectedTool.review === 'PROCESSING') { return () => { onCancel?.(selectedTool) } @@ -178,6 +193,7 @@ const ToolCard = ({ tools, onDelete, onUpgrade, onSubmit, onCancel }: ToolCardPr toolId={selectedTool.toolId} onOpen={handleOnOpenTool} onEdit={handleOnEditTool()} + onSource={handleOnSourceTool()} onPublish={handleOnPublishTool()} onCancelReview={handleOnCancelReview()} onDelete={handleOnDeleteTool} @@ -189,10 +205,10 @@ const ToolCard = ({ tools, onDelete, onUpgrade, onSubmit, onCancel }: ToolCardPr onChange={handleOnVersionChange} options={tools.map((value) => ({ value: value.id, - label: `${value.ver}${value.publish === '0' ? '*' : ''}` + label: `${value.ver}${value.review !== 'PASS' ? '*' : ''}` }))} /> - {tools.every((value) => value.publish !== '0') && ( + {tools.every((value) => value.review === 'PASS') && ( import('~icons/oxygen/store')), - menu: true, - auth: true + menu: true }, { path: 'create', @@ -50,7 +49,24 @@ export const tools: RouteJsonObject[] = [ absolutePath: '/edit', id: 'tools-edit', component: lazy(() => import('@/pages/Tools/Edit')), - name: '查看' + name: '编辑', + auth: true + }, + { + path: 'source/:username/:toolId/:ver', + absolutePath: '/source', + id: 'tools-source-ver', + component: lazy(() => import('@/pages/Tools/Source')), + name: '源码', + auth: true + }, + { + path: 'source/:username/:toolId', + absolutePath: '/source', + id: 'tools-source', + component: lazy(() => import('@/pages/Tools/Source')), + name: '源码', + auth: true }, { path: '*',