Merge branch 'develop' into fix/base-url

This commit is contained in:
yuyang 2022-03-21 14:12:12 +08:00 committed by GitHub
commit f05f54a8af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
76 changed files with 2304 additions and 2670 deletions

View File

@ -11,6 +11,10 @@
"jest": true, "jest": true,
"es6": true "es6": true
}, },
"globals": {
"defineProps": "readonly",
"defineEmits": "readonly"
},
"plugins": [ "plugins": [
"vue", "vue",
"@typescript-eslint" "@typescript-eslint"

View File

@ -0,0 +1,79 @@
name: 反馈 Bug
description: 通过 github 模板进行 Bug 反馈。
title: "[组件名称] 描述问题的标题"
body:
- type: markdown
attributes:
value: |
# 欢迎你的参与
tdesign-vue-next-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://github.com/Tencent/tdesign-vue-next-starter/blob/main/CHANGELOG.md) 和 [旧Issue列表](https://github.com/Tencent/tdesign-vue-next-starter/issues?q=is%3Aissue) 中搜索过你的问题。(你的问题可能已有人提出,也可能已在最新版本中被修正)
- 如果你发现一个已经关闭的旧 Issue 在最新版本中仍然存在,不要在旧 Issue 下面留言,请建一个新的 issue。
- type: input
id: version
attributes:
label: tdesign-vue-next-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: 请填写

View File

@ -1,18 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe Environment**
Add browser version here
**Describe the bug**
A clear and concise description of what the bug is.
**Additional context**
Add any other context about the problem here.

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-next-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-next-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://github.com/Tencent/tdesign-vue-next-starter/blob/main/CHANGELOG.md) 和 [旧Issue列表](https://github.com/Tencent/tdesign-vue-next-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

View File

@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

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 已提供或无须提供

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,27 @@
# force copy from tencent/tdesign
# 国际标准时间+8
name: Close stale issues and PRs
on:
schedule:
- cron: "50 5 * * *"
jobs:
close-issues:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v4
with:
stale-issue-message: "这个 Issue 被标记为了过时 stale ,因为它已经持续 30 天没有任何活动了。删除 stale 标签或评论,否则将在 7 天内关闭。"
stale-pr-message: '这个 PR 已经过时了,因为它已经持续 45 天没有任何活动了。 删除 stale 的标签或评论,否则将在 10 天内关闭。'
close-issue-message: "此 Issue 被自动关闭,因为它自被标记为过时 stale 以来已闲置 7 天。"
close-pr-message: "此 PR 被自动关闭,因为它已经 stable 停滞了 10 天,没有任何活动。"
days-before-stale: 30
days-before-close: 7
days-before-pr-stale: 45
days-before-pr-close: 10
repo-token: ${{ secrets.GITHUB_TOKEN }}
exempt-issue-labels: 'WIP'
exempt-pr-labels: 'WIP'

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'

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 }}

View File

@ -1,28 +1,15 @@
# 文件名建议统一为 pull-request.yml
# 应用 test-build.yml 的 demo
name: MAIN_PULL_REQUEST name: MAIN_PULL_REQUEST
on: on:
pull_request: pull_request:
branches: [develop, main] branches: [develop, main]
types: [opened, synchronize, reopened]
jobs: jobs:
MAIN_PULL_REQUEST: call-test-build:
runs-on: ubuntu-latest uses: Tencent/tdesign/.github/workflows/test-build.yml@main
steps:
- uses: actions/checkout@v2 # install lint
- name: Cache nodemodules
uses: actions/cache@v2
env:
cache-name: cache-nodemodules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- uses: actions/setup-node@v2
with:
node-version: '16'
- run: npm install
shell: bash
- run: npm run lint

View File

@ -1,24 +0,0 @@
name: All_ON_PUSH
on: push
jobs:
All_ON_PUSH:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm install
shell: bash
- run: echo '${{ github.ref }} ... ${{ github.sha }}'
- name: build-site
run: npm run build
- name: upload surge service and generate preview URL
id: deploy
run: |
export GITHUB_SHA=${{ github.sha }}
export GITHUB_SHA_SUB=${GITHUB_SHA: 0: 7 }
export DEPLOY_DOMAIN=https://preview-$GITHUB_SHA_SUB-tdesign-vue-next-starter.surge.sh
npx surge --project ./dist --domain $DEPLOY_DOMAIN --token ${{ secrets.TDESIGN_SURGE_TOKEN }}
echo the preview URL is $DEPLOY_DOMAIN
if: ${{ success() }}
- run: echo "🚀 This job's status is ${{ job.status }}."

1
.gitignore vendored
View File

@ -20,3 +20,4 @@ yarn-error.log
.stylelintcache .stylelintcache
yarn.lock yarn.lock
package-lock.json package-lock.json
pnpm-lock.yaml

View File

