This commit is contained in:
王创世 2024-04-18 13:44:38 +08:00
commit 2284534e06
129 changed files with 10171 additions and 0 deletions

14
.editorconfig Normal file
View File

@ -0,0 +1,14 @@
root = true
[*]
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[*.{ts,js,vue,css}]
indent_size = 2

2
.env Normal file
View File

@ -0,0 +1,2 @@
# 打包路径 根据项目不同按需配置
VITE_BASE_URL = /

2
.env.development Normal file
View File

@ -0,0 +1,2 @@
# 打包路径
VITE_BASE_URL = /

2
.env.site Normal file
View File

@ -0,0 +1,2 @@
# 打包路径 根据项目不同按需配置
VITE_BASE_URL = https://static.tdesign.tencent.com/starter/vue/

3
.eslintignore Normal file
View File

@ -0,0 +1,3 @@
dist
node_modules
!.prettierrc.js

76
.eslintrc Normal file
View File

@ -0,0 +1,76 @@
{
"extends": ["airbnb-base", "prettier", "plugin:@typescript-eslint/recommended", "plugin:vue/essential"],
"env": {
"browser": true,
"node": true,
"jest": true,
"es6": true
},
"globals": {
"cy": "readonly"
},
"plugins": ["vue", "@typescript-eslint"],
"parserOptions": {
"parser": "@typescript-eslint/parser",
"sourceType": "module",
"allowImportExportEverywhere": true,
"ecmaFeatures": {
"jsx": true
}
},
"rules": {
"@typescript-eslint/ban-ts-ignore": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/no-require-imports": 0,
"@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/prefer-for-of": 0,
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
"import/no-extraneous-dependencies": 0,
"import/extensions": 0,
"import/no-unresolved": 0,
"indent": [2, 2],
"camelcase": 0,
"class-methods-use-this": 0,
"new-cap": 0,
"no-new": 1,
"no-shadow": 0,
"no-console": 0,
"no-underscore-dangle": 0,
"no-confusing-arrow": 0,
"no-plusplus": [
"error",
{
"allowForLoopAfterthoughts": true
}
],
"no-param-reassign": 0,
"func-style": 0,
"prefer-default-export": 0,
"max-len": 0,
"consistent-return": 0
},
"overrides": [
{
"files": ["*.vue"],
"rules": {
"vue/return-in-computed-property": 1,
"vue/order-in-components": 2,
"vue/component-name-in-template-casing": [2, "kebab-case"],
"vue/require-default-prop": 0,
"@typescript-eslint/explicit-module-boundary-types": "off",
"import/order": "off"
}
},
{
"files": ["src/*", "*.js"],
"rules": {
"no-var-requires": 0,
"no-console": 0,
"no-unused-expressions": 0,
"@typescript-eslint/explicit-module-boundary-types": "off",
"import/order": "off"
}
}
]
}

View File

