diff --git a/apps/web-main/package.json b/apps/web-main/package.json index 2362ce7..59f6975 100644 --- a/apps/web-main/package.json +++ b/apps/web-main/package.json @@ -13,7 +13,9 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "6.8.0", - "@arco-design/web-react": "2.45.0" + "@arco-design/web-react": "2.45.0", + "@ricons/fluent": "0.12.0", + "dayjs": "1.11.7" }, "devDependencies": { "@types/react": "^18.0.27", diff --git a/apps/web-main/src/assets/base.less b/apps/web-main/src/assets/base.less index 315a36a..edff35f 100644 --- a/apps/web-main/src/assets/base.less +++ b/apps/web-main/src/assets/base.less @@ -1,6 +1,10 @@ @import "normalize.css"; @import "@arco-design/web-react/dist/css/arco.css"; +* { + box-sizing: border-box; +} + html, body { position: relative; diff --git a/apps/web-main/src/components/Card/index.less b/apps/web-main/src/components/Card/index.less index 2b62746..ce86667 100644 --- a/apps/web-main/src/components/Card/index.less +++ b/apps/web-main/src/components/Card/index.less @@ -1,8 +1,15 @@ .bs-card { + overflow: hidden; .arco-card-body { padding: 10px; } + &.mini { + .arco-card-body { + padding: 0; + } + } + &:hover { .cover { background-size: 105%; diff --git a/apps/web-main/src/components/Card/index.tsx b/apps/web-main/src/components/Card/index.tsx index 446092a..8edecdd 100644 --- a/apps/web-main/src/components/Card/index.tsx +++ b/apps/web-main/src/components/Card/index.tsx @@ -1,29 +1,36 @@ import { Card } from "@arco-design/web-react"; -import { url } from "inspector"; +import { MouseEventHandler } from "react"; import "./index.less"; const { Meta } = Card; interface IProps { imgUrl: string; title: string; - desc: string; - action: string; + meta?: { + desc?: string; + action?: string; + }; styles?: {}; + onClick?: MouseEventHandler; } function BsCard(props: IProps) { - return ( + const { imgUrl, title, meta, styles, ...rest } = props; + return meta ? (
-

{props.title}

+

{title}

} @@ -31,12 +38,25 @@ function BsCard(props: IProps) { - {props.desc} - {props.action} + {meta?.desc} + {meta?.action} } />
+ ) : ( + +
+
+

{title}

+
+
+
); } diff --git a/apps/web-main/src/components/TimeScroll/index.less b/apps/web-main/src/components/TimeScroll/index.less index 0d209a4..da2a84a 100644 --- a/apps/web-main/src/components/TimeScroll/index.less +++ b/apps/web-main/src/components/TimeScroll/index.less @@ -1,17 +1,16 @@ .timescroll { position: relative; - width: 20px; - height: 200px; - &:hover { - .caret { - display: block; - } - } + width: 40px; + .caret { display: none; position: absolute; + width: 20px; + height: 20px; + left: 50%; + transform: translateX(-50%); right: 0; - color: var(--color-fill-4); + color: var(--color-text-3); &.up { top: 0; } @@ -25,24 +24,35 @@ transform: translateX(-50%); top: 24px; bottom: 24px; - width: 4px; + width: 3px; background: var(--color-fill-2); + &:hover { + .cursor.active { + opacity: 1; + } + } .cursor { + transition: opacity 0.25s ease; position: absolute; - width: 8px; - height: 8px; - border: 1px solid #333; + left: 50%; + transform: translateX(-50%); + width: 12px; + height: 12px; + border: 2px solid; border-radius: 50%; + &.active { + opacity: 0; + } } .node { position: absolute; - width: 4px; - height: 4px; + width: 3px; + height: 3px; border-radius: 50%; &.bingo { left: 50%; transform: translateX(-50%); - background: var(--color-fill-4); + background: var(--color-text-3); &::before { position: absolute; content: attr(data-year); @@ -50,7 +60,8 @@ transform: translateY(-50%); right: 10px; font-size: 12px; - color: var(--color-fill-4); + color: var(--color-text-3); + line-height: 1; } } &.empty { @@ -59,4 +70,10 @@ } } } + + &:hover { + .caret { + display: block; + } + } } diff --git a/apps/web-main/src/components/TimeScroll/index.tsx b/apps/web-main/src/components/TimeScroll/index.tsx index 5d9722b..2ed358d 100644 --- a/apps/web-main/src/components/TimeScroll/index.tsx +++ b/apps/web-main/src/components/TimeScroll/index.tsx @@ -1,17 +1,59 @@ -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; import "./index.less"; -function TimeScroll() { - const [cursor, setCursor] = useState({ +interface IProps { + className: string; + data: any; +} + +function TimeScroll(props: IProps) { + const [cursorStatic, setCursorStatic] = useState({ top: 0, + color: "var(--color-fill-4)", }); - const onMouseMove = (e: any) => { - const diffY = e.screenY - e.clientY; - console.log(e) - setCursor({ top: diffY }); + const [cursorActive, setCursorActive] = useState({ + top: 0, + color: "var(--color-border-3)", + }); + const [intervalPixel, setIntervalPixel] = useState(); + const cursorActiveRef = useRef(null); + const cursorStaticRef = useRef(null); + const orbitRef = useRef(null); + + /** + * 点击打圈圈 cursorStatic + */ + const onMouseDown = (ev: any) => { + const orbitClient = orbitRef.current!.getBoundingClientRect(); + let mouseY = (ev || window.event).clientY; //鼠标按下的位置 + if (mouseY > orbitClient.top && mouseY < orbitClient.bottom) + setCursorStatic((p) => ({ ...p, top: mouseY - orbitClient.top - 4 })); }; + + /** + * 移动圈圈 coursorActive + */ + const onMouseMove = (ev: any) => { + const orbitClient = orbitRef.current!.getBoundingClientRect(); + const mouseY = (ev || window.event).clientY; + if (mouseY > orbitClient.top && mouseY < orbitClient.bottom) + setCursorActive((p) => ({ ...p, top: mouseY - orbitClient.top - 4 })); + }; + + useEffect(() => { + if (props.data) { + const years = [...new Set(props.data.map((i: any) => i.year))]; + const avg = orbitRef.current!.clientHeight / years.length; + setIntervalPixel(avg); + } + }, [props.data, orbitRef.current]); + return ( -
+
-
- -
-
-
-
+
+ + + {props.data.map((item: any, index: number) => { + return item.data.length > 0 ? ( +
+ ) : ( +
+ ); + })}
span { + transition: all 0.25s; + display: inline-block; + margin-left: 20px; + color: var(--color-text-2); + &:hover { + color: var(--color-text-1); + cursor: pointer; + } + svg { + width: 24px; + height: 24px; + } + &.active { + color: rgb(var(--primary-6)); + } + } + } + } + + .timeline { + flex: 1; + position: relative; + .thumbnail { + position: absolute; + top: 0; + left: 0; + right: 100px; + bottom: 0; + overflow: hidden; + > section { + margin-bottom: 40px; + .time { + padding-bottom: 10px; + color: var(--color-text-2); + } + .statistic { + padding-bottom: 10px; + color: var(--color-text-3); + } + .grid { + display: grid; + grid-column-gap: 10px; + grid-row-gap: 10px; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + .bs-card { + border-radius: 3px; + .cover { + height: 120px; + } + .mask { + p { + font-size: 13px; + } + } + } + } + } + } + .timescroll { + position: absolute; + right: 0; + top: 0; + bottom: 0; + } } } diff --git a/apps/web-main/src/view/Course/index.tsx b/apps/web-main/src/view/Course/index.tsx index d95a1ee..117ed14 100644 --- a/apps/web-main/src/view/Course/index.tsx +++ b/apps/web-main/src/view/Course/index.tsx @@ -1,44 +1,51 @@ import "./index.less"; import Player from "../../components/Player"; import { useMount } from "../../hook"; -import { Select, Message } from "@arco-design/web-react"; +import { Select, Message, Space, Tooltip } from "@arco-design/web-react"; const Option = Select.Option; const options = ["全部", "最新的"]; import BsCard from "../../components/Card"; import TimeScroll from "../../components/TimeScroll"; +import Tab20Regular from "@ricons/fluent/Tab20Regular"; +import Table20Regular from "@ricons/fluent/Table20Regular"; +import { useState } from "react"; +import { recommendListDefault, courseTimeListDefault } from "./mock"; export default function Index() { useMount(() => {}); + const [actions, setActions] = useState([ + { + key: "tab", + icon: Tab20Regular, + active: false, + tip: "单格排列", + }, + { + key: "table", + icon: Table20Regular, + active: true, + tip: "缩略", + }, + ]); + + const [recommendList, setRecommendList] = useState(recommendListDefault); + + const [courseTimeList, setCourseTimeList] = useState(courseTimeListDefault); + + const onClickActionItem = (action: any) => { + setActions((p) => p.map((a) => ({ ...a, active: a.key === action.key }))); + }; + return (
- - - + {recommendList.map((item, index) => ( + + ))}
-
+ +
- +
+ {actions.map((action) => ( + + onClickActionItem(action)} + > + + + + ))} +
+
+
+
+ {courseTimeList.map((item, index) => ( +
+
+ {item.year}年{item.month}月 +
+
{item.data.length} 个视频
+
+ {item.data.map((d: any) => ( + + ))} +
+
+ ))} +
+
); diff --git a/apps/web-main/src/view/Course/mock.ts b/apps/web-main/src/view/Course/mock.ts new file mode 100644 index 0000000..ca45e99 --- /dev/null +++ b/apps/web-main/src/view/Course/mock.ts @@ -0,0 +1,83 @@ +import dayjs from "dayjs"; + +export const recommendListDefault = [ + { + imgUrl: + "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp", + title: "这个非常OK啊", + desc: "推荐内容推荐内容推荐内容推荐内容推荐内容推荐内容推荐内容", + action: "开始学习", + }, + { + imgUrl: + "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/e278888093bef8910e829486fb45dd69.png~tplv-uwbnlip3yd-webp.webp", + title: "这个非常OK啊", + desc: "推荐内容推荐内容推荐内容推荐内容推荐内容推荐内容推荐内容", + action: "开始学习", + }, + { + imgUrl: + "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp", + title: "这个非常OK啊", + desc: "推荐内容推荐内容推荐内容推荐内容推荐内容推荐内容推荐内容", + action: "开始学习", + }, +]; + +export const courseTimeList = [ + { + title: "这个非常OK啊1", + time: "1661990400000", + img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp", + }, + { + title: "这个非常OK啊2", + time: "1630454400000", + img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp", + }, + { + title: "这个非常OK啊333", + time: "1625097600000", + img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp", + }, + { + title: "这个非常OK啊444", + time: "1625184000000", + img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp", + }, + { + title: "这个非常OK啊3", + time: "1598918400000", + img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp", + }, +]; + +const process = (before: any[]) => { + const after: any = {}; + // 提取月份,塞入数据 + before.forEach((item) => { + const year = dayjs(+item.time).year(); + const month = dayjs(+item.time).month() + 1; + if (!(year in after)) after[year] = { [month]: [] }; + if (!(month in after[year])) after[year][month] = []; + after[year][month].push(item); + }); + // 年月为key,倒叙排列 + const compare = (key: string) => (a: any, b: any) => b[key] - a[key]; + const ymArray = Object.keys(after) + .reverse() + .map((year) => + Object.keys(after[year]).map((month) => ({ + year, + month, + index: +`${year}.${+month > 10 ? month : "0" + month}`, + data: after[year][month], + })) + ) + .flat() + .sort(compare("index")); + + return ymArray; +}; + +export const courseTimeListDefault = process(courseTimeList); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5cfa169..eb638c2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -178,10 +178,12 @@ importers: apps/web-main: specifiers: '@arco-design/web-react': 2.45.0 + '@ricons/fluent': 0.12.0 '@types/react': ^18.0.27 '@types/react-dom': ^18.0.10 '@types/react-router-dom': 5.3.3 '@vitejs/plugin-react': ^3.1.0 + dayjs: 1.11.7 less: ^4.1.3 react: ^18.2.0 react-dom: ^18.2.0 @@ -191,6 +193,8 @@ importers: vite-tsconfig-paths: 4.0.5 dependencies: '@arco-design/web-react': registry.npmmirror.com/@arco-design/web-react/2.45.0_5ndqzdd6t4rivxsukjv3i3ak2q + '@ricons/fluent': registry.npmmirror.com/@ricons/fluent/0.12.0 + dayjs: registry.npmmirror.com/dayjs/1.11.7 less: registry.npmmirror.com/less/4.1.3 react: registry.npmmirror.com/react/18.2.0 react-dom: registry.npmmirror.com/react-dom/18.2.0_react@18.2.0 @@ -3801,6 +3805,12 @@ packages: engines: {node: '>=14'} dev: false + registry.npmmirror.com/@ricons/fluent/0.12.0: + resolution: {integrity: sha512-q+mPtxwTCZBeNmIrnKQxHc08f4OvJOxaR1AiGbpJpTMAzm/b8ZdrL14wm5ArYJq+uDNpLynfBYi3CTWsHjRRgQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@ricons/fluent/-/fluent-0.12.0.tgz} + name: '@ricons/fluent' + version: 0.12.0 + dev: false + registry.npmmirror.com/@rollup/plugin-commonjs/24.0.1_rollup@3.17.2: resolution: {integrity: sha512-15LsiWRZk4eOGqvrJyu3z3DaBu5BhXIMeWnijSRvd8irrrg9SHpQ1pH+BUK4H6Z9wL9yOxZJMTLU+Au86XHxow==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.1.tgz} id: registry.npmmirror.com/@rollup/plugin-commonjs/24.0.1