feat: 新增登录过期提示
This commit is contained in:
parent
23e75a24a3
commit
664eee4737
4
.env
4
.env
@ -35,10 +35,10 @@ VITE_SERVICE_SUCCESS_CODE=1
|
||||
VITE_SERVICE_LOGOUT_CODES=8888,8889
|
||||
|
||||
# modal logout codes of backend service, when the code is received, the user will be logged out by displaying a modal
|
||||
VITE_SERVICE_MODAL_LOGOUT_CODES=7777,7778
|
||||
VITE_SERVICE_MODAL_LOGOUT_CODES=0
|
||||
|
||||
# token expired codes of backend service, when the code is received, it will refresh the token and resend the request
|
||||
VITE_SERVICE_EXPIRED_TOKEN_CODES=9999,9998
|
||||
|
||||
# when the route mode is static, the defined super role
|
||||
VITE_STATIC_SUPER_ROLE=R_SUPER
|
||||
VITE_STATIC_SUPER_ROLE=2
|
||||
|
90
.github/ISSUE_TEMPLATE/bug-report_cn.yaml
vendored
Normal file
90
.github/ISSUE_TEMPLATE/bug-report_cn.yaml
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
name: Bug提交
|
||||
description: 在使用软件或功能的过程中遇到了错误
|
||||
title: '[Bug]: '
|
||||
labels: [ "bug?" ]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## 请按照以下要求进行提交
|
||||
### 1. 提交后需要指定标签和截止时间。
|
||||
---
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## 环境信息
|
||||
请根据实际使用环境修改以下信息。
|
||||
|
||||
- type: input
|
||||
id: env-program-ver
|
||||
attributes:
|
||||
label: 软件版本
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: env-vm-ver
|
||||
attributes:
|
||||
label: 运行环境
|
||||
description: 选择运行软件的系统版本
|
||||
options:
|
||||
- Windows (64)
|
||||
- Windows (32/x84)
|
||||
- MacOS
|
||||
- Linux
|
||||
- Ubuntu
|
||||
- CentOS
|
||||
- ArchLinux
|
||||
- UNIX (Android)
|
||||
- 其它(请在下方说明)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: env-vm-arch
|
||||
attributes:
|
||||
label: 运行架构
|
||||
description: (可选) 选择运行软件的系统架构
|
||||
options:
|
||||
- AMD64
|
||||
- x86
|
||||
- ARM [32] (别名:AArch32 / ARMv7)
|
||||
- ARM [64] (别名:AArch64 / ARMv8)
|
||||
- 其它
|
||||
|
||||
- type: textarea
|
||||
id: reproduce-steps
|
||||
attributes:
|
||||
label: 重现步骤
|
||||
description: |
|
||||
我们需要执行哪些操作才能让 bug 出现?
|
||||
简洁清晰的重现步骤能够帮助我们更迅速地定位问题所在。
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: 期望的结果是什么?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: actual
|
||||
attributes:
|
||||
label: 实际的结果是什么?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logging
|
||||
attributes:
|
||||
label: 日志记录(可选)
|
||||
render: golang
|
||||
|
||||
- type: textarea
|
||||
id: extra-desc
|
||||
attributes:
|
||||
label: 补充说明(可选)
|
90
.github/ISSUE_TEMPLATE/bug-report_en.yaml
vendored
Normal file
90
.github/ISSUE_TEMPLATE/bug-report_en.yaml
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
name: Bug Report
|
||||
description: Encountered an error while using the software or feature
|
||||
title: '[Bug]: '
|
||||
labels: [ "bug?" ]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## Please submit according to the following requirements
|
||||
### 1. After submission, you need to specify the label and deadline.
|
||||
---
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## Environment Information
|
||||
Please modify the following information according to the actual usage environment.
|
||||
|
||||
- type: input
|
||||
id: env-program-ver
|
||||
attributes:
|
||||
label: Software Version
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: env-vm-ver
|
||||
attributes:
|
||||
label: Operating Environment
|
||||
description: Select the system version on which the software is running
|
||||
options:
|
||||
- Windows (64)
|
||||
- Windows (32/x84)
|
||||
- MacOS
|
||||
- Linux
|
||||
- Ubuntu
|
||||
- CentOS
|
||||
- ArchLinux
|
||||
- UNIX (Android)
|
||||
- Other (please specify below)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: env-vm-arch
|
||||
attributes:
|
||||
label: Operating Architecture
|
||||
description: (Optional) Select the system architecture on which the software is running
|
||||
options:
|
||||
- AMD64
|
||||
- x86
|
||||
- ARM [32] (Alias:AArch32 / ARMv7)
|
||||
- ARM [64] (Alias:AArch64 / ARMv8)
|
||||
- Other
|
||||
|
||||
- type: textarea
|
||||
id: reproduce-steps
|
||||
attributes:
|
||||
label: Reproduce Steps
|
||||
description: |
|
||||
What operations do we need to perform to make the bug appear?
|
||||
The concise and clear reproduction steps can help us locate the problem more quickly.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: What is the expected result?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: actual
|
||||
attributes:
|
||||
label: What is the actual result?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logging
|
||||
attributes:
|
||||
label: Logging (Optional)
|
||||
render: golang
|
||||
|
||||
- type: textarea
|
||||
id: extra-desc
|
||||
attributes:
|
||||
label: Additional Description (Optional)
|
50
.github/PULL_REQUEST_TEMPLATE/pr_cn.md
vendored
Normal file
50
.github/PULL_REQUEST_TEMPLATE/pr_cn.md
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
首先,感谢你的贡献! 😄
|
||||
|
||||
新特性请提交至 feature 分支,其余可提交至 main 分支。在一个维护者审核通过后合并。请确保填写以下 pull request 的信息,谢谢!~
|
||||
|
||||
[[English Template / 英文模板](./pr_en.md)]
|
||||
|
||||
### 这个变动的性质是
|
||||
|
||||
- [ ] 新特性提交
|
||||
- [ ] 日常 bug 修复
|
||||
- [ ] 站点、文档改进
|
||||
- [ ] 组件样式改进
|
||||
- [ ] TypeScript 定义更新
|
||||
- [ ] 重构
|
||||
- [ ] 代码风格优化
|
||||
- [ ] 分支合并
|
||||
- [ ] 其他改动(是关于什么的改动?)
|
||||
|
||||
### 需求背景
|
||||
|
||||
> 1. 描述相关需求的来源。
|
||||
> 2. 要解决的问题。
|
||||
> 3. 相关的 issue 讨论链接。
|
||||
|
||||
### 实现方案和 API(非新功能可选)
|
||||
|
||||
> 1. 基本的解决思路和其他可选方案。
|
||||
> 2. 列出最终的 API 实现和用法。
|
||||
> 3. 涉及 UI/交互变动需要有截图或 GIF。
|
||||
|
||||
### 对用户的影响和可能的风险(非新功能可选)
|
||||
|
||||
> 1. 这个改动对用户端是否有影响?影响的方面有哪些?
|
||||
> 2. 是否有可能隐含的 break change 和其他风险?
|
||||
|
||||
### Changelog 描述(非新功能可选)
|
||||
|
||||
> 1. 英文描述
|
||||
> 2. 中文描述(可选)
|
||||
|
||||
### 请求合并前的自查清单
|
||||
|
||||
- [ ] 文档已补充或无须补充
|
||||
- [ ] 代码演示已提供或无须提供
|
||||
- [ ] TypeScript 定义已补充或无须补充
|
||||
- [ ] Changelog 已提供或无须提供
|
||||
|
||||
### 后续计划(非新功能可选)
|
||||
|
||||
> 如果这个提交后面还有相关的其他提交和跟进信息,可以写在这里。
|
51
.github/PULL_REQUEST_TEMPLATE/pr_en.md
vendored
Normal file
51
.github/PULL_REQUEST_TEMPLATE/pr_en.md
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
First of all, thank you for your contribution! 😄
|
||||
|
||||
New feature please send pull request to feature branch, and rest to main branch. Pull request will be merged after one of collaborators approve. Please makes sure that these form are filled before submitting your pull request, thank you!
|
||||
|
||||
[[中文版模板 / Chinese template](./pr_cn.md)]
|
||||
|
||||
### This is a ...
|
||||
|
||||
- [ ] New feature
|
||||
- [ ] Bug fix
|
||||
- [ ] Site / document update
|
||||
- [ ] Component style update
|
||||
- [ ] TypeScript definition update
|
||||
- [ ] Refactoring
|
||||
- [ ] Code style optimization
|
||||
- [ ] Branch merge
|
||||
- [ ] Other (about what?)
|
||||
|
||||
### What's the background?
|
||||
|
||||
> 1. Describe the source of requirement.
|
||||
> 2. Resolve what problem.
|
||||
> 3. Related issue link.
|
||||
|
||||
### API Realization (Optional if not new feature)
|
||||
|
||||
> 1. Basic thought of solution and other optional proposal.
|
||||
> 2. List final API realization and usage sample.
|
||||
> 3. GIF or snapshot should be provided if includes UI/interactive modification.
|
||||
|
||||
### What's the effect? (Optional if not new feature)
|
||||
|
||||
> 1. Does this PR affect user? Which part will be affected?
|
||||
> 2. What will say in changelog?
|
||||
> 3. Does this PR contains potential break change or other risk?
|
||||
|
||||
### Changelog description (Optional if not new feature)
|
||||
|
||||
> 1. English description
|
||||
> 2. Chinese description (optional)
|
||||
|
||||
### Self Check before Merge
|
||||
|
||||
- [ ] Doc is updated/provided or not needed
|
||||
- [ ] Demo is updated/provided or not needed
|
||||
- [ ] TypeScript definition is updated/provided or not needed
|
||||
- [ ] Changelog is provided or not needed
|
||||
|
||||
### Additional Plan? (Optional if not new feature)
|
||||
|
||||
> If this PR related with other PR or following info. You can type here.
|
30
.github/workflows/linter.yml
vendored
Normal file
30
.github/workflows/linter.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
---
|
||||
name: Lint Code
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint All Code
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Lint Code Base
|
||||
uses: github/super-linter@v4
|
||||
env:
|
||||
VALIDATE_ALL_CODEBASE: false
|
||||
DEFAULT_BRANCH: main
|
||||
# To change branch master or main
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
FILTER_REGEX_EXCLUDE: (docs|.github)
|
||||
VALIDATE_MARKDOWN: false
|
25
.github/workflows/release.yml
vendored
Normal file
25
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
name: Release
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
|
||||
- run: npx githublogen
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
@ -9,6 +9,7 @@ import type { RouteKey, RoutePath } from '@elegant-router/types';
|
||||
import { useAuthStore } from '@/store/modules/auth';
|
||||
import { useRouteStore } from '@/store/modules/route';
|
||||
import { localStg } from '@/utils/storage';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
/**
|
||||
* create route guard
|
||||
@ -57,6 +58,9 @@ export function createRouteGuard(router: Router) {
|
||||
{
|
||||
condition: !isLogin && needLogin,
|
||||
callback: () => {
|
||||
setTimeout(() => {
|
||||
window.$message?.error?.($t('request.logoutMsg'));
|
||||
}, 500);
|
||||
next({ name: loginRoute, query: { redirect: to.fullPath } });
|
||||
}
|
||||
},
|
||||
@ -160,6 +164,13 @@ async function initRoute(to: RouteLocationNormalized): Promise<RouteLocationRaw
|
||||
return location;
|
||||
}
|
||||
|
||||
if (isLogin) {
|
||||
if (to.path !== '/pwd-login') {
|
||||
const authStore = useAuthStore();
|
||||
await authStore.getInfo();
|
||||
}
|
||||
}
|
||||
|
||||
// initialize the auth route
|
||||
await routeStore.initAuthRoute();
|
||||
|
||||
|
@ -1,9 +1,27 @@
|
||||
import { request } from '../request';
|
||||
|
||||
/** Version */
|
||||
/** Task Retry Job */
|
||||
export function fetchCardCount() {
|
||||
return request<Api.Dashboard.CardCount>({
|
||||
url: '/dashboard/task-retry-job',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/** Retry Line */
|
||||
export function fetchRetryLine(params?: Api.Dashboard.DashboardLineParams) {
|
||||
return request<Api.Dashboard.DashboardLine>({
|
||||
url: '/dashboard/retry/line',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/** Job Line */
|
||||
export function fetchJobLine(params?: Api.Dashboard.DashboardLineParams) {
|
||||
return request<Api.Dashboard.DashboardLine>({
|
||||
url: '/dashboard/job/line',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === '
|
||||
const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
|
||||
|
||||
interface InstanceState {
|
||||
/** whether the request is logout */
|
||||
isLogout: boolean;
|
||||
/** whether the request is refreshing token */
|
||||
isRefreshingToken: boolean;
|
||||
}
|
||||
@ -54,29 +56,35 @@ export const request = createFlatRequest<App.Service.Response, InstanceState>(
|
||||
|
||||
// when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page
|
||||
const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || [];
|
||||
if (logoutCodes.includes(response.data.status)) {
|
||||
if (logoutCodes.includes(response.data.status.toString())) {
|
||||
handleLogout();
|
||||
return null;
|
||||
}
|
||||
|
||||
// when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal
|
||||
const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
|
||||
if (modalLogoutCodes.includes(response.data.status)) {
|
||||
if (modalLogoutCodes.includes(response.data.status.toString())) {
|
||||
// prevent the user from refreshing the page
|
||||
window.addEventListener('beforeunload', handleLogout);
|
||||
|
||||
window.$dialog?.error({
|
||||
title: 'Error',
|
||||
content: response.data.message,
|
||||
positiveText: $t('common.confirm'),
|
||||
maskClosable: false,
|
||||
onPositiveClick() {
|
||||
logoutAndCleanup();
|
||||
},
|
||||
onClose() {
|
||||
logoutAndCleanup();
|
||||
}
|
||||
});
|
||||
// prevent repeated pop-ups
|
||||
if (!request.state.isLogout) {
|
||||
request.state.isLogout = true;
|
||||
window.$dialog?.error({
|
||||
title: 'Error',
|
||||
content: response.data.message,
|
||||
positiveText: $t('common.confirm'),
|
||||
maskClosable: false,
|
||||
onPositiveClick() {
|
||||
request.state.isLogout = false;
|
||||
logoutAndCleanup();
|
||||
},
|
||||
onClose() {
|
||||
request.state.isLogout = false;
|
||||
logoutAndCleanup();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@ -84,7 +92,7 @@ export const request = createFlatRequest<App.Service.Response, InstanceState>(
|
||||
// when the backend response code is in `expiredTokenCodes`, it means the token is expired, and refresh token
|
||||
// the api `refreshToken` can not return error code in `expiredTokenCodes`, otherwise it will be a dead loop, should return `logoutCodes` or `modalLogoutCodes`
|
||||
const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || [];
|
||||
if (expiredTokenCodes.includes(response.data.status) && !request.state.isRefreshingToken) {
|
||||
if (expiredTokenCodes.includes(response.data.status.toString()) && !request.state.isRefreshingToken) {
|
||||
request.state.isRefreshingToken = true;
|
||||
|
||||
const refreshConfig = await handleRefreshToken(response.config);
|
||||
@ -110,7 +118,7 @@ export const request = createFlatRequest<App.Service.Response, InstanceState>(
|
||||
// get backend error message and code
|
||||
if (error.code === BACKEND_ERROR_CODE) {
|
||||
message = error.response?.data?.message || message;
|
||||
backendErrorCode = error.response?.data?.status || '';
|
||||
backendErrorCode = error.response?.data?.status.toString() || '';
|
||||
}
|
||||
|
||||
// the error message is displayed in the modal
|
||||
|
@ -21,7 +21,9 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
||||
/** is super role in static route */
|
||||
const isStaticSuper = computed(() => {
|
||||
const { VITE_AUTH_ROUTE_MODE, VITE_STATIC_SUPER_ROLE } = import.meta.env;
|
||||
return VITE_AUTH_ROUTE_MODE === 'static' && userInfo.roles.includes(VITE_STATIC_SUPER_ROLE);
|
||||
return (
|
||||
VITE_AUTH_ROUTE_MODE === 'static' && userInfo.roles.map(role => role.toString()).includes(VITE_STATIC_SUPER_ROLE)
|
||||
);
|
||||
});
|
||||
|
||||
/** Is login */
|
||||
@ -103,6 +105,21 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
||||
return false;
|
||||
}
|
||||
|
||||
async function getInfo() {
|
||||
const { data: info, error } = await fetchGetUserInfo();
|
||||
|
||||
if (!error) {
|
||||
info!.userName = info?.username;
|
||||
info!.roles = [info.role];
|
||||
localStg.set('userInfo', info);
|
||||
Object.assign(userInfo, info);
|
||||
|
||||
return true;
|
||||
}
|
||||
resetStore();
|
||||
return false;
|
||||
}
|
||||
|
||||
return {
|
||||
token,
|
||||
userInfo,
|
||||
@ -110,6 +127,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
||||
isLogin,
|
||||
loginLoading,
|
||||
resetStore,
|
||||
login
|
||||
login,
|
||||
getInfo
|
||||
};
|
||||
});
|
||||
|
64
src/typings/api.d.ts
vendored
64
src/typings/api.d.ts
vendored
@ -104,6 +104,7 @@ declare namespace Api {
|
||||
* backend api module: "dashboard"
|
||||
*/
|
||||
namespace Dashboard {
|
||||
/** Task Retry Job */
|
||||
type CardCount = {
|
||||
jobTask: JobTask;
|
||||
retryTask: RetryTask;
|
||||
@ -118,8 +119,8 @@ declare namespace Api {
|
||||
};
|
||||
|
||||
type RetryTaskBarList = {
|
||||
x: string;
|
||||
taskTotal: number;
|
||||
x?: string;
|
||||
taskTotal?: number;
|
||||
};
|
||||
|
||||
type RetryTask = {
|
||||
@ -138,6 +139,65 @@ declare namespace Api {
|
||||
totalNum: number;
|
||||
successRate: number;
|
||||
};
|
||||
|
||||
/** Dashboard Line */
|
||||
type DashboardLine = {
|
||||
taskList: TaskList;
|
||||
rankList: RankList[];
|
||||
dashboardLineResponseDOList: DashboardLineResponseDOList[];
|
||||
};
|
||||
|
||||
type DashboardLineResponseDOList = {
|
||||
createDt: string;
|
||||
total: number;
|
||||
} & DashboardLineJob &
|
||||
DashboardLineRetry;
|
||||
|
||||
type DashboardLineJob = {
|
||||
createDt: string;
|
||||
total: number;
|
||||
fail: number;
|
||||
stop: number;
|
||||
cancel: number;
|
||||
success: number;
|
||||
};
|
||||
|
||||
type DashboardLineRetry = {
|
||||
createDt: string;
|
||||
total: number;
|
||||
successNum: number;
|
||||
runningNum: number;
|
||||
maxCountNum: number;
|
||||
suspendNum: number;
|
||||
};
|
||||
|
||||
type RankList = {
|
||||
name: string;
|
||||
total: string;
|
||||
};
|
||||
|
||||
type TaskList = {
|
||||
status: number;
|
||||
data: Datum[];
|
||||
page: number;
|
||||
size: number;
|
||||
total: number;
|
||||
};
|
||||
|
||||
type Datum = {
|
||||
groupName: string;
|
||||
run: number;
|
||||
total: number;
|
||||
};
|
||||
|
||||
type DashboardLineType = 'DAY' | 'WEEK' | 'MONTH' | 'YEAR' | 'OTHERS';
|
||||
|
||||
type DashboardLineParams = {
|
||||
groupName?: string;
|
||||
type: DashboardLineType;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
16
src/typings/components.d.ts
vendored
16
src/typings/components.d.ts
vendored
@ -7,6 +7,7 @@ export {}
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
ACheckableTag: typeof import('ant-design-vue/es')['CheckableTag']
|
||||
AppProvider: typeof import('./../components/common/app-provider.vue')['default']
|
||||
BetterScroll: typeof import('./../components/custom/better-scroll.vue')['default']
|
||||
ButtonIcon: typeof import('./../components/custom/button-icon.vue')['default']
|
||||
@ -16,14 +17,21 @@ declare module 'vue' {
|
||||
FullScreen: typeof import('./../components/common/full-screen.vue')['default']
|
||||
IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
|
||||
IconAntDesignReloadOutlined: typeof import('~icons/ant-design/reload-outlined')['default']
|
||||
IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']
|
||||
IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
|
||||
IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']
|
||||
IconIcRoundDelete: typeof import('~icons/ic/round-delete')['default']
|
||||
IconIcRoundPlus: typeof import('~icons/ic/round-plus')['default']
|
||||
IconIcRoundRefresh: typeof import('~icons/ic/round-refresh')['default']
|
||||
IconIcRoundSearch: typeof import('~icons/ic/round-search')['default']
|
||||
IconLocalBanner: typeof import('~icons/local/banner')['default']
|
||||
IconLocalLogo: typeof import('~icons/local/logo')['default']
|
||||
IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
|
||||
IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
|
||||
IconMdiDrag: typeof import('~icons/mdi/drag')['default']
|
||||
IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default']
|
||||
IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default']
|
||||
IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
|
||||
IconUilSearch: typeof import('~icons/uil/search')['default']
|
||||
LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
|
||||
LookForward: typeof import('./../components/custom/look-forward.vue')['default']
|
||||
@ -34,6 +42,7 @@ declare module 'vue' {
|
||||
NCard: typeof import('naive-ui')['NCard']
|
||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||
NColorPicker: typeof import('naive-ui')['NColorPicker']
|
||||
NDataTable: typeof import('naive-ui')['NDataTable']
|
||||
NDescriptions: typeof import('naive-ui')['NDescriptions']
|
||||
NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem']
|
||||
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
|
||||
@ -44,6 +53,7 @@ declare module 'vue' {
|
||||
NEmpty: typeof import('naive-ui')['NEmpty']
|
||||
NForm: typeof import('naive-ui')['NForm']
|
||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||
NFormItemGi: typeof import('naive-ui')['NFormItemGi']
|
||||
NGi: typeof import('naive-ui')['NGi']
|
||||
NGrid: typeof import('naive-ui')['NGrid']
|
||||
NInput: typeof import('naive-ui')['NInput']
|
||||
@ -56,8 +66,12 @@ declare module 'vue' {
|
||||
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
||||
NModal: typeof import('naive-ui')['NModal']
|
||||
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
|
||||
NPopconfirm: typeof import('naive-ui')['NPopconfirm']
|
||||
NPopover: typeof import('naive-ui')['NPopover']
|
||||
NProgress: typeof import('naive-ui')['NProgress']
|
||||
NRadio: typeof import('naive-ui')['NRadio']
|
||||
NRadioButton: typeof import('naive-ui')['NRadioButton']
|
||||
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
|
||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||
NSelect: typeof import('naive-ui')['NSelect']
|
||||
NSpace: typeof import('naive-ui')['NSpace']
|
||||
@ -65,10 +79,12 @@ declare module 'vue' {
|
||||
NStatistic: typeof import('naive-ui')['NStatistic']
|
||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||
NTab: typeof import('naive-ui')['NTab']
|
||||
NTabPane: typeof import('naive-ui')['NTabPane']
|
||||
NTabs: typeof import('naive-ui')['NTabs']
|
||||
NTag: typeof import('naive-ui')['NTag']
|
||||
NThing: typeof import('naive-ui')['NThing']
|
||||
NTooltip: typeof import('naive-ui')['NTooltip']
|
||||
NTree: typeof import('naive-ui')['NTree']
|
||||
PinToggler: typeof import('./../components/common/pin-toggler.vue')['default']
|
||||
ReloadButton: typeof import('./../components/common/reload-button.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
|
@ -7,13 +7,15 @@ import CardData from './modules/card-data.vue';
|
||||
import LineChart from './modules/line-chart.vue';
|
||||
import PieChart from './modules/pie-chart.vue';
|
||||
import ProjectNews from './modules/project-news.vue';
|
||||
import RetryTab from './modules/retry-tab.vue';
|
||||
import CreativityBanner from './modules/creativity-banner.vue';
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
const gap = computed(() => (appStore.isMobile ? 0 : 16));
|
||||
|
||||
const cardCount = ref<Api.Dashboard.CardCount>();
|
||||
const tabParams = ref<Api.Dashboard.DashboardLineParams>({
|
||||
type: 'WEEK'
|
||||
});
|
||||
|
||||
const getCardData = async () => {
|
||||
const { data: cardData, error } = await fetchCardCount();
|
||||
@ -28,11 +30,31 @@ getCardData();
|
||||
<template>
|
||||
<NSpace vertical :size="16">
|
||||
<HeaderBanner />
|
||||
<CardData class="h-165px" :model-value="cardCount!" />
|
||||
<CardData :model-value="cardCount!" />
|
||||
<NCard :bordered="false" class="card-wrapper">
|
||||
<div class="relative">
|
||||
<NTabs type="line" animated>
|
||||
<NTabPane name="retryTask" :tab="$t('page.home.retryTask')">
|
||||
<RetryTab :type="0" />
|
||||
</NTabPane>
|
||||
<NTabPane name="jobTask" :tab="$t('page.home.jobTask')">
|
||||
<RetryTab :type="1" />
|
||||
</NTabPane>
|
||||
</NTabs>
|
||||
<div class="absolute right-40px top-4px">
|
||||
<NRadioGroup v-model:value="tabParams.type">
|
||||
<NRadioButton value="DAY" label="今日" />
|
||||
<NRadioButton value="WEEK" label="最近一周" />
|
||||
<NRadioButton value="MONTH" label="最近一月" />
|
||||
<NRadioButton value="YEAR" label="全年" />
|
||||
</NRadioGroup>
|
||||
</div>
|
||||
</div>
|
||||
</NCard>
|
||||
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
|
||||
<NGi span="24 s:24 m:14">
|
||||
<NCard :bordered="false" class="card-wrapper">
|
||||
<LineChart :model-value="cardCount" />
|
||||
<LineChart />
|
||||
</NCard>
|
||||
</NGi>
|
||||
<NGi span="24 s:24 m:10">
|
||||
|
@ -9,10 +9,34 @@ defineOptions({
|
||||
});
|
||||
|
||||
interface Props {
|
||||
modelValue: Api.Dashboard.CardCount;
|
||||
modelValue?: Api.Dashboard.CardCount;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: () => ({
|
||||
jobTask: {
|
||||
successNum: 0,
|
||||
failNum: 0,
|
||||
cancelNum: 0,
|
||||
stopNum: 0,
|
||||
totalNum: 0,
|
||||
successRate: 0
|
||||
},
|
||||
retryTask: {
|
||||
totalNum: 0,
|
||||
runningNum: 0,
|
||||
finishNum: 0,
|
||||
maxCountNum: 0,
|
||||
suspendNum: 0
|
||||
},
|
||||
retryTaskBarList: [],
|
||||
onLineService: {
|
||||
total: 0,
|
||||
clientTotal: 0,
|
||||
serverTotal: 0
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
interface CardData {
|
||||
key: string;
|
||||
@ -27,8 +51,6 @@ interface CardData {
|
||||
bottom: { label: string; value: number }[];
|
||||
}
|
||||
|
||||
console.log(props.modelValue);
|
||||
|
||||
const cardData = computed<CardData[]>(() => [
|
||||
{
|
||||
key: 'retryTask',
|
||||
|
@ -53,11 +53,11 @@ const { domRef, updateOptions } = useEcharts(() => ({
|
||||
|
||||
async function mockData() {
|
||||
await new Promise(resolve => {
|
||||
setTimeout(resolve, 500);
|
||||
setTimeout(resolve, 1);
|
||||
});
|
||||
|
||||
if (!props.modelValue) {
|
||||
mockData();
|
||||
await mockData();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -71,7 +71,7 @@ async function mockData() {
|
||||
}
|
||||
|
||||
async function init() {
|
||||
mockData();
|
||||
await mockData();
|
||||
}
|
||||
|
||||
// init
|
||||
|
229
src/views/home/modules/line-retry-chart.vue
Normal file
229
src/views/home/modules/line-retry-chart.vue
Normal file
@ -0,0 +1,229 @@
|
||||
<script setup lang="ts">
|
||||
import { watch } from 'vue';
|
||||
import { useEcharts } from '@/hooks/common/echarts';
|
||||
|
||||
defineOptions({
|
||||
name: 'LineRetryChart'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
type?: number;
|
||||
modelValue: Api.Dashboard.DashboardLine;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
type: 0
|
||||
});
|
||||
|
||||
const { domRef, updateOptions } = useEcharts(() => ({
|
||||
tabIndex: props.type,
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['Success Num', 'Running Num', 'Max Count Num', 'Suspend Num']
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: [] as string[]
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
color: '#f5b386',
|
||||
name: 'Success Num',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
stack: 'Total',
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0.25,
|
||||
color: '#f5b386'
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: '#fff'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
data: [] as number[]
|
||||
},
|
||||
{
|
||||
color: '#40e9c5',
|
||||
name: 'Running Num',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
stack: 'Total',
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0.25,
|
||||
color: '#40e9c5'
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: '#fff'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
data: []
|
||||
},
|
||||
{
|
||||
color: '#b686d4',
|
||||
name: 'Max Count Num',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
stack: 'Total',
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0.25,
|
||||
color: '#b686d4'
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: '#fff'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
data: [] as number[]
|
||||
},
|
||||
{
|
||||
color: '#ec6f6f',
|
||||
name: 'Suspend Num',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
stack: 'Total',
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0.25,
|
||||
color: '#ec6f6f'
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: '#fff'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
data: []
|
||||
}
|
||||
]
|
||||
}));
|
||||
|
||||
async function getData() {
|
||||
await new Promise(resolve => {
|
||||
setTimeout(resolve, 1);
|
||||
});
|
||||
|
||||
if (!props.modelValue) {
|
||||
await getData();
|
||||
return;
|
||||
}
|
||||
|
||||
updateOptions(opts => {
|
||||
opts.xAxis.data = props.modelValue?.dashboardLineResponseDOList.map(x => x.createDt);
|
||||
opts.series[0].data = props.modelValue?.dashboardLineResponseDOList.map(x =>
|
||||
opts.tabIndex === 1 ? x.success : x.successNum
|
||||
);
|
||||
opts.series[1].data = props.modelValue?.dashboardLineResponseDOList.map(x =>
|
||||
opts.tabIndex === 1 ? x.fail : x.runningNum
|
||||
);
|
||||
opts.series[2].data = props.modelValue?.dashboardLineResponseDOList.map(x =>
|
||||
opts.tabIndex === 1 ? x.stop : x.maxCountNum
|
||||
);
|
||||
opts.series[3].data = props.modelValue?.dashboardLineResponseDOList.map(x =>
|
||||
opts.tabIndex === 1 ? x.cancel : x.suspendNum
|
||||
);
|
||||
return opts;
|
||||
});
|
||||
}
|
||||
|
||||
function updateLocale() {
|
||||
updateOptions(opts => {
|
||||
opts.legend.data =
|
||||
opts.tabIndex === 1
|
||||
? ['Success', 'Fail', 'Stop', 'Cancel']
|
||||
: ['Success Num', 'Running Num', 'Max Count Num', 'Suspend Num'];
|
||||
|
||||
opts.series[0].name = opts.legend.data[0];
|
||||
opts.series[1].name = opts.legend.data[1];
|
||||
opts.series[2].name = opts.legend.data[2];
|
||||
opts.series[3].name = opts.legend.data[3];
|
||||
return opts;
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.type,
|
||||
() => {
|
||||
updateLocale();
|
||||
getData();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard :bordered="false" class="card-wrapper">
|
||||
<div ref="domRef" class="h-360px overflow-hidden"></div>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
47
src/views/home/modules/retry-tab.vue
Normal file
47
src/views/home/modules/retry-tab.vue
Normal file
@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { fetchJobLine, fetchRetryLine } from '@/service/api';
|
||||
import LineRetryChart from './line-retry-chart.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'RetryTab'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
type?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
type: 0
|
||||
});
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
const gap = computed(() => (appStore.isMobile ? 0 : 16));
|
||||
|
||||
const data = ref<Api.Dashboard.DashboardLine>();
|
||||
|
||||
const getData = async () => {
|
||||
const { data: lineData, error } = props.type === 1 ? await fetchJobLine() : await fetchRetryLine();
|
||||
|
||||
if (!error) {
|
||||
data.value = lineData;
|
||||
}
|
||||
};
|
||||
|
||||
getData();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
|
||||
<NGi span="24 s:24 m:16">
|
||||
<LineRetryChart :type="type" :model-value="data!"></LineRetryChart>
|
||||
</NGi>
|
||||
<NGi span="24 s:24 m:8">
|
||||
<div>任务量排名</div>
|
||||
</NGi>
|
||||
</NGrid>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
Loading…
Reference in New Issue
Block a user