diff --git a/package-lock.json b/package-lock.json index 02e8c03..34836fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2337,34 +2337,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", @@ -2587,52 +2559,6 @@ } } }, - "node_modules/@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "optional": true, - "peer": true, - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "optional": true, - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/express": { - "version": "4.17.14", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.14.tgz", - "integrity": "sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==", - "optional": true, - "peer": true, - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.31", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz", - "integrity": "sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==", - "optional": true, - "peer": true, - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, "node_modules/@types/http-proxy": { "version": "1.17.9", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", @@ -2641,13 +2567,6 @@ "@types/node": "*" } }, - "node_modules/@types/mime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", - "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", - "optional": true, - "peer": true - }, "node_modules/@types/node": { "version": "18.17.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.1.tgz", @@ -2658,20 +2577,6 @@ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" }, - "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "optional": true, - "peer": true - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "optional": true, - "peer": true - }, "node_modules/@types/react": { "version": "18.0.21", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.21.tgz", @@ -2695,17 +2600,6 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, - "node_modules/@types/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", - "optional": true, - "peer": true, - "dependencies": { - "@types/mime": "*", - "@types/node": "*" - } - }, "node_modules/@types/yoga-layout": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/@types/yoga-layout/-/yoga-layout-1.9.2.tgz", @@ -2750,20 +2644,6 @@ "node": ">= 0.6" } }, - "node_modules/acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", - "dev": true, - "optional": true, - "peer": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/ajv-formats": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", @@ -7515,34 +7395,6 @@ "rimraf": "bin.js" } }, - "node_modules/terser": { - "version": "5.15.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.1.tgz", - "integrity": "sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -9579,33 +9431,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" }, - "@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } - } - }, "@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", @@ -9753,52 +9578,6 @@ "use-sync-external-store": "^1.2.0" } }, - "@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "optional": true, - "peer": true, - "requires": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "optional": true, - "peer": true, - "requires": { - "@types/node": "*" - } - }, - "@types/express": { - "version": "4.17.14", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.14.tgz", - "integrity": "sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==", - "optional": true, - "peer": true, - "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "@types/express-serve-static-core": { - "version": "4.17.31", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz", - "integrity": "sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==", - "optional": true, - "peer": true, - "requires": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, "@types/http-proxy": { "version": "1.17.9", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", @@ -9807,13 +9586,6 @@ "@types/node": "*" } }, - "@types/mime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", - "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", - "optional": true, - "peer": true - }, "@types/node": { "version": "18.17.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.1.tgz", @@ -9824,20 +9596,6 @@ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" }, - "@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "optional": true, - "peer": true - }, - "@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "optional": true, - "peer": true - }, "@types/react": { "version": "18.0.21", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.21.tgz", @@ -9861,17 +9619,6 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, - "@types/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", - "optional": true, - "peer": true, - "requires": { - "@types/mime": "*", - "@types/node": "*" - } - }, "@types/yoga-layout": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/@types/yoga-layout/-/yoga-layout-1.9.2.tgz", @@ -9906,14 +9653,6 @@ "negotiator": "0.6.3" } }, - "acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", - "dev": true, - "optional": true, - "peer": true - }, "ajv-formats": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", @@ -13375,30 +13114,6 @@ } } }, - "terser": { - "version": "5.15.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.1.tgz", - "integrity": "sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "optional": true, - "peer": true - } - } - }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", diff --git a/src/App.tsx b/src/App.tsx index 09a4d53..f2d530b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,6 +13,8 @@ import { ProfilePage } from "./pages/profile"; import { SettingsPage } from "./pages/settings"; import { EditorPage, EditArticlePage } from "./pages/editor"; import { ArticlePage } from "./pages/article"; +import { TopicListPage } from "./pages/topic"; +import { TopicDetailPage } from "./pages/topic/detail"; import { TOKEN_KEY } from "./constants"; @@ -75,6 +77,8 @@ function App() { } /> } /> } /> + } /> + } /> } /> } /> diff --git a/src/components/Header.tsx b/src/components/Header.tsx index aa88295..f6894d4 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -20,6 +20,11 @@ export const Header: React.FC = () => { Home +
  • + + 专题 + +
  • {!isLoggedIn ? ( <>
  • diff --git a/src/interfaces/index.d.ts b/src/interfaces/index.d.ts index a15cc74..8ba71af 100644 --- a/src/interfaces/index.d.ts +++ b/src/interfaces/index.d.ts @@ -33,3 +33,13 @@ export interface IAuthor { image: string; username: string; } + +export interface ITopic { + slug: string; + title: string; + description: string; + coverImage: string; + articleCount: number; + createdAt: string; + updatedAt: string; +} diff --git a/src/pages/topic/detail.tsx b/src/pages/topic/detail.tsx new file mode 100644 index 0000000..cd6108a --- /dev/null +++ b/src/pages/topic/detail.tsx @@ -0,0 +1,295 @@ +import { useParams, Link } from "react-router-dom"; +import { IArticle } from "../../interfaces"; +import { ArticleList } from "../../components/article"; +import { Pagination } from "../../components/Pagination"; +import { useTable, useUpdate, useDelete } from "@refinedev/core"; +import dayjs from "dayjs"; + +interface ITopic { + slug: string; + title: string; + description: string; + icon: string; + color: string; + tag: string; +} + +const mockTopics: Record = { + react: { + slug: "react", + title: "React", + description: "深入探讨 React 生态系统,包括 Hooks、性能优化、状态管理等核心内容", + icon: "ion-social-react", + color: "#61dafb", + tag: "react", + }, + javascript: { + slug: "javascript", + title: "JavaScript", + description: "从基础到高级,全面掌握 JavaScript 编程语言的核心概念和最佳实践", + icon: "ion-social-javascript", + color: "#f7df1e", + tag: "javascript", + }, + typescript: { + slug: "typescript", + title: "TypeScript", + description: "学习 TypeScript 类型系统,提升代码质量和开发效率", + icon: "ion-code", + color: "#3178c6", + tag: "typescript", + }, + nodejs: { + slug: "nodejs", + title: "Node.js", + description: "使用 Node.js 构建高性能后端服务,包含 Express、Koa 等框架", + icon: "ion-social-nodejs", + color: "#339933", + tag: "nodejs", + }, + frontend: { + slug: "frontend", + title: "前端工程化", + description: "探索现代前端工程化实践,包括构建工具、CI/CD、性能优化等", + icon: "ion-gear-b", + color: "#e34c26", + tag: "web", + }, + css: { + slug: "css", + title: "CSS", + description: "从布局到动画,掌握现代 CSS 技术和设计模式", + icon: "ion-paintbrush", + color: "#264de4", + tag: "css", + }, +}; + +export const TopicDetailPage: React.FC = () => { + const { slug } = useParams<{ slug: string }>(); + + const topic = slug ? mockTopics[slug] : null; + + const { mutate: updateMutation, isLoading: updateMutationIsLoading } = + useUpdate(); + const { mutate: deleteMutation, isLoading: deleteMutationIsLoading } = + useDelete(); + + const { + tableQueryResult, + current, + setCurrent, + pageCount, + } = useTable({ + resource: "articles", + filters: topic?.tag + ? [ + { + field: "tag", + operator: "eq", + value: topic.tag, + }, + ] + : [], + pagination: { + current: 1, + pageSize: 10, + }, + }); + + const favoriteUnFavoriteIslLoading = + tableQueryResult.isFetching || + updateMutationIsLoading || + deleteMutationIsLoading; + + const favArticle = (articleSlug: string) => { + updateMutation({ + resource: "articles", + id: articleSlug, + meta: { + URLSuffix: "favorite", + }, + values: {}, + }); + }; + + const unFavArticle = (articleSlug: string) => { + deleteMutation({ + resource: "articles", + id: articleSlug, + meta: { + URLSuffix: "favorite", + }, + }); + }; + + if (!topic) { + return ( +
    +
    +
    +
    + +

    专题不存在

    +

    抱歉,您访问的专题未找到

    + + 返回专题列表 + +
    +
    +
    +
    + ); + } + + const articles = tableQueryResult.data?.data || []; + + return ( +
    +
    +
    +
    +
    +
    +
    + + + 返回专题 + +
    +
    + +
    +
    +

    + {topic.title} +

    +

    + {topic.description} +

    +
    +
    +
    +
    +
    {articles.length > 0 ? articles.length : "-"}
    +
    文章
    +
    +
    +
    #{topic.tag}
    +
    标签
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + {tableQueryResult.isLoading && ( +
    + +

    加载文章中...

    +
    + )} + + {!tableQueryResult.isLoading && articles.length === 0 && ( +
    + +

    该专题下暂无文章

    +

    成为第一个贡献者吧!

    +
    + )} + + {articles.map((article) => ( + + article.favorited + ? unFavArticle(article.slug) + : favArticle(article.slug) + } + isItemFavorited={article.favorited} + isItemLoading={favoriteUnFavoriteIslLoading} + /> + ))} + + {articles.length > 0 && pageCount > 1 && ( + + )} +
    +
    +
    +
    + ); +}; diff --git a/src/pages/topic/index.tsx b/src/pages/topic/index.tsx new file mode 100644 index 0000000..a1433c2 --- /dev/null +++ b/src/pages/topic/index.tsx @@ -0,0 +1,207 @@ +import { Link } from "react-router-dom"; + +interface ITopic { + slug: string; + title: string; + description: string; + icon: string; + color: string; + articleCount: number; +} + +const mockTopics: ITopic[] = [ + { + slug: "react", + title: "React", + description: "深入探讨 React 生态系统,包括 Hooks、性能优化、状态管理等核心内容", + icon: "ion-social-react", + color: "#61dafb", + articleCount: 128, + }, + { + slug: "javascript", + title: "JavaScript", + description: "从基础到高级,全面掌握 JavaScript 编程语言的核心概念和最佳实践", + icon: "ion-social-javascript", + color: "#f7df1e", + articleCount: 256, + }, + { + slug: "typescript", + title: "TypeScript", + description: "学习 TypeScript 类型系统,提升代码质量和开发效率", + icon: "ion-code", + color: "#3178c6", + articleCount: 89, + }, + { + slug: "nodejs", + title: "Node.js", + description: "使用 Node.js 构建高性能后端服务,包含 Express、Koa 等框架", + icon: "ion-social-nodejs", + color: "#339933", + articleCount: 167, + }, + { + slug: "frontend", + title: "前端工程化", + description: "探索现代前端工程化实践,包括构建工具、CI/CD、性能优化等", + icon: "ion-gear-b", + color: "#e34c26", + articleCount: 95, + }, + { + slug: "css", + title: "CSS", + description: "从布局到动画,掌握现代 CSS 技术和设计模式", + icon: "ion-paintbrush", + color: "#264de4", + articleCount: 73, + }, +]; + +export const TopicListPage: React.FC = () => { + return ( +
    +
    +
    +

    + 探索专题 +

    +

    + 发现你感兴趣的技术领域,深入学习前沿知识 +

    +
    +
    + +
    +
    + {mockTopics.map((topic) => ( +
    + +
    { + e.currentTarget.style.boxShadow = "0 8px 24px rgba(0,0,0,0.12)"; + e.currentTarget.style.transform = "translateY(-4px)"; + }} + onMouseLeave={(e) => { + e.currentTarget.style.boxShadow = "0 2px 8px rgba(0,0,0,0.08)"; + e.currentTarget.style.transform = "translateY(0)"; + }} + > +
    +
    +
    + +
    +

    + {topic.title} +

    +

    + {topic.description} +

    +
    + + {topic.articleCount} 篇文章 + + + 查看专题 + + +
    +
    +
    + +
    + ))} +
    +
    +
    + ); +};