@ -17,7 +17,7 @@
### 项目简介 ### 项目简介
TDesign Vue Next Starter 是一个基于 TDesign。使用 `Vue3`、`Vite2`、`TypeScript` 开发,可进行个性化主题配置,旨在提供项目开箱即用的、配置式的中后台项目。 TDesign Vue Next Starter 是一个基于 TDesign。使用 `Vue3`、`Vite2`、`Pinia`、`TypeScript` 开发,可进行个性化主题配置,旨在提供项目开箱即用的、配置式的中后台项目。
<p> <p>
<a href="http://tdesign.tencent.com/starter/vue-next/">在线预览</a> <a href="http://tdesign.tencent.com/starter/vue-next/">在线预览</a>
@ -108,9 +108,9 @@ npm run stylelint:fix
### 兼容性 ### 兼容性
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](https://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" />](https://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" />](https://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" />](https://godban.github.io/browsers-support-badges/)</br>Safari | | [<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 |
| --------- | --------- | --------- | --------- | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| IE10, IE11, Edge | last 2 versions | last 2 versions | last 2 versions | | Edge >=84 | Firefox >=83 | Chrome >=84 | Safari >=14.1 |
### License ### License

View File

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

View File

@ -1,20 +0,0 @@
server {
if ($request_method = HEAD) {
return 200;
}
location / {
alias /usr/share/nginx/html/;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log error;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

View File

@ -1,9 +1,10 @@
{ {
"name": "tdesign-vue-next-starter", "name": "tdesign-vue-next-starter",
"version": "0.0.1", "version": "0.1.0",
"scripts": { "scripts": {
"dev:mock": "vite --open --mode mock", "dev:mock": "vite --open --mode mock",
"dev": "vite --open --mode development", "dev": "vite --open --mode development",
"dev:linux": "vite --mode development",
"build:test": "vite build --mode test", "build:test": "vite build --mode test",
"build": "vue-tsc --noEmit && vite build --mode release", "build": "vue-tsc --noEmit && vite build --mode release",
"preview": "vite preview", "preview": "vite preview",
@ -11,21 +12,25 @@
"lint:fix": "eslint --ext .vue,.js,jsx,.ts,.tsx ./ --max-warnings 0 --fix", "lint:fix": "eslint --ext .vue,.js,jsx,.ts,.tsx ./ --max-warnings 0 --fix",
"stylelint": "stylelint src/**/*.{html,vue,sass,less}", "stylelint": "stylelint src/**/*.{html,vue,sass,less}",
"stylelint:fix": "stylelint --fix src/**/*.{html,vue,vss,sass,less}", "stylelint:fix": "stylelint --fix src/**/*.{html,vue,vss,sass,less}",
"prepare": "husky install" "prepare": "husky install",
"site:preview": "npm run build && cp -r dist _site",
"test": "echo \"no test specified,work in process\""
}, },
"dependencies": { "dependencies": {
"cz-conventional-changelog": "^3.3.0",
"dayjs": "^1.10.6", "dayjs": "^1.10.6",
"echarts": "~5.1.2", "echarts": "~5.1.2",
"hex-to-hsl": "^1.0.2", "hex-to-hsl": "^1.0.2",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"pinia": "^2.0.11",
"qrcode.vue": "^3.2.2", "qrcode.vue": "^3.2.2",
"tdesign-vue-next": "^0.5.0", "tdesign-icons-vue-next": "^0.0.6",
"tdesign-vue-next": "0.10.1",
"tvision-color": "^1.3.1", "tvision-color": "^1.3.1",
"vue": "^3.1.5", "vue": "^3.2.31",
"vue-color-kit": "^1.0.5", "vue-color-kit": "^1.0.5",
"vue-router": "^4.0.11", "vue-router": "^4.0.11",
"vue3-clipboard": "^1.0.0", "vue3-clipboard": "^1.0.0"
"vuex": "^4.0.2"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^15.0.0", "@commitlint/cli": "^15.0.0",
@ -37,7 +42,7 @@
"@vitejs/plugin-vue": "^1.3.0", "@vitejs/plugin-vue": "^1.3.0",
"@vitejs/plugin-vue-jsx": "^1.1.7", "@vitejs/plugin-vue-jsx": "^1.1.7",
"@vue/compiler-sfc": "^3.0.5", "@vue/compiler-sfc": "^3.0.5",
"axios": "^0.24.0", "axios": "^0.26.0",
"commitizen": "^4.2.4", "commitizen": "^4.2.4",
"compressorjs": "^1.0.7", "compressorjs": "^1.0.7",
"eslint": "^7.32.0", "eslint": "^7.32.0",

View File

@ -1,22 +1,19 @@
<template> <template>
<router-view :class="[mode]" /> <router-view :class="[mode]" />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, computed } from 'vue'; import { computed, onMounted } from 'vue';
import { useStore } from 'vuex'; import config from '@/config/style';
import { useSettingStore } from '@/store';
export default defineComponent({ const store = useSettingStore();
setup() {
const store = useStore();
const mode = computed(() => { const mode = computed(() => {
return store.getters['setting/mode']; return store.displayMode;
}); });
return { onMounted(() => {
mode, store.updateConfig({ ...config });
};
},
}); });
</script> </script>
<style lang="less"> <style lang="less">

View File

@ -9,21 +9,15 @@
<user-avatar-icon v-if="product.type === 4" /> <user-avatar-icon v-if="product.type === 4" />
<laptop-icon v-if="product.type === 5" /> <laptop-icon v-if="product.type === 5" />
</div> </div>
<t-tag :theme="product.isSetup ? 'success' : 'default'" :disabled="!product.isSetup">{{ <t-tag :theme="product.isSetup ? 'success' : 'default'" :disabled="!product.isSetup">
product.isSetup ? '已启用' : '已停用' {{ product.isSetup ? '已启用' : '已停用' }}
}}</t-tag> </t-tag>
</t-row> </t-row>
<p class="list-card-item_detail--name"> <p class="list-card-item_detail--name">{{ product.name }}</p>
{{ product.name }} <p class="list-card-item_detail--desc">{{ product.description }}</p>
</p>
<p class="list-card-item_detail--desc">
{{ product.description }}
</p>
<t-row justify="space-between" align="middle" :class="cardControlClass"> <t-row justify="space-between" align="middle" :class="cardControlClass">
<div> <div>
<t-button shape="circle" :disabled="!product.isSetup"> <t-button shape="circle" :disabled="!product.isSetup">{{ typeMap[product.type - 1] }}</t-button>
{{ typeMap[product.type - 1] }}
</t-button>
<t-button shape="circle" :disabled="!product.isSetup"> <t-button shape="circle" :disabled="!product.isSetup">
<add-icon /> <add-icon />
</t-button> </t-button>
@ -52,8 +46,8 @@
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { computed, defineComponent, PropType } from 'vue'; import { computed, PropType } from 'vue';
import { import {
ShopIcon, ShopIcon,
CalendarIcon, CalendarIcon,
@ -71,63 +65,35 @@ export interface CardProductType {
name: string; name: string;
} }
export default defineComponent({ const props = defineProps({
name: 'ListCardComponent', product: {
components: { type: Object as PropType<CardProductType>,
ShopIcon,
CalendarIcon,
ServiceIcon,
UserAvatarIcon,
LaptopIcon,
MoreIcon,
AddIcon,
},
props: {
product: {
type: Object as PropType<CardProductType>,
default: () => {
return {};
},
},
},
emits: ['manage-product', 'delete-item'],
setup(props, ctx) {
const { emit } = ctx;
const cardClass = computed(() => [
'list-card-item',
{
'list-card-item__disabled': !props.product.isSetup,
},
]);
const cardLogoClass = computed(() => [
'list-card-item_detail--logo',
{
'list-card-item_detail--logo__disabled': !props.product.isSetup,
},
]);
const cardControlClass = computed(() => [
'list-card-item_detail--control',
{
'list-card-item_detail--control__disabled': !props.product.isSetup,
},
]);
return {
cardClass,
cardLogoClass,
cardControlClass,
typeMap: ['A', 'B', 'C', 'D', 'E'],
handleClickManage(product) {
emit('manage-product', product);
},
handleClickDelete(product) {
emit('delete-item', product);
},
};
}, },
}); });
const emit = defineEmits(['manage-product', 'delete-item']);
const cardClass = computed(() => ['list-card-item', { 'list-card-item__disabled': !props.product.isSetup }]);
const cardLogoClass = computed(() => [
'list-card-item_detail--logo',
{ 'list-card-item_detail--logo__disabled': !props.product.isSetup },
]);
const cardControlClass = computed(() => [
'list-card-item_detail--control',
{ 'list-card-item_detail--control__disabled': !props.product.isSetup },
]);
const typeMap = ['A', 'B', 'C', 'D', 'E'];
const handleClickManage = (product: CardProductType) => {
emit('manage-product', product);
};
const handleClickDelete = (product: CardProductType) => {
emit('delete-item', product);
};
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -16,70 +16,51 @@
<div v-if="size !== 'small'" class="card-spacer-bottom" /> <div v-if="size !== 'small'" class="card-spacer-bottom" />
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, PropType, computed } from 'vue'; import { computed } from 'vue';
export default defineComponent({ const props = defineProps({
name: 'Card', title: String,
props: { subtitle: String,
title: { describe: String,
type: String as PropType<string>, compact: {
default: '', type: Boolean,
}, default: false,
subtitle: {
type: String as PropType<string>,
default: '',
},
compact: {
type: Boolean as PropType<boolean>,
default: false,
},
describe: {
type: String as PropType<string>,
default: '',
},
size: {
type: String as PropType<string>,
default: 'default',
},
border: {
type: Boolean,
default: false,
},
}, },
setup(props) { size: {
const containerCls = computed(() => { type: String,
const { compact, border } = props; default: 'default',
return ['card-container', { 'card-container-compact': compact, 'card-container--border': border }];
});
const titleCls = computed(() => {
const { size } = props;
return [
'card-title',
{
'card-title--small': size === 'small',
'card-title--default': size !== 'small',
},
];
});
const titleTextCls = computed(() => {
const { size } = props;
return [
{
'card-title__text--small': size === 'small',
'card-title__text--default': size !== 'small',
},
];
});
return {
containerCls,
titleCls,
titleTextCls,
};
}, },
border: {
type: Boolean,
default: false,
},
});
const containerCls = computed(() => {
const { compact, border } = props;
return ['card-container', { 'card-container-compact': compact, 'card-container--border': border }];
});
const titleCls = computed(() => {
const { size } = props;
return [
'card-title',
{
'card-title--small': size === 'small',
'card-title--default': size !== 'small',
},
];
});
const titleTextCls = computed(() => {
const { size } = props;
return [
{
'card-title__text--small': size === 'small',
'card-title__text--default': size !== 'small',
},
];
}); });
</script> </script>
<style lang="less"> <style lang="less">

View File

@ -1,37 +1,28 @@
<template> <template>
<div :style="style" class="color-container" /> <div :style="style" class="color-container" />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { computed, defineComponent, PropType } from 'vue'; import { computed } from 'vue';
import { useStore } from 'vuex';
import { getBrandColor } from '@/config/color'; import { getBrandColor } from '@/config/color';
import { useSettingStore } from '@/store';
const store = useSettingStore();
const panelColor = 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)'; '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 defineComponent({ const props = defineProps({
name: 'Color', value: {
props: { type: String,
value: {
type: String as PropType<string>,
default: 'default',
},
}, },
setup(props) { });
const store = useStore();
const style = computed(() => { const style = computed(() => {
const { value } = props; const { value } = props;
const { colorList } = store.state.setting; const { colorList } = store;
return { return {
background: value !== 'dynamic' ? getBrandColor(value, colorList)['@brand-color'] : panelColor, background: value !== 'dynamic' ? getBrandColor(value, colorList)['@brand-color'] : panelColor,
}; };
});
return {
style,
};
},
}); });
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -8,55 +8,36 @@
<slot /> <slot />
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, PropType, computed } from 'vue'; import { computed } from 'vue';
import Result403Icon from '@/assets/assets-result-403.svg?component'; import Result403Icon from '@/assets/assets-result-403.svg?component';
import Result404Icon from '@/assets/assets-result-404.svg?component'; import Result404Icon from '@/assets/assets-result-404.svg?component';
import Result500Icon from '@/assets/assets-result-500.svg?component'; import Result500Icon from '@/assets/assets-result-500.svg?component';
import ResultIeIcon from '@/assets/assets-result-ie.svg?component'; import ResultIeIcon from '@/assets/assets-result-ie.svg?component';
import ResultWifiIcon from '@/assets/assets-result-wifi.svg?component'; import ResultWifiIcon from '@/assets/assets-result-wifi.svg?component';
export default defineComponent({ const props = defineProps({
name: 'Result', bgUrl: String,
props: { title: String,
bgUrl: { tip: String,
type: String as PropType<string>, type: String,
default: '', });
},
title: { const dynamicComponent = computed(() => {
type: String as PropType<string>, switch (props.type) {
default: '', case '403':
}, return Result403Icon;
tip: { case '404':
type: String as PropType<string>, return Result404Icon;
default: '', case '500':
}, return Result500Icon;
type: { case 'ie':
type: String as PropType<string>, return ResultIeIcon;
default: '', case 'wifi':
}, return ResultWifiIcon;
}, default:
setup(props) { return Result403Icon;
const dynamicComponent = computed(() => { }
switch (props.type) {
case '403':
return Result403Icon;
case '404':
return Result404Icon;
case '500':
return Result500Icon;
case 'ie':
return ResultIeIcon;
case 'wifi':
return ResultWifiIcon;
default:
return Result403Icon;
}
});
return {
dynamicComponent,
};
},
}); });
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -1,35 +1,27 @@
<template> <template>
<img :class="className" :src="url" /> <img :class="className" :src="url" />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, computed, PropType } from 'vue'; import { computed } from 'vue';
export default defineComponent({ const props = defineProps({
name: 'Thumbnail', url: String,
props: { type: {
url: { type: String,
type: String as PropType<string>, default: 'layout',
default: '',
},
type: {
type: String as PropType<string>,
default: 'layout',
},
},
setup(props) {
const className = computed(() => {
const { type } = props;
return [
'thumbnail-container',
{
'thumbnail-circle': type === 'circle',
'thumbnail-layout': type === 'layout',
},
];
});
return { className };
}, },
}); });
const className = computed(() => {
const { type } = props;
return [
'thumbnail-container',
{
'thumbnail-circle': type === 'circle',
'thumbnail-layout': type === 'layout',
},
];
});
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import url('@/style/index.less'); @import url('@/style/index.less');

View File

@ -20,46 +20,31 @@
<span>{{ describe }}</span> <span>{{ describe }}</span>
</span> </span>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, computed, PropType } from 'vue'; import { computed } from 'vue';
export default defineComponent({ const props = defineProps({
name: 'Trend', type: String,
props: { describe: [String, Number],
type: { isReverseColor: {
type: String as PropType<string>, type: Boolean,
default: '', default: false,
},
describe: {
type: [String, Number] as PropType<string | number>,
default: '',
},
isReverseColor: {
type: Boolean as PropType<boolean>,
default: false,
},
},
setup(props) {
const containerCls = computed(() => {
const { isReverseColor, type } = props;
return [
'trend-container',
{
'trend-container__reverse': isReverseColor,
'trend-container__up': !isReverseColor && type === 'up',
'trend-container__down': !isReverseColor && type === 'down',
},
];
});
const iconCls = computed(() => ['trend-icon-container']);
return {
containerCls,
iconCls,
};
}, },
}); });
const containerCls = computed(() => {
const { isReverseColor, type } = props;
return [
'trend-container',
{
'trend-container__reverse': isReverseColor,
'trend-container__up': !isReverseColor && type === 'up',
'trend-container__down': !isReverseColor && type === 'down',
},
];
});
const iconCls = computed(() => ['trend-icon-container']);
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -1,7 +1,7 @@
import hexToHsl from 'hex-to-hsl'; import hexToHsl from 'hex-to-hsl';
/* eslint-disable indent */ /* eslint-disable indent */
export type ColorToken = Record<string, string>; export type TColorToken = Record<string, string>;
export type ColorSeries = Record<string, ColorToken>; export type TColorSeries = Record<string, TColorToken>;
export const defaultLightColor = [ export const defaultLightColor = [
'#0052d9', '#0052d9',
@ -24,7 +24,7 @@ export const defaultDarkColor = [
'#ab87d5', '#ab87d5',
]; ];
export const BACKGROUND_TOKEN: ColorSeries = { export const BACKGROUND_TOKEN: TColorSeries = {
BLUE_GREY: { BLUE_GREY: {
'@gray-color-1': '#F1F2F5', '@gray-color-1': '#F1F2F5',
'@gray-color-2': '#EBEDF1', '@gray-color-2': '#EBEDF1',
@ -60,7 +60,7 @@ export const BACKGROUND_TOKEN: ColorSeries = {
}; };
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
export const NEUTRAL_GREY_TOKEN: ColorToken = { export const NEUTRAL_GREY_TOKEN: TColorToken = {
'@gray-color-1': '#F3F3F3', '@gray-color-1': '#F3F3F3',
'@gray-color-2': '#EEEEEE', '@gray-color-2': '#EEEEEE',
'@gray-color-3': '#E7E7E7', '@gray-color-3': '#E7E7E7',
@ -77,7 +77,7 @@ export const NEUTRAL_GREY_TOKEN: ColorToken = {
'@gray-color-14': '#181818', '@gray-color-14': '#181818',
}; };
export const COLOR_TOKEN: ColorSeries = { export const COLOR_TOKEN: TColorSeries = {
DEFAULT: { DEFAULT: {
'@brand-color': '#0052D9', '@brand-color': '#0052D9',
'@brand-color-1': '#e0ebff', '@brand-color-1': '#e0ebff',
@ -185,35 +185,37 @@ export const COLOR_TOKEN: ColorSeries = {
}, },
}; };
export const LIGHT_CHART_COLORS: ColorToken = { export const LIGHT_CHART_COLORS = {
textColor: 'rgba(0, 0, 0, 0.9)', textColor: 'rgba(0, 0, 0, 0.9)',
placeholderColor: 'rgba(0, 0, 0, 0.35)', placeholderColor: 'rgba(0, 0, 0, 0.35)',
borderColor: '#dcdcdc', borderColor: '#dcdcdc',
containerColor: '#fff', containerColor: '#fff',
}; };
export const DARK_CHART_COLORS: ColorToken = { export const DARK_CHART_COLORS = {
textColor: 'rgba(255, 255, 255, 0.9)', textColor: 'rgba(255, 255, 255, 0.9)',
placeholderColor: 'rgba(255, 255, 255, 0.35)', placeholderColor: 'rgba(255, 255, 255, 0.35)',
borderColor: '#5e5e5e', borderColor: '#5e5e5e',
containerColor: '#242424', containerColor: '#242424',
}; };
export type TChartColor = typeof LIGHT_CHART_COLORS;
function toUnderline(name: string): string { function toUnderline(name: string): string {
return name.replace(/([A-Z])/g, '_$1').toUpperCase(); return name.replace(/([A-Z])/g, '_$1').toUpperCase();
} }
export function getGreyColor(type: string): ColorToken { export function getGreyColor(type: string): TColorToken {
const name = toUnderline(type); const name = toUnderline(type);
return BACKGROUND_TOKEN[name] || {}; return BACKGROUND_TOKEN[name] || {};
} }
export function getBrandColor(type: string, colorList: ColorSeries): ColorToken { export function getBrandColor(type: string, colorList: TColorSeries): TColorToken {
const name = /^#[A-F\d]{6}$/i.test(type) ? type : toUnderline(type); const name = /^#[A-F\d]{6}$/i.test(type) ? type : toUnderline(type);
return colorList[name || 'DEFAULT']; return colorList[name || 'DEFAULT'];
} }
export function getColorList(colorArray: Array<ColorToken>): Array<string> { export function getColorList(colorArray: Array<TColorToken>): Array<string> {
const pureColorList = []; const pureColorList = [];
colorArray.map((colorToken) => Object.keys(colorToken).map((key) => pureColorList.push(colorToken[key]))); colorArray.map((colorToken) => Object.keys(colorToken).map((key) => pureColorList.push(colorToken[key])));
@ -262,7 +264,7 @@ export function generateColorMap(theme: string, colorPalette: Array<string>, mod
}; };
return colorMap; return colorMap;
} }
export function insertThemeStylesheet(theme: string, colorMap: ColorToken, mode: 'light' | 'dark') { export function insertThemeStylesheet(theme: string, colorMap: TColorToken, mode: 'light' | 'dark') {
const isDarkMode = mode === 'dark'; const isDarkMode = mode === 'dark';
const root = !isDarkMode ? `:root[theme-color='${theme}']` : `:root[theme-color='${theme}'][theme-mode='dark']`; const root = !isDarkMode ? `:root[theme-color='${theme}']` : `:root[theme-color='${theme}'][theme-mode='dark']`;

View File

@ -35,5 +35,5 @@ export interface NotificationItem {
status: boolean; status: boolean;
collected: boolean; collected: boolean;
date: string; date: string;
quality: 'high' | 'low' | 'middle'; quality: string;
} }

View File

@ -6,37 +6,25 @@
</t-breadcrumb> </t-breadcrumb>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, computed } from 'vue'; import { computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
export default defineComponent({ const crumbs = computed(() => {
name: 'TdesignStarterBreadcrumb', const route = useRoute();
props: {
isVisible: Boolean,
},
setup() {
const crumbs = computed(() => {
const route = useRoute();
const pathArray = route.path.split('/'); const pathArray = route.path.split('/');
pathArray.shift(); pathArray.shift();
const breadcrumbs = pathArray.reduce((breadcrumbArray, path, idx) => { const breadcrumbs = pathArray.reduce((breadcrumbArray, path, idx) => {
breadcrumbArray.push({ breadcrumbArray.push({
path, path,
to: breadcrumbArray[idx - 1] ? `/${breadcrumbArray[idx - 1].path}/${path}` : `/${path}`, to: breadcrumbArray[idx - 1] ? `/${breadcrumbArray[idx - 1].path}/${path}` : `/${path}`,
title: route.matched[idx].meta.title || path, title: route.matched[idx].meta.title || path,
});
return breadcrumbArray;
}, []);
return breadcrumbs;
}); });
return breadcrumbArray;
return { }, []);
crumbs, return breadcrumbs;
};
},
}); });
</script> </script>
<style scoped> <style scoped>

View File

@ -2,18 +2,8 @@
<div :class="prefix + '-footer'">Copyright @ 2021-{{ new Date().getFullYear() }} Tencent. All Rights Reserved</div> <div :class="prefix + '-footer'">Copyright @ 2021-{{ new Date().getFullYear() }} Tencent. All Rights Reserved</div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue';
import { prefix } from '@/config/global'; import { prefix } from '@/config/global';
export default defineComponent({
name: `${prefix}-footer`,
setup() {
return {
prefix,
};
},
});
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -12,7 +12,7 @@
<search :layout="layout" /> <search :layout="layout" />
</div> </div>
</template> </template>
<menu-content v-show="layout !== 'side'" class="header-menu" :nav-data="menu" /> <MenuContent v-show="layout !== 'side'" class="header-menu" :nav-data="menu" />
<template #operations> <template #operations>
<div class="operations-container"> <div class="operations-container">
<!-- 搜索框 --> <!-- 搜索框 -->
@ -63,11 +63,10 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, PropType, computed, ref } from 'vue'; import { PropType, computed } from 'vue';
import { useStore } from 'vuex';
import { useRouter, useRoute } from 'vue-router'; import { useRouter, useRoute } from 'vue-router';
import { useSettingStore } from '@/store';
import { prefix } from '@/config/global'; import { prefix } from '@/config/global';
import tLogoFull from '@/assets/assets-logo-full.svg?component'; import tLogoFull from '@/assets/assets-logo-full.svg?component';
import { MenuRoute } from '@/interface'; import { MenuRoute } from '@/interface';
@ -76,126 +75,96 @@ import Notice from './Notice.vue';
import Search from './Search.vue'; import Search from './Search.vue';
import MenuContent from './MenuContent'; import MenuContent from './MenuContent';
export default defineComponent({ const props = defineProps({
components: { theme: {
tLogoFull, type: String,
Notice, default: '',
Search,
MenuContent,
}, },
props: { layout: {
theme: { type: String,
type: String as PropType<string>, default: 'top',
default: '',
},
layout: {
type: String as PropType<string>,
default: 'top',
},
showLogo: {
type: Boolean as PropType<boolean>,
default: true,
},
menu: {
type: Array as PropType<MenuRoute[]>,
default: () => [],
},
isFixed: {
type: Boolean as PropType<boolean>,
default: false,
},
isCompact: {
type: Boolean as PropType<boolean>,
default: false,
},
maxLevel: {
type: Number as PropType<number>,
default: 3,
},
}, },
setup(props) { showLogo: {
const store = useStore(); type: Boolean,
const router = useRouter(); default: true,
},
const toggleSettingPanel = () => { menu: {
store.commit('setting/toggleSettingPanel', true); type: Array as PropType<MenuRoute[]>,
}; default: () => [],
},
const active = computed(() => { isFixed: {
const route = useRoute(); type: Boolean,
if (!route.path) { default: false,
return ''; },
} isCompact: {
return route.path type: Boolean,
.split('/') default: false,
.filter((item, index) => index <= props.maxLevel && index > 0) },
.map((item) => `/${item}`) maxLevel: {
.join(''); type: Number,
}); default: 3,
const showMenu = computed(() => !(props.layout === 'mix' && props.showLogo));
const layoutCls = computed(() => [`${prefix}-header-layout`]);
const menuCls = computed(() => {
const { isFixed, layout, isCompact } = props;
return [
{
[`${prefix}-header-menu`]: !isFixed,
[`${prefix}-header-menu-fixed`]: isFixed,
[`${prefix}-header-menu-fixed-side`]: layout === 'side' && isFixed,
[`${prefix}-header-menu-fixed-side-compact`]: layout === 'side' && isFixed && isCompact,
},
];
});
const userVisible = ref(false);
const userVisibleChange = (value: boolean) => {
userVisible.value = value;
};
const changeCollapsed = () => {
store.commit('setting/toggleSidebarCompact');
};
const isSidebarCompact = computed(() => store.state.setting.isSidebarCompact);
const handleNav = (url) => {
router.push(url);
};
const handleLogout = () => {
router.push(`/login?redirect=${router.currentRoute.value.fullPath}`);
};
const navToGitHub = () => {
window.open('https://github.com/tencent/tdesign-vue-next-starter');
};
const navToHelper = () => {
window.open('http://tdesign.tencent.com/starter/docs/get-started');
};
return {
isSidebarCompact,
toggleSettingPanel,
active,
showMenu,
layoutCls,
userVisible,
userVisibleChange,
menuCls,
changeCollapsed,
handleNav,
handleLogout,
navToGitHub,
navToHelper,
};
}, },
}); });
const router = useRouter();
const settingStore = useSettingStore();
const toggleSettingPanel = () => {
settingStore.updateConfig({
showSettingPanel: true,
});
};
const active = computed(() => {
const route = useRoute();
if (!route.path) {
return '';
}
return route.path
.split('/')
.filter((item, index) => index <= props.maxLevel && index > 0)
.map((item) => `/${item}`)
.join('');
});
const layoutCls = computed(() => [`${prefix}-header-layout`]);
const menuCls = computed(() => {
const { isFixed, layout, isCompact } = props;
return [
{
[`${prefix}-header-menu`]: !isFixed,
[`${prefix}-header-menu-fixed`]: isFixed,
[`${prefix}-header-menu-fixed-side`]: layout === 'side' && isFixed,
[`${prefix}-header-menu-fixed-side-compact`]: layout === 'side' && isFixed && isCompact,
},
];
});
const changeCollapsed = () => {
settingStore.updateConfig({
isSidebarCompact: !settingStore.isSidebarCompact,
});
};
const handleNav = (url) => {
router.push(url);
};
const handleLogout = () => {
router.push(`/login?redirect=${router.currentRoute.value.fullPath}`);
};
const navToGitHub = () => {
window.open('https://github.com/tencent/tdesign-vue-next-starter');
};
const navToHelper = () => {
window.open('http://tdesign.tencent.com/starter/docs/get-started');
};
</script> </script>
<style lang="less"> <style lang="less">
@import '@/style/variables.less'; @import '@/style/variables.less';
.@{prefix}-header { .@{prefix}-header {
&-layout { &-layout {
height: 64px; height: 64px;
@ -225,7 +194,6 @@ export default defineComponent({
height: 64px; height: 64px;
} }
} }
.header-menu { .header-menu {
flex: 1 1 1; flex: 1 1 1;
display: inline-flex; display: inline-flex;
@ -236,7 +204,7 @@ export default defineComponent({
align-items: center; align-items: center;
margin-right: 12px; margin-right: 12px;
.t-popup-reference { .t-popup__reference {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -260,7 +228,8 @@ export default defineComponent({
.header-operate-left { .header-operate-left {
display: flex; display: flex;
margin-left: 20px; margin-left: 20px;
align-items: center; align-items: normal;
line-height: 0;
.collapsed-icon { .collapsed-icon {
font-size: 20px; font-size: 20px;

View File

@ -6,17 +6,19 @@ const getMenuList = (list: MenuRoute[], basePath?: string): MenuRoute[] => {
if (!list) { if (!list) {
return []; return [];
} }
return list.map((item) => { return list
const path = basePath ? `${basePath}/${item.path}` : item.path; .map((item) => {
return { const path = basePath ? `${basePath}/${item.path}` : item.path;
path, return {
title: item.meta?.title, path,
icon: item.meta?.icon || '', title: item.meta?.title,
children: getMenuList(item.children, path), icon: item.meta?.icon || '',
meta: item.meta, children: getMenuList(item.children, path),
redirect: item.redirect, meta: item.meta,
}; redirect: item.redirect,
}); };
})
.filter((item) => item.meta && item.meta.hidden !== true);
}; };
const renderIcon = (item) => { const renderIcon = (item) => {
@ -35,6 +37,19 @@ const renderIcon = (item) => {
const useRenderNav = (list: Array<MenuRoute>) => { const useRenderNav = (list: Array<MenuRoute>) => {
return list.map((item) => { return list.map((item) => {
if (!item.children || !item.children.length || item.meta?.single) { if (!item.children || !item.children.length || item.meta?.single) {
const href = item.path.match(/(http|https):\/\/([\w.]+\/?)\S*/);
if (href) {
return (
<t-menu-item
href={href?.[0]}
name={item.path}
value={item.meta?.single ? item.redirect : item.path}
icon={renderIcon(item)}
>
{item.title}
</t-menu-item>
);
}
return ( return (
<t-menu-item <t-menu-item
name={item.path} name={item.path}

View File

@ -45,47 +45,35 @@
</t-popup> </t-popup>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, computed } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useStore } from 'vuex'; import { storeToRefs } from 'pinia';
import { useNotificationStore } from '@/store';
import { NotificationItem } from '@/interface'; import { NotificationItem } from '@/interface';
export default defineComponent({ const router = useRouter();
setup() { const store = useNotificationStore();
const store = useStore(); const { msgData, unreadMsg } = storeToRefs(store);
const { msgData } = store.state.notification;
const unreadMsg = computed(() => store.getters['notification/unreadMsg']); const setRead = (type: string, item?: NotificationItem) => {
const changeMsg = msgData.value;
const setRead = (type: string, item?: NotificationItem) => { if (type === 'all') {
const changeMsg = msgData; changeMsg.forEach((e) => {
if (type === 'all') { e.status = false;
changeMsg.forEach((e: NotificationItem) => { });
e.status = false; } else {
}); changeMsg.forEach((e) => {
} else { if (e.id === item?.id) {
changeMsg.forEach((e: NotificationItem) => { e.status = false;
if (e.id === item?.id) {
e.status = false;
}
});
} }
store.commit('notification/setMsgData', changeMsg); });
}; }
store.setMsgData(changeMsg);
};
const goDetail = () => { const goDetail = () => {
const router = useRouter(); router.push('/detail/secondary');
router.push('/detail/secondary'); };
};
return {
goDetail,
unreadMsg,
setRead,
};
},
});
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -36,31 +36,21 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, ref, PropType } from 'vue'; import { ref } from 'vue';
export default defineComponent({ defineProps({
props: { layout: String,
layout: {
type: String as PropType<string>,
},
},
setup() {
const isSearchFocus = ref(false);
const searchData = ref('');
const changeSearchFocus = (value: boolean) => {
if (!value) {
searchData.value = '';
}
isSearchFocus.value = value;
};
return {
isSearchFocus,
searchData,
changeSearchFocus,
};
},
}); });
const isSearchFocus = ref(false);
const searchData = ref('');
const changeSearchFocus = (value: boolean) => {
if (!value) {
searchData.value = '';
}
isSearchFocus.value = value;
};
</script> </script>
<style lang="less"> <style lang="less">
@import '@/style/variables.less'; @import '@/style/variables.less';
@ -81,13 +71,22 @@ export default defineComponent({
font-size: 20px !important; font-size: 20px !important;
color: @text-color-primary !important; color: @text-color-primary !important;
} }
.t-input__inner { .t-input {
border: none; border: none;
outline: none; outline: none;
box-shadow: none; box-shadow: none;
transform: background @anim-duration-base linear; transition: background @anim-duration-base linear;
.t-input__inner {
transition: background @anim-duration-base linear;
}
.t-input__inner {
background: none;
}
&:hover { &:hover {
background: @bg-color-secondarycontainer; background: @bg-color-secondarycontainer;
.t-input__inner {
background: @bg-color-secondarycontainer;
}
} }
} }
} }
@ -95,7 +94,7 @@ export default defineComponent({
.header-search { .header-search {
width: 200px; width: 200px;
transition: width @anim-duration-base @anim-time-fn-easing; transition: width @anim-duration-base @anim-time-fn-easing;
.t-input__inner { .t-input {
border: 0; border: 0;
padding-left: 40px; padding-left: 40px;
&:focus { &:focus {

View File

@ -1,18 +1,16 @@
import { defineComponent, PropType, computed, onMounted } from 'vue'; import { defineComponent, PropType, computed, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useStore } from 'vuex';
import { prefix } from '@/config/global'; import { prefix } from '@/config/global';
import pgk from '../../../package.json'; import pgk from '../../../package.json';
import MenuContent from './MenuContent'; import MenuContent from './MenuContent';
import tLogo from '@/assets/assets-t-logo.svg?component'; import tLogo from '@/assets/assets-t-logo.svg?component';
import tLogoFull from '@/assets/assets-logo-full.svg?component'; import tLogoFull from '@/assets/assets-logo-full.svg?component';
import { useSettingStore } from '@/store';
const MIN_POINT = 992 - 1; const MIN_POINT = 992 - 1;
const useComputed = (props) => { const useComputed = (props) => {
const store = useStore(); const collapsed = computed(() => useSettingStore().isSidebarCompact);
const collapsed = computed(() => store.state.setting.isSidebarCompact);
const sideNavCls = computed(() => { const sideNavCls = computed(() => {
const { isCompact } = props; const { isCompact } = props;
@ -52,7 +50,6 @@ const useComputed = (props) => {
export default defineComponent({ export default defineComponent({
name: 'SideNav', name: 'SideNav',
components: { components: {
MenuContent,
tLogoFull, tLogoFull,
tLogo, tLogo,
}, },
@ -87,16 +84,20 @@ export default defineComponent({
}, },
}, },
setup(props) { setup(props) {
const store = useStore();
const router = useRouter(); const router = useRouter();
const settingStore = useSettingStore();
const changeCollapsed = () => { const changeCollapsed = () => {
store.commit('setting/toggleSidebarCompact'); settingStore.updateConfig({
isSidebarCompact: !settingStore.isSidebarCompact,
});
}; };
const autoCollapsed = () => { const autoCollapsed = () => {
const isCompact = window.innerWidth <= MIN_POINT; const isCompact = window.innerWidth <= MIN_POINT;
store.commit('setting/showSidebarCompact', isCompact); settingStore.updateConfig({
isSidebarCompact: isCompact,
});
}; };
onMounted(() => { onMounted(() => {
@ -118,12 +119,6 @@ export default defineComponent({
.join(''); .join('');
}; };
const routerChange = (path: string) => {
router.push({
path,
});
};
const goHome = () => { const goHome = () => {
router.push('/dashboard/base'); router.push('/dashboard/base');
}; };
@ -134,7 +129,6 @@ export default defineComponent({
autoCollapsed, autoCollapsed,
changeCollapsed, changeCollapsed,
getActiveName, getActiveName,
routerChange,
goHome, goHome,
}; };
}, },
@ -165,7 +159,7 @@ export default defineComponent({
), ),
}} }}
> >
<menu-content navData={this.menu} /> <MenuContent navData={this.menu} />
</t-menu> </t-menu>
<div class={`${prefix}-side-nav-placeholder${this.collapsed ? '-hidden' : ''}`}></div> <div class={`${prefix}-side-nav-placeholder${this.collapsed ? '-hidden' : ''}`}></div>
</div> </div>

View File

@ -1,135 +1,128 @@
import { defineComponent } from 'vue'; import { defineComponent, computed } from 'vue';
import { mapGetters } from 'vuex'; import { storeToRefs } from 'pinia';
import TdesignHeader from './components/Header.vue'; import { useRoute } from 'vue-router';
import TdesignBreadcrumb from './components/Breadcrumb.vue'; import { usePermissionStore, useSettingStore } from '@/store';
import TdesignFooter from './components/Footer.vue';
import TdesignSideNav from './components/SideNav'; import TDesignHeader from './components/Header.vue';
import TdesignContent from './components/Content.vue'; import TDesignBreadcrumb from './components/Breadcrumb.vue';
import TDesignFooter from './components/Footer.vue';
import TDesignSideNav from './components/SideNav';
import TDesignContent from './components/Content.vue';
import { prefix } from '@/config/global'; import { prefix } from '@/config/global';
import TdesignSetting from './setting.vue'; import TdesignSetting from './setting.vue';
import { SettingType, ClassName } from '@/interface';
import '@/style/layout.less'; import '@/style/layout.less';
const name = `${prefix}-base-layout`; const name = `${prefix}-base-layout`;
export default defineComponent({ export default defineComponent({
name, name,
components: { setup() {
TdesignHeader, const route = useRoute();
TdesignFooter, const permissionStore = usePermissionStore();
TdesignSideNav, const settingStore = useSettingStore();
TdesignSetting, const { routers: menuRouters } = storeToRefs(permissionStore);
TdesignBreadcrumb, const setting = storeToRefs(settingStore);
TdesignContent,
}, const mainLayoutCls = computed(() => [
computed: { {
...mapGetters({ 't-layout--with-sider': settingStore.showSidebar,
showSidebar: 'setting/showSidebar', },
showHeader: 'setting/showHeader', ]);
showHeaderLogo: 'setting/showHeaderLogo',
showSidebarLogo: 'setting/showSidebarLogo', const headerMenu = computed(() => {
showFooter: 'setting/showFooter', if (settingStore.layout === 'mix') {
mode: 'setting/mode', if (settingStore.splitMenu) {
menuRouters: 'permission/routers', console.log(menuRouters);
}), return menuRouters.value.map((menu) => ({
setting(): SettingType {
return this.$store.state.setting;
},
mainLayoutCls(): ClassName {
return [
{
't-layout-has-sider': this.showSidebar,
},
];
},
headerMenu() {
const { layout, splitMenu } = this.$store.state.setting;
const { menuRouters } = this;
if (layout === 'mix') {
if (splitMenu) {
return menuRouters.map((menu) => ({
...menu, ...menu,
children: [], children: [],
})); }));
} }
return []; return [];
} }
return menuRouters; return menuRouters.value;
}, });
sideMenu() {
const { layout, splitMenu } = this.$store.state.setting; const sideMenu = computed(() => {
let { menuRouters } = this; const { layout, splitMenu } = settingStore;
let newMenuRouters = menuRouters.value;
if (layout === 'mix' && splitMenu) { if (layout === 'mix' && splitMenu) {
menuRouters.forEach((menu) => { newMenuRouters.forEach((menu) => {
if (this.$route.path.indexOf(menu.path) === 0) { if (route.path.indexOf(menu.path) === 0) {
menuRouters = menu.children.map((subMenu) => ({ ...subMenu, path: `${menu.path}/${subMenu.path}` })); newMenuRouters = menu.children.map((subMenu) => ({ ...subMenu, path: `${menu.path}/${subMenu.path}` }));
} }
}); });
} }
return menuRouters; return newMenuRouters;
}, });
},
methods: { const renderSidebar = () => {
renderSidebar() {
return ( return (
this.showSidebar && ( settingStore.showSidebar && (
<tdesign-side-nav <TDesignSideNav
showLogo={this.showSidebarLogo} showLogo={settingStore.showSidebarLogo}
layout={this.setting.layout} layout={settingStore.layout}
isFixed={this.setting.isSidebarFixed} isFixed={settingStore.isSidebarFixed}
menu={this.sideMenu} menu={sideMenu.value}
theme={this.mode} theme={settingStore.displayMode}
isCompact={this.setting.isSidebarCompact} isCompact={settingStore.isSidebarCompact}
/> />
) )
); );
}, };
renderHeader() {
const renderHeader = () => {
return ( return (
this.showHeader && ( settingStore.showHeader && (
<tdesign-header <TDesignHeader
showLogo={this.showHeaderLogo} showLogo={settingStore.showHeaderLogo}
theme={this.mode} theme={settingStore.displayMode}
layout={this.setting.layout} layout={settingStore.layout}
isFixed={this.setting.isHeaderFixed} isFixed={settingStore.isHeaderFixed}
menu={this.headerMenu} menu={headerMenu.value}
isCompact={this.setting.isSidebarCompact} isCompact={settingStore.isSidebarCompact}
/> />
) )
); );
}, };
renderContent() {
const { showBreadcrumb } = this.setting; const renderFooter = () => {
const { showFooter } = this; return (
<t-footer class={`${prefix}-footer-layout`}>
<TDesignFooter />
</t-footer>
);
};
const renderContent = () => {
const { showBreadcrumb, showFooter } = settingStore;
return ( return (
<t-layout class={[`${prefix}-layout`]}> <t-layout class={[`${prefix}-layout`]}>
<t-content class={`${prefix}-content-layout`}> <t-content class={`${prefix}-content-layout`}>
{showBreadcrumb && <tdesign-breadcrumb />} {showBreadcrumb && <TDesignBreadcrumb />}
<TdesignContent /> <TDesignContent />
</t-content> </t-content>
{showFooter && this.renderFooter()} {showFooter && renderFooter()}
</t-layout> </t-layout>
); );
}, };
renderFooter() { return {
return ( setting,
<t-footer class={`${prefix}-footer-layout`}> mainLayoutCls,
<tdesign-footer /> renderSidebar,
</t-footer> renderHeader,
); renderContent,
}, };
}, },
render() { render() {
const { layout } = this.setting; const { layout } = this.setting;
const header = this.renderHeader(); const header = this.renderHeader();
const sidebar = this.renderSidebar(); const sidebar = this.renderSidebar();
const content = this.renderContent(); const content = this.renderContent();
return ( return (
<div class={`${prefix}-wrapper`}> <div>
{layout === 'side' ? ( {layout === 'side' ? (
<t-layout class={this.mainLayoutCls} key="side"> <t-layout class={this.mainLayoutCls} key="side">
<t-aside>{sidebar}</t-aside> <t-aside>{sidebar}</t-aside>
@ -141,7 +134,7 @@ export default defineComponent({
<t-layout class={this.mainLayoutCls}>{[sidebar, content]}</t-layout> <t-layout class={this.mainLayoutCls}>{[sidebar, content]}</t-layout>
</t-layout> </t-layout>
)} )}
<tdesign-setting /> <TdesignSetting />
</div> </div>
); );
}, },

View File

@ -3,7 +3,6 @@
v-model:visible="showSettingPanel" v-model:visible="showSettingPanel"
size="408px" size="408px"
:footer="false" :footer="false"
value="medium"
header="页面配置" header="页面配置"
:close-btn="true" :close-btn="true"
class="setting-drawer-container" class="setting-drawer-container"
@ -50,7 +49,7 @@
:value="COLOR_OPTIONS[COLOR_OPTIONS.length - 1]" :value="COLOR_OPTIONS[COLOR_OPTIONS.length - 1]"
class="setting-layout-color-group dynamic-color-btn" class="setting-layout-color-group dynamic-color-btn"
> >
<color-container :value="COLOR_OPTIONS[COLOR_OPTIONS.length - 1]" /> <ColorContainer :value="COLOR_OPTIONS[COLOR_OPTIONS.length - 1]" />
</t-radio-button> </t-radio-button>
</t-popup> </t-popup>
</div> </div>
@ -60,7 +59,7 @@
<t-radio-group v-model="formData.layout" default-vaule="top"> <t-radio-group v-model="formData.layout" default-vaule="top">
<div v-for="(item, index) in LAYOUT_OPTION" :key="index" class="setting-layout-drawer"> <div v-for="(item, index) in LAYOUT_OPTION" :key="index" class="setting-layout-drawer">
<t-radio-button :key="index" :value="item"> <t-radio-button :key="index" :value="item">
<thumbnail :src="getThumbnailUrl(item)" /> <Thumbnail :src="getThumbnailUrl(item)" />
</t-radio-button> </t-radio-button>
</div> </div>
</t-radio-group> </t-radio-group>
@ -69,10 +68,7 @@
<t-switch v-model="formData.splitMenu" /> <t-switch v-model="formData.splitMenu" />
</t-form-item> </t-form-item>
<t-form-item v-show="formData.layout !== 'side'" label="固定 Header" name="isHeaderFixed"> <t-form-item v-show="formData.layout === 'mix'" label="固定 Sidebar" name="isSidebarFixed">
<t-switch v-model="formData.isHeaderFixed" />
</t-form-item>
<t-form-item v-show="formData.layout !== 'top'" label="固定 Sidebar" name="isSidebarFixed">
<t-switch v-model="formData.isSidebarFixed" /> <t-switch v-model="formData.isSidebarFixed" />
</t-form-item> </t-form-item>
@ -86,35 +82,35 @@
<t-form-item label="显示 Footer" name="showFooter"> <t-form-item label="显示 Footer" name="showFooter">
<t-switch v-model="formData.showFooter" /> <t-switch v-model="formData.showFooter" />
</t-form-item> </t-form-item>
<t-form-item v-show="formData.showFooter && !formData.isSidebarFixed" label="footer 内收" name="footerPosition">
<t-switch v-model="formData.isFooterAside" />
</t-form-item>
</t-form> </t-form>
<div class="setting-info"> <div class="setting-info">
<p>请复制后手动修改配置文件: /src/config/style.js</p> <p>请复制后手动修改配置文件: /src/config/style.ts</p>
<t-button theme="primary" variant="text" @click="handleCopy"> 复制配置项 </t-button> <t-button theme="primary" variant="text" @click="handleCopy"> 复制配置项 </t-button>
</div> </div>
</div> </div>
</t-drawer> </t-drawer>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, ref, computed, watch, onMounted } from 'vue'; import { ref, computed, watch, onMounted, watchEffect } from 'vue';
import { useStore } from 'vuex';
import { ColorPicker } from 'vue-color-kit'; import { ColorPicker } from 'vue-color-kit';
import { MessagePlugin, PopupVisibleChangeContext } from 'tdesign-vue-next'; import { MessagePlugin, PopupVisibleChangeContext } from 'tdesign-vue-next';
import { Color } from 'tvision-color'; import { Color } from 'tvision-color';
import useClipboard from 'vue-clipboard3';
import { useSettingStore } from '@/store';
import Thumbnail from '@/components/thumbnail/index.vue';
import ColorContainer from '@/components/color/index.vue';
import 'vue-color-kit/dist/vue-color-kit.css'; import 'vue-color-kit/dist/vue-color-kit.css';
import STYLE_CONFIG from '@/config/style'; import STYLE_CONFIG from '@/config/style';
import { insertThemeStylesheet, generateColorMap } from '@/config/color'; import { insertThemeStylesheet, generateColorMap } 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 SettingDarkIcon from '@/assets/assets-setting-dark.svg';
import SettingLightIcon from '@/assets/assets-setting-light.svg'; import SettingLightIcon from '@/assets/assets-setting-light.svg';
import SettingAutoIcon from '@/assets/assets-setting-auto.svg'; import SettingAutoIcon from '@/assets/assets-setting-auto.svg';
const settingStore = useSettingStore();
const LAYOUT_OPTION = ['side', 'top', 'mix']; const LAYOUT_OPTION = ['side', 'top', 'mix'];
const COLOR_OPTIONS = ['default', 'cyan', 'green', 'yellow', 'orange', 'red', 'pink', 'purple', 'dynamic']; const COLOR_OPTIONS = ['default', 'cyan', 'green', 'yellow', 'orange', 'red', 'pink', 'purple', 'dynamic'];
const MODE_OPTIONS = [ const MODE_OPTIONS = [
@ -123,123 +119,113 @@ const MODE_OPTIONS = [
{ type: 'auto', text: '跟随系统' }, { type: 'auto', text: '跟随系统' },
]; ];
export default defineComponent({ const formData = ref({ ...STYLE_CONFIG });
name: 'DefaultLayoutSetting', const colors = ref();
components: { Thumbnail, ColorContainer, ColorPicker }, const isColoPickerDisplay = ref(false);
setup() {
const formData = ref({ ...STYLE_CONFIG });
const store = useStore();
const colors = ref();
const isColoPickerDisplay = ref(false);
const showSettingPanel = computed({ const showSettingPanel = computed({
get() { get() {
return store.state.setting.showSettingPanel; return settingStore.showSettingPanel;
}, },
set(newVal) { set(newVal: boolean) {
store.commit('setting/toggleSettingPanel', newVal); settingStore.updateConfig({
}, showSettingPanel: newVal,
}); });
},
});
const mode = computed(() => { const mode = computed(() => {
return store.getters['setting/mode']; return settingStore.displayMode;
});
watch(
() => colors.value,
(newColor) => {
const { hex } = newColor;
// hex
const newPalette = Color.getPaletteByGradation({
colors: [hex],
step: 10,
})[0];
const { mode } = settingStore;
const colorMap = generateColorMap(hex, newPalette, mode as 'light' | 'dark');
insertThemeStylesheet(hex, colorMap, mode as 'light' | 'dark');
settingStore.updateConfig({
[hex]: colorMap,
}); });
settingStore.changeBrandTheme(hex);
},
);
watch( const changeColor = (val) => {
() => colors.value, const { hex } = val;
(newColor) => { // hex
const { hex } = newColor; const newPalette = Color.getPaletteByGradation({
const { setting } = store.state; colors: [hex],
step: 10,
})[0];
const { mode } = settingStore;
const colorMap = generateColorMap(hex, newPalette, mode as 'light' | 'dark');
// hex settingStore.updateConfig({
const newPalette = Color.getPaletteByGradation({ [hex]: colorMap,
colors: [hex], });
step: 10,
})[0];
const { mode } = store.state.setting;
const colorMap = generateColorMap(hex, newPalette, mode);
store.commit('setting/addColor', { [hex]: colorMap }); insertThemeStylesheet(hex, colorMap, mode as 'light' | 'dark');
insertThemeStylesheet(hex, colorMap, mode); settingStore.changeBrandTheme(hex);
};
store.dispatch('setting/changeTheme', { ...setting, brandTheme: hex }); onMounted(() => {
}, document.querySelector('.dynamic-color-btn').addEventListener('click', () => {
); isColoPickerDisplay.value = true;
const changeColor = (val) => { });
const { hex } = val; });
const { setting } = store.state;
// hex const onPopupVisibleChange = (visible: boolean, context: PopupVisibleChangeContext) => {
const newPalette = Color.getPaletteByGradation({ if (!visible && context.trigger === 'document') {
colors: [hex], isColoPickerDisplay.value = visible;
step: 10, }
})[0]; };
const { mode } = store.state.setting;
const colorMap = generateColorMap(hex, newPalette, mode);
store.commit('setting/addColor', { [hex]: colorMap }); const handleCopy = () => {
const text = JSON.stringify(formData.value, null, 4);
insertThemeStylesheet(hex, colorMap, mode); const { toClipboard } = useClipboard();
toClipboard(text)
store.dispatch('setting/changeTheme', { ...setting, brandTheme: hex }); .then(() => {
};
onMounted(() => {
document.querySelector('.dynamic-color-btn').addEventListener('click', () => {
isColoPickerDisplay.value = true;
});
});
const onPopupVisibleChange = (visible: boolean, context: PopupVisibleChangeContext) => {
if (!visible && context.trigger === 'document') {
isColoPickerDisplay.value = visible;
}
};
const handleCopy = () => {
MessagePlugin.closeAll(); MessagePlugin.closeAll();
MessagePlugin.success('复制成功'); MessagePlugin.success('复制成功');
}; })
const getModeIcon = (mode: string) => { .catch(() => {
if (mode === 'light') { MessagePlugin.closeAll();
return SettingLightIcon; MessagePlugin.error('复制失败');
} });
if (mode === 'dark') { };
return SettingDarkIcon; const getModeIcon = (mode: string) => {
} if (mode === 'light') {
return SettingAutoIcon; return SettingLightIcon;
}; }
if (mode === 'dark') {
return SettingDarkIcon;
}
return SettingAutoIcon;
};
const handleCloseDrawer = () => { const handleCloseDrawer = () => {
store.commit('setting/toggleSettingPanel', false); settingStore.updateConfig({
}; showSettingPanel: false,
return { });
mode, };
changeColor,
isColoPickerDisplay, const getThumbnailUrl = (name: string): string => {
onPopupVisibleChange, return `https://tdesign.gtimg.com/tdesign-pro/setting/${name}.png`;
MODE_OPTIONS, };
LAYOUT_OPTION,
COLOR_OPTIONS, watchEffect(() => {
formData, settingStore.updateConfig(formData.value);
showSettingPanel,
handleCopy,
getModeIcon,
handleCloseDrawer,
getThumbnailUrl(name: string): string {
return `https://tdesign.gtimg.com/tdesign-pro/setting/${name}.png`;
},
};
},
watch: {
formData: {
handler(newVal) {
this.$store.dispatch('setting/changeTheme', newVal);
},
deep: true,
},
},
}); });
</script> </script>
<style lang="less"> <style lang="less">
@ -331,7 +317,7 @@ export default defineComponent({
.setting-container { .setting-container {
padding-bottom: 100px; padding-bottom: 100px;
} }
.t-radio-group.t-radio-group-medium { .t-radio-group.t-size-m {
min-height: 32px; min-height: 32px;
width: 100%; width: 100%;
height: auto; height: auto;
@ -360,12 +346,12 @@ export default defineComponent({
border: 2px solid @brand-color !important; border: 2px solid @brand-color !important;
} }
.t-form__controls--content { .t-form__controls-content {
justify-content: end; justify-content: end;
} }
} }
.t-form__controls--content { .t-form__controls-content {
justify-content: end; justify-content: end;
} }
} }

View File

@ -1,11 +1,14 @@
import { createApp } from 'vue'; import { createApp } from 'vue';
import TDesign from 'tdesign-vue-next'; import TDesign from 'tdesign-vue-next';
import 'tdesign-vue-next/es/style/index.css';
import VueClipboard from 'vue3-clipboard'; import VueClipboard from 'vue3-clipboard';
import App from './App.vue';
import { store } from './store'; import { store } from './store';
import router from './router'; import router from './router';
import '@/style/index.less'; import '@/style/index.less';
import './permission'; import './permission';
import App from './App.vue';
const app = createApp(App); const app = createApp(App);

View File

@ -1,10 +1,8 @@
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import * as echarts from 'echarts/core'; import * as echarts from 'echarts/core';
import { Color } from 'tvision-color'; import { Color } from 'tvision-color';
import { getBrandColor, defaultLightColor, defaultDarkColor } from '@/config/color'; import { getBrandColor, defaultLightColor, defaultDarkColor, TChartColor } from '@/config/color';
import store from '@/store'; import { getSettingStore } from '@/store';
const { state } = store;
/** /**
* *
@ -14,8 +12,8 @@ const { state } = store;
* @returns {} * @returns {}
*/ */
export function getColorFromTheme(theme: string) { export function getColorFromTheme(theme: string) {
const { setting } = state as any; const settingStore = getSettingStore();
const { colorList, mode } = setting; const { colorList, mode } = settingStore;
const isDarkMode = mode === 'dark'; const isDarkMode = mode === 'dark';
let themeColorList = []; let themeColorList = [];
const themeColor = getBrandColor(theme, colorList); const themeColor = getBrandColor(theme, colorList);
@ -41,8 +39,9 @@ export function getColorFromTheme(theme: string) {
/** 图表颜色 */ /** 图表颜色 */
function chartListColor(): Array<string> { function chartListColor(): Array<string> {
const { setting } = state as any; const settingStore = getSettingStore();
const res = getColorFromTheme(setting.brandTheme); const { brandTheme } = settingStore;
const res = getColorFromTheme(brandTheme);
return res; return res;
} }
@ -149,19 +148,19 @@ export function constructInitDashboardDataset(type: string) {
{ {
value: 135, value: 135,
itemStyle: { itemStyle: {
color: chartListColor()[1], opacity: 0.2,
}, },
}, },
{ {
value: 118, value: 118,
itemStyle: { itemStyle: {
color: chartListColor()[1], opacity: 0.2,
}, },
}, },
{ {
value: 60, value: 60,
itemStyle: { itemStyle: {
color: chartListColor()[1], opacity: 0.2,
}, },
}, },
], ],
@ -178,7 +177,7 @@ export function constructInitDataset({
dateTime = [], dateTime = [],
placeholderColor, placeholderColor,
borderColor, borderColor,
}: { dateTime: Array<string> } & Record<string, string>) { }: { dateTime: Array<string> } & TChartColor) {
// const dataset: Array<Array<string>> = [['时间'], ['入库'], ['出库']]; // const dataset: Array<Array<string>> = [['时间'], ['入库'], ['出库']];
const divideNum = 10; const divideNum = 10;
const timeArray = []; const timeArray = [];
@ -276,7 +275,7 @@ export function getSmoothLineDataSet({
dateTime = [], dateTime = [],
placeholderColor, placeholderColor,
borderColor, borderColor,
}: { dateTime?: Array<string> } & Record<string, string>) { }: { dateTime?: Array<string> } & TChartColor) {
let dateArray: Array<string> = ['00:00', '02:00', '04:00', '06:00']; let dateArray: Array<string> = ['00:00', '02:00', '04:00', '06:00'];
if (dateTime.length > 0) { if (dateTime.length > 0) {
const divideNum = 7; const divideNum = 7;
@ -383,7 +382,7 @@ export function getFolderLineDataSet({
dateTime = [], dateTime = [],
placeholderColor, placeholderColor,
borderColor, borderColor,
}: { dateTime?: Array<string> } & Record<string, string>) { }: { dateTime?: Array<string> } & TChartColor) {
let dateArray: Array<string> = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']; let dateArray: Array<string> = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
if (dateTime.length > 0) { if (dateTime.length > 0) {
const divideNum = 7; const divideNum = 7;
@ -543,7 +542,7 @@ export function getLineChartDataSet({
dateTime = [], dateTime = [],
placeholderColor, placeholderColor,
borderColor, borderColor,
}: { dateTime?: Array<string> } & Record<string, string>) { }: { dateTime?: Array<string> } & TChartColor) {
const divideNum = 10; const divideNum = 10;
const timeArray = []; const timeArray = [];
const inArray = []; const inArray = [];
@ -679,7 +678,7 @@ export function getScatterDataSet({
dateTime = [], dateTime = [],
placeholderColor, placeholderColor,
borderColor, borderColor,
}: { dateTime?: Array<string> } & Record<string, string>): any { }: { dateTime?: Array<string> } & TChartColor): any {
const divideNum = 40; const divideNum = 40;
const timeArray = []; const timeArray = [];
const inArray = []; const inArray = [];
@ -975,7 +974,7 @@ export function get2ColBarChartDataSet({
isMonth = false, isMonth = false,
placeholderColor, placeholderColor,
borderColor, borderColor,
}: { isMonth?: boolean } & Record<string, string>) { }: { isMonth?: boolean } & TChartColor) {
let lastYearListCopy = lastYearList.concat([]); let lastYearListCopy = lastYearList.concat([]);
let thisYearListCopy = lastYearList.concat([]); let thisYearListCopy = lastYearList.concat([]);
@ -1077,7 +1076,8 @@ export function getPieChartDataSet({
radius = 42, radius = 42,
textColor, textColor,
placeholderColor, placeholderColor,
}: { radius: number } & Record<string, string>) { containerColor,
}: { radius?: number } & Record<string, string>) {
return { return {
color: chartListColor(), color: chartListColor(),
tooltip: { tooltip: {
@ -1110,6 +1110,10 @@ export function getPieChartDataSet({
selectedMode: true, selectedMode: true,
hoverAnimation: true, hoverAnimation: true,
silent: true, silent: true,
itemStyle: {
borderColor: containerColor,
borderWidth: 1,
},
label: { label: {
show: true, show: true,
position: 'center', position: 'center',

View File

@ -2,7 +2,7 @@
<div> <div>
<t-row :gutter="[16, 16]"> <t-row :gutter="[16, 16]">
<t-col v-for="(item, index) in PANE_LIST" :key="item.title" :xs="6" :xl="3"> <t-col v-for="(item, index) in PANE_LIST" :key="item.title" :xs="6" :xl="3">
<card :subtitle="item.title" :style="{ height: '168px' }" :class="{ 'main-color': index == 0 }" size="small"> <card :subtitle="item.title" size="small" :style="{ height: '168px' }" :class="{ 'main-color': index == 0 }">
<div class="dashboard-item"> <div class="dashboard-item">
<div class="dashboard-item-top"> <div class="dashboard-item-top">
<span :style="{ fontSize: `${resizeTime * 36}px` }">{{ item.number }}</span> <span :style="{ fontSize: `${resizeTime * 36}px` }">{{ item.number }}</span>
@ -202,14 +202,14 @@
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, onMounted, watch, ref, onUnmounted } from 'vue'; import { onMounted, watch, ref, onUnmounted, nextTick, computed } from 'vue';
import { useStore } from 'vuex';
import * as echarts from 'echarts/core'; import * as echarts from 'echarts/core';
import { TooltipComponent, LegendComponent, GridComponent } from 'echarts/components'; import { TooltipComponent, LegendComponent, GridComponent } from 'echarts/components';
import { PieChart, LineChart, BarChart } from 'echarts/charts'; import { PieChart, LineChart, BarChart } from 'echarts/charts';
import { CanvasRenderer } from 'echarts/renderers'; import { CanvasRenderer } from 'echarts/renderers';
import { useSettingStore } from '@/store';
import { LAST_7_DAYS } from '@/utils/date'; import { LAST_7_DAYS } from '@/utils/date';
// //
@ -228,7 +228,7 @@ import { PANE_LIST, SALE_TEND_LIST, BUY_TEND_LIST, SALE_COLUMNS, BUY_COLUMNS } f
echarts.use([TooltipComponent, LegendComponent, PieChart, GridComponent, LineChart, BarChart, CanvasRenderer]); echarts.use([TooltipComponent, LegendComponent, PieChart, GridComponent, LineChart, BarChart, CanvasRenderer]);
const getThisMonth = (checkedValues?: string[]) => { const getThisMonth = (checkedValues?: string[]) => {
let date; let date: Date;
if (!checkedValues || checkedValues.length === 0) { if (!checkedValues || checkedValues.length === 0) {
date = new Date(); date = new Date();
return `${date.getFullYear()}-${date.getMonth() + 1}`; return `${date.getFullYear()}-${date.getMonth() + 1}`;
@ -241,167 +241,150 @@ const getThisMonth = (checkedValues?: string[]) => {
return `${date.getFullYear()}-${startMonth}${date2.getFullYear()}-${endMonth}`; return `${date.getFullYear()}-${startMonth}${date2.getFullYear()}-${endMonth}`;
}; };
export default defineComponent({ const store = useSettingStore();
name: 'DashboardBase', const resizeTime = ref(1);
components: {
Card,
Trend,
},
setup() {
const store = useStore();
const resizeTime = ref(1);
const { chartColors } = store.state.setting; const chartColors = computed(() => store.chartColors);
// moneyCharts // moneyCharts
let moneyContainer: HTMLElement; let moneyContainer: HTMLElement;
let moneyChart: echarts.ECharts; let moneyChart: echarts.ECharts;
const renderMoneyChart = () => { const renderMoneyChart = () => {
if (!moneyContainer) { if (!moneyContainer) {
moneyContainer = document.getElementById('moneyContainer'); moneyContainer = document.getElementById('moneyContainer');
} }
moneyChart = echarts.init(moneyContainer); moneyChart = echarts.init(moneyContainer);
moneyChart.setOption(constructInitDashboardDataset('line')); moneyChart.setOption(constructInitDashboardDataset('line'));
}; };
// refundCharts // refundCharts
let refundContainer: HTMLElement; let refundContainer: HTMLElement;
let refundChart: echarts.ECharts; let refundChart: echarts.ECharts;
const renderRefundChart = () => { const renderRefundChart = () => {
if (!refundContainer) { if (!refundContainer) {
refundContainer = document.getElementById('refundContainer'); refundContainer = document.getElementById('refundContainer');
} }
refundChart = echarts.init(refundContainer); refundChart = echarts.init(refundContainer);
refundChart.setOption(constructInitDashboardDataset('bar')); refundChart.setOption(constructInitDashboardDataset('bar'));
}; };
// stokeCharts // stokeCharts
let stokeContainer: HTMLElement; let stokeContainer: HTMLElement;
let stokeChart: echarts.ECharts; let stokeChart: echarts.ECharts;
const renderStokeChart = () => { const renderStokeChart = () => {
if (!stokeContainer) { if (!stokeContainer) {
stokeContainer = document.getElementById('stokeContainer'); stokeContainer = document.getElementById('stokeContainer');
} }
stokeChart = echarts.init(stokeContainer); stokeChart = echarts.init(stokeContainer);
stokeChart.setOption(constructInitDataset({ dateTime: LAST_7_DAYS, ...chartColors })); stokeChart.setOption(constructInitDataset({ dateTime: LAST_7_DAYS, ...chartColors.value }));
}; };
// monitorChart // monitorChart
let monitorContainer: HTMLElement; let monitorContainer: HTMLElement;
let monitorChart: echarts.ECharts; let monitorChart: echarts.ECharts;
const renderMonitorChart = () => { const renderMonitorChart = () => {
if (!monitorContainer) { if (!monitorContainer) {
monitorContainer = document.getElementById('monitorContainer'); monitorContainer = document.getElementById('monitorContainer');
} }
monitorChart = echarts.init(monitorContainer); monitorChart = echarts.init(monitorContainer);
monitorChart.setOption(getLineChartDataSet({ ...chartColors })); monitorChart.setOption(getLineChartDataSet({ ...chartColors.value }));
}; };
// monitorChart // monitorChart
let countContainer: HTMLElement; let countContainer: HTMLElement;
let countChart: echarts.ECharts; let countChart: echarts.ECharts;
const renderCountChart = () => { const renderCountChart = () => {
if (!countContainer) { if (!countContainer) {
countContainer = document.getElementById('countContainer'); countContainer = document.getElementById('countContainer');
} }
countChart = echarts.init(countContainer); countChart = echarts.init(countContainer);
countChart.setOption(getPieChartDataSet(chartColors)); countChart.setOption(getPieChartDataSet(chartColors.value));
}; };
const renderCharts = () => { const renderCharts = () => {
renderMoneyChart(); renderMoneyChart();
renderRefundChart(); renderRefundChart();
renderStokeChart(); renderStokeChart();
renderMonitorChart(); renderMonitorChart();
renderCountChart(); renderCountChart();
}; };
// chartSize update // chartSize update
const updateContainer = () => { const updateContainer = () => {
if (document.documentElement.clientWidth >= 1400 && document.documentElement.clientWidth < 1920) { if (document.documentElement.clientWidth >= 1400 && document.documentElement.clientWidth < 1920) {
resizeTime.value = Number((document.documentElement.clientWidth / 2080).toFixed(2)); resizeTime.value = Number((document.documentElement.clientWidth / 2080).toFixed(2));
} else if (document.documentElement.clientWidth < 1080) { } else if (document.documentElement.clientWidth < 1080) {
resizeTime.value = Number((document.documentElement.clientWidth / 1080).toFixed(2)); resizeTime.value = Number((document.documentElement.clientWidth / 1080).toFixed(2));
} else { } else {
resizeTime.value = 1; resizeTime.value = 1;
} }
moneyChart.resize({ moneyChart.resize({
width: resizeTime.value * 120, width: resizeTime.value * 120,
height: resizeTime.value * 66, height: resizeTime.value * 66,
}); });
refundChart.resize({ refundChart.resize({
width: resizeTime.value * 120, width: resizeTime.value * 120,
height: resizeTime.value * 66, height: resizeTime.value * 42,
}); });
stokeChart.resize({ stokeChart.resize({
width: stokeContainer.clientWidth, width: stokeContainer.clientWidth,
height: stokeContainer.clientHeight, height: stokeContainer.clientHeight,
}); });
monitorChart.resize({ monitorChart.resize({
width: monitorContainer.clientWidth, width: monitorContainer.clientWidth,
height: resizeTime.value * 326, height: resizeTime.value * 326,
}); });
countChart.resize({ countChart.resize({
width: resizeTime.value * 326, width: resizeTime.value * 326,
height: resizeTime.value * 326, height: resizeTime.value * 326,
}); });
}; };
onMounted(() => { onMounted(() => {
renderCharts(); renderCharts();
updateContainer(); nextTick(() => {
window.addEventListener('resize', updateContainer, false); updateContainer();
}); });
window.addEventListener('resize', updateContainer, false);
onUnmounted(() => {
window.removeEventListener('resize', updateContainer);
});
const currentMonth = ref(getThisMonth());
watch(
() => store.state.setting.brandTheme,
() => {
changeChartsTheme([refundChart, stokeChart, monitorChart, countChart]);
},
);
watch(
() => store.state.setting.mode,
() => {
[moneyChart, refundChart, stokeChart, monitorChart, countChart].forEach((item) => {
item.dispose();
});
renderCharts();
},
);
return {
resizeTime,
currentMonth,
LAST_7_DAYS,
PANE_LIST,
BUY_TEND_LIST,
SALE_TEND_LIST,
SALE_COLUMNS,
BUY_COLUMNS,
onCurrencyChange(checkedValues: string[]) {
currentMonth.value = getThisMonth(checkedValues);
monitorChart.setOption(getLineChartDataSet({ dateTime: checkedValues, ...chartColors }));
},
onStokeDataChange(checkedValues: string[]) {
stokeChart.setOption(constructInitDataset({ dateTime: checkedValues, ...chartColors }));
},
rehandleClickOp(val: MouseEvent) {
console.log(val);
},
getRankClass(index: number) {
return ['dashboard-rank', { 'dashboard-rank__top': index < 3 }];
},
};
},
}); });
onUnmounted(() => {
window.removeEventListener('resize', updateContainer);
});
const currentMonth = ref(getThisMonth());
watch(
() => store.brandTheme,
() => {
changeChartsTheme([refundChart, stokeChart, monitorChart, countChart]);
},
);
watch(
() => store.mode,
() => {
[moneyChart, refundChart, stokeChart, monitorChart, countChart].forEach((item) => {
item.dispose();
});
renderCharts();
},
);
const onCurrencyChange = (checkedValues: string[]) => {
currentMonth.value = getThisMonth(checkedValues);
monitorChart.setOption(getLineChartDataSet({ dateTime: checkedValues, ...chartColors.value }));
};
const onStokeDataChange = (checkedValues: string[]) => {
stokeChart.setOption(constructInitDataset({ dateTime: checkedValues, ...chartColors.value }));
};
const rehandleClickOp = (val: MouseEvent) => {
console.log(val);
};
const getRankClass = (index: number) => {
return ['dashboard-rank', { 'dashboard-rank__top': index < 3 }];
};
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import './index.less'; @import './index.less';

View File

@ -57,107 +57,97 @@
</card> </card>
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, onMounted, onUnmounted, watch } from 'vue'; import { nextTick, onMounted, onUnmounted, watch, computed } from 'vue';
import { useStore } from 'vuex';
import * as echarts from 'echarts/core'; import * as echarts from 'echarts/core';
import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components'; import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components';
import { LineChart, ScatterChart } from 'echarts/charts'; import { LineChart, ScatterChart } from 'echarts/charts';
import { CanvasRenderer } from 'echarts/renderers'; import { CanvasRenderer } from 'echarts/renderers';
import ProductCard from '@/pages/list/card/components/Card.vue'; import ProductCard from '@/components/card/Card.vue';
import { changeChartsTheme, getFolderLineDataSet, getScatterDataSet } from '../base/index'; import { changeChartsTheme, getFolderLineDataSet, getScatterDataSet } from '../base/index';
import { PANE_LIST_DATA, PRODUCT_LIST } from './constants'; import { PANE_LIST_DATA, PRODUCT_LIST } from './constants';
import { LAST_7_DAYS } from '@/utils/date'; import { LAST_7_DAYS } from '@/utils/date';
import { useSettingStore } from '@/store';
import Trend from '@/components/trend/index.vue'; import Trend from '@/components/trend/index.vue';
import Card from '@/components/card/index.vue'; import Card from '@/components/card/index.vue';
echarts.use([GridComponent, LegendComponent, TooltipComponent, LineChart, ScatterChart, CanvasRenderer]); echarts.use([GridComponent, LegendComponent, TooltipComponent, LineChart, ScatterChart, CanvasRenderer]);
export default defineComponent({ const store = useSettingStore();
name: 'DashboardDetail', const chartColors = computed(() => store.chartColors);
components: {
Card,
Trend,
ProductCard,
},
setup() {
const store = useStore();
const { chartColors } = store.state.setting;
// lineChart logic
let lineContainer: HTMLElement;
let lineChart: echarts.ECharts;
const renderLineChart = () => {
lineContainer = document.getElementById('lineContainer');
lineChart = echarts.init(lineContainer);
lineChart.setOption(getFolderLineDataSet({ ...chartColors }));
};
// scatterChart logic // lineChart logic
let scatterContainer: HTMLElement; let lineContainer: HTMLElement;
let scatterChart: echarts.ECharts; let lineChart: echarts.ECharts;
const renderScatterChart = () => { const renderLineChart = () => {
scatterContainer = document.getElementById('scatterContainer'); lineContainer = document.getElementById('lineContainer');
scatterChart = echarts.init(scatterContainer); lineChart = echarts.init(lineContainer);
scatterChart.setOption(getScatterDataSet({ ...chartColors })); lineChart.setOption(getFolderLineDataSet({ ...chartColors.value }));
}; };
// chartSize update // scatterChart logic
const updateContainer = () => { let scatterContainer: HTMLElement;
lineChart?.resize({ let scatterChart: echarts.ECharts;
width: lineContainer.clientWidth, const renderScatterChart = () => {
height: lineContainer.clientHeight, scatterContainer = document.getElementById('scatterContainer');
}); scatterChart = echarts.init(scatterContainer);
scatterChart?.resize({ scatterChart.setOption(getScatterDataSet({ ...chartColors.value }));
width: scatterContainer.clientWidth, };
height: scatterContainer.clientHeight,
});
};
const renderCharts = () => { // chartSize update
renderScatterChart(); const updateContainer = () => {
renderLineChart(); lineChart?.resize({
}; width: lineContainer.clientWidth,
height: lineContainer.clientHeight,
});
scatterChart?.resize({
width: scatterContainer.clientWidth,
height: scatterContainer.clientHeight,
});
};
onMounted(() => { const renderCharts = () => {
renderCharts(); renderScatterChart();
window.addEventListener('resize', updateContainer, false); renderLineChart();
}); };
onUnmounted(() => { onMounted(() => {
window.removeEventListener('resize', updateContainer); renderCharts();
}); window.addEventListener('resize', updateContainer, false);
nextTick(() => {
watch( updateContainer();
() => store.state.setting.mode, });
() => {
renderCharts();
},
);
watch(
() => store.state.setting.brandTheme,
() => {
changeChartsTheme([lineChart, scatterChart]);
},
);
return {
LAST_7_DAYS,
PRODUCT_LIST,
PANE_LIST_DATA,
onSatisfyChange() {
scatterChart.setOption(getScatterDataSet({ ...chartColors }));
},
onMaterialChange(value: string[]) {
const { chartColors } = store.state.setting;
lineChart.setOption(getFolderLineDataSet({ dateTime: value, ...chartColors }));
},
};
},
}); });
onUnmounted(() => {
window.removeEventListener('resize', updateContainer);
});
watch(
() => store.mode,
() => {
renderCharts();
},
);
watch(
() => store.brandTheme,
() => {
changeChartsTheme([lineChart, scatterChart]);
},
);
const onSatisfyChange = () => {
scatterChart.setOption(getScatterDataSet({ ...chartColors.value }));
};
const onMaterialChange = (value: string[]) => {
const chartColors = computed(() => store.chartColors);
lineChart.setOption(getFolderLineDataSet({ dateTime: value, ...chartColors.value }));
};
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import url('./index.less'); @import url('./index.less');

View File

@ -109,91 +109,71 @@
</t-dialog> </t-dialog>
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import { prefix } from '@/config/global'; import { prefix } from '@/config/global';
import { BASE_INFO_DATA, TABLE_COLUMNS_DATA, PRODUCT_LIST } from './constants'; import { BASE_INFO_DATA, TABLE_COLUMNS_DATA as columns, PRODUCT_LIST } from './constants';
import request from '@/utils/request'; import request from '@/utils/request';
import { ResDataType } from '@/interface'; import { ResDataType } from '@/interface';
import Card from '@/components/card/index.vue'; import Card from '@/components/card/index.vue';
import Product from './components/Product.vue'; import Product from './components/Product.vue';
export default defineComponent({ const data = ref([]);
name: 'DetailAdvanced', const pagination = ref({
components: { defaultPageSize: 10,
Card, total: 100,
Product, defaultCurrent: 1,
},
setup() {
const data = ref([]);
const pagination = ref({
defaultPageSize: 10,
total: 100,
defaultCurrent: 1,
});
const updateCurrent = ref(0);
const stepUpdate = () => {
setInterval(() => {
if (updateCurrent.value > 5) {
updateCurrent.value = -1;
}
updateCurrent.value += 1;
}, 2000);
};
const fetchData = async () => {
try {
const res: ResDataType = await request.get('/api/get-purchase-list');
if (res.code === 0) {
const { list = [] } = res.data;
data.value = list;
pagination.value = {
...pagination.value,
total: list.length,
};
}
} catch (e) {
console.log(e);
}
};
onMounted(() => {
stepUpdate();
fetchData();
});
const visible = ref(false);
return {
BASE_INFO_DATA,
prefix,
PRODUCT_LIST,
columns: TABLE_COLUMNS_DATA,
data,
pagination,
visible,
updateCurrent,
sortChange(val) {
console.log(val);
},
rehandleChange(changeParams, triggerAndData) {
console.log('统一Change', changeParams, triggerAndData);
},
listClick() {
visible.value = true;
},
deleteClickOp(columns) {
data.value.splice(columns.index, 1);
},
onConfirm() {
visible.value = false;
},
};
},
}); });
const updateCurrent = ref(0);
const stepUpdate = () => {
setInterval(() => {
if (updateCurrent.value > 5) {
updateCurrent.value = -1;
}
updateCurrent.value += 1;
}, 2000);
};
const fetchData = async () => {
try {
const res: ResDataType = await request.get('/api/get-purchase-list');
if (res.code === 0) {
const { list = [] } = res.data;
data.value = list;
pagination.value = {
...pagination.value,
total: list.length,
};
}
} catch (e) {
console.log(e);
}
};
onMounted(() => {
stepUpdate();
fetchData();
});
const visible = ref(false);
const sortChange = (val) => {
console.log(val);
};
const rehandleChange = (changeParams, triggerAndData) => {
console.log('统一Change', changeParams, triggerAndData);
};
const listClick = () => {
visible.value = true;
};
const deleteClickOp = (columns) => {
data.value.splice(columns.rowIndex, 1);
};
const onConfirm = () => {
visible.value = false;
};
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import url('./index.less'); @import url('./index.less');

View File

@ -26,8 +26,7 @@
</card> </card>
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue';
import Card from '@/components/card/index.vue'; import Card from '@/components/card/index.vue';
const BASE_INFO_DATA = [ const BASE_INFO_DATA = [
@ -108,16 +107,6 @@ const BASE_INFO_DATA = [
type: null, type: null,
}, },
]; ];
export default defineComponent({
name: 'ListBase',
components: { Card },
data() {
return {
BASE_INFO_DATA,
};
},
});
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import url('./index.less'); @import url('./index.less');

View File

@ -68,17 +68,17 @@
</t-dialog> </t-dialog>
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, onMounted, onUnmounted, ref, watch } from 'vue'; import { onMounted, onUnmounted, ref, watch, computed } from 'vue';
import { useStore } from 'vuex';
import * as echarts from 'echarts/core'; import * as echarts from 'echarts/core';
import { TitleComponent, ToolboxComponent, TooltipComponent, GridComponent, LegendComponent } from 'echarts/components'; import { TitleComponent, ToolboxComponent, TooltipComponent, GridComponent, LegendComponent } from 'echarts/components';
import { BarChart, LineChart } from 'echarts/charts'; import { BarChart, LineChart } from 'echarts/charts';
import { CanvasRenderer } from 'echarts/renderers'; import { CanvasRenderer } from 'echarts/renderers';
import { useSettingStore } from '@/store';
import { changeChartsTheme, getSmoothLineDataSet, get2ColBarChartDataSet } from '../../dashboard/base/index'; import { changeChartsTheme, getSmoothLineDataSet, get2ColBarChartDataSet } from '../../dashboard/base/index';
import { BASE_INFO_DATA, TABLE_COLUMNS } from './constants'; import { BASE_INFO_DATA, TABLE_COLUMNS as columns } from './constants';
import { prefix } from '@/config/global'; import { prefix } from '@/config/global';
import Card from '@/components/card/index.vue'; import Card from '@/components/card/index.vue';
@ -96,118 +96,104 @@ echarts.use([
CanvasRenderer, CanvasRenderer,
]); ]);
export default defineComponent({ const store = useSettingStore();
name: 'DetailDeploy',
components: { Card },
setup() {
const store = useStore();
const { chartColors } = store.state.setting; const chartColors = computed(() => store.chartColors);
const data = ref([]); const data = ref([]);
const pagination = ref({ const pagination = ref({
defaultPageSize: 10, defaultPageSize: 10,
total: 100, total: 100,
defaultCurrent: 1, defaultCurrent: 1,
});
const fetchData = async () => {
try {
const res: ResDataType = await request.get('/api/get-project-list');
if (res.code === 0) {
const { list = [] } = res.data;
data.value = list;
pagination.value = {
...pagination.value,
total: list.length,
};
}
} catch (e) {
console.log(e);
}
};
const visible = ref(false);
// monitorChart logic
let monitorContainer: HTMLElement;
let monitorChart: echarts.ECharts;
onMounted(() => {
monitorContainer = document.getElementById('monitorContainer');
monitorChart = echarts.init(monitorContainer);
monitorChart.setOption(getSmoothLineDataSet({ ...chartColors }));
setInterval(() => {
monitorChart.setOption(getSmoothLineDataSet({ ...chartColors }));
}, 3000);
});
// dataChart logic
let dataContainer: HTMLElement;
let dataChart: echarts.ECharts;
onMounted(() => {
dataContainer = document.getElementById('dataContainer');
dataChart = echarts.init(dataContainer);
dataChart.setOption(get2ColBarChartDataSet({ ...chartColors }));
});
const intervalTimer = null;
/// / chartSize update
const updateContainer = () => {
monitorChart.resize({
width: monitorContainer.clientWidth,
height: monitorContainer.clientHeight,
});
dataChart.resize({
width: dataContainer.clientWidth,
height: dataContainer.clientHeight,
});
};
onUnmounted(() => {
window.removeEventListener('resize', updateContainer);
clearInterval(intervalTimer);
});
const onAlertChange = () => {
dataChart.setOption(get2ColBarChartDataSet({ ...chartColors }));
};
onMounted(() => {
fetchData();
window.addEventListener('resize', updateContainer, false);
});
watch(
() => store.state.setting.brandTheme,
() => {
changeChartsTheme([monitorChart, dataChart]);
},
);
return {
prefix,
BASE_INFO_DATA,
columns: TABLE_COLUMNS,
data,
pagination,
visible,
sortChange(val) {
console.log(val);
},
rehandleChange(changeParams, triggerAndData) {
console.log('统一Change', changeParams, triggerAndData);
},
listClick() {
visible.value = true;
},
onConfirm() {
visible.value = false;
},
deleteClickOp(e) {
data.value.splice(e.index, 1);
},
onAlertChange,
};
},
}); });
const fetchData = async () => {
try {
const res: ResDataType = await request.get('/api/get-project-list');
if (res.code === 0) {
const { list = [] } = res.data;
data.value = list;
pagination.value = {
...pagination.value,
total: list.length,
};
}
} catch (e) {
console.log(e);
}
};
const visible = ref(false);
// monitorChart logic
let monitorContainer: HTMLElement;
let monitorChart: echarts.ECharts;
onMounted(() => {
monitorContainer = document.getElementById('monitorContainer');
monitorChart = echarts.init(monitorContainer);
monitorChart.setOption(getSmoothLineDataSet({ ...chartColors.value }));
setInterval(() => {
monitorChart.setOption(getSmoothLineDataSet({ ...chartColors.value }));
}, 3000);
});
// dataChart logic
let dataContainer: HTMLElement;
let dataChart: echarts.ECharts;
onMounted(() => {
dataContainer = document.getElementById('dataContainer');
dataChart = echarts.init(dataContainer);
dataChart.setOption(get2ColBarChartDataSet({ ...chartColors.value }));
});
const intervalTimer = null;
/// / chartSize update
const updateContainer = () => {
monitorChart.resize({
width: monitorContainer.clientWidth,
height: monitorContainer.clientHeight,
});
dataChart.resize({
width: dataContainer.clientWidth,
height: dataContainer.clientHeight,
});
};
onUnmounted(() => {
window.removeEventListener('resize', updateContainer);
clearInterval(intervalTimer);
});
const onAlertChange = () => {
dataChart.setOption(get2ColBarChartDataSet({ ...chartColors.value }));
};
onMounted(() => {
fetchData();
window.addEventListener('resize', updateContainer, false);
});
watch(
() => store.brandTheme,
() => {
changeChartsTheme([monitorChart, dataChart]);
},
);
const sortChange = (val) => {
console.log(val);
};
const rehandleChange = (changeParams, triggerAndData) => {
console.log('统一Change', changeParams, triggerAndData);
};
const listClick = () => {
visible.value = true;
};
const onConfirm = () => {
visible.value = false;
};
const deleteClickOp = (e) => {
data.value.splice(e.rowIndex, 1);
};
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import url('../base/index.less'); @import url('../base/index.less');

View File

@ -49,12 +49,13 @@
/> />
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, ref, computed, ComputedRef } from 'vue'; import { ref, computed } from 'vue';
import { useStore } from 'vuex'; import { storeToRefs } from 'pinia';
import { NOTIFICATION_TYPES } from '@/constants'; import { NOTIFICATION_TYPES } from '@/constants';
import { NotificationItem } from '@/interface'; import { NotificationItem } from '@/interface';
import EmptyIcon from '@/assets/assets-empty.svg?component'; import EmptyIcon from '@/assets/assets-empty.svg?component';
import { useNotificationStore } from '@/store';
const TAB_LIST = [ const TAB_LIST = [
{ {
@ -71,68 +72,47 @@ const TAB_LIST = [
}, },
]; ];
export default defineComponent({ const tabValue = ref('msgData');
name: 'DetailSecondary',
components: {
EmptyIcon,
},
setup() {
const tabValue = ref('msgData');
const visible = ref(false); const visible = ref(false);
const selectedItem = ref<NotificationItem>(); const selectedItem = ref<NotificationItem>();
const store = useStore(); const store = useNotificationStore();
const { msgData, unreadMsg, readMsg } = storeToRefs(store);
const { msgData } = store.state.notification; const msgDataList = computed(() => {
if (tabValue.value === 'msgData') return msgData.value;
const msgDataList: ComputedRef<NotificationItem[]> = computed(() => { if (tabValue.value === 'unreadMsg') return unreadMsg.value;
if (tabValue.value === 'msgData') return msgData; if (tabValue.value === 'readMsg') return readMsg.value;
if (tabValue.value === 'unreadMsg') return store.getters['notification/unreadMsg']; return [];
if (tabValue.value === 'readMsg') return store.getters['notification/readMsg'];
return [];
});
const handleClickDeleteBtn = (item: NotificationItem) => {
visible.value = true;
selectedItem.value = item;
};
const setReadStatus = (item: NotificationItem) => {
const changeMsg = msgData;
changeMsg.forEach((e: NotificationItem) => {
if (e.id === item.id) {
if (e.status) e.status = false;
}
});
store.commit('notification/setMsgData', changeMsg);
};
const deleteMsg = () => {
const item = selectedItem.value;
const changeMsg = msgData;
changeMsg.forEach((e: NotificationItem, index: number) => {
if (e.id === item?.id) {
changeMsg.splice(index, 1);
}
});
visible.value = false;
store.commit('notification/setMsgData', changeMsg);
};
return {
TAB_LIST,
NOTIFICATION_TYPES,
visible,
selectedItem,
tabValue,
msgDataList,
handleClickDeleteBtn,
setReadStatus,
deleteMsg,
};
},
}); });
const handleClickDeleteBtn = (item: NotificationItem) => {
visible.value = true;
selectedItem.value = item;
};
const setReadStatus = (item: NotificationItem) => {
const changeMsg = msgData.value;
changeMsg.forEach((e: NotificationItem) => {
if (e.id === item.id) {
if (e.status) e.status = false;
}
});
store.setMsgData(changeMsg);
};
const deleteMsg = () => {
const item = selectedItem.value;
const changeMsg = msgData.value;
changeMsg.forEach((e: NotificationItem, index: number) => {
if (e.id === item?.id) {
changeMsg.splice(index, 1);
}
});
visible.value = false;
store.setMsgData(changeMsg);
};
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import url('./index.less'); @import url('./index.less');

View File

@ -1,167 +1,148 @@
<template> <template>
<div> <t-form
ref="form"
class="base-form"
:data="formData"
:rules="FORM_RULES"
label-align="top"
:label-width="100"
@reset="onReset"
@submit="onSubmit"
>
<div class="form-basic-container"> <div class="form-basic-container">
<div class="form-basic-item"> <div class="form-basic-item">
<div class="form-basic-container-title">合同信息</div> <div class="form-basic-container-title">合同信息</div>
<!-- 表单内容 --> <!-- 表单内容 -->
<t-form
ref="form"
class="base-form"
:data="formData"
:rules="FORM_RULES"
label-align="top"
:label-width="100"
@reset="onReset"
@submit="onSubmit"
>
<!-- 合同名称,合同类型 -->
<t-row class="row-gap" :gutter="[16, 24]">
<t-col :span="6">
<t-form-item label="合同名称" name="name">
<t-input v-model="formData.name" :style="{ width: '322px' }" placeholder="请输入内容" />
</t-form-item>
</t-col>
<t-col :span="6">
<t-form-item label="合同类型" name="type">
<t-select
v-model="formData.type"
:style="{ width: '322px' }"
placeholder="请选择类型"
class="demo-select-base"
clearable
>
<t-option v-for="(item, index) in TYPE_OPTIONS" :key="index" :value="item.value" :label="item.label">
{{ item.label }}
</t-option>
</t-select>
</t-form-item>
</t-col>
<!-- 合同收付类型 --> <!-- 合同名称,合同类型 -->
<t-col :span="8"> <t-row class="row-gap" :gutter="[16, 24]">
<t-form-item label="合同收付类型" name="payment"> <t-col :span="6">
<t-radio-group v-model="formData.payment"> <t-form-item label="合同名称" name="name">
<t-radio value="1"> 收款 </t-radio> <t-input v-model="formData.name" :style="{ width: '322px' }" placeholder="请输入内容" />
<t-radio value="2"> 付款 </t-radio> </t-form-item>
</t-radio-group> </t-col>
<span class="space-item" /> <t-col :span="6">
<t-form-item label="合同类型" name="type">
<t-select
v-model="formData.type"
:style="{ width: '322px' }"
placeholder="请选择类型"
class="demo-select-base"
clearable
>
<t-option v-for="(item, index) in TYPE_OPTIONS" :key="index" :value="item.value" :label="item.label">
{{ item.label }}
</t-option>
</t-select>
</t-form-item>
</t-col>
<!-- 合同收付类型 -->
<t-col :span="8">
<t-form-item label="合同收付类型" name="payment">
<t-radio-group v-model="formData.payment">
<t-radio value="1"> 收款 </t-radio>
<t-radio value="2"> 付款 </t-radio>
</t-radio-group>
<span class="space-item" />
<div>
<t-input placeholder="请输入金额" :style="{ width: '160px' }" /> <t-input placeholder="请输入金额" :style="{ width: '160px' }" />
</t-form-item> </div>
</t-col> </t-form-item>
</t-col>
<t-col :span="6"> <t-col :span="6">
<t-form-item label="甲方" name="partyA"> <t-form-item label="甲方" name="partyA">
<t-select <t-select
v-model="formData.partyA" v-model="formData.partyA"
:style="{ width: '322px' }" :style="{ width: '322px' }"
class="demo-select-base" class="demo-select-base"
placeholder="请选择类型" placeholder="请选择类型"
clearable clearable
> >
<t-option <t-option v-for="(item, index) in PARTY_A_OPTIONS" :key="index" :value="item.value" :label="item.label">
v-for="(item, index) in PARTY_A_OPTIONS" {{ item.label }}
:key="index" </t-option>
:value="item.value" </t-select>
:label="item.label" </t-form-item>
> </t-col>
{{ item.label }} <t-col :span="6">
</t-option> <t-form-item label="乙方" name="partyB">
</t-select> <t-select
</t-form-item> v-model="formData.partyB"
</t-col> :style="{ width: '322px' }"
<t-col :span="6"> placeholder="请选择类型"
<t-form-item label="乙方" name="partyB"> class="demo-select-base"
<t-select clearable
v-model="formData.partyB" >
:style="{ width: '322px' }" <t-option v-for="(item, index) in PARTY_B_OPTIONS" :key="index" :value="item.value" :label="item.label">
placeholder="请选择类型" {{ item.label }}
class="demo-select-base" </t-option>
clearable </t-select>
> </t-form-item>
<t-option </t-col>
v-for="(item, index) in PARTY_B_OPTIONS" <t-col :span="6">
:key="index" <t-form-item label="合同签订日期" name="signDate">
:value="item.value" <t-date-picker
:label="item.label" v-model="formData.signDate"
> :style="{ width: '322px' }"
{{ item.label }} theme="primary"
</t-option> mode="date"
</t-select> separator="/"
</t-form-item> />
</t-col> </t-form-item>
<t-col :span="6"> </t-col>
<t-form-item label="合同签订日期" name="signDate"> <t-col :span="6">
<t-date-picker <t-form-item label="合同生效日期" name="startDate">
v-model="formData.signDate" <t-date-picker
:style="{ width: '322px' }" v-model="formData.startDate"
theme="primary" :style="{ width: '322px' }"
mode="date" theme="primary"
separator="/" mode="date"
/> separator="/"
</t-form-item> />
</t-col> </t-form-item>
<t-col :span="6"> </t-col>
<t-form-item label="合同生效日期" name="startDate"> <t-col :span="6">
<t-date-picker <t-form-item label="合同结束日期" name="endDate">
v-model="formData.startDate" <t-date-picker
:style="{ width: '322px' }" v-model="formData.endDate"
theme="primary" :style="{ width: '322px' }"
mode="date" theme="primary"
separator="/" mode="date"
/> separator="/"
</t-form-item> />
</t-col> </t-form-item>
<t-col :span="6"> </t-col>
<t-form-item label="合同结束日期" name="endDate"> <t-col :span="6">
<t-date-picker <t-form-item label="" name="files">
v-model="formData.endDate" <t-upload
:style="{ width: '322px' }" v-model="formData.files"
theme="primary" action="https://service-bv448zsw-1257786608.gz.apigw.tencentcs.com/api/upload-demo"
mode="date" tips="请上传pdf文件大小在60M以内"
separator="/" :size-limit="{ size: 60, unit: 'MB' }"
/> :format-response="formatResponse"
</t-form-item> :before-upload="beforeUpload"
</t-col> @fail="handleFail"
<t-col :span="6"> >
<t-form-item label="" name="files"> <t-button class="form-submit-upload-btn" variant="outline"> 上传合同文件 </t-button>
<t-upload </t-upload>
v-model="formData.files" </t-form-item>
action="https://service-bv448zsw-1257786608.gz.apigw.tencentcs.com/api/upload-demo" </t-col>
tips="请上传pdf文件大小在60M以内" </t-row>
:size-limit="{ size: 60, unit: 'MB' }"
:format-response="formatResponse"
:before-upload="beforeUpload"
@fail="handleFail"
>
<t-button class="form-submit-upload-btn" variant="outline"> 上传合同文件 </t-button>
</t-upload>
</t-form-item>
</t-col>
</t-row>
</t-form>
<div class="form-basic-container-title form-title-gap">其它信息</div> <div class="form-basic-container-title form-title-gap">其它信息</div>
<t-form
ref="form" <t-form-item label="备注" name="comment">
class="base-form" <t-textarea v-model="formData.comment" :height="124" placeholder="请输入备注" />
:data="formData" </t-form-item>
:rules="FORM_RULES" <t-form-item label="公证人">
label-align="top" <t-avatar-group>
:label-width="100" <t-avatar>D</t-avatar>
@reset="onReset" <t-avatar>S</t-avatar>
@submit="onSubmit" <t-avatar>+</t-avatar>
> </t-avatar-group>
<t-form-item label="备注" name="comment"> </t-form-item>
<t-textarea v-model="formData.comment" :height="124" placeholder="请输入备注" />
</t-form-item>
<t-form-item label="公证人">
<t-avatar-group>
<t-avatar>D</t-avatar>
<t-avatar>S</t-avatar>
<t-avatar>+</t-avatar>
</t-avatar-group>
</t-form-item>
</t-form>
</div> </div>
</div> </div>
@ -173,53 +154,41 @@
</div> </div>
</div> </div>
</div> </div>
</div> </t-form>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, ref } from 'vue'; import { ref } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next'; import { MessagePlugin } from 'tdesign-vue-next';
import { FORM_RULES, INITIAL_DATA, TYPE_OPTIONS, PARTY_A_OPTIONS, PARTY_B_OPTIONS } from './constants'; import { FORM_RULES, INITIAL_DATA, TYPE_OPTIONS, PARTY_A_OPTIONS, PARTY_B_OPTIONS } from './constants';
export default defineComponent({ const formData = ref({ ...INITIAL_DATA });
name: 'FormBase',
setup() {
const formData = ref({ ...INITIAL_DATA });
return { const onReset = () => {
TYPE_OPTIONS, MessagePlugin.warning('取消新建');
PARTY_A_OPTIONS, };
PARTY_B_OPTIONS, const onSubmit = ({ validateResult }) => {
FORM_RULES, if (validateResult === true) {
formData, MessagePlugin.success('新建成功');
onReset() { }
MessagePlugin.warning('取消新建'); };
}, const beforeUpload = (file) => {
onSubmit({ validateResult }) { if (!/\.(pdf)$/.test(file.name)) {
if (validateResult === true) { MessagePlugin.warning('请上传pdf文件');
MessagePlugin.success('新建成功'); return false;
} }
}, if (file.size > 60 * 1024 * 1024) {
beforeUpload(file) { MessagePlugin.warning('上传文件不能大于60M');
if (!/\.(pdf)$/.test(file.name)) { return false;
MessagePlugin.warning('请上传pdf文件'); }
return false; return true;
} };
if (file.size > 60 * 1024 * 1024) { const handleFail = ({ file }) => {
MessagePlugin.warning('上传文件不能大于60M'); MessagePlugin.error(`文件 ${file.name} 上传失败`);
return false; };
} // error url /
return true; const formatResponse = (res) => {
}, return { ...res, error: '上传失败,请重试', url: res.url };
handleFail({ file }) { };
MessagePlugin.error(`文件 ${file.name} 上传失败`);
},
// error url /
formatResponse(res) {
return { ...res, error: '上传失败,请重试', url: res.url };
},
};
},
});
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import url('./index.less'); @import url('./index.less');

View File

@ -139,8 +139,8 @@
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { ValidateResultContext } from 'tdesign-vue-next'; import { ValidateResultContext } from 'tdesign-vue-next';
import Card from '@/components/card/index.vue'; import Card from '@/components/card/index.vue';
@ -155,53 +155,36 @@ import {
INITIAL_DATA3, INITIAL_DATA3,
} from './constants'; } from './constants';
export default defineComponent({ const formData1 = ref({ ...INITIAL_DATA1 });
name: 'FormStep', const formData2 = ref({ ...INITIAL_DATA2 });
components: { Card }, const formData3 = ref({ ...INITIAL_DATA3 });
setup() { const activeForm = ref(0);
const formData1 = ref({ ...INITIAL_DATA1 });
const formData2 = ref({ ...INITIAL_DATA2 });
const formData3 = ref({ ...INITIAL_DATA3 });
const activeForm = ref(0);
const amount = computed(() => { const amount = computed(() => {
if (formData1.value.name === '1') { if (formData1.value.name === '1') {
return '565421'; return '565421';
} }
if (formData1.value.name === '2') { if (formData1.value.name === '2') {
return '278821'; return '278821';
} }
if (formData1.value.name === '3') { if (formData1.value.name === '3') {
return '109824'; return '109824';
} }
return '--'; return '--';
});
return {
NAME_OPTIONS,
TYPE_OPTIONS,
ADDRESS_OPTIONS,
FORM_RULES,
formData1,
formData2,
formData3,
activeForm,
amount,
onSubmit(result: ValidateResultContext<FormData>, val: number) {
if (result.validateResult === true) {
activeForm.value = val;
}
},
onReset(val: number) {
activeForm.value = val;
},
complete() {
const router = useRouter();
router.replace({ path: '/detail/advanced' });
},
};
},
}); });
const onSubmit = (result: ValidateResultContext<FormData>, val: number) => {
if (result.validateResult === true) {
activeForm.value = val;
}
};
const onReset = (val: number) => {
activeForm.value = val;
};
const complete = () => {
const router = useRouter();
router.replace({ path: '/detail/advanced' });
};
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import url('./index.less'); @import url('./index.less');

View File

@ -7,11 +7,13 @@
<t-button variant="base" theme="default" :disabled="!selectedRowKeys.length"> 导出合同 </t-button> <t-button variant="base" theme="default" :disabled="!selectedRowKeys.length"> 导出合同 </t-button>
<p v-if="!!selectedRowKeys.length" class="selected-count">已选{{ selectedRowKeys.length }}</p> <p v-if="!!selectedRowKeys.length" class="selected-count">已选{{ selectedRowKeys.length }}</p>
</div> </div>
<t-input v-model="searchValue" class="search-input" placeholder="请输入你需要搜索的内容" clearable> <div class="search-input">
<template #suffix-icon> <t-input v-model="searchValue" placeholder="请输入你需要搜索的内容" clearable>
<search-icon size="20px" /> <template #suffix-icon>
</template> <search-icon size="20px" />
</t-input> </template>
</t-input>
</div>
</t-row> </t-row>
<t-table <t-table
@ -64,8 +66,8 @@
/> />
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, ref, onMounted, computed } from 'vue'; import { ref, onMounted, computed } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { SearchIcon } from 'tdesign-icons-vue-next'; import { SearchIcon } from 'tdesign-icons-vue-next';
import { MessagePlugin } from 'tdesign-vue-next'; import { MessagePlugin } from 'tdesign-vue-next';
@ -78,121 +80,96 @@ import request from '@/utils/request';
import { COLUMNS } from './constants'; import { COLUMNS } from './constants';
export default defineComponent({ const data = ref([]);
name: 'ListBaseCard', const pagination = ref({
components: { defaultPageSize: 20,
Card, total: 100,
SearchIcon, defaultCurrent: 1,
Trend,
},
setup() {
const data = ref([]);
const pagination = ref({
defaultPageSize: 20,
total: 100,
defaultCurrent: 1,
});
const searchValue = ref('');
const dataLoading = ref(false);
const fetchData = async () => {
dataLoading.value = true;
try {
const res: ResDataType = await request.get('/api/get-list');
if (res.code === 0) {
const { list = [] } = res.data;
data.value = list;
pagination.value = {
...pagination.value,
total: list.length,
};
}
} catch (e) {
console.log(e);
} finally {
dataLoading.value = false;
}
};
const deleteIdx = ref(-1);
const confirmBody = computed(() => {
if (deleteIdx.value > -1) {
const { name } = data.value[deleteIdx.value];
return `删除后,${name}的所有合同信息将被清空,且无法恢复`;
}
return '';
});
onMounted(() => {
fetchData();
});
const confirmVisible = ref(false);
const selectedRowKeys = ref([1, 2]);
const router = useRouter();
const resetIdx = () => {
deleteIdx.value = -1;
};
const onConfirmDelete = () => {
//
data.value.splice(deleteIdx.value - 1, 1);
pagination.value.total = data.value.length;
const selectedIdx = selectedRowKeys.value.indexOf(deleteIdx.value);
if (selectedIdx > -1) {
selectedRowKeys.value.splice(selectedIdx, 1);
}
confirmVisible.value = false;
MessagePlugin.success('删除成功');
resetIdx();
};
const onCancel = () => {
resetIdx();
};
return {
CONTRACT_STATUS,
CONTRACT_TYPES,
CONTRACT_PAYMENT_TYPES,
COLUMNS,
data,
searchValue,
dataLoading,
pagination,
confirmBody,
confirmVisible,
rowKey: 'index',
onConfirmDelete,
onCancel,
selectedRowKeys,
rehandleSelectChange(val: number[]) {
selectedRowKeys.value = val;
},
rehandlePageChange(curr, pageInfo) {
console.log('分页变化', curr, pageInfo);
},
rehandleChange(changeParams, triggerAndData) {
console.log('统一Change', changeParams, triggerAndData);
},
handleClickDetail() {
router.push('/detail/base');
},
handleSetupContract() {
router.push('/form/base');
},
handleClickDelete({ row }) {
deleteIdx.value = row.index;
confirmVisible.value = true;
},
};
},
methods: {},
}); });
const searchValue = ref('');
const dataLoading = ref(false);
const fetchData = async () => {
dataLoading.value = true;
try {
const res: ResDataType = await request.get('/api/get-list');
if (res.code === 0) {
const { list = [] } = res.data;
data.value = list;
pagination.value = {
...pagination.value,
total: list.length,
};
}
} catch (e) {
console.log(e);
} finally {
dataLoading.value = false;
}
};
const deleteIdx = ref(-1);
const confirmBody = computed(() => {
if (deleteIdx.value > -1) {
const { name } = data.value[deleteIdx.value];
return `删除后,${name}的所有合同信息将被清空,且无法恢复`;
}
return '';
});
onMounted(() => {
fetchData();
});
const confirmVisible = ref(false);
const selectedRowKeys = ref([1, 2]);
const router = useRouter();
const resetIdx = () => {
deleteIdx.value = -1;
};
const onConfirmDelete = () => {
//
data.value.splice(deleteIdx.value, 1);
pagination.value.total = data.value.length;
const selectedIdx = selectedRowKeys.value.indexOf(deleteIdx.value);
if (selectedIdx > -1) {
selectedRowKeys.value.splice(selectedIdx, 1);
}
confirmVisible.value = false;
MessagePlugin.success('删除成功');
resetIdx();
};
const onCancel = () => {
resetIdx();
};
const rowKey = 'index';
const rehandleSelectChange = (val: number[]) => {
selectedRowKeys.value = val;
};
const rehandlePageChange = (curr, pageInfo) => {
console.log('分页变化', curr, pageInfo);
};
const rehandleChange = (changeParams, triggerAndData) => {
console.log('统一Change', changeParams, triggerAndData);
};
const handleClickDetail = () => {
router.push('/detail/base');
};
const handleSetupContract = () => {
router.push('/form/base');
};
const handleClickDelete = (row: { rowIndex: any }) => {
deleteIdx.value = row.rowIndex;
confirmVisible.value = true;
};
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '@/style/variables'; @import '@/style/variables';

View File

@ -8,8 +8,8 @@
</t-form-item> </t-form-item>
<t-form-item label="产品状态" name="status"> <t-form-item label="产品状态" name="status">
<t-radio-group v-model="formData.status"> <t-radio-group v-model="formData.status">
<t-radio value="0"> 已停用 </t-radio> <t-radio value="0">已停用</t-radio>
<t-radio value="1"> 已启用 </t-radio> <t-radio value="1">已启用</t-radio>
</t-radio-group> </t-radio-group>
</t-form-item> </t-form-item>
<t-form-item label="产品描述" name="description"> <t-form-item label="产品描述" name="description">
@ -26,16 +26,16 @@
<t-textarea v-model="textareaValue" :style="{ width: '480px' }" placeholder="请输入内容" name="description" /> <t-textarea v-model="textareaValue" :style="{ width: '480px' }" placeholder="请输入内容" name="description" />
</t-form-item> </t-form-item>
<t-form-item style="float: right"> <t-form-item style="float: right">
<t-button variant="outline" @click="onClickCloseBtn"> 取消 </t-button> <t-button variant="outline" @click="onClickCloseBtn">取消</t-button>
<t-button theme="primary" type="submit"> 确定 </t-button> <t-button theme="primary" type="submit">确定</t-button>
</t-form-item> </t-form-item>
</t-form> </t-form>
</template> </template>
</t-dialog> </t-dialog>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next'; import { MessagePlugin } from 'tdesign-vue-next';
const INITIAL_DATA = { const INITIAL_DATA = {
@ -53,72 +53,61 @@ const SELECT_OPTIONS = [
{ label: 'CVM', value: '3' }, { label: 'CVM', value: '3' },
]; ];
export default defineComponent({ const props = defineProps({
props: { visible: {
visible: { type: Boolean,
type: Boolean, default: false,
default: false,
},
data: {
type: Object,
default: () => {
return {};
},
},
}, },
setup(props, ctx) { data: {
const formVisible = ref(false); type: Object,
const formData = ref(props.data); default: () => {
const textareaValue = ref(''); return {};
},
const onSubmit = ({ result, firstError }) => {
if (!firstError) {
MessagePlugin.success('提交成功');
formVisible.value = false;
} else {
console.log('Errors: ', result);
MessagePlugin.warning(firstError);
}
};
const onClickCloseBtn = () => {
formVisible.value = false;
formData.value = { ...INITIAL_DATA };
};
watch(
() => formVisible.value,
(val) => {
const { emit } = ctx;
emit('update:visible', val);
},
);
watch(
() => props.visible,
(val) => {
formVisible.value = val;
},
);
watch(
() => props.data,
(val) => {
formData.value = val;
},
);
return {
SELECT_OPTIONS,
formVisible,
formData,
textareaValue,
onSubmit,
onClickCloseBtn,
rules: {
name: [{ required: true, message: '请输入产品名称', type: 'error' }],
},
};
}, },
}); });
const formVisible = ref(false);
const formData = ref(props.data);
const textareaValue = ref('');
const onSubmit = ({ result, firstError }) => {
if (!firstError) {
MessagePlugin.success('提交成功');
formVisible.value = false;
} else {
console.log('Errors: ', result);
MessagePlugin.warning(firstError);
}
};
const onClickCloseBtn = () => {
formVisible.value = false;
formData.value = { ...INITIAL_DATA };
};
const emit = defineEmits(['update:visible']);
watch(
() => formVisible.value,
(val) => {
emit('update:visible', val);
},
);
watch(
() => props.visible,
(val) => {
formVisible.value = val;
},
);
watch(
() => props.data,
(val) => {
formData.value = val;
},
);
const rules = {
name: [{ required: true, message: '请输入产品名称', type: 'error' }],
};
</script> </script>

View File

@ -2,11 +2,13 @@
<div> <div>
<div class="list-card-operation"> <div class="list-card-operation">
<t-button @click="formDialogVisible = true"> 新建产品 </t-button> <t-button @click="formDialogVisible = true"> 新建产品 </t-button>
<t-input v-model="searchValue" class="search-input" placeholder="请输入你需要搜索的内容" clearable> <div class="search-input">
<template #suffix-icon> <t-input v-model="searchValue" placeholder="请输入你需要搜索的内容" clearable>
<search-icon v-if="searchValue === ''" size="20px" /> <template #suffix-icon>
</template> <search-icon v-if="searchValue === ''" size="20px" />
</t-input> </template>
</t-input>
</div>
</div> </div>
<dialog-form v-model:visible="formDialogVisible" :data="formData" /> <dialog-form v-model:visible="formDialogVisible" :data="formData" />
@ -58,11 +60,11 @@
/> />
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, ref, computed, onMounted } from 'vue'; import { ref, computed, onMounted } from 'vue';
import { SearchIcon } from 'tdesign-icons-vue-next'; import { SearchIcon } from 'tdesign-icons-vue-next';
import { MessagePlugin } from 'tdesign-vue-next'; import { MessagePlugin } from 'tdesign-vue-next';
import Card from './components/Card.vue'; import Card from '@/components/card/Card.vue';
import DialogForm from './components/DialogForm.vue'; import DialogForm from './components/DialogForm.vue';
import request from '@/utils/request'; import request from '@/utils/request';
import { ResDataType } from '@/interface'; import { ResDataType } from '@/interface';
@ -76,88 +78,68 @@ const INITIAL_DATA = {
amount: 0, amount: 0,
}; };
export default defineComponent({ const pagination = ref({ current: 1, pageSize: 12, total: 0 });
name: 'ListBase', const deleteProduct = ref(undefined);
components: {
SearchIcon,
Card,
DialogForm,
},
setup() {
const pagination = ref({ current: 1, pageSize: 12, total: 0 });
const deleteProduct = ref(undefined);
const productList = ref([]); const productList = ref([]);
const dataLoading = ref(true); const dataLoading = ref(true);
const fetchData = async () => { const fetchData = async () => {
try { try {
const res: ResDataType = await request.get('/api/get-card-list'); const res: ResDataType = await request.get('/api/get-card-list');
if (res.code === 0) { if (res.code === 0) {
const { list = [] } = res.data; const { list = [] } = res.data;
productList.value = list; productList.value = list;
pagination.value = { pagination.value = {
...pagination.value, ...pagination.value,
total: list.length, total: list.length,
}; };
} }
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} finally { } finally {
dataLoading.value = false; dataLoading.value = false;
} }
}; };
const confirmBody = computed(() => const confirmBody = computed(() =>
deleteProduct.value ? `确认删除后${deleteProduct.value.name}的所有产品信息将被清空, 且无法恢复` : '', deleteProduct.value ? `确认删除后${deleteProduct.value.name}的所有产品信息将被清空, 且无法恢复` : '',
); );
onMounted(() => { onMounted(() => {
fetchData(); fetchData();
});
const formDialogVisible = ref(false);
const searchValue = ref('');
const confirmVisible = ref(false);
const formData = ref({ ...INITIAL_DATA });
return {
pagination,
productList,
dataLoading,
formDialogVisible,
confirmBody,
searchValue,
confirmVisible,
formData,
onPageSizeChange(size: number) {
pagination.value.pageSize = size;
pagination.value.current = 1;
},
onCurrentChange(current: number) {
pagination.value.current = current;
},
handleDeleteItem(product) {
confirmVisible.value = true;
deleteProduct.value = product;
},
onConfirmDelete() {
const { index } = deleteProduct.value;
productList.value.splice(index - 1, 1);
confirmVisible.value = false;
MessagePlugin.success('删除成功');
},
onCancel() {
deleteProduct.value = undefined;
formData.value = { ...INITIAL_DATA };
},
handleManageProduct(product) {
formDialogVisible.value = true;
formData.value = { ...product, status: product?.isSetup ? '1' : '0' };
},
};
},
}); });
const formDialogVisible = ref(false);
const searchValue = ref('');
const confirmVisible = ref(false);
const formData = ref({ ...INITIAL_DATA });
const onPageSizeChange = (size: number) => {
pagination.value.pageSize = size;
pagination.value.current = 1;
};
const onCurrentChange = (current: number) => {
pagination.value.current = current;
};
const handleDeleteItem = (product) => {
confirmVisible.value = true;
deleteProduct.value = product;
};
const onConfirmDelete = () => {
const { index } = deleteProduct.value;
productList.value.splice(index - 1, 1);
confirmVisible.value = false;
MessagePlugin.success('删除成功');
};
const onCancel = () => {
deleteProduct.value = undefined;
formData.value = { ...INITIAL_DATA };
};
const handleManageProduct = (product) => {
formDialogVisible.value = true;
formData.value = { ...product, status: product?.isSetup ? '1' : '0' };
};
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '@/style/variables.less'; @import '@/style/variables.less';

View File

@ -19,7 +19,7 @@
<t-form-item label="合同状态" name="status"> <t-form-item label="合同状态" name="status">
<t-select <t-select
v-model="formData.status" v-model="formData.status"
class="form-item-content`" class="form-item-content"
:options="CONTRACT_STATUS_OPTIONS" :options="CONTRACT_STATUS_OPTIONS"
placeholder="请选择合同状态" placeholder="请选择合同状态"
/> />
@ -39,7 +39,7 @@
<t-form-item label="合同类型" name="type"> <t-form-item label="合同类型" name="type">
<t-select <t-select
v-model="formData.type" v-model="formData.type"
class="form-item-content`" class="form-item-content"
:options="CONTRACT_TYPE_OPTIONS" :options="CONTRACT_TYPE_OPTIONS"
placeholder="请选择合同类型" placeholder="请选择合同类型"
/> />
@ -94,7 +94,7 @@
</t-table> </t-table>
<t-dialog <t-dialog
v-model:visible="confirmVisible" v-model:visible="confirmVisible"
header="是否确认删除该产品" header="确认删除当前所选合同?"
:body="confirmBody" :body="confirmBody"
:on-cancel="onCancel" :on-cancel="onCancel"
@confirm="onConfirmDelete" @confirm="onConfirmDelete"
@ -102,8 +102,8 @@
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, ref, computed, onMounted } from 'vue'; import { ref, computed, onMounted } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next'; import { MessagePlugin } from 'tdesign-vue-next';
import Trend from '@/components/trend/index.vue'; import Trend from '@/components/trend/index.vue';
import request from '@/utils/request'; import request from '@/utils/request';
@ -123,7 +123,6 @@ const COLUMNS = [
fixed: 'left', fixed: 'left',
minWidth: '300', minWidth: '300',
align: 'left', align: 'left',
ellipsis: true,
colKey: 'name', colKey: 'name',
}, },
{ title: '合同状态', colKey: 'status', width: 200, cell: { col: 'status' } }, { title: '合同状态', colKey: 'status', width: 200, cell: { col: 'status' } },
@ -167,115 +166,89 @@ const searchForm = {
type: '', type: '',
}; };
export default defineComponent({ const formData = ref({ ...searchForm });
name: 'ListTable', const rowKey = 'index';
components: { const verticalAlign = 'top';
Trend, const hover = true;
},
setup() {
const formData = ref({ ...searchForm });
const tableConfig = {
rowKey: 'index',
verticalAlign: 'top',
hover: true,
};
const pagination = ref({
defaultPageSize: 20,
total: 100,
defaultCurrent: 1,
});
const confirmVisible = ref(false);
const data = ref([]); const pagination = ref({
defaultPageSize: 20,
const dataLoading = ref(false); total: 100,
const fetchData = async () => { defaultCurrent: 1,
dataLoading.value = true;
try {
const res: ResDataType = await request.get('/api/get-list');
if (res.code === 0) {
const { list = [] } = res.data;
data.value = list;
pagination.value = {
...pagination.value,
total: list.length,
};
}
} catch (e) {
console.log(e);
} finally {
dataLoading.value = false;
}
};
const deleteIdx = ref(-1);
const confirmBody = computed(() => {
if (deleteIdx.value > -1) {
const { no, name } = data.value[deleteIdx.value];
return `产品编号:${no}, 产品名称: ${name}`;
}
return '';
});
const resetIdx = () => {
deleteIdx.value = -1;
};
const onConfirmDelete = () => {
//
data.value.splice(deleteIdx.value - 1, 1);
pagination.value.total = data.value.length;
confirmVisible.value = false;
MessagePlugin.success('删除成功');
resetIdx();
};
const onCancel = () => {
resetIdx();
};
onMounted(() => {
fetchData();
});
return {
data,
COLUMNS,
CONTRACT_STATUS,
CONTRACT_STATUS_OPTIONS,
CONTRACT_TYPES,
CONTRACT_TYPE_OPTIONS,
CONTRACT_PAYMENT_TYPES,
formData,
pagination,
confirmVisible,
confirmBody,
...tableConfig,
onConfirmDelete,
onCancel,
dataLoading,
handleClickDelete({ row }) {
deleteIdx.value = row.index;
confirmVisible.value = true;
},
onReset(val) {
console.log(val);
},
onSubmit(val) {
console.log(val);
},
rehandlePageChange(curr, pageInfo) {
console.log('分页变化', curr, pageInfo);
},
rehandleChange(changeParams, triggerAndData) {
console.log('统一Change', changeParams, triggerAndData);
},
rehandleClickOp({ text, row }) {
console.log(text, row);
},
};
},
}); });
const confirmVisible = ref(false);
const data = ref([]);
const dataLoading = ref(false);
const fetchData = async () => {
dataLoading.value = true;
try {
const res: ResDataType = await request.get('/api/get-list');
if (res.code === 0) {
const { list = [] } = res.data;
data.value = list;
pagination.value = {
...pagination.value,
total: list.length,
};
}
} catch (e) {
console.log(e);
} finally {
dataLoading.value = false;
}
};
const deleteIdx = ref(-1);
const confirmBody = computed(() => {
if (deleteIdx.value > -1) {
const { name } = data.value[deleteIdx.value];
return `删除后,${name}的所有合同信息将被清空,且无法恢复`;
}
return '';
});
const resetIdx = () => {
deleteIdx.value = -1;
};
const onConfirmDelete = () => {
//
data.value.splice(deleteIdx.value, 1);
pagination.value.total = data.value.length;
confirmVisible.value = false;
MessagePlugin.success('删除成功');
resetIdx();
};
const onCancel = () => {
resetIdx();
};
onMounted(() => {
fetchData();
});
const handleClickDelete = ({ row }) => {
deleteIdx.value = row.rowIndex;
confirmVisible.value = true;
};
const onReset = (val) => {
console.log(val);
};
const onSubmit = (val) => {
console.log(val);
};
const rehandlePageChange = (curr, pageInfo) => {
console.log('分页变化', curr, pageInfo);
};
const rehandleChange = (changeParams, triggerAndData) => {
console.log('统一Change', changeParams, triggerAndData);
};
const rehandleClickOp = ({ text, row }) => {
console.log(text, row);
};
</script> </script>
<style lang="less"> <style lang="less">

View File

@ -1,14 +1,6 @@
<template> <template>
<common-table /> <common-table />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue';
import CommonTable from '../components/CommonTable.vue'; import CommonTable from '../components/CommonTable.vue';
export default defineComponent({
name: 'ListBase',
components: {
CommonTable,
},
});
</script> </script>

View File

@ -15,38 +15,24 @@
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, ref } from 'vue'; import { ref } from 'vue';
import { SearchIcon } from 'tdesign-icons-vue-next'; import { SearchIcon } from 'tdesign-icons-vue-next';
import { prefix } from '@/config/global';
import { TREE_DATA } from './constants'; import { TREE_DATA } from './constants';
import CommonTable from '../components/CommonTable.vue'; import CommonTable from '../components/CommonTable.vue';
export default defineComponent({ const filterByText = ref();
name: 'ListTree', const filterText = ref();
components: {
SearchIcon, const expanded = ['0', '0-0', '0-1', '0-2', '0-3', '0-4'];
CommonTable,
}, const onInput = () => {
setup() { filterByText.value = (node) => {
const filterByText = ref(); const rs = node.label.indexOf(filterText.value) >= 0;
const filterText = ref(); return rs;
return { };
prefix, };
TREE_DATA,
expanded: ['0', '0-0', '0-1', '0-2', '0-3', '0-4'],
filterText,
filterByText,
onInput() {
filterByText.value = (node) => {
const rs = node.label.indexOf(filterText.value) >= 0;
return rs;
};
},
};
},
});
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '@/style/variables.less'; @import '@/style/variables.less';

View File

@ -15,34 +15,24 @@
</header> </header>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue';
import { useStore } from 'vuex';
import LogoFullIcon from '@/assets/assets-logo-full.svg?component'; import LogoFullIcon from '@/assets/assets-logo-full.svg?component';
import { useSettingStore } from '@/store';
export default defineComponent({ const settingStore = useSettingStore();
components: { LogoFullIcon }, const toggleSettingPanel = () => {
setup() { settingStore.updateConfig({
const store = useStore(); showSettingPanel: true,
const toggleSettingPanel = () => { });
store.commit('setting/toggleSettingPanel', true); };
};
const navToGitHub = () => { const navToGitHub = () => {
window.open('https://github.com/tencent/tdesign-vue-next-starter'); window.open('https://github.com/tencent/tdesign-vue-next-starter');
}; };
const navToHelper = () => { const navToHelper = () => {
window.open('http://tdesign.tencent.com/starter/docs/get-started'); window.open('http://tdesign.tencent.com/starter/docs/get-started');
}; };
return {
toggleSettingPanel,
navToGitHub,
navToHelper,
};
},
});
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -70,13 +70,15 @@
</t-form> </t-form>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, ref } from 'vue'; import { ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useStore } from 'vuex';
import QrcodeVue from 'qrcode.vue'; import QrcodeVue from 'qrcode.vue';
import { MessagePlugin } from 'tdesign-vue-next'; import { MessagePlugin } from 'tdesign-vue-next';
import { useCounter } from '@/hooks'; import { useCounter } from '@/hooks';
import { useUserStore } from '@/store';
const userStore = useUserStore();
const INITIAL_DATA = { const INITIAL_DATA = {
phone: '', phone: '',
@ -93,48 +95,32 @@ const FORM_RULES = {
verifyCode: [{ required: true, message: '验证码必填', type: 'error' }], verifyCode: [{ required: true, message: '验证码必填', type: 'error' }],
}; };
export default defineComponent({ const type = ref('password');
components: { QrcodeVue },
setup() {
const type = ref('password');
const formData = ref({ ...INITIAL_DATA }); const formData = ref({ ...INITIAL_DATA });
const showPsw = ref(false); const showPsw = ref(false);
const [countDown, handleCounter] = useCounter(); const [countDown, handleCounter] = useCounter();
const switchType = (val: string) => { const switchType = (val: string) => {
type.value = val; type.value = val;
}; };
const router = useRouter(); const router = useRouter();
const store = useStore();
const onSubmit = async ({ validateResult }) => { const onSubmit = async ({ validateResult }) => {
if (validateResult === true) { if (validateResult === true) {
try { try {
await store.dispatch('user/login', formData.value); await userStore.login(formData.value);
MessagePlugin.success('登陆成功');
router.push({
path: '/dashboard/base',
});
} catch (e) {
console.log(e);
MessagePlugin.error(e.message);
}
}
};
return { MessagePlugin.success('登陆成功');
FORM_RULES, router.push({
formData, path: '/dashboard/base',
showPsw, });
type, } catch (e) {
switchType, console.log(e);
countDown, MessagePlugin.error(e.message);
handleCounter, }
onSubmit, }
}; };
},
});
</script> </script>

View File

@ -70,8 +70,8 @@
</t-form> </t-form>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, ref } from 'vue'; import { ref } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next'; import { MessagePlugin } from 'tdesign-vue-next';
import { useCounter } from '@/hooks'; import { useCounter } from '@/hooks';
@ -93,47 +93,30 @@ const FORM_RULES = {
verifyCode: [{ required: true, message: '验证码必填', type: 'error' }], verifyCode: [{ required: true, message: '验证码必填', type: 'error' }],
}; };
export default defineComponent({ const type = ref('phone');
setup(props, ctx) {
const type = ref('phone');
const emailOptions = ref([]);
const form = ref(); const form = ref();
const formData = ref({ ...INITIAL_DATA }); const formData = ref({ ...INITIAL_DATA });
const showPsw = ref(false); const showPsw = ref(false);
const [countDown, handleCounter] = useCounter(); const [countDown, handleCounter] = useCounter();
const onSubmit = ({ validateResult }) => { const emit = defineEmits(['registerSuccess']);
if (validateResult === true) {
if (!formData.value.checked) {
MessagePlugin.error('请同意TDesign服务协议和TDesign 隐私声明');
return;
}
MessagePlugin.success('注册成功');
const { emit } = ctx;
emit('registerSuccess');
}
};
const switchType = (val) => { const onSubmit = ({ validateResult }) => {
form.value.reset(); if (validateResult === true) {
type.value = val; if (!formData.value.checked) {
}; MessagePlugin.error('请同意TDesign服务协议和TDesign 隐私声明');
return;
}
MessagePlugin.success('注册成功');
emit('registerSuccess');
}
};
return { const switchType = (val) => {
FORM_RULES, form.value.reset();
formData, type.value = val;
showPsw, };
form,
type,
emailOptions,
countDown,
handleCounter,
onSubmit,
switchType,
};
},
});
</script> </script>

View File

@ -19,7 +19,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background-size: cover; background-size: cover;
background-position: 50%; background-position: 100%;
position: relative; position: relative;
} }

View File

@ -22,34 +22,18 @@
<footer class="copyright">Copyright @ 2021-2022 Tencent. All Rights Reserved</footer> <footer class="copyright">Copyright @ 2021-2022 Tencent. All Rights Reserved</footer>
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, ref } from 'vue'; import { ref } from 'vue';
import Login from './components/Login.vue'; import Login from './components/Login.vue';
import Register from './components/Register.vue'; import Register from './components/Register.vue';
import LoginHeader from './components/Header.vue'; import LoginHeader from './components/Header.vue';
import TdesignSetting from '@/layouts/setting.vue'; import TdesignSetting from '@/layouts/setting.vue';
export default defineComponent({ const type = ref('login');
name: 'LoginIndex', const switchType = (val: string) => {
components: { type.value = val;
Login, };
Register,
LoginHeader,
TdesignSetting,
},
setup() {
const type = ref('login');
const switchType = (val: string) => {
type.value = val;
};
return {
type,
switchType,
};
},
});
</script> </script>
<style lang="less"> <style lang="less">
@import url('./index.less'); @import url('./index.less');

View File

@ -4,12 +4,6 @@
</result> </result>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue';
import Result from '@/components/result/index.vue'; import Result from '@/components/result/index.vue';
export default defineComponent({
name: 'Result403',
components: { Result },
});
</script> </script>

View File

@ -4,12 +4,6 @@
</result> </result>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue';
import Result from '@/components/result/index.vue'; import Result from '@/components/result/index.vue';
export default defineComponent({
name: 'Result404',
components: { Result },
});
</script> </script>

View File

@ -4,12 +4,6 @@
</result> </result>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue';
import Result from '@/components/result/index.vue'; import Result from '@/components/result/index.vue';
export default defineComponent({
name: 'Result500',
components: { Result },
});
</script> </script>

View File

@ -19,15 +19,9 @@
</result> </result>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue';
import Result from '@/components/result/index.vue'; import Result from '@/components/result/index.vue';
import Thumbnail from '@/components/thumbnail/index.vue'; import Thumbnail from '@/components/thumbnail/index.vue';
export default defineComponent({
name: 'ResultBrowserIncompatible',
components: { Result, Thumbnail },
});
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '@/style/variables.less'; @import '@/style/variables.less';

View File

@ -4,20 +4,12 @@
<div class="result-success-title">项目创建失败</div> <div class="result-success-title">项目创建失败</div>
<div class="result-success-describe">企业微信联系检查创建者权限或返回修改</div> <div class="result-success-describe">企业微信联系检查创建者权限或返回修改</div>
<div> <div>
<t-button theme="default" @click="() => $router.push('/form/base')">返回首页</t-button> <t-button @click="() => $router.push('/form/base')">返回修改</t-button>
<t-button @click="() => $router.push('/form/base')"> 返回修改 </t-button> <t-button theme="default" @click="() => $router.push('/dashboard/base')">返回首页</t-button>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'ResultSuccess',
});
</script>
<style lang="less" scoped> <style lang="less" scoped>
@import '@/style/variables.less'; @import '@/style/variables.less';

View File

@ -7,12 +7,6 @@
</result> </result>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue';
import Result from '@/components/result/index.vue'; import Result from '@/components/result/index.vue';
export default defineComponent({
name: 'ResultNetworkError',
components: { Result },
});
</script> </script>

View File

@ -10,14 +10,6 @@
</div> </div>
</template> </template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'ResultSuccess',
});
</script>
<style lang="less" scoped> <style lang="less" scoped>
@import '@/style/variables.less'; @import '@/style/variables.less';

View File

@ -89,13 +89,13 @@
</t-col> </t-col>
</t-row> </t-row>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, nextTick, onMounted, onUnmounted, watch } from 'vue'; import { nextTick, onMounted, onUnmounted, watch, computed } from 'vue';
import { useStore } from 'vuex';
import * as echarts from 'echarts/core'; import * as echarts from 'echarts/core';
import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components'; import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components';
import { LineChart } from 'echarts/charts'; import { LineChart } from 'echarts/charts';
import { CanvasRenderer } from 'echarts/renderers'; import { CanvasRenderer } from 'echarts/renderers';
import { useSettingStore } from '@/store';
import { LAST_7_DAYS } from '@/utils/date'; import { LAST_7_DAYS } from '@/utils/date';
import { USER_INFO_LIST, TEAM_MEMBERS, PRODUCT_LIST } from './constants'; import { USER_INFO_LIST, TEAM_MEMBERS, PRODUCT_LIST } from './constants';
@ -109,84 +109,68 @@ import Card from '@/components/card/index.vue';
echarts.use([GridComponent, TooltipComponent, LineChart, CanvasRenderer, LegendComponent]); echarts.use([GridComponent, TooltipComponent, LineChart, CanvasRenderer, LegendComponent]);
export default defineComponent({ let lineContainer: HTMLElement;
components: { let lineChart: echarts.ECharts;
Card, const store = useSettingStore();
}, const chartColors = computed(() => store.chartColors);
setup() {
let lineContainer: HTMLElement;
let lineChart: echarts.ECharts;
const store = useStore();
const { chartColors } = store.state.setting;
const onLineChange = (value) => { const onLineChange = (value) => {
lineChart.setOption(getFolderLineDataSet(value)); lineChart.setOption(getFolderLineDataSet(value));
}; };
const initChart = () => { const initChart = () => {
lineContainer = document.getElementById('lineContainer'); lineContainer = document.getElementById('lineContainer');
lineChart = echarts.init(lineContainer); lineChart = echarts.init(lineContainer);
lineChart.setOption({ lineChart.setOption({
grid: { grid: {
x: 30, // 80px x: 30, // 80px
y: 30, // 60px y: 30, // 60px
x2: 10, // 80px x2: 10, // 80px
y2: 30, // 60px y2: 30, // 60px
}, },
...getFolderLineDataSet({ ...chartColors }), ...getFolderLineDataSet({ ...chartColors.value }),
}); });
}; };
const updateContainer = () => { const updateContainer = () => {
lineChart?.resize({ lineChart?.resize({
width: lineContainer.clientWidth, width: lineContainer.clientWidth,
height: lineContainer.clientHeight, height: lineContainer.clientHeight,
}); });
}; };
onMounted(() => { onMounted(() => {
nextTick(() => { nextTick(() => {
initChart(); initChart();
}); });
window.addEventListener('resize', updateContainer, false); window.addEventListener('resize', updateContainer, false);
});
onUnmounted(() => {
window.removeEventListener('resize', updateContainer);
});
const getIcon = (type) => {
switch (type) {
case 'a':
return ProductAIcon;
case 'b':
return ProductBIcon;
case 'c':
return ProductCIcon;
case 'd':
return ProductDIcon;
default:
return ProductAIcon;
}
};
watch(
() => store.state.setting.brandTheme,
() => {
changeChartsTheme([lineChart]);
},
);
return {
LAST_7_DAYS,
USER_INFO_LIST,
TEAM_MEMBERS,
PRODUCT_LIST,
onLineChange,
getIcon,
};
},
}); });
onUnmounted(() => {
window.removeEventListener('resize', updateContainer);
});
const getIcon = (type) => {
switch (type) {
case 'a':
return ProductAIcon;
case 'b':
return ProductBIcon;
case 'c':
return ProductCIcon;
case 'd':
return ProductDIcon;
default:
return ProductAIcon;
}
};
watch(
() => store.brandTheme,
() => {
changeChartsTheme([lineChart]);
},
);
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import url('./index.less'); @import url('./index.less');

View File

@ -2,42 +2,47 @@ import { MessagePlugin } from 'tdesign-vue-next';
import NProgress from 'nprogress'; // progress bar import NProgress from 'nprogress'; // progress bar
import 'nprogress/nprogress.css'; // progress bar style import 'nprogress/nprogress.css'; // progress bar style
import store from '@/store'; import { getPermissionStore, getUserStore } from '@/store';
import router from '@/router'; import router from '@/router';
const permissionStore = getPermissionStore();
const userStore = getUserStore();
NProgress.configure({ showSpinner: false }); NProgress.configure({ showSpinner: false });
const whiteListRouters = store.getters['permission/whiteListRouters']; const { whiteListRouters } = permissionStore;
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
NProgress.start(); NProgress.start();
const token = store.getters['user/token']; const { token } = userStore;
if (token) { if (token) {
if (to.path === '/login') { if (to.path === '/login') {
setTimeout(() => { userStore.logout();
store.dispatch('user/logout'); permissionStore.restore();
store.dispatch('permission/restore');
});
next(); next();
return; return;
} }
const roles = store.getters['user/roles']; const { roles } = userStore;
if (roles && roles.length > 0) { if (roles && roles.length > 0) {
next(); next();
} else { } else {
try { try {
await store.dispatch('user/getUserInfo'); await userStore.getUserInfo();
await store.dispatch('permission/initRoutes', store.getters['user/roles']); const { roles } = userStore;
next({ ...to }); await permissionStore.initRoutes(roles);
if (router.hasRoute(to.name)) {
next();
} else {
next(`/`);
}
} catch (error) { } catch (error) {
console.log(error);
MessagePlugin.error(error); MessagePlugin.error(error);
await store.commit('user/removeToken');
next(`/login?redirect=${to.path}`); next(`/login?redirect=${to.path}`);
NProgress.done(); NProgress.done();
} }

View File

@ -4,6 +4,8 @@ import baseRouters from './modules/base';
import componentsRouters from './modules/components'; import componentsRouters from './modules/components';
import othersRouters from './modules/others'; import othersRouters from './modules/others';
// 关于单层路由meta 中设置 { single: true } 即可为单层路由,{ hidden: true } 即可在侧边栏隐藏该路由
// 存放动态路由 // 存放动态路由
export const asyncRouterList: Array<RouteRecordRaw> = [...baseRouters, ...componentsRouters, ...othersRouters]; export const asyncRouterList: Array<RouteRecordRaw> = [...baseRouters, ...componentsRouters, ...othersRouters];
@ -19,17 +21,18 @@ const defaultRouterList: Array<RouteRecordRaw> = [
redirect: '/dashboard/base', redirect: '/dashboard/base',
component: () => import('@/layouts/blank.vue'), component: () => import('@/layouts/blank.vue'),
}, },
{
path: '/:w+',
name: '404Page',
redirect: '/result/404',
},
]; ];
export const page404 = { export const allRoutes = [...defaultRouterList, ...asyncRouterList];
path: '/:w+',
name: '404Page',
redirect: '/result/404',
};
const router = createRouter({ const router = createRouter({
history: createWebHashHistory(), history: createWebHashHistory(),
routes: defaultRouterList, routes: allRoutes,
scrollBehavior() { scrollBehavior() {
return { return {
el: '#app', el: '#app',

View File

@ -1,16 +1,12 @@
import { createStore } from 'vuex'; import { createPinia } from 'pinia';
import user from './modules/user';
import notification from './modules/notification';
import setting from './modules/setting';
import permission from './modules/permission';
export const store = createStore({ const store = createPinia();
modules: {
user, export { store };
setting,
notification, export * from './modules/notification';
permission, export * from './modules/permission';
}, export * from './modules/user';
}); export * from './modules/setting';
export default store; export default store;

View File

@ -1,85 +1,76 @@
// 定义的state初始值 import { defineStore } from 'pinia';
import { NotificationItem } from '@/interface'; import { NotificationItem } from '@/interface';
const state = { const msgData = [
msgData: [ {
{ id: '123',
id: '123', content: '腾讯大厦一楼改造施工项目 已通过审核!',
content: '腾讯大厦一楼改造施工项目 已通过审核!', type: '合同动态',
type: '合同动态', status: true,
status: true, collected: false,
collected: false, date: '2021-01-01 08:00',
date: '2021-01-01 08:00', quality: 'high',
quality: 'high',
},
{
id: '124',
content: '三季度生产原材料采购项目 开票成功!',
type: '票务动态',
status: true,
collected: false,
date: '2021-01-01 08:00',
quality: 'low',
},
{
id: '125',
content: '2021-01-01 10:00的【国家电网线下签约】会议即将开始请提前10分钟前往 会议室1 进行签到!',
type: '会议通知',
status: true,
collected: false,
date: '2021-01-01 08:00',
quality: 'middle',
},
{
id: '126',
content: '一季度生产原材料采购项目 开票成功!',
type: '票务动态',
status: true,
collected: false,
date: '2021-01-01 08:00',
quality: 'low',
},
{
id: '127',
content: '二季度生产原材料采购项目 开票成功!',
type: '票务动态',
status: true,
collected: false,
date: '2021-01-01 08:00',
quality: 'low',
},
{
id: '128',
content: '三季度生产原材料采购项目 开票成功!',
type: '票务动态',
status: true,
collected: false,
date: '2021-01-01 08:00',
quality: 'low',
},
],
};
type NotificationStateType = typeof state;
type MsgDataType = typeof state.msgData;
const mutations = {
setMsgData(state: NotificationStateType, data: MsgDataType) {
state.msgData = data;
}, },
}; {
id: '124',
content: '三季度生产原材料采购项目 开票成功!',
type: '票务动态',
status: true,
collected: false,
date: '2021-01-01 08:00',
quality: 'low',
},
{
id: '125',
content: '2021-01-01 10:00的【国家电网线下签约】会议即将开始请提前10分钟前往 会议室1 进行签到!',
type: '会议通知',
status: true,
collected: false,
date: '2021-01-01 08:00',
quality: 'middle',
},
{
id: '126',
content: '一季度生产原材料采购项目 开票成功!',
type: '票务动态',
status: true,
collected: false,
date: '2021-01-01 08:00',
quality: 'low',
},
{
id: '127',
content: '二季度生产原材料采购项目 开票成功!',
type: '票务动态',
status: true,
collected: false,
date: '2021-01-01 08:00',
quality: 'low',
},
{
id: '128',
content: '三季度生产原材料采购项目 开票成功!',
type: '票务动态',
status: true,
collected: false,
date: '2021-01-01 08:00',
quality: 'low',
},
];
const getters = { type MsgDataType = typeof msgData;
unreadMsg: (state: NotificationStateType) => state.msgData.filter((item: NotificationItem) => item.status),
readMsg: (state: NotificationStateType) => state.msgData.filter((item: NotificationItem) => !item.status),
};
const actions = {}; export const useNotificationStore = defineStore('notification', {
state: () => ({
export default { msgData,
namespaced: true, }),
state, getters: {
mutations, unreadMsg: (state) => state.msgData.filter((item: NotificationItem) => item.status),
actions, readMsg: (state) => state.msgData.filter((item: NotificationItem) => !item.status),
getters, },
}; actions: {
setMsgData(data: MsgDataType) {
this.msgData = data;
},
},
});

View File

@ -1,13 +1,19 @@
import router, { asyncRouterList, page404 } from '@/router'; import { defineStore } from 'pinia';
import { RouteRecordRaw } from 'vue-router';
import router, { asyncRouterList } from '@/router';
import { store } from '@/store';
function filterPermissionsRouters(routes, roles) { function filterPermissionsRouters(routes: Array<RouteRecordRaw>, roles: Array<unknown>) {
const res = []; const res = [];
const removeRoutes = [];
routes.forEach((route) => { routes.forEach((route) => {
const children = []; const children = [];
route.children?.forEach((childRouter) => { route.children?.forEach((childRouter) => {
const roleCode = childRouter.meta?.roleCode || childRouter.name; const roleCode = childRouter.meta?.roleCode || childRouter.name;
if (roles.indexOf(roleCode) !== -1) { if (roles.indexOf(roleCode) !== -1) {
children.push(childRouter); children.push(childRouter);
} else {
removeRoutes.push(childRouter);
} }
}); });
if (children.length > 0) { if (children.length > 0) {
@ -15,60 +21,46 @@ function filterPermissionsRouters(routes, roles) {
res.push(route); res.push(route);
} }
}); });
return res; return { accessedRouters: res, removeRoutes };
} }
const state = { export const usePermissionStore = defineStore('permission', {
whiteListRouters: ['/login'], state: () => ({
routers: [], whiteListRouters: ['/login'],
}; routers: [],
removeRoutes: [],
}),
actions: {
async initRoutes(roles: Array<unknown>) {
let accessedRouters = [];
const mutations = { let removeRoutes = [];
setRouters: (state, routers) => { // special token
state.routers = routers; if (roles.includes('all')) {
accessedRouters = asyncRouterList;
} else {
const res = filterPermissionsRouters(asyncRouterList, roles);
accessedRouters = res.accessedRouters;
removeRoutes = res.removeRoutes;
}
this.routers = accessedRouters;
this.removeRoutes = removeRoutes;
removeRoutes.forEach((item: RouteRecordRaw) => {
if (router.hasRoute(item.name)) {
router.removeRoute(item.name);
}
});
},
async restore() {
this.removeRoutes.forEach((item: RouteRecordRaw) => {
router.addRoute(item);
});
},
}, },
}; });
const getters = { export function getPermissionStore() {
routers: (state) => { return usePermissionStore(store);
return state.routers; }
},
whiteListRouters: (state) => {
return state.whiteListRouters;
},
};
const actions = {
async initRoutes({ commit }, roles) {
let accessedRouters;
// special token
if (roles.includes('ALL_ROUTERS')) {
accessedRouters = asyncRouterList;
} else {
accessedRouters = filterPermissionsRouters(asyncRouterList, roles);
}
commit('setRouters', accessedRouters);
// register routers
state.routers.concat(page404).forEach((item) => {
router.addRoute(item);
});
},
async restore({ commit, state }) {
// remove routers
state.routers.concat(page404).forEach((item) => {
router.removeRoute(item.name);
});
commit('setRouters', []);
},
};
export default {
namespaced: true,
state,
mutations,
actions,
getters,
};

View File

@ -1,7 +1,8 @@
import { defineStore } from 'pinia';
import { COLOR_TOKEN, LIGHT_CHART_COLORS, DARK_CHART_COLORS } from '@/config/color';
import STYLE_CONFIG from '@/config/style'; import STYLE_CONFIG from '@/config/style';
import { COLOR_TOKEN, ColorSeries, ColorToken, LIGHT_CHART_COLORS, DARK_CHART_COLORS } from '@/config/color'; import { store } from '@/store';
// 定义的state初始值
const state = { const state = {
...STYLE_CONFIG, ...STYLE_CONFIG,
showSettingPanel: false, showSettingPanel: false,
@ -9,102 +10,62 @@ const state = {
chartColors: LIGHT_CHART_COLORS, chartColors: LIGHT_CHART_COLORS,
}; };
type IInitStateType = typeof state; export type TState = typeof state;
interface IStateType extends IInitStateType { export const useSettingStore = defineStore('setting', {
isAsideFooter: boolean; state: () => state,
showSettingPanel: boolean; getters: {
} showSidebar: (state) => state.layout !== 'top',
showSidebarLogo: (state) => state.layout === 'side',
const mutations = { showHeaderLogo: (state) => state.layout !== 'side',
update(state: IStateType, payload: IStateType) { displayMode: (state) => {
state.showBreadcrumb = payload.showBreadcrumb; if (state.mode === 'auto') {
state.mode = payload.mode; const media = window.matchMedia('(prefers-color-scheme:dark)');
state.layout = payload.layout; if (media.matches) {
state.isSidebarCompact = payload.isSidebarCompact; return 'dark';
state.splitMenu = payload.splitMenu; }
state.isFooterAside = payload.isFooterAside; return 'light';
state.isSidebarFixed = payload.isSidebarFixed;
state.isHeaderFixed = payload.isHeaderFixed;
state.showHeader = payload.showHeader;
state.backgroundTheme = payload.backgroundTheme;
state.brandTheme = payload.brandTheme;
},
toggleSidebarCompact(state: IStateType) {
state.isSidebarCompact = !state.isSidebarCompact;
},
showSidebarCompact(state: IStateType, payload: boolean) {
state.isSidebarCompact = payload;
},
toggleSettingPanel(state: IStateType, payload: boolean) {
state.showSettingPanel = payload;
},
addColor(state: IStateType, payload: ColorSeries) {
state.colorList = { ...state.colorList, ...payload };
},
changeChartColor(state: IStateType, payload: ColorToken) {
state.chartColors = { ...payload };
},
};
const getters = {
showHeader: (state: IStateType) => state.showHeader,
showSidebar: (state: IStateType) => state.layout !== 'top',
showSidebarLogo: (state: IStateType) => state.layout === 'side',
showHeaderLogo: (state: IStateType) => state.layout !== 'side',
showFooter: (state: IStateType) => state.showFooter,
mode: (state: IStateType) => {
if (state.mode === 'auto') {
const media = window.matchMedia('(prefers-color-scheme:dark)');
if (media.matches) {
return 'dark';
} }
return 'light'; return state.mode;
} },
return state.mode;
}, },
}; actions: {
async changeMode(mode: 'dark' | 'light' | 'auto') {
let theme = mode;
const actions = { if (mode === 'auto') {
async changeTheme({ commit, dispatch }, payload) { const media = window.matchMedia('(prefers-color-scheme:dark)');
dispatch('changeMode', payload); if (media.matches) {
dispatch('changeBrandTheme', payload); theme = 'dark';
commit('update', payload); } else {
}, theme = 'light';
async changeMode({ commit, state }, payload: IStateType) { }
let theme = payload.mode;
if (payload.mode === 'auto') {
const media = window.matchMedia('(prefers-color-scheme:dark)');
if (media.matches) {
theme = 'dark';
} else {
theme = 'light';
} }
} const isDarkMode = theme === 'dark';
const isDarkMode = theme === 'dark';
if (theme !== state.mode) {
document.documentElement.setAttribute('theme-mode', isDarkMode ? 'dark' : ''); document.documentElement.setAttribute('theme-mode', isDarkMode ? 'dark' : '');
}
commit('changeChartColor', isDarkMode ? DARK_CHART_COLORS : LIGHT_CHART_COLORS); this.chartColor = isDarkMode ? DARK_CHART_COLORS : LIGHT_CHART_COLORS;
},
changeBrandTheme(brandTheme: string) {
document.documentElement.setAttribute('theme-color', brandTheme);
},
updateConfig(payload: Partial<TState>) {
for (const key in payload) {
if (payload[key] !== undefined) {
this[key] = payload[key];
}
if (key === 'mode') {
this.changeMode(payload[key]);
}
if (key === 'brandTheme') {
this.changeBrandTheme(payload[key]);
}
}
},
}, },
changeBrandTheme({ state }: { state: IStateType }, payload: IStateType) { });
const { brandTheme, mode } = payload;
if (brandTheme !== state.brandTheme) {
document.documentElement.setAttribute(
'theme-color',
/^#([a-fA-F\d]{6}|[a-fA-F\d]{3})$/.test(brandTheme) && mode === 'dark' ? `${brandTheme}` : brandTheme,
);
}
},
};
export default { export function getSettingStore() {
namespaced: true, return useSettingStore(store);
state, }
mutations,
actions,
getters,
};

View File

@ -1,102 +1,86 @@
import { defineStore } from 'pinia';
import { TOKEN_NAME } from '@/config/global'; import { TOKEN_NAME } from '@/config/global';
import { store } from '@/store';
const InitUserInfo = { const InitUserInfo = {
roles: [], roles: [],
}; };
// 定义的state初始值 export const useUserStore = defineStore('user', {
const state = { state: () => ({
token: localStorage.getItem(TOKEN_NAME) || 'main_token', // 默认token不走权限 token: localStorage.getItem(TOKEN_NAME) || 'main_token', // 默认token不走权限
userInfo: InitUserInfo, userInfo: InitUserInfo,
}; }),
getters: {
const mutations = { roles: (state) => {
setToken(state, token) { return state.userInfo?.roles;
localStorage.setItem(TOKEN_NAME, token); },
state.token = token;
}, },
removeToken(state) { actions: {
localStorage.removeItem(TOKEN_NAME); async login(userInfo: Record<string, unknown>) {
state.token = ''; const mockLogin = async (userInfo: Record<string, unknown>) => {
}, // 登录请求流程
setUserInfo(state, userInfo) { console.log(userInfo);
state.userInfo = userInfo; // const { account, password } = userInfo;
}, // if (account !== 'td') {
}; // return {
// code: 401,
const getters = { // message: '账号不存在',
token: (state) => { // };
return state.token; // }
}, // if (['main_', 'dev_'].indexOf(password) === -1) {
roles: (state) => { // return {
return state.userInfo?.roles; // code: 401,
}, // message: '密码错误',
}; // };
// }
const actions = { // const token = {
async login({ commit }, userInfo) { // main_: 'main_token',
const mockLogin = async (userInfo) => { // dev_: 'dev_token',
// 登录请求流程 // }[password];
console.log(userInfo);
// const { account, password } = userInfo;
// if (account !== 'td') {
// return {
// code: 401,
// message: '账号不存在',
// };
// }
// if (['main_', 'dev_'].indexOf(password) === -1) {
// return {
// code: 401,
// message: '密码错误',
// };
// }
// const token = {
// main_: 'main_token',
// dev_: 'dev_token',
// }[password];
return {
code: 200,
message: '登陆成功',
data: 'main_token',
};
};
const res = await mockLogin(userInfo);
if (res.code === 200) {
commit('setToken', res.data);
} else {
throw res;
}
},
async getUserInfo({ commit, state }) {
const mockRemoteUserInfo = async (token) => {
if (token === 'main_token') {
return { return {
name: 'td_main', code: 200,
roles: ['ALL_ROUTERS'], message: '登陆成功',
data: 'main_token',
}; };
}
return {
name: 'td_dev',
roles: ['userIndex', 'dashboardBase', 'login'],
}; };
};
const res = await mockRemoteUserInfo(state.token); const res = await mockLogin(userInfo);
if (res.code === 200) {
this.token = res.data;
} else {
throw res;
}
},
async getUserInfo() {
const mockRemoteUserInfo = async (token: string) => {
if (token === 'main_token') {
return {
name: 'td_main',
roles: ['all'],
};
}
return {
name: 'td_dev',
roles: ['userIndex', 'dashboardBase', 'login'],
};
};
commit('setUserInfo', res); const res = await mockRemoteUserInfo(this.token);
this.userInfo = res;
},
async logout() {
localStorage.removeItem(TOKEN_NAME);
this.token = '';
this.userInfo = InitUserInfo;
},
async removeToken() {
this.token = '';
},
}, },
async logout({ commit }) { });
commit('removeToken');
commit('setUserInfo', InitUserInfo);
},
};
export default { export function getUserStore() {
namespaced: true, return useUserStore(store);
state, }
mutations,
actions,
getters,
};

View File

@ -4,7 +4,7 @@
// layout rewrite // layout rewrite
.t-layout--sider { .t-layout__sider {
width: fit-content; width: fit-content;
} }
@ -12,8 +12,7 @@
margin-left: @spacer; margin-left: @spacer;
} }
.t-layout.t-layout-has-sider { .t-layout.t-layout--with-sider {
> .t-layout { > .t-layout {
flex: 1; flex: 1;
min-width: 760px; min-width: 760px;
@ -88,7 +87,7 @@
position: fixed; position: fixed;
top: 0; top: 0;
bottom: 0; bottom: 0;
z-index: 10; z-index: 200;
transition: all 0.3s; transition: all 0.3s;
min-height: 100%; min-height: 100%;