| @@ -20,9 +20,9 @@ export default { | |||
| // localhost:8000/api/** -> https://preview.pro.ant.design/api/** | |||
| '/api/': { | |||
| // 要代理的地址 | |||
| target: 'http://172.20.32.197:31213', // 开发环境 | |||
| // target: 'http://172.20.32.235:31213', // 测试环境 | |||
| // target: 'http://172.20.32.44:8082', | |||
| // target: 'http://172.20.32.197:31213', // 开发环境 | |||
| target: 'http://172.20.32.235:31213', // 测试环境 | |||
| // target: 'http://36.103.199.74:31213/', // 公网环境 | |||
| // target: 'http://172.20.32.164:8082', | |||
| // 配置了这个可以从 http 代理到 https | |||
| // 依赖 origin 的功能可能需要这个,比如 cookie | |||
| @@ -75,6 +75,7 @@ | |||
| "fabric": "^5.3.0", | |||
| "highlight.js": "^11.7.0", | |||
| "lodash": "^4.17.21", | |||
| "motion": "~12.23.12", | |||
| "omit.js": "^2.0.2", | |||
| "pnpm": "^8.9.0", | |||
| "query-string": "^8.1.0", | |||
| @@ -82,6 +83,7 @@ | |||
| "rc-util": "^5.30.0", | |||
| "react": "^18.2.0", | |||
| "react-activation": "^0.12.4", | |||
| "react-countup": "~6.5.3", | |||
| "react-cropper": "^2.3.3", | |||
| "react-dev-inspector": "^1.8.1", | |||
| "react-dom": "^18.2.0", | |||
| @@ -8,7 +8,7 @@ | |||
| * - Please do NOT serve this file on production. | |||
| */ | |||
| const PACKAGE_VERSION = '2.7.0' | |||
| const PACKAGE_VERSION = '2.7.1' | |||
| const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f' | |||
| const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') | |||
| const activeClientIds = new Set() | |||
| @@ -3,7 +3,7 @@ | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| padding: 4.375rem 16.25rem 9.375rem; | |||
| padding: 4.375rem @home-padding-x 9.375rem; | |||
| .backgroundFullImage(url(@/assets/img/home/code-bg.png)); | |||
| &__item { | |||
| @@ -3,7 +3,7 @@ | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| padding: 0 16.25rem 11.625rem; | |||
| padding: 0 @home-padding-x 11.125rem; | |||
| &__item { | |||
| position: relative; | |||
| @@ -1,15 +1,28 @@ | |||
| .intro { | |||
| position: relative; | |||
| z-index: 10; | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| background-image: url(@/assets/img/home/header-bg.png); | |||
| background-repeat: no-repeat; | |||
| background-position: left top; | |||
| background-size: 100% 100%; | |||
| position: fixed; | |||
| top: 0; | |||
| right: 0; | |||
| left: 0; | |||
| z-index: 100; | |||
| height: @home-info-height; | |||
| overflow: hidden; | |||
| background-color: red; | |||
| &__content { | |||
| position: relative; | |||
| z-index: 10; | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| padding: 1.25rem @home-padding-x 0; | |||
| background-image: url(@/assets/img/home/header-bg.png); | |||
| background-repeat: no-repeat; | |||
| background-position: left top; | |||
| background-size: 100% 100%; | |||
| } | |||
| &__title { | |||
| margin-top: 1.25rem; | |||
| margin-bottom: 1.125rem; | |||
| color: #ffffff; | |||
| font-weight: 400; | |||
| @@ -1,23 +1,60 @@ | |||
| import { convertRemToPx } from '@/utils'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { motion, useMotionTemplate, useScroll, useSpring, useTransform } from 'motion/react'; | |||
| import NavBar from '../NavBar'; | |||
| import StatisticsBlock from '../Statistics'; | |||
| import styles from './index.less'; | |||
| function IntroBlock() { | |||
| const navigate = useNavigate(); | |||
| const { scrollY } = useScroll(); | |||
| const springValue = useSpring(scrollY, { | |||
| stiffness: 100, | |||
| damping: 30, | |||
| restDelta: 0.001, | |||
| }); | |||
| const initialHeight = convertRemToPx(35); | |||
| console.log(initialHeight); | |||
| const height = useTransform(() => `max(calc(35rem - ${springValue.get()}px), 5rem)`); | |||
| // const left = useTransform(() => `min(${springValue.get()}px, 16.25rem)`); | |||
| const left = useTransform(springValue, [0, initialHeight], [0.0, 16.25]); | |||
| const leftRem = useMotionTemplate`${left}rem`; | |||
| const radius = useTransform(springValue, [0, initialHeight], [0.0, 2.5]); | |||
| const radiusRem = useMotionTemplate`${radius}rem`; | |||
| const top = useTransform(springValue, [0, initialHeight], [0, 10]); | |||
| const paddingX = useTransform(springValue, [0, initialHeight], [16.25, 10]); | |||
| const paddingXRem = useMotionTemplate`1.25rem ${paddingX}rem 0`; | |||
| return ( | |||
| <div className={styles.intro}> | |||
| <NavBar></NavBar> | |||
| <div className={styles['intro__title']}>智能材料科研平台</div> | |||
| <div className={styles['intro__desc']}> | |||
| 智能材料科研平台是用于材料研究和开发的技术平台,它旨在提供实验数据收集、分析和可视化等功能, | |||
| 以支持材料工程师、科学家和研究人员在材料设计、性能评估和工艺优化方面的工作。 | |||
| </div> | |||
| <div className={styles['intro__button']} onClick={() => navigate('/workspace')}> | |||
| 开始使用 | |||
| </div> | |||
| <StatisticsBlock></StatisticsBlock> | |||
| </div> | |||
| <motion.div | |||
| className={styles.intro} | |||
| style={{ | |||
| height: height, | |||
| left: leftRem, | |||
| right: leftRem, | |||
| borderRadius: radiusRem, | |||
| top: top, | |||
| }} | |||
| transition={{ | |||
| type: 'spring', | |||
| duration: 0.3, | |||
| }} | |||
| > | |||
| <motion.div className={styles['intro__content']} style={{ padding: paddingXRem }}> | |||
| <NavBar></NavBar> | |||
| <div className={styles['intro__title']}>智能材料科研平台</div> | |||
| <div className={styles['intro__desc']}> | |||
| 智能材料科研平台是用于材料研究和开发的技术平台,它旨在提供实验数据收集、分析和可视化等功能, | |||
| 以支持材料工程师、科学家和研究人员在材料设计、性能评估和工艺优化方面的工作。 | |||
| </div> | |||
| <div className={styles['intro__button']} onClick={() => navigate('/workspace')}> | |||
| 开始使用 | |||
| </div> | |||
| <StatisticsBlock></StatisticsBlock> | |||
| </motion.div> | |||
| </motion.div> | |||
| ); | |||
| } | |||
| @@ -3,7 +3,7 @@ | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| padding: 0 16.25rem 11.625rem; | |||
| padding: 0 @home-padding-x 11.125rem; | |||
| &__item { | |||
| position: relative; | |||
| @@ -3,12 +3,21 @@ | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| margin-top: 1.25rem; | |||
| padding: 4.125rem 16.25rem 14.75rem; | |||
| padding: 4.125rem @home-padding-x 14.75rem; | |||
| .backgroundFullImage(url(@/assets/img/home/model-bg.png)); | |||
| &__content { | |||
| display: flex; | |||
| flex-wrap: wrap; | |||
| gap: 1.625rem 1.25rem; | |||
| align-items: center; | |||
| width: 100%; | |||
| } | |||
| &__item { | |||
| position: relative; | |||
| display: flex; | |||
| align-items: center; | |||
| width: calc((100% - 2 * 1.25rem) / 3); | |||
| padding: 1.875rem 1.25rem; | |||
| color: @home-text-color-tertiary; | |||
| @@ -3,10 +3,31 @@ import { getPublicModelsReq } from '@/services/home'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { Divider, Flex } from 'antd'; | |||
| import { motion, type Variants } from 'motion/react'; | |||
| import { useEffect, useState } from 'react'; | |||
| import BlockTitle from '../BlockTitle'; | |||
| import styles from './index.less'; | |||
| const modelVariants: Variants = { | |||
| offscreen: (index: number) => ({ | |||
| y: 0, | |||
| opacity: 1, | |||
| transition: { | |||
| ease: 'linear', | |||
| duration: 0.1, | |||
| }, | |||
| }), | |||
| onscreen: { | |||
| y: [0, 200, 0], | |||
| opacity: [0, 0, 1], | |||
| transition: { | |||
| ease: 'easeInOut', | |||
| duration: 0.5, | |||
| times: [0, 0, 1], | |||
| }, | |||
| }, | |||
| }; | |||
| function ModelBlock() { | |||
| const navigate = useNavigate(); | |||
| const [modelData, setModelData] = useState<ModelData[]>([]); | |||
| @@ -30,10 +51,14 @@ function ModelBlock() { | |||
| style={{ marginBottom: '5.25rem' }} | |||
| onClick={() => navigate('/dataset/model')} | |||
| ></BlockTitle> | |||
| <Flex align="center" style={{ width: '100%' }} wrap gap="1.625rem 1.25rem"> | |||
| <div className={styles['model__content']}> | |||
| {modelData.map((item, index) => { | |||
| return ( | |||
| <Flex | |||
| <motion.div | |||
| variants={modelVariants} | |||
| initial={'offscreen'} | |||
| whileInView={'onscreen'} | |||
| custom={index} | |||
| className={styles['model__item']} | |||
| key={item.id} | |||
| onClick={() => { | |||
| @@ -64,10 +89,10 @@ function ModelBlock() { | |||
| <div className={styles['model__item__category']}>材料研发</div> | |||
| </Flex> */} | |||
| </div> | |||
| </Flex> | |||
| </motion.div> | |||
| ); | |||
| })} | |||
| </Flex> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| @@ -1,9 +1,9 @@ | |||
| .nav-bar { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: space-between; | |||
| width: 100%; | |||
| margin-bottom: 4.375rem; | |||
| padding: 1.25rem 15.625rem; | |||
| color: white; | |||
| font-size: 0.9375rem; | |||
| @@ -20,24 +20,30 @@ | |||
| &__menu-item { | |||
| margin-right: 3.125rem; | |||
| padding: 0.25rem 0.5rem; | |||
| border-radius: 0.375rem; | |||
| cursor: pointer; | |||
| &:hover { | |||
| background-color: rgba(0, 0, 0, 0.06); | |||
| } | |||
| &:last-of-type { | |||
| margin-right: 0; | |||
| } | |||
| } | |||
| &__avatar { | |||
| width: 2rem; | |||
| height: 2rem; | |||
| margin-right: 0; | |||
| margin-left: auto; | |||
| cursor: pointer; | |||
| } | |||
| :global { | |||
| .ant-dropdown-trigger { | |||
| height: 2.15rem; | |||
| &__login { | |||
| margin-right: 0; | |||
| margin-left: auto; | |||
| cursor: pointer; | |||
| .ant-avatar { | |||
| margin-right: 0 !important; | |||
| } | |||
| } | |||
| .ant-dropdown-trigger > .anticon { | |||
| display: none; | |||
| } | |||
| } | |||
| } | |||
| @@ -2,6 +2,7 @@ import { getAccessToken } from '@/access'; | |||
| import Avatar from '@/components/RightContent/AvatarDropdown'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { Flex } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import styles from './index.less'; | |||
| function NavBar() { | |||
| @@ -47,26 +48,30 @@ function NavBar() { | |||
| className={styles['nav-bar__app-logo']} | |||
| ></img> | |||
| <span className={styles['nav-bar__app-name']}>智能材料科研平台</span> | |||
| <div className={styles['nav-bar__menu-item']} onClick={() => gotoPage('service')}> | |||
| 服务 | |||
| </div> | |||
| <div className={styles['nav-bar__menu-item']} onClick={() => gotoPage('model')}> | |||
| 模型 | |||
| </div> | |||
| <div className={styles['nav-bar__menu-item']} onClick={() => gotoPage('dataset')}> | |||
| 数据集 | |||
| </div> | |||
| <div className={styles['nav-bar__menu-item']} onClick={() => gotoPage('mirror')}> | |||
| 镜像 | |||
| </div> | |||
| <div className={styles['nav-bar__menu-item']} onClick={() => gotoPage('codeConfig')}> | |||
| 代码配置 | |||
| </div> | |||
| </Flex> | |||
| <div className={styles['nav-bar__menu-item']} onClick={() => gotoPage('service')}> | |||
| 服务 | |||
| </div> | |||
| <div className={styles['nav-bar__menu-item']} onClick={() => gotoPage('model')}> | |||
| 模型 | |||
| </div> | |||
| <div className={styles['nav-bar__menu-item']} onClick={() => gotoPage('dataset')}> | |||
| 数据集 | |||
| </div> | |||
| <div className={styles['nav-bar__menu-item']} onClick={() => gotoPage('mirror')}> | |||
| 镜像 | |||
| </div> | |||
| <div className={styles['nav-bar__menu-item']} onClick={() => gotoPage('codeConfig')}> | |||
| 代码配置 | |||
| </div> | |||
| {token ? ( | |||
| <Avatar menu isHome /> | |||
| ) : ( | |||
| <div className={styles['nav-bar__login']} onClick={() => gotoPage('login')}> | |||
| <div | |||
| className={classNames(styles['nav-bar__menu-item'], styles['nav-bar__login'])} | |||
| onClick={() => gotoPage('login')} | |||
| > | |||
| 登录 | |||
| </div> | |||
| )} | |||
| @@ -0,0 +1,25 @@ | |||
| import { motion, useInView, Variants } from 'motion/react'; | |||
| import { ReactNode, useRef } from 'react'; | |||
| type ScrollRevealProps = { | |||
| children: ReactNode; | |||
| variants: Variants; | |||
| }; | |||
| function ScrollReveal({ children, variants }: ScrollRevealProps) { | |||
| const ref = useRef<HTMLDivElement>(null); | |||
| const isInView = useInView(ref, { amount: 'all' }); | |||
| return ( | |||
| <motion.div | |||
| variants={variants} | |||
| ref={ref} | |||
| initial="offscreen" | |||
| animate={isInView ? 'onscreen' : 'offscreen'} | |||
| > | |||
| {children} | |||
| </motion.div> | |||
| ); | |||
| } | |||
| export default ScrollReveal; | |||
| @@ -4,7 +4,7 @@ | |||
| flex-direction: column; | |||
| align-items: center; | |||
| width: 100%; | |||
| padding: 5.625rem 16.25rem 6.25rem; | |||
| padding: 5.625rem @home-padding-x 6.25rem; | |||
| .backgroundFullImage(url(@/assets/img/home/service-bg.png)); | |||
| &__item { | |||
| @@ -8,10 +8,31 @@ import { formatDate } from '@/utils/date'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { Flex } from 'antd'; | |||
| import { motion, type Variants } from 'motion/react'; | |||
| import { useEffect, useState } from 'react'; | |||
| import BlockTitle from '../BlockTitle'; | |||
| import styles from './index.less'; | |||
| const serviceVariants: Variants = { | |||
| offscreen: { | |||
| y: -100, | |||
| opacity: 0, | |||
| transition: { | |||
| ease: 'linear', | |||
| duration: 0, | |||
| }, | |||
| }, | |||
| onscreen: (index: number) => ({ | |||
| y: 0, | |||
| opacity: 1, | |||
| transition: { | |||
| type: 'spring', | |||
| duration: 1, | |||
| delay: index * 0.3, | |||
| }, | |||
| }), | |||
| }; | |||
| function ServiceBlock() { | |||
| const navigate = useNavigate(); | |||
| const [serviceData, setServiceData] = useState<ServiceData[]>([]); | |||
| @@ -38,10 +59,14 @@ function ServiceBlock() { | |||
| <Flex align="center" style={{ width: '100%' }} gap={'0 1.125rem'}> | |||
| {serviceData.map((item, index) => { | |||
| return ( | |||
| <div | |||
| <motion.div | |||
| variants={serviceVariants} | |||
| className={styles['service__item']} | |||
| key={item.id} | |||
| onClick={() => navigate(`/dataset/modelDeployment/serviceInfo/${item.id}`)} | |||
| initial="offscreen" | |||
| whileInView="onscreen" | |||
| custom={index} | |||
| > | |||
| <div className={styles['service__item__image-container']}> | |||
| <img | |||
| @@ -59,7 +84,7 @@ function ServiceBlock() { | |||
| <div className={styles['service__item__user']}>{item.create_by}</div> | |||
| <div className={styles['service__item__date']}>{formatDate(item.create_time)}</div> | |||
| </Flex> | |||
| </div> | |||
| </motion.div> | |||
| ); | |||
| })} | |||
| </Flex> | |||
| @@ -6,6 +6,7 @@ import ServiceIcon from '@/assets/img/home/service.png'; | |||
| import { getAssetPublicCountReq } from '@/services/home'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useEffect, useState } from 'react'; | |||
| import CountUp from 'react-countup'; | |||
| import styles from './index.less'; | |||
| function StatisticsBlock() { | |||
| @@ -84,7 +85,14 @@ function StatisticsBlock() { | |||
| <div key={item.title} className={styles['statistics__item']}> | |||
| <img src={item.icon} draggable={false} className={styles['statistics__item__icon']} /> | |||
| <div> | |||
| <div className={styles['statistics__item__count']}>{item.value ?? '--'}</div> | |||
| <div className={styles['statistics__item__count']}> | |||
| {item.value ? ( | |||
| <CountUp end={item.value} duration={1} enableScrollSpy></CountUp> | |||
| ) : ( | |||
| '--' | |||
| )} | |||
| </div> | |||
| <div className={styles['statistics__item__name']}>{item.title}</div> | |||
| </div> | |||
| </div> | |||
| @@ -1,6 +1,5 @@ | |||
| .home { | |||
| height: 100%; | |||
| overflow-y: auto; | |||
| padding-top: @home-info-height; | |||
| font-family: Alibaba; | |||
| &__separator { | |||
| @@ -31,6 +31,8 @@ | |||
| @home-text-color-secondary: @text-color-secondary; | |||
| @home-text-color-tertiary: #8284a4; | |||
| @home-divider-color: #d8d8d8; | |||
| @home-padding-x: 16.25rem; | |||
| @home-info-height: 35rem; | |||
| @workspace-background: linear-gradient( | |||
| 179.03deg, | |||
| @@ -365,3 +365,16 @@ export const trimCharacter = (str: string, ch: string): string => { | |||
| export const convertEmptyStringToUndefined = (value?: string): string | undefined => { | |||
| return value === '' ? undefined : value; | |||
| }; | |||
| /** | |||
| * Converts rem to px. | |||
| * | |||
| * @param {number} rem - The value of rem. | |||
| * @return {number} The value of px | |||
| */ | |||
| export const convertRemToPx = (rem: number): number => { | |||
| const fontSize = document.documentElement.style.fontSize; | |||
| const font = parseFloat(fontSize); | |||
| return font * rem; | |||
| }; | |||