├── .editorconfig
├── .env
├── .env.development
├── .env.site
├── .eslintignore
├── .eslintrc.js
├── .github
├── ISSUE_TEMPLATE
│ ├── bug-report.zh-CN.yml
│ ├── config.yml
│ └── feature-report.zh-CN.yml
├── PULL_REQUEST_TEMPLATE.md
├── configs
│ └── typos_config.toml
├── dependabot.yml
└── workflows
│ ├── issue-assignees.temp.yml
│ ├── issue-help-wanted.temp.yml
│ ├── issue-mark-duplicate.temp.yml
│ ├── issue-reply.temp.yml
│ ├── issue-synchronize.temp.yml
│ ├── pr-spelling.temp.yml
│ ├── preview-publish.yml
│ └── pull-request.yml
├── .gitignore
├── .husky
└── pre-commit
├── .prettierrc.js
├── CHANGELOG.md
├── LICENSE
├── README-zh_CN.md
├── README.md
├── docs
└── docs-starter.png
├── index.html
├── mock
├── api
│ └── getList.js
└── index.js
├── package-lock.json
├── package.json
├── public
├── favicon.ico
└── robots.txt
├── src
├── assets
│ ├── image
│ │ ├── assets-login-bg-black.png
│ │ └── assets-login-bg-white.png
│ └── svg
│ │ ├── assets-logo-full.svg
│ │ ├── assets-product-1.svg
│ │ ├── assets-product-2.svg
│ │ ├── assets-product-3.svg
│ │ ├── assets-product-4.svg
│ │ ├── assets-result-403.svg
│ │ ├── assets-result-404.svg
│ │ ├── assets-result-500.svg
│ │ ├── assets-result-browser-incompatible.svg
│ │ ├── assets-result-maintenance.svg
│ │ ├── assets-result-network-error.svg
│ │ ├── assets-setting-auto.svg
│ │ ├── assets-setting-dark.svg
│ │ ├── assets-setting-light.svg
│ │ └── assets-t-logo.svg
├── components
│ ├── Board
│ │ ├── index.module.less
│ │ └── index.tsx
│ ├── DatePicker
│ │ └── index.tsx
│ └── ErrorPage
│ │ ├── index.module.less
│ │ └── index.tsx
├── configs
│ ├── color.ts
│ └── host.ts
├── global.d.ts
├── hooks
│ └── useDynamicChart.ts
├── layouts
│ ├── components
│ │ ├── AppLayout.module.less
│ │ ├── AppLayout.tsx
│ │ ├── AppRouter.module.less
│ │ ├── AppRouter.tsx
│ │ ├── Footer.tsx
│ │ ├── Header
│ │ │ ├── HeaderIcon.module.less
│ │ │ ├── HeaderIcon.tsx
│ │ │ ├── Search.module.less
│ │ │ ├── Search.tsx
│ │ │ ├── index.module.less
│ │ │ └── index.tsx
│ │ ├── Menu.module.less
│ │ ├── Menu.tsx
│ │ ├── MenuLogo.tsx
│ │ ├── Page.module.less
│ │ ├── Page.tsx
│ │ └── Setting
│ │ │ ├── RadioColor.module.less
│ │ │ ├── RadioColor.tsx
│ │ │ ├── RadioRect.module.less
│ │ │ ├── RadioRect.tsx
│ │ │ ├── index.module.less
│ │ │ └── index.tsx
│ ├── index.module.less
│ └── index.tsx
├── main.tsx
├── modules
│ ├── global
│ │ └── index.ts
│ ├── list
│ │ ├── base.ts
│ │ ├── card.ts
│ │ └── select.ts
│ ├── store.ts
│ └── user
│ │ └── index.ts
├── pages
│ ├── Dashboard
│ │ ├── Base
│ │ │ ├── chart.ts
│ │ │ ├── components
│ │ │ │ ├── MiddleChart.module.less
│ │ │ │ ├── MiddleChart.tsx
│ │ │ │ ├── Overview.module.less
│ │ │ │ ├── Overview.tsx
│ │ │ │ ├── RankList.module.less
│ │ │ │ ├── RankList.tsx
│ │ │ │ ├── TopPanel.module.less
│ │ │ │ └── TopPanel.tsx
│ │ │ ├── constant.ts
│ │ │ └── index.tsx
│ │ └── Detail
│ │ │ ├── chart.ts
│ │ │ ├── components
│ │ │ ├── MonthPurchase.tsx
│ │ │ ├── PurchaseThrend.module.less
│ │ │ ├── PurchaseTrend.tsx
│ │ │ ├── Satisfaction.module.less
│ │ │ └── Satisfaction.tsx
│ │ │ ├── constant.ts
│ │ │ └── index.tsx
│ ├── Detail
│ │ ├── Advanced
│ │ │ ├── components
│ │ │ │ ├── Base.module.less
│ │ │ │ ├── Base.tsx
│ │ │ │ ├── Detail.tsx
│ │ │ │ ├── Product.tsx
│ │ │ │ ├── ProductCard.module.less
│ │ │ │ ├── ProductCard.tsx
│ │ │ │ └── Progress.tsx
│ │ │ ├── consts.ts
│ │ │ ├── index.module.less
│ │ │ └── index.tsx
│ │ ├── Base
│ │ │ ├── consts.ts
│ │ │ ├── index.module.less
│ │ │ └── index.tsx
│ │ ├── Deploy
│ │ │ ├── chart.ts
│ │ │ ├── components
│ │ │ │ ├── BottomTable.module.less
│ │ │ │ ├── BottomTable.tsx
│ │ │ │ ├── DynamicLineChart.tsx
│ │ │ │ ├── ManagementPopup.module.less
│ │ │ │ ├── ManagementPopup.tsx
│ │ │ │ ├── TopChart.module.less
│ │ │ │ └── TopChart.tsx
│ │ │ ├── constant.ts
│ │ │ └── index.tsx
│ │ └── Secondary
│ │ │ ├── consts.ts
│ │ │ ├── index.module.less
│ │ │ └── index.tsx
│ ├── Form
│ │ ├── Base
│ │ │ ├── index.module.less
│ │ │ └── index.tsx
│ │ └── Step
│ │ │ ├── components
│ │ │ ├── StepFour.tsx
│ │ │ ├── StepOne.tsx
│ │ │ ├── StepThree.tsx
│ │ │ ├── StepTwo.tsx
│ │ │ ├── index.module.less
│ │ │ └── index.ts
│ │ │ ├── index.module.less
│ │ │ └── index.tsx
│ ├── List
│ │ ├── Base
│ │ │ ├── index.module.less
│ │ │ └── index.tsx
│ │ ├── Card
│ │ │ ├── components
│ │ │ │ ├── ProductCard.module.less
│ │ │ │ └── ProductCard.tsx
│ │ │ ├── index.module.less
│ │ │ └── index.tsx
│ │ ├── Select
│ │ │ ├── components
│ │ │ │ └── SearchForm.tsx
│ │ │ ├── consts.ts
│ │ │ ├── index.module.less
│ │ │ └── index.tsx
│ │ └── Tree
│ │ │ ├── consts.ts
│ │ │ ├── index.module.less
│ │ │ └── index.tsx
│ ├── Login
│ │ ├── components
│ │ │ ├── Header
│ │ │ │ ├── index.module.less
│ │ │ │ └── index.tsx
│ │ │ ├── Login
│ │ │ │ ├── index.module.less
│ │ │ │ └── index.tsx
│ │ │ └── Register
│ │ │ │ ├── index.module.less
│ │ │ │ └── index.tsx
│ │ ├── hooks
│ │ │ └── useCountDown.ts
│ │ ├── index.module.less
│ │ └── index.tsx
│ ├── Result
│ │ ├── 403
│ │ │ └── index.tsx
│ │ ├── 404
│ │ │ └── index.tsx
│ │ ├── 500
│ │ │ └── index.tsx
│ │ ├── BrowserIncompatible
│ │ │ ├── index.module.less
│ │ │ └── index.tsx
│ │ ├── Fail
│ │ │ ├── index.module.less
│ │ │ └── index.tsx
│ │ ├── Maintenance
│ │ │ ├── index.module.less
│ │ │ └── index.tsx
│ │ ├── NetworkError
│ │ │ └── index.tsx
│ │ ├── Success
│ │ │ ├── index.module.less
│ │ │ └── index.tsx
│ │ └── index.module.less
│ └── User
│ │ ├── chart.ts
│ │ ├── consts.ts
│ │ ├── index.module.less
│ │ └── index.tsx
├── router
│ ├── index.ts
│ └── modules
│ │ ├── dashboard.ts
│ │ ├── detail.ts
│ │ ├── form.ts
│ │ ├── list.ts
│ │ ├── login.ts
│ │ ├── others.ts
│ │ ├── result.ts
│ │ └── user.ts
├── services
│ ├── contract.ts
│ └── product.ts
├── styles
│ ├── common.module.less
│ └── index.less
├── types
│ └── index.d.ts
└── utils
│ ├── chart.ts
│ ├── color.ts
│ ├── path.ts
│ └── request.ts
├── tsconfig.json
└── vite.config.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # indicate this is the root of the project
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | # 打包路径 根据项目不同按需配置
2 | VITE_BASE_URL = /
3 |
4 | NODE_ENV=production
5 |
--------------------------------------------------------------------------------
/.env.development:
--------------------------------------------------------------------------------
1 | # 打包路径
2 | VITE_BASE_URL= /
3 |
4 | NODE_ENV=development
5 |
--------------------------------------------------------------------------------
/.env.site:
--------------------------------------------------------------------------------
1 | # 打包路径 根据项目不同按需配置
2 | VITE_BASE_URL=https://static.tdesign.tencent.com/starter/react/
3 |
4 | NODE_ENV=production
5 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build
2 | mock
3 | config
4 | public
5 | scripts
6 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: [
4 | 'airbnb-base',
5 | 'prettier',
6 | 'plugin:react/recommended',
7 | 'plugin:import/typescript',
8 | 'plugin:@typescript-eslint/recommended',
9 | ],
10 | plugins: ['eslint-plugin-prettier'],
11 | parser: '@typescript-eslint/parser',
12 | env: {
13 | jest: true,
14 | },
15 | settings: {
16 | react: {
17 | version: 'detect',
18 | },
19 | },
20 | rules: {
21 | 'no-shadow': 'off',
22 | 'no-param-reassign': ['error', { props: false }],
23 | 'no-console': 'off',
24 | 'no-plusplus': [
25 | 'error',
26 | {
27 | allowForLoopAfterthoughts: true,
28 | },
29 | ],
30 | 'react/display-name': 'off',
31 | // jsx 单引号
32 | 'jsx-quotes': [2, 'prefer-single'],
33 | 'import/no-cycle': 'off', // TODO: remove
34 | 'import/extensions': 'off',
35 | 'import/no-unresolved': 'off',
36 | 'import/order': 'off',
37 | 'import/prefer-default-export': 'off',
38 | 'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
39 | // 关闭variable必须全部大写规则
40 | '@typescript-eslint/naming-convention': [
41 | 'error',
42 | {
43 | selector: 'variable',
44 | modifiers: ['const'],
45 | format: null,
46 | },
47 | ],
48 | '@typescript-eslint/no-empty-function': 'off',
49 | '@typescript-eslint/no-explicit-any': 'off',
50 | '@typescript-eslint/ban-types': 'off',
51 | // 统一eslint prettier配置
52 | 'prettier/prettier': [
53 | 'warn',
54 | {},
55 | {
56 | usePrettierrc: true,
57 | },
58 | ],
59 | },
60 | };
61 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.zh-CN.yml:
--------------------------------------------------------------------------------
1 | name: 反馈 Bug
2 | description: 通过 github 模板进行 Bug 反馈。
3 | title: "[组件名称] 描述问题的标题"
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: |
8 | # 欢迎你的参与
9 | tdesign-react-starter 的 Issue 列表接受 bug 报告或是新功能请求。也可加入官方社区:
10 |
11 | 在发布一个 Issue 前,请确保:
12 | - 在 [常见问题](https://tdesign.tencent.com/about/faq)、[更新日志](https://tdesign.tencent.com/about/release) 和 [旧Issue列表](https://github.com/Tencent/tdesign-react-starter/issues?q=is%3Aissue) 中搜索过你的问题。(你的问题可能已有人提出,也可能已在最新版本中被修正)
13 | - 如果你发现一个已经关闭的旧 Issue 在最新版本中仍然存在,不要在旧 Issue 下面留言,请建一个新的 issue。
14 |
15 | - type: input
16 | id: version
17 | attributes:
18 | label: tdesign-react-starter 版本
19 | description: 请检查在最新项目版本中能否重现此 issue。
20 | placeholder: 请填写
21 | validations:
22 | required: true
23 |
24 | - type: input
25 | id: reproduce
26 | attributes:
27 | label: 重现链接
28 | description: 请提供尽可能精简的 CodePen、CodeSandbox 或 GitHub 仓库的链接。请不要填无关链接,否则你的 Issue 将被关闭。
29 | placeholder: 请填写
30 |
31 | - type: textarea
32 | id: reproduceSteps
33 | attributes:
34 | label: 重现步骤
35 | description: 请清晰的描述重现该 Issue 的步骤,这能帮助我们快速定位问题。没有清晰重现步骤将不会被修复,标有 'need reproduction' 的 Issue 在 7 天内不提供相关步骤,将被关闭。
36 | placeholder: 请填写
37 |
38 | - type: textarea
39 | id: expect
40 | attributes:
41 | label: 期望结果
42 | placeholder: 请填写
43 |
44 | - type: textarea
45 | id: actual
46 | attributes:
47 | label: 实际结果
48 | placeholder: 请填写
49 |
50 | - type: input
51 | id: frameworkVersion
52 | attributes:
53 | label: 框架版本
54 | placeholder: Vue(3.2.0)
55 |
56 | - type: input
57 | id: browsersVersion
58 | attributes:
59 | label: 浏览器版本
60 | placeholder: Chrome(8.213.231.123)
61 |
62 | - type: input
63 | id: systemVersion
64 | attributes:
65 | label: 系统版本
66 | placeholder: MacOS(11.2.3)
67 |
68 | - type: input
69 | id: nodeVersion
70 | attributes:
71 | label: Node版本
72 | placeholder: 请填写
73 |
74 | - type: textarea
75 | id: remarks
76 | attributes:
77 | label: 补充说明
78 | description: 可以是遇到这个 bug 的业务场景、上下文等信息。
79 | placeholder: 请填写
80 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: true
2 | contact_links:
3 | - name: 使用 issue-helper 新建
4 | url: https://Tencent.github.io/tdesign/issue-helper/?lang=zh-CN&repo=Tencent/tdesign
5 | about: 使用 https://Tencent.github.io/tdesign/issue-helper/ 创建 issue,其中包含 bug 和 feature,表单提交更加严格。
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-report.zh-CN.yml:
--------------------------------------------------------------------------------
1 | name: 反馈新功能
2 | description: 通过 github 模板进行新功能反馈。
3 | title: "[组件名称] 描述问题的标题"
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: |
8 | # 欢迎你的参与
9 | tdesign-react-starter 的 Issue 列表接受 bug 报告或是新功能请求。也可加入官方社区:
10 |
11 | 在发布一个 Issue 前,请确保:
12 | - 在 [常见问题](https://tdesign.tencent.com/about/faq)、[更新日志](https://tdesign.tencent.com/about/release) 和 [旧Issue列表](https://github.com/Tencent/tdesign-react-starter/issues?q=is%3Aissue) 中搜索过你的问题。(你的问题可能已有人提出,也可能已在最新版本中被修正)
13 | - 如果你发现一个已经关闭的旧 Issue 在最新版本中仍然存在,不要在旧 Issue 下面留言,请建一个新的 issue。
14 |
15 | - type: textarea
16 | id: functionContent
17 | attributes:
18 | label: 这个功能解决了什么问题
19 | description: 请详尽说明这个需求的用例和场景。最重要的是:解释清楚是怎样的用户体验需求催生了这个功能上的需求。我们将考虑添加在现有 API 无法轻松实现的功能。新功能的用例也应当足够常见。
20 | placeholder: 请填写
21 | validations:
22 | required: true
23 |
24 | - type: textarea
25 | id: functionalExpectations
26 | attributes:
27 | label: 你建议的方案是什么
28 | placeholder: 请填写
29 | validations:
30 | required: true
31 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
6 |
7 | ### 🤔 这个 PR 的性质是?
8 |
9 | - [ ] 日常 bug 修复
10 | - [ ] 新特性提交
11 | - [ ] 文档改进
12 | - [ ] 演示代码改进
13 | - [ ] 组件样式/交互改进
14 | - [ ] CI/CD 改进
15 | - [ ] 重构
16 | - [ ] 代码风格优化
17 | - [ ] 测试用例
18 | - [ ] 分支合并
19 | - [ ] 其他
20 |
21 | ### 🔗 相关 Issue
22 |
23 |
26 |
27 | ### 💡 需求背景和解决方案
28 |
29 |
34 |
35 | ### 📝 更新日志
36 |
37 |
40 |
41 | - fix(组件名称): 处理问题或特性描述 ...
42 |
43 | - [ ] 本条 PR 不需要纳入 Changelog
44 |
45 | ### ☑️ 请求合并前的自查清单
46 |
47 | ⚠️ 请自检并全部**勾选全部选项**。⚠️
48 |
49 | - [ ] 文档已补充或无须补充
50 | - [ ] 代码演示已提供或无须提供
51 | - [ ] TypeScript 定义已补充或无须补充
52 | - [ ] Changelog 已提供或无须提供
53 |
--------------------------------------------------------------------------------
/.github/configs/typos_config.toml:
--------------------------------------------------------------------------------
1 | files.extend-exclude = [
2 | "package-lock.json",
3 | "src/configs/color.ts"
4 | ]
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # Basic dependabot.yml file with
2 | # minimum configuration for two package managers
3 |
4 | version: 2
5 | updates:
6 | # Enable version updates for npm
7 | - package-ecosystem: "npm"
8 | # Look for `package.json` and `lock` files in the `root` directory
9 | directory: "/"
10 | # Check the npm registry for updates every day (weekdays)
11 | schedule:
12 | interval: "monthly"
13 |
14 | # Enable version updates for Docker
15 | - package-ecosystem: "docker"
16 | # Look for a `Dockerfile` in the `root` directory
17 | directory: "/"
18 | # Check for updates once a week
19 | schedule:
20 | interval: "monthly"
21 |
--------------------------------------------------------------------------------
/.github/workflows/issue-assignees.temp.yml:
--------------------------------------------------------------------------------
1 | # force copy from tencent/tdesign
2 | name: Issue Add Assigness
3 |
4 | on:
5 | issues:
6 | types: [opened, edited]
7 |
8 | jobs:
9 | mark-duplicate:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: wow-actions/auto-comment@v1
13 | with:
14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
15 | issuesOpened: |
16 | 👋 @{{ author }},感谢给 TDesign 提出了 issue。
17 | 请根据 issue 模版确保背景信息的完善,我们将调查并尽快回复你。
18 |
19 | # https://docs.github.com/cn/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#issues
20 | - uses: 94dreamer/issue-assignees@main
21 | id: assignees
22 | with:
23 | project_name: ${{github.event.repository.name}}
24 | issue_title: ${{github.event.issue.title}}
25 |
26 | - run: echo ${{ steps.assignees.outputs.contributors }}
27 | - name: Add assigness
28 | if: steps.assignees.outputs.contributors != ''
29 | uses: actions-cool/issues-helper@v3
30 | with:
31 | actions: 'add-assignees'
32 | token: ${{ secrets.GITHUB_TOKEN }}
33 | issue-number: ${{ github.event.issue.number }}
34 | assignees: ${{ steps.assignees.outputs.contributors }}
35 |
36 | - run: |
37 | contributors=${{ steps.assignees.outputs.contributors }}
38 | contributorstring=${contributors//,/ @}
39 | echo "::set-output name=string::@$contributorstring"
40 | id: contributors
41 |
42 | - name: 通知贡献者
43 | if: steps.assignees.outputs.contributors != ''
44 | uses: actions-cool/maintain-one-comment@v2.0.0
45 | with:
46 | token: ${{ secrets.GITHUB_TOKEN }}
47 | body: |
48 | ♥️ 有劳 ${{ steps.contributors.outputs.string }} 尽快确认问题。
49 | 确认有效后将下一步计划和可能需要的时间回复给 @${{ github.event.issue.user.login }} 。
50 |
51 | number: ${{ github.event.issue.number }}
52 | body-include: ""
53 |
--------------------------------------------------------------------------------
/.github/workflows/issue-help-wanted.temp.yml:
--------------------------------------------------------------------------------
1 | # force copy from tencent/tdesign
2 | name: Issue Help wanted
3 | on:
4 | issues:
5 | types:
6 | - labeled
7 | jobs:
8 | add-comment:
9 | if: github.event.label.name == 'help wanted'
10 | runs-on: ubuntu-latest
11 | permissions:
12 | issues: write
13 | steps:
14 | - name: Add comment
15 | uses: peter-evans/create-or-update-comment@v1
16 | with:
17 | issue-number: ${{ github.event.issue.number }}
18 | body: |
19 | 任何人都可以处理此问题。
20 | **请务必在您的 `pull request` 中引用此问题。** :sparkles:
21 | 感谢你的贡献! :sparkles:
22 | reactions: heart
--------------------------------------------------------------------------------
/.github/workflows/issue-mark-duplicate.temp.yml:
--------------------------------------------------------------------------------
1 | # force copy from tencent/tdesign
2 | # 当在 issue 的 comment 回复类似 `Duplicate of #111` 这样的话,issue 将被自动打上 重复提交标签 并且 cloese
3 | name: Issue Mark Duplicate
4 |
5 | on:
6 | issue_comment:
7 | types: [created, edited]
8 |
9 | jobs:
10 | mark-duplicate:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: mark-duplicate
14 | uses: actions-cool/issues-helper@v2
15 | with:
16 | actions: "mark-duplicate"
17 | token: ${{ secrets.GITHUB_TOKEN }}
18 | duplicate-labels: "duplicate"
19 | close-issue: true
20 |
--------------------------------------------------------------------------------
/.github/workflows/issue-reply.temp.yml:
--------------------------------------------------------------------------------
1 | # force copy from tencent/tdesign
2 | # 当被打上 Need Reproduce 标签时候,自动提示需要重现实例
3 |
4 | name: ISSUE_REPLY
5 |
6 | on:
7 | issues:
8 | types: [labeled]
9 |
10 | jobs:
11 | issue-reply:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Need Reproduce
15 | if: github.event.label.name == 'Need Reproduce'
16 | uses: actions-cool/issues-helper@v2
17 | with:
18 | actions: 'create-comment'
19 | issue-number: ${{ github.event.issue.number }}
20 | body: |
21 | 你好 @${{ github.event.issue.user.login }}, 我们需要你提供一个在线的重现实例以便于我们帮你排查问题。你可以通过点击 [此处](https://codesandbox.io/) 创建一个 codesandbox 或者提供一个最小化的 GitHub 仓库。请确保选择准确的版本。
22 |
--------------------------------------------------------------------------------
/.github/workflows/issue-synchronize.temp.yml:
--------------------------------------------------------------------------------
1 | # force copy from tencent/tdesign
2 | name: Issue Add Assigness
3 |
4 | on:
5 | issues:
6 | types: [opened, reopened]
7 |
8 | jobs:
9 | mark-duplicate:
10 | runs-on: ubuntu-latest
11 | steps:
12 | # https://docs.github.com/cn/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#issues
13 | - uses: 94dreamer/create-report@main
14 | with:
15 | wxhook: ${{ secrets.WX_HOOK_URL }}
16 | token: ${{ secrets.GITHUB_TOKEN }}
17 | type: 'issue'
--------------------------------------------------------------------------------
/.github/workflows/pr-spelling.temp.yml:
--------------------------------------------------------------------------------
1 | # force copy from tencent/tdesign
2 | name: pr-spell-check
3 | on: [pull_request]
4 |
5 | jobs:
6 | run:
7 | name: Spell Check with Typos
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v2
11 | - name: Check spelling
12 | uses: crate-ci/typos@master
13 | with:
14 | config: .github/configs/typos_config.toml
15 |
--------------------------------------------------------------------------------
/.github/workflows/preview-publish.yml:
--------------------------------------------------------------------------------
1 | # 文件名建议统一为 preview-publish
2 | # 应用 preview.yml 的 demo
3 | name: PREVIEW_PUBLISH
4 |
5 | on:
6 | workflow_run:
7 | workflows: ["MAIN_PULL_REQUEST"]
8 | types:
9 | - completed
10 |
11 | jobs:
12 | call-preview:
13 | uses: Tencent/tdesign/.github/workflows/preview.yml@main
14 | secrets:
15 | TDESIGN_SURGE_TOKEN: ${{ secrets.TDESIGN_SURGE_TOKEN }}
16 |
--------------------------------------------------------------------------------
/.github/workflows/pull-request.yml:
--------------------------------------------------------------------------------
1 | # 文件名建议统一为 pull-request.yml
2 | # 应用 test-build.yml 的 demo
3 | # 要求 package.json 提供 lint test site:preview
4 |
5 | name: MAIN_PULL_REQUEST
6 |
7 | on:
8 | pull_request:
9 | branches: [develop, main, site]
10 | types: [opened, synchronize, reopened]
11 |
12 | jobs:
13 | call-test-build:
14 | uses: Tencent/tdesign/.github/workflows/test-build.yml@main
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /coverage
6 |
7 | # production
8 | /dist
9 |
10 | # misc
11 | .DS_Store
12 | .idea
13 | .VSCodeCounter
14 |
15 | # log
16 | npm-debug.log*
17 | yarn-debug.log*
18 | yarn-error.log*
19 |
20 | # lock 文件 请根据自身项目或团队需求选择具体的包管理工具 并移除具体的ignore的lock文件
21 | yarn.lock
22 | pnpm-lock.yaml
23 |
24 | # code editor setting
25 | /.vscode
26 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | if [[ `uname` == 'Darwin' ]]; then
5 | PATH="/usr/local/bin:$PATH"
6 | fi
7 |
8 | npm run lint:fix
9 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 120,
3 | tabWidth: 2,
4 | useTabs: false,
5 | semi: true,
6 | singleQuote: true,
7 | quoteProps: 'as-needed',
8 | jsxSingleQuote: true,
9 | trailingComma: 'all',
10 | bracketSpacing: true,
11 | jsxBracketSameLine: false,
12 | arrowParens: 'always',
13 | rangeStart: 0,
14 | rangeEnd: null,
15 | requirePragma: false,
16 | insertPragma: false,
17 | proseWrap: 'preserve',
18 | htmlWhitespaceSensitivity: 'css',
19 | vueIndentScriptAndStyle: false,
20 | endOfLine: 'lf',
21 | embeddedLanguageFormatting: 'auto',
22 | };
23 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | # Changelog
3 |
4 | ## 0.3.1
5 | ### 🚀 Features
6 |
7 | - 移除所有内置主题色相关代码,全部通过 tvision-color 计算获取 by @uyarn #144
8 |
9 | - 卡片统一默认样式为无边框 by @uyarn #144
10 |
11 | **Full Changelog**: https://github.com/Tencent/tdesign-react-starter/compare/0.3.0...0.3.1
12 |
13 | ## 0.3.0
14 | ❗ Breaking Changes
15 |
16 | - 统一使用`.env`文件区分环境 生产环境改为`release` by @uyarn in https://github.com/Tencent/tdesign-react-starter/pull/136
17 |
18 | 🚀 Features
19 |
20 | - 默认路由模式改为`history`路由 by @uyarn in https://github.com/Tencent/tdesign-react-starter/pull/136
21 |
22 | **Full Changelog**: https://github.com/Tencent/tdesign-react-starter/compare/0.2.0...0.3.0
23 |
24 | ## 0.2.0
25 | ### ❗ Breaking Changes
26 |
27 | - 升级组件库依赖至 0.43+ 更新主题色配色方案 by @uyarn in https://github.com/Tencent/tdesign-react-starter/pull/129
28 |
29 | ### 🚀 Features
30 |
31 | - 新增自定义颜色面板选择 by @uyarn in https://github.com/Tencent/tdesign-react-starter/pull/129
32 |
33 | ### 🐞 Bug Fixes
34 |
35 | - 修复卡片面板的标题丢失的问题 by @uyarn https://github.com/Tencent/tdesign-react-starter/pull/129
36 |
37 | **Full Changelog**: https://github.com/Tencent/tdesign-react-starter/compare/0.1.6...0.2.0
38 |
39 | ## 0.1.6
40 | ### Features
41 |
42 | - 升级组件库依赖至0.42+ 版本
43 |
44 | ### Bug Fixes
45 |
46 | - 修复部分页面样式展示的缺陷
47 |
48 | - 优化顶部菜单布局
49 |
50 | ## 0.1.5
51 | ## Features
52 |
53 | - 新增卡片列表页 by @xucz in https://github.com/Tencent/tdesign-react-starter/pull/91
54 |
55 | - 菜单路由配置`hidden`和`single`功能 by @xucz in https://github.com/Tencent/tdesign-react-starter/pull/95
56 |
57 | ## Bug Fixes
58 |
59 | - 同步DatePicker组件升级的改动 by @uyarn in https://github.com/Tencent/tdesign-react-starter/pull/87
60 |
61 | **Full Changelog**: https://github.com/Tencent/tdesign-react-starter/compare/0.1.4...0.1.5
62 |
63 | ## 0.1.4
64 | ## Features
65 |
66 | - 增加面包屑导航
67 |
68 | - 搜索框样式优化
69 |
70 | - Layout组件命名语义化
71 |
72 | - 图表主题功能优化
73 |
74 | ## 0.1.3
75 | 1. 升级tdesign-react
76 |
77 | 2. 替换为tdesign-react Card 组件
78 |
79 | 3. table样式优化
80 |
81 | ## 0.1.2
82 | - 升级axios、tdesign @xucz
83 |
84 | - fix: loss css token @uyarn
85 |
86 | - 修复图表文字重叠 @xucz
87 |
88 | ## 0.1.1
89 | ## Features
90 |
91 | - 新增登录页 #42 by @uyarn
92 |
93 | - Logo点击跳转首页 #45 by @aouos
94 |
95 | ## 0.1.0
96 | ## Bug Fixes
97 |
98 | - 修复页面主题色 by @xucz
99 |
100 | - 修复页面布局
101 |
102 | - 修复版本图表渲染问题
103 |
104 | ## Features
105 |
106 | - 增加数据Mock by @xucz
107 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021-present TDesign
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/docs/docs-starter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tencent/tdesign-react-starter/4a70384d5af80843b135d3e8bd8b1f0be34a779f/docs/docs-starter.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | TDesign Starter
7 |
8 |
9 |
10 |
11 |
12 |
13 |
20 |
21 |
22 |
23 |
28 |
29 |
30 |
31 |
32 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/mock/api/getList.js:
--------------------------------------------------------------------------------
1 | const Mock = require('mockjs');
2 | export default {
3 | url: '/api/get-list',
4 | method: 'get',
5 | response: () => {
6 | return {
7 | code: 0,
8 | msg: 'ok',
9 | data: {
10 | ...Mock.mock({
11 | 'list|100': [
12 | {
13 | 'index|+1': 1,
14 | 'status|1': '@natural(0, 4)',
15 | no: 'BH00@natural(01, 100)',
16 | name: '@city()办公用品采购项目',
17 | 'paymentType|1': '@natural(0, 1)',
18 | 'contractType|1': '@natural(0, 2)',
19 | updateTime: '2020-05-30 @date("HH:mm:ss")',
20 | amount: '@natural(10, 500),000,000',
21 | adminName: '@cname()',
22 | },
23 | ],
24 | }),
25 | },
26 | };
27 | },
28 | };
29 |
--------------------------------------------------------------------------------
/mock/index.js:
--------------------------------------------------------------------------------
1 | import getList from './api/getList';
2 |
3 | export default [getList];
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@tencent/tdesign-react-starter",
3 | "version": "0.3.1",
4 | "private": true,
5 | "scripts": {
6 | "dev:mock": "vite --open --mode mock",
7 | "dev": "vite --open --mode development",
8 | "dev:linux": "vite --mode development",
9 | "build:test": "vite build --mode test",
10 | "build": "vite build --mode release",
11 | "build:site": "vite build --mode site",
12 | "site:preview": "npm run build && cp -r dist _site",
13 | "preview": "vite preview --mode test",
14 | "test": "echo \"no test specified,work in process\"",
15 | "test:coverage": "echo \"no test specified,work in process\"",
16 | "lint": "eslint ./src --ext ts,tsx",
17 | "lint:fix": "eslint ./src --ext ts,tsx --fix",
18 | "prepare": "husky install"
19 | },
20 | "devDependencies": {
21 | "@honkhonk/vite-plugin-svgr": "^1.1.0",
22 | "@types/echarts": "^4.9.13",
23 | "@types/lodash": "^4.14.178",
24 | "@types/mockjs": "^1.0.6",
25 | "@types/node": "^20.2.5",
26 | "@types/qrcode.react": "^1.0.2",
27 | "@types/react": "^18.2.0",
28 | "@types/react-dom": "^18.2.0",
29 | "@types/react-router-dom": "^5.3.3",
30 | "@vitejs/plugin-react": "^1.3.2",
31 | "eslint": "^8.3.0",
32 | "eslint-config-airbnb-base": "^15.0.0",
33 | "eslint-config-prettier": "^8.4.0",
34 | "eslint-config-react-app": "^7.0.0",
35 | "eslint-plugin-import": "^2.25.4",
36 | "eslint-plugin-prettier": "^4.0.0",
37 | "eslint-plugin-react": "^7.28.0",
38 | "husky": "^8.0.1",
39 | "less": "^4.1.3",
40 | "prettier": "^2.5.1",
41 | "typescript": "^4.8.4",
42 | "vite": "^2.9.15",
43 | "vite-plugin-mock": "^2.9.6"
44 | },
45 | "dependencies": {
46 | "@reduxjs/toolkit": "^1.8.5",
47 | "axios": "^1.2.0",
48 | "axios-jsonp": "^1.0.4",
49 | "classnames": "^2.3.1",
50 | "dayjs": "^1.10.7",
51 | "echarts": "^5.3.0",
52 | "echarts-for-react": "^3.0.2",
53 | "lodash": "^4.17.21",
54 | "mockjs": "^1.1.0",
55 | "qrcode.react": "^3.1.0",
56 | "react": "^18.2.0",
57 | "react-dom": "^18.2.0",
58 | "react-redux": "^7.2.4",
59 | "react-router-dom": "^6.3.0",
60 | "tdesign-icons-react": "^0.4.3",
61 | "tdesign-react": "^1.10.4",
62 | "tvision-color": "^1.5.0"
63 | },
64 | "browserslist": {
65 | "production": [
66 | ">0.2%",
67 | "not dead",
68 | "not op_mini all"
69 | ],
70 | "development": [
71 | "last 1 chrome version",
72 | "last 1 firefox version",
73 | "last 1 safari version"
74 | ]
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tencent/tdesign-react-starter/4a70384d5af80843b135d3e8bd8b1f0be34a779f/public/favicon.ico
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/assets/image/assets-login-bg-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tencent/tdesign-react-starter/4a70384d5af80843b135d3e8bd8b1f0be34a779f/src/assets/image/assets-login-bg-black.png
--------------------------------------------------------------------------------
/src/assets/image/assets-login-bg-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tencent/tdesign-react-starter/4a70384d5af80843b135d3e8bd8b1f0be34a779f/src/assets/image/assets-login-bg-white.png
--------------------------------------------------------------------------------
/src/assets/svg/assets-product-1.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/svg/assets-product-2.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/svg/assets-product-3.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/svg/assets-product-4.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/svg/assets-result-403.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/svg/assets-result-404.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/svg/assets-result-500.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/svg/assets-result-network-error.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/svg/assets-setting-auto.svg:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/src/assets/svg/assets-setting-dark.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/svg/assets-setting-light.svg:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/src/assets/svg/assets-t-logo.svg:
--------------------------------------------------------------------------------
1 |
52 |
--------------------------------------------------------------------------------
/src/components/Board/index.module.less:
--------------------------------------------------------------------------------
1 | .boardPanelDark {
2 | background: var(--td-brand-color) !important;
3 | .boardTitle,
4 | .boardItemLeft,
5 | .boardItemDesc,
6 | .trendColorUp,
7 | .trendColorDown,
8 | .boardItemBottom {
9 | color: var(--td-text-color-anti);
10 | }
11 | .trendIconUp,
12 | .trendIconDown {
13 | background: var(--td-brand-color-5);
14 | }
15 | }
16 | .boardPanel {
17 | :global {
18 | .t-card__body {
19 | padding-top: 0;
20 | }
21 | }
22 | }
23 | .boardTitle {
24 | line-height: 22px;
25 | font-size: 14px;
26 | color: var(--td-text-color-secondary);
27 | }
28 |
29 | .boardItem {
30 | display: flex;
31 | justify-content: space-between;
32 | align-items: center;
33 | }
34 |
35 | .boardItemLeft {
36 | display: inline-block;
37 | color: var(--td-text-color-primary);
38 | font-size: 27px;
39 | line-height: 44px;
40 | }
41 |
42 | .boardItemBottom {
43 | display: flex;
44 | justify-content: space-between;
45 | align-items: center;
46 | }
47 |
48 | .boardItemIcon {
49 | opacity: 0.6;
50 | }
51 |
52 | .boardItemDesc {
53 | display: flex;
54 | align-items: center;
55 | justify-content: center;
56 | line-height: 22px;
57 | color: var(--td-text-color-placeholder);
58 | }
59 |
60 | .trendIcon {
61 | border-radius: 50%;
62 | width: 16px;
63 | height: 16px;
64 | margin-left: 8px;
65 | margin-right: 8px;
66 | }
67 |
68 | .trendIconUp {
69 | background: var(--td-error-color-2);
70 | }
71 |
72 | .trendIconDown {
73 | background: var(--td-success-color-2);
74 | }
75 |
76 | .trendColorUp {
77 | color: #e34d59;
78 | display: flex;
79 | align-items: center;
80 | }
81 |
82 | .trendColorDown {
83 | color: #00a870;
84 | display: flex;
85 | align-items: center;
86 | }
87 |
--------------------------------------------------------------------------------
/src/components/Board/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ChevronRightIcon } from 'tdesign-icons-react';
3 | import { Card } from 'tdesign-react';
4 | import classnames from 'classnames';
5 | import Style from './index.module.less';
6 |
7 | export enum ETrend {
8 | up,
9 | down,
10 | }
11 |
12 | export interface IBoardProps extends React.HTMLAttributes {
13 | title?: string;
14 | count?: string;
15 | Icon?: React.ReactElement;
16 | desc?: string;
17 | trend?: ETrend;
18 | trendNum?: string;
19 | dark?: boolean;
20 | border?: boolean;
21 | }
22 |
23 | export const TrendIcon = ({ trend, trendNum }: { trend?: ETrend; trendNum?: string | number }) => (
24 |
30 |
36 | {trend === ETrend.up ? (
37 |
41 | ) : (
42 |
46 | )}
47 |
48 | {trendNum}
49 |
50 | );
51 |
52 | const Board = ({ title, count, desc, trend, trendNum, Icon, dark, border = false }: IBoardProps) => (
53 | {title}}
55 | className={classnames({
56 | [Style.boardPanelDark]: dark,
57 | [Style.boardPanel]: true,
58 | })}
59 | bordered={border}
60 | footer={
61 |
62 |
63 | {desc}
64 |
65 |
66 |
67 |
68 | }
69 | >
70 |
71 |
{count}
72 |
{Icon}
73 |
74 |
75 | );
76 |
77 | export default React.memo(Board);
78 |
--------------------------------------------------------------------------------
/src/components/DatePicker/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { DateRangePicker, DateRangeValue } from 'tdesign-react';
3 | import dayjs from 'dayjs';
4 |
5 | const RECENT_7_DAYS: DateRangeValue = [
6 | dayjs().subtract(7, 'day').format('YYYY-MM-DD'),
7 | dayjs().subtract(1, 'day').format('YYYY-MM-DD'),
8 | ];
9 |
10 | const LastWeekDatePicker = (onChange: (value: DateRangeValue) => void) => (
11 | onChange(value)}
17 | />
18 | );
19 |
20 | export default LastWeekDatePicker;
21 |
--------------------------------------------------------------------------------
/src/components/ErrorPage/index.module.less:
--------------------------------------------------------------------------------
1 | .errorBox {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: center;
5 | align-items: center;
6 | height: 75vh;
7 | padding: 24px;
8 | min-height: 400px;
9 | color: var(--td-brand-color);
10 | img {
11 | width: 200px;
12 | height: 140px;
13 | color: var(--td-brand-color);
14 | }
15 |
16 | .title {
17 | font-weight: 500;
18 | font-size: 20px;
19 | line-height: 28px;
20 | margin-top: 8px;
21 | color: var(--td-text-color-primary);
22 | }
23 | .description {
24 | margin: 8px 0 32px;
25 | font-size: 14px;
26 | line-height: 22px;
27 | color: var(--td-text-color-secondary);
28 | }
29 |
30 | .rightButton {
31 | margin-left: 8px;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/ErrorPage/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { Button } from 'tdesign-react';
3 |
4 | import Light403Icon from 'assets/svg/assets-result-403.svg?component';
5 | import Light404Icon from 'assets/svg/assets-result-404.svg?component';
6 | import Light500Icon from 'assets/svg/assets-result-500.svg?component';
7 | import style from './index.module.less';
8 |
9 | enum ECode {
10 | forbidden = 403,
11 | notFount = 404,
12 | error = 500,
13 | }
14 |
15 | interface IErrorPageProps {
16 | code: ECode;
17 | title?: string;
18 | desc?: string;
19 | }
20 |
21 | const errorInfo = {
22 | [ECode.forbidden]: {
23 | title: '403 Forbidden',
24 | desc: '抱歉,您无权限访问此页面',
25 | icon: ,
26 | },
27 | [ECode.notFount]: {
28 | title: '404 Not Found',
29 | desc: '抱歉,您访问的页面不存在。',
30 | icon: ,
31 | },
32 | [ECode.error]: {
33 | title: '500 Internal Server Error',
34 | desc: '抱歉,服务器出错啦!',
35 | icon: ,
36 | },
37 | };
38 |
39 | const ErrorPage: React.FC = (props) => {
40 | const info = errorInfo[props.code];
41 | return (
42 |
43 | {info?.icon}
44 |
{info?.title}
45 |
{info?.desc}
46 |
47 |
48 | );
49 | };
50 |
51 | export default memo(ErrorPage);
52 |
--------------------------------------------------------------------------------
/src/configs/color.ts:
--------------------------------------------------------------------------------
1 | import { ETheme } from 'types/index.d';
2 |
3 | export const defaultColor = ['#0052d9', '#0594fa', '#00a870', '#ebb105', '#ed7b2f', '#e34d59', '#ed49b4', '#834ec2'];
4 |
5 | export const darkColor = ['#4582e6', '#29a4fb', '#03a56f', '#ca8d03', '#ed7b2f', '#ea7b84', '#f172c5', '#ab87d5'];
6 |
7 | export const CHART_COLORS = {
8 | [ETheme.light]: {
9 | textColor: 'rgba(0, 0, 0, 0.9)',
10 | placeholderColor: 'rgba(0, 0, 0, 0.35)',
11 | borderColor: '#dcdcdc',
12 | containerColor: '#fff',
13 | },
14 | [ETheme.dark]: {
15 | textColor: 'rgba(255, 255, 255, 0.9)',
16 | placeholderColor: 'rgba(255, 255, 255, 0.35)',
17 | borderColor: '#5e5e5e',
18 | containerColor: '#242424',
19 | },
20 | };
21 |
--------------------------------------------------------------------------------
/src/configs/host.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | mock: {
3 | // 本地mock数据
4 | API: '',
5 | },
6 | development: {
7 | // 开发环境接口请求
8 | API: 'https://service-exndqyuk-1257786608.gz.apigw.tencentcs.com',
9 | },
10 | test: {
11 | // 测试环境接口地址
12 | API: 'https://service-exndqyuk-1257786608.gz.apigw.tencentcs.com',
13 | },
14 | release: {
15 | // 正式环境接口地址
16 | API: 'https://service-bv448zsw-1257786608.gz.apigw.tencentcs.com',
17 | },
18 | site: {
19 | // TDesign部署特殊需要 与release功能一致
20 | API: 'https://service-bv448zsw-1257786608.gz.apigw.tencentcs.com',
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/src/global.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.avif' {
2 | export default src as string;
3 | }
4 |
5 | declare module '*.bmp' {
6 | export default src as string;
7 | }
8 |
9 | declare module '*.gif' {
10 | export default src as string;
11 | }
12 |
13 | declare module '*.jpg' {
14 | export default src as string;
15 | }
16 |
17 | declare module '*.jpeg' {
18 | export default src as string;
19 | }
20 |
21 | declare module '*.png' {
22 | export default src as string;
23 | }
24 |
25 | declare module '*.webp' {
26 | export default src as string;
27 | }
28 |
29 | declare module '*.svg' {
30 | export default src as string;
31 | }
32 | declare module '*.svg?component' {
33 | export default src as string;
34 | }
35 | declare module '*.module.css' {
36 | export default classes as { readonly [key: string]: string };
37 | }
38 |
39 | declare module '*.module.less' {
40 | export default classes as { readonly [key: string]: string };
41 | }
42 |
43 | declare module '*.less' {
44 | export default classes as { readonly [key: string]: string };
45 | }
46 |
47 | declare module 'tvision-color';
48 |
49 | declare interface ImportMeta {
50 | env: {
51 | MODE: 'development' | 'test' | 'release' | 'mock' | 'site';
52 | };
53 | }
54 |
--------------------------------------------------------------------------------
/src/hooks/useDynamicChart.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 | import { useAppSelector } from 'modules/store';
3 | import { selectGlobal } from 'modules/global';
4 | import { getChartColor } from 'utils/color';
5 | import { CHART_COLORS } from 'configs/color';
6 | import lodashSet from 'lodash/set';
7 | import lodashMap from 'lodash/map';
8 | import { ETheme } from '../types';
9 |
10 | export type TChartColorKey = keyof (typeof CHART_COLORS)[ETheme.light];
11 | /**
12 | * 根据当前主题色返回动态的图表颜色列表
13 | * @param options 图表的固定配置
14 | * @param configs 需要动态变换颜色的字段
15 | * @returns string[]
16 | */
17 | export default function useDynamicChart(
18 | options: Record,
19 | configs?: Partial>>,
20 | ) {
21 | const { theme, color } = useAppSelector(selectGlobal);
22 | return useMemo(() => {
23 | const dynamicColor = getChartColor(theme, color);
24 | const newOptions = {
25 | ...options,
26 | };
27 | // 设置动态的图表颜色
28 | lodashSet(newOptions, 'color', dynamicColor.colorList);
29 | if (configs) {
30 | lodashMap(configs, (config, configKey: TChartColorKey) => {
31 | config?.map((val) => lodashSet(newOptions, val, dynamicColor[configKey]));
32 | });
33 | }
34 | return newOptions;
35 | }, [theme, color, options]);
36 | }
37 |
--------------------------------------------------------------------------------
/src/layouts/components/AppLayout.module.less:
--------------------------------------------------------------------------------
1 | .sidePanel {
2 | height: 100%;
3 | display: flex;
4 | flex-direction: row!important;
5 | }
6 |
7 | .sideContainer {
8 | flex: 1;
9 | min-width: 760px;
10 | overflow: auto;
11 | }
12 |
13 | .topPanel {
14 | min-width: 1150px;
15 | }
16 |
17 | .mixPanel {
18 | height: 100%;
19 | }
20 |
21 | .mixMain {
22 | flex-direction: row!important;
23 | flex: 1;
24 | overflow: auto;
25 | }
26 |
27 | .mixContent {
28 | overflow: auto;
29 | }
30 |
--------------------------------------------------------------------------------
/src/layouts/components/AppLayout.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Layout } from 'tdesign-react';
3 | import { ELayout } from 'modules/global';
4 | import Header from './Header';
5 | import Footer from './Footer';
6 | import Menu from './Menu';
7 | import classnames from 'classnames';
8 | import Content from './AppRouter';
9 |
10 | import Style from './AppLayout.module.less';
11 |
12 | const SideLayout = React.memo(() => (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | ));
22 |
23 | const TopLayout = React.memo(() => (
24 |
25 |
26 |
27 |
28 |
29 | ));
30 |
31 | const MixLayout = React.memo(() => (
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | ));
43 |
44 | const FullPageLayout = React.memo(() => );
45 |
46 | export default {
47 | [ELayout.side]: SideLayout,
48 | [ELayout.top]: TopLayout,
49 | [ELayout.mix]: MixLayout,
50 | [ELayout.fullPage]: FullPageLayout,
51 | };
52 |
--------------------------------------------------------------------------------
/src/layouts/components/AppRouter.module.less:
--------------------------------------------------------------------------------
1 | .loading {
2 | height: 100%;
3 | width: 100%;
4 | display: flex;
5 | justify-content: center;
6 | align-content: center;
7 | }
8 |
--------------------------------------------------------------------------------
/src/layouts/components/AppRouter.tsx:
--------------------------------------------------------------------------------
1 | import React, { Suspense, memo } from 'react';
2 | import { Routes, Route, Navigate } from 'react-router-dom';
3 | import { Layout, Loading } from 'tdesign-react';
4 | import routers, { IRouter } from 'router';
5 | import { resolve } from 'utils/path';
6 | import Page from './Page';
7 | import Style from './AppRouter.module.less';
8 |
9 | const { Content } = Layout;
10 |
11 | type TRenderRoutes = (routes: IRouter[], parentPath?: string, breadcrumbs?: string[]) => React.ReactNode[];
12 | /**
13 | * 渲染应用路由
14 | * @param routes
15 | * @param parentPath
16 | * @param breadcrumb
17 | */
18 | const renderRoutes: TRenderRoutes = (routes, parentPath = '', breadcrumb = []) =>
19 | routes.map((route, index: number) => {
20 | const { Component, children, redirect, meta } = route;
21 | const currentPath = resolve(parentPath, route.path);
22 | let currentBreadcrumb = breadcrumb;
23 |
24 | if (meta?.title) {
25 | currentBreadcrumb = currentBreadcrumb.concat([meta?.title]);
26 | }
27 |
28 | if (redirect) {
29 | // 重定向
30 | return } />;
31 | }
32 |
33 | if (Component) {
34 | // 有路由菜单
35 | return (
36 |
41 |
42 |
43 | }
44 | />
45 | );
46 | }
47 | // 无路由菜单
48 | return children ? renderRoutes(children, currentPath, currentBreadcrumb) : null;
49 | });
50 |
51 | const AppRouter = () => (
52 |
53 |
56 |
57 |
58 | }
59 | >
60 | {renderRoutes(routers)}
61 |
62 |
63 | );
64 |
65 | export default memo(AppRouter);
66 |
--------------------------------------------------------------------------------
/src/layouts/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Layout, Row } from 'tdesign-react';
3 | import { useAppSelector } from 'modules/store';
4 | import { selectGlobal } from 'modules/global';
5 |
6 | const { Footer: TFooter } = Layout;
7 |
8 | const Footer = () => {
9 | const globalState = useAppSelector(selectGlobal);
10 | if (!globalState.showFooter) {
11 | return null;
12 | }
13 |
14 | return (
15 |
16 | Copyright © 2022-{new Date().getFullYear()} Tencent. All Rights Reserved
17 |
18 | );
19 | };
20 |
21 | export default React.memo(Footer);
22 |
--------------------------------------------------------------------------------
/src/layouts/components/Header/HeaderIcon.module.less:
--------------------------------------------------------------------------------
1 | .header {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | }
6 | .badge {
7 | :global {
8 | .t-badge--circle {
9 | top: 5px;
10 | right: 6px;
11 | }
12 | }
13 | }
14 | .menuIcon {
15 | width: 30px !important;
16 | height: 30px !important;
17 | }
18 | .dropItem {
19 | display: flex;
20 | align-items: center;
21 | span {
22 | margin-left: 8px;
23 | }
24 | }
25 | .dropdown {
26 | padding-left: 0;
27 | padding-right: 0;
28 | .icon {
29 | font-size: 20px;
30 | display: inline-block;
31 | margin: 0 5px;
32 | }
33 | :global {
34 | .t-button--variant-text {
35 | padding-left: 9px;
36 | padding-right: 9px;
37 | }
38 | .t-button__text {
39 | display: flex;
40 | align-items: center;
41 | }
42 | .t-dropdown__item {
43 | max-width: none !important;
44 | width: 117px;
45 | &-text {
46 | display: flex;
47 | align-items: center;
48 | }
49 | .t-icon {
50 | margin-right: 8px;
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/layouts/components/Header/HeaderIcon.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { useNavigate } from 'react-router-dom';
3 | import { Button, Popup, Badge, Dropdown, Space } from 'tdesign-react';
4 | import {
5 | Icon,
6 | LogoGithubIcon,
7 | MailIcon,
8 | HelpCircleIcon,
9 | SettingIcon,
10 | PoweroffIcon,
11 | UserCircleIcon,
12 | } from 'tdesign-icons-react';
13 | import { useAppDispatch } from 'modules/store';
14 | import { toggleSetting } from 'modules/global';
15 | import { logout } from 'modules/user';
16 | import Style from './HeaderIcon.module.less';
17 |
18 | const { DropdownMenu, DropdownItem } = Dropdown;
19 |
20 | export default memo(() => {
21 | const dispatch = useAppDispatch();
22 | const navigate = useNavigate();
23 |
24 | const gotoWiki = () => {
25 | window.open('https://tdesign.tencent.com/react/overview');
26 | };
27 |
28 | const gotoGitHub = () => {
29 | window.open('https://github.com/Tencent/tdesign-react-starter');
30 | };
31 |
32 | const clickHandler = (data: any) => {
33 | if (data.value === 1) {
34 | navigate('/user/index');
35 | }
36 | };
37 | const handleLogout = async () => {
38 | await dispatch(logout());
39 | navigate('/login/index');
40 | };
41 |
42 | return (
43 |
44 |
45 | } />
46 |
47 |
48 | }
55 | />
56 |
57 |
58 | }
65 | />
66 |
67 |
68 |
73 |
74 |
75 |
76 |
77 | 个人中心
78 |
79 |
80 |
81 |
85 |
86 |
87 |
88 |
89 |
98 |
99 | );
100 | });
101 |
--------------------------------------------------------------------------------
/src/layouts/components/Header/Search.module.less:
--------------------------------------------------------------------------------
1 | .panel {
2 | :global {
3 | .t-input {
4 | border: none;
5 | }
6 | .t-input:hover {
7 | background: var(--td-bg-color-secondarycontainer);
8 | transition: background var(--td-anim-duration-base) linear
9 | }
10 | .t-input--focused {
11 | box-shadow: none;
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/layouts/components/Header/Search.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Input } from 'tdesign-react';
3 | import { SearchIcon } from 'tdesign-icons-react';
4 | import Style from './Search.module.less';
5 |
6 | const Search = () => } placeholder='请输入搜索内容' />;
7 |
8 | export default React.memo(Search);
9 |
--------------------------------------------------------------------------------
/src/layouts/components/Header/index.module.less:
--------------------------------------------------------------------------------
1 | .panel {
2 | flex-shrink: 0;
3 | padding-left: 20px;
4 | padding-right: 20px;
5 | position: sticky;
6 | top: 0;
7 | z-index: 101;
8 | display: flex;
9 | justify-content: space-between;
10 | border-bottom: 1px solid var(--td-component-stroke);
11 | }
12 |
--------------------------------------------------------------------------------
/src/layouts/components/Header/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { Layout, Button, Space } from 'tdesign-react';
3 | import { ViewListIcon } from 'tdesign-icons-react';
4 | import { useAppDispatch, useAppSelector } from 'modules/store';
5 | import { selectGlobal, toggleMenu } from 'modules/global';
6 | import HeaderIcon from './HeaderIcon';
7 | import { HeaderMenu } from '../Menu';
8 | import Search from './Search';
9 | import Style from './index.module.less';
10 |
11 | const { Header } = Layout;
12 |
13 | export default memo((props: { showMenu?: boolean }) => {
14 | const globalState = useAppSelector(selectGlobal);
15 | const dispatch = useAppDispatch();
16 |
17 | if (!globalState.showHeader) {
18 | return null;
19 | }
20 |
21 | let HeaderLeft;
22 | if (props.showMenu) {
23 | HeaderLeft = (
24 |
25 |
26 |
27 | );
28 | } else {
29 | HeaderLeft = (
30 |
31 |
40 | );
41 | }
42 |
43 | return (
44 |
45 | {HeaderLeft}
46 |
47 |
48 | );
49 | });
50 |
--------------------------------------------------------------------------------
/src/layouts/components/Menu.module.less:
--------------------------------------------------------------------------------
1 | .menuPanel {
2 | display: flex;
3 | flex-direction: column;
4 | height: 100%;
5 | }
6 |
7 | .menuTip {
8 | color: var(--td-text-color-primary);
9 | opacity: 0.4;
10 | height: 38px;
11 | line-height: 38px;
12 | text-align: center;
13 | }
14 |
15 | .menuLogo {
16 | width: 100%;
17 | height: 100%;
18 | display: flex;
19 | justify-content: center;
20 | align-items: center;
21 | cursor: pointer;
22 | color: var(--td-text-color-primary);
23 | }
24 |
25 | .menuMiniLogo {
26 | width: 28px;
27 | height: 28px;
28 | }
29 |
30 | .logoFull {
31 | color: var(--td-text-color-primary);
32 | width: 184px;
33 | height: 32px;
34 | }
35 |
--------------------------------------------------------------------------------
/src/layouts/components/Menu.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo, useState } from 'react';
2 | import { useNavigate, useLocation } from 'react-router-dom';
3 | import { Menu, MenuValue } from 'tdesign-react';
4 | import router, { IRouter } from 'router';
5 | import { resolve } from 'utils/path';
6 | import { useAppSelector } from 'modules/store';
7 | import { selectGlobal } from 'modules/global';
8 | import MenuLogo from './MenuLogo';
9 | import Style from './Menu.module.less';
10 |
11 | const { SubMenu, MenuItem, HeadMenu } = Menu;
12 |
13 | interface IMenuProps {
14 | showLogo?: boolean;
15 | showOperation?: boolean;
16 | }
17 |
18 | const renderMenuItems = (menu: IRouter[], parentPath = '') => {
19 | const navigate = useNavigate();
20 | return menu.map((item) => {
21 | const { children, meta, path } = item;
22 |
23 | if (!meta || meta?.hidden === true) {
24 | // 无meta信息 或 hidden == true,路由不显示为菜单
25 | return null;
26 | }
27 |
28 | const { Icon, title, single } = meta;
29 | const routerPath = resolve(parentPath, path);
30 |
31 | if (!children || children.length === 0) {
32 | return (
33 | : undefined}
37 | onClick={() => navigate(routerPath)}
38 | >
39 | {title}
40 |
41 | );
42 | }
43 |
44 | if (single && children?.length > 0) {
45 | const firstChild = children[0];
46 | if (firstChild?.meta && !firstChild?.meta?.hidden) {
47 | const { Icon, title } = meta;
48 | const singlePath = resolve(resolve(parentPath, path), firstChild.path);
49 | return (
50 | : undefined}
54 | onClick={() => navigate(singlePath)}
55 | >
56 | {title}
57 |
58 | );
59 | }
60 | }
61 |
62 | return (
63 | : undefined}>
64 | {renderMenuItems(children, routerPath)}
65 |
66 | );
67 | });
68 | };
69 |
70 | /**
71 | * 顶部菜单
72 | */
73 | export const HeaderMenu = memo(() => {
74 | const globalState = useAppSelector(selectGlobal);
75 | const location = useLocation();
76 | const [active, setActive] = useState(location.pathname); // todo
77 |
78 | return (
79 | setActive(v)}
85 | >
86 | {renderMenuItems(router)}
87 |
88 | );
89 | });
90 |
91 | /**
92 | * 左侧菜单
93 | */
94 | export default memo((props: IMenuProps) => {
95 | const location = useLocation();
96 | const globalState = useAppSelector(selectGlobal);
97 |
98 | const { version } = globalState;
99 | const bottomText = globalState.collapsed ? version : `TDesign Starter ${version}`;
100 |
101 | return (
102 |
114 | );
115 | });
116 |
--------------------------------------------------------------------------------
/src/layouts/components/MenuLogo.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import Style from './Menu.module.less';
3 | import FullLogo from 'assets/svg/assets-logo-full.svg?component';
4 | import MiniLogo from 'assets/svg/assets-t-logo.svg?component';
5 | import { useNavigate } from 'react-router-dom';
6 |
7 | interface IProps {
8 | collapsed?: boolean;
9 | }
10 |
11 | export default memo((props: IProps) => {
12 | const navigate = useNavigate();
13 |
14 | const handleClick = () => {
15 | navigate('/');
16 | };
17 |
18 | return (
19 |
20 | {props.collapsed ? : }
21 |
22 | );
23 | });
24 |
--------------------------------------------------------------------------------
/src/layouts/components/Page.module.less:
--------------------------------------------------------------------------------
1 | .panel {
2 | margin: 24px;
3 | padding: 0;
4 | overflow: auto;
5 | }
6 |
7 | .breadcrumb {
8 | margin-bottom: 8px;
9 | }
10 |
--------------------------------------------------------------------------------
/src/layouts/components/Page.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { useAppDispatch, useAppSelector } from '../../modules/store';
3 | import { selectGlobal, switchFullPage } from '../../modules/global';
4 | import { Layout, Breadcrumb } from 'tdesign-react';
5 | import Style from './Page.module.less';
6 |
7 | const { Content } = Layout;
8 | const { BreadcrumbItem } = Breadcrumb;
9 |
10 | const Page = ({
11 | children,
12 | isFullPage,
13 | breadcrumbs,
14 | }: React.PropsWithChildren<{ isFullPage?: boolean; breadcrumbs?: string[] }>) => {
15 | const globalState = useAppSelector(selectGlobal);
16 | const dispatch = useAppDispatch();
17 | useEffect(() => {
18 | dispatch(switchFullPage(isFullPage));
19 | }, [isFullPage]);
20 |
21 | if (isFullPage) {
22 | return <>{children}>;
23 | }
24 |
25 | return (
26 |
27 | {globalState.showBreadcrumbs && (
28 |
29 | {breadcrumbs?.map((item, index) => (
30 | {item}
31 | ))}
32 |
33 | )}
34 | {children}
35 |
36 | );
37 | };
38 |
39 | export default React.memo(Page);
40 |
--------------------------------------------------------------------------------
/src/layouts/components/Setting/RadioColor.module.less:
--------------------------------------------------------------------------------
1 | .panel {
2 | display: flex;
3 | justify-content: space-between;
4 | }
5 |
6 | .box {
7 | cursor: pointer;
8 | border: 2px solid;
9 | padding: 4px;
10 | border-radius: 50%;
11 | }
12 |
13 | .item {
14 | width: 24px;
15 | height: 24px;
16 | border-radius: 50%;
17 | }
18 |
--------------------------------------------------------------------------------
/src/layouts/components/Setting/RadioColor.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { defaultColor } from 'configs/color';
3 | import { Popup, ColorPickerPanel } from 'tdesign-react';
4 | import Style from './RadioColor.module.less';
5 |
6 | interface IProps {
7 | defaultValue?: number | string;
8 | onChange: (color: string) => void;
9 | }
10 |
11 | const RadioColor = (props: IProps) => {
12 | const [isColoPickerDisplay, onPopupVisibleChange] = useState(false);
13 |
14 | return (
15 |
16 | {defaultColor.map((color, index) => (
17 |
props?.onChange(color)}
20 | className={Style.box}
21 | style={{ borderColor: props.defaultValue === color && !isColoPickerDisplay ? color : 'transparent' }}
22 | >
23 |
24 |
25 | ))}
26 |
27 |
onPopupVisibleChange(v)}
33 | overlayInnerStyle={{ padding: 0 }}
34 | content={
35 | props?.onChange(v)}
37 | colorModes={['monochrome']}
38 | format='HEX'
39 | swatchColors={[]}
40 | defaultValue={props.defaultValue as string}
41 | />
42 | }
43 | >
44 |
62 |
63 |
64 | );
65 | };
66 |
67 | export default React.memo(RadioColor);
68 |
--------------------------------------------------------------------------------
/src/layouts/components/Setting/RadioRect.module.less:
--------------------------------------------------------------------------------
1 | .radioRectPanel {
2 | display: flex;
3 | justify-content: space-between;
4 | }
5 |
6 | .rectItem {
7 | border: 2px solid #e3e6eb;
8 | padding: 8px;
9 | border-radius: 3px;
10 | cursor: pointer;
11 | }
12 |
13 | .rectItemSelected {
14 | border-color: var(--td-brand-color);
15 | }
16 |
17 | .rectText {
18 | text-align: center;
19 | font-size: 14px;
20 | margin-top: 8px;
21 | line-height: 21px;
22 | }
23 |
24 | .rectImg {
25 | width: 88px;
26 | height: 48px;
27 | background-size: 100% 100%;
28 | }
29 |
--------------------------------------------------------------------------------
/src/layouts/components/Setting/RadioRect.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo, useState } from 'react';
2 | import classname from 'classnames';
3 | import Style from './RadioRect.module.less';
4 |
5 | interface IOption {
6 | value?: any;
7 | image: JSX.Element | string;
8 | name?: string;
9 | }
10 |
11 | interface IProps {
12 | defaultValue?: number | string;
13 | onChange: (value?: any) => void;
14 | options: IOption[];
15 | }
16 |
17 | export default memo((props: IProps) => {
18 | const [selectValue, setSelectValue] = useState(props.defaultValue);
19 |
20 | const handleClick = (option: IOption) => {
21 | setSelectValue(option.value);
22 | props?.onChange(option.value);
23 | };
24 |
25 | return (
26 |
27 | {props.options.map((item, index) => {
28 | let ImageItem = item.image;
29 | if (typeof item.image === 'string') {
30 | ImageItem =
;
31 | }
32 |
33 | return (
34 |
35 |
handleClick(item)}
38 | >
39 | {ImageItem}
40 |
41 | {item.name &&
{item.name}
}
42 |
43 | );
44 | })}
45 |
46 | );
47 | });
48 |
--------------------------------------------------------------------------------
/src/layouts/components/Setting/index.module.less:
--------------------------------------------------------------------------------
1 | .settingTitle {
2 | margin: 32px 0 24px 0;
3 | color: var(--td-text-color-primary);
4 | font-size: 14px;
5 | line-height: 22px;
6 | font-weight: 500;
7 | }
8 |
9 | .settingSubTitle {
10 | font-size: 14px;
11 | line-height: 21px;
12 | margin-bottom: 24px;
13 | }
14 |
--------------------------------------------------------------------------------
/src/layouts/index.module.less:
--------------------------------------------------------------------------------
1 | .panel {
2 | height: 100%;
3 |
4 | :global {
5 | .t-menu--scroll::-webkit-scrollbar {
6 | width: 8px;
7 | background: transparent;
8 | }
9 | .t-menu--scroll::-webkit-scrollbar-thumb {
10 | border-radius: 6px;
11 | border: 2px solid transparent;
12 | background-clip: content-box;
13 | background-color: #eeeeee;
14 | }
15 |
16 | .t-default-menu.t-menu--dark {
17 | background: var(--td-gray-color-13);
18 | }
19 | .t-default-menu:not(.t-menu--dark) .t-menu__item.t-is-active:not(.t-is-opened) {
20 | background-color: var(--td-brand-color-1);
21 | color: var(--td-brand-color);
22 | .t-icon {
23 | color: var(--td-brand-color);
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/layouts/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo, useEffect } from 'react';
2 | import { Drawer, Layout } from 'tdesign-react';
3 | import throttle from 'lodash/throttle';
4 | import { useAppSelector, useAppDispatch } from 'modules/store';
5 | import { selectGlobal, toggleSetting, toggleMenu, ELayout, switchTheme } from 'modules/global';
6 | import Setting from './components/Setting';
7 | import AppLayout from './components/AppLayout';
8 | import Style from './index.module.less';
9 |
10 | export default memo(() => {
11 | const globalState = useAppSelector(selectGlobal);
12 | const dispatch = useAppDispatch();
13 |
14 | const AppContainer = AppLayout[globalState.isFullPage ? ELayout.fullPage : globalState.layout];
15 |
16 | useEffect(() => {
17 | dispatch(switchTheme(globalState.theme));
18 | const handleResize = throttle(() => {
19 | if (window.innerWidth < 900) {
20 | dispatch(toggleMenu(true));
21 | } else if (window.innerWidth > 1000) {
22 | dispatch(toggleMenu(false));
23 | }
24 | }, 100);
25 | window.addEventListener('resize', handleResize);
26 | return () => {
27 | window.removeEventListener('resize', handleResize);
28 | };
29 | }, []);
30 |
31 | return (
32 |
33 |
34 | dispatch(toggleSetting())}
41 | >
42 |
43 |
44 |
45 | );
46 | });
47 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import { Provider } from 'react-redux';
4 | import { BrowserRouter } from 'react-router-dom';
5 | import store from 'modules/store';
6 | import App from 'layouts/index';
7 |
8 | import 'tdesign-react/es/style/index.css';
9 |
10 | import './styles/index.less';
11 |
12 | const env = import.meta.env.MODE || 'development';
13 | const baseRouterName = env === 'site' ? '/starter/react/' : '';
14 |
15 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
16 | const root = document.getElementById('app')!;
17 |
18 | const renderApp = () => {
19 | ReactDOM.createRoot(root).render(
20 |
21 |
22 |
23 |
24 | ,
25 | );
26 | };
27 |
28 | renderApp();
29 |
--------------------------------------------------------------------------------
/src/modules/list/base.ts:
--------------------------------------------------------------------------------
1 | import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
2 | import { RootState } from '../store';
3 | import { getContractList, IContract } from 'services/contract';
4 |
5 | const namespace = 'list/base';
6 |
7 | interface IInitialState {
8 | loading: boolean;
9 | current: number;
10 | pageSize: number;
11 | total: number;
12 | contractList: IContract[];
13 | }
14 |
15 | const initialState: IInitialState = {
16 | loading: true,
17 | current: 1,
18 | pageSize: 10,
19 | total: 0,
20 | contractList: [],
21 | };
22 |
23 | export const getList = createAsyncThunk(
24 | `${namespace}/getList`,
25 | async (params: { pageSize: number; current: number }) => {
26 | const result = await getContractList(params);
27 | return {
28 | list: result?.list,
29 | total: result?.total,
30 | pageSize: params.pageSize,
31 | current: params.current,
32 | };
33 | },
34 | );
35 |
36 | const listBaseSlice = createSlice({
37 | name: namespace,
38 | initialState,
39 | reducers: {
40 | clearPageState: () => initialState,
41 | },
42 | extraReducers: (builder) => {
43 | builder
44 | .addCase(getList.pending, (state) => {
45 | state.loading = true;
46 | })
47 | .addCase(getList.fulfilled, (state, action) => {
48 | state.loading = false;
49 | state.contractList = action.payload?.list;
50 | state.total = action.payload?.total;
51 | state.pageSize = action.payload?.pageSize;
52 | state.current = action.payload?.current;
53 | })
54 | .addCase(getList.rejected, (state) => {
55 | state.loading = false;
56 | });
57 | },
58 | });
59 |
60 | export const { clearPageState } = listBaseSlice.actions;
61 |
62 | export const selectListBase = (state: RootState) => state.listBase;
63 |
64 | export default listBaseSlice.reducer;
65 |
--------------------------------------------------------------------------------
/src/modules/list/card.ts:
--------------------------------------------------------------------------------
1 | import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
2 | import { RootState } from '../store';
3 | import { getProductList, IProduct } from 'services/product';
4 |
5 | const namespace = 'list/card';
6 |
7 | interface IInitialState {
8 | pageLoading: boolean;
9 | loading: boolean;
10 | current: number;
11 | pageSize: number;
12 | total: number;
13 | productList: IProduct[];
14 | }
15 |
16 | const initialState: IInitialState = {
17 | pageLoading: true,
18 | loading: true,
19 | current: 1,
20 | pageSize: 12,
21 | total: 0,
22 | productList: [],
23 | };
24 |
25 | export const getList = createAsyncThunk(
26 | `${namespace}/getList`,
27 | async (params: { pageSize: number; current: number }) => {
28 | const { pageSize, current } = params;
29 | const result = await getProductList({
30 | pageSize,
31 | current,
32 | });
33 | return {
34 | list: result?.list,
35 | total: result?.total,
36 | pageSize: params.pageSize,
37 | current: params.current,
38 | };
39 | },
40 | );
41 |
42 | const listCardSlice = createSlice({
43 | name: namespace,
44 | initialState,
45 | reducers: {
46 | clearPageState: () => initialState,
47 | switchPageLoading: (state, action: PayloadAction) => {
48 | state.pageLoading = action.payload;
49 | },
50 | },
51 | extraReducers: (builder) => {
52 | builder
53 | .addCase(getList.pending, (state) => {
54 | state.loading = true;
55 | })
56 | .addCase(getList.fulfilled, (state, action) => {
57 | state.loading = false;
58 | state.productList = action.payload?.list;
59 | state.total = action.payload?.total;
60 | state.pageSize = action.payload?.pageSize;
61 | state.current = action.payload?.current;
62 | })
63 | .addCase(getList.rejected, (state) => {
64 | state.loading = false;
65 | });
66 | },
67 | });
68 |
69 | export const { clearPageState, switchPageLoading } = listCardSlice.actions;
70 |
71 | export const selectListCard = (state: RootState) => state.listCard;
72 |
73 | export default listCardSlice.reducer;
74 |
--------------------------------------------------------------------------------
/src/modules/list/select.ts:
--------------------------------------------------------------------------------
1 | import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
2 | import { RootState } from '../store';
3 | import { getContractList, IContract } from 'services/contract';
4 |
5 | const namespace = 'list/select';
6 |
7 | interface IInitialState {
8 | loading: boolean;
9 | current: number;
10 | pageSize: number;
11 | total: number;
12 | contractList: IContract[];
13 | }
14 |
15 | const initialState: IInitialState = {
16 | loading: true,
17 | current: 1,
18 | pageSize: 10,
19 | total: 0,
20 | contractList: [],
21 | };
22 |
23 | export const getList = createAsyncThunk(
24 | `${namespace}/getSelectList`,
25 | async (params: { pageSize: number; current: number }) => {
26 | const result = await getContractList(params);
27 | return {
28 | list: result?.list,
29 | total: result?.total,
30 | pageSize: params.pageSize,
31 | current: params.current,
32 | };
33 | },
34 | );
35 |
36 | const listSelectSlice = createSlice({
37 | name: namespace,
38 | initialState,
39 | reducers: {
40 | clearPageState: () => initialState,
41 | },
42 | extraReducers: (builder) => {
43 | builder
44 | .addCase(getList.pending, (state) => {
45 | state.loading = true;
46 | })
47 | .addCase(getList.fulfilled, (state, action) => {
48 | state.loading = false;
49 | state.contractList = action.payload?.list;
50 | state.total = action.payload?.total;
51 | state.pageSize = action.payload?.pageSize;
52 | state.current = action.payload?.current;
53 | })
54 | .addCase(getList.rejected, (state) => {
55 | state.loading = false;
56 | });
57 | },
58 | });
59 |
60 | export const { clearPageState } = listSelectSlice.actions;
61 |
62 | export const selectListSelect = (state: RootState) => state.listSelect;
63 |
64 | export default listSelectSlice.reducer;
65 |
--------------------------------------------------------------------------------
/src/modules/store.ts:
--------------------------------------------------------------------------------
1 | import { configureStore, combineReducers } from '@reduxjs/toolkit';
2 | import { TypedUseSelectorHook, useSelector, useDispatch } from 'react-redux';
3 |
4 | import global from './global';
5 | import user from './user';
6 | import listBase from './list/base';
7 | import listSelect from './list/select';
8 | import listCard from './list/card';
9 |
10 | const reducer = combineReducers({
11 | global,
12 | user,
13 | listBase,
14 | listSelect,
15 | listCard,
16 | });
17 |
18 | export const store = configureStore({
19 | reducer,
20 | });
21 |
22 | export type RootState = ReturnType;
23 | export type AppDispatch = typeof store.dispatch;
24 |
25 | export const useAppDispatch = () => useDispatch();
26 | export const useAppSelector: TypedUseSelectorHook = useSelector;
27 |
28 | export default store;
29 |
--------------------------------------------------------------------------------
/src/modules/user/index.ts:
--------------------------------------------------------------------------------
1 | import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
2 | import { RootState } from '../store';
3 |
4 | const namespace = 'user';
5 | const TOKEN_NAME = 'tdesign-starter';
6 |
7 | const initialState = {
8 | token: localStorage.getItem(TOKEN_NAME) || 'main_token', // 默认token不走权限
9 | userInfo: {},
10 | };
11 |
12 | // login
13 | export const login = createAsyncThunk(`${namespace}/login`, async (userInfo: Record) => {
14 | const mockLogin = async (userInfo: Record) => {
15 | // 登录请求流程
16 | console.log(userInfo);
17 | // const { account, password } = userInfo;
18 | // if (account !== 'td') {
19 | // return {
20 | // code: 401,
21 | // message: '账号不存在',
22 | // };
23 | // }
24 | // if (['main_', 'dev_'].indexOf(password) === -1) {
25 | // return {
26 | // code: 401,
27 | // message: '密码错误',
28 | // };
29 | // }
30 | // const token = {
31 | // main_: 'main_token',
32 | // dev_: 'dev_token',
33 | // }[password];
34 | return {
35 | code: 200,
36 | message: '登陆成功',
37 | data: 'main_token',
38 | };
39 | };
40 |
41 | const res = await mockLogin(userInfo);
42 | if (res.code === 200) {
43 | return res.data;
44 | }
45 | throw res;
46 | });
47 |
48 | // getUserInfo
49 | export const getUserInfo = createAsyncThunk(`${namespace}/getUserInfo`, async (_, { getState }: any) => {
50 | const { token } = getState();
51 | const mockRemoteUserInfo = async (token: string) => {
52 | if (token === 'main_token') {
53 | return {
54 | name: 'td_main',
55 | roles: ['all'],
56 | };
57 | }
58 | return {
59 | name: 'td_dev',
60 | roles: ['userIndex', 'dashboardBase', 'login'],
61 | };
62 | };
63 |
64 | const res = await mockRemoteUserInfo(token);
65 |
66 | return res;
67 | });
68 |
69 | const userSlice = createSlice({
70 | name: namespace,
71 | initialState,
72 | reducers: {
73 | logout: (state) => {
74 | localStorage.removeItem(TOKEN_NAME);
75 | state.token = '';
76 | state.userInfo = {};
77 | },
78 | remove: (state) => {
79 | state.token = '';
80 | },
81 | },
82 | extraReducers: (builder) => {
83 | builder
84 | .addCase(login.fulfilled, (state, action) => {
85 | localStorage.setItem(TOKEN_NAME, action.payload);
86 |
87 | state.token = action.payload;
88 | })
89 | .addCase(getUserInfo.fulfilled, (state, action) => {
90 | state.userInfo = action.payload;
91 | });
92 | },
93 | });
94 |
95 | export const selectListBase = (state: RootState) => state.listBase;
96 |
97 | export const { logout, remove } = userSlice.actions;
98 |
99 | export default userSlice.reducer;
100 |
--------------------------------------------------------------------------------
/src/pages/Dashboard/Base/components/MiddleChart.module.less:
--------------------------------------------------------------------------------
1 | .middleChartPanel {
2 | margin-top: 16px;
3 | }
4 |
--------------------------------------------------------------------------------
/src/pages/Dashboard/Base/components/MiddleChart.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Col, Row, Card } from 'tdesign-react';
3 | import ReactEcharts from 'echarts-for-react';
4 | import useDynamicChart from 'hooks/useDynamicChart';
5 | import LastWeekDatePicker from 'components/DatePicker';
6 | import { getLineChartOptions, getPieChartOptions } from '../chart';
7 | import Style from './MiddleChart.module.less';
8 |
9 | const lineOptions = getLineChartOptions();
10 | const pieOptions = getPieChartOptions();
11 |
12 | const MiddleChart = () => {
13 | const [customOptions, setCustomOptions] = useState(lineOptions);
14 |
15 | const onTimeChange = (value: Array) => {
16 | const options = getLineChartOptions(value);
17 | setCustomOptions(options);
18 | };
19 |
20 | const dynamicLineChartOption = useDynamicChart(customOptions, {
21 | placeholderColor: ['legend.textStyle.color', 'xAxis.axisLabel.color', 'yAxis.axisLabel.color'],
22 | borderColor: ['series.0.itemStyle.borderColor', 'series.1.itemStyle.borderColor'],
23 | });
24 |
25 | const dynamicPieChartOption = useDynamicChart(pieOptions, {
26 | placeholderColor: ['legend.textStyle.color'],
27 | containerColor: ['series.0.itemStyle.borderColor'],
28 | textColor: ['label.color', 'label.color'],
29 | });
30 |
31 | return (
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | );
45 | };
46 |
47 | export default React.memo(MiddleChart);
48 |
--------------------------------------------------------------------------------
/src/pages/Dashboard/Base/components/Overview.module.less:
--------------------------------------------------------------------------------
1 | .overviewPanel {
2 | background-color: var(--td-bg-color-container);
3 | border-radius: 3px;
4 | margin-top: 16px;
5 | }
6 |
--------------------------------------------------------------------------------
/src/pages/Dashboard/Base/components/Overview.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Button, Col, Row, Card } from 'tdesign-react';
3 | import ReactEcharts from 'echarts-for-react';
4 | import useDynamicChart from 'hooks/useDynamicChart';
5 | import Board, { ETrend } from 'components/Board';
6 | import LastWeekDatePicker from 'components/DatePicker';
7 | import { getBarChartOptions } from '../chart';
8 | import Style from './Overview.module.less';
9 |
10 | const options = getBarChartOptions();
11 | const Overview = (): React.ReactElement => {
12 | const [customOptions, setCustomOptions] = useState(options);
13 | const onTimeChange = (value: any) => {
14 | // eslint-disable-next-line no-shadow
15 | const options = getBarChartOptions(value);
16 | setCustomOptions(options);
17 | };
18 |
19 | const dynamicChartOption = useDynamicChart(customOptions, {
20 | placeholderColor: ['legend.textStyle.color', 'xAxis.axisLabel.color', 'yAxis.axisLabel.color'],
21 | });
22 |
23 | return (
24 |
25 |
26 |
27 |
28 |
34 |
35 |
36 |
37 | 导出数据} bordered={false}>
38 |
39 |
40 |
49 |
50 |
51 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | );
67 | };
68 |
69 | export default React.memo(Overview);
70 |
--------------------------------------------------------------------------------
/src/pages/Dashboard/Base/components/RankList.module.less:
--------------------------------------------------------------------------------
1 | .rankListPanel {
2 | margin-top: 16px;
3 | }
4 |
5 | .rankIndex {
6 | display: inline-flex;
7 | width: 24px;
8 | height: 24px;
9 | border-radius: 50%;
10 | color: #fff;
11 | font-size: 14px;
12 | background-color: var(--td-gray-color-5);
13 | align-items: center;
14 | justify-content: center;
15 | font-weight: 700;
16 | }
17 | .rankIndexTop {
18 | background: var(--td-brand-color);
19 | }
20 |
--------------------------------------------------------------------------------
/src/pages/Dashboard/Base/components/TopPanel.module.less:
--------------------------------------------------------------------------------
1 | .iconWrap {
2 | display: inline-flex;
3 | align-items: center;
4 | justify-content: center;
5 | width: 56px;
6 | height: 56px;
7 | background: var(--td-brand-color-1);
8 | border-radius: 50%;
9 | position: absolute;
10 | top: 32px;
11 | right: 32px;
12 | }
13 |
14 | .svgIcon {
15 | font-size: 24px;
16 | color: var(--td-brand-color);
17 | }
18 |
19 | .paneLineChart {
20 | width: 103px;
21 | height: 56px;
22 | overflow: hidden;
23 | position: absolute;
24 | top: 55px;
25 | right: 32px;
26 | }
27 | .paneBarChart {
28 | width: 103px;
29 | height: 36px;
30 | -webkit-tap-highlight-color: transparent;
31 | user-select: none;
32 | position: absolute;
33 | top: 55px;
34 | right: 32px;
35 | }
36 |
--------------------------------------------------------------------------------
/src/pages/Dashboard/Base/components/TopPanel.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { Col, Row } from 'tdesign-react';
3 | import { UsergroupIcon, FileIcon } from 'tdesign-icons-react';
4 | import ReactEcharts from 'echarts-for-react';
5 | import Board, { ETrend, IBoardProps } from 'components/Board';
6 | import useDynamicChart from 'hooks/useDynamicChart';
7 | import { MICRO_CHART_OPTIONS_BAR, MICRO_CHART_OPTIONS_LINE } from '../chart';
8 | import Style from './TopPanel.module.less';
9 |
10 | const BarChartIcon = memo(() => {
11 | const dynamicChartOption = useDynamicChart(MICRO_CHART_OPTIONS_BAR);
12 | return (
13 |
14 |
20 |
21 | );
22 | });
23 |
24 | const PieChartIcon = memo(() => (
25 |
26 |
32 |
33 | ));
34 |
35 | const PANE_LIST: Array = [
36 | {
37 | title: '总收入',
38 | count: '¥ 28,425.00',
39 | trend: ETrend.up,
40 | trendNum: '20.5%',
41 | Icon: ,
42 | },
43 | {
44 | title: '总退款',
45 | count: '¥ 768.00',
46 | trend: ETrend.down,
47 | trendNum: '20.5%',
48 | Icon: ,
49 | },
50 | {
51 | title: '活跃用户(个)',
52 | count: '1126',
53 | trend: ETrend.down,
54 | trendNum: '20.5%',
55 | Icon: (
56 |
57 |
58 |
59 | ),
60 | },
61 | {
62 | title: '产生订单(个)',
63 | count: '527',
64 | trend: ETrend.down,
65 | trendNum: '20.5%',
66 | Icon: (
67 |
68 |
69 |
70 | ),
71 | },
72 | ];
73 |
74 | const TopPanel = () => (
75 |
76 | {PANE_LIST.map((item, index) => (
77 |
78 |
87 |
88 | ))}
89 |
90 | );
91 |
92 | export default React.memo(TopPanel);
93 |
--------------------------------------------------------------------------------
/src/pages/Dashboard/Base/constant.ts:
--------------------------------------------------------------------------------
1 | export const SALE_TREND_LIST = [
2 | {
3 | growUp: 1,
4 | productName: '国家电网有限公司',
5 | count: 7059,
6 | date: '2021-09-01',
7 | },
8 | {
9 | growUp: -1,
10 | productName: '深圳燃气集团股份有限公司',
11 | count: 6437,
12 | date: '2021-09-01',
13 | },
14 | {
15 | growUp: 4,
16 | productName: '国家烟草专卖局',
17 | count: 4221,
18 | date: '2021-09-01',
19 | },
20 | {
21 | growUp: 3,
22 | productName: '中国电信集团有限公司',
23 | count: 3317,
24 | date: '2021-09-01',
25 | },
26 | {
27 | growUp: -3,
28 | productName: '中国移动通信集团有限公司',
29 | count: 3015,
30 | date: '2021-09-01',
31 | },
32 | {
33 | growUp: -3,
34 | productName: '新余市办公用户采购项目',
35 | count: 2015,
36 | date: '2021-09-12',
37 | },
38 | ];
39 |
40 | export const PURCHASE_TREND_LIST = [
41 | {
42 | growUp: 1,
43 | productName: '腾讯科技(深圳)有限公司',
44 | count: 3015,
45 | date: '2021-09-01',
46 | },
47 | {
48 | growUp: -1,
49 | productName: '大润发有限公司',
50 | count: 2015,
51 | date: '2021-09-01',
52 | },
53 | {
54 | growUp: 6,
55 | productName: '四川海底捞股份有限公司',
56 | count: 1815,
57 | date: '2021-09-11',
58 | },
59 | {
60 | growUp: -3,
61 | productName: '索尼(中国)有限公司',
62 | count: 1015,
63 | date: '2021-09-21',
64 | },
65 | {
66 | growUp: -4,
67 | productName: '松下电器(中国)有限公司',
68 | count: 445,
69 | date: '2021-09-19',
70 | },
71 | {
72 | growUp: -3,
73 | productName: '新余市办公用户采购项目',
74 | count: 2015,
75 | date: '2021-09-12',
76 | },
77 | ];
78 |
--------------------------------------------------------------------------------
/src/pages/Dashboard/Base/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import TopPanel from './components/TopPanel';
3 | import MiddleChart from './components/MiddleChart';
4 | import RankList from './components/RankList';
5 | import Overview from './components/Overview';
6 |
7 | const DashBoard = () => (
8 |
9 |
10 |
11 |
12 |
13 |
14 | );
15 |
16 | export default memo(DashBoard);
17 |
--------------------------------------------------------------------------------
/src/pages/Dashboard/Detail/components/MonthPurchase.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Col, Row, Card } from 'tdesign-react';
3 | import Board from 'components/Board';
4 | import { PANE_LIST } from '../constant';
5 |
6 | const MonthPurchase = () => (
7 |
8 |
9 | {PANE_LIST.map((item) => (
10 |
11 |
19 |
20 | ))}
21 |
22 |
23 | );
24 |
25 | export default React.memo(MonthPurchase);
26 |
--------------------------------------------------------------------------------
/src/pages/Dashboard/Detail/components/PurchaseThrend.module.less:
--------------------------------------------------------------------------------
1 | .purchaseTrendPanel {
2 | margin-top: 16px;
3 | }
4 |
5 | .productTrendPanel {
6 | display: flex;
7 | flex-direction: column;
8 | border-radius: 3px;
9 | overflow: hidden;
10 | cursor: pointer;
11 | color: var(--td-text-color-secondary);
12 | background: var(--td-bg-color-container);
13 | padding: 24px 32px;
14 | }
15 |
16 | .productLogo {
17 | width: 56px;
18 | height: 56px;
19 | border-radius: 50%;
20 | display: flex;
21 | justify-content: center;
22 | align-items: center;
23 | background: var(--td-brand-color-1);
24 | font-size: 32px;
25 | color: var(--td-brand-color);
26 | }
27 |
28 | .productName {
29 | margin: 24px 0 8px;
30 | font-size: 16px;
31 | font-weight: 400;
32 | color: var(--td-text-color-primary);
33 | }
34 |
35 | .productDesc {
36 | font-size: 12px;
37 | line-height: 20px;
38 | overflow: hidden;
39 | text-overflow: ellipsis;
40 | display: -webkit-box;
41 | -webkit-line-clamp: 2;
42 | -webkit-box-orient: vertical;
43 | margin-bottom: 24px;
44 | height: 40px;
45 | }
46 |
47 | .iconWrap {
48 | position: relative;
49 | .lightBtn {
50 | position: absolute;
51 | left: 22px;
52 | background-color: var(--td-brand-color-2);
53 | --ripple-color: var(--td-brand-color-2);
54 | color: var(--td-brand-color);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/pages/Dashboard/Detail/components/PurchaseTrend.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Col, Dropdown, Row, Tag, Card, Avatar } from 'tdesign-react';
3 | import { Icon, AddIcon } from 'tdesign-icons-react';
4 | import ReactEcharts from 'echarts-for-react';
5 | import LastWeekDatePicker from 'components/DatePicker';
6 | import useDynamicChart from 'hooks/useDynamicChart';
7 | import { PRODUCT_LIST } from '../constant';
8 | import { getLineChartOptions } from '../chart';
9 | import Style from './PurchaseThrend.module.less';
10 |
11 | interface IProductTrendProps {
12 | description: string;
13 | isSetup: boolean;
14 | name: string;
15 | type: string;
16 | icon: string;
17 | }
18 |
19 | const ProductTrend = ({ type, isSetup, description, name, icon }: IProductTrendProps) => (
20 |
21 |
22 |
23 |
24 |
25 | {isSetup ? '已启用' : '已停用'}
26 |
27 |
28 |
{name}
29 |
{description}
30 |
31 |
32 | {type}
33 | }>
34 |
35 | {},
42 | },
43 | {
44 | content: '删除',
45 | value: 'delete',
46 | onClick: () => {},
47 | },
48 | ]}
49 | >
50 |
51 |
52 |
53 |
54 | );
55 |
56 | const options = getLineChartOptions();
57 |
58 | const PurchaseTrend = () => {
59 | const [customOptions, setCustomOptions] = useState(options);
60 |
61 | const onTimeChange = (value: Array) => {
62 | const options = getLineChartOptions(value);
63 | setCustomOptions(options);
64 | };
65 |
66 | const dynamicChartOptions = useDynamicChart(customOptions, {
67 | placeholderColor: ['legend.textStyle.color', 'xAxis.axisLabel.color', 'yAxis.axisLabel.color'],
68 | });
69 |
70 | return (
71 |
72 |
73 |
74 |
80 |
81 |
82 |
83 |
84 | {PRODUCT_LIST.map((item, index) => (
85 |
86 |
93 |
94 | ))}
95 |
96 |
97 |
98 | );
99 | };
100 |
101 | export default React.memo(PurchaseTrend);
102 |
--------------------------------------------------------------------------------
/src/pages/Dashboard/Detail/components/Satisfaction.module.less:
--------------------------------------------------------------------------------
1 | .satisfactionPanel {
2 | margin-top: 16px;
3 | }
4 |
5 | .operation {
6 | display: flex;
7 | }
8 |
9 | .exportBtn {
10 | margin-left: 8px;
11 | }
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/pages/Dashboard/Detail/components/Satisfaction.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Button, Card } from 'tdesign-react';
3 | import ReactEcharts from 'echarts-for-react';
4 | import LastWeekDatePicker from 'components/DatePicker';
5 | import useDynamicChart from 'hooks/useDynamicChart';
6 | import { getScatterChartOptions } from '../chart';
7 | import Style from './Satisfaction.module.less';
8 |
9 | const Satisfaction = () => {
10 | const options = getScatterChartOptions();
11 | const [customOptions, setCustomOptions] = useState(options);
12 | const onTimeChange = (value: Array) => {
13 | const options = getScatterChartOptions(value);
14 | setCustomOptions(options);
15 | };
16 |
17 | const dynamicChartOption = useDynamicChart(customOptions, {
18 | placeholderColor: ['legend.textStyle.color', 'xAxis.axisLabel.color', 'yAxis.axisLabel.color'],
19 | });
20 |
21 | return (
22 |
23 |
27 | {LastWeekDatePicker(onTimeChange)}
28 |
29 |
30 | }
31 | bordered={false}
32 | >
33 |
39 |
40 |
41 | );
42 | };
43 |
44 | export default React.memo(Satisfaction);
45 |
--------------------------------------------------------------------------------
/src/pages/Dashboard/Detail/constant.ts:
--------------------------------------------------------------------------------
1 | import { IBoardProps, ETrend } from 'components/Board';
2 |
3 | export const PANE_LIST: Array = [
4 | {
5 | title: '总申请数(次)',
6 | count: '1126',
7 | trendNum: '10%',
8 | trend: ETrend.up,
9 | },
10 | {
11 | title: '供应商数量(个)',
12 | count: '13',
13 | trendNum: '13%',
14 | trend: ETrend.down,
15 | },
16 | {
17 | title: '采购商品品类(类)',
18 | count: '4',
19 | trendNum: '10%',
20 | trend: ETrend.up,
21 | },
22 | {
23 | title: '申请人数量(人)',
24 | count: '90',
25 | trendNum: '44%',
26 | trend: ETrend.down,
27 | },
28 | {
29 | title: '申请完成率(%)',
30 | count: '80.5',
31 | trendNum: '70%',
32 | trend: ETrend.up,
33 | },
34 | {
35 | title: '到货及时率(%)',
36 | count: '78',
37 | trendNum: '16%',
38 | trend: ETrend.up,
39 | },
40 | ];
41 |
42 | export const PRODUCT_LIST = [
43 | {
44 | description: 'SSL证书又叫服务器证书,腾讯云为您提供证书的一站式服务,包括免费、付费证书的申请、管理及部',
45 | index: 1,
46 | isSetup: true,
47 | name: 'SSL证书',
48 | type: 'D',
49 | icon: 'user',
50 | },
51 | {
52 | description: 'SSL证书又叫服务器证书,腾讯云为您提供证书的一站式服务,包括免费、付费证书的申请、管理及部',
53 | index: 1,
54 | isSetup: true,
55 | name: 'SSL证书',
56 | type: 'C',
57 | icon: 'calendar',
58 | },
59 | ];
60 |
--------------------------------------------------------------------------------
/src/pages/Dashboard/Detail/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import MonthPurchase from './components/MonthPurchase';
3 | import PurchaseTrend from './components/PurchaseTrend';
4 | import PurchaseSatisfaction from './components/Satisfaction';
5 |
6 | export default memo(() => (
7 |
12 | ));
13 |
--------------------------------------------------------------------------------
/src/pages/Detail/Advanced/components/Base.module.less:
--------------------------------------------------------------------------------
1 | .infoBox {
2 | column-count: 2;
3 | .infoBoxItem {
4 | padding: 12px 0;
5 | display: flex;
6 | color: var(--td-text-color-primary);
7 | h1 {
8 | font-weight: 400;
9 | font-size: 14px;
10 | color: var(--td-text-color-secondary);
11 | text-align: left;
12 | line-height: 22px;
13 | margin: 0;
14 | }
15 | span {
16 | margin-left: 24px;
17 | font-size: 14px;
18 | line-height: 1.5;
19 | overflow: hidden;
20 | white-space: nowrap;
21 | text-overflow: ellipsis;
22 | }
23 | i {
24 | display: inline-block;
25 | width: 8px;
26 | height: 8px;
27 | border-radius: 50%;
28 | background: var(--td-success-color-5);
29 | margin-right: 5px;
30 | }
31 | .inProgress {
32 | color: var(--td-success-color-5);
33 | }
34 | .pdf {
35 | color: var(--td-brand-color);
36 | &:hover {
37 | cursor: pointer;
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/pages/Detail/Advanced/components/Base.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Card } from 'tdesign-react';
3 | import classnames from 'classnames';
4 | import { dataInfo } from '../consts';
5 | import Style from './Base.module.less';
6 |
7 | const Base = () => (
8 |
9 |
10 | {dataInfo.map((item) => (
11 |
12 |
{item.name}
13 |
19 | {item.type === 'status' && }
20 | {item.value}
21 |
22 |
23 | ))}
24 |
25 |
26 | );
27 |
28 | export default React.memo(Base);
29 |
--------------------------------------------------------------------------------
/src/pages/Detail/Advanced/components/Detail.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Button, Table, Tag, Card } from 'tdesign-react';
3 | import { dataBuyList, total } from '../consts';
4 | import Style from '../index.module.less';
5 |
6 | const Detail = () => (
7 |
8 |
27 | {row.name}
28 |
29 | {row.tag}
30 |
31 | >
32 | );
33 | },
34 | },
35 | {
36 | align: 'left',
37 | width: 200,
38 | ellipsis: true,
39 | colKey: 'code',
40 | title: '产品编号',
41 | },
42 | {
43 | align: 'left',
44 | width: 100,
45 | ellipsis: true,
46 | colKey: 'amount',
47 | title: '采购数量',
48 | cell({ row }) {
49 | return row.amount;
50 | },
51 | },
52 | {
53 | align: 'left',
54 | width: 200,
55 | ellipsis: true,
56 | colKey: 'department',
57 | title: '申请部门',
58 | },
59 | {
60 | align: 'left',
61 | width: 500,
62 | ellipsis: true,
63 | colKey: 'createtime',
64 | title: '创建时间',
65 | },
66 | {
67 | align: 'left',
68 | fixed: 'right',
69 | width: 300,
70 | colKey: 'op',
71 | title: '操作',
72 | cell() {
73 | return (
74 | <>
75 |
78 |
81 | >
82 | );
83 | },
84 | },
85 | ]}
86 | rowKey='id'
87 | verticalAlign='top'
88 | hover
89 | pagination={{
90 | defaultPageSize: 10,
91 | total,
92 | defaultCurrent: 1,
93 | showJumper: true,
94 | onChange(pageInfo) {
95 | console.log(pageInfo, 'onChange pageInfo');
96 | },
97 | onCurrentChange(current, pageInfo) {
98 | console.log(current, 'onCurrentChange current');
99 | console.log(pageInfo, 'onCurrentChange pageInfo');
100 | },
101 | onPageSizeChange(size, pageInfo) {
102 | console.log(size, 'onPageSizeChange size');
103 | console.log(pageInfo, 'onPageSizeChange pageInfo');
104 | },
105 | }}
106 | />
107 |
108 | );
109 |
110 | export default React.memo(Detail);
111 |
--------------------------------------------------------------------------------
/src/pages/Detail/Advanced/components/Product.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Col, Row, Card } from 'tdesign-react';
3 | import ProductCard from './ProductCard';
4 | import Style from '../index.module.less';
5 |
6 | const Product = () => (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
23 |
24 |
25 |
35 |
36 |
37 |
38 |
39 | );
40 |
41 | export default React.memo(Product);
42 |
--------------------------------------------------------------------------------
/src/pages/Detail/Advanced/components/ProductCard.module.less:
--------------------------------------------------------------------------------
1 | .productionCard {
2 | position: relative;
3 | background-color: var(--td-bg-color-container);
4 | border: 1px solid var(--td-component-border);
5 | border-radius: 3px;
6 | padding: 20px 32px 24px;
7 | height: 212px;
8 | box-sizing: content-box;
9 | }
10 |
11 | .productionTitle {
12 | margin-bottom: 25px;
13 | position: relative;
14 | }
15 |
16 | .title {
17 | font-weight: 500;
18 | font-size: 24px;
19 | color: var(--td-text-color-primary);
20 | }
21 |
22 | .productionIcon {
23 | position: absolute;
24 | top: 0px;
25 | right: 0px;
26 | background: var(--td-brand-color-1);
27 | color: var(--td-brand-color);
28 | font-size: 56px;
29 | padding: 14px;
30 | border-radius: 100%;
31 | }
32 |
33 | .tag {
34 | margin-right: 4px;
35 | margin-top: 8px;
36 | margin-left: unset;
37 | border: unset;
38 | }
39 |
40 | .item {
41 | position: relative;
42 | padding-top: 8px;
43 | padding-bottom: 8px;
44 | }
45 |
46 | .info {
47 | display: inline-block;
48 | width: 60%;
49 | text-align: left;
50 | font-size: 14px;
51 | color: var(--td-text-color-placeholder);
52 | }
53 |
54 | .icon {
55 | position: absolute;
56 | bottom: 8px;
57 | right: 0;
58 | color: rgba(0, 0, 0, 0.26);
59 | }
60 |
61 | .footer {
62 | position: absolute;
63 | width: 100%;
64 | bottom: 0px;
65 | left: 0;
66 | .percent {
67 | position: absolute;
68 | bottom: 15px;
69 | right: 32px;
70 | color: var(--td-text-color-placeholder);
71 | }
72 | .progress {
73 | margin: 0;
74 | }
75 | }
76 |
77 | .productionAdd {
78 | position: relative;
79 | background-color: var(--td-bg-color-container);
80 | border: 1px dashed var(--td-component-border);
81 | border-radius: 3px;
82 | padding: 20px 32px 24px;
83 | height: 212px;
84 | box-sizing: content-box;
85 | display: flex;
86 | justify-content: center;
87 | align-items: center;
88 | }
89 |
90 | .productionAddBtn {
91 | font-size: 14px;
92 | color: var(--td-text-color-secondary);
93 | margin: 0 auto;
94 | text-align: center;
95 | line-height: 22px;
96 | display: flex;
97 | flex-direction: column;
98 | align-items: center;
99 | cursor: pointer;
100 | .productionAddIcon {
101 | background: var(--td-brand-color-1);
102 | color: var(--td-brand-color);
103 | font-size: 33px;
104 | padding: 8px;
105 | border-radius: 100%;
106 | margin-bottom: 12px;
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/pages/Detail/Advanced/components/ProductCard.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Progress, Tag } from 'tdesign-react';
3 | import { AddIcon, ChevronRightIcon, Icon } from 'tdesign-icons-react';
4 | import Styles from './ProductCard.module.less';
5 |
6 | interface IProps {
7 | isAdd?: boolean;
8 | title?: string;
9 | tags?: string[];
10 | desc?: string;
11 | progress?: number;
12 | percent?: string;
13 | Icon?: string;
14 | color?: string;
15 | trackColor?: string;
16 | }
17 |
18 | const ProductCard = (props: IProps) => (
19 |
20 | {props.isAdd ? (
21 |
27 | ) : (
28 |
29 |
30 |
31 |
{props.title}
32 |
33 | {props?.tags?.map((tag, index) => (
34 |
35 | {tag}
36 |
37 | ))}
38 |
39 |
40 |
41 | {props.desc}
42 |
43 |
44 |
45 |
{props.percent}
46 |
49 |
50 |
51 | )}
52 |
53 | );
54 |
55 | export default React.memo(ProductCard);
56 |
--------------------------------------------------------------------------------
/src/pages/Detail/Advanced/components/Progress.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Steps, Card } from 'tdesign-react';
3 | import { dataStep, stepCurrent } from '../consts';
4 | import Style from '../index.module.less';
5 |
6 | const { StepItem } = Steps;
7 |
8 | const Progress = () => (
9 |
10 |
11 | {dataStep.map((item) => (
12 |
13 | ))}
14 |
15 |
16 | );
17 |
18 | export default React.memo(Progress);
19 |
--------------------------------------------------------------------------------
/src/pages/Detail/Advanced/consts.ts:
--------------------------------------------------------------------------------
1 | // Mock Data of 基本信息
2 | interface InfoItem {
3 | id: number;
4 | name: string;
5 | value: string;
6 | type?: string;
7 | }
8 | export const dataInfo: InfoItem[] = [
9 | { id: 1, name: '合同名称', value: '总部办公用品采购项目' },
10 | { id: 2, name: '合同状态', value: '履行中', type: 'status' },
11 | { id: 3, name: '合同编号', value: 'BH00010' },
12 | { id: 4, name: '合同类型', value: '主合同' },
13 | { id: 5, name: '合同收付类型', value: '付款' },
14 | { id: 6, name: '合同金额', value: '5,000,000元' },
15 | { id: 7, name: '甲方', value: '腾讯科技(深圳)有限公司' },
16 | { id: 8, name: '乙方', value: '欧尚' },
17 | { id: 9, name: '合同签订日期', value: '2020-12-20' },
18 | { id: 10, name: '合同生效日期', value: '2021-01-20' },
19 | { id: 11, name: '合同结束日期', value: '2022-12-20' },
20 | { id: 12, name: '合同附件', value: '总部办公用品采购项目合同.pdf', type: 'link' },
21 | { id: 13, name: '备注', value: '--' },
22 | { id: 14, name: '创建时间', value: '2020-12-22 10:00:00' },
23 | ];
24 |
25 | // Mock Data of 变更记录
26 | interface IStepItem {
27 | id: number;
28 | name: string;
29 | detail?: string;
30 | }
31 | export const dataStep: IStepItem[] = [
32 | { id: 1, name: '申请提交', detail: '已于12月21日提交' },
33 | { id: 2, name: '电子发票', detail: '预计1~3个工作日' },
34 | { id: 3, name: '发票已邮寄', detail: '电子发票开出后7个工作日内联系' },
35 | { id: 4, name: '完成', detail: '' },
36 | ];
37 | export const stepCurrent = 2;
38 |
39 | // Mock Data of 产品采购明细
40 | export const total = 36;
41 | const listTables: any = [];
42 | for (let i = 0; i < total; i++) {
43 | listTables.push({
44 | id: i,
45 | number: 'S20201228115950963',
46 | name: 'Macbook ',
47 | tag: '电子产品',
48 | code: 'p_tmp_60a637cd0d ',
49 | amount: 52,
50 | department: '财务部',
51 | createtime: '2021-12-30 10:43:56',
52 | });
53 | }
54 | export const dataBuyList = listTables;
55 |
--------------------------------------------------------------------------------
/src/pages/Detail/Advanced/index.module.less:
--------------------------------------------------------------------------------
1 | .cardBox {
2 | margin-top: 15px;
3 | }
4 |
--------------------------------------------------------------------------------
/src/pages/Detail/Advanced/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import Base from './components/Base';
3 | import ProgressComp from './components/Progress';
4 | import Product from './components/Product';
5 | import Detail from './components/Detail';
6 |
7 | export default memo(() => (
8 |
14 | ));
15 |
--------------------------------------------------------------------------------
/src/pages/Detail/Base/consts.ts:
--------------------------------------------------------------------------------
1 | export const dataInfo = [
2 | { id: 1, name: '合同名称', value: '总部办公用品采购项目' },
3 | { id: 2, name: '合同状态', value: '履行中', type: 'status' },
4 | { id: 3, name: '合同编号', value: 'BH00010' },
5 | { id: 4, name: '合同类型', value: '主合同' },
6 | { id: 5, name: '合同收付类型', value: '付款' },
7 | { id: 6, name: '合同金额', value: '5,000,000元' },
8 | { id: 7, name: '甲方', value: '腾讯科技(深圳)有限公司' },
9 | { id: 8, name: '乙方', value: '欧尚' },
10 | { id: 9, name: '合同签订日期', value: '2020-12-20' },
11 | { id: 10, name: '合同生效日期', value: '2021-01-20' },
12 | { id: 11, name: '合同结束日期', value: '2022-12-20' },
13 | { id: 12, name: '合同附件', value: '总部办公用品采购项目合同.pdf', type: 'link' },
14 | { id: 13, name: '备注', value: '--' },
15 | { id: 14, name: '创建时间', value: '2020-12-22 10:00:00' },
16 | ];
17 |
18 | export const dataStep = [
19 | { id: 1, name: '上传合同附件', detail: '这里是提示文字' },
20 | { id: 2, name: '修改合同金额', detail: '这里是提示文字' },
21 | { id: 3, name: '新建合同', detail: '2020-12-01 15:00:00 管理员-李川操作' },
22 | ];
23 |
--------------------------------------------------------------------------------
/src/pages/Detail/Base/index.module.less:
--------------------------------------------------------------------------------
1 | @Wrap: infoBox;
2 |
3 | .@{Wrap} {
4 | column-count: 2;
5 | &Item {
6 | padding: 12px 0;
7 | display: flex;
8 | color: var(--td-text-color-primary);
9 | h1 {
10 | font-weight: 400;
11 | font-size: 14px;
12 | color: var(--td-text-color-secondary);
13 | text-align: left;
14 | line-height: 22px;
15 | margin: 0;
16 | }
17 | span {
18 | margin-left: 24px;
19 | font-size: 14px;
20 | line-height: 1.5;
21 | overflow: hidden;
22 | white-space: nowrap;
23 | text-overflow: ellipsis;
24 | }
25 | i {
26 | display: inline-block;
27 | width: 8px;
28 | height: 8px;
29 | border-radius: 50%;
30 | background: var(--td-success-color-5);
31 | margin-right: 5px;
32 | }
33 | .inProgress {
34 | color: var(--td-success-color-5);
35 | }
36 | .pdf {
37 | color: var(--td-brand-color);
38 | &:hover {
39 | cursor: pointer;
40 | }
41 | }
42 | }
43 | }
44 |
45 | .logBox {
46 | margin-top: 15px;
47 | }
48 |
49 | @media (min-width: 992px) {
50 | .@{Wrap} {
51 | &Item {
52 | h1 {
53 | width: 200px;
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/pages/Detail/Base/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { Steps, Card } from 'tdesign-react';
3 | import classnames from 'classnames';
4 | import { dataInfo, dataStep } from './consts';
5 | import Style from './index.module.less';
6 |
7 | const { StepItem } = Steps;
8 |
9 | export default memo(() => (
10 |
11 |
12 |
13 | {dataInfo.map((item) => (
14 |
15 |
{item.name}
16 |
22 | {item.type === 'status' && }
23 | {item.value}
24 |
25 |
26 | ))}
27 |
28 |
29 |
30 |
31 |
32 | {dataStep.map((item) => (
33 |
34 | ))}
35 |
36 |
37 |
38 |
39 | ));
40 |
--------------------------------------------------------------------------------
/src/pages/Detail/Deploy/components/BottomTable.module.less:
--------------------------------------------------------------------------------
1 | .operationLink {
2 | color: var(--td-brand-color);
3 | text-decoration: none;
4 | margin-right: 24px;
5 | cursor: pointer;
6 | transition: color 0.2s cubic-bezier(0.38, 0, 0.24, 1);
7 | &:last-child {
8 | margin-right: 0;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/pages/Detail/Deploy/components/BottomTable.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { Table, Card } from 'tdesign-react';
3 | import { TableSort, TdPrimaryTableProps } from 'tdesign-react/es/table';
4 | import request from 'utils/request';
5 | import { TABLE_COLUMNS } from '../constant';
6 | import ManagementPopup from './ManagementPopup';
7 | import Style from './BottomTable.module.less';
8 |
9 | const BottomTable = () => {
10 | const [sort, setSort] = useState({ sortBy: 'name', descending: true });
11 | const [visible, setVisible] = useState(false);
12 | const [{ tableData }, setTableData] = useState({ tableData: [] });
13 | const pagination = {
14 | pageSize: 10,
15 | total: tableData.length,
16 | pageSizeOptions: [],
17 | };
18 |
19 | useEffect(() => {
20 | request.get('/api/get-project-list').then((res: any) => {
21 | if (res.code === 0) {
22 | const { list = [] } = res.data;
23 | setTableData({ tableData: list });
24 | }
25 | });
26 | }, []);
27 |
28 | const removeRow = (rowIndex: number) => {
29 | console.log(' rowIndex = ', rowIndex);
30 | console.log(' tableData = ', tableData);
31 |
32 | tableData.splice(rowIndex, 1);
33 | setTableData({ tableData });
34 | };
35 |
36 | const getTableColumns = (columns: TdPrimaryTableProps['columns']): TdPrimaryTableProps['columns'] => {
37 | if (columns) {
38 | columns[4].cell = (context) => {
39 | const { rowIndex } = context;
40 | return (
41 | <>
42 | setVisible(!visible)}>
43 | 管理
44 |
45 | removeRow(rowIndex)}>
46 | 删除
47 |
48 | >
49 | );
50 | };
51 | }
52 | return columns;
53 | };
54 |
55 | return (
56 | <>
57 |
58 | setSort(newSort)}
65 | />
66 |
67 | {visible && }
68 | >
69 | );
70 | };
71 |
72 | export default React.memo(BottomTable);
73 |
--------------------------------------------------------------------------------
/src/pages/Detail/Deploy/components/DynamicLineChart.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { EChartOption } from 'echarts';
3 | import ReactEcharts from 'echarts-for-react';
4 | import useDynamicChart from 'hooks/useDynamicChart';
5 | import { getLineOptions } from '../chart';
6 |
7 | const DynamicLineChart = () => {
8 | const [lineOptions, setLineOptions] = useState(getLineOptions());
9 | const dynamicLineChartOptions = useDynamicChart(lineOptions, {
10 | placeholderColor: ['legend.textStyle.color', 'xAxis.axisLabel.color', 'yAxis.axisLabel.color'],
11 | });
12 |
13 | useEffect(() => {
14 | const timer = setInterval(() => setLineOptions(getLineOptions()), 3000);
15 | return () => {
16 | clearInterval(timer);
17 | };
18 | }, []);
19 |
20 | return (
21 |
27 | );
28 | };
29 |
30 | export default React.memo(DynamicLineChart);
31 |
--------------------------------------------------------------------------------
/src/pages/Detail/Deploy/components/ManagementPopup.module.less:
--------------------------------------------------------------------------------
1 | .popupBox {
2 | max-height: 620px;
3 | }
4 |
5 | .popupItem {
6 | padding: 12px 0;
7 | display: flex;
8 | height: 46px;
9 | align-items: center;
10 | box-sizing: border-box;
11 | > h1 {
12 | margin: 0;
13 | width: 84px;
14 | font-size: 14px;
15 | color: var(--td-text-color-secondary);
16 | text-align: left;
17 | line-height: 22px;
18 | }
19 | > p {
20 | margin: 0 0 0 24px;
21 | }
22 | }
23 |
24 | .popupItem_blue {
25 | color: var(--td-brand-color-8);
26 | }
27 |
28 | .popupItem_green {
29 | color: var(--td-success-color-5);
30 | }
31 |
--------------------------------------------------------------------------------
/src/pages/Detail/Deploy/components/ManagementPopup.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Dialog } from 'tdesign-react';
3 | import classnames from 'classnames';
4 | import { BASE_INFO_DATA } from '../constant';
5 | import Style from './ManagementPopup.module.less';
6 |
7 | interface IProps {
8 | visible: boolean;
9 | }
10 |
11 | const ManagementPopup = ({ visible }: IProps): React.ReactElement => {
12 | const [isShow, setVisible] = useState(visible);
13 | const handleConfirm = () => setVisible(!isShow);
14 |
15 | return (
16 |
41 | );
42 | };
43 |
44 | export default React.memo(ManagementPopup);
45 |
--------------------------------------------------------------------------------
/src/pages/Detail/Deploy/components/TopChart.module.less:
--------------------------------------------------------------------------------
1 | .panel {
2 | margin-bottom: 16px;
3 | }
4 | .deployPanelLeft {
5 | border-radius: 3px;
6 | min-width: 230px;
7 | width: 100%;
8 | }
9 |
--------------------------------------------------------------------------------
/src/pages/Detail/Deploy/components/TopChart.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Col, Radio, Row, Card } from 'tdesign-react';
3 | import { EChartOption } from 'echarts';
4 | import ReactEcharts from 'echarts-for-react';
5 | import { getBarOptions } from '../chart';
6 | import useDynamicChart from 'hooks/useDynamicChart';
7 | import DynamicLineChart from './DynamicLineChart';
8 | import Style from './TopChart.module.less';
9 |
10 | const TopChart = () => {
11 | const [barOptions, setBarOptions] = useState(getBarOptions());
12 |
13 | const tabChange = (isMonth: boolean) => {
14 | setBarOptions(getBarOptions(isMonth));
15 | };
16 | const dynamicBarChartOptions = useDynamicChart(barOptions, {
17 | placeholderColor: ['legend.textStyle.color', 'xAxis.0.axisLabel.color', 'yAxis.0.axisLabel.color'],
18 | });
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | tabChange(val === 'month')}>
33 | 本周
34 | 本月
35 |
36 | }
37 | bordered={false}
38 | >
39 |
45 |
46 |
47 |
48 | );
49 | };
50 |
51 | export default React.memo(TopChart);
52 |
--------------------------------------------------------------------------------
/src/pages/Detail/Deploy/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import TopChart from './components/TopChart';
3 | import BottomTable from './components/BottomTable';
4 |
5 | const Deploy = () => (
6 |
7 |
8 |
9 |
10 | );
11 |
12 | export default memo(Deploy);
13 |
--------------------------------------------------------------------------------
/src/pages/Detail/Secondary/consts.ts:
--------------------------------------------------------------------------------
1 | export type TStatus = 1 | 2; // 1: 未读 2:已读
2 | export interface IItem {
3 | id: number;
4 | content: string;
5 | tag: string;
6 | createTime: string;
7 | status: TStatus;
8 | type: number;
9 | }
10 |
11 | export const dataItemList: IItem[] = [
12 | {
13 | id: 1,
14 | content: '腾讯大厦一楼改造施工项目 已通过审核!',
15 | tag: '合同动态',
16 | createTime: '2021-01-01 08:00',
17 | status: 1,
18 | type: 1,
19 | },
20 | {
21 | id: 2,
22 | content: '三季度生产原材料采购项目 开票成功!',
23 | tag: '票务动态',
24 | createTime: '2021-01-01 08:00',
25 | status: 2,
26 | type: 2,
27 | },
28 | {
29 | id: 3,
30 | content: '2021-01-01 10:00的【国家电网线下签约】会议即将开始,请提前10分钟前往 会议室1 进行签到!',
31 | tag: '会议通知',
32 | createTime: '2021-01-01 08:00',
33 | status: 1,
34 | type: 3,
35 | },
36 | {
37 | id: 4,
38 | content: '一季度生产原材料采购项目 开票成功!',
39 | tag: '票务动态',
40 | createTime: '2021-01-01 08:00',
41 | status: 1,
42 | type: 2,
43 | },
44 | {
45 | id: 5,
46 | content: '二季度生产原材料采购项目 开票成功!',
47 | tag: '票务动态',
48 | createTime: '2021-01-01 08:00',
49 | status: 1,
50 | type: 2,
51 | },
52 | {
53 | id: 6,
54 | content: '三季度生产原材料采购项目 开票成功!',
55 | tag: '票务动态',
56 | createTime: '2021-01-01 08:00',
57 | status: 1,
58 | type: 2,
59 | },
60 | ];
61 |
--------------------------------------------------------------------------------
/src/pages/Detail/Secondary/index.module.less:
--------------------------------------------------------------------------------
1 | .secondaryNotification {
2 | background-color: var(--td-bg-color-container);
3 | border-radius: 3px;
4 | padding: 24px 32px;
5 | .msgList {
6 | height: 70vh;
7 | .listItem {
8 | cursor: pointer;
9 | padding: 13px 24px 13px 0;
10 | &:hover {
11 | background-color: var(--td-bg-color-container-hover);
12 | }
13 | &:hover .createTime {
14 | display: none;
15 | }
16 | .action {
17 | display: none;
18 | }
19 | &:hover .action {
20 | display: block;
21 | }
22 | &:after {
23 | content: "";
24 | position: absolute;
25 | left: 0;
26 | bottom: 0;
27 | width: 100%;
28 | height: 1px;
29 | background: var(--td-border-level-1-color);
30 | }
31 | }
32 | .content {
33 | margin: 0;
34 | font-size: 14px;
35 | color: var(--td-text-color-placeholder);
36 | text-align: left;
37 | line-height: 22px;
38 | overflow: hidden;
39 | text-overflow: ellipsis;
40 | white-space: nowrap;
41 | }
42 | .tag {
43 | margin-right: 8px;
44 | }
45 | .noData {
46 | text-align: center;
47 | height: 28px;
48 | line-height: 28px;
49 | }
50 | .unread {
51 | color: var(--td-brand-color-8);
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/pages/Form/Base/index.module.less:
--------------------------------------------------------------------------------
1 | .formContainer {
2 | width: 676px;
3 | margin: 0 auto;
4 | display: flex;
5 | align-items: center;
6 | justify-content: center;
7 | background-color: var(--td-bg-color-container);
8 | padding: 30px 0 134px;
9 | }
10 |
11 | .titleText {
12 | font-size: 20px;
13 | margin: 64px 0 32px;
14 | color: var(--td-text-color-primary);
15 | }
16 |
17 | .dateCol {
18 | :global {
19 | .t-form__controls-content {
20 | display: inline;
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/pages/Form/Step/components/StepFour.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { useNavigate } from 'react-router-dom';
3 | import { CheckCircleFilledIcon } from 'tdesign-icons-react';
4 | import { Button } from 'tdesign-react';
5 | import Style from './index.module.less';
6 |
7 | export default memo((props: { callback: Function }) => {
8 | const navigate = useNavigate();
9 | const { callback } = props;
10 |
11 | const onClickAgain = () => {
12 | callback('first');
13 | };
14 |
15 | const onCheck = () => {
16 | const url = '/detail/advanced';
17 | navigate(url);
18 | };
19 |
20 | return (
21 |
22 |
30 |
31 |
完成开票申请
32 |
预计1~3个工作日会将电子发票发至邮箱,发票邮寄请耐心等待
33 |
34 |
37 |
40 |
41 |
42 |
43 | );
44 | });
45 |
--------------------------------------------------------------------------------
/src/pages/Form/Step/components/StepOne.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { Alert, Button, Form, Select } from 'tdesign-react';
3 | import Style from '../index.module.less';
4 |
5 | const { FormItem } = Form;
6 | const { Option } = Select;
7 |
8 | const message = [
9 | '1、申请开票后,电子发票在1~3个工作日内开具;增值税专用发票(纸质)如资质审核通过,将在电子发票开具后10个工作日内为您寄出;',
10 | '2、开票金额为您实际支付金额;',
11 | '3、如有疑问请直接联系:13300001111。',
12 | ];
13 |
14 | const names = [
15 | {
16 | label: '合同A',
17 | value: 'A',
18 | },
19 | {
20 | label: '合同B',
21 | value: 'B',
22 | },
23 | {
24 | label: '合同C',
25 | value: 'C',
26 | },
27 | ];
28 |
29 | const types = [
30 | {
31 | label: '类型A',
32 | value: 'A',
33 | },
34 | {
35 | label: '类型B',
36 | value: 'B',
37 | },
38 | {
39 | label: '类型C',
40 | value: 'C',
41 | },
42 | ];
43 |
44 | export default memo((props: { callback: Function }) => {
45 | const { callback } = props;
46 | const next = () => {
47 | callback('next');
48 | };
49 |
50 | return (
51 | <>
52 |
55 |
83 | >
84 | );
85 | });
86 |
--------------------------------------------------------------------------------
/src/pages/Form/Step/components/StepThree.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { Button, Form, Select, Input, Textarea } from 'tdesign-react';
3 |
4 | const { FormItem } = Form;
5 | const { Option } = Select;
6 |
7 | const addressOptions = [
8 | {
9 | label: '广东省深圳市南山区',
10 | value: '0',
11 | },
12 | {
13 | label: '北京市海淀区',
14 | value: '1',
15 | },
16 | {
17 | label: '四川省成都市高新区',
18 | value: '2',
19 | },
20 | {
21 | label: '广东省广州市天河区',
22 | value: '3',
23 | },
24 | {
25 | label: '陕西省西安市高新区',
26 | value: '4',
27 | },
28 | ];
29 |
30 | export default memo((props: { current: number; callback: Function; steps: any[] }) => {
31 | const { current, callback, steps } = props;
32 |
33 | const next = () => {
34 | callback('next');
35 | };
36 |
37 | const prev = () => {
38 | callback('back');
39 | };
40 |
41 | return (
42 |
90 | );
91 | });
92 |
--------------------------------------------------------------------------------
/src/pages/Form/Step/components/StepTwo.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { Button, Form, Input } from 'tdesign-react';
3 |
4 | const { FormItem } = Form;
5 |
6 | export default memo((props: { current: number; callback: Function; steps: any[] }) => {
7 | const { current, callback, steps = [] } = props;
8 |
9 | const next = () => {
10 | callback('next');
11 | };
12 |
13 | const prev = () => {
14 | callback('back');
15 | };
16 |
17 | return (
18 |
65 | );
66 | });
67 |
--------------------------------------------------------------------------------
/src/pages/Form/Step/components/index.module.less:
--------------------------------------------------------------------------------
1 | .stepFourWrapper {
2 | text-align: center;
3 | }
4 |
5 | .icon {
6 | font-size: 52px;
7 | color: green;
8 | }
9 |
10 | .title {
11 | margin-top: 20px;
12 | font-size: 20px;
13 | // font-weight: 500;
14 | color: var(--td-text-color-secondary);
15 | }
--------------------------------------------------------------------------------
/src/pages/Form/Step/components/index.ts:
--------------------------------------------------------------------------------
1 | export { default as StepOne } from './StepOne';
2 | export { default as StepTwo } from './StepTwo';
3 | export { default as StepThree } from './StepThree';
4 | export { default as StepFour } from './StepFour';
5 |
--------------------------------------------------------------------------------
/src/pages/Form/Step/index.module.less:
--------------------------------------------------------------------------------
1 | .alertBox {
2 | padding: 24px 0;
3 | }
--------------------------------------------------------------------------------
/src/pages/Form/Step/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import classnames from 'classnames';
3 | import { Steps } from 'tdesign-react';
4 | import { StepOne, StepTwo, StepThree, StepFour } from './components';
5 | import CommonStyle from 'styles/common.module.less';
6 |
7 | const { StepItem: Step } = Steps;
8 | interface IStep {
9 | title: string;
10 | content: string;
11 | component: any;
12 | }
13 |
14 | const steps: IStep[] = [
15 | {
16 | title: '申请提交',
17 | content: '申请提交已于12月21日提交',
18 | component: StepOne,
19 | },
20 | {
21 | title: '电子发票',
22 | content: '预计1~3个工作日',
23 | component: StepTwo,
24 | },
25 | {
26 | title: '发票已邮寄',
27 | content: '电子发票开出后7个工作日内联系',
28 | component: StepThree,
29 | },
30 | {
31 | title: '完成',
32 | content: '',
33 | component: StepFour,
34 | },
35 | ];
36 |
37 | export default memo(() => {
38 | const [current, setCurrent] = React.useState(0);
39 | const Comp = steps[current].component;
40 |
41 | const next = () => {
42 | setCurrent(current + 1);
43 | };
44 |
45 | const prev = () => {
46 | setCurrent(current - 1);
47 | };
48 |
49 | const handleSteps = (value: string) => {
50 | switch (value) {
51 | case 'back':
52 | prev();
53 | break;
54 | case 'next':
55 | next();
56 | break;
57 | case 'first':
58 | setCurrent(0);
59 | break;
60 | default:
61 | break;
62 | }
63 | };
64 |
65 | return (
66 |
67 | <>
68 |
69 | {steps.map((item) => (
70 |
71 | ))}
72 |
73 |
74 |
75 |
76 | >
77 |
78 | );
79 | });
80 |
--------------------------------------------------------------------------------
/src/pages/List/Base/index.module.less:
--------------------------------------------------------------------------------
1 | .toolBar {
2 | color: #666;
3 | margin-bottom: 20px;
4 | }
5 |
--------------------------------------------------------------------------------
/src/pages/List/Card/components/ProductCard.module.less:
--------------------------------------------------------------------------------
1 | .panel {
2 | cursor: pointer;
3 | }
4 |
5 | .name {
6 | margin-bottom: 8px;
7 | font-size: 16px;
8 | font-weight: 400;
9 | color: var(--td-text-color-primary);
10 | }
11 |
12 | .description {
13 | color: var(--td-text-color-secondary);
14 | font-size: 12px;
15 | line-height: 20px;
16 | overflow: hidden;
17 | text-overflow: ellipsis;
18 | display: -webkit-box;
19 | -webkit-line-clamp: 2;
20 | -webkit-box-orient: vertical;
21 | height: 40px;
22 | }
23 |
24 | .footer {
25 | display: flex;
26 | justify-content: space-between;
27 | align-items: center;
28 | }
29 |
--------------------------------------------------------------------------------
/src/pages/List/Card/components/ProductCard.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Card, Avatar, Tag, Dropdown, Button } from 'tdesign-react';
3 | import { UserAvatarIcon, CalendarIcon, LaptopIcon, ShopIcon, ServiceIcon, Icon } from 'tdesign-icons-react';
4 | import { IProduct } from 'services/product';
5 | import Style from './ProductCard.module.less';
6 |
7 | const { Group: AvatarGroup } = Avatar;
8 | const icons = [UserAvatarIcon, CalendarIcon, LaptopIcon, ShopIcon, ServiceIcon];
9 |
10 | const CardIcon = React.memo(() => {
11 | const random = Math.floor(Math.random() * icons.length);
12 | const Icon = icons[random];
13 | return ;
14 | });
15 |
16 | const ProductCard = ({ product }: { product: IProduct }) => {
17 | const disabled = !product.isSetup;
18 | return (
19 |
25 | 已停用
26 |
27 | ) : (
28 | 已启用
29 | )
30 | }
31 | avatar={
32 |
33 |
34 |
35 | }
36 | footer={
37 |
38 |
39 | {String.fromCharCode(64 + product.type || 0)}
40 | +
41 |
42 |
55 |
58 |
59 |
60 | }
61 | >
62 | {product?.name}
63 | {product?.description}
64 |
65 | );
66 | };
67 |
68 | export default React.memo(ProductCard);
69 |
--------------------------------------------------------------------------------
/src/pages/List/Card/index.module.less:
--------------------------------------------------------------------------------
1 | .toolBar {
2 | display: flex;
3 | justify-content: space-between;
4 | .search {
5 | width: 342px;
6 | }
7 | }
8 | .cardList {
9 | margin-top: 14px;
10 | margin-bottom: 24px;
11 | }
12 |
13 | .pagination {
14 | padding: 16px;
15 | }
16 |
17 | .loading {
18 | display: flex;
19 | justify-content: center;
20 | margin: 20px 0;
21 | }
22 |
--------------------------------------------------------------------------------
/src/pages/List/Card/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { Row, Col, Button, Input, Pagination, Loading } from 'tdesign-react';
3 | import { SearchIcon } from 'tdesign-icons-react';
4 | import { useAppDispatch, useAppSelector } from 'modules/store';
5 | import { selectListCard, clearPageState, getList, switchPageLoading } from 'modules/list/card';
6 | import ProductCard from './components/ProductCard';
7 | import { PageInfo } from 'tdesign-react/es/pagination/type';
8 | import Style from './index.module.less';
9 |
10 | const CardList = () => {
11 | const dispatch = useAppDispatch();
12 | const pageState = useAppSelector(selectListCard);
13 |
14 | const pageInit = async () => {
15 | await dispatch(
16 | getList({
17 | pageSize: pageState.pageSize,
18 | current: 1,
19 | }),
20 | );
21 | await dispatch(switchPageLoading(false));
22 | };
23 |
24 | useEffect(() => {
25 | pageInit();
26 | return () => {
27 | clearPageState();
28 | };
29 | }, []);
30 |
31 | const onChange = async ({ current, pageSize }: PageInfo) => {
32 | await dispatch(
33 | getList({
34 | pageSize,
35 | current,
36 | }),
37 | );
38 | };
39 |
40 | const handlePageSizeChange = async (pageSize: number, { current }: PageInfo) => {
41 | await dispatch(
42 | getList({
43 | pageSize,
44 | current,
45 | }),
46 | );
47 | };
48 |
49 | return (
50 |
51 |
52 |
53 | } placeholder='请输入你需要搜索的内容' />
54 |
55 | {pageState.pageLoading ? (
56 |
57 |
58 |
59 | ) : (
60 | <>
61 |
62 |
63 | {pageState?.productList?.map((product, index) => (
64 |
65 |
66 |
67 | ))}
68 |
69 |
70 |
78 | >
79 | )}
80 |
81 | );
82 | };
83 |
84 | export default React.memo(CardList);
85 |
--------------------------------------------------------------------------------
/src/pages/List/Select/components/SearchForm.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, memo } from 'react';
2 | import { Row, Col, Form, Input, Button, MessagePlugin, Select } from 'tdesign-react';
3 | import { CONTRACT_STATUS_OPTIONS, CONTRACT_TYPE_OPTIONS } from '../consts';
4 | import { FormInstanceFunctions, SubmitContext } from 'tdesign-react/es/form/type';
5 |
6 | const { FormItem } = Form;
7 |
8 | export type FormValueType = {
9 | name?: string;
10 | status?: string;
11 | number?: string;
12 | time?: string;
13 | type?: string;
14 | };
15 |
16 | export type SearchFormProps = {
17 | onCancel: () => void;
18 | onSubmit: (values: FormValueType) => Promise;
19 | };
20 |
21 | const SearchForm: React.FC = (props) => {
22 | const formRef = useRef();
23 | const onSubmit = (e: SubmitContext) => {
24 | if (e.validateResult === true) {
25 | MessagePlugin.info('提交成功');
26 | }
27 | const queryValue = formRef?.current?.getFieldsValue?.(true);
28 | console.log('form 数据', queryValue);
29 | };
30 |
31 | const onReset = () => {
32 | props.onCancel();
33 | MessagePlugin.info('重置成功');
34 | };
35 |
36 | return (
37 |
38 |
74 |
75 | );
76 | };
77 |
78 | export default memo(SearchForm);
79 |
--------------------------------------------------------------------------------
/src/pages/List/Select/consts.ts:
--------------------------------------------------------------------------------
1 | interface IOption {
2 | value: number | string;
3 | label: string;
4 | }
5 |
6 | // 合同状态枚举
7 | export const CONTRACT_STATUS = {
8 | FAIL: 0,
9 | AUDIT_PENDING: 1,
10 | EXEC_PENDING: 2,
11 | EXECUTING: 3,
12 | FINISH: 4,
13 | };
14 |
15 | export const CONTRACT_STATUS_OPTIONS: Array = [
16 | { value: CONTRACT_STATUS.FAIL, label: '审核失败' },
17 | { value: CONTRACT_STATUS.AUDIT_PENDING, label: '待审核' },
18 | { value: CONTRACT_STATUS.EXEC_PENDING, label: '待履行' },
19 | { value: CONTRACT_STATUS.EXECUTING, label: '审核成功' },
20 | { value: CONTRACT_STATUS.FINISH, label: '已完成' },
21 | ];
22 |
23 | // 合同类型枚举
24 | export const CONTRACT_TYPES = {
25 | MAIN: 0,
26 | SUB: 1,
27 | SUPPLEMENT: 2,
28 | };
29 |
30 | export const CONTRACT_TYPE_OPTIONS: Array = [
31 | { value: CONTRACT_TYPES.MAIN, label: '主合同' },
32 | { value: CONTRACT_TYPES.SUB, label: '子合同' },
33 | { value: CONTRACT_TYPES.SUPPLEMENT, label: '补充合同' },
34 | ];
35 |
--------------------------------------------------------------------------------
/src/pages/List/Select/index.module.less:
--------------------------------------------------------------------------------
1 | .list-common-table-query {
2 | margin-bottom: 30px;
3 | }
4 |
5 | .table-container {
6 | margin-top: 30px;
7 | }
8 | &-btn {
9 | margin-right: 10px;
10 | color: #29a4fb;
11 | cursor: pointer;
12 | }
13 |
--------------------------------------------------------------------------------
/src/pages/List/Tree/consts.ts:
--------------------------------------------------------------------------------
1 | export const treeList = [
2 | {
3 | label: '深圳总部',
4 | value: 0,
5 | children: [
6 | {
7 | label: '总办',
8 | value: '0-0',
9 | },
10 | {
11 | label: '市场部',
12 | value: '0-1',
13 | children: [
14 | {
15 | label: '采购1组',
16 | value: '0-1-0',
17 | },
18 | {
19 | label: '采购2组',
20 | value: '0-1-1',
21 | },
22 | ],
23 | },
24 | {
25 | label: '技术部',
26 | value: '0-2',
27 | },
28 | ],
29 | },
30 | {
31 | label: '北京总部',
32 | value: 1,
33 | children: [
34 | {
35 | label: '总办',
36 | value: '1-0',
37 | },
38 | {
39 | label: '市场部',
40 | value: '1-1',
41 | children: [
42 | {
43 | label: '采购1组',
44 | value: '1-1-0',
45 | },
46 | {
47 | label: '采购2组',
48 | value: '1-1-1',
49 | },
50 | ],
51 | },
52 | ],
53 | },
54 | {
55 | label: '上海总部',
56 | value: 2,
57 | children: [
58 | {
59 | label: '市场部',
60 | value: '2-0',
61 | },
62 | {
63 | label: '财务部',
64 | value: '2-1',
65 | children: [
66 | {
67 | label: '财务1组',
68 | value: '2-1-0',
69 | },
70 | {
71 | label: '财务2组',
72 | value: '2-1-1',
73 | },
74 | ],
75 | },
76 | ],
77 | },
78 | {
79 | label: '湖南',
80 | value: 3,
81 | },
82 | {
83 | label: '湖北',
84 | value: 4,
85 | },
86 | ];
87 |
--------------------------------------------------------------------------------
/src/pages/List/Tree/index.module.less:
--------------------------------------------------------------------------------
1 | .content {
2 | display: flex;
3 | }
4 |
5 | .treeContent {
6 | width: 200px;
7 | padding: 30px 32px;
8 | border-right: 1px solid var(--td-border-level-1-color);
9 | }
10 |
11 | .search {
12 | width: 150px;
13 | }
14 |
15 | .tableContent {
16 | padding: 30px 32px;
17 | width: calc(100% - 200px);
18 | }
19 |
--------------------------------------------------------------------------------
/src/pages/List/Tree/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Input, Tree } from 'tdesign-react';
3 | import { SearchIcon } from 'tdesign-icons-react';
4 | import classnames from 'classnames';
5 | import { SelectTable } from '../Select';
6 | import { treeList } from './consts';
7 | import CommonStyle from 'styles/common.module.less';
8 | import Style from './index.module.less';
9 |
10 | const TreeTable: React.FC = () => (
11 |
12 |
13 | } placeholder='请输入关键词' />
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 |
22 | export default TreeTable;
23 |
--------------------------------------------------------------------------------
/src/pages/Login/components/Header/index.module.less:
--------------------------------------------------------------------------------
1 | .loginHeader {
2 | height: 64px;
3 | padding: 0 24px;
4 | display: flex;
5 | justify-content: space-between;
6 | align-items: center;
7 | backdrop-filter: blur(5px);
8 | color: var(--td-text-color-primary);
9 | }
10 |
11 | .logo {
12 | width: 188px;
13 | height: 64px;
14 | }
15 |
16 | .operationsContainer {
17 | display: flex;
18 | align-items: center;
19 | .t-button {
20 | margin-left: 16px;
21 | }
22 | }
23 |
24 | .operationsButton {
25 | margin-left: 16px;
26 | }
27 |
28 | .icon {
29 | height: 20px;
30 | width: 20px;
31 | padding: 6px;
32 | box-sizing: content-box;
33 |
34 | &:hover {
35 | cursor: pointer;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/pages/Login/components/Header/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Button } from 'tdesign-react';
3 | import { LogoGithubIcon, HelpCircleIcon, SettingIcon } from 'tdesign-icons-react';
4 | import { useAppDispatch } from 'modules/store';
5 | import { toggleSetting } from 'modules/global';
6 |
7 | import LogoFullIcon from 'assets/svg/assets-logo-full.svg?component';
8 | import Style from './index.module.less';
9 |
10 | export default function Header() {
11 | const dispatch = useAppDispatch();
12 |
13 | const navToGitHub = () => {
14 | window.open('https://github.com/tencent/tdesign-react-starter');
15 | };
16 |
17 | const navToHelper = () => {
18 | window.open('http://tdesign.tencent.com/starter/docs/react/get-started');
19 | };
20 |
21 | const toggleSettingPanel = () => {
22 | dispatch(toggleSetting());
23 | };
24 |
25 | return (
26 |
60 | );
61 | }
62 |
--------------------------------------------------------------------------------
/src/pages/Login/components/Login/index.module.less:
--------------------------------------------------------------------------------
1 | .itemContainer {
2 | width: 400px;
3 | margin-top: 48px;
4 | }
5 |
6 | .checkContainer {
7 | display: flex;
8 | align-items: center;
9 | font-size: 14px;
10 | color: var(--td-text-color-secondary);
11 |
12 | &.rememberPwd {
13 | margin-bottom: 16px;
14 | justify-content: space-between;
15 | }
16 |
17 | :global {
18 | .t-checkbox__label {
19 | color: var(--td-text-color-secondary);
20 | }
21 |
22 | span {
23 | color: var(--td-brand-color);
24 |
25 | &:hover {
26 | cursor: pointer;
27 | }
28 | }
29 | }
30 | }
31 |
32 | .tipContainer {
33 | width: 192px;
34 | margin-bottom: 16px;
35 | font-size: 14px;
36 | display: flex;
37 | justify-content: space-between;
38 |
39 | :global {
40 | .tip {
41 | color: var(--td-text-color-primary);
42 | }
43 |
44 | .refresh {
45 | display: flex;
46 | align-items: center;
47 | color: var(--td-brand-color);
48 |
49 | .t-icon {
50 | font-size: 14px;
51 | margin-left: 4px;
52 | }
53 |
54 | &:hover {
55 | cursor: pointer;
56 | }
57 | }
58 | }
59 | }
60 |
61 | .checkContainerTip {
62 | float: right;
63 | font-size: 14px;
64 | color: var(--td-brand-color);
65 | }
66 |
67 | .verificationBtn {
68 | flex-shrink: 0;
69 | width: 102px;
70 | height: 40px;
71 | margin-left: 11px;
72 | }
73 |
74 | .btnContainer {
75 | margin-top: 48px;
76 | }
77 |
78 | .switchContainer {
79 | margin-top: 24px;
80 |
81 | :global {
82 | .tip {
83 | font-size: 14px;
84 | color: var(--td-brand-color);
85 | cursor: pointer;
86 | display: inline-flex;
87 | align-items: center;
88 | margin-right: 14px;
89 |
90 | &:not(:last-child) {
91 | &::after {
92 | content: '';
93 | display: block;
94 | width: 1px;
95 | height: 12px;
96 | background: var(--td-gray-color-3);
97 | margin-left: 14px;
98 | }
99 | }
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/pages/Login/components/Register/index.module.less:
--------------------------------------------------------------------------------
1 | .itemContainer {
2 | width: 400px;
3 | margin-top: 48px;
4 | }
5 |
6 | .verificationBtn {
7 | flex-shrink: 0;
8 | width: 102px;
9 | height: 40px;
10 | margin-left: 11px;
11 | }
12 |
13 | .switchContainer {
14 | margin-top: 24px;
15 | }
16 |
17 | .switchTip {
18 | font-size: 14px;
19 | color: var(--td-brand-color);
20 | cursor: pointer;
21 | display: inline-flex;
22 | align-items: center;
23 | margin-right: 14px;
24 | }
25 |
26 | .checkContainer {
27 | font-size: 14px;
28 | color: var(--td-text-color-secondary);
29 | :global {
30 | .tip {
31 | float: right;
32 | font-size: 14px;
33 | color: var(--td-brand-color);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/pages/Login/hooks/useCountDown.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useRef } from 'react';
2 |
3 | const useCountdown = (duration: number) => {
4 | const [countdown, setCountdown] = useState(0);
5 | const [isSetup, toggleSetup] = useState(false);
6 | const timer = useRef();
7 |
8 | useEffect(() => {
9 | if (isSetup) {
10 | setCountdown(duration);
11 | timer.current = setInterval(() => {
12 | setCountdown((current) => current - 1);
13 | }, 1000);
14 | } else clearInterval(timer.current as NodeJS.Timeout);
15 | }, [isSetup]);
16 |
17 | useEffect(() => {
18 | if (countdown === 0) {
19 | toggleSetup(false);
20 | }
21 | }, [countdown]);
22 |
23 | useEffect(() => () => clearInterval(timer.current as NodeJS.Timeout), []);
24 |
25 | function setupCountdown() {
26 | toggleSetup(true);
27 | }
28 |
29 | return { countdown, setupCountdown };
30 | };
31 |
32 | export default useCountdown;
33 |
--------------------------------------------------------------------------------
/src/pages/Login/index.module.less:
--------------------------------------------------------------------------------
1 | .loginWrapper {
2 | height: 100vh;
3 | display: flex;
4 | flex-direction: column;
5 | background-size: cover;
6 | background-position: 100%;
7 | position: relative;
8 | &.dark {
9 | background-color: var(--td-bg-color-page);
10 | background-image: url('assets/image/assets-login-bg-black.png');
11 | }
12 | &.light {
13 | background-color: white;
14 | background-image: url('assets/image/assets-login-bg-white.png');
15 | }
16 | }
17 |
18 | .loginContainer {
19 | position: absolute;
20 | top: 22%;
21 | left: 5%;
22 | min-height: 500px;
23 | line-height: 22px;
24 | }
25 |
26 | .title {
27 | font-size: 36px;
28 | line-height: 44px;
29 | color: var(--td-text-color-primary);
30 | margin-top: 4px;
31 | margin-bottom: 0;
32 | }
33 |
34 | .subTitle {
35 | margin-top: 16px;
36 | }
37 |
38 | .tip {
39 | display: inline-block;
40 | margin-right: 8px;
41 | font-size: 14px;
42 | margin-top: 0;
43 | margin-bottom: 0;
44 |
45 | &.registerTip {
46 | color: var(--td-text-color-secondary);
47 | }
48 |
49 | &.loginTip {
50 | color: var(--td-text-color-primary);
51 | cursor: pointer;
52 | }
53 | }
54 |
55 | .copyright {
56 | font-size: 14px;
57 | position: absolute;
58 | left: 5%;
59 | bottom: 64px;
60 | color: var(--td-text-color-secondary);
61 | }
62 |
63 | @media screen and (max-height: 762px) {
64 | .copyright {
65 | display: none;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/pages/Login/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo, useState } from 'react';
2 | import classNames from 'classnames';
3 | import Login from './components/Login';
4 | import Register from './components/Register';
5 | import LoginHeader from './components/Header';
6 | import { useAppSelector } from 'modules/store';
7 | import { selectGlobal } from 'modules/global';
8 |
9 | import Style from './index.module.less';
10 |
11 | export default memo(() => {
12 | const [type, setType] = useState('login');
13 | const globalState = useAppSelector(selectGlobal);
14 | const { theme } = globalState;
15 | const handleSwitchLoginType = () => {
16 | setType(type === 'register' ? 'login' : 'register');
17 | };
18 |
19 | return (
20 |
23 |
24 |
25 |
26 |
登录到
27 |
TDesign Starter
28 |
29 |
30 | {type === 'register' ? '已有账号?' : '没有账号吗?'}
31 |
32 |
33 | {type === 'register' ? '登录' : '注册新账号'}
34 |
35 |
36 |
37 | {type === 'login' ?
:
}
38 |
39 |
40 |
41 | );
42 | });
43 |
--------------------------------------------------------------------------------
/src/pages/Result/403/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ErrorPage from 'components/ErrorPage';
3 |
4 | const UnAuthorized = () => ;
5 |
6 | export default React.memo(UnAuthorized);
7 |
--------------------------------------------------------------------------------
/src/pages/Result/404/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ErrorPage from 'components/ErrorPage';
3 |
4 | const NotFound = () => ;
5 |
6 | export default React.memo(NotFound);
7 |
--------------------------------------------------------------------------------
/src/pages/Result/500/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ErrorPage from 'components/ErrorPage';
3 |
4 | const ServerError = () => ;
5 |
6 | export default React.memo(ServerError);
7 |
--------------------------------------------------------------------------------
/src/pages/Result/BrowserIncompatible/index.module.less:
--------------------------------------------------------------------------------
1 | .Content {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: center;
5 | align-items: center;
6 | height: 75vh;
7 | padding: 24px;
8 | min-height: 400px;
9 | color: var(--td-brand-color);
10 |
11 | .title {
12 | font-weight: 500;
13 | font-size: 20px;
14 | line-height: 28px;
15 | margin-top: 8px;
16 | color: var(--td-text-color-primary);
17 | }
18 | .description {
19 | margin: 8px 0 32px;
20 | font-size: 14px;
21 | line-height: 22px;
22 | color: var(--td-text-color-secondary);
23 | }
24 |
25 | .rightButton {
26 | margin-left: 8px;
27 | }
28 |
29 | .resultSlotContainer {
30 | position: relative;
31 | display: flex;
32 | flex-direction: column;
33 | align-items: center;
34 | justify-content: space-between;
35 | color: var(--td-text-color-secondary);
36 |
37 | .recommendContainer {
38 | position: absolute;
39 | display: flex;
40 | align-items: center;
41 | justify-content: space-between;
42 | top: 175px;
43 | padding: 24px 48px;
44 | width: 640px;
45 | background: var(--td-bg-color-container);
46 | box-shadow: 0 1px 2px var(--td-shadow-1);
47 | border-radius: 3px;
48 |
49 | .recommendBrowser {
50 | display: flex;
51 |
52 | img {
53 | width: 36.67px;
54 | height: 36.67px;
55 | }
56 | }
57 | .recommendBrowser > div {
58 | display: flex;
59 | flex-direction: column;
60 | align-items: center;
61 | justify-content: space-between;
62 | height: 70px;
63 | }
64 | .recommendBrowser > div + div {
65 | margin-left: 40px;
66 | }
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/pages/Result/BrowserIncompatible/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { Button } from 'tdesign-react';
3 |
4 | import BrowserIncompatibleIcon from 'assets/svg/assets-result-browser-incompatible.svg?component';
5 | import style from './index.module.less';
6 |
7 | const BrowserIncompatible = () => (
8 |
9 |
10 |
浏览器版本低
11 |
抱歉,您正在使用的浏览器版本过低,无法打开当前网页。
12 |
13 |
14 |
15 |
16 |
TDesign Starter 推荐以下主流浏览器
17 |
18 |
19 |

20 |
Chrome
21 |
22 |
23 |

24 |
QQ Browser
25 |
26 |
27 |
28 |
29 |
30 | );
31 |
32 | export default memo(BrowserIncompatible);
33 |
--------------------------------------------------------------------------------
/src/pages/Result/Fail/index.module.less:
--------------------------------------------------------------------------------
1 | .Content {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: center;
5 | align-items: center;
6 | height: 75vh;
7 |
8 | .icon {
9 | font-size: 72px;
10 | color: var(--td-text-color-secondary);
11 | }
12 |
13 | .title {
14 | font-weight: 500;
15 | font-size: 20px;
16 | line-height: 28px;
17 | margin-top: 28px;
18 | color: var(--td-text-color-primary);
19 | }
20 | .description {
21 | margin: 8px 0 32px;
22 | font-size: 14px;
23 | line-height: 22px;
24 | color: var(--td-text-color-secondary);
25 | }
26 |
27 | .rightButton {
28 | margin-left: 8px;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/pages/Result/Fail/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { Button } from 'tdesign-react';
3 | import { ErrorCircleIcon } from 'tdesign-icons-react';
4 |
5 | import style from './index.module.less';
6 |
7 | const Fail = () => (
8 |
9 |
10 |
创建失败
11 |
抱歉,您的项目创建失败,企业微信联系检查创建者权限,或返回修改。
12 |
13 |
14 |
17 |
18 |
19 | );
20 |
21 | export default memo(Fail);
22 |
--------------------------------------------------------------------------------
/src/pages/Result/Maintenance/index.module.less:
--------------------------------------------------------------------------------
1 | .Content {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: center;
5 | align-items: center;
6 | height: 75vh;
7 | padding: 24px;
8 | min-height: 400px;
9 | color: var(--td-brand-color);
10 |
11 | .title {
12 | font-weight: 500;
13 | font-size: 20px;
14 | line-height: 28px;
15 | margin-top: 8px;
16 | color: var(--td-text-color-primary);
17 | }
18 | .description {
19 | margin: 8px 0 32px;
20 | font-size: 14px;
21 | line-height: 22px;
22 | color: var(--td-text-color-secondary);
23 | }
24 |
25 | .rightButton {
26 | margin-left: 8px;
27 | }
28 |
29 | .resultSlotContainer {
30 | position: relative;
31 | display: flex;
32 | flex-direction: column;
33 | align-items: center;
34 | justify-content: space-between;
35 | color: var(--td-text-color-secondary);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/pages/Result/Maintenance/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { Button } from 'tdesign-react';
3 |
4 | import MaintenanceIcon from 'assets/svg/assets-result-maintenance.svg?component';
5 | import style from './index.module.less';
6 |
7 | const BrowserIncompatible = () => (
8 |
9 |
10 |
系统维护中
11 |
系统维护中,请稍后再试。
12 |
13 |
14 |
15 |
16 |
17 | );
18 |
19 | export default memo(BrowserIncompatible);
20 |
--------------------------------------------------------------------------------
/src/pages/Result/NetworkError/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { Button } from 'tdesign-react';
3 |
4 | import NetworkErrorIcon from 'assets/svg/assets-result-network-error.svg?component';
5 | import style from '../index.module.less';
6 |
7 | const NetworkError = () => (
8 |
9 |
10 |
网络异常
11 |
网络异常,请稍后再试
12 |
13 |
14 |
17 |
18 |
19 | );
20 |
21 | export default memo(NetworkError);
22 |
--------------------------------------------------------------------------------
/src/pages/Result/Success/index.module.less:
--------------------------------------------------------------------------------
1 | .Content {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: center;
5 | align-items: center;
6 | height: 75vh;
7 |
8 | .icon {
9 | font-size: 72px;
10 | color: var(--td-success-color);
11 | }
12 |
13 | .title {
14 | font-weight: 500;
15 | font-size: 20px;
16 | line-height: 28px;
17 | margin-top: 28px;
18 | color: var(--td-text-color-primary);
19 | }
20 | .description {
21 | margin: 8px 0 32px;
22 | font-size: 14px;
23 | line-height: 22px;
24 | color: var(--td-text-color-secondary);
25 | }
26 |
27 | .rightButton {
28 | margin-left: 8px;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/pages/Result/Success/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { Button } from 'tdesign-react';
3 | import { CheckCircleIcon } from 'tdesign-icons-react';
4 |
5 | import style from './index.module.less';
6 |
7 | const Success = () => (
8 |
9 |
10 |
项目已创建成功
11 |
可以联系负责人分发应用
12 |
13 |
14 |
17 |
18 |
19 | );
20 |
21 | export default memo(Success);
22 |
--------------------------------------------------------------------------------
/src/pages/Result/index.module.less:
--------------------------------------------------------------------------------
1 | .Content {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: center;
5 | align-items: center;
6 | height: 75vh;
7 | padding: 24px;
8 | min-height: 400px;
9 | color: var(--td-brand-color);
10 |
11 | .title {
12 | font-weight: 500;
13 | font-size: 20px;
14 | line-height: 28px;
15 | margin-top: 8px;
16 | color: var(--td-text-color-primary);
17 | }
18 | .description {
19 | margin: 8px 0 32px;
20 | font-size: 14px;
21 | line-height: 22px;
22 | color: var(--td-text-color-secondary);
23 | }
24 |
25 | .rightButton {
26 | margin-left: 8px;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/pages/User/chart.ts:
--------------------------------------------------------------------------------
1 | export const visitData = {
2 | tooltip: {
3 | trigger: 'axis',
4 | },
5 | legend: {
6 | data: ['杯子', '茶叶', '蜂蜜', '面粉'],
7 | },
8 | grid: {
9 | left: '3%',
10 | right: '4%',
11 | bottom: '3%',
12 | containLabel: true,
13 | },
14 | toolbox: {
15 | feature: {
16 | saveAsImage: {},
17 | },
18 | },
19 | xAxis: {
20 | type: 'category',
21 | boundaryGap: false,
22 | data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
23 | },
24 | yAxis: {
25 | type: 'value',
26 | },
27 | series: [
28 | {
29 | name: '杯子',
30 | type: 'line',
31 | data: [21, 99, 56, 66, 55, 7, 83],
32 | },
33 | {
34 | name: '茶叶',
35 | type: 'line',
36 | data: [84, 30, 70, 14, 19, 75, 73],
37 | },
38 | {
39 | name: '蜂蜜',
40 | type: 'line',
41 | data: [57, 3, 25, 13, 49, 80, 11],
42 | },
43 | {
44 | name: '面粉',
45 | type: 'line',
46 | data: [8, 85, 2, 77, 10, 65, 90],
47 | },
48 | ],
49 | };
50 |
--------------------------------------------------------------------------------
/src/pages/User/consts.ts:
--------------------------------------------------------------------------------
1 | // 团队成员数据
2 | export const TEAMS = [
3 | {
4 | id: 1,
5 | name: 'Lovellzhong 钟某某',
6 | position: '直客销售 港澳拓展组员工',
7 | avatar: 'https://tdesign.gtimg.com/starter/avatar-user-a.png',
8 | },
9 | {
10 | id: 2,
11 | name: 'Jiajingwang 彭某某',
12 | position: '前端开发 前台研发组员工',
13 | avatar: 'https://tdesign.gtimg.com/starter/avatar-user-b.png',
14 | },
15 | {
16 | id: 3,
17 | name: 'cruisezhang 林某某',
18 | position: '技术产品 产品组员工',
19 | avatar: 'https://tdesign.gtimg.com/starter/avatar-user-c.png',
20 | },
21 | {
22 | id: 4,
23 | name: 'Lovellzhang 商某某',
24 | position: '产品运营 港澳拓展组员工',
25 | avatar: 'https://tdesign.gtimg.com/starter/avatar-user-d.png',
26 | },
27 | ];
28 |
--------------------------------------------------------------------------------
/src/pages/User/index.module.less:
--------------------------------------------------------------------------------
1 | // 欢迎语
2 | .welcome {
3 | .name {
4 | font-size: 20px;
5 | color: var(--td-text-color-primary);
6 | }
7 |
8 | .regular {
9 | margin-right: 15px;
10 | font-size: 14px;
11 | }
12 |
13 | .logo {
14 | width: 180px;
15 | }
16 | }
17 |
18 | // 基本信息
19 | .userinfo {
20 | margin-top: 16px;
21 | .label {
22 | white-space: nowrap;
23 | text-overflow: ellipsis;
24 | overflow: hidden;
25 | line-height: 24px;
26 | margin: 20px 0 6px;
27 | font-size: 14px;
28 | color: var(--td-text-color-placeholder);
29 | }
30 |
31 | .value {
32 | white-space: nowrap;
33 | text-overflow: ellipsis;
34 | overflow: hidden;
35 | line-height: 40px;
36 | font-size: 14px;
37 | color: var(--td-text-color-primary);
38 | }
39 | }
40 |
41 | // 统计数据
42 | .statistics {
43 | margin-top: 16px;
44 | padding-top: 20px;
45 | }
46 |
47 | // 职位信息
48 | .postmsg {
49 | background: var(--td-brand-color);
50 | color: #fff;
51 | .avatar {
52 | width: 90px;
53 | height: 90px;
54 | font-size: 45px;
55 | position: relative;
56 | display: inline-flex;
57 | background: var(--td-brand-color-2);
58 | color: var(--td-text-color-brand);
59 | overflow: hidden;
60 | white-space: nowrap;
61 | vertical-align: middle;
62 | justify-content: center;
63 | align-items: center;
64 | box-sizing: content-box;
65 | border-radius: 50%;
66 | }
67 |
68 | .name {
69 | line-height: 37px;
70 | font-size: 20px;
71 | margin-top: 36px;
72 | }
73 |
74 | .position {
75 | line-height: 24px;
76 | font-size: 14px;
77 | margin-top: 8px;
78 | }
79 | }
80 |
81 | // 团队
82 | .teams {
83 | margin-top: 16px;
84 | :global {
85 | .t-list-item {
86 | padding: 15px 0;
87 | }
88 | }
89 | }
90 |
91 | // 服务产品
92 | .product {
93 | margin-top: 16px;
94 | .logo {
95 | width: 48px;
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import React, { lazy } from 'react';
2 | import { BrowserRouterProps } from 'react-router-dom';
3 | import dashboard from './modules/dashboard';
4 | import list from './modules/list';
5 | import form from './modules/form';
6 | import detail from './modules/detail';
7 | import result from './modules/result';
8 | import user from './modules/user';
9 | import login from './modules/login';
10 | import otherRoutes from './modules/others';
11 |
12 | export interface IRouter {
13 | path: string;
14 | redirect?: string;
15 | Component?: React.FC | (() => any);
16 | /**
17 | * 当前路由是否全屏显示
18 | */
19 | isFullPage?: boolean;
20 | /**
21 | * meta未赋值 路由不显示到菜单中
22 | */
23 | meta?: {
24 | title?: string;
25 | Icon?: React.FC;
26 | /**
27 | * 侧边栏隐藏该路由
28 | */
29 | hidden?: boolean;
30 | /**
31 | * 单层路由
32 | */
33 | single?: boolean;
34 | };
35 | children?: IRouter[];
36 | }
37 |
38 | const routes: IRouter[] = [
39 | {
40 | path: '/login',
41 | Component: lazy(() => import('pages/Login')),
42 | isFullPage: true,
43 | meta: {
44 | hidden: true,
45 | },
46 | },
47 | {
48 | path: '/',
49 | redirect: '/dashboard/base',
50 | },
51 | ];
52 |
53 | const allRoutes = [...routes, ...dashboard, ...list, ...form, ...detail, ...result, ...user, ...login, ...otherRoutes];
54 |
55 | export default allRoutes;
56 |
--------------------------------------------------------------------------------
/src/router/modules/dashboard.ts:
--------------------------------------------------------------------------------
1 | import { lazy } from 'react';
2 | import { DashboardIcon } from 'tdesign-icons-react';
3 | import { IRouter } from '../index';
4 |
5 | const dashboard: IRouter[] = [
6 | {
7 | path: '/dashboard',
8 | meta: {
9 | title: '统计报表',
10 | Icon: DashboardIcon,
11 | },
12 | children: [
13 | {
14 | path: 'base',
15 | Component: lazy(() => import('pages/Dashboard/Base')),
16 | meta: {
17 | title: '概览仪表盘',
18 | },
19 | },
20 | {
21 | path: 'detail',
22 | Component: lazy(() => import('pages/Dashboard/Detail')),
23 | meta: {
24 | title: '统计报表',
25 | },
26 | },
27 | ],
28 | },
29 | ];
30 |
31 | export default dashboard;
32 |
--------------------------------------------------------------------------------
/src/router/modules/detail.ts:
--------------------------------------------------------------------------------
1 | import { lazy } from 'react';
2 | import { LayersIcon } from 'tdesign-icons-react';
3 | import { IRouter } from '../index';
4 |
5 | const result: IRouter[] = [
6 | {
7 | path: '/detail',
8 | meta: {
9 | title: '详情页',
10 | Icon: LayersIcon,
11 | },
12 | children: [
13 | {
14 | path: 'base',
15 | Component: lazy(() => import('pages/Detail/Base')),
16 | meta: {
17 | title: '基础详情页',
18 | },
19 | },
20 | {
21 | path: 'advanced',
22 | Component: lazy(() => import('pages/Detail/Advanced')),
23 | meta: { title: '多卡片详情页' },
24 | },
25 | {
26 | path: 'deploy',
27 | Component: lazy(() => import('pages/Detail/Deploy')),
28 | meta: { title: '数据详情页' },
29 | },
30 | {
31 | path: 'secondary',
32 | Component: lazy(() => import('pages/Detail/Secondary')),
33 | meta: { title: '二级详情页' },
34 | },
35 | ],
36 | },
37 | ];
38 |
39 | export default result;
40 |
--------------------------------------------------------------------------------
/src/router/modules/form.ts:
--------------------------------------------------------------------------------
1 | import { lazy } from 'react';
2 | import { QueueIcon } from 'tdesign-icons-react';
3 | import { IRouter } from '../index';
4 |
5 | const result: IRouter[] = [
6 | {
7 | path: '/form',
8 | meta: {
9 | title: '表单类',
10 | Icon: QueueIcon,
11 | },
12 | children: [
13 | {
14 | path: 'base',
15 | Component: lazy(() => import('pages/Form/Base')),
16 | meta: {
17 | title: '基础表单页',
18 | },
19 | },
20 | {
21 | path: 'step',
22 | Component: lazy(() => import('pages/Form/Step')),
23 | meta: { title: '分步表单页' },
24 | },
25 | ],
26 | },
27 | ];
28 |
29 | export default result;
30 |
--------------------------------------------------------------------------------
/src/router/modules/list.ts:
--------------------------------------------------------------------------------
1 | import { lazy } from 'react';
2 | import { ViewModuleIcon } from 'tdesign-icons-react';
3 | import { IRouter } from '../index';
4 |
5 | const result: IRouter[] = [
6 | {
7 | path: '/list',
8 | meta: {
9 | title: '列表页',
10 | Icon: ViewModuleIcon,
11 | },
12 | children: [
13 | {
14 | path: 'base',
15 | Component: lazy(() => import('pages/List/Base')),
16 | meta: {
17 | title: '基础列表页',
18 | },
19 | },
20 | {
21 | path: 'card',
22 | Component: lazy(() => import('pages/List/Card')),
23 | meta: {
24 | title: '卡片列表页',
25 | },
26 | },
27 | {
28 | path: 'select',
29 | Component: lazy(() => import('pages/List/Select')),
30 | meta: { title: '筛选列表页' },
31 | },
32 | {
33 | path: 'tree',
34 | Component: lazy(() => import('pages/List/Tree')),
35 | meta: { title: '树状筛选列表页' },
36 | },
37 | ],
38 | },
39 | ];
40 |
41 | export default result;
42 |
--------------------------------------------------------------------------------
/src/router/modules/login.ts:
--------------------------------------------------------------------------------
1 | import { lazy } from 'react';
2 | import { LogoutIcon } from 'tdesign-icons-react';
3 | import { IRouter } from '../index';
4 |
5 | const result: IRouter[] = [
6 | {
7 | path: '/login',
8 | meta: {
9 | title: '登录页',
10 | Icon: LogoutIcon,
11 | },
12 | children: [
13 | {
14 | path: 'index',
15 | Component: lazy(() => import('pages/Login')),
16 | isFullPage: true,
17 | meta: {
18 | title: '登录中心',
19 | },
20 | },
21 | ],
22 | },
23 | ];
24 |
25 | export default result;
26 |
--------------------------------------------------------------------------------
/src/router/modules/others.ts:
--------------------------------------------------------------------------------
1 | import { lazy } from 'react';
2 | import { IRouter } from '../index';
3 |
4 | const otherRoutes: IRouter[] = [
5 | {
6 | path: '/403',
7 | Component: lazy(() => import('pages/Result/403')),
8 | },
9 | {
10 | path: '/500',
11 | Component: lazy(() => import('pages/Result/500')),
12 | },
13 | {
14 | path: '*',
15 | Component: lazy(() => import('pages/Result/404')),
16 | },
17 | ];
18 |
19 | export default otherRoutes;
20 |
--------------------------------------------------------------------------------
/src/router/modules/result.ts:
--------------------------------------------------------------------------------
1 | import { lazy } from 'react';
2 | import { CheckCircleIcon } from 'tdesign-icons-react';
3 | import { IRouter } from '../index';
4 |
5 | const result: IRouter[] = [
6 | {
7 | path: '/result',
8 | meta: {
9 | title: '结果页',
10 | Icon: CheckCircleIcon,
11 | },
12 | children: [
13 | {
14 | path: 'success',
15 | Component: lazy(() => import('pages/Result/Success')),
16 | meta: {
17 | title: '成功页',
18 | },
19 | },
20 | {
21 | path: 'fail',
22 | Component: lazy(() => import('pages/Result/Fail')),
23 | meta: {
24 | title: '失败页',
25 | },
26 | },
27 | {
28 | path: 'network-error',
29 | Component: lazy(() => import('pages/Result/NetworkError')),
30 | meta: {
31 | title: '网络异常',
32 | },
33 | },
34 | {
35 | path: '403',
36 | Component: lazy(() => import('pages/Result/403')),
37 | meta: {
38 | title: '无权限',
39 | },
40 | },
41 | {
42 | path: '404',
43 | Component: lazy(() => import('pages/Result/404')),
44 | meta: {
45 | title: '访问页面不存在页',
46 | },
47 | },
48 | {
49 | path: '500',
50 | Component: lazy(() => import('pages/Result/500')),
51 | meta: {
52 | title: '服务器出错页',
53 | },
54 | },
55 | {
56 | path: 'browser-incompatible',
57 | Component: lazy(() => import('pages/Result/BrowserIncompatible')),
58 | meta: {
59 | title: '浏览器不兼容页',
60 | },
61 | },
62 | {
63 | path: 'maintenance',
64 | Component: lazy(() => import('pages/Result/Maintenance')),
65 | meta: {
66 | title: '系统维护页',
67 | },
68 | },
69 | ],
70 | },
71 | ];
72 |
73 | export default result;
74 |
--------------------------------------------------------------------------------
/src/router/modules/user.ts:
--------------------------------------------------------------------------------
1 | import { lazy } from 'react';
2 | import { UserCircleIcon } from 'tdesign-icons-react';
3 | import { IRouter } from '../index';
4 |
5 | const result: IRouter[] = [
6 | {
7 | path: '/user',
8 | meta: {
9 | title: '个人页',
10 | Icon: UserCircleIcon,
11 | },
12 | children: [
13 | {
14 | path: 'index',
15 | Component: lazy(() => import('pages/User')),
16 | meta: {
17 | title: '个人中心',
18 | },
19 | },
20 | ],
21 | },
22 | ];
23 |
24 | export default result;
25 |
--------------------------------------------------------------------------------
/src/services/contract.ts:
--------------------------------------------------------------------------------
1 | import request from 'utils/request';
2 |
3 | export interface IContract {
4 | adminName: string;
5 | amount: string;
6 | contractType: number;
7 | index: number;
8 | name: string;
9 | no: string;
10 | paymentType: 1 | 2;
11 | status: number;
12 | updateTime: string;
13 | }
14 |
15 | interface IResult {
16 | list: IContract[];
17 | }
18 |
19 | interface IParams {
20 | pageSize: number;
21 | current: number;
22 | }
23 |
24 | export const getContractList = async (params: IParams) => {
25 | const result = await request.get('/api/get-list');
26 |
27 | // 模拟接口分页
28 | let list = result?.data?.list || [];
29 | const total = list.length;
30 | list = list.splice(params.pageSize * (params.current - 1), params.pageSize);
31 | return {
32 | list,
33 | total,
34 | };
35 | };
36 |
--------------------------------------------------------------------------------
/src/services/product.ts:
--------------------------------------------------------------------------------
1 | import request from 'utils/request';
2 |
3 | export interface IProduct {
4 | banner: string;
5 | description: string;
6 | index: number;
7 | isSetup: boolean;
8 | name: string;
9 | type: number;
10 | }
11 |
12 | interface IResult {
13 | list: IProduct[];
14 | }
15 |
16 | interface IParams {
17 | pageSize: number;
18 | current: number;
19 | }
20 |
21 | export const getProductList = async (params: IParams) => {
22 | const result = await request.get('api/get-card-list');
23 |
24 | // 模拟接口分页
25 | let list = result?.data?.list || [];
26 | const total = list.length;
27 | list = list.splice(params.pageSize * (params.current - 1), params.pageSize);
28 | return {
29 | list,
30 | total,
31 | };
32 | };
33 |
--------------------------------------------------------------------------------
/src/styles/common.module.less:
--------------------------------------------------------------------------------
1 | .pageWithPadding {
2 | padding: 30px 32px;
3 | }
4 |
5 | .pageWithColor {
6 | background: var(--td-bg-color-container);
7 | box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1);
8 | border-radius: 3px;
9 | }
10 |
--------------------------------------------------------------------------------
/src/styles/index.less:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',
4 | 'Droid Sans', 'Helvetica Neue', sans-serif;
5 | -webkit-font-smoothing: antialiased;
6 | -moz-osx-font-smoothing: grayscale;
7 | }
8 |
9 | html,
10 | body,
11 | #app {
12 | height: 100%;
13 | }
14 |
15 | code {
16 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
17 | }
18 |
--------------------------------------------------------------------------------
/src/types/index.d.ts:
--------------------------------------------------------------------------------
1 | export enum ETheme {
2 | light = 'light',
3 | dark = 'dark',
4 | }
5 |
--------------------------------------------------------------------------------
/src/utils/chart.ts:
--------------------------------------------------------------------------------
1 | // 获取 chart 的 mock 数据
2 | import dayjs, { Dayjs } from 'dayjs';
3 |
4 | const RECENT_7_DAYS: [Dayjs, Dayjs] = [dayjs().subtract(7, 'day'), dayjs().subtract(1, 'day')];
5 | export const ONE_WEEK_LIST = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
6 |
7 | export const getRandomInt = (num = 100): number => {
8 | const resultNum = Number((Math.random() * num).toFixed(0));
9 | return resultNum <= 1 ? 1 : resultNum;
10 | };
11 |
12 | type ChartValue = number | string;
13 |
14 | export function getTimeArray(dateTime: string[] = [], divideNum = 10, format = 'MM-DD'): string[] {
15 | const timeArray = [];
16 | if (dateTime.length === 0) {
17 | dateTime.push(...RECENT_7_DAYS.map((item) => item.format(format)));
18 | }
19 | for (let i = 0; i < divideNum; i++) {
20 | const dateAbsTime: number = (new Date(dateTime[1]).getTime() - new Date(dateTime[0]).getTime()) / divideNum;
21 | const timeNode: number = new Date(dateTime[0]).getTime() + dateAbsTime * i;
22 | timeArray.push(dayjs(timeNode).format(format));
23 | }
24 |
25 | return timeArray;
26 | }
27 |
28 | export const getChartDataSet = (dateTime: Array = [], divideNum = 10): ChartValue[][] => {
29 | const timeArray = getTimeArray(dateTime, divideNum);
30 | const inArray = [];
31 | const outArray = [];
32 | for (let index = 0; index < divideNum; index++) {
33 | inArray.push(getRandomInt().toString());
34 | outArray.push(getRandomInt().toString());
35 | }
36 |
37 | return [timeArray, inArray, outArray];
38 | };
39 |
--------------------------------------------------------------------------------
/src/utils/path.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 解析当前菜单对应的路由
3 | * @param path1
4 | * @param path2
5 | */
6 | export const resolve = (path1 = '', path2 = '') => {
7 | let separator = '/';
8 | if (path1.endsWith('/') || path2.startsWith('/')) {
9 | separator = '';
10 | }
11 | return `${path1}${separator}${path2}`;
12 | };
13 |
--------------------------------------------------------------------------------
/src/utils/request.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import proxy from '../configs/host';
3 |
4 | const env = import.meta.env.MODE || 'development';
5 | const API_HOST = proxy[env].API;
6 |
7 | const SUCCESS_CODE = 0;
8 | const TIMEOUT = 5000;
9 |
10 | export const instance = axios.create({
11 | baseURL: API_HOST,
12 | timeout: TIMEOUT,
13 | withCredentials: true,
14 | });
15 |
16 | instance.interceptors.response.use(
17 | // eslint-disable-next-line consistent-return
18 | (response) => {
19 | if (response.status === 200) {
20 | const { data } = response;
21 | if (data.code === SUCCESS_CODE) {
22 | return data;
23 | }
24 | return Promise.reject(data);
25 | }
26 | return Promise.reject(response?.data);
27 | },
28 | (e) => Promise.reject(e),
29 | );
30 |
31 | export default instance;
32 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "src",
4 | "target": "esnext",
5 | "lib": ["dom", "dom.iterable", "esnext"],
6 | "allowJs": true,
7 | "skipLibCheck": true,
8 | "esModuleInterop": true,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "module": "esnext",
14 | "moduleResolution": "node",
15 | "resolveJsonModule": true,
16 | "isolatedModules": true,
17 | "noEmit": true,
18 | "jsx": "react-jsx"
19 | },
20 | "include": ["src"]
21 | }
22 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import { loadEnv } from 'vite';
3 | import { viteMockServe } from 'vite-plugin-mock';
4 | import react from '@vitejs/plugin-react';
5 | import svgr from '@honkhonk/vite-plugin-svgr';
6 |
7 | const CWD = process.cwd();
8 |
9 | export default (params) => {
10 | const { mode } = params;
11 | const { VITE_BASE_URL } = loadEnv(mode, CWD);
12 |
13 | return {
14 | base: VITE_BASE_URL,
15 | resolve: {
16 | alias: {
17 | assets: path.resolve(__dirname, './src/assets'),
18 | components: path.resolve(__dirname, './src/components'),
19 | configs: path.resolve(__dirname, './src/configs'),
20 | layouts: path.resolve(__dirname, './src/layouts'),
21 | modules: path.resolve(__dirname, './src/modules'),
22 | pages: path.resolve(__dirname, './src/pages'),
23 | styles: path.resolve(__dirname, './src/styles'),
24 | utils: path.resolve(__dirname, './src/utils'),
25 | services: path.resolve(__dirname, './src/services'),
26 | router: path.resolve(__dirname, './src/router'),
27 | hooks: path.resolve(__dirname, './src/hooks'),
28 | types: path.resolve(__dirname, './src/types'),
29 | },
30 | },
31 |
32 | css: {
33 | preprocessorOptions: {
34 | less: {
35 | modifyVars: {
36 | // 如需自定义组件其他 token, 在此处配置
37 | },
38 | },
39 | },
40 | },
41 |
42 | plugins: [
43 | svgr(),
44 | react(),
45 | mode === 'mock' &&
46 | viteMockServe({
47 | mockPath: './mock',
48 | localEnabled: true,
49 | }),
50 | ],
51 |
52 | build: {
53 | cssCodeSplit: false,
54 | },
55 |
56 | server: {
57 | host: '0.0.0.0',
58 | port: 3003,
59 | proxy: {
60 | '/api': {
61 | // 用于开发环境下的转发请求
62 | // 更多请参考:https://vitejs.dev/config/#server-proxy
63 | target: 'https://service-exndqyuk-1257786608.gz.apigw.tencentcs.com',
64 | changeOrigin: true,
65 | },
66 | },
67 | },
68 | };
69 | };
70 |
--------------------------------------------------------------------------------