Showing
13 changed files
with
1001 additions
and
0 deletions
Too many changes to show.
To preserve performance only 13 of 27 files are displayed.
.eslintrc.cjs
0 → 100644
1 | +module.exports = { | |
2 | + root: true, | |
3 | + env: { browser: true, es2020: true }, | |
4 | + extends: [ | |
5 | + 'eslint:recommended', | |
6 | + 'plugin:@typescript-eslint/recommended', | |
7 | + 'plugin:react-hooks/recommended', | |
8 | + ], | |
9 | + ignorePatterns: ['dist', '.eslintrc.cjs'], | |
10 | + parser: '@typescript-eslint/parser', | |
11 | + plugins: ['react-refresh'], | |
12 | + rules: { | |
13 | + 'react-refresh/only-export-components': [ | |
14 | + 'warn', | |
15 | + { allowConstantExport: true }, | |
16 | + ], | |
17 | + }, | |
18 | +} | ... | ... |
README.md
0 → 100644
1 | +# React + TypeScript + Vite | |
2 | + | |
3 | +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. | |
4 | + | |
5 | +Currently, two official plugins are available: | |
6 | + | |
7 | +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh | |
8 | +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh | |
9 | + | |
10 | +## Expanding the ESLint configuration | |
11 | + | |
12 | +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: | |
13 | + | |
14 | +- Configure the top-level `parserOptions` property like this: | |
15 | + | |
16 | +```js | |
17 | +export default { | |
18 | + // other rules... | |
19 | + parserOptions: { | |
20 | + ecmaVersion: 'latest', | |
21 | + sourceType: 'module', | |
22 | + project: ['./tsconfig.json', './tsconfig.node.json', './tsconfig.app.json'], | |
23 | + tsconfigRootDir: __dirname, | |
24 | + }, | |
25 | +} | |
26 | +``` | |
27 | + | |
28 | +- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` | |
29 | +- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` | |
30 | +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list | |
31 | + | |
32 | +## node 版本 16.15.0 | |
33 | +## 初始化 npm i --force | |
34 | +## 运行 npm run dev | |
35 | +## 打包 npm run build dis文件给到后端上环境 | |
36 | + | |
37 | + | |
38 | +## 备注 | |
39 | +### publish分支 线上正式版本 corpCode lt | |
40 | +### test分支 线上测试版本 corpCode ceshi | |
41 | + | |
42 | +## 关于open接口请求问题 | |
43 | +### 前端不需要单独处理跨域问题(关于proxy的配置),后端在open接口那一层已经处理了 | |
44 | +### 接口里面的query参数,corpCode必填,如果不写,会在控制台报跨域问题 | |
45 | + | |
46 | +## 关于各个版本分支说明 | |
47 | +### publish_first-20240807 最初始版本--前端代码写死的,仿照霍邱写的 | |
48 | +## publish_second-20240910 第二个版本,出了UI稿,前三个页面数据是平台配置open接口返回的,最后一个没定稿是第一个版本里面的静态页面,但是UI都是静态效果 | |
49 | +## publish_three-20241017 第三个版本,在第二个版本基础上加了动效,酷炫版本 | |
50 | +## publish_four- 第四个版本,增加了集成页、多个大屏页(暂时未拉取,应该在v4.0-20241017分支完成后 合并到test分支后拉取) | |
51 | + | |
52 | +# 测试环境测试分支 _baseUrl: https://test.qgutech.com/ | |
53 | +## test_four-czjkq-test 滁州经开区 在第四个版本基础上拉的分支,后续第四个版本需要合并过来,现在是为了使用集成页(corpCode: czjkq) | |
54 | +## test_four-fxjkq-test 肥西经开区 在第四个版本基础上拉的分支,后续第四个版本需要合并过来,现在是为了使用集成页(corpCode: fxjkq) | |
55 | +## test_four-aqjkq-test 安庆经开区 在第四个版本基础上拉的分支,后续第四个版本需要合并过来,现在是为了使用集成页(corpCode: aqjkq) | |
56 | +## test_four-yajkq-test 义安经开区 在第四个版本基础上拉的分支,后续第四个版本需要合并过来,现在是为了使用集成页(corpCode: yajkq) | |
57 | + | |
58 | + | |
59 | +# 生产环境测试分支 _baseUrl: https://apaas.qixiaocloud.com | |
60 | +## test_four-fxjkq 肥西经开区 在第四个版本基础上拉的分支,后续第四个版本需要合并过来,现在是为了使用集成页(corpCode: ceshi) | |
61 | +## test_four-czjkq 滁州经开区 在第四个版本基础上拉的分支,后续第四个版本需要合并过来,现在是为了使用集成页(corpCode: ceshi1) | |
62 | +## test_four-aqjkq 安庆经开区 在第四个版本基础上拉的分支,后续第四个版本需要合并过来,现在是为了使用集成页(corpCode: ceshi2) | |
63 | +## test_four-yajkq 义安经开区 在第四个版本基础上拉的分支,后续第四个版本需要合并过来,现在是为了使用集成页(corpCode: ceshi3) | |
64 | + | |
65 | + | |
66 | +# 生产环境正式分支 _baseUrl: https://apaas.qixiaocloud.com | |
67 | +## publish_four-aqjkq 安庆经开区 在第四个版本基础上拉的分支,后续第四个版本需要合并过来,现在是为了使用集成页(corpCode: aqjkq) | |
68 | +## publish_four-czjkq 滁州经开区 在第四个版本基础上拉的分支,后续第四个版本需要合并过来,现在是为了使用集成页(corpCode: czjkq) | |
69 | +## publish_four-fxjkq 肥西经开区 在第四个版本基础上拉的分支,后续第四个版本需要合并过来,现在是为了使用集成页(corpCode: fxjkq) | |
70 | +## publish_four-yajkq 义安经开区 在第四个版本基础上拉的分支,后续第四个版本需要合并过来,现在是为了使用集成页(corpCode: yajkq) | |
71 | +## publish_four-szsgxq 狮子山高新区 在第四个版本基础上拉的分支,后续第四个版本需要合并过来,现在是为了使用集成页(corpCode: szsgxq)_ | |
72 | + | |
73 | +# 滁州经开区-私有化部署 _baseUrl: http://192.168.0.143:81 | |
74 | +## private_ip-czjkq | ... | ... |
index.html
0 → 100644
1 | +<!doctype html> | |
2 | +<html lang="en"> | |
3 | + <head> | |
4 | + <meta charset="UTF-8" /> | |
5 | + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
6 | + <title>徽智制联-deepSeek</title> | |
7 | + <link rel="icon" type="image/svg+xml" href="/seat_logo.png" /> | |
8 | + | |
9 | + <script type="text/javascript"> | |
10 | + | |
11 | + </script> | |
12 | + </head> | |
13 | + <body> | |
14 | + <div id="root"></div> | |
15 | + <script type="module" src="/src/main.tsx"></script> | |
16 | + </body> | |
17 | +</html> | ... | ... |
package.json
0 → 100644
1 | +{ | |
2 | + "name": "hzzlDp", | |
3 | + "private": true, | |
4 | + "version": "0.0.1", | |
5 | + "type": "module", | |
6 | + "scripts": { | |
7 | + "dev": "vite", | |
8 | + "build": "tsc -b && vite build", | |
9 | + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", | |
10 | + "preview": "vite preview" | |
11 | + }, | |
12 | + "dependencies": { | |
13 | + "antd": "^5.22.3", | |
14 | + "axios": "^1.7.3", | |
15 | + "cesium": "^1.124.0", | |
16 | + "echarts": "^5.5.1", | |
17 | + "echarts-for-react": "^3.0.2", | |
18 | + "lodash": "^4.17.11", | |
19 | + "normalize.css": "^8.0.1", | |
20 | + "rc-pagination": "^4.2.0", | |
21 | + "rc-tooltip": "^6.2.0", | |
22 | + "react": "^18.3.1", | |
23 | + "js-cookie": "^3.0.5", | |
24 | + "react-dom": "^18.3.1", | |
25 | + "react-echarts": "^0.1.1", | |
26 | + "react-router-dom": "^6.26.0", | |
27 | + "swiper": "^8.4.4", | |
28 | + "react-markdown": "^9.0.1", | |
29 | + "lodash-es": "^4.17.21", | |
30 | + "rehype-raw": "^7.0.0", | |
31 | + "remark-gfm": "^4.0.0", | |
32 | + "rehype-katex": "^7.0.1", | |
33 | + "remark-breaks": "^4.0.0", | |
34 | + "remark-math": "^6.0.0", | |
35 | + "react-syntax-highlighter": "^15.6.1" | |
36 | + }, | |
37 | + "devDependencies": { | |
38 | + "@types/js-cookie": "^3.0.6", | |
39 | + "@types/echarts": "^4.9.22", | |
40 | + "@types/lodash": "^4.17.7", | |
41 | + "@types/react": "^18.3.3", | |
42 | + "@types/react-dom": "^18.3.0", | |
43 | + "@typescript-eslint/eslint-plugin": "^7.15.0", | |
44 | + "@typescript-eslint/parser": "^7.15.0", | |
45 | + "@vitejs/plugin-react": "^4.3.1", | |
46 | + "eslint": "^8.57.0", | |
47 | + "eslint-plugin-react-hooks": "^4.6.2", | |
48 | + "eslint-plugin-react-refresh": "^0.4.7", | |
49 | + "less": "^4.2.0", | |
50 | + "less-loader": "^12.2.0", | |
51 | + "typescript": "^5.2.2", | |
52 | + "@types/react-syntax-highlighter": "^15.5.13", | |
53 | + "vite": "^5.3.4" | |
54 | + } | |
55 | +} | ... | ... |
public/logo.png
0 → 100644
1.59 KB
public/seat_logo.png
0 → 100644
190 Bytes
src/assets/react.svg
0 → 100644
1 | +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg> | |
\ No newline at end of file | ... | ... |
src/components/deepSeekIndex/Markdown.tsx
0 → 100644
1 | +import React, {useState, useEffect, useMemo, memo} from "react"; | |
2 | +import '../../pages/deep-seek-index/base.less' | |
3 | +import '@/pages/deep-seek-index/style.less' | |
4 | +import ReactMarkdown from 'react-markdown' | |
5 | +import { flow } from 'lodash-es' | |
6 | +import 'katex/dist/katex.min.css' | |
7 | +import RemarkMath from 'remark-math' | |
8 | +import RemarkBreaks from 'remark-breaks' | |
9 | +import RehypeKatex from 'rehype-katex' | |
10 | +import RemarkGfm from 'remark-gfm' | |
11 | +import RehypeRaw from 'rehype-raw' | |
12 | +import SyntaxHighlighter from 'react-syntax-highlighter' | |
13 | + | |
14 | + | |
15 | +import MarkdownButton from './markdown-button' | |
16 | +import MarkdownForm from './markdown-form' | |
17 | +import ThinkBlock from './markdown-block' | |
18 | +import VideoGallery from './videoGallery' | |
19 | + | |
20 | +interface MarkdownProps { | |
21 | + content: any | |
22 | + customDisallowedElements?: any | |
23 | +} | |
24 | + | |
25 | +const capitalizationLanguageNameMap: Record<string, string> = { | |
26 | + sql: 'SQL', | |
27 | + javascript: 'JavaScript', | |
28 | + java: 'Java', | |
29 | + typescript: 'TypeScript', | |
30 | + vbscript: 'VBScript', | |
31 | + css: 'CSS', | |
32 | + html: 'HTML', | |
33 | + xml: 'XML', | |
34 | + php: 'PHP', | |
35 | + python: 'Python', | |
36 | + yaml: 'Yaml', | |
37 | + mermaid: 'Mermaid', | |
38 | + markdown: 'MarkDown', | |
39 | + makefile: 'MakeFile', | |
40 | + echarts: 'ECharts', | |
41 | + shell: 'Shell', | |
42 | + powershell: 'PowerShell', | |
43 | + json: 'JSON', | |
44 | + latex: 'Latex', | |
45 | + svg: 'SVG', | |
46 | +} | |
47 | + | |
48 | +const getCorrectCapitalizationLanguageName = (language: string) => { | |
49 | + if (!language) | |
50 | + return 'Plain' | |
51 | + | |
52 | + if (language in capitalizationLanguageNameMap) | |
53 | + return capitalizationLanguageNameMap[language] | |
54 | + | |
55 | + return language.charAt(0).toUpperCase() + language.substring(1) | |
56 | +} | |
57 | + | |
58 | + | |
59 | +const preprocessThinkTag = (content: string) => { | |
60 | + return flow([ | |
61 | + (str: string) => str.replace('<think>\n', '<details data-think=true>\n'), | |
62 | + (str: string) => str.replace('\n</think>', '\n[ENDTHINKFLAG]</details>'), | |
63 | + ])(content) | |
64 | +} | |
65 | + | |
66 | +const preprocessLaTeX = (content: string) => { | |
67 | + if (typeof content !== 'string') | |
68 | + return content | |
69 | + | |
70 | + return flow([ | |
71 | + (str: string) => str.replace(/\\\[(.*?)\\\]/g, (_, equation) => `$$${equation}$$`), | |
72 | + (str: string) => str.replace(/\\\[(.*?)\\\]/gs, (_, equation) => `$$${equation}$$`), | |
73 | + (str: string) => str.replace(/\\\((.*?)\\\)/g, (_, equation) => `$$${equation}$$`), | |
74 | + (str: string) => str.replace(/(^|[^\\])\$(.+?)\$/g, (_, prefix, equation) => `${prefix}$${equation}$`), | |
75 | + ])(content) | |
76 | +} | |
77 | + | |
78 | + | |
79 | +const CodeBlock: any = memo(({ inline, className, children, ...props }: any) => { | |
80 | + const [isSVG, setIsSVG] = useState(true) | |
81 | + const match = /language-(\w+)/.exec(className || '') | |
82 | + const language = match?.[1] | |
83 | + const languageShowName = getCorrectCapitalizationLanguageName(language || '') | |
84 | + const chartData = useMemo(() => { | |
85 | + if (language === 'echarts') { | |
86 | + try { | |
87 | + return JSON.parse(String(children).replace(/\n$/, '')) | |
88 | + } | |
89 | + catch (error) { } | |
90 | + } | |
91 | + return JSON.parse('{"title":{"text":"ECharts error - Wrong JSON format."}}') | |
92 | + }, [language, children]) | |
93 | + | |
94 | + const renderCodeContent = useMemo(() => { | |
95 | + const content = String(children).replace(/\n$/, '') | |
96 | + if (language === 'mermaid' && isSVG) { | |
97 | + return <Flowchart PrimitiveCode={content} /> | |
98 | + } | |
99 | + else if (language === 'echarts') { | |
100 | + return ( | |
101 | + <div style={{ minHeight: '350px', minWidth: '100%', overflowX: 'scroll' }}> | |
102 | + <ErrorBoundary> | |
103 | + <ReactEcharts option={chartData} style={{ minWidth: '700px' }} /> | |
104 | + </ErrorBoundary> | |
105 | + </div> | |
106 | + ) | |
107 | + } | |
108 | + else if (language === 'svg' && isSVG) { | |
109 | + return ( | |
110 | + <ErrorBoundary> | |
111 | + <SVGRenderer content={content} /> | |
112 | + </ErrorBoundary> | |
113 | + ) | |
114 | + } | |
115 | + else { | |
116 | + return ( | |
117 | + <SyntaxHighlighter | |
118 | + {...props} | |
119 | + customStyle={{ | |
120 | + paddingLeft: 12, | |
121 | + borderBottomLeftRadius: '10px', | |
122 | + borderBottomRightRadius: '10px', | |
123 | + backgroundColor: 'var(--color-components-input-bg-normal)', | |
124 | + }} | |
125 | + language={match?.[1]} | |
126 | + showLineNumbers | |
127 | + PreTag="div" | |
128 | + > | |
129 | + {content} | |
130 | + </SyntaxHighlighter> | |
131 | + ) | |
132 | + } | |
133 | + }, [language, match, props, children, chartData, isSVG]) | |
134 | + | |
135 | + if (inline || !match) | |
136 | + return <code {...props} className={className}>{children}</code> | |
137 | + | |
138 | + return ( | |
139 | + <div className='relative'> | |
140 | + <div className='flex h-8 items-center justify-between rounded-t-[10px] border-b border-divider-subtle bg-components-input-bg-normal p-1 pl-3'> | |
141 | + <div className='system-xs-semibold-uppercase text-text-secondary'>{languageShowName}</div> | |
142 | + <div className='flex items-center gap-1'> | |
143 | + {/* todo */} | |
144 | + {/*{(['mermaid', 'svg']).includes(language!) && <SVGBtn isSVG={isSVG} setIsSVG={setIsSVG} />}*/} | |
145 | + {/*<ActionButton>*/} | |
146 | + {/* <CopyIcon content={String(children).replace(/\n$/, '')} />*/} | |
147 | + {/*</ActionButton>*/} | |
148 | + </div> | |
149 | + </div> | |
150 | + {renderCodeContent} | |
151 | + </div> | |
152 | + ) | |
153 | +}) | |
154 | +CodeBlock.displayName = 'CodeBlock' | |
155 | + | |
156 | + | |
157 | +const VideoBlock: any = memo(({ node }: any) => { | |
158 | + const srcs = node.children.filter((child: any) => 'properties' in child).map((child: any) => (child as any).properties.src) | |
159 | + if (srcs.length === 0) | |
160 | + return null | |
161 | + return <VideoGallery key={srcs.join()} srcs={srcs} /> | |
162 | +}) | |
163 | +VideoBlock.displayName = 'VideoBlock' | |
164 | + | |
165 | +const AudioBlock: any = memo(({ node }: any) => { | |
166 | + const srcs = node.children.filter((child: any) => 'properties' in child).map((child: any) => (child as any).properties.src) | |
167 | + if (srcs.length === 0) | |
168 | + return null | |
169 | + return <AudioGallery key={srcs.join()} srcs={srcs} /> | |
170 | +}) | |
171 | +AudioBlock.displayName = 'AudioBlock' | |
172 | + | |
173 | +const ScriptBlock = memo(({ node }: any) => { | |
174 | + const scriptContent = node.children[0]?.value || '' | |
175 | + return `<script>${scriptContent}</script>` | |
176 | +}) | |
177 | +ScriptBlock.displayName = 'ScriptBlock' | |
178 | + | |
179 | +const Paragraph = (paragraph: any) => { | |
180 | + const children_node = paragraph?.node?.children | |
181 | + if (children_node && children_node?.[0] && 'tagName' in children_node?.[0] && children_node?.[0]?.tagName === 'img') { | |
182 | + return ( | |
183 | + <> | |
184 | + <ImageGallery srcs={[children_node?.[0].properties?.src]} /> | |
185 | + { | |
186 | + Array.isArray(paragraph?.children) ? <p>{paragraph?.children?.slice(1)}</p> : null | |
187 | + } | |
188 | + </> | |
189 | + ) | |
190 | + } | |
191 | + return <p>{paragraph?.children}</p> | |
192 | +} | |
193 | + | |
194 | +const Img = ({ src }: any) => { | |
195 | + return (<ImageGallery srcs={[src]} />) | |
196 | +} | |
197 | + | |
198 | +const Link = ({ node, ...props }: any) => { | |
199 | + if (node.properties?.href && node.properties.href?.toString().startsWith('abbr')) { | |
200 | + // eslint-disable-next-line react-hooks/rules-of-hooks | |
201 | + // const { onSend } = useChatContext() | |
202 | + const hidden_text = decodeURIComponent(node.properties.href.toString().split('abbr:')[1]) | |
203 | + | |
204 | + return <abbr className="cursor-pointer underline !decoration-primary-700 decoration-dashed" title={node.children[0]?.value}>{node.children[0]?.value}</abbr> | |
205 | + } | |
206 | + else { | |
207 | + return <a {...props} target="_blank" className="cursor-pointer underline !decoration-primary-700 decoration-dashed">{node.children[0] ? node.children[0]?.value : 'Download'}</a> | |
208 | + } | |
209 | +} | |
210 | + | |
211 | +const Markdown: React.FC = (props: MarkdownProps) => { | |
212 | + | |
213 | + const latexContent = flow([ | |
214 | + preprocessThinkTag, | |
215 | + preprocessLaTeX, | |
216 | + ])(props?.content || '') | |
217 | + | |
218 | + return ( | |
219 | + <div className={'mark-down'}> | |
220 | + <ReactMarkdown | |
221 | + remarkPlugins={[ | |
222 | + RemarkGfm, | |
223 | + [RemarkMath, { singleDollarTextMath: false }], | |
224 | + RemarkBreaks, | |
225 | + ]} | |
226 | + rehypePlugins={[ | |
227 | + RehypeKatex, | |
228 | + RehypeRaw as any, | |
229 | + // The Rehype plug-in is used to remove the ref attribute of an element | |
230 | + () => { | |
231 | + return (tree) => { | |
232 | + const iterate = (node: any) => { | |
233 | + if (node.type === 'element' && node.properties?.ref) | |
234 | + delete node.properties.ref | |
235 | + | |
236 | + if (node.type === 'element' && !/^[a-z][a-z0-9]*$/i.test(node.tagName)) { | |
237 | + node.type = 'text' | |
238 | + node.value = `<${node.tagName}` | |
239 | + } | |
240 | + | |
241 | + if (node.children) | |
242 | + node.children.forEach(iterate) | |
243 | + } | |
244 | + tree.children.forEach(iterate) | |
245 | + } | |
246 | + }, | |
247 | + ]} | |
248 | + disallowedElements={['iframe', 'head', 'html', 'meta', 'link', 'style', 'body', ...(props?.customDisallowedElements || [])]} | |
249 | + components={{ | |
250 | + code: CodeBlock, | |
251 | + img: Img, | |
252 | + video: VideoBlock, | |
253 | + audio: AudioBlock, | |
254 | + a: Link, | |
255 | + p: Paragraph, | |
256 | + button: MarkdownButton, | |
257 | + form: MarkdownForm, | |
258 | + script: ScriptBlock as any, | |
259 | + details: ThinkBlock, | |
260 | + }} | |
261 | + > | |
262 | + {latexContent} | |
263 | + </ReactMarkdown> | |
264 | + </div> | |
265 | + ); | |
266 | +}; | |
267 | + | |
268 | +export default Markdown; | ... | ... |
1 | +import React, { useEffect, useRef, useState } from 'react' | |
2 | +import '../../pages/deep-seek-index/base.less' | |
3 | +import '../../pages/deep-seek-index/style.less' | |
4 | + | |
5 | +const hasEndThink = (children: any): boolean => { | |
6 | + if (typeof children === 'string') | |
7 | + return children.includes('[ENDTHINKFLAG]') | |
8 | + | |
9 | + if (Array.isArray(children)) | |
10 | + return children.some(child => hasEndThink(child)) | |
11 | + | |
12 | + if (children?.props?.children) | |
13 | + return hasEndThink(children.props.children) | |
14 | + | |
15 | + return false | |
16 | +} | |
17 | + | |
18 | +const removeEndThink = (children: any): any => { | |
19 | + if (typeof children === 'string') | |
20 | + return children.replace('[ENDTHINKFLAG]', '') | |
21 | + | |
22 | + if (Array.isArray(children)) | |
23 | + return children.map(child => removeEndThink(child)) | |
24 | + | |
25 | + if (children?.props?.children) { | |
26 | + return React.cloneElement( | |
27 | + children, | |
28 | + { | |
29 | + ...children.props, | |
30 | + children: removeEndThink(children.props.children), | |
31 | + }, | |
32 | + ) | |
33 | + } | |
34 | + | |
35 | + return children | |
36 | +} | |
37 | + | |
38 | +const useThinkTimer = (children: any) => { | |
39 | + const [startTime] = useState(Date.now()) | |
40 | + const [elapsedTime, setElapsedTime] = useState(0) | |
41 | + const [isComplete, setIsComplete] = useState(false) | |
42 | + const timerRef = useRef<NodeJS.Timeout>() | |
43 | + | |
44 | + useEffect(() => { | |
45 | + timerRef.current = setInterval(() => { | |
46 | + if (!isComplete) | |
47 | + setElapsedTime(Math.floor((Date.now() - startTime) / 100) / 10) | |
48 | + }, 100) | |
49 | + | |
50 | + return () => { | |
51 | + if (timerRef.current) | |
52 | + clearInterval(timerRef.current) | |
53 | + } | |
54 | + }, [startTime, isComplete]) | |
55 | + | |
56 | + useEffect(() => { | |
57 | + if (hasEndThink(children)) { | |
58 | + setIsComplete(true) | |
59 | + | |
60 | + if (timerRef.current) | |
61 | + clearInterval(timerRef.current) | |
62 | + } | |
63 | + }, [children]) | |
64 | + | |
65 | + return { elapsedTime, isComplete } | |
66 | +} | |
67 | + | |
68 | +export const ThinkBlock = ({ children, ...props }: any) => { | |
69 | + const { elapsedTime, isComplete } = useThinkTimer(children) | |
70 | + const displayContent = removeEndThink(children) | |
71 | + | |
72 | + if (!(props['data-think'] ?? false)) | |
73 | + return (<details {...props}>{children}</details>) | |
74 | + | |
75 | + return ( | |
76 | + <details {...(!isComplete && { open: true })} className="group"> | |
77 | + <summary className="flex cursor-pointer select-none list-none items-center whitespace-nowrap pl-2 font-bold text-gray-500"> | |
78 | + <div className="flex shrink-0 items-center"> | |
79 | + <svg | |
80 | + className="mr-2 h-3 w-3 transition-transform duration-500 group-open:rotate-90" | |
81 | + fill="none" | |
82 | + stroke="currentColor" | |
83 | + viewBox="0 0 24 24" | |
84 | + > | |
85 | + <path | |
86 | + strokeLinecap="round" | |
87 | + strokeLinejoin="round" | |
88 | + strokeWidth={2} | |
89 | + d="M9 5l7 7-7 7" | |
90 | + /> | |
91 | + </svg> | |
92 | + {isComplete ? `思考(${elapsedTime.toFixed(1)}s)` : `深度思考中...(${elapsedTime.toFixed(1)}s)`} | |
93 | + </div> | |
94 | + </summary> | |
95 | + <div className="ml-2 border-l border-gray-300 bg-gray-50 p-3 text-gray-500"> | |
96 | + {displayContent} | |
97 | + </div> | |
98 | + </details> | |
99 | + ) | |
100 | +} | |
101 | + | |
102 | +export default ThinkBlock | ... | ... |
1 | +import { Button } from 'antd'; | |
2 | +import '../../pages/deep-seek-index/base.less' | |
3 | +import '../../pages/deep-seek-index/style.less' | |
4 | + | |
5 | +const MarkdownButton = ({ node }: any) => { | |
6 | + const variant = node.properties.dataVariant | |
7 | + const message = node.properties.dataMessage | |
8 | + const link = node.properties.dataLink | |
9 | + const size = node.properties.dataSize | |
10 | + | |
11 | + function is_valid_url(url: string): boolean { | |
12 | + try { | |
13 | + const parsed_url = new URL(url) | |
14 | + return ['http:', 'https:'].includes(parsed_url.protocol) | |
15 | + } | |
16 | + catch { | |
17 | + return false | |
18 | + } | |
19 | + } | |
20 | + | |
21 | + return <Button | |
22 | + variant={variant} | |
23 | + size={size} | |
24 | + onClick={() => { | |
25 | + if (is_valid_url(link)) { | |
26 | + window.open(link, '_blank') | |
27 | + return | |
28 | + } | |
29 | + console.log('message', message) | |
30 | + }} | |
31 | + > | |
32 | + <span className='text-[13px]'>{node.children[0]?.value || ''}</span> | |
33 | + </Button> | |
34 | +} | |
35 | +MarkdownButton.displayName = 'MarkdownButton' | |
36 | + | |
37 | +export default MarkdownButton | ... | ... |
1 | +import React, { useEffect, useState } from 'react' | |
2 | +import '../../pages/deep-seek-index/base.less' | |
3 | +import '../../pages/deep-seek-index/style.less' | |
4 | +import { | |
5 | + Button, | |
6 | + Input, | |
7 | + DatePicker, | |
8 | + TimePicker, | |
9 | + Checkbox, | |
10 | + Select | |
11 | +} from 'antd'; | |
12 | + | |
13 | +enum DATA_FORMAT { | |
14 | + TEXT = 'text', | |
15 | + JSON = 'json', | |
16 | +} | |
17 | +enum SUPPORTED_TAGS { | |
18 | + LABEL = 'label', | |
19 | + INPUT = 'input', | |
20 | + TEXTAREA = 'textarea', | |
21 | + BUTTON = 'button', | |
22 | +} | |
23 | +enum SUPPORTED_TYPES { | |
24 | + TEXT = 'text', | |
25 | + PASSWORD = 'password', | |
26 | + EMAIL = 'email', | |
27 | + NUMBER = 'number', | |
28 | + DATE = 'date', | |
29 | + TIME = 'time', | |
30 | + DATETIME = 'datetime', | |
31 | + CHECKBOX = 'checkbox', | |
32 | + SELECT = 'select', | |
33 | +} | |
34 | +const MarkdownForm = ({ node }: any) => { | |
35 | + | |
36 | + const [formValues, setFormValues] = useState<{ [key: string]: any }>({}) | |
37 | + | |
38 | + useEffect(() => { | |
39 | + const initialValues: { [key: string]: any } = {} | |
40 | + node.children.forEach((child: any) => { | |
41 | + if ([SUPPORTED_TAGS.INPUT, SUPPORTED_TAGS.TEXTAREA].includes(child.tagName)) | |
42 | + initialValues[child.properties.name] = child.properties.value | |
43 | + }) | |
44 | + setFormValues(initialValues) | |
45 | + }, [node.children]) | |
46 | + | |
47 | + const getFormValues = (children: any) => { | |
48 | + const values: { [key: string]: any } = {} | |
49 | + children.forEach((child: any) => { | |
50 | + if ([SUPPORTED_TAGS.INPUT, SUPPORTED_TAGS.TEXTAREA].includes(child.tagName)) | |
51 | + values[child.properties.name] = formValues[child.properties.name] | |
52 | + }) | |
53 | + return values | |
54 | + } | |
55 | + | |
56 | + const onSubmit = (e: any) => { | |
57 | + e.preventDefault() | |
58 | + const format = node.properties.dataFormat || DATA_FORMAT.TEXT | |
59 | + const result = getFormValues(node.children) | |
60 | + | |
61 | + if (format === DATA_FORMAT.JSON) { | |
62 | + console.log('result', JSON.stringify(result)) | |
63 | + } | |
64 | + else { | |
65 | + const textResult = Object.entries(result) | |
66 | + .map(([key, value]) => `${key}: ${value}`) | |
67 | + .join('\n') | |
68 | + console.log('textResult', textResult) | |
69 | + } | |
70 | + } | |
71 | + return ( | |
72 | + <form | |
73 | + autoComplete="off" | |
74 | + className='flex flex-col self-stretch' | |
75 | + onSubmit={(e: any) => { | |
76 | + e.preventDefault() | |
77 | + e.stopPropagation() | |
78 | + }} | |
79 | + > | |
80 | + {node.children.filter((i: any) => i.type === 'element').map((child: any, index: number) => { | |
81 | + if (child.tagName === SUPPORTED_TAGS.LABEL) { | |
82 | + return ( | |
83 | + <label | |
84 | + key={index} | |
85 | + htmlFor={child.properties.for} | |
86 | + className="system-md-semibold my-2 text-text-secondary" | |
87 | + > | |
88 | + {child.children[0]?.value || ''} | |
89 | + </label> | |
90 | + ) | |
91 | + } | |
92 | + if (child.tagName === SUPPORTED_TAGS.INPUT && Object.values(SUPPORTED_TYPES).includes(child.properties.type)) { | |
93 | + if (child.properties.type === SUPPORTED_TYPES.DATE || child.properties.type === SUPPORTED_TYPES.DATETIME) { | |
94 | + return ( | |
95 | + <DatePicker | |
96 | + key={index} | |
97 | + value={formValues[child.properties.name]} | |
98 | + needTimePicker={child.properties.type === SUPPORTED_TYPES.DATETIME} | |
99 | + onChange={(date) => { | |
100 | + setFormValues(prevValues => ({ | |
101 | + ...prevValues, | |
102 | + [child.properties.name]: date, | |
103 | + })) | |
104 | + }} | |
105 | + onClear={() => { | |
106 | + setFormValues(prevValues => ({ | |
107 | + ...prevValues, | |
108 | + [child.properties.name]: undefined, | |
109 | + })) | |
110 | + }} | |
111 | + /> | |
112 | + ) | |
113 | + } | |
114 | + if (child.properties.type === SUPPORTED_TYPES.TIME) { | |
115 | + return ( | |
116 | + <TimePicker | |
117 | + key={index} | |
118 | + value={formValues[child.properties.name]} | |
119 | + onChange={(time) => { | |
120 | + setFormValues(prevValues => ({ | |
121 | + ...prevValues, | |
122 | + [child.properties.name]: time, | |
123 | + })) | |
124 | + }} | |
125 | + onClear={() => { | |
126 | + setFormValues(prevValues => ({ | |
127 | + ...prevValues, | |
128 | + [child.properties.name]: undefined, | |
129 | + })) | |
130 | + }} | |
131 | + /> | |
132 | + ) | |
133 | + } | |
134 | + if (child.properties.type === SUPPORTED_TYPES.CHECKBOX) { | |
135 | + return ( | |
136 | + <div className='mt-2 flex h-6 items-center space-x-2' key={index}> | |
137 | + <Checkbox | |
138 | + key={index} | |
139 | + checked={formValues[child.properties.name]} | |
140 | + onCheck={() => { | |
141 | + setFormValues(prevValues => ({ | |
142 | + ...prevValues, | |
143 | + [child.properties.name]: !prevValues[child.properties.name], | |
144 | + })) | |
145 | + }} | |
146 | + /> | |
147 | + <span>{child.properties.dataTip || child.properties['data-tip'] || ''}</span> | |
148 | + </div> | |
149 | + ) | |
150 | + } | |
151 | + if (child.properties.type === SUPPORTED_TYPES.SELECT) { | |
152 | + return ( | |
153 | + <Select | |
154 | + key={index} | |
155 | + allowSearch={false} | |
156 | + className="w-full" | |
157 | + items={(() => { | |
158 | + let options = child.properties.dataOptions || child.properties['data-options'] || [] | |
159 | + if (typeof options === 'string') { | |
160 | + try { | |
161 | + options = JSON.parse(options) | |
162 | + } | |
163 | + catch (e) { | |
164 | + console.error('Failed to parse options:', e) | |
165 | + options = [] | |
166 | + } | |
167 | + } | |
168 | + return options.map((option: string) => ({ | |
169 | + name: option, | |
170 | + value: option, | |
171 | + })) | |
172 | + })()} | |
173 | + defaultValue={formValues[child.properties.name]} | |
174 | + onSelect={(item) => { | |
175 | + setFormValues(prevValues => ({ | |
176 | + ...prevValues, | |
177 | + [child.properties.name]: item.value, | |
178 | + })) | |
179 | + }} | |
180 | + /> | |
181 | + ) | |
182 | + } | |
183 | + | |
184 | + return ( | |
185 | + <Input | |
186 | + key={index} | |
187 | + type={child.properties.type} | |
188 | + name={child.properties.name} | |
189 | + placeholder={child.properties.placeholder} | |
190 | + value={formValues[child.properties.name]} | |
191 | + onChange={(e) => { | |
192 | + setFormValues(prevValues => ({ | |
193 | + ...prevValues, | |
194 | + [child.properties.name]: e.target.value, | |
195 | + })) | |
196 | + }} | |
197 | + /> | |
198 | + ) | |
199 | + } | |
200 | + if (child.tagName === SUPPORTED_TAGS.TEXTAREA) { | |
201 | + return ( | |
202 | + <Input.TextArea | |
203 | + key={index} | |
204 | + name={child.properties.name} | |
205 | + placeholder={child.properties.placeholder} | |
206 | + value={formValues[child.properties.name]} | |
207 | + onChange={(e) => { | |
208 | + setFormValues(prevValues => ({ | |
209 | + ...prevValues, | |
210 | + [child.properties.name]: e.target.value, | |
211 | + })) | |
212 | + }} | |
213 | + /> | |
214 | + ) | |
215 | + } | |
216 | + if (child.tagName === SUPPORTED_TAGS.BUTTON) { | |
217 | + const variant = child.properties.dataVariant | |
218 | + const size = child.properties.dataSize | |
219 | + | |
220 | + return ( | |
221 | + <Button | |
222 | + variant={variant} | |
223 | + size={size} | |
224 | + className='mt-4' | |
225 | + key={index} | |
226 | + onClick={onSubmit} | |
227 | + > | |
228 | + <span className='text-[13px]'>{child.children[0]?.value || ''}</span> | |
229 | + </Button> | |
230 | + ) | |
231 | + } | |
232 | + | |
233 | + return <p key={index}>Unsupported tag: {child.tagName}</p> | |
234 | + })} | |
235 | + </form> | |
236 | + ) | |
237 | +} | |
238 | +MarkdownForm.displayName = 'MarkdownForm' | |
239 | +export default MarkdownForm | ... | ... |
src/index.less
0 → 100644
1 | +:root { | |
2 | + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; | |
3 | + line-height: 1.5; | |
4 | + font-weight: 400; | |
5 | + | |
6 | + color-scheme: light dark; | |
7 | + color: rgba(255, 255, 255, 0.87); | |
8 | + background-color: #242424; | |
9 | + | |
10 | + font-synthesis: none; | |
11 | + text-rendering: optimizeLegibility; | |
12 | + -webkit-font-smoothing: antialiased; | |
13 | + -moz-osx-font-smoothing: grayscale; | |
14 | +} | |
15 | + | |
16 | +a { | |
17 | + font-weight: 500; | |
18 | + color: #646cff; | |
19 | + text-decoration: inherit; | |
20 | +} | |
21 | +a:hover { | |
22 | + color: #535bf2; | |
23 | +} | |
24 | + | |
25 | +body { | |
26 | + margin: 0; | |
27 | + min-height: 100vh; | |
28 | +} | |
29 | + | |
30 | +h1 { | |
31 | + font-size: 3.2em; | |
32 | + line-height: 1.1; | |
33 | +} | |
34 | + | |
35 | +p { | |
36 | + margin: 0; | |
37 | +} | |
38 | + | |
39 | +button { | |
40 | + border-radius: 8px; | |
41 | + border: 1px solid transparent; | |
42 | + padding: 0.6em 1.2em; | |
43 | + font-size: 1em; | |
44 | + font-weight: 500; | |
45 | + font-family: inherit; | |
46 | + background-color: #1a1a1a; | |
47 | + cursor: pointer; | |
48 | + transition: border-color 0.25s; | |
49 | +} | |
50 | +button:hover { | |
51 | + border-color: #646cff; | |
52 | +} | |
53 | +button:focus, | |
54 | +button:focus-visible { | |
55 | + outline: 4px auto -webkit-focus-ring-color; | |
56 | +} | |
57 | + | |
58 | +@media (prefers-color-scheme: light) { | |
59 | + :root { | |
60 | + color: #213547; | |
61 | + background-color: #ffffff; | |
62 | + } | |
63 | + a:hover { | |
64 | + color: #747bff; | |
65 | + } | |
66 | + button { | |
67 | + background-color: #f9f9f9; | |
68 | + } | |
69 | +} | |
70 | + | |
71 | +#root { | |
72 | + width: 100vw; | |
73 | + height: 100vh; | |
74 | + background: #fff; | |
75 | + overflow: hidden; | |
76 | +} | |
77 | + | |
78 | +.custom-tooltip { | |
79 | + opacity: 1 !important; | |
80 | + .rc-tooltip-arrow { | |
81 | + border-top-color: #fff !important; | |
82 | + bottom: 4px !important; | |
83 | + } | |
84 | + .rc-tooltip-content { | |
85 | + .rc-tooltip-inner { | |
86 | + background-color: #fff; | |
87 | + min-height: 0; | |
88 | + padding: 4px 6px; | |
89 | + line-height: 16px; | |
90 | + color: #666; | |
91 | + } | |
92 | + } | |
93 | +} | |
94 | +// | |
95 | +///* Webkit 浏览器(Chrome, Safari, Edge) */ | |
96 | +//::-webkit-scrollbar { | |
97 | +// width: 5px; /* 或者 height: 12px; 对于水平滚动条 */ | |
98 | +//} | |
99 | +// | |
100 | +//::-webkit-scrollbar-thumb { | |
101 | +// background-color: #fafafa; /* 滚动条的颜色 */ | |
102 | +// border-radius: 6px; /* 滚动条的圆角 */ | |
103 | +//} | |
104 | +// | |
105 | +// | |
106 | +//::-webkit-scrollbar-track { | |
107 | +// background-color: #fafafa; /* 滚动条轨道的颜色 */ | |
108 | +// border-radius: 6px; /* 轨道的圆角 */ | |
109 | +//} | |
110 | + | |
111 | + | |
112 | +// 一行省略 | |
113 | +.omit1 { | |
114 | + word-break: break-all; | |
115 | + text-overflow: ellipsis; | |
116 | + display: -webkit-box; | |
117 | + -webkit-box-orient: vertical; | |
118 | + -webkit-line-clamp: 1; | |
119 | + overflow: hidden; | |
120 | + overflow-wrap:break-word; | |
121 | + word-break: break-all; | |
122 | +} | |
123 | + | |
124 | +// 两行省略 | |
125 | +.omit2 { | |
126 | + word-break: break-all; | |
127 | + text-overflow: ellipsis; | |
128 | + display: -webkit-box; | |
129 | + -webkit-box-orient: vertical; | |
130 | + -webkit-line-clamp: 2; | |
131 | + overflow: hidden; | |
132 | + overflow-wrap:break-word; | |
133 | + word-break: break-all; | |
134 | +} | |
135 | + | |
136 | +// 三行省略 | |
137 | +.omit3 { | |
138 | + overflow: hidden; | |
139 | + text-overflow: ellipsis; | |
140 | + display: -webkit-box; | |
141 | + -webkit-line-clamp: 3; | |
142 | + -webkit-box-orient: vertical; | |
143 | + overflow-wrap:break-word; | |
144 | + word-break: break-all; | |
145 | +} | |
146 | +//四行省略 | |
147 | +.omit4 { | |
148 | + overflow: hidden; | |
149 | + text-overflow: ellipsis; | |
150 | + display: -webkit-box; | |
151 | + -webkit-line-clamp: 4; | |
152 | + -webkit-box-orient: vertical; | |
153 | + overflow-wrap:break-word; | |
154 | + word-break: break-all; | |
155 | +} | |
156 | + | |
157 | +//四行省略 | |
158 | +.omit5 { | |
159 | + overflow: hidden; | |
160 | + text-overflow: ellipsis; | |
161 | + display: -webkit-box; | |
162 | + -webkit-line-clamp: 5; | |
163 | + -webkit-box-orient: vertical; | |
164 | + overflow-wrap:break-word; | |
165 | + word-break: break-all; | |
166 | +} | |
167 | + | |
168 | +// 六行省略 | |
169 | +.omit6 { | |
170 | + overflow: hidden; | |
171 | + text-overflow: ellipsis; | |
172 | + display: -webkit-box; | |
173 | + -webkit-line-clamp: 6; | |
174 | + -webkit-box-orient: vertical; | |
175 | + overflow-wrap:break-word; | |
176 | + word-break: break-all; | |
177 | +} | ... | ... |
src/main.tsx
0 → 100644
1 | +import ReactDOM from 'react-dom/client' | |
2 | +import "normalize.css" | |
3 | +import 'rc-tooltip/assets/bootstrap.css'; | |
4 | +import './index.less' | |
5 | +import router from './router' | |
6 | + | |
7 | +import { | |
8 | + RouterProvider, | |
9 | +} from "react-router-dom"; | |
10 | + | |
11 | +ReactDOM.createRoot(document.getElementById('root')!).render( | |
12 | + <RouterProvider router={router} /> | |
13 | +) | ... | ... |