diff --git a/README.md b/README.md
index 8fcf34746..cb973bc60 100644
--- a/README.md
+++ b/README.md
@@ -10,12 +10,9 @@
- [TypeScript Frontend](frontend/)
- [Swift](swift/)
- [UI 设计师](design/)
-- [TypeScript Backend](backend/)
-- [TypeScript Fullstack](fullstack/)
-北京:
-- [TypeScript Backend](backend/)
-- [TypeScript Fullstack(偏 Backend)](fullstack/)
+深圳/香港:
+- [Support Engineer](it-support-engineer/)
链接中有更为具体的 JD。
@@ -129,4 +126,4 @@ Fork 当前仓库并在相关文件夹下找到你的作业。完成作业后提
1. 面试时我们会考察编程语言的掌握能力(TS/Swift)。
2. 我们看重解决问题的能力,以及发散思维的能力。
-3. 所有办公地点在市区,交通便利。
+3. 所有办公地点在市区,交通便利。
\ No newline at end of file
diff --git a/frontend/src/App.css b/frontend/src/App.css
index ebdb2969e..fd7a3f038 100644
--- a/frontend/src/App.css
+++ b/frontend/src/App.css
@@ -1,14 +1,44 @@
.App {
text-align: center;
position: relative;
+ width: 100vw;
+ height: 100vh;
}
-.title {
- font-size: 56px;
- font-weight: 600;
+.container {
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+ padding-top: 170px;
+ padding-bottom: 100px;
+ box-sizing: border-box;
+ background-position: center bottom 100px;
+ background-size: auto 60%;
+ background-repeat: no-repeat;
}
-.text {
- font-size: 28px;
- font-weight: 400;
+.container .text-content {
+ position: relative;
+ z-index: 1;
}
+
+.container .text-content .title {
+ font-size: 60px;
+ line-height: 60px;
+ color: inherit;
+ margin: 0;
+ text-align: center;
+ white-space: pre;
+}
+
+.container .text-content .content {
+ font-size: 34px;
+ text-align: center;
+ line-height: 50px;
+ white-space: pre;
+}
\ No newline at end of file
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
index 4db7ebc25..50feef9c0 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/App.test.tsx
@@ -1,9 +1,12 @@
-import React from 'react';
+import React from 'react'
import { render } from '@testing-library/react';
import App from './App';
-test('renders learn react link', () => {
- const { getByText } = render();
- const linkElement = getByText(/learn react/i);
- expect(linkElement).toBeInTheDocument();
-});
+describe('App', () => {
+ it('component should be render correctly', () => {
+ const { rerender } = render();
+ expect(() => {
+ rerender();
+ }).not.toBeNull();
+ });
+});
\ No newline at end of file
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index c57638ada..65fc2c098 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,7 +1,54 @@
-import './App.css';
+import Carousel from './components/Carousel/index'
+import imgIPhone from './assets/iphone.png'
+import imgTablet from './assets/tablet.png'
+import imgAirPods from './assets/airpods.png'
+
+import './App.css'
+
+export interface CarouselDataItem{
+ id: string
+ color: string
+ backgroundColor: string
+ title?: string[]
+ contents?: string[]
+ bg: string
+}
+
+
+const data: CarouselDataItem[] = [
+ {
+ id: '1',
+ color: '#fff',
+ backgroundColor: '#111111',
+ title: ['xPhone'],
+ contents: ['Lots to love. Less to spend.', 'Starting at $399.'],
+ bg: imgIPhone
+ },
+ {
+ id: '2',
+ color: '#000',
+ backgroundColor: '#FAFAFA',
+ title: ['Tablet'],
+ contents: ['Just the right amount of everything.'],
+ bg: imgTablet
+ },
+ {
+ id: '3',
+ color: '#000',
+ backgroundColor: '#F1F1F1',
+ title: ['Buy a Tablet or xPhone for college.', 'Get airPods.'],
+ bg: imgAirPods
+ }
+]
function App() {
- return
{/* write your component here */}
;
+ return (
+
+
+
+ )
}
-export default App;
+export default App;
\ No newline at end of file
diff --git a/frontend/src/components/Carousel/index.css b/frontend/src/components/Carousel/index.css
new file mode 100644
index 000000000..a44c8e355
--- /dev/null
+++ b/frontend/src/components/Carousel/index.css
@@ -0,0 +1,81 @@
+@keyframes progress {
+ from {
+ transform: scaleX(0);
+ }
+ to {
+ transform: scaleX(1);
+ }
+ }
+
+ .carousel {
+ width: 100%;
+ height: 100%;
+ overflow-x: hidden;
+ }
+
+ .carousel .carousel-wrapper {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-wrap: nowrap;
+ padding: 0;
+ margin: 0;
+ transition: transform 0.3s;
+ }
+
+ .carousel .carousel-wrapper .carousel-item {
+ width: 100%;
+ height: 100%;
+ flex-shrink: 0;
+ position: relative;
+ list-style: none;
+ }
+
+ .carousel .indicators {
+ position: absolute;
+ left: 50%;
+ transform: translateX(-50%);
+ bottom: 30px;
+ display: flex;
+ align-items: center;
+ padding: 0;
+ }
+
+ .carousel .indicators .indicator {
+ list-style: none;
+ box-sizing: border-box;
+ width: 40px;
+ height: 20px;
+ border-radius: 1px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ }
+
+ .carousel .indicators .indicator + .indicator {
+ margin-left: 8px;
+ }
+
+ .carousel .indicators .indicator .indicator__track {
+ display: block;
+ width: 100%;
+ height: 2px;
+ background: rgb(140, 140, 140);
+ }
+
+ .carousel .indicators .indicator .indicator__bar{
+ display: block;
+ border-radius: 1px;
+ width: 100%;
+ height: 2px;
+ background-color: #fff;
+ transform: scaleX(0);
+ transform-origin: left;
+ }
+
+
+ .carousel .indicators .indicator.active .indicator__bar {
+ animation: progress linear;
+ animation-duration: var(--indicator-animation-duration);
+ }
\ No newline at end of file
diff --git a/frontend/src/components/Carousel/index.tsx b/frontend/src/components/Carousel/index.tsx
new file mode 100644
index 000000000..0938839e4
--- /dev/null
+++ b/frontend/src/components/Carousel/index.tsx
@@ -0,0 +1,70 @@
+/* *
+ * @author Ethan
+ * @email 271757749@qq.com
+ * @datetime 2023/04/19
+ * */
+
+import { type FC, type CSSProperties, useEffect, useState } from 'react'
+import './index.css'
+
+import { CarouselDataItem } from '../../App'
+
+export interface CarouselProps {
+ items: CarouselDataItem[]
+ delay?: number
+}
+
+const Carousel: FC = ({ items, delay = 3000}) => {
+ const [active, setActive] = useState(0)
+ useEffect(() => {
+ const timer = setTimeout(() => setActive((active + 1) % items.length), delay)
+ return () => clearTimeout(timer)
+ }, [items, delay, active])
+
+ return (
+
+
+ {items.map((item, idx) => (
+ -
+
+
+ {item.title &&
{item.title.join('\r\n')}
}
+ {item.contents && {item.contents.join('\r\n')}
}
+
+
+
+ ))}
+
+
+ {items.map((item, idx) => (
+ - {
+ setActive(idx)
+ }
+ }
+ >
+
+
+
+
+ ))}
+
+
+ )
+}
+
+export default Carousel
diff --git a/frontend/src/components/index.test.tsx b/frontend/src/components/index.test.tsx
new file mode 100644
index 000000000..b70e4b3ae
--- /dev/null
+++ b/frontend/src/components/index.test.tsx
@@ -0,0 +1,50 @@
+import React from 'react';
+import { render, fireEvent, screen } from '@testing-library/react';
+import Carousel from '../components/Carousel/index'
+import imgIPhone from '../assets/iphone.png'
+import imgTablet from '../assets/tablet.png'
+import imgAirPods from '../assets/airpods.png'
+
+beforeEach(()=>{
+ jest.clearAllMocks();
+})
+
+describe('renders carousel component correctly', () => {
+ const Component = () => (
+
+ )
+ test('click dots',()=>{
+ const { container } = render()
+ const dots = container.children[0].querySelectorAll(".indicator__track");
+ //click dots[0] show first page, the title should be xPhone
+ fireEvent.click(dots[0]);
+ expect(screen.getByText("xPhone")).not.toBeNull()
+ //click dots[1] show second page, the title should be Tablet
+ fireEvent.click(dots[1]);
+ expect(screen.getByText("Tablet")).not.toBeNull()
+ //click dots[2] show second page, the title should be Tablet
+ fireEvent.click(dots[2]);
+ expect(screen.getByText("Buy a Tablet or xPhone for college. Get airPods.")).not.toBeNull()
+})});