@ -0,0 +1,79 @@
name: 反馈 Bug
description: 通过 github 模板进行 Bug 反馈。
title: "[组件名称] 描述问题的标题"
body:
- type: markdown
attributes:
value: |
# 欢迎你的参与
tdesign-vue-starter 的 Issue 列表接受 bug 报告或是新功能请求。也可加入官方社区:<img width="80px" src="https://user-images.githubusercontent.com/15634204/157386871-bf84c2ea-a02f-4c1c-b6fd-577450cdbcf7.png" />
在发布一个 Issue 前,请确保:
- 在 [常见问题](https://tdesign.tencent.com/about/faq)、[更新日志](https://tdesign.tencent.com/about/release) 和 [旧Issue列表](https://github.com/Tencent/tdesign-vue-starter/issues?q=is%3Aissue) 中搜索过你的问题。(你的问题可能已有人提出,也可能已在最新版本中被修正)
- 如果你发现一个已经关闭的旧 Issue 在最新版本中仍然存在,不要在旧 Issue 下面留言,请建一个新的 issue。
- type: input
id: version
attributes:
label: tdesign-vue-starter 版本
description: 请检查在最新项目版本中能否重现此 issue。
placeholder: 请填写
validations:
required: true
- type: input
id: reproduce
attributes:
label: 重现链接
description: 请提供尽可能精简的 CodePen、CodeSandbox 或 GitHub 仓库的链接。请不要填无关链接,否则你的 Issue 将被关闭。
placeholder: 请填写
- type: textarea
id: reproduceSteps
attributes:
label: 重现步骤
description: 请清晰的描述重现该 Issue 的步骤,这能帮助我们快速定位问题。没有清晰重现步骤将不会被修复,标有 'need reproduction' 的 Issue 在 7 天内不提供相关步骤,将被关闭。
placeholder: 请填写
- type: textarea
id: expect
attributes:
label: 期望结果
placeholder: 请填写
- type: textarea
id: actual
attributes:
label: 实际结果
placeholder: 请填写
- type: input
id: frameworkVersion
attributes:
label: 框架版本
placeholder: Vue(3.2.0)
- type: input
id: browsersVersion
attributes:
label: 浏览器版本
placeholder: Chrome(8.213.231.123)
- type: input
id: systemVersion
attributes:
label: 系统版本
placeholder: MacOS(11.2.3)
- type: input
id: nodeVersion
attributes:
label: Node版本
placeholder: 请填写
- type: textarea
id: remarks
attributes:
label: 补充说明
description: 可以是遇到这个 bug 的业务场景、上下文等信息。
placeholder: 请填写

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: 使用 issue-helper 新建
url: https://Tencent.github.io/tdesign/issue-helper/?lang=zh-CN&repo=Tencent/tdesign-vue-starter
about: 使用 https://Tencent.github.io/tdesign/issue-helper/ 创建 issue其中包含 bug 和 feature表单提交更加严格。

View File

@ -0,0 +1,30 @@
name: 反馈新功能
description: 通过 github 模板进行新功能反馈。
title: "[组件名称] 描述问题的标题"
body:
- type: markdown
attributes:
value: |
# 欢迎你的参与
tdesign-vue-starter 的 Issue 列表接受 bug 报告或是新功能请求。也可加入官方社区:<img width="80px" src="https://user-images.githubusercontent.com/15634204/157386871-bf84c2ea-a02f-4c1c-b6fd-577450cdbcf7.png" />
在发布一个 Issue 前,请确保:
- 在 [常见问题](https://tdesign.tencent.com/about/faq)、[更新日志](https://tdesign.tencent.com/about/release) 和 [旧Issue列表](https://github.com/Tencent/tdesign-vue-starter/issues?q=is%3Aissue) 中搜索过你的问题。(你的问题可能已有人提出,也可能已在最新版本中被修正)
- 如果你发现一个已经关闭的旧 Issue 在最新版本中仍然存在,不要在旧 Issue 下面留言,请建一个新的 issue。
- type: textarea
id: functionContent
attributes:
label: 这个功能解决了什么问题
description: 请详尽说明这个需求的用例和场景。最重要的是:解释清楚是怎样的用户体验需求催生了这个功能上的需求。我们将考虑添加在现有 API 无法轻松实现的功能。新功能的用例也应当足够常见。
placeholder: 请填写
validations:
required: true
- type: textarea
id: functionalExpectations
attributes:
label: 你建议的方案是什么
placeholder: 请填写
validations:
required: true

52
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,52 @@
<!--
首先,感谢你的贡献!😄
请阅读并遵循 [TDesign 贡献指南](https://github.com/Tencent/tdesign/blob/main/docs/contributing.md),填写以下 pull request 的信息。
PR 在维护者审核通过后会合并,谢谢!
-->
### 🤔 这个 PR 的性质是?
- [ ] 日常 bug 修复
- [ ] 新特性提交
- [ ] 文档改进
- [ ] 演示代码改进
- [ ] 组件样式/交互改进
- [ ] CI/CD 改进
- [ ] 重构
- [ ] 代码风格优化
- [ ] 测试用例
- [ ] 分支合并
- [ ] 其他
### 🔗 相关 Issue
<!--
1. 描述相关需求的来源,如相关的 issue 讨论链接。
-->
### 💡 需求背景和解决方案
<!--
1. 要解决的具体问题。
2. 列出最终的 API 实现和用法。
3. 涉及UI/交互变动需要有截图或 GIF。
-->
### 📝 更新日志
<!--
从用户角度描述具体变化,以及可能的 breaking change 和其他风险。
-->
- fix(组件名称): 处理问题或特性描述 ...
- [ ] 本条 PR 不需要纳入 Changelog
### ☑️ 请求合并前的自查清单
⚠️ 请自检并全部**勾选全部选项**。⚠️
- [ ] 文档已补充或无须补充
- [ ] 代码演示已提供或无须提供
- [ ] TypeScript 定义已补充或无须补充
- [ ] Changelog 已提供或无须提供

20
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,20 @@
# Basic dependabot.yml file with
# minimum configuration for two package managers
version: 2
updates:
# Enable version updates for npm
- package-ecosystem: "npm"
# Look for `package.json` and `lock` files in the `root` directory
directory: "/"
# Check the npm registry for updates every day (weekdays)
schedule:
interval: "monthly"
# Enable version updates for Docker
- package-ecosystem: "docker"
# Look for a `Dockerfile` in the `root` directory
directory: "/"
# Check for updates once a week
schedule:
interval: "monthly"

View File

@ -0,0 +1,52 @@
# force copy from tencent/tdesign
name: Issue Add Assigness
on:
issues:
types: [opened, edited]
jobs:
mark-duplicate:
runs-on: ubuntu-latest
steps:
- uses: wow-actions/auto-comment@v1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
issuesOpened: |
👋 @{{ author }},感谢给 TDesign 提出了 issue。
请根据 issue 模版确保背景信息的完善,我们将调查并尽快回复你。
# https://docs.github.com/cn/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#issues
- uses: 94dreamer/issue-assignees@main
id: assignees
with:
project_name: ${{github.event.repository.name}}
issue_title: ${{github.event.issue.title}}
- run: echo ${{ steps.assignees.outputs.contributors }}
- name: Add assigness
if: steps.assignees.outputs.contributors != ''
uses: actions-cool/issues-helper@v3
with:
actions: 'add-assignees'
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
assignees: ${{ steps.assignees.outputs.contributors }}
- run: |
contributors=${{ steps.assignees.outputs.contributors }}
contributorstring=${contributors//,/ @}
echo "::set-output name=string::@$contributorstring"
id: contributors
- name: 通知贡献者
if: steps.assignees.outputs.contributors != ''
uses: actions-cool/maintain-one-comment@v2.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
body: |
♥️ 有劳 ${{ steps.contributors.outputs.string }} 尽快确认问题。
确认有效后将下一步计划和可能需要的时间回复给 @${{ github.event.issue.user.login }} 。
<!-- AUTO_ASSIGENEES_NOTIFY_HOOK -->
number: ${{ github.event.issue.number }}
body-include: "<!-- AUTO_ASSIGENEES_NOTIFY_HOOK -->"

View File

@ -0,0 +1,22 @@
# force copy from tencent/tdesign
name: Issue Help wanted
on:
issues:
types:
- labeled
jobs:
add-comment:
if: github.event.label.name == 'help wanted'
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Add comment
uses: peter-evans/create-or-update-comment@v1
with:
issue-number: ${{ github.event.issue.number }}
body: |
任何人都可以处理此问题。
**请务必在您的 `pull request` 中引用此问题。** :sparkles:
感谢你的贡献! :sparkles:
reactions: heart

View File

@ -0,0 +1,19 @@
# force copy from tencent/tdesign
# 当在 issue 的 comment 回复类似 `Duplicate of #111` 这样的话issue 将被自动打上 重复提交标签 并且 cloese
name: Issue Mark Duplicate
on:
issue_comment:
types: [created, edited]
jobs:
mark-duplicate:
runs-on: ubuntu-latest
steps:
- name: mark-duplicate
uses: actions-cool/issues-helper@v2
with:
actions: "mark-duplicate"
token: ${{ secrets.GITHUB_TOKEN }}
duplicate-labels: "duplicate"
close-issue: true

21
.github/workflows/issue-reply.temp.yml vendored Normal file
View File

@ -0,0 +1,21 @@
# force copy from tencent/tdesign
# 当被打上 Need Reproduce 标签时候,自动提示需要重现实例
name: ISSUE_REPLY
on:
issues:
types: [labeled]
jobs:
issue-reply:
runs-on: ubuntu-latest
steps:
- name: Need Reproduce
if: github.event.label.name == 'Need Reproduce'
uses: actions-cool/issues-helper@v2
with:
actions: 'create-comment'
issue-number: ${{ github.event.issue.number }}
body: |
你好 @${{ github.event.issue.user.login }}, 我们需要你提供一个在线的重现实例以便于我们帮你排查问题。你可以通过点击 [此处](https://codesandbox.io/) 创建一个 codesandbox 或者提供一个最小化的 GitHub 仓库。请确保选择准确的版本。

View File

@ -0,0 +1,17 @@
# force copy from tencent/tdesign
name: Issue Add Assigness
on:
issues:
types: [opened, reopened]
jobs:
mark-duplicate:
runs-on: ubuntu-latest
steps:
# https://docs.github.com/cn/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#issues
- uses: 94dreamer/create-report@main
with:
wxhook: ${{ secrets.WX_HOOK_URL }}
token: ${{ secrets.GITHUB_TOKEN }}
type: 'issue'

12
.github/workflows/pr-spelling.temp.yml vendored Normal file
View File

@ -0,0 +1,12 @@
# force copy from tencent/tdesign
name: pr-spell-check
on: [pull_request]
jobs:
run:
name: Spell Check with Typos
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Check spelling
uses: crate-ci/typos@master

15
.github/workflows/preview-publish.yml vendored Normal file
View File

@ -0,0 +1,15 @@
# 文件名建议统一为 preview-publish
# 应用 preview.yml 的 demo
name: PREVIEW_PUBLISH
on:
workflow_run:
workflows: ["MAIN_PULL_REQUEST"]
types:
- completed
jobs:
call-preview:
uses: Tencent/tdesign/.github/workflows/preview.yml@main
secrets:
TDESIGN_SURGE_TOKEN: ${{ secrets.TDESIGN_SURGE_TOKEN }}

14
.github/workflows/pull-request.yml vendored Normal file
View File

@ -0,0 +1,14 @@
# 文件名建议统一为 pull-request.yml
# 应用 test-build.yml 的 demo
name: MAIN_PULL_REQUEST
on:
pull_request:
branches: [develop, main, site]
types: [opened, synchronize, reopened]
jobs:
call-test-build:
uses: Tencent/tdesign/.github/workflows/test-build.yml@main
# install lint

13
.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
.vscode
.history
README.html
.stylelintcache
.idea
yarn.lock
package-lock.json

4
.husky/commit-msg Normal file
View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install commitlint -e $GIT_PARAMS

4
.husky/pre-commit Normal file
View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged

View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
exec < /dev/tty && npx git-cz --hook || true

39
.prettierrc.js Normal file
View File

@ -0,0 +1,39 @@
module.exports = {
// 一行最多 120 字符
printWidth: 120,
// 使用 2 个空格缩进
tabWidth: 2,
// 不使用缩进符,而使用空格
useTabs: false,
// 行尾需要有分号
semi: true,
// 使用单引号
singleQuote: true,
// 对象的 key 仅在必要时用引号
quoteProps: 'as-needed',
// jsx 不使用单引号,而使用双引号
jsxSingleQuote: false,
// 末尾需要有逗号
trailingComma: 'all',
// 大括号内的首尾需要空格
bracketSpacing: true,
// jsx 标签的反尖括号需要换行
jsxBracketSameLine: false,
// 箭头函数,只有一个参数的时候,也需要括号
arrowParens: 'always',
// 每个文件格式化的范围是文件的全部内容
rangeStart: 0,
rangeEnd: Infinity,
// 不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准
proseWrap: 'preserve',
// 根据显示样式决定 html 要不要折行
htmlWhitespaceSensitivity: 'css',
// vue 文件中的 script 和 style 内不用缩进
vueIndentScriptAndStyle: false,
// 换行符使用 lf
endOfLine: 'lf',
};

8
.stylelintignore Normal file
View File

@ -0,0 +1,8 @@
# .stylelintignore
# 旧的不需打包的样式库
*.min.css
# 其他类型文件
*.js
*.jpg
*.woff

9
LICENSE Normal file
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2021-present TDesign
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:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
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.

22
PROXY.md Normal file
View File

@ -0,0 +1,22 @@
# TDesign starter 本地开发联调
## 工具准备
- 浏览器插件:[SwitchyOmega](https://chrome.google.com/webstore/detail/proxy-switchyomega/padekgcemlokbadohgkifijomclgjgif?hl=zh-CNhttps://chrome.google.com/webstore/detail/proxy-switchyomega/padekgcemlokbadohgkifijomclgjgif?hl=zh-CN)
- 调试代理工具:[whistle](https://wproxy.org/whistle/)
## 代理配置
`npm run dev`开启本地服务后,可以配置代理将线上域名的非后台 api 路径(如`/api`)的静态文件请求代理到本地,这样就可以进行本地联调和前端代码热更新了。
举例子:
```
/tdesign.tencent.com(?!\/api)/ 127.0.0.1:3001
```
其中:
- tdesign.tencent.com修改你的线上域名
- /api修改成后台请求路径
- 3001修改成本地服务端口

115
README-zh_CN.md Normal file
View File

@ -0,0 +1,115 @@
<p style="display:flex; justify-content: center">
</p>
<p align="center">
<a href="https://tdesign.tencent.com/starter/vue/#/dashboard/base" target="_blank">
<img alt="TDesign Logo" width="200" src="https://tdesign.gtimg.com/starter/brand-logo.svg">
</a>
</p>
<p align="center">
<a href="https://nodejs.org/en/about/releases/"><img src="https://img.shields.io/node/v/vite.svg" alt="node compatility"></a>
<a href="https://github.com/Tencent/tdesign-vue/blob/develop/LICENSE">
<img src="https://img.shields.io/npm/l/tdesign-vue.svg?sanitize=true" alt="License">
</a>
</p>
简体中文 | [English](./README.md)
### 项目简介
TDesign Vue Starter 是一个基于 tdesign-vue使用 `Vue2`、`Vite`开发,可进行个性化主题配置,旨在提供项目开箱即用的、配置式的中后台项目。
<p>
<a href="http://tdesign.tencent.com/starter/vue/">在线预览</a>
·
<a href="https://tdesign.tencent.com/starter/docs/vue/get-started">使用文档</a>
</p>
<img src="docs/docs-starter.png">
### 特性
- 内置多种常用的中后台页面
- 完善的目录结构
- 完善的代码规范配置
- 支持暗黑模式
- 自定义主题颜色
- 多种空间布局
- 内置 Mock 数据方案
### 使用
> 通过 [tdesign-starter-cli](https://www.npmjs.com/package/tdesign-starter-cli) 初始化项目仓库
```bash
## 1、安装 tdesign-starter-cli
npm i tdesign-starter-cli@latest -g
## 2、创建项目
td-starter init
```
### 开发
```bash
## 安装依赖
npm install
## 启动项目
npm run dev
```
### 构建
```bash
## 构建正式环境
npm run build
## 构建测试环境
npm run build:test
```
### 其他
```bash
## 预览构建产物
npm run preview
## 代码格式检查
npm run lint
## 代码格式检查与自动修复
npm run lint:fix
## style格式检查
npm run stylelint
## style格式检查与自动修复
npm run stylelint:fix
```
### 如何贡献
非常欢迎您的贡献!提交您的 [Issue](https://github.com/tencent/tdesign-vue-starter/issues/new/choose) 或者提交 [Pull Request](https://github.com/Tencent/tdesign-vue-starter/pulls)。
#### 贡献提交规范
- [Angular Convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)
- [Vue Style Guide](https://v3.vuejs.org/style-guide/#rule-categories)
### 兼容性
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br> IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Edge >=84 | Firefox >=83 | Chrome >=84 | Safari >=14.1 |
### 社区版本
基于 TDesign Vue 的 starter-kit 有多种社区版本,访问 [社区链接](https://tdesign.tencent.com/starter/docs/vue/community-link) 可以访问更多版本。
如果您也开发了 TDesign Starter 的社区版本,可以提交 Issue 或者直接给我们提Pull Request 😊。
### 开源协议
TDesign 遵循 [MIT 协议](https://github.com/Tencent/tdesign-vue-starter/LICENSE)。

94
README.md Normal file
View File

@ -0,0 +1,94 @@
<p style="display:flex; justify-content: center">
</p>
<p align="center">
<a href="https://tdesign.tencent.com/starter/vue/#/dashboard/base" target="_blank">
<img alt="TDesign Logo" width="200" src="https://tdesign.gtimg.com/starter/brand-logo.svg">
</a>
</p>
<p align="center">
<a href="https://nodejs.org/en/about/releases/"><img src="https://img.shields.io/node/v/vite.svg" alt="node compatility"></a>
<a href="https://github.com/Tencent/tdesign-vue/blob/develop/LICENSE">
<img src="https://img.shields.io/npm/l/tdesign-vue.svg?sanitize=true" alt="License">
</a>
</p>
English | [简体中文](./README-zh_CN.md)
### Introduction
TDesign Vue Starter is a TDesign-based project developed with `Vue2`, `Vite`. It can be customized theme configuration, and aims to provide project out-of-the-box, configuration-style middle and background projects.
<p>
<a href="http://tdesign.tencent.com/starter/vue/">Live Preview</a>
·
<a href="https://tdesign.tencent.com/starter/docs/vue/get-started">Documentation</a>
</p>
<img src="docs/docs-starter.png">
### Features
- Various provided pages for develop
- Complete directory structure for develop
- Code specification configuration
- Support dark mode
- Custom theme colors
- Various space layouts
- Mock data scheme
### Usage
> Initialize project with our CLI tool `tdesign-starter-cli`
```bash
## install tdesign-starter-cli
npm i tdesign-starter-cli@latest -g
## create project
td-starter init
```
### Develop
```bash
## install dependencies
npm install
## set up
npm run dev
```
### Build
```bash
## build
npm run build
## build for test
npm run build:test
```
### Contributing Guide
We welcome contributions to our project. Create your [Issue](https://github.com/tencent/tdesign-vue-starter/issues/new/choose) or Submit your [Pull Request](https://github.com/Tencent/tdesign-vue-starter/pulls).
#### Commit Specification
- [Angular Convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)
- [Vue Style Guide](https://v3.vuejs.org/style-guide/#rule-categories)
### Browser Support
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br> IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Edge >=84 | Firefox >=83 | Chrome >=84 | Safari >=14.1 |
### Community Versions
There are kinds of community versions of starter-kit based on TDesign Vue Next, visit [community-link](https://tdesign.tencent.com/starter/docs/vue-next/community-link) for more detail. If you developed a community versions of tdesign starter, please create a issue or submit a pull request to let us know 😊.
### License
The MIT License. Please see [the license file](LICENSE) for more information.

15
cache.dockerfile Normal file
View File

@ -0,0 +1,15 @@
# 选择一个 Base 镜像
FROM node:12
# 设置工作目录
WORKDIR /space
# 将 by 中的文件列表 COPY 过来
COPY . .
# 根据 COPY 过来的文件进行依赖的安装
RUN npm i
# 设置好需要的环境变量
ENV NODE_PATH=/space/node_modules

1
commitlint.config.js Normal file
View File

@ -0,0 +1 @@
module.exports = { extends: ['@commitlint/config-conventional'] };

13
globals.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
// 通用声明
declare type ClassName = { [className: string]: any } | ClassName[] | string;
declare interface ImportMeta {
env: {
MODE: 'mock' | 'development' | 'test' | 'release' | 'site';
};
}
declare module '*.svg' {
const content: string;
export default content;
}

17
index.html Normal file
View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title></title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
<script>
window.global = window;
</script>
</html>

13
jsx.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
import Vue, { VNode } from 'vue';
declare global {
namespace JSX {
type Element = VNode;
type ElementClass = Vue;
interface IntrinsicElements {
[elem: string]: any;
}
type IntrinsicAttributes = any;
}
}

90
package.json Normal file
View File

@ -0,0 +1,90 @@
{
"name": "yunshangxie",
"version": "0.6.2",
"scripts": {
"dev:mock": "vite --open --mode mock",
"dev": "vite --open --mode development",
"dev:linux": "vite --mode development",
"build:test": "vite build --mode test",
"build": "vite build --mode release",
"build:site": "vite build --mode site",
"site:preview": "npm run build && cp -r dist _site",
"preview": "vite preview",
"lint": "eslint --ext .vue,.js,.jsx,.ts,.tsx ./ --max-warnings 0",
"lint:fix": "eslint --ext .vue,.js,jsx,.ts,.tsx ./ --max-warnings 0 --fix",
"stylelint": "stylelint src/**/*.{html,vue,sass,less}",
"stylelint:fix": "stylelint --cache --fix src/**/*.{html,vue,vss,sass,less}",
"prepare": "node -e \"if(require('fs').existsSync('.git')){process.exit(1)}\" || is-ci || husky install",
"test": "echo \"no test specified,work in process\"",
"test:coverage": "echo \"no test:coverage specified,work in process\""
},
"dependencies": {
"@wangeditor/editor-for-vue": "^1.0.2",
"axios": "^1.1.3",
"dayjs": "^1.10.8",
"echarts": "5.1.2",
"lodash": "^4.17.21",
"nprogress": "^0.2.0",
"qrcode.vue": "^1.7.0",
"tdesign-icons-vue": "^0.1.11",
"tdesign-vue": "^1.9.0",
"tvision-color": "~1.6.0",
"typescript": "^5.1.6",
"vue": "~2.6.14",
"vue-clipboard2": "^0.3.1",
"vue-router": "^3.5.1",
"vuex": "^3.6.2",
"vuex-router-sync": "^5.0.0"
},
"devDependencies": {
"@commitlint/cli": "^17.0.3",
"@commitlint/config-conventional": "^17.1.0",
"@types/vue-color": "^2.4.3",
"@typescript-eslint/eslint-plugin": "^6.17.0",
"@typescript-eslint/parser": "^6.17.0",
"commitizen": "^4.2.3",
"eslint": "^7.22.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-vue": "^7.8.0",
"husky": "^8.0.1",
"less": "^4.1.0",
"lint-staged": "^13.0.3",
"mockjs": "^1.1.0",
"prettier": "^2.6.0",
"stylelint": "~13.13.1",
"stylelint-config-prettier": "~9.0.3",
"stylelint-less": "1.0.5",
"stylelint-order": "~4.1.0",
"vite": "^4.1.4",
"vite-plugin-mock": "^3.0.0",
"vite-plugin-theme": "^0.8.1",
"vite-plugin-vue2": "^2.0.1",
"vite-plugin-vue2-svg": "~0.4.0",
"vue-template-compiler": "~2.6.14"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"prepare-commit-msg": "exec < /dev/tty && git cz --hook || true",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"lint-staged": {
"*.{js,jsx,vue,ts,tsx}": [
"prettier --write",
"npm run lint:fix"
],
"*.{html,vue,vss,sass,less}": [
"npm run stylelint:fix"
]
},
"description": "云商协"
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

5
shims-vue.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
declare module '*.vue' {
import Vue from 'vue';
export default Vue;
}

25
src/App.vue Normal file
View File

@ -0,0 +1,25 @@
<template>
<router-view :class="[mode]" />
</template>
<script>
import Vue from 'vue';
import config from '@/config/style';
export default Vue.extend({
computed: {
mode() {
return this.$store.getters['setting/mode'];
},
},
mounted() {
this.$store.dispatch('setting/changeTheme', { ...config });
},
});
</script>
<style lang="less">
.t-default-menu .t-menu__item{
height: 50px;
font-size: 16px;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 972 KiB

View File

@ -0,0 +1,39 @@
<svg width="1em" height="1em" viewBox="0 0 188 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M77.7301 8.34426H80.6699L78.3234 21.7922H75.3799L77.7301 8.34426Z" fill="currentcolor"/>
<path d="M78.5992 3.09961H81.6221L81.0742 6.12246H78.0513L78.5992 3.09961Z" fill="currentcolor"/>
<path d="M32.5765 6.46921H36.937L37.4131 3.82422H25.4615L24.9854 6.46921H29.3723L26.6706 21.7913H29.8559L32.5765 6.46921Z" fill="currentcolor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.4051 11.4275C51.5998 10.4187 51.7035 9.39434 51.7149 8.3669C51.7149 4.97375 50.007 3.8364 45.4765 3.84774H39.2381L36.083 21.8035H43.3908C48.1329 21.8035 49.8181 20.5906 50.8836 14.4504L51.4051 11.4275ZM45.0004 6.47006C47.5623 6.47006 48.5372 6.80258 48.5372 8.83922C48.5177 9.70444 48.4254 10.5665 48.2613 11.4162L47.7399 14.4391C47.0522 18.4481 46.3191 19.1585 42.7597 19.1585H39.7369L41.9473 6.47006H45.0004Z" fill="currentcolor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M63.863 10.8329C63.8629 11.1826 63.8313 11.5317 63.7685 11.8758L63.436 13.765C63.0582 15.8735 62.276 16.6103 60.3074 16.6103H55.0816L54.9607 17.3471C54.8956 17.6913 54.8564 18.0399 54.8436 18.39C54.8436 19.2175 55.2705 19.4102 56.6459 19.4102H61.4145L60.4396 21.8058H56.0754C52.9958 21.8058 51.9189 21.0199 51.9189 19.1004C51.9308 18.5439 51.9864 17.9893 52.0852 17.4416L53.011 12.1289C53.5777 8.85291 54.9531 8.19166 58.4407 8.19166H60.0542C62.5292 8.17654 63.863 8.83779 63.863 10.8329ZM60.9006 11.2599C60.9006 10.6893 60.5454 10.5457 59.4534 10.5457H57.7682C56.6762 10.5457 56.1547 10.6893 55.8789 12.1138L55.501 14.294H59.5327C59.6593 14.3151 59.7889 14.3081 59.9124 14.2735C60.036 14.239 60.1504 14.1778 60.2477 14.0942C60.345 14.0106 60.4228 13.9066 60.4755 13.7897C60.5283 13.6728 60.5547 13.5457 60.553 13.4174L60.8137 11.8758C60.8561 11.6728 60.8826 11.4669 60.893 11.2599H60.9006Z" fill="currentcolor"/>
<path d="M69.644 19.3964H64.0215L64.4485 21.8185H69.644C72.4477 21.8185 73.1543 21.486 73.5813 19.0186L73.842 17.5072C73.9744 16.8585 74.0527 16.2001 74.0762 15.5385C74.0762 14.1896 73.3885 13.7853 71.3028 13.7853H68.4575C67.9096 13.7853 67.7471 13.7361 67.7471 13.4301C67.754 13.2471 67.7768 13.065 67.8152 12.886L68.0079 11.8166C68.1741 10.8909 68.3366 10.7511 69.2624 10.7511H74.269L75.2627 8.35547H69.2624C66.5834 8.35547 65.873 8.6502 65.3969 11.3783L65.208 12.4477C65.073 13.0947 64.9933 13.752 64.9699 14.4125C64.9699 15.7652 65.6576 16.1695 67.7471 16.1695H70.4715C71.0194 16.1695 71.1856 16.2187 71.1856 16.5247C71.1769 16.7078 71.1529 16.8898 71.1139 17.0688L70.8985 18.3271C70.7322 19.2529 70.566 19.3964 69.644 19.3964Z" fill="currentcolor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M93.2642 13.8225C93.4341 12.9856 93.5377 12.1366 93.5741 11.2833C93.5741 9.00486 92.3158 8.17735 89.3194 8.16602H87.6833C83.7196 8.16602 82.843 9.03509 82.1553 12.8137L81.8001 14.7823C81.6298 15.6192 81.5262 16.4682 81.4902 17.3215C81.4902 19.5962 82.7372 20.4275 85.76 20.4275H89.1607L88.9945 21.3532C88.6393 23.439 88.2841 23.5826 86.4326 23.5826H81.5469L81.9739 26.0008H87.0976C89.8258 26.0008 91.2238 25.3585 91.7944 22.1354L93.2642 13.8225ZM90.3132 13.7961L89.5877 18.0432L86.4931 18.0318C84.9287 18.0318 84.43 17.8429 84.43 16.8227C84.475 16.1366 84.5697 15.4547 84.7133 14.7823L85.0723 12.8137C85.3784 11.0944 85.6618 10.5465 87.4641 10.5465H88.5335C90.0751 10.5465 90.5966 10.7392 90.5966 11.7557C90.5573 12.4423 90.4625 13.1247 90.3132 13.7961Z" fill="currentcolor"/>
<path d="M107.112 10.7615C107.059 11.7344 106.933 12.7019 106.735 13.6559L105.31 21.7911H102.37L103.818 13.6559C103.946 13.0334 104.026 12.4016 104.056 11.7666C104.056 10.9353 103.7 10.7691 102.586 10.7691H99.1208L97.2013 21.776H94.2578L96.6081 8.32812H103.13C106.183 8.34324 107.112 9.06872 107.112 10.7615Z" fill="currentcolor"/>
<path d="M112.333 21.9715H117.879C121.093 21.9715 122.473 21.5668 123.045 18.3532L123.473 15.9727C123.568 15.4728 123.783 14.0683 123.783 13.4017C123.783 11.6164 122.497 11.3307 120.474 11.3307H117.427C116.76 11.3307 116.427 11.2117 116.427 10.807C116.427 10.5928 116.498 10.1167 116.617 9.49773L116.903 8.02182C117.141 6.80777 117.332 6.56972 118.427 6.56972H124.211L125.306 3.95117H118.26C115.284 3.95117 114.356 4.61771 113.856 7.47431L113.404 9.99763C113.285 10.688 113.19 11.3069 113.19 11.8306C113.19 13.3303 113.832 14.0683 115.76 14.0683H119.307C120.164 14.0683 120.498 14.1159 120.498 14.6396C120.498 14.9015 120.45 15.4252 120.283 16.2583L120.045 17.4962C119.736 19.1625 119.498 19.3292 117.927 19.3292H111.856L112.333 21.9715Z" fill="currentcolor"/>
<path d="M130.124 10.8784H133.909L134.337 8.47411H130.552L131.266 4.47488H128.314L127.6 8.47411H125.815L125.41 10.8784H127.172L126.101 16.9487C125.958 17.7104 125.839 18.5436 125.839 19.1625C125.839 21.2574 127.172 21.9715 129.838 21.9715H131.576L132.599 19.5672H130.981C129.433 19.5672 128.838 19.5434 128.838 18.5912C128.838 18.2103 128.933 17.5914 129.052 16.9487L130.124 10.8784Z" fill="currentcolor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M136.155 8.47411H142.344C145.034 8.47411 146.081 9.21207 146.081 10.926C146.081 11.4973 145.915 12.4495 145.772 13.2589L144.248 21.9715H137.107C134.798 21.9715 134.084 21.1384 134.084 19.5672C134.084 18.9959 134.179 18.377 134.298 17.6628L134.441 16.8535C135.012 13.6636 136.059 13.4732 139.297 13.4732H142.868L142.939 13.0447C143.034 12.5448 143.106 12.1639 143.106 11.8306C143.106 11.1165 142.701 10.8784 141.344 10.8784H136.559L136.155 8.47411ZM137.059 18.8531C137.059 19.4244 137.297 19.5672 137.94 19.5672H141.796L142.463 15.7584H138.749C137.75 15.7584 137.512 15.9727 137.345 16.9487L137.154 18.0199C137.083 18.4008 137.059 18.6626 137.059 18.8531Z" fill="currentcolor"/>
<path d="M146.66 21.9715H149.636L151.231 12.9971C151.54 11.2117 151.897 10.926 153.611 10.926H155.778L156.801 8.47411H153.516C149.779 8.47411 148.922 9.14065 148.279 12.8304L146.66 21.9715Z" fill="currentcolor"/>
<path d="M165.098 10.8784H161.313L160.242 16.9487C160.123 17.5914 160.028 18.2103 160.028 18.5912C160.028 19.5434 160.623 19.5672 162.17 19.5672H163.789L162.765 21.9715H161.027C158.361 21.9715 157.028 21.2574 157.028 19.1625C157.028 18.5436 157.147 17.7104 157.29 16.9487L158.361 10.8784H156.6L157.004 8.47411H158.79L159.504 4.47488H162.456L161.742 8.47411H165.527L165.098 10.8784Z" fill="currentcolor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M173.343 8.30748H171.724C168.225 8.30748 166.844 8.97402 166.273 12.2591L165.345 17.5914C165.249 18.1627 165.178 18.7579 165.178 19.2578C165.178 21.186 166.273 21.9715 169.368 21.9715H173.748L174.724 19.5672H169.939C168.558 19.5672 168.13 19.3768 168.13 18.5436C168.13 18.2818 168.177 17.9009 168.249 17.4962L168.368 16.7582H173.605C175.581 16.7582 176.366 16.0203 176.747 13.9016L177.08 12.0211C177.152 11.6402 177.176 11.2593 177.176 10.9736C177.176 8.97402 175.843 8.30748 173.343 8.30748ZM174.129 12.0211L173.867 13.5684C173.772 14.1873 173.51 14.4492 172.843 14.4492H168.796L169.177 12.2591C169.439 10.8308 169.963 10.688 171.058 10.688H172.748C173.843 10.688 174.2 10.8308 174.2 11.4021C174.2 11.5688 174.176 11.7354 174.129 12.0211Z" fill="currentcolor"/>
<path d="M179.919 21.9715H176.943L178.562 12.8304C179.205 9.14065 180.062 8.47411 183.799 8.47411H187.084L186.06 10.926H183.894C182.18 10.926 181.823 11.2117 181.514 12.9971L179.919 21.9715Z" fill="currentcolor"/>
<path d="M5.21231 5.28999H0.481548C0.412498 5.29078 0.344103 5.27654 0.281107 5.24826C0.21811 5.21998 0.162019 5.17833 0.116725 5.12621C0.0714314 5.07408 0.0380182 5.01273 0.0188032 4.9464C-0.000411771 4.88008 -0.00496858 4.81036 0.00544886 4.7421L0.783833 0.377856C0.80328 0.271783 0.8593 0.175885 0.942146 0.106846C1.02499 0.0378082 1.12942 0 1.23726 0L6.14939 0L5.21231 5.28999Z" fill="#0064FF"/>
<path d="M8.52984 16.5117H3.22852L5.21226 5.29688H10.5136L8.52984 16.5117Z" fill="url(#paint0_linear_18110_34031)"/>
<path d="M7.20647 21.8093H2.85735C2.78839 21.8085 2.72041 21.7929 2.65803 21.7635C2.59565 21.7341 2.54035 21.6915 2.49589 21.6388C2.45142 21.5861 2.41884 21.5244 2.40036 21.458C2.38188 21.3916 2.37794 21.3219 2.38881 21.2538L3.23142 16.5117H8.52142L7.65235 21.4239C7.63472 21.53 7.58048 21.6267 7.49905 21.6971C7.41761 21.7675 7.3141 21.8072 7.20647 21.8093V21.8093Z" fill="#009BFF"/>
<path d="M20.0919 5.28999H5.21191L6.149 0H20.8476C20.9168 0.00013044 20.9851 0.0153276 21.0478 0.0445343C21.1105 0.0737409 21.166 0.116256 21.2106 0.169121C21.2552 0.221986 21.2878 0.283932 21.306 0.35065C21.3242 0.417368 21.3277 0.487255 21.3162 0.555449L20.5416 4.91213C20.5236 5.01823 20.4685 5.11448 20.3862 5.1837C20.3038 5.25293 20.1995 5.2906 20.0919 5.28999V5.28999Z" fill="url(#paint1_linear_18110_34031)"/>
<defs>
<linearGradient id="paint0_linear_18110_34031" x1="7.81543" y1="4.88815" x2="7.92589" y2="15.4398" gradientUnits="userSpaceOnUse">
<stop stop-color="#009BFF"/>
<stop offset="0.35" stop-color="#0081FE"/>
<stop offset="0.75" stop-color="#006AFD"/>
<stop offset="1" stop-color="#0062FD"/>
</linearGradient>
<linearGradient id="paint1_linear_18110_34031" x1="5.68577" y1="2.65136" x2="20.3785" y2="5.41299" gradientUnits="userSpaceOnUse">
<stop offset="0.03" stop-color="#E9FFFF"/>
<stop offset="0.17" stop-color="#C4FAC9"/>
<stop offset="0.33" stop-color="#A0F694"/>
<stop offset="0.48" stop-color="#82F269"/>
<stop offset="0.63" stop-color="#6AEF47"/>
<stop offset="0.76" stop-color="#5AED2F"/>
<stop offset="0.89" stop-color="#4FEB20"/>
<stop offset="1" stop-color="#4CEB1B"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -0,0 +1,5 @@
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="49" height="49" rx="24.5" fill="#0052D9" stroke="none"/>
<path d="M23.9219 31H26.0859L22.0781 19.7266H19.8125L15.8125 31H17.8516L18.8203 28.1172H22.9688L23.9219 31ZM20.8359 21.875H20.9688L22.5 26.5234H19.2891L20.8359 21.875ZM30.6328 29.6172C29.7734 29.6172 29.1562 29.1875 29.1562 28.4688C29.1562 27.7734 29.6641 27.3828 30.75 27.3125L32.6797 27.1875V27.8672C32.6797 28.8594 31.8047 29.6172 30.6328 29.6172ZM30.0625 31.1406C31.1797 31.1406 32.1172 30.6562 32.5938 29.8281H32.7266V31H34.5938V25.1641C34.5938 23.3516 33.3594 22.2812 31.1641 22.2812C29.1328 22.2812 27.7188 23.2422 27.5625 24.75H29.3906C29.5703 24.1719 30.1797 23.8594 31.0703 23.8594C32.1172 23.8594 32.6797 24.3281 32.6797 25.1641V25.8828L30.4766 26.0156C28.3984 26.1406 27.2344 27.0312 27.2344 28.5781C27.2344 30.1406 28.4141 31.1406 30.0625 31.1406Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 979 B

View File

@ -0,0 +1,5 @@
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="49" height="49" rx="24.5" fill="#0594FA" stroke="none"/>
<path d="M20.6641 31C23.0703 31 24.5391 29.7734 24.5391 27.7969C24.5391 26.3281 23.4922 25.2344 21.9766 25.0938V24.9531C23.1094 24.7734 23.9844 23.7266 23.9844 22.5391C23.9844 20.8047 22.6953 19.7266 20.5547 19.7266H15.8438V31H20.6641ZM17.8594 21.3281H20.0625C21.2812 21.3281 21.9922 21.9062 21.9922 22.8984C21.9922 23.9141 21.2344 24.4688 19.8047 24.4688H17.8594V21.3281ZM17.8594 29.3984V25.9219H20.125C21.6641 25.9219 22.4766 26.5156 22.4766 27.6406C22.4766 28.7891 21.6875 29.3984 20.2031 29.3984H17.8594ZM31.2812 31.1406C33.4375 31.1406 34.7891 29.4453 34.7891 26.7266C34.7891 23.9922 33.4453 22.3125 31.2812 22.3125C30.1094 22.3125 29.125 22.8828 28.6797 23.8125H28.5469V19.1484H26.6094V31H28.4766V29.6484H28.6094C29.0938 30.5859 30.0859 31.1406 31.2812 31.1406ZM30.6719 23.9609C31.9922 23.9609 32.7969 25.0078 32.7969 26.7266C32.7969 28.4453 32 29.4922 30.6719 29.4922C29.3438 29.4922 28.5156 28.4297 28.5156 26.7266C28.5156 25.0234 29.3516 23.9609 30.6719 23.9609Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,5 @@
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="49" height="49" rx="24.5" fill="#7587DB" stroke="none"/>
<path d="M21.1172 31.2812C23.7188 31.2812 25.625 29.7266 25.8906 27.4219H23.9062C23.6328 28.6875 22.5469 29.4844 21.1172 29.4844C19.1953 29.4844 18 27.9062 18 25.3594C18 22.8203 19.1953 21.2422 21.1094 21.2422C22.5312 21.2422 23.6172 22.1172 23.8984 23.4688H25.8828C25.6484 21.1172 23.6719 19.4453 21.1094 19.4453C17.9141 19.4453 15.9375 21.7031 15.9375 25.3672C15.9375 29.0156 17.9219 31.2812 21.1172 31.2812ZM35.3125 25.3281C35.1094 23.5312 33.7812 22.2812 31.5859 22.2812C29.0156 22.2812 27.5078 23.9297 27.5078 26.7031C27.5078 29.5156 29.0234 31.1719 31.5938 31.1719C33.7578 31.1719 35.1016 29.9688 35.3125 28.1797H33.4688C33.2656 29.0703 32.5938 29.5469 31.5859 29.5469C30.2656 29.5469 29.4688 28.5 29.4688 26.7031C29.4688 24.9297 30.2578 23.9062 31.5859 23.9062C32.6484 23.9062 33.2891 24.5 33.4688 25.3281H35.3125Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,5 @@
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="49" height="49" rx="24.5" fill="#00A870" stroke="none"/>
<path d="M15.875 19.7266V31H20.1016C23.4766 31 25.4062 28.9297 25.4062 25.3125C25.4062 21.7734 23.4531 19.7266 20.1016 19.7266H15.875ZM17.8906 21.4688H19.8359C22.0469 21.4688 23.3516 22.8828 23.3516 25.3438C23.3516 27.8594 22.0781 29.2578 19.8359 29.2578H17.8906V21.4688ZM30.5938 31.1406C31.7812 31.1406 32.7656 30.5859 33.25 29.6484H33.3828V31H35.2578V19.1484H33.3203V23.8125H33.1875C32.7344 22.875 31.7656 22.3125 30.5938 22.3125C28.4375 22.3125 27.0781 24.0156 27.0781 26.7188C27.0781 29.4375 28.4297 31.1406 30.5938 31.1406ZM31.1953 23.9609C32.5234 23.9609 33.3438 25.0234 33.3438 26.7266C33.3438 28.4453 32.5312 29.4922 31.1953 29.4922C29.8672 29.4922 29.0625 28.4531 29.0625 26.7266C29.0625 25.0078 29.875 23.9609 31.1953 23.9609Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 958 B

View File

@ -0,0 +1,32 @@
<svg width="200" height="140" viewBox="0 0 200 140" fill="none" xmlns="http://www.w3.org/2000/svg">
<g mask="url(#mask0_17_619)">
<path d="M30 62H118V122H30V62Z" fill="#97A3B7" />
<g filter="url(#filter0_f_17_619)">
<rect x="12" y="84" width="80" height="60" fill="#E3E6EB" />
</g>
<g filter="url(#filter1_f_17_619)">
<rect x="80" y="54" width="80" height="60" fill="#E3E6EB" />
</g>
<rect x="46" y="105" width="32" height="2" fill="white" />
<rect x="46" y="98" width="32" height="2" fill="white" />
<rect x="46" y="88" width="16" height="2" fill="white" />
</g>
<path opacity="0.9" d="M63 20H151V30H63V20Z" fill="currentcolor" />
<mask id="mask1_17_619" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="63" y="30" width="88" height="50">
<path d="M63 30H151V80H63V30Z" fill="currentcolor" />
</mask>
<g mask="url(#mask1_17_619)">
<path d="M63 30H151V80H63V30Z" fill="currentcolor" />
<g opacity="0.3" filter="url(#filter2_f_17_619)">
<path d="M30 62H118V122H30V62Z" fill="#97A3B7" />
</g>
</g>
<path fill-rule="evenodd" clip-rule="evenodd"
d="M95.6863 40.8577L105.964 51.1345C106.295 51.0466 106.642 50.9998 107 50.9998C109.213 50.9998 111 52.7865 111 54.9998C111 55.3574 110.953 55.7038 110.866 56.0333L121.142 66.3135L118.314 69.1419L113.716 64.5448C111.653 65.423 109.384 65.9089 107 65.9089C99.7273 65.9089 93.5164 61.3853 91 54.9998C92.1785 52.0093 94.1673 49.4271 96.6961 47.5268L92.8579 43.6861L95.6863 40.8577ZM99 54.9998C99 59.4158 102.584 62.9998 107 62.9998C108.483 62.9998 109.872 62.5957 111.063 61.8917L108.034 58.8657C107.704 58.9532 107.358 58.9998 107 58.9998C104.787 58.9998 103 57.2131 103 54.9998C103 54.6423 103.047 54.2958 103.134 53.9663L100.107 50.9389C99.4037 52.1295 99 53.5178 99 54.9998ZM107 44.0907C114.273 44.0907 120.484 48.6143 123 54.9998C122.071 57.3574 120.638 59.4612 118.834 61.1773L114.729 57.0717C114.906 56.4108 115 55.7162 115 54.9998C115 50.5838 111.416 46.9998 107 46.9998C106.284 46.9998 105.589 47.0941 104.928 47.2711L102.378 44.7205C103.848 44.3101 105.398 44.0907 107 44.0907Z"
fill="white" />
<rect x="68" y="24" width="2" height="2" fill="white" />
<rect x="74" y="24" width="2" height="2" fill="white" />
<rect x="80" y="24" width="66" height="2" fill="white" />
<path d="M157 53.9998L181.249 95.9998H132.751L157 53.9998Z" fill="white" stroke="black" />
<path d="M157 88.9998L157 70.9998" stroke="black" />
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,36 @@
<svg width="200" height="140" viewBox="0 0 200 140" fill="none" xmlns="http://www.w3.org/2000/svg">
<g mask="url(#mask0_16559_24301)">
<path d="M30 62H118V122H30V62Z" fill="#97A3B7" />
<g filter="url(#filter0_f_16559_24301)">
<rect x="12" y="84" width="80" height="60" fill="#E3E6EB" />
</g>
<g filter="url(#filter1_f_16559_24301)">
<rect x="80" y="54" width="80" height="60" fill="#E3E6EB" />
</g>
<path d="M49 93L42 100L49 107" stroke="white" stroke-width="2" />
<path d="M69 107L76 100L69 93" stroke="white" stroke-width="2" />
<path d="M62.3647 87.4431L55.6355 112.557" stroke="white" stroke-width="2" />
</g>
<path opacity="0.9" d="M63 20H151V30H63V20Z" fill="currentcolor" />
<mask id="mask1_16559_24301" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="63" y="30" width="88" height="50">
<path d="M63 30H151V80H63V30Z" fill="currentcolor" />
</mask>
<g mask="url(#mask1_16559_24301)">
<path d="M63 30H151V80H63V30Z" fill="currentcolor" />
<g opacity="0.3" filter="url(#filter2_f_16559_24301)">
<path d="M30 62H118V122H30V62Z" fill="#97A3B7" />
</g>
</g>
<path fill-rule="evenodd" clip-rule="evenodd"
d="M105.25 41C112.015 41 117.5 46.4845 117.5 53.25C117.5 55.6827 116.791 57.9498 115.568 59.8558L121 65.2877L117.288 69L111.856 63.5681C109.95 64.7909 107.683 65.5 105.25 65.5C98.4845 65.5 93 60.0155 93 53.25C93 46.4845 98.4845 41 105.25 41ZM105.25 44.5C100.418 44.5 96.5 48.4175 96.5 53.25C96.5 58.0825 100.418 62 105.25 62C110.082 62 114 58.0825 114 53.25C114 48.4175 110.082 44.5 105.25 44.5Z"
fill="white" />
<rect x="68" y="24" width="2" height="2" fill="white" />
<rect x="74" y="24" width="2" height="2" fill="white" />
<rect x="80" y="24" width="66" height="2" fill="white" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M153 56C140.85 56 131 65.8497 131 78C131 82.6039 132.414 86.8776 134.832 90.4102L127 98.5L139.495 95.3681C143.222 98.2709 147.909 100 153 100C165.15 100 175 90.1503 175 78C175 65.8497 165.15 56 153 56Z"
fill="white" />
<path
d="M131 78L131.5 78V78L131 78ZM134.832 90.4102L135.191 90.758L135.475 90.4647L135.245 90.1278L134.832 90.4102ZM127 98.5L126.641 98.1522L125.422 99.411L127.122 98.985L127 98.5ZM139.495 95.3681L139.802 94.9736L139.61 94.8238L139.373 94.8831L139.495 95.3681ZM153 100L153 100.5L153 100.5L153 100ZM175 78L174.5 78L174.5 78L175 78ZM131.5 78C131.5 66.1259 141.126 56.5 153 56.5V55.5C140.574 55.5 130.5 65.5736 130.5 78L131.5 78ZM135.245 90.1278C132.882 86.6757 131.5 82.5 131.5 78H130.5C130.5 82.7079 131.946 87.0794 134.419 90.6926L135.245 90.1278ZM134.473 90.0624L126.641 98.1522L127.359 98.8478L135.191 90.758L134.473 90.0624ZM127.122 98.985L139.616 95.8531L139.373 94.8831L126.878 98.015L127.122 98.985ZM153 99.5C148.024 99.5 143.445 97.8105 139.802 94.9736L139.187 95.7626C143 98.7314 147.794 100.5 153 100.5V99.5ZM174.5 78C174.5 89.8741 164.874 99.5 153 99.5L153 100.5C165.426 100.5 175.5 90.4264 175.5 78L174.5 78ZM153 56.5C164.874 56.5 174.5 66.1259 174.5 78H175.5C175.5 65.5736 165.426 55.5 153 55.5V56.5Z"
fill="black" />
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -0,0 +1,32 @@
<svg width="200" height="140" viewBox="0 0 200 140" fill="none" xmlns="http://www.w3.org/2000/svg">
<g mask="url(#mask0_16559_24251)">
<path d="M68 48L106.105 70V114L68 136L29.8949 114V70L68 48Z" fill="#97A3B7" />
<g filter="url(#filter0_f_16559_24251)">
<rect x="46.3911" y="92" width="80" height="60" fill="#E3E6EB" />
</g>
<g filter="url(#filter1_f_16559_24251)">
<rect y="23" width="80" height="60" fill="#E3E6EB" />
</g>
</g>
<mask id="mask1_16559_24251" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="80" y="9" width="78" height="88">
<path d="M119 9L157.105 31V75L119 97L80.8949 75V31L119 9Z" fill="currentcolor" />
</mask>
<g mask="url(#mask1_16559_24251)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M80.895 31V75L119 97L157.105 75V31L119 53L80.895 31Z"
fill="currentcolor" />
<path opacity="0.9" d="M119 -35L157.105 -13L157.105 31.5L119 53.5L80.8952 31.5L80.895 -13L119 -35Z"
fill="currentcolor" />
<g opacity="0.3" filter="url(#filter2_f_16559_24251)">
<path d="M68 48L106.105 70V114L68 136L29.8949 114V70L68 48Z" fill="#97A3B7" />
</g>
</g>
<path
d="M143 68.822L147.867 85.875L148 86.3405L148.469 86.2228L165.671 81.911L153.336 94.6522L152.999 95L153.336 95.3478L165.671 108.089L148.469 103.777L148 103.659L147.867 104.125L143 121.178L138.133 104.125L138 103.659L137.531 103.777L120.329 108.089L132.664 95.3478L133.001 95L132.664 94.6522L120.329 81.911L137.531 86.2228L138 86.3405L138.133 85.875L143 68.822Z"
fill="white" stroke="black" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M123.243 35.0821L126.071 33.4493L123.243 31.8164L120.414 33.4493L123.243 35.0821ZM119 32.6329L121.828 31L114.757 26.9179L111.929 28.5507L119 32.6329ZM127.485 35.8986C122.806 38.6001 115.194 38.6001 110.515 35.8986C105.835 33.197 105.835 28.803 110.515 26.1014C115.194 23.3999 122.806 23.3999 127.485 26.1014C132.165 28.803 132.165 33.197 127.485 35.8986ZM107.686 24.4686C101.438 28.0756 101.438 33.9244 107.686 37.5314C113.934 41.1384 124.066 41.1384 130.314 37.5314C136.562 33.9244 136.562 28.0756 130.314 24.4686C124.066 20.8616 113.934 20.8616 107.686 24.4686Z"
fill="white" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M41.8989 86.2863L44.7272 87.9193L44.7275 94.4512L41.8992 92.8181L41.8989 86.2863ZM53.9194 93.2269L56.7477 94.86L56.7479 101.392L53.9196 99.7587L53.9194 93.2269ZM44.7281 107.515L41.8999 105.882L41.9 109.148L44.7283 110.781L44.7282 107.515L53.92 112.822L53.9201 116.088L56.7484 117.721L56.7483 114.455L53.9201 112.822L53.92 109.556L44.728 104.249L44.7281 107.515Z"
fill="white" />
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,33 @@
<svg width="200" height="140" viewBox="0 0 200 140" fill="none" xmlns="http://www.w3.org/2000/svg">
<g mask="url(#mask0_22_990)">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M144.569 105.61L96.5692 133.322L48.5693 105.61V83.7121L96.569 55.9995L144.569 83.7122V105.61Z"
fill="#97A3B7" />
<g filter="url(#filter0_f_22_990)">
<rect x="-3" y="33.9995" width="80" height="60" fill="#E3E6EB" />
</g>
<g filter="url(#filter1_f_22_990)">
<rect x="97" y="97.9995" width="80" height="60" fill="#E3E6EB" />
</g>
</g>
<mask id="mask1_22_990" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="53" y="16" width="86" height="69">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M113.357 42.715L129.829 33.2059C128.859 32.4995 127.789 31.823 126.643 31.1615C121.268 28.0584 114.723 26.0153 107.758 25.023C103.549 19.4606 97.5775 16.1248 90.4344 16.1945C83.6788 16.2821 74.9482 21.9412 68.9271 30.5602C68.096 30.975 67.2847 31.4099 66.4949 31.8659C52.1168 40.1664 49.5542 52.6155 59.0218 61.9309C57.9871 56.1259 58.712 51.0657 62.1231 45.7161C62.0653 46.3482 61.9127 50.143 61.8906 50.7834C61.2209 69.6969 76.9107 84.8409 88.03 84.7113C96.4806 84.6119 103.595 79.6976 108.349 72.0797C114.563 70.8487 120.438 68.786 125.443 65.8968C138.919 58.1167 142.01 46.7146 134.547 37.629L117.948 47.2113C119.71 50.8655 117.997 55.034 112.87 57.9936C107.744 60.9532 100.523 61.9424 94.1928 60.9252C91.3499 60.4563 88.6706 59.5834 86.4524 58.3029L86.4042 58.275L113.357 42.715ZM78.6546 53.7727C72.5285 49.7965 72.9717 43.5469 79.8498 39.5762C86.7276 35.6056 97.5532 35.3496 104.441 38.8864L78.6546 53.7727ZM93.5561 18.1703C98.1657 18.1302 102.284 20.5752 105.496 24.7401C97.0486 23.8219 88.1122 24.4143 79.9732 26.5054C83.672 21.3809 88.444 18.2301 93.5561 18.1703ZM91.3238 81.6169C85.471 81.685 80.3525 77.691 76.9473 71.2848C85.7921 73.6267 95.8719 74.0599 105.374 72.6022C101.618 78.1222 96.6601 81.5531 91.3238 81.6169Z"
fill="currentcolor" />
</mask>
<g mask="url(#mask1_22_990)">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M113.357 42.715L129.829 33.2059C128.859 32.4995 127.789 31.823 126.643 31.1615C121.268 28.0584 114.723 26.0153 107.758 25.023C103.549 19.4606 97.5775 16.1248 90.4344 16.1945C83.6788 16.2821 74.9482 21.9412 68.9271 30.5602C68.096 30.975 67.2847 31.4099 66.4949 31.8659C52.1168 40.1664 49.5542 52.6155 59.0218 61.9309C57.9871 56.1259 58.712 51.0657 62.1231 45.7161C62.0653 46.3482 61.9127 50.143 61.8906 50.7834C61.2209 69.6969 76.9107 84.8409 88.03 84.7113C96.4806 84.6119 103.595 79.6976 108.349 72.0797C114.563 70.8487 120.438 68.786 125.443 65.8968C138.919 58.1167 142.01 46.7146 134.547 37.629L117.948 47.2113C119.71 50.8655 117.997 55.034 112.87 57.9936C107.744 60.9532 100.523 61.9424 94.1928 60.9252C91.3499 60.4563 88.6706 59.5834 86.4524 58.3029L86.4042 58.275L113.357 42.715ZM78.6546 53.7727C72.5285 49.7965 72.9717 43.5469 79.8498 39.5762C86.7276 35.6056 97.5532 35.3496 104.441 38.8864L78.6546 53.7727ZM93.5561 18.1703C98.1657 18.1302 102.284 20.5752 105.496 24.7401C97.0486 23.8219 88.1122 24.4143 79.9732 26.5054C83.672 21.3809 88.444 18.2301 93.5561 18.1703ZM91.3238 81.6169C85.471 81.685 80.3525 77.691 76.9473 71.2848C85.7921 73.6267 95.8719 74.0599 105.374 72.6022C101.618 78.1222 96.6601 81.5531 91.3238 81.6169Z"
fill="currentcolor" />
<g opacity="0.3" filter="url(#filter2_f_22_990)">
<path d="M96.569 55.9995L144.569 83.7122V139.138L96.569 166.85L48.5692 139.138V83.7122L96.569 55.9995Z"
fill="#97A3B7" />
</g>
</g>
<ellipse cx="155" cy="78" rx="22" ry="22" transform="rotate(180 155 78)" fill="white" stroke="black" />
<path d="M155 83L155 65" stroke="black" />
<rect x="155" y="87" width="0.00390625" height="0.00390625" fill="#C4C4C4" stroke="black" stroke-width="2"
stroke-linejoin="round" />
<path d="M96.5693 112L96.5693 87.9995" stroke="white" stroke-width="2" />
<path d="M86.5693 97.9995L96.5693 87.9995L106.569 97.9995" stroke="white" stroke-width="2" />
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -0,0 +1,49 @@
<svg width="200" height="140" viewBox="0 0 200 140" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_216_313" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="80" y="9" width="78" height="88">
<path d="M119 9L157.105 31V75L119 97L80.8949 75V31L119 9Z" fill="currentColor"/>
</mask>
<g mask="url(#mask0_216_313)">
<path d="M119 9L157.105 31V75L119 97L80.8949 75V31L119 9Z" fill="currentColor"/>
<path opacity="0.9" d="M119 -35L157.105 -13V31L119 53L80.8949 31V-13L119 -35Z" fill="currentColor" />
<g opacity="0.3" filter="url(#filter0_f_216_313)">
<path d="M68 48L106.105 70V114L68 136L29.8949 114V70L68 48Z" fill="#97A3B7"/>
</g>
</g>
<mask id="mask1_216_313" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="29" y="48" width="78" height="88">
<path d="M68 48L106.105 70V114L68 136L29.8949 114V70L68 48Z" fill="#97A3B7"/>
</mask>
<g mask="url(#mask1_216_313)">
<path d="M68 48L106.105 70V114L68 136L29.8949 114V70L68 48Z" fill="#97A3B7"/>
<g filter="url(#filter1_f_216_313)">
<rect x="46.3906" y="92" width="80" height="60" fill="#E3E6EB"/>
</g>
<g filter="url(#filter2_f_216_313)">
<rect y="23" width="80" height="60" fill="#E3E6EB"/>
</g>
</g>
<path d="M41.8984 86.2866L44.7267 87.9197L44.727 94.4515L41.8987 92.8185L41.8984 86.2866Z" fill="white"/>
<path d="M53.9189 93.2273L56.7472 94.8603L56.7474 101.392L53.9191 99.7591L53.9189 93.2273Z" fill="white"/>
<path d="M44.7276 107.515L41.8994 105.882L41.8995 109.148L44.7278 110.781L44.7276 107.515L53.9195 112.823L53.9196 116.088L56.7479 117.721L56.7478 114.455L53.9195 112.823L53.9195 109.557L44.7275 104.249L44.7276 107.515Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M108.348 23.4792C106.188 25.9904 106.535 29.3829 109.395 31.5604C112.66 34.0461 117.963 34.007 121.24 31.4731C124.516 28.9392 124.526 24.8699 121.261 22.3841C118.401 20.2066 113.977 19.9666 110.721 21.6439L115.923 25.6047L113.55 27.4399L108.348 23.4792Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M120.865 33.0087L129.83 39.8346L132.203 37.9994L123.238 31.1735C122.917 31.521 122.554 31.8531 122.149 32.1657C121.745 32.4784 121.315 32.7594 120.865 33.0087ZM119.662 32.0932C120.123 31.8557 120.561 31.5798 120.967 31.2655C121.373 30.9513 121.73 30.6134 122.035 30.2579L122.035 30.2579C121.73 30.6134 121.373 30.9513 120.967 31.2655C120.561 31.5798 120.123 31.8557 119.662 32.0931L119.662 32.0932Z" fill="white"/>
<path d="M144 70L168.249 112H119.751L144 70Z" fill="white" stroke="#181818"/>
<path d="M144 100L144 82" stroke="#181818"/>
<path d="M144 105H144.004V105.004H144V105Z" stroke="#181818" stroke-width="2" stroke-linejoin="round"/>
<defs>
<filter id="filter0_f_216_313" x="23.8949" y="42" width="88.2102" height="100" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="3" result="effect1_foregroundBlur_216_313"/>
</filter>
<filter id="filter1_f_216_313" x="-3.60938" y="42" width="180" height="160" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="25" result="effect1_foregroundBlur_216_313"/>
</filter>
<filter id="filter2_f_216_313" x="-50" y="-27" width="180" height="160" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="25" result="effect1_foregroundBlur_216_313"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1,23 @@
<svg width="200" height="140" viewBox="0 0 200 140" fill="none" xmlns="http://www.w3.org/2000/svg">
<g mask="url(#mask0_21_716)">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M33 46.843L96.3214 119L159.643 46.843C142.742 31.9998 120.583 23 96.3214 23C72.0601 23 49.9009 31.9998 33 46.843Z"
fill="#97A3B7" />
<g filter="url(#filter0_f_21_716)">
<rect x="95" y="21" width="80" height="60" fill="#E3E6EB" />
</g>
<g filter="url(#filter1_f_21_716)">
<rect x="-7" y="43" width="80" height="60" fill="#E3E6EB" />
</g>
</g>
<path
d="M72.8122 63.6882L69.6548 66.8455L75.9009 73.0916C71.2469 75.1648 66.9663 77.925 63.188 81.2433L96.3213 119L108.234 105.425L114.647 111.837L117.804 108.68L80.4504 71.3261C80.4505 71.3261 80.4503 71.3261 80.4504 71.3261L72.8122 63.6882Z"
fill="currentcolor" />
<path
d="M129.455 81.2433L114.137 98.6982L85.3974 69.9585C88.9142 69.1786 92.5697 68.7674 96.3213 68.7674C109.016 68.7674 120.611 73.4766 129.455 81.2433Z"
fill="currentcolor" />
<path
d="M152 21.822L156.867 38.875L157 39.3405L157.469 39.2228L174.671 34.911L162.336 47.6522L161.999 48L162.336 48.3478L174.671 61.089L157.469 56.7772L157 56.6595L156.867 57.125L152 74.178L147.133 57.125L147 56.6595L146.531 56.7772L129.329 61.089L141.664 48.3478L142.001 48L141.664 47.6522L129.329 34.911L146.531 39.2228L147 39.3405L147.133 38.875L152 21.822Z"
fill="white" stroke="black" />
<path d="M101 31L90 42L101 53L93 61" stroke="white" stroke-width="2" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,13 @@
<svg width="88" height="48" viewBox="0 0 88 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0H88V48H0V0Z" fill="var(--td-component-border)"/>
<path d="M42.8627 14.0518V16.7601H44.4877V14.0518H42.8627Z" fill="var(--td-text-color-primary)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M38.3488 23.9824C38.3488 21.0407 40.7335 18.656 43.6752 18.656C46.6168 18.656 49.0015 21.0407 49.0015 23.9824C49.0015 26.9241 46.6168 29.3088 43.6752 29.3088C40.7335 29.3088 38.3488 26.9241 38.3488 23.9824ZM43.6752 20.281C41.6309 20.281 39.9738 21.9382 39.9738 23.9824C39.9738 26.0266 41.6309 27.6838 43.6752 27.6838C45.7194 27.6838 47.3766 26.0266 47.3766 23.9824C47.3766 21.9382 45.7194 20.281 43.6752 20.281Z" fill="var(--td-text-color-primary)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M52.208 26.781H49.5867L47.5262 33.48L49.0794 33.9577L49.5903 32.2968H52.2045L52.7154 33.9577L54.2686 33.48L52.208 26.781ZM51.7047 30.6718L51.0077 28.406H50.787L50.0901 30.6718H51.7047Z" fill="var(--td-text-color-primary)"/>
<path d="M48.2077 18.3009L50.1225 16.3861L51.2715 17.5351L49.3567 19.4499L48.2077 18.3009Z" fill="var(--td-text-color-primary)"/>
<path d="M53.6057 23.1699H50.8974V24.7949H53.6057V23.1699Z" fill="var(--td-text-color-primary)"/>
<path d="M44.4877 31.2045V33.9129H42.8627V31.2045H44.4877Z" fill="var(--td-text-color-primary)"/>
<path d="M37.2279 31.5786L39.1427 29.6638L37.9936 28.5147L36.0788 30.4295L37.2279 31.5786Z" fill="var(--td-text-color-primary)"/>
<path d="M36.453 24.7949H33.7446V23.1699H36.453V24.7949Z" fill="var(--td-text-color-primary)"/>
<path d="M36.0788 17.5351L37.9936 19.4499L39.1427 18.3009L37.2279 16.3861L36.0788 17.5351Z" fill="var(--td-text-color-primary)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,5 @@
<svg width="88" height="48" viewBox="0 0 88 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0H88V48H0V0Z" fill="#13161B"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M52.5327 26.8699C52.0346 26.9554 51.5225 26.9999 51 26.9999C46.0294 26.9999 42 22.9705 42 17.9999C42 16.9964 42.1642 16.0313 42.4673 15.1299C38.2268 15.8574 35 19.5518 35 23.9999C35 28.9705 39.0294 32.9999 44 32.9999C47.9671 32.9999 51.3346 30.4332 52.5327 26.8699Z" fill="#949EAA"/>
</svg>

After

Width:  |  Height:  |  Size: 495 B

View File

@ -0,0 +1,13 @@
<svg width="88" height="48" viewBox="0 0 88 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="88" height="48" fill="var(--td-component-border)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M43.9999 20.583C42.1129 20.583 40.5832 22.1127 40.5832 23.9997C40.5832 25.8867 42.1129 27.4163 43.9999 27.4163C45.8869 27.4163 47.4166 25.8867 47.4166 23.9997C47.4166 22.1127 45.8869 20.583 43.9999 20.583ZM39.0832 23.9997C39.0832 21.2843 41.2845 19.083 43.9999 19.083C46.7153 19.083 48.9166 21.2843 48.9166 23.9997C48.9166 26.7151 46.7153 28.9163 43.9999 28.9163C41.2845 28.9163 39.0832 26.7151 39.0832 23.9997Z" fill="var(--td-text-color-primary)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M43.2499 17.333V14.833H44.7499V17.333H43.2499Z" fill="var(--td-text-color-primary)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.1838 18.7552L49.9513 16.9877L51.0119 18.0483L49.2444 19.8158L48.1838 18.7552Z" fill="var(--td-text-color-primary)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M50.6666 23.2497H53.1666V24.7497H50.6666V23.2497Z" fill="var(--td-text-color-primary)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M49.2444 28.1835L51.0119 29.951L49.9513 31.0117L48.1838 29.2442L49.2444 28.1835Z" fill="var(--td-text-color-primary)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M44.7499 30.6663V33.1663H43.2499V30.6663H44.7499Z" fill="var(--td-text-color-primary)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M39.8162 29.2442L38.0487 31.0117L36.988 29.951L38.7555 28.1835L39.8162 29.2442Z" fill="var(--td-text-color-primary)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M37.3333 24.7497H34.8333V23.2497H37.3333V24.7497Z" fill="var(--td-text-color-primary)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M38.7555 19.8158L36.988 18.0483L38.0487 16.9877L39.8162 18.7552L38.7555 19.8158Z" fill="var(--td-text-color-primary)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,41 @@
<svg width="1em" height="1em" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2557_23660)">
<path d="M9.14038 8.25177H3.53919C3.45719 8.25173 3.37619 8.23372 3.30188 8.19901C3.22758 8.16431 3.16179 8.11375 3.10912 8.05089C3.05646 7.98803 3.01819 7.91439 2.99703 7.83516C2.97587 7.75593 2.97234 7.67303 2.98665 7.59228L3.87785 2.44561C3.90079 2.32052 3.96685 2.20742 4.06455 2.12601C4.16225 2.04459 4.2854 2.00001 4.41258 2.00001H10.2054L9.14038 8.25177Z" fill="url(#paint0_linear_2557_23660)"/>
<path d="M13.0301 21.4727H6.77832L9.11772 8.2473H15.3695L13.0301 21.4727Z" fill="url(#paint1_linear_2557_23660)"/>
<path d="M11.4707 27.7245H6.34183C6.26051 27.7236 6.18034 27.7051 6.10678 27.6704C6.03322 27.6358 5.96801 27.5856 5.91557 27.5235C5.86313 27.4613 5.82471 27.3886 5.80292 27.3102C5.78113 27.2319 5.77648 27.1497 5.78929 27.0694L6.78297 21.4861H13.0214L11.9965 27.2789C11.9738 27.4025 11.9091 27.5144 11.8132 27.5956C11.7174 27.6769 11.5963 27.7224 11.4707 27.7245Z" fill="url(#paint2_linear_2557_23660)"/>
<path d="M26.666 8.25176H9.14062L10.2457 2.01337H27.5795C27.6613 2.01417 27.7419 2.03268 27.8158 2.06763C27.8898 2.10258 27.9552 2.15313 28.0077 2.21581C28.0603 2.27849 28.0986 2.35179 28.12 2.43069C28.1415 2.50959 28.1456 2.59221 28.1321 2.67285L27.2186 7.81953C27.1941 7.94607 27.1246 8.0595 27.0231 8.13892C26.9216 8.21834 26.7948 8.25841 26.666 8.25176V8.25176Z" fill="url(#paint3_linear_2557_23660)"/>
</g>
<defs>
<linearGradient id="paint0_linear_2557_23660" x1="2.71742" y1="5.12812" x2="10.0624" y2="4.98122" gradientUnits="userSpaceOnUse">
<stop stop-color="#0062FF"/>
<stop offset="0.26" stop-color="#006AFF"/>
<stop offset="0.68" stop-color="#0081FF"/>
<stop offset="1" stop-color="#0097FF"/>
</linearGradient>
<linearGradient id="paint1_linear_2557_23660" x1="12.383" y1="7.62346" x2="9.12269" y2="22.0419" gradientUnits="userSpaceOnUse">
<stop stop-color="#0097FF"/>
<stop offset="0.32" stop-color="#0081FF"/>
<stop offset="0.74" stop-color="#006AFF"/>
<stop offset="1" stop-color="#0062FF"/>
</linearGradient>
<linearGradient id="paint2_linear_2557_23660" x1="5.63057" y1="27.3635" x2="12.8582" y2="21.4727" gradientUnits="userSpaceOnUse">
<stop stop-color="#009EFF"/>
<stop offset="0.31" stop-color="#00A3FF"/>
<stop offset="0.71" stop-color="#00B3FF"/>
<stop offset="1" stop-color="#00C3FF"/>
</linearGradient>
<linearGradient id="paint3_linear_2557_23660" x1="8.84911" y1="5.12811" x2="27.9399" y2="4.74629" gradientUnits="userSpaceOnUse">
<stop offset="0.03" stop-color="#ECFFFE"/>
<stop offset="0.19" stop-color="#AFF1D9"/>
<stop offset="0.34" stop-color="#79E5B9"/>
<stop offset="0.49" stop-color="#4EDB9F"/>
<stop offset="0.63" stop-color="#2CD48A"/>
<stop offset="0.77" stop-color="#14CE7C"/>
<stop offset="0.89" stop-color="#05CB73"/>
<stop offset="1" stop-color="#00CA70"/>
</linearGradient>
<clipPath id="clip0_2557_23660">
<rect width="25.1407" height="25.72" fill="white" transform="translate(2.97754 2)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,35 @@
<template>
<div :style="style" class="color-container" />
</template>
<script lang="ts">
import { DEFAULT_COLOR_OPTIONS } from '@/config/color';
const panelColor =
'conic-gradient(from 90deg at 50% 50%, #FF0000 -19.41deg, #FF0000 18.76deg, #FF8A00 59.32deg, #FFE600 99.87deg, #14FF00 141.65deg, #00A3FF 177.72deg, #0500FF 220.23deg, #AD00FF 260.13deg, #FF00C7 300.69deg, #FF0000 340.59deg, #FF0000 378.76deg)';
export default {
name: 'Color',
props: {
value: {
type: String,
default: 'default',
},
},
computed: {
style() {
const { value } = this;
return {
background: DEFAULT_COLOR_OPTIONS.indexOf(value) > -1 ? value : panelColor,
};
},
},
};
</script>
<style lang="less" scoped>
.color-container {
width: 24px;
height: 24px;
border-radius: 50%;
display: inline-block;
}
</style>

View File

@ -0,0 +1,121 @@
<template>
<t-card theme="poster2" :bordered="false">
<template #avatar>
<t-avatar size="56px">
<template #icon>
<shop-icon v-if="product.type === 1" />
<calendar-icon v-if="product.type === 2" />
<service-icon v-if="product.type === 3" />
<user-avatar-icon v-if="product.type === 4" />
<laptop-icon v-if="product.type === 5" />
</template>
</t-avatar>
</template>
<template #status>
<t-tag :theme="product.isSetup ? 'success' : 'default'" :disabled="!product.isSetup">{{
product.isSetup ? '已启用' : '已停用'
}}</t-tag>
</template>
<template #content>
<p class="list-card-item_detail--name">{{ product.name }}</p>
<p class="list-card-item_detail--desc">{{ product.description }}</p>
</template>
<template #footer>
<t-avatar-group cascading="left-up" :max="2">
<t-avatar>{{ typeMap[product.type - 1] }}</t-avatar>
<t-avatar
><template #icon>
<add-icon />
</template>
</t-avatar>
</t-avatar-group>
</template>
<template #actions>
<t-dropdown
:disabled="!product.isSetup"
trigger="click"
:options="[
{
content: '管理',
value: 'manage',
onClick: () => handleManageProduct(product),
},
{
content: '删除',
value: 'delete',
onClick: () => handleDeleteItem(product),
},
]"
>
<t-button theme="default" :disabled="!product.isSetup" shape="square" variant="text">
<more-icon />
</t-button>
</t-dropdown>
</template>
</t-card>
</template>
<script lang="ts">
import { ShopIcon, CalendarIcon, ServiceIcon, UserAvatarIcon, LaptopIcon, MoreIcon, AddIcon } from 'tdesign-icons-vue';
export default {
name: 'ListCard',
components: {
ShopIcon,
CalendarIcon,
ServiceIcon,
UserAvatarIcon,
LaptopIcon,
MoreIcon,
AddIcon,
},
props: {
product: {
type: Object,
},
},
data() {
return { typeMap: ['A', 'B', 'C', 'D', 'E'] };
},
methods: {
handleManageProduct(product) {
this.$emit('manage-product', product);
},
handleDeleteItem(product) {
this.$emit('delete-item', product);
},
},
};
</script>
<style lang="less" scoped>
@import '@/style/variables';
.list-card-item {
display: flex;
flex-direction: column;
cursor: pointer;
&_detail {
min-height: 140px;
&--name {
margin-bottom: 8px;
font-size: 16px;
font-weight: 400;
color: var(--td-text-color-primary);
}
&--desc {
color: var(--td-text-color-secondary);
font-size: 12px;
line-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
height: 40px;
}
}
}
</style>

View File

@ -0,0 +1,118 @@
<template>
<div class="result-container">
<div class="result-bg-img">
<component :is="dynamicComponent"></component>
</div>
<div class="result-title">{{ title }}</div>
<div class="result-tip">{{ tip }}</div>
<slot />
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import Result403Icon from '@/assets/assets-result-403.svg';
import Result404Icon from '@/assets/assets-result-404.svg';
import Result500Icon from '@/assets/assets-result-500.svg';
import ResultIeIcon from '@/assets/assets-result-ie.svg';
import ResultWifiIcon from '@/assets/assets-result-wifi.svg';
import ResultMaintenanceIcon from '@/assets/assets-result-maintenance.svg';
export default Vue.extend({
name: 'Result',
props: {
bgUrl: {
type: String,
default: '',
},
title: {
type: String,
default: '',
},
tip: {
type: String,
default: '',
},
type: {
type: String,
default: '',
},
},
computed: {
dynamicComponent() {
switch (this.type) {
case '403':
return Result403Icon;
case '404':
return Result404Icon;
case '500':
return Result500Icon;
case 'ie':
return ResultIeIcon;
case 'wifi':
return ResultWifiIcon;
case 'maintenance':
return ResultMaintenanceIcon;
default:
return Result403Icon;
}
},
},
});
</script>
<style lang="less" scoped>
@import '@/style/variables';
.result {
&-link {
color: var(--td-brand-color);
text-decoration: none;
cursor: pointer;
&:hover {
color: var(--td-brand-color);
}
&:active {
color: var(--td-brand-color);
}
&--active {
color: var(--td-brand-color);
}
&:focus {
text-decoration: none;
}
}
&-container {
min-height: 400px;
height: 75vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 24px;
}
&-bg-img {
width: 200px;
color: var(--td-brand-color);
}
&-title {
color: var(--td-text-color-primary);
font: var(--td-font-title-large);
font-weight: 500;
font-style: normal;
margin-top: 8px;
}
&-tip {
margin: 8px 0 32px;
font: var(--td-font-body-medium);
color: var(--td-text-color-secondary);
}
}
</style>

View File

@ -0,0 +1,49 @@
<template>
<img :class="className" :src="url" />
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
name: 'thumbnail',
props: {
url: {
type: String,
default: '',
},
type: {
type: String,
default: 'layout',
},
},
computed: {
className() {
return [
'thumbnail-container',
{
'thumbnail-circle': this.type === 'circle',
'thumbnail-layout': this.type === 'layout',
},
];
},
},
});
</script>
<style lang="less" scoped>
@import url('@/style/index.less');
.thumbnail {
&-container {
display: inline-block;
}
&-circle {
border-radius: var(--td-radius-circle);
}
&-layout {
width: 88px;
height: 48px;
}
}
</style>

View File

@ -0,0 +1,105 @@
<template>
<span :class="containerCls">
<span :class="iconCls">
<svg
v-if="type === 'down'"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M11.5 8L8 11.5L4.5 8" stroke="currentColor" stroke-width="1.5" />
<path d="M8 11L8 4" stroke="currentColor" stroke-width="1.5" />
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" v-else>
<path d="M4.5 8L8 4.5L11.5 8" stroke="currentColor" stroke-width="1.5" />
<path d="M8 5V12" stroke="currentColor" stroke-width="1.5" />
</svg>
</span>
<span>{{ describe }}</span>
</span>
</template>
<script>
import Vue from 'vue';
export default Vue.extend({
name: 'Trend',
props: {
type: String,
describe: [String, Number],
isReverseColor: Boolean,
},
computed: {
containerCls() {
return [
'trend-container',
{
'trend-container__reverse': this.isReverseColor,
'trend-container__up': !this.isReverseColor && this.type === 'up',
'trend-container__down': !this.isReverseColor && this.type === 'down',
},
];
},
iconCls() {
return ['trend-icon-container'];
},
},
});
</script>
<style lang="less" scoped>
@import '@/style/variables.less';
.trend {
&-container {
&__up {
color: var(--td-error-color);
display: inline-flex;
align-items: center;
justify-content: center;
.trend-icon-container {
background: var(--td-error-color-2);
margin-right: 8px;
}
}
&__down {
color: var(--td-success-color);
display: inline-flex;
align-items: center;
justify-content: center;
.trend-icon-container {
background: var(--td-success-color-2);
margin-right: 8px;
}
}
&__reverse {
color: #ffffff;
display: inline-flex;
align-items: center;
justify-content: center;
.trend-icon-container {
background: var(--td-brand-color-5);
margin-right: 8px;
}
}
.trend-icon-container {
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
}
}
}
</style>

30
src/config/color.ts Normal file
View File

@ -0,0 +1,30 @@
export type TColorToken = Record<string, string>;
export type TColorSeries = Record<string, TColorToken>;
// TODO: 中性色暂时固定 待tvision-color生成带色彩倾向的中性色
export const LIGHT_CHART_COLORS = {
textColor: 'rgba(0, 0, 0, 0.9)',
placeholderColor: 'rgba(0, 0, 0, 0.35)',
borderColor: '#dcdcdc',
containerColor: '#fff',
};
export const DARK_CHART_COLORS = {
textColor: 'rgba(255, 255, 255, 0.9)',
placeholderColor: 'rgba(255, 255, 255, 0.35)',
borderColor: '#5e5e5e',
containerColor: '#242424',
};
export type TChartColor = typeof LIGHT_CHART_COLORS;
export const DEFAULT_COLOR_OPTIONS = [
'#0052D9',
'#0594FA',
'#00A870',
'#EBB105',
'#ED7B2F',
'#E34D59',
'#ED49B4',
'#834EC2',
];

2
src/config/global.ts Normal file
View File

@ -0,0 +1,2 @@
export const prefix = 'tdesign-starter';
export const TOKEN_NAME = 'tdesign-starter';

26
src/config/host.ts Normal file
View File

@ -0,0 +1,26 @@
export default {
development: {
// 开发环境接口请求
API: '',
// 开发环境 cdn 路径
CDN: '',
},
test: {
// 测试环境接口地址
API: 'https://service-exndqyuk-1257786608.gz.apigw.tencentcs.com',
// 测试环境 cdn 路径
CDN: '',
},
release: {
// 正式环境接口地址
API: 'https://service-bv448zsw-1257786608.gz.apigw.tencentcs.com',
// 正式环境 cdn 路径
CDN: '',
},
site: {
// 正式环境接口地址
API: 'https://service-bv448zsw-1257786608.gz.apigw.tencentcs.com',
// 正式环境 cdn 路径
CDN: '',
},
};

14
src/config/style.ts Normal file
View File

@ -0,0 +1,14 @@
export default {
showFooter: true,
isSidebarCompact: false,
showBreadcrumb: false,
mode: 'light',
layout: 'side',
splitMenu: false,
isFooterAside: false,
isSidebarFixed: true,
isHeaderFixed: true,
isUseTabsRouter: false,
showHeader: true,
brandTheme: '#0052D9',
};

46
src/constants/index.ts Normal file
View File

@ -0,0 +1,46 @@
interface IOption {
value: number | string;
label: string;
}
// 合同状态枚举
export const CONTRACT_STATUS = {
FAIL: 0,
AUDIT_PENDING: 1,
EXEC_PENDING: 2,
EXECUTING: 3,
FINISH: 4,
};
export const CONTRACT_STATUS_OPTIONS: Array<IOption> = [
{ value: CONTRACT_STATUS.FAIL, label: '审核失败' },
{ value: CONTRACT_STATUS.AUDIT_PENDING, label: '待审核' },
{ value: CONTRACT_STATUS.EXEC_PENDING, label: '待履行' },
{ value: CONTRACT_STATUS.EXECUTING, label: '审核成功' },
{ value: CONTRACT_STATUS.FINISH, label: '已完成' },
];
// 合同类型枚举
export const CONTRACT_TYPES = {
MAIN: 0,
SUB: 1,
SUPPLEMENT: 2,
};
export const CONTRACT_TYPE_OPTIONS: Array<IOption> = [
{ value: CONTRACT_TYPES.MAIN, label: '主合同' },
{ value: CONTRACT_TYPES.SUB, label: '子合同' },
{ value: CONTRACT_TYPES.SUPPLEMENT, label: '补充合同' },
];
// 合同收付类型枚举
export const CONTRACT_PAYMENT_TYPES = {
PAYMENT: 0,
RECIPT: 1,
};
// 通知的优先级对应的TAG类型
export const NOTIFICATION_TYPES = {
low: 'primary',
middle: 'warning',
high: 'danger',
};

39
src/interface.ts Normal file
View File

@ -0,0 +1,39 @@
import STYLE_CONFIG from '@/config/style';
export interface ResDataType {
code: number;
data: any;
}
export interface MenuRoute {
path: string;
title?: string;
icon?:
| string
| {
render: () => void;
};
redirect?: string;
children: MenuRoute[];
meta: any;
}
export type ModeType = 'dark' | 'light';
export type SettingType = typeof STYLE_CONFIG;
export type ClassName = { [className: string]: any } | ClassName[] | string;
export type CommonObjType = {
[key: string]: string | number;
};
export interface NotificationItem {
id: string;
content: string;
type: string;
status: boolean;
collected: boolean;
date: string;
quality: 'high' | 'low' | 'middle';
}

12
src/layouts/blank.vue Normal file
View File

@ -0,0 +1,12 @@
<template>
<div class="tdesign-wrapper">
<router-view />
</div>
</template>
<style lang="less" scoped>
.tdesign-wrapper {
height: 100vh;
display: flex;
flex-direction: column;
}
</style>

View File

@ -0,0 +1,39 @@
<template>
<t-breadcrumb :max-item-width="'150'" class="tdesign-breadcrumb">
<t-breadcrumbItem v-for="item in crumbs" :key="item.to" :to="item.to">
{{ item.title }}
</t-breadcrumbItem>
</t-breadcrumb>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
name: 'TdesignStarterBreadcrumb',
props: {
isVisible: Boolean,
},
computed: {
crumbs() {
const pathArray = this.$route.path.split('/');
pathArray.shift();
const breadcrumbs = pathArray.reduce((breadcrumbArray, path, idx) => {
breadcrumbArray.push({
path,
to: breadcrumbArray[idx - 1] ? `/${breadcrumbArray[idx - 1].path}/${path}` : `/${path}`,
title: this.$route.matched[idx].meta.title || path,
});
return breadcrumbArray;
}, []);
return breadcrumbs;
},
},
});
</script>
<style scoped>
.tdesign-breadcrumb {
margin-bottom: 8px;
}
</style>

View File

@ -0,0 +1,43 @@
<template>
<transition name="fade" mode="out-in">
<keep-alive :include="this.aliveViews">
<router-view v-if="!this.isRefreshing" />
</keep-alive>
</transition>
</template>
<script lang="ts">
import { mapGetters } from 'vuex';
import isBoolean from 'lodash/isBoolean';
import isUndefined from 'lodash/isUndefined';
export default {
computed: {
...mapGetters({
tabRouterList: 'tabRouter/tabRouterList',
isRefreshing: 'tabRouter/isRefreshing',
isUseTabsRouter: 'setting/isUseTabsRouter',
}),
aliveViews() {
return this.tabRouterList
.filter((route) => {
const keepAliveConfig = route.meta?.keepAlive;
const isRouteKeepAlive = isUndefined(keepAliveConfig) || (isBoolean(keepAliveConfig) && keepAliveConfig); // keepalive
return route.isAlive && isRouteKeepAlive;
})
.map((route) => route.name);
},
},
};
</script>
<style lang="less" scoped>
@import '@/style/variables';
.fade-leave-active,
.fade-enter-active {
transition: opacity @anim-duration-slow @anim-time-fn-easing;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>

View File

@ -0,0 +1,27 @@
<template>
<div :class="prefix + '-footer'">Copyright © 2021-{{ new Date().getFullYear() }} Tencent. All Rights Reserved</div>
</template>
<script>
import { prefix } from '@/config/global';
import Vue from 'vue';
export default Vue.extend({
name: `${prefix}-footer`,
data() {
return {
prefix,
};
},
});
</script>
<style lang="less" scoped>
@import '@/style/variables';
.@{starter-prefix}-footer {
color: var(--td-text-color-placeholder);
line-height: 20px;
text-align: center;
}
</style>

View File

@ -0,0 +1,334 @@
<template>
<div :class="layoutCls">
<t-head-menu :class="menuCls" :theme="theme" expandType="popup" :value="active">
<template #logo>
<span v-if="showLogo" class="header-logo-container" @click="handleNav('/dashboard/base')">
<logo-full class="t-logo" />
</span>
<div v-else class="header-operate-left">
<!-- <t-button theme="default" shape="square" variant="text" @click="changeCollapsed">-->
<!-- <view-list-icon class="collapsed-icon" />-->
<!-- </t-button>-->
<!-- <search :layout="layout" />-->
</div>
</template>
<menu-content v-show="layout !== 'side'" class="header-menu" :navData="menu" />
<template #operations>
<div class="operations-container">
<!-- 搜索框 -->
<!-- <search v-if="layout !== 'side'" :layout="layout" />-->
<!-- 全局通知 -->
<!-- <notice />-->
<!-- <t-tooltip placement="bottom" content="代码仓库">-->
<!-- <t-button theme="default" shape="square" variant="text" @click="navToGitHub">-->
<!-- <logo-github-icon />-->
<!-- </t-button>-->
<!-- </t-tooltip>-->
<!-- <t-tooltip placement="bottom" content="帮助文档">-->
<!-- <t-button theme="default" shape="square" variant="text" @click="navToHelper">-->
<!-- <help-circle-icon />-->
<!-- </t-button>-->
<!-- </t-tooltip>-->
<t-dropdown :min-column-width="125" trigger="click">
<template #dropdown>
<t-dropdown-menu>
<t-dropdown-item class="operations-dropdown-container-item" @click="handleNav('/user/index')">
<user-circle-icon />个人中心
</t-dropdown-item>
<t-dropdown-item class="operations-dropdown-container-item" @click="handleLogout">
<poweroff-icon />退出登录
</t-dropdown-item>
</t-dropdown-menu>
</template>
<t-button class="header-user-btn" theme="default" variant="text">
<template #icon>
<user-circle-icon class="header-user-avatar" />
</template>
<div class="header-user-account">{{association.association_name}}</div>
<template #suffix>
<chevron-down-icon />
</template>
</t-button>
</t-dropdown>
<t-tooltip placement="bottom" content="系统设置">
<t-button theme="default" shape="square" variant="text" @click="toggleSettingPanel">
<setting-icon />
</t-button>
</t-tooltip>
</div>
</template>
</t-head-menu>
</div>
</template>
<script>
import store from '@/store'
import Vue from 'vue';
import {
ViewListIcon,
LogoGithubIcon,
HelpCircleIcon,
UserCircleIcon,
PoweroffIcon,
SettingIcon,
ChevronDownIcon,
} from 'tdesign-icons-vue';
import { prefix } from '@/config/global';
import LogoFull from '@/assets/assets-logo-full.svg';
import Notice from './Notice.vue';
import Search from './Search.vue';
import MenuContent from './MenuContent.vue';
export default Vue.extend({
components: {
MenuContent,
LogoFull,
Notice,
Search,
ViewListIcon,
LogoGithubIcon,
HelpCircleIcon,
UserCircleIcon,
PoweroffIcon,
SettingIcon,
ChevronDownIcon,
},
props: {
theme: String,
layout: {
type: String,
default: 'top',
},
showLogo: {
type: Boolean,
default: true,
},
menu: {
type: Array,
},
isFixed: {
type: Boolean,
default: false,
},
isCompact: {
type: Boolean,
default: false,
},
maxLevel: {
type: Number,
default: 3,
},
},
data() {
return {
prefix,
visibleNotice: false,
isSearchFocus: false,
association:{}
};
},
mounted() {
if(typeof (store.state.user.association)=='object'){
this.association=store.state.user.association;
}else{
this.association=JSON.parse(store.state.user.association);
}
},
computed: {
active() {
if (!this.$route.path) {
return '';
}
return this.$route.path
.split('/')
.filter((item, index) => index <= this.maxLevel && index > 0)
.map((item) => `/${item}`)
.join('');
},
showMenu() {
return !(this.layout === 'mix' && this.showLogo === 'side');
},
layoutCls() {
return [`${this.prefix}-header-layout`];
},
menuCls() {
return [
{
[`${this.prefix}-header-menu`]: !this.isFixed,
[`${this.prefix}-header-menu-fixed`]: this.isFixed,
[`${this.prefix}-header-menu-fixed-side`]: this.layout === 'side' && this.isFixed,
[`${this.prefix}-header-menu-fixed-side-compact`]: this.layout === 'side' && this.isFixed && this.isCompact,
},
];
},
},
methods: {
toggleSettingPanel() {
this.$store.commit('setting/toggleSettingPanel', true);
},
handleLogout() {
this.$store.commit('user/removeAssociation');
this.$store.commit('user/removeToken');
this.$router.push(`/login`);
//this.$router.push(`/login?redirect=${this.$router.history.current.fullPath}`);
},
changeCollapsed() {
this.$store.commit('setting/toggleSidebarCompact');
},
handleNav(url) {
this.$router.push(url);
},
navToGitHub() {
window.open('https://github.com/Tencent/tdesign-vue-starter');
},
navToHelper() {
window.open('http://tdesign.tencent.com/starter/docs/get-started');
},
},
});
</script>
<style lang="less">
@import '@/style/variables.less';
.header-menu {
flex: 1 1 1;
display: inline-flex;
}
.operations-container {
display: flex;
align-items: center;
margin-right: 12px;
.t-popup__reference {
display: flex;
align-items: center;
justify-content: center;
}
.t-button {
margin: 0 8px;
&.header-user-btn {
margin: 0;
}
}
.t-icon {
font-size: 20px;
&.general {
margin-right: 16px;
}
}
}
.header-operate-left {
display: flex;
margin-left: 20px;
align-items: normal;
line-height: 0;
.collapsed-icon {
font-size: 20px;
}
}
.header-logo-container {
width: 184px;
height: 26px;
display: flex;
margin-left: 24px;
color: var(--td-text-color-primary);
.t-logo {
width: 100%;
height: 100%;
&:hover {
cursor: pointer;
}
}
&:hover {
cursor: pointer;
}
}
.header-user-account {
display: inline-flex;
align-items: center;
color: var(--td-text-color-primary);
.t-icon {
margin-left: 4px;
font-size: 16px;
}
}
.t-head-menu__inner {
border-bottom: 1px solid var(--td-border-level-1-color);
}
.t-menu--light {
.header-user-account {
color: var(--td-text-color-primary);
}
}
.t-menu--dark {
.t-head-menu__inner {
border-bottom: 1px solid var(--td-gray-color-10);
}
.header-user-account {
color: rgba(255, 255, 255, 0.55);
}
.t-button {
--ripple-color: var(--td-gray-color-10) !important;
&:hover {
background: var(--td-gray-color-12) !important;
}
}
}
.operations-dropdown-container-item {
width: 100%;
display: flex;
align-items: center;
.t-icon {
margin-right: 8px;
}
.t-dropdown__item {
.t-dropdown__item__content {
display: flex;
justify-content: center;
}
.t-dropdown__item__content__text {
display: flex;
align-items: center;
font-size: 14px;
}
}
.t-dropdown__item {
width: 100%;
margin-bottom: 0px;
}
&:last-child {
.t-dropdown__item {
margin-bottom: 8px;
}
}
}
</style>

View File

@ -0,0 +1,168 @@
<template>
<t-layout :class="[`${prefix}-layout`]">
<t-tabs
v-if="isUseTabsRouter"
theme="card"
:class="`${prefix}-layout-tabs-nav`"
:value="$route.path"
@change="handleChangeCurrentTab"
:style="{ position: 'sticky', top: 0, width: '100%' }"
>
<t-tab-panel
v-for="(route, idx) in tabRouterList"
:value="route.path"
:key="`${route.path}_${idx}`"
:removable="!route.isHome"
@remove="() => handleRemove(route.path, idx)"
>
<template #label>
<t-dropdown
trigger="context-menu"
:minColumnWidth="128"
:popupProps="{
overlayClassName: 'route-tabs-dropdown',
onVisibleChange: (visible, ctx) => handleTabMenuClick(visible, ctx, route.path),
visible: activeTabPath === route.path,
}"
>
<template v-if="!route.isHome">
{{ route.title }}
</template>
<home-icon v-else />
<template #dropdown>
<t-dropdown-menu>
<t-dropdown-item @click="() => handleRefresh(route.path, idx)">
<refresh-icon />
刷新
</t-dropdown-item>
<t-dropdown-item v-if="idx > 0" @click="() => handleCloseAhead(route.path, idx)">
<arrow-left-icon />
关闭左侧
</t-dropdown-item>
<t-dropdown-item
v-if="idx < tabRouterList.length - 1"
@click="() => handleCloseBehind(route.path, idx)"
>
<arrow-right-icon />
关闭右侧
</t-dropdown-item>
<t-dropdown-item @click="() => handleCloseOther(route.path, idx)">
<close-circle-icon />
关闭其它
</t-dropdown-item>
</t-dropdown-menu>
</template>
</t-dropdown>
</template>
</t-tab-panel>
</t-tabs>
<t-content :class="`${prefix}-content-layout`">
<layout-breadcrumb v-if="setting.showBreadcrumb" />
<common-content />
</t-content>
<t-footer v-if="showFooter" :class="`${prefix}-footer-layout`">
<layout-footer />
</t-footer>
</t-layout>
</template>
<script lang="ts">
import Vue from 'vue';
import { mapGetters } from 'vuex';
import { RefreshIcon, ArrowLeftIcon, ArrowRightIcon, HomeIcon, CloseCircleIcon } from 'tdesign-icons-vue';
import CommonContent from './Content.vue';
import LayoutBreadcrumb from './Breadcrumb.vue';
import LayoutFooter from './Footer.vue';
import { prefix } from '@/config/global';
import { SettingType } from '@/interface';
export default Vue.extend({
name: 'LayoutContent',
components: {
CommonContent,
LayoutFooter,
LayoutBreadcrumb,
RefreshIcon,
ArrowLeftIcon,
ArrowRightIcon,
HomeIcon,
CloseCircleIcon,
},
data() {
return {
prefix,
activeTabPath: '',
};
},
computed: {
...mapGetters({
showFooter: 'setting/showFooter',
mode: 'setting/mode',
isUseTabsRouter: 'setting/isUseTabsRouter',
menuRouters: 'permission/routers',
tabRouterList: 'tabRouter/tabRouterList',
}),
setting(): SettingType {
return this.$store.state.setting;
},
},
methods: {
handleRemove(path: string, routeIdx: number) {
const nextRouter = this.tabRouterList[routeIdx + 1] || this.tabRouterList[routeIdx - 1];
this.$store.commit('tabRouter/subtractCurrentTabRouter', { path, routeIdx });
if (path === this.$router.history?.current?.path) {
this.$router.push(nextRouter.path);
}
},
handleChangeCurrentTab(path: string) {
this.$router.push(path);
},
handleRefresh(currentPath: string, routeIdx: number) {
this.$store.commit('tabRouter/toggleTabRouterAlive', routeIdx);
this.$nextTick(() => {
this.$store.commit('tabRouter/toggleTabRouterAlive', routeIdx);
this.$router.replace({ path: currentPath });
});
this.activeTabPath = null;
},
handleCloseAhead(path: string, routeIdx: number) {
this.$store.commit('tabRouter/subtractTabRouterAhead', { path, routeIdx });
this.handleOperationEffect('ahead', routeIdx);
},
handleCloseBehind(path: string, routeIdx: number) {
this.$store.commit('tabRouter/subtractTabRouterBehind', { path, routeIdx });
this.handleOperationEffect('behind', routeIdx);
},
handleCloseOther(path: string, routeIdx: number) {
this.$store.commit('tabRouter/subtractTabRouterOther', { path, routeIdx });
this.handleOperationEffect('other', routeIdx);
},
handleOperationEffect(type: 'other' | 'ahead' | 'behind', routeIndex: number) {
const currentPath = this.$router.history?.current?.path;
const tabRouters = this.tabRouterList;
const currentIdx = tabRouters.findIndex((i: { path: string }) => i.path === currentPath);
//
//
const needRefreshRouter =
(type === 'other' && currentIdx !== routeIndex) ||
(type === 'ahead' && currentIdx < routeIndex) ||
(type === 'behind' && currentIdx === -1);
if (needRefreshRouter) {
const nextRouteIdx = type === 'behind' ? tabRouters.length - 1 : 1;
const nextRouter = this.tabRouterList[nextRouteIdx];
this.$router.push(nextRouter.path);
}
this.activeTabPath = null;
},
handleTabMenuClick(visible: boolean, ctx, path: string) {
if (ctx?.trigger === 'document') this.activeTabPath = null;
if (visible) this.activeTabPath = path;
},
},
});
</script>

View File

@ -0,0 +1,52 @@
<template>
<common-header
v-if="showHeader"
:showLogo="showHeaderLogo"
:theme="mode"
:layout="setting.layout"
:isFixed="setting.isHeaderFixed"
:menu="headerMenu"
:isCompact="setting.isSidebarCompact"
:maxLevel="setting.splitMenu ? 1 : 3"
/>
</template>
<script lang="ts">
import Vue from 'vue';
import { mapGetters } from 'vuex';
import CommonHeader from './Header.vue';
import { SettingType } from '@/interface';
export default Vue.extend({
name: 'LayoutHeader',
components: {
CommonHeader,
},
computed: {
...mapGetters({
showHeader: 'setting/showHeader',
showHeaderLogo: 'setting/showHeaderLogo',
mode: 'setting/mode',
menuRouters: 'permission/routers',
}),
setting(): SettingType {
return this.$store.state.setting;
},
headerMenu() {
const { layout, splitMenu } = this.$store.state.setting;
const { menuRouters } = this;
if (layout === 'mix') {
if (splitMenu) {
return menuRouters.map((menu) => ({
...menu,
children: [],
}));
}
return [];
}
return menuRouters;
},
},
});
</script>

View File

@ -0,0 +1,51 @@
<template>
<side-nav
v-if="showSidebar"
:showLogo="showSidebarLogo"
:layout="setting.layout"
:isFixed="setting.isSidebarFixed"
:menu="sideMenu"
:theme="mode"
:isCompact="setting.isSidebarCompact"
:maxLevel="setting.splitMenu ? 2 : 3"
/>
</template>
<script lang="ts">
import Vue from 'vue';
import { mapGetters } from 'vuex';
import SideNav from './SideNav.vue';
import { SettingType } from '@/interface';
export default Vue.extend({
name: 'LayoutSidebar',
components: {
SideNav,
},
computed: {
...mapGetters({
showSidebar: 'setting/showSidebar',
showSidebarLogo: 'setting/showSidebarLogo',
mode: 'setting/mode',
menuRouters: 'permission/routers',
}),
setting(): SettingType {
return this.$store.state.setting;
},
sideMenu() {
const { layout, splitMenu } = this.$store.state.setting;
let { menuRouters } = this;
if (layout === 'mix' && splitMenu) {
menuRouters.forEach((menu) => {
if (this.$route.path.indexOf(menu.path) === 0) {
menuRouters = menu.children.map((subMenu) => ({ ...subMenu, path: `${menu.path}/${subMenu.path}` }));
}
});
}
return menuRouters;
},
},
});
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,108 @@
<template>
<div>
<template v-for="item in list">
<template v-if="!item.children || !item.children.length || item.meta?.single">
<t-menu-item
v-if="getHref(item)"
:key="`href${item.path}`"
:href="getHref(item)?.[0]"
:name="item.path"
:value="item.meta?.single ? item.redirect || item.path : item.path"
>
<template #icon>
<t-icon v-if="typeof item.icon === 'string' && item.icon" :name="item.icon" />
<renderFnIcon :item="item" />
</template>
{{ item.title }}
</t-menu-item>
<t-menu-item
v-else
:key="`${item.path}`"
:to="item.path"
:name="item.path"
:value="item.meta?.single ? item.redirect || item.path : item.path"
>
<template #icon>
<t-icon v-if="typeof item.icon === 'string' && item.icon" :name="item.icon" />
<renderFnIcon :item="item" />
</template>
{{ item.title }}
</t-menu-item>
</template>
<t-submenu v-else :name="item.path" :value="item.path" :title="item.title" :key="item.path">
<template #icon>
<t-icon v-if="typeof item.icon === 'string' && item.icon" :name="item.icon" />
<renderFnIcon :item="item" />
</template>
<menu-content v-if="item.children" :nav-data="item.children" />
</t-submenu>
</template>
</div>
</template>
<script lang="ts">
import Vue, { PropType } from 'vue';
import { prefix } from '@/config/global';
import { MenuRoute } from '@/interface';
const getMenuList = (list: MenuRoute[], basePath?: string): MenuRoute[] => {
if (!list) {
return [];
}
return list
.map((item) => {
const path = basePath && !item.path.includes(basePath) ? `${basePath}/${item.path}` : item.path;
return {
path,
title: item.meta?.title,
icon: item.meta?.icon || '',
children: getMenuList(item.children, path),
meta: item.meta,
redirect: item.redirect,
};
})
.filter((item) => item.meta && item.meta.hidden !== true);
};
Vue.component('renderFnIcon', {
props: {
item: {
type: Object as PropType<MenuRoute>,
required: true,
},
},
// 使 createElement
render(createElement) {
if (typeof this.item.icon === 'function' || (this.item.icon && typeof this.item.icon.render === 'function')) {
return createElement(this.item.icon, {
class: 't-icon',
});
}
return undefined;
},
});
export default Vue.extend({
name: 'MenuContent',
props: {
navData: Array,
},
data() {
return {
prefix,
};
},
computed: {
list(): Array<MenuRoute> {
return getMenuList(this.navData);
},
},
methods: {
getHref(item: MenuRoute) {
return item.path.match(/(http|https):\/\/([\w.]+\/?)\S*/);
},
},
});
</script>

View File

@ -0,0 +1,221 @@
<template>
<t-popup
expand-animation
placement="bottom-right"
trigger="click"
:visible="isNoticeVisible"
@visible-change="onPopupVisibleChange"
>
<template #content>
<div class="header-msg">
<div class="header-msg-top">
<p>通知</p>
<t-button v-if="unreadMsg.length > 0" class="clear-btn" variant="text" theme="primary" @click="setRead('all')"
>清空</t-button
>
</div>
<t-list v-if="unreadMsg.length > 0" class="narrow-scrollbar" :split="true">
<t-list-item v-for="(item, index) in unreadMsg" :key="index">
<div>
<p class="msg-content">{{ item.content }}</p>
<p class="msg-type">{{ item.type }}</p>
</div>
<p class="msg-time">{{ item.date }}</p>
<template #action>
<t-button size="small" variant="outline" @click="setRead('radio', item)"> 设为已读 </t-button>
</template>
</t-list-item>
</t-list>
<div v-else class="empty-list">
<img src="https://tdesign.gtimg.com/pro-template/personal/nothing.png" alt="空" />
<p>暂无通知</p>
</div>
<div class="header-msg-bottom">
<t-button
v-if="unreadMsg.length > 0"
class="header-msg-bottom-link"
variant="text"
theme="primary"
@click="goDetail"
>查看全部</t-button
>
</div>
</div>
</template>
<t-badge :count="unreadMsg.length" :offset="[15, 6]">
<t-button theme="default" shape="square" variant="text" @click="isNoticeVisible = true">
<mail-icon />
</t-button>
</t-badge>
</t-popup>
</template>
<script lang="ts">
import Vue from 'vue';
import { mapState, mapGetters } from 'vuex';
import { MailIcon } from 'tdesign-icons-vue';
import { NotificationItem } from '@/interface';
export default Vue.extend({
components: {
MailIcon,
},
data() {
return {
isNoticeVisible: false,
};
},
computed: {
...mapState('notification', ['msgData']),
...mapGetters('notification', ['unreadMsg']),
},
methods: {
onPopupVisibleChange(visible: boolean, context) {
if (context.trigger === 'trigger-element-click') {
this.isNoticeVisible = true;
return;
}
this.isNoticeVisible = visible;
},
goDetail() {
this.$router.push('/detail/secondary');
this.isNoticeVisible = false;
},
setRead(type: string, item?: NotificationItem) {
const changeMsg = this.msgData;
if (type === 'all') {
changeMsg.forEach((e) => {
e.status = false;
});
} else {
changeMsg.forEach((e) => {
if (e.id === item.id) {
e.status = false;
}
});
}
this.$store.commit('notification/setMsgData', changeMsg);
},
},
});
</script>
<style lang="less" scoped>
@import '@/style/variables.less';
.header-msg {
width: 400px;
height: 500px;
.empty-list {
height: calc(100% - 104px);
text-align: center;
padding-top: 135px;
font-size: 14px;
color: var(--td-text-color-secondary);
img {
width: 63px;
}
p {
margin-top: 30px;
}
}
&-top {
position: relative;
height: 56px;
font-size: 16px;
color: var(--td-text-color-primary);
text-align: center;
line-height: 56px;
border-bottom: 1px solid var(--td-component-border);
.clear-btn {
position: absolute;
top: 12px;
right: 24px;
}
}
&-bottom {
height: 48px;
align-items: center;
display: flex;
justify-content: center;
&-link {
text-decoration: none;
font-size: 14px;
color: var(--td-brand-color);
line-height: 48px;
cursor: pointer;
}
}
.t-list {
height: calc(100% - 104px);
}
.t-list-item {
overflow: hidden;
width: 100%;
padding: 16px 24px;
border-radius: var(--td-radius-default);
font-size: 14px;
color: var(--td-text-color-primary);
line-height: 22px;
cursor: pointer;
&:hover {
transition: background 0.2s ease;
background: var(--td-bg-color-container-hover);
.msg-content {
color: var(--td-brand-color);
}
.t-list-item__action {
button {
bottom: 16px;
opacity: 1;
}
}
.msg-time {
bottom: -6px;
opacity: 0;
}
}
.msg-content {
margin-bottom: 16px;
}
.msg-type {
color: var(--td-text-color-secondary);
}
.t-list-item__action {
button {
opacity: 0;
position: absolute;
right: 24px;
bottom: -6px;
}
}
.msg-time {
transition: all 0.2s ease;
opacity: 1;
position: absolute;
right: 24px;
bottom: 16px;
color: var(--td-text-color-secondary);
}
}
}
</style>

View File

@ -0,0 +1,134 @@
<template>
<div v-if="layout === 'side'" class="header-menu-search">
<t-input
:class="{ 'hover-active': isSearchFocus }"
placeholder="请输入搜索内容"
@blur="changeSearchFocus(false)"
@focus="changeSearchFocus(true)"
>
<template #prefix-icon>
<search-icon class="icon" size="16" />
</template>
</t-input>
</div>
<div v-else class="header-menu-search-left">
<t-button
:class="{ 'search-icon-hide': isSearchFocus }"
theme="default"
shape="square"
variant="text"
@click="changeSearchFocus(true)"
>
<search-icon />
</t-button>
<t-input
ref="inputRef"
v-model="searchData"
:class="['header-search', { 'width-zero': !isSearchFocus }]"
placeholder="输入要搜索内容"
:autofocus="isSearchFocus"
@blur="changeSearchFocus(false)"
>
<template #prefix-icon>
<search-icon size="16" />
</template>
</t-input>
</div>
</template>
<script lang="ts">
import Vue, { PropType } from 'vue';
import { SearchIcon } from 'tdesign-icons-vue';
export default Vue.extend({
components: {
SearchIcon,
},
props: {
layout: {
type: String as PropType<string>,
},
},
data() {
return {
isSearchFocus: false,
searchData: '',
};
},
methods: {
changeSearchFocus(value: boolean) {
if (!value) {
this.searchData = '';
}
this.isSearchFocus = value;
},
},
});
</script>
<style lang="less" scoped>
@import '@/style/variables.less';
.header-menu-search {
display: flex;
margin-left: 16px;
.hover-active {
.t-input {
background: var(--td-bg-color-secondarycontainer);
}
/deep/ .t-icon {
color: var(--td-brand-color);
}
}
/deep/ .t-icon {
font-size: 20px;
color: var(--td-text-color-primary);
}
.t-input {
border: none;
outline: none;
box-shadow: none;
transition: background @anim-duration-base linear;
&:hover {
background: var(--td-bg-color-secondarycontainer);
}
}
}
.header-search {
width: 200px;
transition: width @anim-duration-base @anim-time-fn-easing;
.t-input {
border: 0;
padding-left: 40px;
&:focus {
box-shadow: none;
}
}
&.width-zero {
width: 0;
opacity: 0;
}
}
.t-button {
transition: opacity @anim-duration-base @anim-time-fn-easing;
}
.search-icon-hide {
opacity: 0;
}
.header-menu-search-left {
display: flex;
align-items: center;
}
</style>

View File

@ -0,0 +1,157 @@
<template>
<div :class="sideNavCls">
<t-menu
width="232px"
:class="menuCls"
:theme="theme"
:value="active"
:collapsed="collapsed"
:defaultExpanded="defaultExpanded"
>
<template #logo>
<span v-if="showLogo" :class="`${prefix}-side-nav-logo-wrapper`" @click="() => handleNav('/dashboard/base')">
<!-- <component :is="getLogo" :class="`${prefix}-side-nav-logo-${collapsed ? 't' : 'tdesign'}-logo`" />-->
<img :src="$store.state.user.apiUrl+association.association_image" style="height: 50px">
</span>
</template>
<menu-content :navData="menu" />
<template #operations>
<!-- <span class="version-container"> {{ !collapsed ? `TDesign Starter ${pgk.version}` : pgk.version }} </span>-->
<div style="text-align: center" class="version-container">{{association.association_name}}</div>
</template>
</t-menu>
<div :class="`${prefix}-side-nav-placeholder${collapsed ? '-hidden' : ''}`"></div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { prefix } from '@/config/global';
import { ClassName } from '@/interface';
import Logo from '@/assets/assets-t-logo.svg';
import LogoFull from '@/assets/assets-logo-full.svg';
import MenuContent from './MenuContent.vue';
import pgk from '../../../package.json';
import store from '@/store'
const MIN_POINT = 992 - 1;
export default Vue.extend({
name: 'sideNav',
components: {
MenuContent,
},
props: {
menu: Array,
showLogo: {
type: Boolean,
default: true,
},
isFixed: {
type: Boolean,
default: true,
},
layout: String,
headerHeight: {
type: String,
default: '56px',
},
theme: {
type: String,
default: 'light',
},
isCompact: {
type: Boolean,
default: false,
},
maxLevel: {
type: Number,
default: 3,
},
},
data() {
return {
prefix,
pgk,
association:{}
};
},
computed: {
defaultExpanded() {
const path = this.active;
const parentPath = path.substring(0, path.lastIndexOf('/'));
if (parentPath.lastIndexOf('/')) {
const threeLevel = parentPath.substring(0, parentPath.lastIndexOf('/'));
return threeLevel === '' ? [] : [threeLevel, parentPath];
}
return parentPath === '' ? [] : [parentPath];
},
iconName(): string {
return this.$store.state.setting.isSidebarCompact ? 'menu-fold' : 'menu-unfold';
},
collapsed(): boolean {
return this.$store.state.setting.isSidebarCompact;
},
sideNavCls(): Array<ClassName> {
return [
`${this.prefix}-sidebar-layout`,
{
[`${this.prefix}-sidebar-compact`]: this.isCompact,
},
];
},
menuCls(): Array<ClassName> {
return [
`${this.prefix}-side-nav`,
{
[`${this.prefix}-side-nav-no-logo`]: !this.showLogo,
[`${this.prefix}-side-nav-no-fixed`]: !this.isFixed,
[`${this.prefix}-side-nav-mix-fixed`]: this.layout === 'mix' && this.isFixed,
},
];
},
layoutCls(): Array<ClassName> {
return [`${this.prefix}-side-nav-${this.layout}`, `${this.prefix}-sidebar-layout`];
},
active(): string {
if (!this.$route.path) {
return '';
}
return this.$route.path
.split('/')
.filter((_item: string, index: number) => index <= this.maxLevel && index > 0)
.map((item: string) => `/${item}`)
.join('');
},
getLogo() {
if (this.collapsed) {
return Logo;
}
return LogoFull;
},
},
mounted() {
this.autoCollapsed();
if(typeof (store.state.user.association)=='object'){
this.association=store.state.user.association;
}else{
this.association=JSON.parse(store.state.user.association);
}
window.onresize = () => {
this.autoCollapsed();
};
},
methods: {
changeCollapsed(): void {
this.$store.commit('setting/toggleSidebarCompact');
},
autoCollapsed(): void {
const isCompact = window.innerWidth <= MIN_POINT;
this.$store.commit('setting/showSidebarCompact', isCompact);
},
handleNav(url: string) {
this.$router.push(url);
},
},
});
</script>

100
src/layouts/index.vue Normal file
View File

@ -0,0 +1,100 @@
<template>
<div>
<template v-if="setting.layout === 'side'">
<t-layout key="side">
<t-aside><layout-sidebar /></t-aside>
<t-layout>
<t-header><layout-header /></t-header>
<t-content><layout-content /></t-content>
</t-layout>
</t-layout>
</template>
<template v-else-if="setting.layout === 'top'">
<t-layout key="top">
<t-header> <layout-header /></t-header>
<t-content><layout-content /></t-content>
</t-layout>
</template>
<template v-else>
<t-layout key="mix">
<t-header><layout-header /></t-header>
<t-layout>
<t-aside><layout-sidebar /></t-aside>
<t-content><layout-content /></t-content>
</t-layout>
</t-layout>
</template>
<setting />
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { mapGetters } from 'vuex';
import LayoutHeader from './components/LayoutHeader.vue';
import LayoutContent from './components/LayoutContent.vue';
import LayoutSidebar from './components/LayoutSidebar.vue';
import Setting from './setting.vue';
import { prefix } from '@/config/global';
import { SettingType } from '@/interface';
import '@/style/layout.less';
const name = `${prefix}-base-layout`;
export default Vue.extend({
name,
components: {
LayoutHeader,
LayoutContent,
LayoutSidebar,
Setting,
},
computed: {
...mapGetters({
tabRouterList: 'tabRouter/tabRouterList',
}),
setting(): SettingType {
return this.$store.state.setting;
},
},
watch: {
$route(newRoute) {
//
const {
path,
meta: { title },
name,
} = newRoute;
this.$store.commit('tabRouter/appendTabRouterList', { path, title, name, isAlive: true });
},
},
// createddestroyed
created() {
window.addEventListener('beforeunload', this.setTabRouterListCache);
},
destroyed() {
window.removeEventListener('beforeunload', this.setTabRouterListCache);
},
mounted() {
const {
path,
meta: { title },
name,
} = this.$route;
if (localStorage.getItem('tabRouterList')) this.getTabRouterListCache();
this.$store.commit('tabRouter/appendTabRouterList', { path, title, name, isAlive: true });
},
methods: {
getTabRouterListCache() {
this.$store.commit('tabRouter/initTabRouterList', JSON.parse(localStorage.getItem('tabRouterList')));
},
setTabRouterListCache() {
localStorage.setItem('tabRouterList', JSON.stringify(this.tabRouterList));
},
},
});
</script>

404
src/layouts/setting.vue Normal file
View File

@ -0,0 +1,404 @@
<template>
<div>
<t-drawer
size="408px"
:footer="false"
:visible.sync="showSettingPanel"
header="页面配置"
:closeBtn="true"
:onCloseBtnClick="handleCloseDrawer"
class="setting-drawer-container"
>
<div class="setting-container">
<t-form :data="formData" size="large" ref="form" labelAlign="left" @reset="onReset" @submit="onSubmit">
<div class="setting-group-title">主题模式</div>
<t-radio-group v-model="formData.mode">
<div v-for="(item, index) in MODE_OPTIONS" :key="index" class="setting-layout-drawer">
<div>
<t-radio-button :key="index" :value="item.type"
><component :is="getModeIcon(item.type)"
/></t-radio-button>
<p :style="{ textAlign: 'center', marginTop: '8px' }">{{ item.text }}</p>
</div>
</div>
</t-radio-group>
<div class="setting-group-title">主题色</div>
<t-radio-group v-model="formData.brandTheme">
<div v-for="(item, index) in DEFAULT_COLOR_OPTIONS" :key="index" class="setting-layout-drawer">
<t-radio-button :key="index" :value="item" class="setting-layout-color-group">
<color-container :value="item" />
</t-radio-button>
</div>
<div class="setting-layout-drawer">
<t-popup
destroy-on-close
expand-animation
placement="bottom-right"
trigger="click"
:visible="isColoPickerDisplay"
@visible-change="onPopupVisibleChange"
:overlayStyle="{ padding: 0 }"
>
<template #content>
<t-color-picker-panel
:on-change="changeColor"
:color-modes="['monochrome']"
format="HEX"
:swatch-colors="[]"
/></template>
<t-radio-button :value="dynamicColor" :class="['setting-layout-color-group', 'dynamic-color-btn']">
<color-container :value="dynamicColor" />
</t-radio-button>
</t-popup>
</div>
</t-radio-group>
<div class="setting-group-title">导航布局</div>
<t-radio-group v-model="formData.layout">
<div v-for="(item, index) in LAYOUT_OPTION" :key="index" class="setting-layout-drawer">
<t-radio-button :key="index" :value="item"><thumbnail :src="getThumbnailUrl(item)" /></t-radio-button>
</div>
</t-radio-group>
<t-form-item v-show="formData.layout === 'mix'" label="分割菜单(混合模式下有效)" name="splitMenu">
<t-switch v-model="formData.splitMenu"></t-switch>
</t-form-item>
<t-form-item v-show="formData.layout !== 'side'" label="固定 Header" name="isHeaderFixed">
<t-switch v-model="formData.isHeaderFixed"></t-switch>
</t-form-item>
<t-form-item v-show="formData.layout !== 'top'" label="固定 Sidebar" name="isSidebarFixed">
<t-switch v-model="formData.isSidebarFixed"></t-switch>
</t-form-item>
<div class="setting-group-title">元素开关</div>
<t-form-item label="显示 Header" name="showHeader" v-show="formData.layout === 'side'">
<t-switch v-model="formData.showHeader"></t-switch>
</t-form-item>
<t-form-item label="显示 Breadcrumbs" name="showBreadcrumb">
<t-switch v-model="formData.showBreadcrumb"></t-switch>
</t-form-item>
<t-form-item label="显示 Footer" name="showFooter">
<t-switch v-model="formData.showFooter"></t-switch>
</t-form-item>
<t-form-item label="使用 多标签Tab页" name="isUseTabsRouter">
<t-switch v-model="formData.isUseTabsRouter"></t-switch>
</t-form-item>
<t-form-item
label="footer 内收"
name="footerPosition"
v-show="formData.showFooter && !formData.isSidebarFixed"
>
<t-switch v-model="formData.isFooterAside"></t-switch>
</t-form-item>
</t-form>
<div class="setting-info">
<p>请复制后手动修改配置文件: /src/config/style.ts</p>
<t-button theme="primary" variant="text" @click="handleCopy"> 复制配置项 </t-button>
</div>
</div>
</t-drawer>
</div>
</template>
<script lang="ts">
import { mapGetters } from 'vuex';
import { Color } from 'tvision-color';
import { PopupVisibleChangeContext } from 'tdesign-vue';
import STYLE_CONFIG from '@/config/style';
import { insertThemeStylesheet, generateColorMap } from '@/utils/color';
import { DEFAULT_COLOR_OPTIONS } from '@/config/color';
import Thumbnail from '@/components/thumbnail/index.vue';
import ColorContainer from '@/components/color/index.vue';
import SettingDarkIcon from '@/assets/assets-setting-dark.svg';
import SettingLightIcon from '@/assets/assets-setting-light.svg';
import SettingAutoIcon from '@/assets/assets-setting-auto.svg';
const LAYOUT_OPTION = ['side', 'top', 'mix'];
const MODE_OPTIONS = [
{ type: 'light', text: '明亮' },
{ type: 'dark', text: '暗黑' },
{ type: 'auto', text: '跟随系统' },
];
export default {
name: 'DefaultLayoutSetting',
components: { Thumbnail, ColorContainer },
data() {
return {
colors: {
hex: null,
},
MODE_OPTIONS,
LAYOUT_OPTION,
DEFAULT_COLOR_OPTIONS,
visible: false,
formData: { ...STYLE_CONFIG },
isColoPickerDisplay: false,
};
},
computed: {
...mapGetters('setting', ['showSettingBtn']),
showSettingPanel: {
get() {
return this.$store.state.setting.showSettingPanel;
},
set(newVal) {
this.$store.commit('setting/toggleSettingPanel', newVal);
},
},
iconName() {
return this.visible ? 'close' : 'setting';
},
showOthers() {
return (this.formData.showFooter && !this.formData.isSidebarFixed) || !this.formData.splitMenu;
},
dynamicColor() {
const isDynamic = DEFAULT_COLOR_OPTIONS.indexOf(this.formData.brandTheme) === -1;
return isDynamic ? this.formData.brandTheme : '';
},
},
watch: {
formData: {
handler(newVal) {
if (!newVal.brandTheme) return;
// formData store
const { isSidebarCompact } = this.$store.state.setting;
this.$store.dispatch('setting/changeTheme', { ...newVal, isSidebarCompact });
},
deep: true,
},
},
mounted() {
document.querySelector('.dynamic-color-btn')?.addEventListener('click', () => {
this.isColoPickerDisplay = true;
});
},
methods: {
onPopupVisibleChange(visible: boolean, context: PopupVisibleChangeContext) {
if (!visible && context.trigger === 'document') this.isColoPickerDisplay = visible;
},
onReset(): void {
this.formData = {
...STYLE_CONFIG,
};
this.$message.success('已恢复初始设置');
},
onSubmit({ result, firstError, e }): void {
e.preventDefault();
if (result === true) {
this.visible = false;
} else {
this.$message.warning(firstError);
}
},
getModeIcon(mode: string) {
if (mode === 'light') {
return SettingLightIcon;
}
if (mode === 'dark') {
return SettingDarkIcon;
}
return SettingAutoIcon;
},
getThumbnailUrl(name: string) {
return `https://tdesign.gtimg.com/starter/setting/${name}.png`;
},
handleClick(): void {
this.$store.commit('setting/toggleSettingPanel', true);
},
handleCloseDrawer(): void {
this.$store.commit('setting/toggleSettingPanel', false);
},
handleCopy(): void {
const text = JSON.stringify(this.formData, null, 4);
this.$copyText(text).then(() => {
this.$message.closeAll();
this.$message.success('复制成功');
});
},
changeColor(hex: string) {
const { setting } = this.$store.state;
const { colors: newPalette, primary: brandColorIndex } = Color.getColorGradations({
colors: [hex],
step: 10,
remainInput: false, //
})[0];
const { mode } = this.$store.state.setting;
const colorMap = generateColorMap(hex, newPalette, mode, brandColorIndex);
this.formData.brandTheme = hex;
this.$store.commit('setting/addColor', { [hex]: colorMap });
this.$store.dispatch('setting/changeTheme', { ...setting, brandTheme: hex });
insertThemeStylesheet(hex, colorMap, mode);
},
},
};
</script>
<style lang="less">
@import '@/style/variables.less';
.tdesign-setting {
z-index: 100;
position: fixed;
bottom: 200px;
right: 0;
transition: transform 0.3s cubic-bezier(0.7, 0.3, 0.1, 1), visibility 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
height: 40px;
width: 40px;
border-radius: 20px 0 0 20px;
transition: all 0.3s;
.t-icon {
margin-left: 8px;
}
.tdesign-setting-text {
font-size: 12px;
display: none;
}
&:hover {
width: 96px;
.tdesign-setting-text {
display: inline-block;
}
}
}
.setting-layout-color-group {
display: inline-flex;
justify-content: center;
align-items: center;
border-radius: 50% !important;
padding: 6px !important;
border: 2px solid transparent !important;
> .t-radio-button__label {
display: inline-flex;
}
}
.tdesign-setting-close {
position: fixed;
bottom: 200px;
right: 300px;
}
.setting-group-title {
font-size: 14px;
line-height: 22px;
margin: 32px 0 24px 0;
text-align: left;
font-family: PingFang SC;
font-style: normal;
font-weight: 500;
color: var(--td-text-color-primary);
}
.setting-group-color {
position: relative;
> div {
position: absolute;
z-index: 2;
right: 0;
}
}
.setting-link {
cursor: pointer;
color: var(--td-brand-color);
margin-bottom: 8px;
}
.setting-info {
position: absolute;
padding: 24px;
bottom: 0;
left: 0;
line-height: 20px;
font-size: 12px;
text-align: center;
color: var(--td-text-color-placeholder);
width: 100%;
background: var(--td-bg-color-container);
}
.setting-drawer-container {
.setting-container {
padding-bottom: 100px;
}
.t-radio-group.t-size-m {
min-height: 32px;
width: 100%;
height: auto;
justify-content: space-between;
align-items: center;
}
.setting-layout-drawer {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 16px;
.t-radio-button {
display: inline-flex;
height: 100%;
max-height: 78px;
padding: 8px;
border-radius: var(--td-radius-default);
border: 2px solid var(--td-component-border);
height: auto;
> .t-radio-button__label {
display: inline-flex;
}
}
.t-is-checked {
border: 2px solid var(--td-brand-color) !important;
}
.t-form__controls-content {
justify-content: end;
}
}
.t-form__controls-content {
justify-content: end;
}
}
.setting-route-theme {
.t-form__label {
min-width: 310px !important;
color: var(--td-text-color-secondary);
}
}
.setting-color-theme {
.setting-layout-drawer {
.t-radio-button {
height: 32px;
}
&:last-child {
margin-right: 0;
}
}
}
.setting-drawer-container .t-radio-group.t-radio-group__outline.t-size-m .t-radio-button {
height: auto;
}
</style>

51
src/main.jsx Normal file
View File

@ -0,0 +1,51 @@
import Vue from 'vue';
import VueRouter from 'vue-router';
import { sync } from 'vuex-router-sync';
import TDesign from 'tdesign-vue';
import VueClipboard from 'vue-clipboard2';
import axiosInstance from '@/utils/request';
import App from './App.vue';
import router from './router';
import zhConfig from 'tdesign-vue/es/locale/zh_CN';
// import enConfig from 'tdesign-vue/es/locale/en_US'; //
import 'tdesign-vue/es/style/index.css';
import '@/style/index.less';
import './permission';
import store from './store';
Vue.use(VueRouter);
Vue.use(TDesign);
Vue.use(VueClipboard);
Vue.component('t-page-header');
Vue.prototype.$request = axiosInstance;
const originPush = VueRouter.prototype.push;
VueRouter.prototype.push = function push(location) {
return originPush.call(this, location).catch((err) => err);
};
const originReplace = VueRouter.prototype.replace;
VueRouter.prototype.replace = function replace(location) {
return originReplace.call(this, location).catch((err) => err);
};
Vue.config.productionTip = false;
sync(store, router);
new Vue({
router,
store,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
render: (h) => (
<div>
{/* config-provider
<t-config-provider globalConfig={enConfig}> */}
<t-config-provider globalConfig={zhConfig}>
<App />
</t-config-provider>
</div>
),
}).$mount('#app');

View File

@ -0,0 +1,367 @@
<template>
<t-card :bordered="false">
<div class="form-step-container">
<t-button @click="add">新增</t-button>
<t-table
rowKey="index"
:data="list"
:columns="columns"
:stripe="true"
:bordered="false"
:hover="true"
size="large"
table-layout="auto"
cellEmptyContent="-"
:pagination="pagination"
>
<template #activity_image="{ row }">
<div style="text-align: center;">
<t-image
:src="$store.state.user.apiUrl+row.activity_image"
fit="contain"
:style="{ width: '120px',margin:'0 auto'}"
/>
</div>
</template>
<template #select="{ row }">
<t-space size="24px">
<t-button @click="openInfo(row)">报名详情</t-button>
<t-button theme="warning" @click="edit(row)">编辑</t-button>
<t-popconfirm content="确认删除吗" @confirm="del(row)">
<t-button theme="danger">删除</t-button>
</t-popconfirm>
</t-space>
</template>
</t-table>
<div style="margin-top: 30px">
<t-pagination
:total="pagination.total"
:page-size="pagination.size"
@current-change="onCurrentChange"
:showPageSize="false"
></t-pagination>
</div>
</div>
<t-dialog header="报名详情" :confirmBtn="null" :visible="infoMode" :onClose="onCloseMy" width="45%" top="50px">
<t-table
rowKey="index"
:data="info_list"
:columns="info_columns"
:stripe="true"
:bordered="false"
:hover="true"
size="large"
table-layout="auto"
cellEmptyContent="-"
>
<template #photo_image="{ row }">
<img :src="'http://192.168.3.130/'+row.photo_image" style="width: 50px;height: 50px">
</template>
</t-table>
<div style="margin-top: 30px">
<t-pagination
:total="info_pagination.total"
:page-size="info_pagination.size"
@current-change="onCurrentChangeInfo"
:showPageSize="false"
></t-pagination>
</div>
</t-dialog>
<t-dialog :header="isEdit?'编辑活动':'新增活动'" :visible="addMode" :onClose="onCloseMy" @confirm="onSubmit"
width="45%" top="20px">
<t-form>
<t-form-item label="活动标题" name="activity_name">
<t-input placeholder="请输入活动标题" v-model="addForm.activity_name"/>
</t-form-item>
<t-form-item label="活动简介" name="activity_desc">
<t-input placeholder="请输入活动简介" v-model="addForm.activity_desc"/>
</t-form-item>
<t-form-item label="活动地址" name="activity_location">
<t-input placeholder="请输入活动地址" v-model="addForm.activity_location"/>
</t-form-item>
<t-form-item label="活动简介" name="activity_desc">
<div style="border: 1px solid #ccc;">
<!-- 工具栏 -->
<Toolbar
style="border-bottom: 1px solid #ccc"
mode="default"
:editor="editor"
:defaultConfig="toolbarConfig"
/>
<Editor
style="height: 200px"
v-model="addForm.activity_content"
:defaultConfig="editorConfig"
mode="default"
@onCreated="(e) => onCreated(e)"
/>
</div>
</t-form-item>
<t-form-item label="活动主图">
<t-upload
:action="$store.state.user.apiUrl+'/api/common/upload'"
v-model="addForm.activity_image_show"
theme="image"
tips="请选择单张图片文件上传"
accept="image/*"
:format-response="formatResponse"
></t-upload>
</t-form-item>
<t-form-item label="活动开始时间" name="activity_start_time">
<t-date-picker :clearable="true" :style="{ width: '480px' }" placeholder="活动开始时间"
:enableTimePicker="true" :allow-input="false"
v-model="addForm.activity_start_time"></t-date-picker>
</t-form-item>
<t-form-item label="活动结束时间" name="activity_end_time">
<t-date-picker :clearable="true" :style="{ width: '480px' }" placeholder="活动结束时间"
:enableTimePicker="true" :allow-input="false"
v-model="addForm.activity_end_time"></t-date-picker>
</t-form-item>
</t-form>
</t-dialog>
</t-card>
</template>
<script lang="ts">
import store from "@/store";
import {Editor, Toolbar} from '@wangeditor/editor-for-vue';
export default {
components: {Editor, Toolbar},
data() {
return {
visible: false,
infoMode: false,
addForm: {
activity_name: '',
activity_desc: '',
activity_image_show: [],
activity_image: '',
activity_start_time: '',
activity_end_time: '',
activity_content: '',
activity_location: ''
},
addMode: false,
isEdit: false,
editID: 0,
infoId: 0,
info_list: [],
info_columns: [
{colKey: 'photo_image', title: '证件照', align: 'center'},
{colKey: 'nikename', title: '姓名', align: 'center'},
{colKey: 'phone', title: '手机号', align: 'center'},
{colKey: 'position', title: '职位', align: 'center'},
{colKey: 'application_time', title: '报名时间', align: 'center'},
],
info_pagination: {
page: 1,
size: 10,
total: 0,
},
list: [],
columns: [
{colKey: 'activity_name', title: '活动标题', align: 'center'},
{colKey: 'activity_desc', title: '活动简介', align: 'center'},
{colKey: 'activity_image', title: '活动图片', align: 'center'},
{colKey: 'activity_start_time', title: '活动开始时间', align: 'center'},
{colKey: 'activity_end_time', title: '活动结束时间', align: 'center'},
{colKey: 'activity_location', title: '活动地点', align: 'center'},
{colKey: 'select', title: '操作', width: 200, align: 'center'},
],
pagination: {
page: 1,
size: 10,
total: 0,
},
editor: null,
toolbarConfig: {
showLinkImg: false,
uploadImgShowBase64: true,
excludeKeys: [
'insertVideo', //
'uploadVideo',
'group-video',
'insertImage',//
'insertLink',//
'insertTable',//
'codeBlock',//
]
},
editorConfig: {
placeholder: '',
readOnly: false, //
autoFocus: true,
MENU_CONF: {
uploadImage: {
server: store.state.user.apiUrl + '/api/common/upload',
fieldName: 'file',
customInsert(res: any, insertFn: InsertFnType) { // TS
// customInsert(res, insertFn) { // JS
// res
console.log(res);
// res url alt href
insertFn(res.data.fullurl, '', '')
},
}
}
},
}
},
beforeDestroy() {
const editor = this.editor
if (editor == null) return
editor.destroy() //
},
mounted() {
//this.member_id=this.$route.query.id;
this.getList();
},
methods: {
onCurrentChangeInfo(d) {
this.info_pagination.page = d;
this.openInfo();
},
onCurrentChange(d) {
this.pagination.page = d;
this.getList();
},
openInfo(d) {
this.infoMode = true;
this.infoId = d.id;
this.$request
.post("/application/find", {
application_id: this.infoId,
page: this.info_pagination.page,
size: this.info_pagination.size
})
.then((res) => {
console.log(res);
if (res.code == 1) {
this.info_list = res.data.ret;
this.info_pagination.total = res.data.count;
}
})
.catch((e) => {
console.log(e);
});
},
formatResponse(res) {
console.log(res);
this.addForm.activity_image = res.data.url;
return {url: res.data.fullurl};
},
onCreated(editor) {
this.editor = Object.seal(editor);
},
del(d) {
console.log(d);
this.$request
.post('/activity/del', {id: d.id})
.then((res) => {
if (res.code == 1) {
this.$message.success(res.msg);
this.getList();
} else {
this.$message.error(res.msg);
}
console.log(res);
})
.catch((e) => {
console.log(e);
});
},
add() {
this.addMode = true;
this.isEdit = false;
},
edit(d) {
console.log(d);
this.addForm.id = d.id;
this.addForm.activity_name = d.activity_name;
this.addForm.activity_desc = d.activity_desc;
this.addForm.activity_image_show = [{url: store.state.user.apiUrl + d.activity_image}];
this.addForm.activity_image = d.activity_image;
this.addForm.activity_start_time = d.activity_start_time;
this.addForm.activity_end_time = d.activity_end_time;
this.addForm.activity_location = d.activity_location;
this.addForm.activity_content = d.activity_content;
this.addMode = true;
this.isEdit = true;
},
getList() {
this.$request
.post("/activity", {page: this.pagination.page, size: this.pagination.size})
.then((res) => {
console.log(res);
if (res.code == 1) {
this.list = res.data.ret;
this.pagination.total = res.data.count;
}
})
.catch((e) => {
console.log(e);
});
},
onSubmit() {
console.log(this.addForm);
if (this.addForm.activity_name == '') {
this.$message.error('活动标题不能为空');
return;
}
if (this.addForm.activity_desc == '') {
this.$message.error('活动简介不能为空');
return;
}
if (this.addForm.activity_image == '') {
this.$message.error('活动图片不能为空');
return;
}
if (this.addForm.activity_content == '') {
this.$message.error('活动详情不能为空');
return;
}
if (this.addForm.activity_start_time == '') {
this.$message.error('活动开始时间不能为空');
return;
}
if (this.addForm.activity_end_time == '') {
this.$message.error('活动结束时间不能为空');
return;
}
if (this.addForm.activity_location == '') {
this.$message.error('活动地址不能为空');
return;
}
console.log(this.addForm);
var url = '/activity/add';
if (this.isEdit) {
url = '/activity/update';
}
this.$request
.post(url, this.addForm)
.then((res) => {
console.log(res);
if (res.code == 1) {
this.$message.success(res.msg);
this.addMode = false;
this.getList();
} else {
this.$message.error(res.msg);
}
})
.catch((e) => {
console.log(e);
});
},
onCloseMy() {
this.addMode = false;
this.infoMode = false;
}
}
}
</script>
<style src="@wangeditor/editor/dist/css/style.css"></style>

View File

@ -0,0 +1,254 @@
<template>
<t-card :bordered="false">
<div class="form-step-container">
<t-row>
<t-col :span="6" :offset="3">
<t-form>
<t-form-item label="协会头像" name="association_image">
<t-upload
:action="$store.state.user.apiUrl+'/api/common/upload'"
v-model="addForm.association_image_show"
theme="image"
tips="请选择单张图片文件上传"
accept="image/*"
:format-response="formatResponse"
></t-upload>
</t-form-item>
<t-form-item label="协会名称" name="association_name">
<t-input placeholder="请输入协会名称" v-model="addForm.association_name"/>
</t-form-item>
<t-form-item label="协会地址" name="association_dizhi">
<t-input placeholder="请输入协会地址" v-model="addForm.association_dizhi"/>
</t-form-item>
<t-form-item label="协会邮箱" name="association_emal">
<t-input placeholder="请输入协会邮箱" v-model="addForm.association_emal"/>
</t-form-item>
<t-form-item label="协会电话" name="association_phone">
<t-input placeholder="请输入协会电话" v-model="addForm.association_phone"/>
</t-form-item>
<t-form-item label="协会简介" name="association_desc">
<div style="border: 1px solid #ccc;">
<!-- 工具栏 -->
<Toolbar
style="border-bottom: 1px solid #ccc"
mode="default"
:editor="editor"
:defaultConfig="toolbarConfig"
/>
<Editor
style="height: 200px"
v-model="addForm.association_desc"
:defaultConfig="editorConfig"
mode="default"
@onCreated="(e) => onCreated(e)"
/>
</div>
</t-form-item>
<t-form-item label="协会规童制度" name="association_guizhang">
<div style="border: 1px solid #ccc;">
<!-- 工具栏 -->
<Toolbar
style="border-bottom: 1px solid #ccc"
mode="default"
:editor="editorGuiZhang"
:defaultConfig="toolbarConfig"
/>
<Editor
style="height: 200px"
v-model="addForm.association_guizhang"
:defaultConfig="editorConfig"
mode="default"
@onCreated="(e) => onCreatedGuiZhang(e)"
/>
</div>
</t-form-item>
<t-form-item label="入会须知" name="ruhuixizhu">
<div style="border: 1px solid #ccc;">
<!-- 工具栏 -->
<Toolbar
style="border-bottom: 1px solid #ccc"
mode="default"
:editor="editorRuHui"
:defaultConfig="toolbarConfig"
/>
<Editor
style="height: 200px"
v-model="addForm.ruhuixizhu"
:defaultConfig="editorConfig"
mode="default"
@onCreated="(e) => onCreatedRuHui(e)"
/>
</div>
</t-form-item>
</t-form>
</t-col>
</t-row>
<t-row justify="center">
<t-col>
<div style="padding: 30px">
<t-button theme="primary" @click="onSubmit()" style="width: 150px" size="large">保存</t-button>
</div>
</t-col>
</t-row>
</div>
</t-card>
</template>
<script lang="ts">
import {Editor, Toolbar} from '@wangeditor/editor-for-vue';
import store from "@/store";
export default {
components: {Editor, Toolbar},
data() {
return {
info:{},
addForm:{
association_name:'',
association_phone:'',
association_guizhang:'',
association_emal:'',
association_dizhi:'',
association_desc:'',
association_image:'',
association_image_show:[],
ruhuixizhu:''
},
editor: null,
editorGuiZhang:null,
editorRuHui:null,
toolbarConfig: {
showLinkImg: false,
uploadImgShowBase64: true,
excludeKeys: [
'insertVideo', //
'uploadVideo',
'group-video',
'insertImage',//
'insertLink',//
'insertTable',//
'codeBlock',//
]
},
editorConfig: {
placeholder: '',
readOnly: false, //
autoFocus: true,
MENU_CONF: {
uploadImage: {
server: store.state.user.apiUrl + '/api/common/upload',
fieldName: 'file',
customInsert(res: any, insertFn: InsertFnType) { // TS
// customInsert(res, insertFn) { // JS
// res
console.log(res);
// res url alt href
insertFn(res.data.fullurl, '', '')
},
}
}
},
}
},
beforeDestroy() {
const editor = this.editor
if (editor == null) return
editor.destroy() //
},
mounted() {
//this.member_id=this.$route.query.id;
this.getList();
},
methods: {
formatResponse(res) {
console.log(res);
this.addForm.association_image = res.data.url;
return {url: res.data.fullurl};
},
getList() {
this.$request
.post("/association/find")
.then((res) => {
console.log(res);
this.info = res.data;
this.addForm.id=res.data.id;
this.addForm.association_name=res.data.association_name;
this.addForm.association_phone=res.data.association_phone;
this.addForm.association_emal=res.data.association_emal;
this.addForm.association_dizhi=res.data.association_dizhi;
this.addForm.association_desc=res.data.association_desc;
this.addForm.association_guizhang=res.data.association_guizhang;
this.addForm.association_image=res.data.association_image;
this.addForm.association_image_show=[{url:store.state.user.apiUrl+res.data.association_image}];
this.addForm.ruhuixizhu=res.data.ruhuixizhu;
})
.catch((e) => {
console.log(e);
});
},
onSubmit() {
if (this.association_name == '') {
this.$message.error('协会名称不能为空');
return;
}
if (this.association_phone == '') {
this.$message.error('协会电话不能为空');
return;
}
if (this.association_emal == '') {
this.$message.error('协会邮箱不能为空');
return;
}
if (this.association_dizhi == '') {
this.$message.error('协会地址不能为空');
return;
}
if (this.association_desc == '') {
this.$message.error('协会简介不能为空');
return;
}
if (this.association_guizhang == '') {
this.$message.error('协会规童制度不能为空');
return;
}
if (this.association_image == '') {
this.$message.error('协会头像不能为空');
return;
}
if (this.ruhuixizhu == '') {
this.$message.error('入会须知不能为空');
return;
}
this.$request
.post('/association/update',this.addForm)
.then((res) => {
if(res.code==1){
this.$message.success(res.msg);
localStorage.setItem('associationInfo', JSON.stringify(this.addForm) );
this.getList();
}else {
this.$message.error(res.msg);
}
console.log(res);
})
.catch((e) => {
console.log(e);
});
},
onCreated(editor) {
this.editor = Object.seal(editor);
},
onCreatedGuiZhang(editor) {
this.editorGuiZhang = Object.seal(editor);
},
onCreatedRuHui(editor){
this.editorRuHui = Object.seal(editor);
}
}
}
</script>
<style src="@wangeditor/editor/dist/css/style.css"></style>

View File

@ -0,0 +1,158 @@
<template>
<t-row :gutter="[16, 16]">
<t-col :xs="12" :xl="9">
<t-card title="统计数据" subtitle="(万元)" class="dashboard-chart-card" :bordered="false">
<template #actions>
<div class="dashboard-chart-title-container">
<t-date-range-picker
class="card-date-picker-container"
theme="primary"
mode="date"
:default-value="LAST_7_DAYS"
@change="onCurrencyChange"
/>
</div>
</template>
<div
id="monitorContainer"
ref="monitorContainer"
:style="{ width: '100%', height: `${resizeTime * 326}px` }"
></div>
</t-card>
</t-col>
<t-col :xs="12" :xl="3">
<t-card title="销售渠道" :subtitle="currentMonth" class="dashboard-chart-card" :bordered="false">
<div
id="countContainer"
ref="countContainer"
:style="{ width: `${resizeTime * 326}px`, height: `${resizeTime * 326}px`, margin: '0 auto' }"
></div>
</t-card>
</t-col>
</t-row>
</template>
<script>
import { TooltipComponent, LegendComponent, GridComponent } from 'echarts/components';
import { PieChart, LineChart } from 'echarts/charts';
import { CanvasRenderer } from 'echarts/renderers';
import * as echarts from 'echarts/core';
import { mapState } from 'vuex';
import { LAST_7_DAYS } from '@/utils/date';
import { getPieChartDataSet, getLineChartDataSet } from '../index';
import { changeChartsTheme } from '@/utils/color';
echarts.use([TooltipComponent, LegendComponent, PieChart, GridComponent, LineChart, CanvasRenderer]);
export default {
name: 'MiddleChart',
data() {
return {
LAST_7_DAYS,
resizeTime: 1,
currentMonth: this.getThisMonth(),
};
},
computed: {
...mapState('setting', ['brandTheme', 'mode']), //
},
watch: {
brandTheme() {
changeChartsTheme([this.countChart, this.monitorChart]);
},
mode() {
[this.countChart, this.monitorChart].forEach((item) => {
item.dispose();
});
this.renderCharts();
},
},
mounted() {
this.$nextTick(() => {
this.updateContainer();
});
window.addEventListener('resize', this.updateContainer, false);
this.renderCharts();
},
methods: {
/** 获取当前选中时间的短时间表达法 */
getThisMonth(checkedValues = '') {
let date;
if (!checkedValues || checkedValues.length === 0) {
date = new Date();
return `${date.getFullYear()}-${date.getMonth() + 1}`;
}
date = new Date(checkedValues[0]);
const date2 = new Date(checkedValues[1]);
const startMonth = date.getMonth() + 1 > 9 ? date.getMonth() + 1 : `0${date.getMonth() + 1}`;
const endMonth = date2.getMonth() + 1 > 9 ? date2.getMonth() + 1 : `0${date2.getMonth() + 1}`;
return `${date.getFullYear()}-${startMonth}${date2.getFullYear()}-${endMonth}`;
},
/** 资金走趋选择 */
onCurrencyChange(checkedValues) {
const { chartColors } = this.$store.state.setting;
this.currentMonth = this.getThisMonth(checkedValues);
this.monitorChart.setOption(getLineChartDataSet({ dateTime: checkedValues, ...chartColors }));
},
updateContainer() {
if (document.documentElement.clientWidth >= 1400 && document.documentElement.clientWidth < 1920) {
this.resizeTime = (document.documentElement.clientWidth / 2080).toFixed(2);
} else if (document.documentElement.clientWidth < 1080) {
this.resizeTime = (document.documentElement.clientWidth / 1080).toFixed(2);
} else {
this.resizeTime = 1;
}
this.countChart.resize({
//
width: `${this.resizeTime * 326}px`,
height: `${this.resizeTime * 326}px`,
});
this.monitorChart.resize({
//
width: this.monitorContainer.clientWidth,
height: `${this.resizeTime * 326}px`,
});
},
renderCharts() {
const { chartColors } = this.$store.state.setting;
//
if (!this.monitorContainer) {
this.monitorContainer = document.getElementById('monitorContainer');
}
this.monitorChart = echarts.init(this.monitorContainer);
this.monitorChart.setOption(getLineChartDataSet({ ...chartColors }));
//
if (!this.countContainer) {
this.countContainer = document.getElementById('countContainer');
}
this.countChart = echarts.init(this.countContainer);
const option = getPieChartDataSet(chartColors);
this.countChart.setOption(option);
},
},
};
</script>
<style lang="less" scoped>
.dashboard-chart-card {
padding: 8px;
/deep/ .t-card__header {
padding-bottom: 24px;
}
/deep/ .t-card__title {
font-size: 20px;
font-weight: 500;
}
}
</style>

View File

@ -0,0 +1,189 @@
<template>
<t-card :bordered="false">
<t-row>
<t-col :xs="12" :xl="9">
<t-card
:bordered="false"
title="出入库概览"
subtitle="(件)"
:class="{ 'dashboard-overview-card': true, 'overview-panel': true }"
>
<template #actions>
<t-date-range-picker
class="card-date-picker-container"
theme="primary"
mode="date"
:default-value="LAST_7_DAYS"
@change="onStokeDataChange"
/>
</template>
<div
id="stokeContainer"
style="width: 100%; height: 351px"
ref="stokeContainer"
class="dashboard-chart-container"
></div>
</t-card>
</t-col>
<t-col :xs="12" :xl="3">
<t-card :bordered="false" :class="{ 'dashboard-overview-card': true, 'export-panel': true }">
<template #actions>
<t-button>导出数据</t-button>
</template>
<t-row>
<t-col :xs="6" :xl="12">
<t-card :bordered="false" subtitle="本月出库总计(件)" class="inner-card">
<div class="inner-card__content">
<div class="inner-card__content-title">1726</div>
<div class="inner-card__content-footer">
自从上周以来
<trend class="trend-tag" type="down" :is-reverse-color="false" describe="20.3%" />
</div>
</div>
</t-card>
</t-col>
<t-col :xs="6" :xl="12">
<t-card :bordered="false" subtitle="本月入库总计(件)" class="inner-card">
<div class="inner-card__content">
<div class="inner-card__content-title">226</div>
<div class="inner-card__content-footer">
自从上周以来
<trend class="trend-tag" type="down" :is-reverse-color="false" describe="20.3%" />
</div>
</div>
</t-card>
</t-col>
</t-row>
</t-card>
</t-col>
</t-row>
</t-card>
</template>
<script>
import { TooltipComponent, LegendComponent, GridComponent } from 'echarts/components';
import { BarChart } from 'echarts/charts';
import { CanvasRenderer } from 'echarts/renderers';
import * as echarts from 'echarts/core';
import { mapState } from 'vuex';
import { constructInitDataset } from '../index';
import { changeChartsTheme } from '@/utils/color';
import { LAST_7_DAYS } from '@/utils/date';
import Trend from '@/components/trend/index.vue';
import { PANE_LIST, SALE_TEND_LIST, BUY_TEND_LIST, SALE_COLUMNS, BUY_COLUMNS } from '@/service/service-base';
echarts.use([TooltipComponent, LegendComponent, GridComponent, BarChart, CanvasRenderer]);
export default {
name: 'Overview',
components: {
Trend,
},
data() {
return {
panelList: PANE_LIST,
buyTendList: BUY_TEND_LIST,
saleTendList: SALE_TEND_LIST,
saleColumns: SALE_COLUMNS,
buyColumns: BUY_COLUMNS,
LAST_7_DAYS,
};
},
computed: {
...mapState('setting', ['brandTheme', 'mode']), //
},
watch: {
brandTheme() {
changeChartsTheme([this.stokeChart]);
},
mode() {
[this.stokeChart].forEach((item) => {
item.dispose();
});
this.renderCharts();
},
},
mounted() {
this.$nextTick(() => {
this.updateContainer();
});
window.addEventListener('resize', this.updateContainer, false);
this.renderCharts();
},
methods: {
/** 出入库概览日期更新 */
onStokeDataChange(checkedValues) {
const { chartColors } = this.$store.state.setting;
this.stokeChart.setOption(constructInitDataset({ dateTime: checkedValues, ...chartColors }));
},
updateContainer() {
this.stokeChart.resize({
//
width: this.stokeContainer.clientWidth,
height: this.stokeContainer.clientHeight,
});
},
renderCharts() {
const { chartColors } = this.$store.state.setting;
//
if (!this.stokeContainer) this.stokeContainer = document.getElementById('stokeContainer');
this.stokeChart = echarts.init(this.stokeContainer);
this.stokeChart.setOption(constructInitDataset({ dateTime: LAST_7_DAYS, ...chartColors }));
},
},
};
</script>
<style lang="less" scoped>
@import '@/style/variables.less';
.dashboard-overview-card {
/deep/ .t-card__header {
padding-bottom: 24px;
}
/deep/ .t-card__title {
font-size: 20px;
font-weight: 500;
}
&.overview-panel {
border-right: none;
}
&.export-panel {
border-left: none;
}
}
.inner-card {
padding: 24px 0;
/deep/ .t-card__header {
padding-bottom: 0;
}
&__content {
&-title {
font-size: 36px;
line-height: 44px;
}
&-footer {
display: flex;
align-items: center;
line-height: 22px;
color: var(--td-text-color-placeholder);
.trend-tag {
margin-left: 4px;
}
}
}
}
</style>

View File

@ -0,0 +1,111 @@
<template>
<t-row :gutter="[16, 16]">
<t-col :xs="12" :xl="6">
<t-card title="销售订单排名" class="dashboard-rank-card" :bordered="false">
<template #actions>
<t-radio-group default-value="dateVal">
<t-radio-button value="dateVal">本周</t-radio-button>
<t-radio-button value="monthVal">近三个月</t-radio-button>
</t-radio-group>
</template>
<t-table :data="saleTendList" :columns="saleColumns" rowKey="productName">
<template #index="{ rowIndex }">
<span :class="getRankClass(rowIndex)">
{{ rowIndex + 1 }}
</span>
</template>
<span slot="growUp" slot-scope="{ row }">
<trend :type="row.growUp > 0 ? 'up' : 'down'" :describe="Math.abs(row.growUp)" />
</span>
<template #operation="slotProps">
<a class="link" @click="rehandleClickOp(slotProps)">详情</a>
</template>
</t-table>
</t-card>
</t-col>
<t-col :xs="12" :xl="6">
<t-card title="采购订单排名" class="dashboard-rank-card" :bordered="false">
<template #actions>
<t-radio-group default-value="dateVal">
<t-radio-button value="dateVal">本周</t-radio-button>
<t-radio-button value="monthVal">近三个月</t-radio-button>
</t-radio-group>
</template>
<t-table :data="buyTendList" :columns="buyColumns" rowKey="productName">
<template #index="{ rowIndex }">
<span :class="getRankClass(rowIndex)">
{{ rowIndex + 1 }}
</span>
</template>
<span slot="growUp" slot-scope="{ row }">
<trend :type="row.growUp > 0 ? 'up' : 'down'" :describe="Math.abs(row.growUp)" />
</span>
<template #operation="slotProps">
<a class="link" @click="rehandleClickOp(slotProps)">详情</a>
</template>
</t-table>
</t-card>
</t-col>
</t-row>
</template>
<script>
import Trend from '@/components/trend/index.vue';
import { SALE_TEND_LIST, BUY_TEND_LIST, SALE_COLUMNS, BUY_COLUMNS } from '@/service/service-base';
export default {
name: 'RankList',
components: {
Trend,
},
data() {
return {
buyTendList: BUY_TEND_LIST,
saleTendList: SALE_TEND_LIST,
saleColumns: SALE_COLUMNS,
buyColumns: BUY_COLUMNS,
};
},
methods: {
rehandleClickOp(val) {
console.log(val);
},
getRankClass(index) {
return ['dashboard-rank__cell', { 'dashboard-rank__cell--top': index < 3 }];
},
},
};
</script>
<style lang="less" scoped>
@import '@/style/variables.less';
.dashboard-rank-card {
padding: 8px;
/deep/ .t-card__header {
padding-bottom: 24px;
}
/deep/ .t-card__title {
font-size: 20px;
font-weight: 500;
}
}
.dashboard-rank__cell {
display: inline-flex;
width: 24px;
height: 24px;
border-radius: 50%;
color: white;
font-size: 14px;
background-color: var(--td-gray-color-5);
align-items: center;
justify-content: center;
font-weight: 700;
&--top {
background: var(--td-brand-color);
}
}
</style>

View File

@ -0,0 +1,246 @@
<template>
<t-row :gutter="[16, 16]">
<t-col :xs="6" :xl="3" v-for="(item, index) in panelList" :key="item.title">
<t-card
:bordered="false"
:title="item.title"
:style="{ height: '168px' }"
:class="{ 'dashboard-item': true, 'dashboard-item--main-color': index == 0 }"
>
<div class="dashboard-item-top">
<span :style="{ fontSize: `${resizeTime * 36}px` }">{{ item.number }}</span>
</div>
<div class="dashboard-item-left">
<div
v-if="index === 0"
id="moneyContainer"
class="dashboard-chart-container"
:style="{ width: `${resizeTime * 120}px`, height: `${resizeTime * 66}px` }"
></div>
<div
v-else-if="index === 1"
id="refundContainer"
class="dashboard-chart-container"
:style="{ width: `${resizeTime * 120}px`, height: `${resizeTime * 42}px` }"
></div>
<span v-else-if="index === 2" :style="{ marginTop: `-24px` }">
<usergroup-icon />
</span>
<span v-else :style="{ marginTop: '-24px' }">
<file-icon />
</span>
</div>
<template #footer>
<div class="dashboard-item-bottom">
<div class="dashboard-item-block">
自从上周以来
<trend
class="dashboard-item-trend"
:type="item.upTrend ? 'up' : 'down'"
:is-reverse-color="index === 0"
:describe="item.upTrend || item.downTrend"
/>
</div>
<chevron-right-icon />
</div>
</template>
</t-card>
</t-col>
</t-row>
</template>
<script>
import { LineChart, BarChart } from 'echarts/charts';
import * as echarts from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import { UsergroupIcon, FileIcon, ChevronRightIcon } from 'tdesign-icons-vue';
import { mapState } from 'vuex';
import Trend from '@/components/trend/index.vue';
import { constructInitDashboardDataset } from '../index';
import { changeChartsTheme } from '@/utils/color';
import { PANE_LIST } from '@/service/service-base';
echarts.use([LineChart, BarChart, CanvasRenderer]);
export default {
name: 'TopPanel',
components: {
Trend,
UsergroupIcon,
FileIcon,
ChevronRightIcon,
},
data() {
return {
resizeTime: 1,
panelList: PANE_LIST,
};
},
computed: {
...mapState('setting', ['brandTheme', 'mode']), //
},
watch: {
brandTheme() {
changeChartsTheme([this.refundChart]);
},
mode() {
[this.moneyCharts, this.refundChart].forEach((item) => {
item.dispose();
});
this.renderCharts();
},
},
mounted() {
this.$nextTick(() => {
this.updateContainer();
});
window.addEventListener('resize', this.updateContainer, false);
this.renderCharts();
},
methods: {
updateContainer() {
if (document.documentElement.clientWidth >= 1400 && document.documentElement.clientWidth < 1920) {
this.resizeTime = (document.documentElement.clientWidth / 2080).toFixed(2);
} else if (document.documentElement.clientWidth < 1080) {
this.resizeTime = (document.documentElement.clientWidth / 1080).toFixed(2);
} else {
this.resizeTime = 1;
}
this.moneyCharts.resize({
//
width: `${this.resizeTime * 120}px`,
height: `${this.resizeTime * 66}px`,
});
this.refundChart.resize({
//
width: `${this.resizeTime * 120}px`,
height: `${this.resizeTime * 42}px`,
});
},
renderCharts() {
const { chartColors } = this.$store.state.setting;
//
if (!this.moneyContainer) this.moneyContainer = document.getElementById('moneyContainer');
this.moneyCharts = echarts.init(this.moneyContainer);
this.moneyCharts.setOption(constructInitDashboardDataset('line'));
// 退
if (!this.refundContainer) this.refundContainer = document.getElementById('refundContainer');
this.refundChart = echarts.init(this.refundContainer);
this.refundChart.setOption(constructInitDashboardDataset('bar', chartColors));
},
},
};
</script>
<style lang="less" scoped>
@import '@/style/variables.less';
.dashboard-item {
padding: 8px;
/deep/ .t-card__footer {
padding-top: 0;
}
/deep/ .t-card__title {
font-size: 14px;
font-weight: 500;
}
/deep/ .t-card__body {
display: flex;
flex-direction: column;
justify-content: space-between;
flex: 1;
position: relative;
}
&:hover {
cursor: pointer;
}
&-top {
display: flex;
flex-direction: row;
align-items: flex-start;
> span {
display: inline-block;
color: var(--td-text-color-primary);
font-size: 36px;
line-height: 44px;
}
}
&-bottom {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
> .t-icon {
cursor: pointer;
}
}
&-block {
display: flex;
align-items: center;
justify-content: center;
line-height: 22px;
color: var(--td-text-color-placeholder);
}
&-trend {
margin-left: 8px;
}
&-left {
position: absolute;
top: 0;
right: 32px;
> span {
display: inline-flex;
align-items: center;
justify-content: center;
width: 56px;
height: 56px;
background: var(--td-brand-color-1);
border-radius: 50%;
.t-icon {
font-size: 24px;
color: var(--td-brand-color);
}
}
}
//
&--main-color {
background: var(--td-brand-color);
color: var(--td-text-color-primary);
/deep/ .t-card__title,
.dashboard-item-top span,
.dashboard-item-bottom {
color: var(--td-text-color-anti);
}
.dashboard-item-block {
color: var(--td-text-color-anti);
opacity: 0.6;
}
.dashboard-item-bottom {
color: var(--td-text-color-anti);
}
}
}
</style>

View File

@ -0,0 +1,702 @@
import dayjs from 'dayjs';
import { getChartListColor } from '@/utils/color';
import { getRandomArray } from '@/utils/charts';
/** 首页 dashboard 折线图 */
export function constructInitDashboardDataset(type: string) {
const dateArray: Array<string> = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
const datasetAxis = {
xAxis: {
type: 'category',
show: false,
data: dateArray,
},
yAxis: {
show: false,
type: 'value',
},
grid: {
top: 0,
left: 0,
right: 0,
bottom: 0,
},
};
if (type === 'line') {
const lineDataset = {
...datasetAxis,
color: ['#fff'],
series: [
{
data: [150, 230, 224, 218, 135, 147, 260],
type,
showSymbol: true,
symbol: 'circle',
symbolSize: 0,
markPoint: {
data: [
{ type: 'max', name: '最大值' },
{ type: 'min', name: '最小值' },
],
},
itemStyle: {
normal: {
lineStyle: {
width: 2,
},
},
},
},
],
};
return lineDataset;
}
if (type === 'bar') {
const barDataset = {
...datasetAxis,
color: getChartListColor(),
series: [
{
data: [
100,
130,
184,
218,
{
value: 135,
itemStyle: {
opacity: 0.2,
},
},
{
value: 118,
itemStyle: {
opacity: 0.2,
},
},
{
value: 60,
itemStyle: {
opacity: 0.2,
},
},
],
type,
barWidth: 9,
},
],
};
return barDataset;
}
}
/** 柱状图数据源 */
export function constructInitDataset({
dateTime = [],
placeholderColor,
borderColor,
}: { dateTime: Array<string> } & Record<string, string>) {
const divideNum = 10;
const timeArray = [];
const inArray = [];
const outArray = [];
for (let i = 0; i < divideNum; i++) {
if (dateTime.length > 0) {
const dateAbsTime: number = (new Date(dateTime[1]).getTime() - new Date(dateTime[0]).getTime()) / divideNum;
const enhandTime: number = new Date(dateTime[0]).getTime() + dateAbsTime * i;
timeArray.push(dayjs(enhandTime).format('MM-DD'));
} else {
timeArray.push(
dayjs()
.subtract(divideNum - i, 'day')
.format('MM-DD'),
);
}
inArray.push(getRandomArray().toString());
outArray.push(getRandomArray().toString());
}
const dataset = {
color: getChartListColor(),
tooltip: {
trigger: 'item',
},
xAxis: {
type: 'category',
data: timeArray,
axisLabel: {
color: placeholderColor,
},
axisLine: {
lineStyle: {
color: borderColor,
width: 1,
},
},
},
yAxis: {
type: 'value',
axisLabel: {
color: placeholderColor,
},
splitLine: {
lineStyle: {
color: borderColor,
},
},
},
grid: {
top: '5%',
left: '25px',
right: 0,
bottom: '60px',
},
legend: {
icon: 'rect',
itemWidth: 12,
itemHeight: 4,
itemGap: 48,
textStyle: {
fontSize: 12,
color: placeholderColor,
},
left: 'center',
bottom: '0',
orient: 'horizontal',
data: ['本月', '上月'],
},
series: [
{
name: '本月',
data: outArray,
type: 'bar',
},
{
name: '上月',
data: inArray,
type: 'bar',
},
],
};
return dataset;
}
export function getLineChartDataSet({
dateTime = [],
placeholderColor,
borderColor,
}: { dateTime?: Array<string> } & Record<string, string>) {
const divideNum = 10;
const timeArray = [];
const inArray = [];
const outArray = [];
for (let i = 0; i < divideNum; i++) {
if (dateTime.length > 0) {
const dateAbsTime: number = (new Date(dateTime[1]).getTime() - new Date(dateTime[0]).getTime()) / divideNum;
const enhandTime: number = new Date(dateTime[0]).getTime() + dateAbsTime * i;
timeArray.push(dayjs(enhandTime).format('MM-DD'));
} else {
timeArray.push(
dayjs()
.subtract(divideNum - i, 'day')
.format('MM-DD'),
);
}
inArray.push(getRandomArray().toString());
outArray.push(getRandomArray().toString());
}
const dataSet = {
color: getChartListColor(),
tooltip: {
trigger: 'item',
},
grid: {
left: '0',
right: '20px',
top: '5px',
bottom: '36px',
containLabel: true,
},
legend: {
left: 'center',
bottom: '0',
orient: 'horizontal', // legend 横向布局。
data: ['本月', '上月'],
textStyle: {
fontSize: 12,
color: placeholderColor,
},
},
xAxis: {
type: 'category',
data: timeArray,
boundaryGap: false,
axisLabel: {
color: placeholderColor,
},
axisLine: {
lineStyle: {
width: 1,
},
},
},
yAxis: {
type: 'value',
axisLabel: {
color: placeholderColor,
},
splitLine: {
lineStyle: {
color: borderColor,
},
},
},
series: [
{
name: '本月',
data: outArray,
type: 'line',
smooth: false,
showSymbol: true,
symbol: 'circle',
symbolSize: 8,
itemStyle: {
normal: {
borderColor,
borderWidth: 1,
},
},
areaStyle: {
normal: {
opacity: 0.1,
},
},
},
{
name: '上月',
data: inArray,
type: 'line',
smooth: false,
showSymbol: true,
symbol: 'circle',
symbolSize: 8,
itemStyle: {
normal: {
borderColor,
borderWidth: 1,
},
},
},
],
};
return dataSet;
}
/**
*
*
* @export
* @param {string} productName
* @param {number} divideNum
*/
export function getSelftItemList(productName: string, divideNum: number): string[] {
const productArray: string[] = [productName];
for (let i = 0; i < divideNum; i++) {
productArray.push(getRandomArray(100 * i).toString());
}
return productArray;
}
/**
*
*
* @export
* @returns {any[]}
*/
export function getScatterDataSet({
dateTime = [],
placeholderColor,
borderColor,
}: { dateTime?: Array<string> } & Record<string, string>): any {
const divideNum = 40;
const timeArray = [];
const inArray = [];
const outArray = [];
for (let i = 0; i < divideNum; i++) {
// const [timeArray, inArray, outArray] = dataset;
if (dateTime.length > 0) {
const dateAbsTime: number = (new Date(dateTime[1]).getTime() - new Date(dateTime[0]).getTime()) / divideNum;
const enhandTime: number = new Date(dateTime[0]).getTime() + dateAbsTime * i;
timeArray.push(dayjs(enhandTime).format('MM-DD'));
} else {
timeArray.push(
dayjs()
.subtract(divideNum - i, 'day')
.format('MM-DD'),
);
}
inArray.push(getRandomArray().toString());
outArray.push(getRandomArray().toString());
}
return {
color: getChartListColor(),
xAxis: {
data: timeArray,
axisLabel: {
color: placeholderColor,
},
splitLine: { show: false },
axisLine: {
lineStyle: {
color: borderColor,
width: 1,
},
},
},
yAxis: {
type: 'value',
// splitLine: { show: false},
axisLabel: {
color: placeholderColor,
},
nameTextStyle: {
padding: [0, 0, 0, 60],
},
axisTick: {
show: false,
axisLine: {
show: false,
},
},
axisLine: {
show: false,
},
splitLine: {
lineStyle: {
color: borderColor,
},
},
},
tooltip: {
trigger: 'item',
},
grid: {
top: '5px',
left: '25px',
right: '5px',
bottom: '60px',
},
legend: {
left: 'center',
bottom: '0',
orient: 'horizontal', // legend 横向布局。
data: ['按摩仪', '咖啡机'],
itemHeight: 8,
itemWidth: 8,
textStyle: {
fontSize: 12,
color: placeholderColor,
},
},
series: [
{
name: '按摩仪',
symbolSize: 10,
data: outArray.reverse(),
type: 'scatter',
},
{
name: '咖啡机',
symbolSize: 10,
data: inArray.concat(inArray.reverse()),
type: 'scatter',
},
],
};
}
/**
*
*
* @export
* @returns {any[]}
*/
export function getAreaChartDataSet(): any {
const xAxisData = [];
const data1 = [];
const data2 = [];
for (let i = 0; i < 50; i++) {
xAxisData.push(`${i}`);
data1.push((getRandomArray() * Math.sin(i / 5) * (i / 5 - 5) + i / 6) * 2);
data2.push((getRandomArray() * Math.cos(i / 5) * (i / 5 - 5) + i / 6) * 2);
}
return {
color: getChartListColor(),
// title: {
// text: '柱状图动画延迟',
// },
legend: {
left: 'center',
bottom: '5%',
orient: 'horizontal',
data: ['测试', '上线'],
},
tooltip: {
trigger: 'item',
},
xAxis: {
data: xAxisData,
splitLine: {
show: false,
},
},
yAxis: {},
series: [
{
name: '测试',
type: 'bar',
data: data1,
emphasis: {
focus: 'series',
},
animationDelay(idx: number) {
return idx * 10;
},
},
{
name: '上线',
type: 'bar',
data: data2,
emphasis: {
focus: 'series',
},
animationDelay(idx: number) {
return idx * 10 + 100;
},
},
],
animationEasing: 'elasticOut',
animationDelayUpdate(idx: number) {
return idx * 5;
},
};
}
/**
*
*
* @export
* @param {boolean} [isMonth=false]
* @returns {*}
*/
export function getColumnChartDataSet(isMonth = false) {
if (isMonth) {
return {
color: getChartListColor(),
legend: {
left: 'center',
top: '10%',
orient: 'horizontal', // legend 横向布局。
data: ['直接访问'],
},
tooltip: {
trigger: 'axis',
axisPointer: {
// 坐标轴指示器,坐标轴触发有效
type: 'shadow', // 默认为直线,可选为:'line' | 'shadow'
},
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: [
{
type: 'category',
data: ['1', '4', '8', '12', '16', '20', '24'],
axisTick: {
alignWithLabel: true,
},
},
],
yAxis: [
{
type: 'value',
},
],
series: [
{
name: '直接访问',
type: 'bar',
barWidth: '60%',
data: [
getRandomArray(Math.random() * 100),
getRandomArray(Math.random() * 200),
getRandomArray(Math.random() * 300),
getRandomArray(Math.random() * 400),
getRandomArray(Math.random() * 500),
getRandomArray(Math.random() * 600),
getRandomArray(Math.random() * 700),
],
},
],
};
}
return {
color: getChartListColor(),
tooltip: {
trigger: 'axis',
axisPointer: {
// 坐标轴指示器,坐标轴触发有效
type: 'shadow', // 默认为直线,可选为:'line' | 'shadow'
},
},
legend: {
left: 'center',
bottom: '0%',
orient: 'horizontal', // legend 横向布局。
data: ['直接访问'],
},
grid: {
left: '3%',
right: '4%',
bottom: '13%',
containLabel: true,
},
xAxis: [
{
type: 'category',
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
axisTick: {
alignWithLabel: true,
},
},
],
yAxis: [
{
type: 'value',
},
],
series: [
{
name: '直接访问',
type: 'bar',
barWidth: '20%',
data: [
getRandomArray(Math.random() * 100),
getRandomArray(Math.random() * 200),
getRandomArray(Math.random() * 300),
getRandomArray(Math.random() * 400),
getRandomArray(Math.random() * 500),
getRandomArray(Math.random() * 600),
getRandomArray(Math.random() * 700),
],
},
],
};
}
export function getPieChartDataSet({
radius = 42,
textColor,
placeholderColor,
containerColor,
}: { radius: number } & Record<string, string>) {
return {
color: getChartListColor(),
tooltip: {
show: false,
trigger: 'axis',
position: null,
},
grid: {
top: '0',
right: '0',
},
legend: {
selectedMode: false,
itemWidth: 12,
itemHeight: 4,
textStyle: {
fontSize: 12,
color: placeholderColor,
},
left: 'center',
bottom: '0',
orient: 'horizontal', // legend 横向布局。
},
series: [
{
name: '销售渠道',
type: 'pie',
radius: ['48%', '60%'],
avoidLabelOverlap: true,
selectedMode: true,
hoverAnimation: true,
silent: true,
itemStyle: {
borderColor: containerColor,
borderWidth: 1,
},
label: {
show: true,
position: 'center',
formatter: ['{value|{d}%}', '{name|{b}渠道占比}'].join('\n'),
rich: {
value: {
color: textColor,
fontSize: 28,
fontWeight: 'normal',
lineHeight: 46,
},
name: {
color: '#909399',
fontSize: 12,
lineHeight: 14,
},
},
},
emphasis: {
label: {
show: true,
formatter: ['{value|{d}%}', '{name|{b}渠道占比}'].join('\n'),
rich: {
value: {
color: textColor,
fontSize: 28,
fontWeight: 'normal',
lineHeight: 46,
},
name: {
color: '#909399',
fontSize: 14,
lineHeight: 14,
},
},
},
},
labelLine: {
show: false,
},
data: [
{
value: 1048,
name: '线上',
},
{ value: radius * 7, name: '门店' },
],
},
],
};
}

View File

@ -0,0 +1,44 @@
<template>
<div>
<!-- <t-back-top-->
<!-- container=".tdesign-starter-layout"-->
<!-- :visible-height="0"-->
<!-- style="position: absolute"-->
<!-- :offset="['24px', '80px']"-->
<!-- >-->
<!-- <t-icon name="backtop" size="20px" />-->
<!-- </t-back-top>-->
<!-- 顶部 card -->
<top-panel class="row-container" />
<!-- 中部图表 -->
<middle-chart class="row-container" />
<!-- 列表排名 -->
<rank-list class="row-container" />
<!-- 出入库概览 -->
<output-overview class="row-container" />
</div>
</template>
<script>
import TopPanel from './components/TopPanel.vue';
import MiddleChart from './components/MiddleChart.vue';
import RankList from './components/RankList.vue';
import OutputOverview from './components/OutputOverview.vue';
export default {
name: 'DashboardBase',
components: {
TopPanel,
MiddleChart,
RankList,
OutputOverview,
},
};
</script>
<style scoped lang="less">
.row-container {
margin-bottom: 16px;
}
/deep/ .t-card__body {
padding-top: 0;
}
</style>

View File

@ -0,0 +1,267 @@
import dayjs from 'dayjs';
import { getChartListColor } from '@/utils/color';
import { getDateArray, getRandomArray } from '@/utils/charts';
/**
*
*
* @export
* @returns {any[]}
*/
export function getScatterDataSet({
dateTime = [],
placeholderColor,
borderColor,
}: { dateTime?: Array<string> } & Record<string, string>): any {
const divideNum = 40;
const timeArray = [];
const inArray = [];
const outArray = [];
for (let i = 0; i < divideNum; i++) {
// const [timeArray, inArray, outArray] = dataset;
if (dateTime.length > 0) {
const dateAbsTime: number = (new Date(dateTime[1]).getTime() - new Date(dateTime[0]).getTime()) / divideNum;
const endTime: number = new Date(dateTime[0]).getTime() + dateAbsTime * i;
timeArray.push(dayjs(endTime).format('MM-DD'));
} else {
timeArray.push(
dayjs()
.subtract(divideNum - i, 'day')
.format('MM-DD'),
);
}
inArray.push(getRandomArray().toString());
outArray.push(getRandomArray().toString());
}
return {
color: getChartListColor(),
xAxis: {
data: timeArray,
axisLabel: {
color: placeholderColor,
},
splitLine: { show: false },
axisLine: {
lineStyle: {
color: borderColor,
width: 1,
},
},
},
yAxis: {
type: 'value',
// splitLine: { show: false},
axisLabel: {
color: placeholderColor,
},
nameTextStyle: {
padding: [0, 0, 0, 60],
},
axisTick: {
show: false,
axisLine: {
show: false,
},
},
axisLine: {
show: false,
},
splitLine: {
lineStyle: {
color: borderColor,
},
},
},
tooltip: {
trigger: 'item',
},
grid: {
top: '5px',
left: '25px',
right: '5px',
bottom: '60px',
},
legend: {
left: 'center',
bottom: '0',
orient: 'horizontal', // legend 横向布局。
data: ['按摩仪', '咖啡机'],
itemHeight: 8,
itemWidth: 8,
textStyle: {
fontSize: 12,
color: placeholderColor,
},
},
series: [
{
name: '按摩仪',
symbolSize: 10,
data: outArray.reverse(),
type: 'scatter',
},
{
name: '咖啡机',
symbolSize: 10,
data: inArray.concat(inArray.reverse()),
type: 'scatter',
},
],
};
}
/** 折线图数据 */
export function getFolderLineDataSet({
dateTime = [],
placeholderColor,
borderColor,
}: { dateTime?: Array<string> } & Record<string, string>) {
let dateArray: Array<string> = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
if (dateTime.length > 0) {
const divideNum = 7;
dateArray = getDateArray(dateTime, divideNum);
}
return {
color: getChartListColor(),
grid: {
top: '5%',
right: '10px',
left: '30px',
bottom: '60px',
},
legend: {
left: 'center',
bottom: '0',
orient: 'horizontal', // legend 横向布局。
data: ['杯子', '茶叶', '蜂蜜', '面粉'],
textStyle: {
fontSize: 12,
color: placeholderColor,
},
},
xAxis: {
type: 'category',
data: dateArray,
boundaryGap: false,
axisLabel: {
color: placeholderColor,
},
axisLine: {
lineStyle: {
color: borderColor,
width: 1,
},
},
},
yAxis: {
type: 'value',
axisLabel: {
color: placeholderColor,
},
splitLine: {
lineStyle: {
color: borderColor,
},
},
},
tooltip: {
trigger: 'item',
},
series: [
{
showSymbol: true,
symbol: 'circle',
symbolSize: 8,
name: '杯子',
stack: '总量',
data: [
getRandomArray(),
getRandomArray(),
getRandomArray(),
getRandomArray(),
getRandomArray(),
getRandomArray(),
getRandomArray(),
],
type: 'line',
itemStyle: {
normal: {
borderColor,
borderWidth: 1,
},
},
},
{
showSymbol: true,
symbol: 'circle',
symbolSize: 8,
name: '茶叶',
stack: '总量',
data: [
getRandomArray(),
getRandomArray(),
getRandomArray(),
getRandomArray(),
getRandomArray(),
getRandomArray(),
getRandomArray(),
],
type: 'line',
itemStyle: {
normal: {
borderColor,
borderWidth: 1,
},
},
},
{
showSymbol: true,
symbol: 'circle',
symbolSize: 8,
name: '蜂蜜',
stack: '总量',
data: [
getRandomArray(),
getRandomArray(),
getRandomArray(),
getRandomArray(),
getRandomArray(),
getRandomArray(),
getRandomArray(),
],
type: 'line',
itemStyle: {
normal: {
borderColor,
borderWidth: 1,
},
},
},
{
showSymbol: true,
symbol: 'circle',
symbolSize: 8,
name: '面粉',
stack: '总量',
data: [
getRandomArray(),
getRandomArray(),
getRandomArray(),
getRandomArray(),
getRandomArray(),
getRandomArray(),
getRandomArray(),
],
type: 'line',
itemStyle: {
normal: {
borderColor,
borderWidth: 1,
},
},
},
],
};
}

View File

@ -0,0 +1,242 @@
<template>
<div class="dashboard-detail">
<t-card title="本月采购申请情况" class="dashboard-detail-card" :bordered="false">
<t-row :gutter="[16, 16]">
<t-col v-for="(item, index) in PANE_LIST_DATA" :key="index" :xs="6" :xl="3">
<t-card :class="['dashboard-list-card']" :description="item.title">
<div class="dashboard-list-card__number">{{ item.number }}</div>
<div class="dashboard-list-card__text">
<div class="dashboard-list-card__text-left">
环比
<trend class="icon" :type="item.upTrend ? 'up' : 'down'" :describe="item.upTrend || item.downTrend" />
</div>
<chevron-right-icon />
</div>
</t-card>
</t-col>
</t-row>
</t-card>
<t-row :gutter="[16, 16]" class="row-margin">
<t-col :xs="12" :xl="9">
<t-card :class="{ 'dashboard-detail-card': true }" title="采购商品申请趋势" subtitle="(件)" :bordered="false">
<template #actions>
<t-date-range-picker
style="width: 250px"
:default-value="LAST_7_DAYS"
theme="primary"
mode="date"
@change="onMaterialChange"
/>
</template>
<div id="lineContainer" ref="lineContainer" style="width: 100%; height: 410px"></div>
</t-card>
</t-col>
<t-col :xs="12" :xl="3">
<product-card
v-for="(item, index) in PRODUCT_LIST"
:key="index"
:product="item"
:class="{ 'row-margin': index !== 0 }"
/>
</t-col>
</t-row>
<t-card :class="{ 'dashboard-detail-card': true }" title="采购商品满意度分布" class="row-margin" :bordered="false">
<template #actions>
<t-date-range-picker
style="display: inline-block; margin-right: 8px; width: 250px"
:defaultValue="LAST_7_DAYS"
theme="primary"
mode="date"
@change="onSatisfyChange"
>
</t-date-range-picker>
<t-button>导出数据</t-button>
</template>
<div id="scatterContainer" style="width: 100%; height: 374px"></div>
</t-card>
</div>
</template>
<script lang="ts">
import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components';
import { LineChart, ScatterChart } from 'echarts/charts';
import { CanvasRenderer } from 'echarts/renderers';
import * as echarts from 'echarts/core';
import { mapState } from 'vuex';
import { ChevronRightIcon } from 'tdesign-icons-vue';
import Trend from '@/components/trend/index.vue';
import ProductCard from '@/components/product-card/index.vue';
import { LAST_7_DAYS } from '@/utils/date';
import { changeChartsTheme } from '@/utils/color';
import { PANE_LIST_DATA, PRODUCT_LIST } from '@/service/service-detail';
import { getFolderLineDataSet, getScatterDataSet } from './index';
echarts.use([GridComponent, LegendComponent, TooltipComponent, LineChart, ScatterChart, CanvasRenderer]);
export default {
name: 'DashboardDetail',
components: { Trend, ProductCard, ChevronRightIcon },
data() {
return {
PANE_LIST_DATA,
PRODUCT_LIST,
dashboardBase: '',
lineContainer: '',
scatterContainer: '',
lineChart: '',
scatterChart: '',
productList: [
{
description: 'SSL证书又叫服务器证书腾讯云为您提供证书的一站式服务包括免费、付费证书的申请、管理及部',
index: 1,
isSetup: true,
name: 'SSL证书',
type: 4,
},
],
LAST_7_DAYS,
};
},
computed: {
...mapState('setting', ['brandTheme', 'mode']),
},
watch: {
brandTheme() {
changeChartsTheme([this.lineChart, this.scatterChart]);
},
mode() {
this.renderCharts();
},
},
mounted() {
this.$nextTick(() => {
this.updateContainer();
});
this.renderCharts();
},
methods: {
/** 采购商品满意度选择 */
onSatisfyChange(value: string) {
const { chartColors } = this.$store.state.setting;
this.scatterChart.setOption(getScatterDataSet({ dateTime: value, ...chartColors }));
},
/** 采购商品申请趋势选择 */
onMaterialChange(value: string) {
const { chartColors } = this.$store.state.setting;
this.lineChart.setOption(getFolderLineDataSet({ dateTime: value, ...chartColors }));
},
updateContainer() {
this.lineChart.resize?.({
width: this.lineContainer.clientWidth,
height: this.lineContainer.clientHeight,
});
this.scatterChart.resize?.({
width: this.scatterContainer.clientWidth,
height: this.scatterContainer.clientHeight,
});
},
renderCharts() {
const { chartColors } = this.$store.state.setting;
if (!this.lineContainer) {
this.lineContainer = document.getElementById('lineContainer');
}
this.lineChart = echarts.init(this.lineContainer);
this.lineChart.setOption(getFolderLineDataSet({ ...chartColors }));
window.addEventListener('resize', this.updateContainer, false);
if (!this.scatterContainer) {
this.scatterContainer = document.getElementById('scatterContainer');
}
this.scatterChart = echarts.init(this.scatterContainer);
this.scatterChart.setOption(getScatterDataSet({ ...chartColors }));
},
},
};
</script>
<style lang="less" scoped>
@import '@/style/variables.less';
.row-margin {
margin-top: 16px;
}
// 8px;
.dashboard-detail-card {
padding: 8px;
/deep/ .t-card__title {
font-size: 20px;
font-weight: 500;
}
/deep/ .t-card__actions {
display: flex;
align-items: center;
}
}
.dashboard-list-card {
display: flex;
flex-direction: column;
flex: 1;
height: 170px;
padding: 8px;
/deep/ .t-card__header {
padding-bottom: 8px;
}
/deep/ .t-card__body {
flex: 1;
display: flex;
padding-top: 0;
flex-direction: column;
justify-content: space-between;
}
&.dark {
&:hover {
background: var(--td-gray-color-14);
cursor: pointer;
}
}
&.light {
&:hover {
background: var(--td-gray-color-14);
cursor: pointer;
}
}
&__number {
font-size: 36px;
line-height: 44px;
color: var(--td-text-color-primary);
}
&__text {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
font-size: 14px;
color: var(--td-text-color-placeholder);
text-align: left;
line-height: 18px;
&-left {
display: flex;
.icon {
margin: 0 8px;
}
}
}
}
</style>

202
src/pages/login/index.less Normal file
View File

@ -0,0 +1,202 @@
@import '@/style/variables.less';
.light {
&.login-wrapper {
background-color: white;
background-image: url('@/assets/assets-login-bg-white.png');
}
}
.dark {
&.login-wrapper {
background-color: var(--td-bg-color-page);
background-image: url('@/assets/assets-login-bg-black.png');
}
}
.login-wrapper {
height: 100vh;
display: flex;
flex-direction: column;
background-size: cover;
background-position: 100%;
position: relative;
}
.login-container {
position: absolute;
top: 22%;
left: 5%;
min-height: 500px;
line-height: 22px;
}
.title-container {
.title {
font-size: 36px;
line-height: 44px;
color: var(--td-text-color-primary);
margin-top: 4px;
&.margin-no {
margin-top: 0;
}
}
.sub-title {
margin-top: 16px;
.tip {
display: inline-block;
margin-right: 8px;
font-size: 14px;
&:first-child {
color: var(--td-text-color-secondary);
}
&:last-child {
color: var(--td-text-color-primary);
cursor: pointer;
}
}
}
}
.item-container {
width: 400px;
margin-top: 48px;
&.login-qrcode {
.tip-container {
width: 192px;
margin-bottom: 16px;
font-size: 14px;
display: flex;
justify-content: space-between;
.tip {
color: var(--td-text-color-primary);
}
.refresh {
display: flex;
align-items: center;
color: var(--td-brand-color);
.t-icon {
font-size: 14px;
}
&:hover {
cursor: pointer;
}
}
}
.bottom-container {
margin-top: 32px;
}
}
&.login-phone {
.bottom-container {
margin-top: 66px;
}
}
.check-container {
display: flex;
align-items: center;
&.remember-pwd {
margin-bottom: 16px;
justify-content: space-between;
}
.t-checkbox__label {
color: var(--td-text-color-secondary);
}
span {
color: var(--td-brand-color);
&:hover {
cursor: pointer;
}
}
}
.verification-code {
display: flex;
align-items: center;
.t-form__controls {
width: 100%;
button {
flex-shrink: 0;
width: 102px;
height: 40px;
margin-left: 11px;
}
}
}
.btn-container {
margin-top: 48px;
}
}
.switch-container {
margin-top: 24px;
.tip {
font-size: 14px;
color: var(--td-brand-color);
cursor: pointer;
display: inline-flex;
align-items: center;
margin-right: 14px;
&:last-child {
&::after {
display: none;
}
}
&::after {
content: '';
display: block;
width: 1px;
height: 12px;
background: var(--td-gray-color-3);
margin-left: 14px;
}
}
}
.check-container {
font-size: 14px;
color: var(--td-text-color-secondary);
.tip {
float: right;
font-size: 14px;
color: var(--td-brand-color);
}
}
.copyright {
font-size: 14px;
position: absolute;
left: 5%;
bottom: var(--td-comp-size-xxxl);
color: var(--td-text-color-secondary);
}
@media screen and (max-height: 700px) {
.copyright {
display: none;
}
}

184
src/pages/login/index.vue Normal file
View File

@ -0,0 +1,184 @@
<template>
<div class="login-wrapper">
<div class="login-container">
<div class="title-container">
<h1 class="title margin-no">登录到</h1>
<h1 class="title" v-if="info!=''">{{info.association_name}}</h1>
<h1 class="title" v-if="info==''">智慧云商协后台管理系统</h1>
</div>
<t-form
ref="form"
:class="['item-container', `login-login`]"
:data="formData"
:rules="FORM_RULES"
label-width="0"
@submit="onSubmit"
>
<template>
<t-form-item name="zhanghu">
<t-input v-model="formData.zhanghu" size="large" placeholder="请输入账号admin">
<template #prefix-icon>
<user-icon/>
</template>
</t-input>
</t-form-item>
<t-form-item name="password">
<t-input
v-model="formData.password"
size="large"
type="password"
clearable
key="password"
placeholder="请输入登录密码admin"
>
<template #prefix-icon>
<lock-on-icon/>
</template>
<template #suffix-icon>
<browse-icon v-if="showPsw" @click="showPsw = !showPsw" key="browse"/>
<browse-off-icon v-else @click="showPsw = !showPsw" key="browse-off"/>
</template>
</t-input>
</t-form-item>
<div class="check-container remember-pwd">
<t-checkbox>记住账号</t-checkbox>
<div style="color: var(--td-brand-color)">
<span @click="openUrl(0)">会员注册</span>
<span style="margin-left: 20px" @click="openUrl(1)">会员登录</span>
</div>
</div>
</template>
<t-form-item class="btn-container">
<t-button block size="large" type="submit"> 登录</t-button>
</t-form-item>
</t-form>
</div>
<footer class="copyright">Copyright @ 2021-2022 Tencent. All Rights Reserved</footer>
</div>
</template>
<script>
import {UserIcon, LockOnIcon} from 'tdesign-icons-vue';
export default {
components: {
UserIcon,
LockOnIcon,
},
data() {
return {
formData: {
zhanghu: 'admin',
password: 'qinze731344.',
},
FORM_RULES: {
zhanghu: [{required: true, message: '账号必填', type: 'error'}],
password: [{required: true, message: '密码必填', type: 'error'}],
},
showPsw: false,
countDown: 0,
intervalTimer: null,
id:0,
info:''
};
},
mounted() {
console.log(this.$route.query.id);
if(typeof (this.$route.query.id)!='undefined'){
this.id=this.$route.query.id;
this.getInfo();
}
},
methods: {
openUrl(type){
if(this.id===0){
if(type==0){
this.$message.error('后台注册链接有误,请联系管理员或者后台链接!',);
}else{
this.$message.error('后台登录链接有误,请联系管理员或者后台链接!',);
}
}else{
if(type==0){
this.$router.push('/register?id='+this.id);
}else{
this.$router.push('/land?id='+this.id);
}
}
},
getInfo(){
this.$request
.post("/association/find",{association_id:this.id})
.then( (res) => {
console.log(res);
if(res.code==1){
this.info=res.data;
}
})
.catch((e) => {
console.log(e);
});
},
async onSubmit({ validateResult }) {
if (validateResult === true) {
this.$request
.post("/login/login",this.formData)
.then( (res) => {
console.log(res);
if(res.code === 0){
this.$message.error('帐号或密码错误!');
}else{
this.$store.dispatch('user/login', res.data);
this.$message.success('登录成功');
setTimeout(() => {
this.$router.push('/dashboard/base');
}, 1000);
}
})
.catch((e) => {
console.log(e);
});
}
},
},
};
</script>
<style lang="less">
@import url('./index.less');
.login-header {
height: var(--td-comp-size-xxxl);
padding: 0 24px;
display: flex;
justify-content: space-between;
align-items: center;
backdrop-filter: blur(5px);
color: var(--td-text-color-primary);
.logo {
width: 188px;
height: var(--td-comp-size-xxxl);
}
.operations-container {
display: flex;
align-items: center;
.t-button {
margin-left: 16px;
}
.icon {
height: 20px;
width: 20px;
padding: 6px;
box-sizing: content-box;
&:hover {
cursor: pointer;
}
}
}
}
</style>

View File

@ -0,0 +1,327 @@
<template>
<t-card :bordered="false">
<div class="form-step-container">
<t-button @click="add">新增</t-button>
<t-table
rowKey="index"
:data="list"
:columns="columns"
:stripe="true"
:bordered="false"
:hover="true"
size="large"
table-layout="auto"
cellEmptyContent="-"
:pagination="pagination"
>
<template #select="{ row }">
<t-space size="24px">
<t-button theme="warning" @click="edit(row)">编辑</t-button>
<t-popconfirm content="确认删除吗" @confirm="del(row)">
<t-button theme="danger">删除</t-button>
</t-popconfirm>
</t-space>
</template>
</t-table>
<div style="margin-top: 30px">
<t-pagination
:total="pagination.total"
:page-size="pagination.size"
@current-change="onCurrentChange"
:showPageSize="false"
></t-pagination>
</div>
</div>
<t-dialog :header="isEdit?'编辑新闻':'新增新闻'" :visible="addMode" :onClose="onCloseMy" @confirm="onSubmit"
width="45%" top="20px">
<t-form>
<t-form-item label="新闻标题" name="news_title">
<t-input placeholder="请输入新闻标题" v-model="addForm.news_title"/>
</t-form-item>
<t-form-item label="简短标题" name="news_titleshort">
<t-input placeholder="请输入简短标题" v-model="addForm.news_titleshort"/>
</t-form-item>
<t-form-item label="文章分类" name="activity_location">
<t-select v-model="addForm.gory_id">
<t-option v-for="(item,index) in gory_list" :key="index" :label="item.name" :value="item.id" />
</t-select>
</t-form-item>
<t-form-item label="作者" name="news_auto">
<t-input placeholder="请输入作者" v-model="addForm.news_auto"/>
</t-form-item>
<t-form-item label="来源" name="news_source">
<t-input placeholder="请输入来源" v-model="addForm.news_source"/>
</t-form-item>
<t-form-item label="新闻详情" name="news_content">
<div style="border: 1px solid #ccc;z-index: 99">
<!-- 工具栏 -->
<Toolbar
style="border-bottom: 1px solid #ccc"
mode="default"
:editor="editor"
:defaultConfig="toolbarConfig"
/>
<Editor
style="height: 200px"
v-model="addForm.news_content"
:defaultConfig="editorConfig"
mode="default"
@onCreated="(e) => onCreated(e)"
/>
</div>
</t-form-item>
<t-form-item label="新闻主图">
<t-upload
:action="$store.state.user.apiUrl+'/api/common/upload'"
v-model="addForm.news_image_show"
theme="image"
tips="请选择单张图片文件上传"
accept="image/*"
:format-response="formatResponse"
></t-upload>
</t-form-item>
<t-form-item
label="展示时间"
name="days">
<t-date-picker :clearable="true" placeholder="展示时间"
:enableTimePicker="true" :allow-input="false"
v-model="addForm.showtime"></t-date-picker>
</t-form-item>
</t-form>
</t-dialog>
</t-card>
</template>
<script lang="ts">
import store from "@/store";
import {Editor, Toolbar} from '@wangeditor/editor-for-vue';
export default {
components: {Editor, Toolbar},
data() {
return {
visible: false,
infoMode: false,
addForm: {
news_title: '',
news_titleshort: '',
gory_id: null,
news_auto: '',
news_source: '',
news_content: '',
news_image: '',
news_image_show:[],
showtime:'',
},
addMode: false,
isEdit: false,
editID: 0,
list: [],
gory_list:[],
columns: [
{colKey: 'news_title', title: '新闻标题', align: 'center'},
{colKey: 'name', title: '类别', align: 'center'},
{colKey: 'news_auto', title: '作者', align: 'center'},
{colKey: 'news_hits', title: '点击量', align: 'center'},
{colKey: 'news_source', title: '来源', align: 'center'},
{colKey: 'showtime', title: '创建时间', align: 'center'},
{colKey: 'select', title: '操作', width: 200, align: 'center'},
],
pagination: {
page: 1,
size: 10,
total: 0,
},
editor: null,
toolbarConfig: {
showLinkImg: false,
uploadImgShowBase64: true,
excludeKeys: [
'insertVideo', //
'insertImage',//
'insertLink',//
'insertTable',//
'codeBlock',//
]
},
editorConfig: {
placeholder: '',
readOnly: false, //
autoFocus: true,
MENU_CONF: {
uploadImage: {
server: store.state.user.apiUrl + '/api/common/upload',
fieldName: 'file',
customInsert(res: any, insertFn: InsertFnType) { // TS
// customInsert(res, insertFn) { // JS
// res
console.log(res);
// res url alt href
insertFn(res.data.fullurl, '', '')
},
},
uploadVideo: {
server: store.state.user.apiUrl + '/api/common/upload',
fieldName: 'file',
customInsert(res: any, insertFn: InsertFnType) { // TS
// customInsert(res, insertFn) { // JS
// res
console.log(res);
// res url alt href
insertFn(res.data.fullurl, '', '')
},
}
}
},
}
},
beforeDestroy() {
const editor = this.editor
if (editor == null) return
editor.destroy() //
},
mounted() {
//this.member_id=this.$route.query.id;
this.getList();
this.getGory();
},
methods: {
onCurrentChange(d) {
this.pagination.page = d;
this.getList();
},
formatResponse(res) {
console.log(res);
this.addForm.news_image = res.data.url;
return {url: res.data.fullurl};
},
onCreated(editor) {
this.editor = Object.seal(editor);
},
del(d) {
console.log(d);
this.$request
.post('/news/del', {news_id: d.news_id})
.then((res) => {
if (res.code == 1) {
this.$message.success(res.msg);
this.getList();
} else {
this.$message.error(res.msg);
}
console.log(res);
})
.catch((e) => {
console.log(e);
});
},
add() {
this.addMode = true;
this.isEdit = false;
},
edit(d) {
console.log(d);
this.addForm.news_id = d.news_id;
this.addForm.news_title = d.news_title;
this.addForm.news_titleshort = d.news_titleshort;
this.addForm.news_image_show = [{url: store.state.user.apiUrl + d.news_image}];
this.addForm.news_image = d.news_image;
this.addForm.gory_id = d.gory_id;
this.addForm.news_auto = d.news_auto;
this.addForm.news_source = d.news_source;
this.addForm.news_content = d.news_content;
this.addForm.showtime = d.showtime;
this.addMode = true;
this.isEdit = true;
},
getList() {
this.$request
.post("/news", {page: this.pagination.page, size: this.pagination.size})
.then((res) => {
console.log(res);
if (res.code == 1) {
this.list = res.data.ret;
this.pagination.total = res.data.count;
}
})
.catch((e) => {
console.log(e);
});
},
getGory(){
this.$request
.post("/news/gory")
.then((res) => {
console.log(res);
if (res.code == 1) {
this.gory_list=res.data;
}
})
.catch((e) => {
console.log(e);
});
},
onSubmit() {
console.log(this.addForm);
if (this.addForm.news_title == '') {
this.$message.error('新闻标题不能为空');
return;
}
if (this.addForm.news_titleshort == '') {
this.$message.error('简短标题不能为空');
return;
}
if (this.addForm.gory_id == null) {
this.$message.error('请选择新闻分类');
return;
}
if (this.addForm.news_auto == '') {
this.$message.error('作者不能为空');
return;
}
if (this.addForm.news_source == '') {
this.$message.error('来源不能为空');
return;
}
if (this.addForm.news_content == '') {
this.$message.error('详情不能为空');
return;
}
if (this.addForm.news_image=='') {
this.$message.error('新闻主图不能为空');
return;
}
if (this.addForm.showtime=='') {
this.$message.error('展示时间不能为空');
return;
}
var url = '/news/add';
if (this.isEdit) {
url = '/news/update';
}
this.$request
.post(url, this.addForm)
.then((res) => {
console.log(res);
if (res.code == 1) {
this.$message.success(res.msg);
this.addMode = false;
this.getList();
} else {
this.$message.error(res.msg);
}
})
.catch((e) => {
console.log(e);
});
},
onCloseMy() {
this.addMode = false;
this.infoMode = false;
}
}
}
</script>
<style src="@wangeditor/editor/dist/css/style.css"></style>

View File

@ -0,0 +1,202 @@
@import '@/style/variables.less';
.light {
&.login-wrapper {
background-color: white;
background-image: url('@/assets/assets-login-bg-white.png');
}
}
.dark {
&.login-wrapper {
background-color: var(--td-bg-color-page);
background-image: url('@/assets/assets-login-bg-black.png');
}
}
.login-wrapper {
height: 100vh;
display: flex;
flex-direction: column;
background-size: cover;
background-position: 100%;
position: relative;
}
.login-container {
position: absolute;
top: 22%;
left: 5%;
min-height: 500px;
line-height: 22px;
}
.title-container {
.title {
font-size: 36px;
line-height: 44px;
color: var(--td-text-color-primary);
margin-top: 4px;
&.margin-no {
margin-top: 0;
}
}
.sub-title {
margin-top: 16px;
.tip {
display: inline-block;
margin-right: 8px;
font-size: 14px;
&:first-child {
color: var(--td-text-color-secondary);
}
&:last-child {
color: var(--td-text-color-primary);
cursor: pointer;
}
}
}
}
.item-container {
width: 400px;
margin-top: 48px;
&.login-qrcode {
.tip-container {
width: 192px;
margin-bottom: 16px;
font-size: 14px;
display: flex;
justify-content: space-between;
.tip {
color: var(--td-text-color-primary);
}
.refresh {
display: flex;
align-items: center;
color: var(--td-brand-color);
.t-icon {
font-size: 14px;
}
&:hover {
cursor: pointer;
}
}
}
.bottom-container {
margin-top: 32px;
}
}
&.login-phone {
.bottom-container {
margin-top: 66px;
}
}
.check-container {
display: flex;
align-items: center;
&.remember-pwd {
margin-bottom: 16px;
justify-content: space-between;
}
.t-checkbox__label {
color: var(--td-text-color-secondary);
}
span {
color: var(--td-brand-color);
&:hover {
cursor: pointer;
}
}
}
.verification-code {
display: flex;
align-items: center;
.t-form__controls {
width: 100%;
button {
flex-shrink: 0;
width: 102px;
height: 40px;
margin-left: 11px;
}
}
}
.btn-container {
margin-top: 48px;
}
}
.switch-container {
margin-top: 24px;
.tip {
font-size: 14px;
color: var(--td-brand-color);
cursor: pointer;
display: inline-flex;
align-items: center;
margin-right: 14px;
&:last-child {
&::after {
display: none;
}
}
&::after {
content: '';
display: block;
width: 1px;
height: 12px;
background: var(--td-gray-color-3);
margin-left: 14px;
}
}
}
.check-container {
font-size: 14px;
color: var(--td-text-color-secondary);
.tip {
float: right;
font-size: 14px;
color: var(--td-brand-color);
}
}
.copyright {
font-size: 14px;
position: absolute;
left: 5%;
bottom: var(--td-comp-size-xxxl);
color: var(--td-text-color-secondary);
}
@media screen and (max-height: 700px) {
.copyright {
display: none;
}
}

View File

@ -0,0 +1,160 @@
<template>
<div class="list-card list-common-table" v-if="id!=0">
<t-card :bordered="false">
<t-steps :defaultCurrent="current" :style="{ width: '1200px',margin:'20px auto' }">
<t-step-item :title="info.association_name"></t-step-item>
<t-step-item title="会员信息填写"></t-step-item>
<t-step-item title="公司信息填写"></t-step-item>
</t-steps>
<div style="margin-top: 40px">
<template v-if="current==0">
<t-card :title="info.association_name+'入会须知'" :bordered="true" hover-shadow :style="{ width: '1200px',margin:'0 auto' }">
<div v-html="info.ruhuixizhu"
style="height: 500px; overflow-y: auto;white-space: break-spaces;line-height: 25px;"></div>
<div style="text-align: center;padding: 20px">
<t-checkbox v-model="ok">同意安装协议</t-checkbox>
</div>
<div style="text-align: center">
<t-button @click="okIns">下一步</t-button>
</div>
</t-card>
</template>
<template v-if="current==1">
<t-card title="安装环境监测" :bordered="true" hover-shadow :style="{ width: '1200px',margin:'0 auto' }">
<div style="text-align: center;padding: 20px">
<t-table
row-key="index"
:data="receiveCode"
:columns="columns"
>
<template #value="{ row }">
<t-tag style="cursor: pointer" v-if="row.check==0" theme="danger">{{ row.value }}</t-tag>
<t-tag style="cursor: pointer" v-if="row.check==1" theme="success">{{ row.value }}</t-tag>
</template>
</t-table>
</div>
<div style="text-align: center">
<t-space :style="{ width: '300px',margin:'0 auto',textAlign:'center' }">
<t-button @click="getreceiveCode">重新检测</t-button>
<t-button @click="okreceiveCode">下一步</t-button>
</t-space>
</div>
</t-card>
</template>
<!-- <template v-if="current==2">-->
<!-- <t-card title="数据库信息" :bordered="true" hover-shadow :style="{ width: '900px',margin:'0 auto' }">-->
<!-- <t-form-->
<!-- :data="formData"-->
<!-- :rules="rules"-->
<!-- @submit="onSubmit"-->
<!-- :label-width="150"-->
<!-- >-->
<!-- <t-space direction="vertical" :size="40">-->
<!-- <t-form-item label="数据库地址" name="database_ip">-->
<!-- <t-input :style="{ width: '480px' }" v-model="formData.database_ip"-->
<!-- placeholder="请填写数据库地址"></t-input>-->
<!-- </t-form-item>-->
<!-- <t-form-item label="数据库端口" name="database_no">-->
<!-- <t-input :style="{ width: '480px' }" v-model="formData.database_no"-->
<!-- placeholder="请填写数据库端口"></t-input>-->
<!-- </t-form-item>-->
<!-- <t-form-item label="数据库名称" name="database_name">-->
<!-- <t-input :style="{ width: '480px' }" v-model="formData.database_name"-->
<!-- placeholder="请填写数据库名称"></t-input>-->
<!-- </t-form-item>-->
<!-- <t-form-item label="数据库用户名" name="database_user_name">-->
<!-- <t-input :style="{ width: '480px' }" v-model="formData.database_user_name"-->
<!-- placeholder="请填写数据库用户名"></t-input>-->
<!-- </t-form-item>-->
<!-- <t-form-item label="数据库密码" name="database_pwd">-->
<!-- <t-input :style="{ width: '480px' }" v-model="formData.database_pwd"-->
<!-- placeholder="请填写数据库密码"></t-input>-->
<!-- </t-form-item>-->
<!-- <div style="font-weight: 600;font-size: 16px">创始人信息</div>-->
<!-- <t-form-item label="管理员账号" name="admin_user_name">-->
<!-- <t-input :style="{ width: '480px' }" v-model="formData.admin_user_name"-->
<!-- placeholder="请填写管理员账号"></t-input>-->
<!-- </t-form-item>-->
<!-- <t-form-item label="管理员密码" name="admin_user_pwd">-->
<!-- <t-input :style="{ width: '480px' }" v-model="formData.admin_user_pwd"-->
<!-- placeholder="请填写管理员密码"></t-input>-->
<!-- </t-form-item>-->
<!-- <t-form-item label="重复密码" name="admin_user_pwd2">-->
<!-- <t-input :style="{ width: '480px' }" v-model="formData.admin_user_pwd2"-->
<!-- placeholder="请重复填写密码"></t-input>-->
<!-- </t-form-item>-->
<!-- <t-form-item label="二级密码" help="升级框架、卸载应用的时候需要验证,请妥善保管" name="admin_two_pwd">-->
<!-- <t-input :style="{ width: '480px' }" v-model="formData.admin_two_pwd"-->
<!-- placeholder="请填写二级密码"></t-input>-->
<!-- </t-form-item>-->
<!-- <t-form-item>-->
<!-- <t-space :style="{ width: '300px',margin:'0 auto',textAlign:'center' }">-->
<!-- <t-button theme="primary" type="submit">创建数据</t-button>-->
<!-- </t-space>-->
<!-- </t-form-item>-->
<!-- </t-space>-->
<!-- </t-form>-->
<!-- </t-card>-->
<!-- </template>-->
</div>
</t-card>
</div>
</template>
<script>
import {UserIcon, LockOnIcon} from 'tdesign-icons-vue';
export default {
components: {
UserIcon,
LockOnIcon,
},
data() {
return {
formData: {
zhanghu: '',
password: '',
},
ok: false,
current:0,
id:0,
info:''
};
},
mounted() {
console.log(this.$route.query.id);
if(typeof (this.$route.query.id)!='undefined'){
this.id=this.$route.query.id;
this.getInfo();
}else{
this.$message.error('入会链接有误!',0);
}
},
methods: {
getInfo(){
this.$request
.post("/association/find",{association_id:this.id})
.then( (res) => {
console.log(res);
if(res.code==1){
this.info=res.data;
}
})
.catch((e) => {
console.log(e);
});
},
okIns() {
if (!this.ok) {
this.$message.error('请认真阅读且同意入会协议!');
return;
}
this.current = 1;
//this.getreceiveCode();
},
},
};
</script>
<style lang="less">
</style>

View File

@ -0,0 +1,139 @@
<template>
<t-card :bordered="false">
<div class="form-step-container">
<t-form
ref="form"
:data="formData"
:label-width="80"
@submit="onSubmit"
:style="{ marginBottom: '30px' }"
>
<t-row>
<t-col :span="5">
<t-row :gutter="[16, 24]">
<t-col :flex="2">
<t-form-item label="姓名" name="nikename">
<t-input
v-model="formData.nikename"
class="form-item-content"
type="search"
placeholder="请输入姓名"
:style="{ minWidth: '134px' }"
/>
</t-form-item>
</t-col>
<t-col :flex="2">
<t-form-item label="手机号" name="phone">
<t-input
v-model="formData.phone"
class="form-item-content"
placeholder="请输入手机号"
:style="{ minWidth: '134px' }"
/>
</t-form-item>
</t-col>
</t-row>
</t-col>
<t-col :span="2" class="operation-container">
<t-button theme="primary" type="submit" :style="{ marginLeft: '8px' }"> 查询</t-button>
</t-col>
</t-row>
</t-form>
<t-table
rowKey="index"
:data="list"
:columns="columns"
:stripe="true"
:bordered="false"
:hover="true"
size="large"
table-layout="auto"
cellEmptyContent="-"
>
<template #photo_image="{ row }">
<img :src="'http://192.168.3.130/'+row.photo_image" style="width: 50px;height: 50px">
</template>
<template #gender="{ row }">
{{row.gender==1?"男":"女"}}
</template>
<template #select="{ row }">
<t-space size="24px">
<t-button theme="default" @click="openUrl(row.member_id)">详细信息</t-button>
<!-- <t-button>编辑</t-button>-->
<t-button theme="warning">删除</t-button>
</t-space>
</template>
</t-table>
<div style="margin-top: 30px">
<t-pagination
:total="total"
:page-size="size"
@current-change="onCurrentChange"
:showPageSize="false"
></t-pagination>
</div>
</div>
</t-card>
</template>
<script lang="ts">
export default {
data() {
return {
association:{},
list:[],
columns:[
{ colKey: 'member_id', title: '会员ID'},
{ colKey: 'photo_image', title: '证件照'},
{ colKey: 'nikename', title: '会员姓名'},
{ colKey: 'position_name', title: '头衔'},
{ colKey: 'gender', title: '性别'},
{ colKey: 'phone', title: '手机号'},
{ colKey: 'nation', title: '民族'},
{ colKey: 'select', title: '操作',width: 200},
],
total:0,
page:1,
size:10,
formData:{
nikename: '',
phone: ''
},
}
},
mounted() {
this.association=JSON.parse(this.$store.state.user.association);
this.getList();
},
methods: {
onCurrentChange(d){
this.page=d;
this.getList();
},
openUrl(member_id){
this.$router.push('/user/user_info?id='+member_id);
},
getList() {
this.formData.page=this.page;
this.formData.size=this.size;
this.$request
.post("/member",this.formData)
.then( (res) => {
console.log(res);
this.list=res.data.ret;
this.total=res.data.count;
})
.catch((e) => {
console.log(e);
});
},
onSubmit(){
this.getList();
},
}
}
</script>
<style scoped lang="less">
</style>

View File

@ -0,0 +1,144 @@
<template>
<div class="detail-base">
<t-card title="基本信息" :bordered="false" class="info-block">
<t-descriptions :column="4">
<t-descriptions-item label="证件照">
<div slot="content">
<img :src="$store.state.user.apiUrl+info.photo_image" style="width: 100px;height: 100px">
</div>
</t-descriptions-item>
<t-descriptions-item label="会员姓名" :content="info.nikename"></t-descriptions-item>
<t-descriptions-item label="头衔" :content="info.position_name"></t-descriptions-item>
<t-descriptions-item label="手机号" :content="info.phone"></t-descriptions-item>
<t-descriptions-item label="性别">
<div slot="content">
{{info.gender==1?"男":"女"}}
</div>
</t-descriptions-item>
<t-descriptions-item label="出生日期" :content="info.birth_time"></t-descriptions-item>
<t-descriptions-item label="籍贯" :content="info.jiguan"></t-descriptions-item>
<t-descriptions-item label="民族" :content="info.nation"></t-descriptions-item>
<t-descriptions-item label="政治面貌" :content="info.political"></t-descriptions-item>
<t-descriptions-item label="毕业院校" :content="info.institution"></t-descriptions-item>
<t-descriptions-item label="学历" :content="info.education"></t-descriptions-item>
<t-descriptions-item label="学位" :content="info.academic_degree"></t-descriptions-item>
</t-descriptions>
</t-card>
<t-card title="个人信息" :bordered="false" class="info-block">
<t-descriptions :column="2">
<t-descriptions-item label="身份证正面照">
<div slot="content">
<t-image-viewer v-model="visible" :draggable="true" mode="modeless" :images="[$store.state.user.apiUrl+info.cardz_image]">
<template #trigger="{ open }">
<div class="tdesign-demo-image-viewer__ui-image" @click="open">
<img alt="test" :src="$store.state.user.apiUrl+info.cardz_image" class="tdesign-demo-image-viewer__ui-image--img" />
</div>
</template>
</t-image-viewer>
</div>
</t-descriptions-item>
<t-descriptions-item label="身份证反面照">
<div slot="content">
<img :src="'http://192.168.3.130/'+info.cardf_image">
</div>
</t-descriptions-item>
<t-descriptions-item label="身份证号码" :content="info.card_number"></t-descriptions-item>
<t-descriptions-item label="工作单位" :content="info.work_unit"></t-descriptions-item>
<t-descriptions-item label="单位职务" :content="info.unit_position"></t-descriptions-item>
<t-descriptions-item label="微信号" :content="info.wx_number"></t-descriptions-item>
<t-descriptions-item label="邮箱地址" :content="info.mailbox"></t-descriptions-item>
<t-descriptions-item label="固定电话" :content="info.fixed_telephone"></t-descriptions-item>
<t-descriptions-item label="其他社会职务" :content="info.other_social_positions"></t-descriptions-item>
<t-descriptions-item label="其他联系人" :content="info.other_contacts"></t-descriptions-item>
<t-descriptions-item label="主要成就以及获奖情况" :content="info.achievement_award"></t-descriptions-item>
</t-descriptions>
</t-card>
<t-card title="企业信息" :bordered="false" class="info-block">
<t-descriptions :column="2">
<t-descriptions-item label="营业执照">
<div slot="content">
<t-image-viewer v-model="visible" :draggable="true" mode="modeless" :images="[$store.state.user.apiUrl+info.business_license_image]">
<template #trigger="{ open }">
<div class="tdesign-demo-image-viewer__ui-image" @click="open">
<img alt="test" :src="$store.state.user.apiUrl+info.business_license_image" class="tdesign-demo-image-viewer__ui-image--img" />
</div>
</template>
</t-image-viewer>
</div>
</t-descriptions-item>
<t-descriptions-item label="统一社会信用代码" :content="info.unified_code">
</t-descriptions-item>
<t-descriptions-item label="企业性质" :content="info.enterprise_nature"></t-descriptions-item>
<t-descriptions-item label="企业网址" :content="info.enterprise_website "></t-descriptions-item>
<t-descriptions-item label="是否上市">
<div slot="content">
{{info.if_list==1?"否":"是"}}
</div>
</t-descriptions-item>
<t-descriptions-item label="企业注册地" :content="info.enterprise_location"></t-descriptions-item>
<t-descriptions-item label="上年度营业额" :content="info.previous_revenue+'万'"></t-descriptions-item>
<t-descriptions-item label="上年度纳税额" :content="info.previous_tax+'万'"></t-descriptions-item>
<t-descriptions-item label="上年度净利润" :content="info.previous_profit+'万'"></t-descriptions-item>
<t-descriptions-item label="上年度公益性捐赠支出" :content="info.previous_donation+'万'"></t-descriptions-item>
<t-descriptions-item label="企业介绍" :content="info.enterprise_Introduction"></t-descriptions-item>
<t-descriptions-item label="业务介绍" :content="info.introdiction"></t-descriptions-item>
<t-descriptions-item label="企业所获荣誉以及专利" :content="info.enterorise_honor"></t-descriptions-item>
<t-descriptions-item label="其他企业任职传况" :content="info.qitaqiyerenzhiqingkuang"></t-descriptions-item>
<t-descriptions-item label="是否建立团组织">
<div slot="content">
{{info.if_organization==1?"否":"是"}}
</div>
</t-descriptions-item>
<template v-if="info.if_organization==0">
<t-descriptions-item label="团组织性质" :content="info.nature"></t-descriptions-item>
<t-descriptions-item label="建团时间" :content="info.jiantuan_time"></t-descriptions-item>
<t-descriptions-item label="建团人数" :content="info.jiantuan_number"></t-descriptions-item>
<t-descriptions-item label="青年人数" :content="info.youth_number"></t-descriptions-item>
<t-descriptions-item label="上级团组织" :content="info.superior_nature"></t-descriptions-item>
<t-descriptions-item label="批复文件" :content="info.documents_file"></t-descriptions-item>
<t-descriptions-item label="团委负责人信息" :content="info.tuanweifuzerenxinxi"></t-descriptions-item>
<t-descriptions-item label="建团时间" :content="info.jiantuan_time"></t-descriptions-item>
</template>
</t-descriptions>
</t-card>
</div>
</template>
<script lang="ts">
export default {
data() {
return {
visible:false,
info:{},
member_id:0,
}
},
mounted() {
this.member_id=this.$route.query.id;
this.getInfo();
},
methods: {
getInfo() {
this.$request
.post("/member/find",{member_id:this.member_id})
.then( (res) => {
console.log(res);
this.info=res.data;
})
.catch((e) => {
console.log(e);
});
},
onSubmit(){
this.getList();
},
}
}
</script>
<style scoped lang="less">
.info-block{
margin-bottom: 20px;
}
</style>

View File

@ -0,0 +1,136 @@
<template>
<t-card :bordered="false">
<div class="form-step-container">
<t-button @click="add">新增</t-button>
<t-table
rowKey="index"
:data="list"
:columns="columns"
:stripe="true"
:bordered="false"
:hover="true"
size="large"
table-layout="auto"
cellEmptyContent="-"
>
<template #select="{ row }">
<t-space size="24px">
<t-button theme="warning" @click="edit(row)">编辑</t-button>
<t-popconfirm content="确认删除吗" @confirm="del(row)">
<t-button theme="danger" >删除</t-button>
</t-popconfirm>
</t-space>
</template>
</t-table>
</div>
<t-dialog header="新增行业" :visible="addMode" :onClose="onCloseMy" @confirm="onSubmit">
<t-form>
<t-form-item label="行业名称" name="name">
<t-input placeholder="请输入行业名称" v-model="industry_name"/>
</t-form-item>
<t-form-item label="行业简介" name="name">
<t-input placeholder="请输入行业简介" v-model="industry_description"/>
</t-form-item>
</t-form>
</t-dialog>
</t-card>
</template>
<script lang="ts">
export default {
data() {
return {
industry_description:'',
industry_name: '',
addMode: false,
isEdit: false,
editID:0,
list: [],
columns: [
{colKey: 'industry_name', title: '行业名称'},
{colKey: 'industry_description', title: '行业简介'},
{colKey: 'select', title: '操作', width: 200},
],
}
},
mounted() {
//this.member_id=this.$route.query.id;
this.getList();
},
methods: {
del(d){
console.log(d);
this.$request
.post('/industry/del',{id:d.id})
.then((res) => {
if(res.code==1){
this.$message.success(res.msg);
this.getList();
}else {
this.$message.error(res.msg);
}
console.log(res);
})
.catch((e) => {
console.log(e);
});
},
add(){
this.industry_description='';
this.industry_name= '';
this.addMode=true;
this.isEdit=false;
},
edit(d){
this.editID=d.id;
this.industry_name=d.industry_name;
this.industry_description=d.industry_description;
this.addMode=true;
this.isEdit=true;
},
getList() {
this.$request
.post("/industry")
.then((res) => {
console.log(res);
this.list = res.data;
})
.catch((e) => {
console.log(e);
});
},
onSubmit() {
if (this.industry_name == '') {
this.$message.error('行业名称不能为空');
return;
}
var url='/industry/add';
if(this.isEdit){
url='/industry/update';
}
this.$request
.post(url,{industry_name:this.industry_name,industry_description:this.industry_description,id:this.editID})
.then((res) => {
if(res.code==1){
this.$message.success(res.msg);
this.addMode=false;
this.getList();
}else {
this.$message.error(res.msg);
}
console.log(res);
})
.catch((e) => {
console.log(e);
});
},
onCloseMy() {
this.addMode = false;
}
}
}
</script>
<style scoped lang="less">
</style>

Some files were not shown because too many files have changed in this diff Show More