feat: 新增首页卡片统计

This commit is contained in:
xlsea 2024-03-22 11:22:07 +08:00
parent 6b1cf17428
commit 8e405db674
24 changed files with 520 additions and 104 deletions

6
.env
View File

@ -1,8 +1,10 @@
VITE_BASE_URL=/ VITE_BASE_URL=/
VITE_APP_TITLE=SoybeanAdmin VITE_APP_TITLE=Easy Retry
VITE_APP_DESC=SoybeanAdmin is a fresh and elegant admin template VITE_APP_DESC=A flexible, reliable, and fast platform for distributed task retry and distributed task scheduling.
VITE_APP_VERSION=v3.1.0
# the prefix of the icon name # the prefix of the icon name
VITE_ICON_PREFIX=icon VITE_ICON_PREFIX=icon

View File

@ -1,5 +1,5 @@
# backend service base url, prod environment # backend service base url, prod environment
VITE_SERVICE_BASE_URL=https://mock.apifox.com/m1/3109515-0-default VITE_SERVICE_BASE_URL=/proxy-default
# other backend service base url, prod environment # other backend service base url, prod environment
VITE_OTHER_SERVICE_BASE_URL= `{ VITE_OTHER_SERVICE_BASE_URL= `{

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

BIN
public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@ -2,12 +2,14 @@
defineOptions({ defineOptions({
name: 'GlobalFooter' name: 'GlobalFooter'
}); });
const { VITE_APP_VERSION } = import.meta.env;
</script> </script>
<template> <template>
<DarkModeContainer class="h-full flex-center"> <DarkModeContainer class="h-full flex-center">
<a href="https://github.com/soybeanjs/soybean-admin/blob/main/LICENSE" target="_blank" rel="noopener noreferrer"> <a href="https://www.easyretry.com/" target="_blank" rel="noopener noreferrer">
Copyright MIT © 2021 Soybean Copyright © 2024 Easy Retry {{ VITE_APP_VERSION }}
</a> </a>
</DarkModeContainer> </DarkModeContainer>
</template> </template>

View File

@ -16,9 +16,7 @@ const options = ref(
const onChange = (value: string) => { const onChange = (value: string) => {
localStg.set('namespaceId', value); localStg.set('namespaceId', value);
setTimeout(() => { router.go(0);
router.go(0);
}, 500);
}; };
</script> </script>

View File

@ -1,6 +1,7 @@
const local: App.I18n.Schema = { const local: App.I18n.Schema = {
system: { system: {
title: 'SoybeanAdmin' title: 'Easy Retry',
desc: 'A flexible, reliable, and fast platform for distributed task retry and distributed task scheduling.'
}, },
common: { common: {
action: 'Action', action: 'Action',
@ -35,6 +36,10 @@ const local: App.I18n.Schema = {
update: 'Update', update: 'Update',
updateSuccess: 'Update Success', updateSuccess: 'Update Success',
userCenter: 'User Center', userCenter: 'User Center',
success: 'Success',
fail: 'Fail',
stop: 'Stop',
running: 'Running',
yesOrNo: { yesOrNo: {
yes: 'Yes', yes: 'Yes',
no: 'No' no: 'No'
@ -164,6 +169,7 @@ const local: App.I18n.Schema = {
confirmPasswordPlaceholder: 'Please enter password again', confirmPasswordPlaceholder: 'Please enter password again',
codeLogin: 'Verification code login', codeLogin: 'Verification code login',
confirm: 'Confirm', confirm: 'Confirm',
login: 'Login',
back: 'Back', back: 'Back',
validateSuccess: 'Verification passed', validateSuccess: 'Verification passed',
loginSuccess: 'Login successfully', loginSuccess: 'Login successfully',
@ -212,8 +218,30 @@ const local: App.I18n.Schema = {
devDep: 'Development Dependency' devDep: 'Development Dependency'
}, },
home: { home: {
greeting: 'Good morning, {userName}, today is another day full of vitality!', // 问候语
Greeting: '{userName}, welcome back.',
morningGreeting: 'Good morning, {userName}, today is another day full of vitality!',
bthGreeting: "Good morning, {userName}, how's work going? Don't be sedentary. Get up and walk around more often!",
noonGreeting: "Good noon, {userName}, it's lunchtime after a long morning at work!",
athGreeting: "Good afternoon, {userName}, it's easy to get sleepy in the late afternoon yet, time for a nap!",
duskGreeting:
"{userName}, it's evening, the view of the sunset outside the window is very beautiful, the most beautiful thing is the red sunset.",
eveningGreeting: 'Good evening, {userName}, how are you doing today? Please take care to rest early!',
earlyMorningGreeting: "{userName}, It's so late already. Get some rest. Good night.",
weatherDesc: 'Today is cloudy to clear, 20℃ - 25℃!', weatherDesc: 'Today is cloudy to clear, 20℃ - 25℃!',
// 卡片统计
retryTaskCount: 'Retry Task',
jobTaskCount: 'Job Task',
userCount: 'User',
retryTask: 'Retry Task',
retryTaskTip: 'Total task volume: retry/callback task volume',
jobTask: 'Job Task',
jobTaskTip: 'Success rate: total completion/total dispatch amount',
onlineServiceCount: 'Online Machine',
onlineServiceTip: 'Always online machines: the sum of clients and servers registered to the system',
workflow: 'Workflow',
workflowTip: 'Workflow Tip',
// ...
projectCount: 'Project Count', projectCount: 'Project Count',
todo: 'Todo', todo: 'Todo',
message: 'Message', message: 'Message',
@ -271,6 +299,18 @@ const local: App.I18n.Schema = {
disable: 'Disable' disable: 'Disable'
} }
}, },
machine: {
type: {
client: 'Client',
server: 'Server'
}
},
retryTask: {
status: {
maxRetryTimes: 'Max times',
pauseRetry: 'Pause'
}
},
role: { role: {
title: 'Role List', title: 'Role List',
roleName: 'Role Name', roleName: 'Role Name',

View File

@ -1,6 +1,7 @@
const local: App.I18n.Schema = { const local: App.I18n.Schema = {
system: { system: {
title: 'Soybean 管理系统' title: 'Easy Retry',
desc: '灵活,可靠和快速的分布式任务重试和分布式任务调度平台'
}, },
common: { common: {
action: '操作', action: '操作',
@ -35,6 +36,10 @@ const local: App.I18n.Schema = {
update: '更新', update: '更新',
updateSuccess: '更新成功', updateSuccess: '更新成功',
userCenter: '个人中心', userCenter: '个人中心',
success: '成功',
fail: '失败',
stop: '停止',
running: '运行中',
yesOrNo: { yesOrNo: {
yes: '是', yes: '是',
no: '否' no: '否'
@ -163,6 +168,7 @@ const local: App.I18n.Schema = {
passwordPlaceholder: '请输入密码', passwordPlaceholder: '请输入密码',
confirmPasswordPlaceholder: '请再次输入密码', confirmPasswordPlaceholder: '请再次输入密码',
codeLogin: '验证码登录', codeLogin: '验证码登录',
login: '登录',
confirm: '确定', confirm: '确定',
back: '返回', back: '返回',
validateSuccess: '验证成功', validateSuccess: '验证成功',
@ -212,8 +218,26 @@ const local: App.I18n.Schema = {
devDep: '开发依赖' devDep: '开发依赖'
}, },
home: { home: {
greeting: '早安,{userName}, 今天又是充满活力的一天!', Greeting: '{userName},欢迎回来!',
morningGreeting: '早安,{userName},今天又是充满活力的一天!',
bthGreeting: '上午好,{userName},工作顺利吗,不要久坐,多起来走动走动哦!',
noonGreeting: '中午好,{userName},工作了一个上午,现在是午餐时间!',
athGreeting: '下午好,{userName},午后很容易犯困呢,是时候该打个盹了!',
duskGreeting: '{userName},傍晚了,窗外夕阳的景色很美丽呢,最美不过夕阳红~',
eveningGreeting: '晚上好,{userName},今天过得怎么样?请注意早点休息!',
earlyMorningGreeting: '{userName},已经这么晚了呀,早点休息吧,晚安~',
weatherDesc: '今日多云转晴20℃ - 25℃!', weatherDesc: '今日多云转晴20℃ - 25℃!',
retryTaskCount: '重试任务',
jobTaskCount: '定时任务',
userCount: '用户',
retryTask: '重试任务',
retryTaskTip: '总任务量: 重试/回调任务量',
jobTask: '定时任务',
jobTaskTip: '成功率:总完成/总调度量',
onlineServiceCount: '总在线机器',
onlineServiceTip: '总在线机器:注册到系统的客户端和服务端之和',
workflow: '工作流',
workflowTip: '工作流提示',
projectCount: '项目数', projectCount: '项目数',
todo: '待办', todo: '待办',
message: '消息', message: '消息',
@ -271,6 +295,18 @@ const local: App.I18n.Schema = {
disable: '禁用' disable: '禁用'
} }
}, },
machine: {
type: {
client: '客户端',
server: '服务端'
}
},
retryTask: {
status: {
maxRetryTimes: '最大重试次数',
pauseRetry: '暂停重试'
}
},
role: { role: {
title: '角色列表', title: '角色列表',
roleName: '角色名称', roleName: '角色名称',

View File

@ -2,7 +2,7 @@
import { getRgbOfColor } from '@sa/utils'; import { getRgbOfColor } from '@sa/utils';
import { $t } from '@/locales'; import { $t } from '@/locales';
import { localStg } from '@/utils/storage'; import { localStg } from '@/utils/storage';
import systemLogo from '@/assets/svg-icon/logo.svg?raw'; import systemLogo from '@/assets/svg-icon/full-logo.svg?raw';
export function setupLoading() { export function setupLoading() {
const themeColor = localStg.get('themeColor') || '#646cff'; const themeColor = localStg.get('themeColor') || '#646cff';
@ -18,7 +18,7 @@ export function setupLoading() {
'right-0 bottom-0 animate-delay-1500' 'right-0 bottom-0 animate-delay-1500'
]; ];
const logoWithClass = systemLogo.replace('<svg', `<svg class="size-128px text-primary"`); const logoWithClass = systemLogo.replace('<svg', `<svg class="size-256px text-primary"`);
const dot = loadingClasses const dot = loadingClasses
.map(item => { .map(item => {
@ -34,7 +34,7 @@ export function setupLoading() {
${dot} ${dot}
</div> </div>
</div> </div>
<h2 class="text-28px font-500 text-#646464">${$t('system.title')}</h2> <h2 class="text-26px font-500 pt-32px text-#646464">${$t('system.desc')}</h2>
</div>`; </div>`;
const app = document.getElementById('app'); const app = document.getElementById('app');

View File

@ -0,0 +1,9 @@
import { request } from '../request';
/** Version */
export function fetchCardCount() {
return request<Api.Dashboard.CardCount>({
url: '/dashboard/task-retry-job',
method: 'get'
});
}

View File

@ -1,3 +1,5 @@
export * from './auth'; export * from './auth';
export * from './route'; export * from './route';
export * from './system';
export * from './dashboard';
export * from './system-manage'; export * from './system-manage';

View File

@ -0,0 +1,9 @@
import { request } from '../request';
/** Version */
export function fetchVersion() {
return request<string>({
url: '/system/version',
method: 'get'
});
}

View File

@ -1,7 +1,7 @@
/** Default theme settings */ /** Default theme settings */
export const themeSettings: App.Theme.ThemeSetting = { export const themeSettings: App.Theme.ThemeSetting = {
themeScheme: 'light', themeScheme: 'light',
themeColor: '#646cff', themeColor: '#22aae3',
otherColor: { otherColor: {
info: '#2080f0', info: '#2080f0',
success: '#52c41a', success: '#52c41a',

42
src/typings/api.d.ts vendored
View File

@ -95,6 +95,48 @@ declare namespace Api {
} }
} }
/**
* namespace Dashboard
*
* backend api module: "dashboard"
*/
namespace Dashboard {
type CardCount = {
jobTask: JobTask;
retryTask: RetryTask;
retryTaskBarList: RetryTaskBarList[];
onLineService: OnlineService;
};
type OnlineService = {
total: number;
clientTotal: number;
serverTotal: number;
};
type RetryTaskBarList = {
x: string;
taskTotal: number;
};
type RetryTask = {
totalNum: number;
runningNum: number;
finishNum: number;
maxCountNum: number;
suspendNum: number;
};
type JobTask = {
successNum: number;
failNum: number;
cancelNum: number;
stopNum: number;
totalNum: number;
successRate: number;
};
}
/** /**
* namespace SystemManage * namespace SystemManage
* *

38
src/typings/app.d.ts vendored
View File

@ -247,6 +247,7 @@ declare namespace App {
type Schema = { type Schema = {
system: { system: {
title: string; title: string;
desc: string;
}; };
common: { common: {
action: string; action: string;
@ -281,6 +282,10 @@ declare namespace App {
update: string; update: string;
updateSuccess: string; updateSuccess: string;
userCenter: string; userCenter: string;
success: string;
fail: string;
stop: string;
running: string;
yesOrNo: { yesOrNo: {
yes: string; yes: string;
no: string; no: string;
@ -346,6 +351,7 @@ declare namespace App {
passwordPlaceholder: string; passwordPlaceholder: string;
confirmPasswordPlaceholder: string; confirmPasswordPlaceholder: string;
codeLogin: string; codeLogin: string;
login: string;
confirm: string; confirm: string;
back: string; back: string;
validateSuccess: string; validateSuccess: string;
@ -395,7 +401,25 @@ declare namespace App {
devDep: string; devDep: string;
}; };
home: { home: {
greeting: string; Greeting: string;
morningGreeting: string;
bthGreeting: string;
noonGreeting: string;
athGreeting: string;
duskGreeting: string;
eveningGreeting: string;
earlyMorningGreeting: string;
retryTaskCount: string;
jobTaskCount: string;
userCount: string;
retryTask: string;
retryTaskTip: string;
jobTask: string;
jobTaskTip: string;
onlineServiceCount: string;
onlineServiceTip: string;
workflow: string;
workflowTip: string;
weatherDesc: string; weatherDesc: string;
projectCount: string; projectCount: string;
todo: string; todo: string;
@ -454,6 +478,18 @@ declare namespace App {
disable: string; disable: string;
}; };
}; };
machine: {
type: {
client: string;
server: string;
};
};
retryTask: {
status: {
maxRetryTimes: string;
pauseRetry: string;
};
};
role: { role: {
title: string; title: string;
roleName: string; roleName: string;

View File

@ -16,14 +16,21 @@ declare module 'vue' {
FullScreen: typeof import('./../components/common/full-screen.vue')['default'] FullScreen: typeof import('./../components/common/full-screen.vue')['default']
IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default'] IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
IconAntDesignReloadOutlined: typeof import('~icons/ant-design/reload-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'] IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['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'] IconLocalBanner: typeof import('~icons/local/banner')['default']
IconLocalLogo: typeof import('~icons/local/logo')['default'] IconLocalLogo: typeof import('~icons/local/logo')['default']
IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default'] IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-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'] IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default']
IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default'] IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default']
IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
IconUilSearch: typeof import('~icons/uil/search')['default'] IconUilSearch: typeof import('~icons/uil/search')['default']
LangSwitch: typeof import('./../components/common/lang-switch.vue')['default'] LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
LookForward: typeof import('./../components/custom/look-forward.vue')['default'] LookForward: typeof import('./../components/custom/look-forward.vue')['default']
@ -34,6 +41,9 @@ declare module 'vue' {
NCard: typeof import('naive-ui')['NCard'] NCard: typeof import('naive-ui')['NCard']
NCheckbox: typeof import('naive-ui')['NCheckbox'] NCheckbox: typeof import('naive-ui')['NCheckbox']
NColorPicker: typeof import('naive-ui')['NColorPicker'] 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'] NDialogProvider: typeof import('naive-ui')['NDialogProvider']
NDivider: typeof import('naive-ui')['NDivider'] NDivider: typeof import('naive-ui')['NDivider']
NDrawer: typeof import('naive-ui')['NDrawer'] NDrawer: typeof import('naive-ui')['NDrawer']
@ -42,6 +52,7 @@ declare module 'vue' {
NEmpty: typeof import('naive-ui')['NEmpty'] NEmpty: typeof import('naive-ui')['NEmpty']
NForm: typeof import('naive-ui')['NForm'] NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem'] NFormItem: typeof import('naive-ui')['NFormItem']
NFormItemGi: typeof import('naive-ui')['NFormItemGi']
NGi: typeof import('naive-ui')['NGi'] NGi: typeof import('naive-ui')['NGi']
NGrid: typeof import('naive-ui')['NGrid'] NGrid: typeof import('naive-ui')['NGrid']
NInput: typeof import('naive-ui')['NInput'] NInput: typeof import('naive-ui')['NInput']
@ -54,13 +65,21 @@ declare module 'vue' {
NMessageProvider: typeof import('naive-ui')['NMessageProvider'] NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NModal: typeof import('naive-ui')['NModal'] NModal: typeof import('naive-ui')['NModal']
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider'] 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']
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
NScrollbar: typeof import('naive-ui')['NScrollbar'] NScrollbar: typeof import('naive-ui')['NScrollbar']
NSelect: typeof import('naive-ui')['NSelect'] NSelect: typeof import('naive-ui')['NSelect']
NSkeleton: typeof import('naive-ui')['NSkeleton']
NSpace: typeof import('naive-ui')['NSpace'] NSpace: typeof import('naive-ui')['NSpace']
NSpin: typeof import('naive-ui')['NSpin']
NStatistic: typeof import('naive-ui')['NStatistic'] NStatistic: typeof import('naive-ui')['NStatistic']
NSwitch: typeof import('naive-ui')['NSwitch'] NSwitch: typeof import('naive-ui')['NSwitch']
NTab: typeof import('naive-ui')['NTab'] NTab: typeof import('naive-ui')['NTab']
NTabs: typeof import('naive-ui')['NTabs'] NTabs: typeof import('naive-ui')['NTabs']
NTag: typeof import('naive-ui')['NTag']
NThing: typeof import('naive-ui')['NThing'] NThing: typeof import('naive-ui')['NThing']
NTooltip: typeof import('naive-ui')['NTooltip'] NTooltip: typeof import('naive-ui')['NTooltip']
PinToggler: typeof import('./../components/common/pin-toggler.vue')['default'] PinToggler: typeof import('./../components/common/pin-toggler.vue')['default']

View File

@ -15,6 +15,8 @@ declare namespace Env {
readonly VITE_APP_TITLE: string; readonly VITE_APP_TITLE: string;
/** The description of the application */ /** The description of the application */
readonly VITE_APP_DESC: string; readonly VITE_APP_DESC: string;
/** The version of the application */
readonly VITE_APP_VERSION: string;
/** The router history mode */ /** The router history mode */
readonly VITE_ROUTER_HISTORY_MODE?: RouterHistoryMode; readonly VITE_ROUTER_HISTORY_MODE?: RouterHistoryMode;
/** The prefix of the iconify icon */ /** The prefix of the iconify icon */

View File

@ -3,14 +3,11 @@ import { computed } from 'vue';
import type { Component } from 'vue'; import type { Component } from 'vue';
import { getColorPalette, mixColor } from '@sa/utils'; import { getColorPalette, mixColor } from '@sa/utils';
import { $t } from '@/locales'; import { $t } from '@/locales';
import GlobalFooter from '@/layouts/modules/global-footer/index.vue';
import { useAppStore } from '@/store/modules/app'; import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme'; import { useThemeStore } from '@/store/modules/theme';
import { loginModuleRecord } from '@/constants/app'; import { loginModuleRecord } from '@/constants/app';
import PwdLogin from './modules/pwd-login.vue'; import PwdLogin from './modules/pwd-login.vue';
import CodeLogin from './modules/code-login.vue';
import Register from './modules/register.vue';
import ResetPwd from './modules/reset-pwd.vue';
import BindWechat from './modules/bind-wechat.vue';
interface Props { interface Props {
/** The login module */ /** The login module */
@ -21,6 +18,8 @@ const props = withDefaults(defineProps<Props>(), {
module: 'pwd-login' module: 'pwd-login'
}); });
const { VITE_APP_VERSION } = import.meta.env;
const appStore = useAppStore(); const appStore = useAppStore();
const themeStore = useThemeStore(); const themeStore = useThemeStore();
@ -30,13 +29,7 @@ interface LoginModule {
component: Component; component: Component;
} }
const modules: LoginModule[] = [ const modules: LoginModule[] = [{ key: 'pwd-login', label: loginModuleRecord['pwd-login'], component: PwdLogin }];
{ key: 'pwd-login', label: loginModuleRecord['pwd-login'], component: PwdLogin },
{ key: 'code-login', label: loginModuleRecord['code-login'], component: CodeLogin },
{ key: 'register', label: loginModuleRecord.register, component: Register },
{ key: 'reset-pwd', label: loginModuleRecord['reset-pwd'], component: ResetPwd },
{ key: 'bind-wechat', label: loginModuleRecord['bind-wechat'], component: BindWechat }
];
const activeModule = computed(() => { const activeModule = computed(() => {
const findItem = modules.find(item => item.key === props.module); const findItem = modules.find(item => item.key === props.module);
@ -63,7 +56,10 @@ const bgColor = computed(() => {
<div class="w-400px <sm:w-300px"> <div class="w-400px <sm:w-300px">
<header class="flex-y-center justify-between"> <header class="flex-y-center justify-between">
<SystemLogo class="text-64px text-primary <sm:text-48px" /> <SystemLogo class="text-64px text-primary <sm:text-48px" />
<h3 class="text-28px text-primary font-500 <sm:text-22px">{{ $t('system.title') }}</h3> <h3 class="flex text-28px text-primary font-500 <sm:text-22px">
{{ $t('system.title') }}
<span class="mt-3px pl-12px text-16px color-#00000072 font-600">{{ VITE_APP_VERSION }}</span>
</h3>
<div class="i-flex-vertical"> <div class="i-flex-vertical">
<ThemeSchemaSwitch <ThemeSchemaSwitch
:theme-schema="themeStore.themeScheme" :theme-schema="themeStore.themeScheme"
@ -80,12 +76,18 @@ const bgColor = computed(() => {
</div> </div>
</header> </header>
<main class="pt-24px"> <main class="pt-24px">
<h3 class="text-18px text-primary font-medium">{{ $t(activeModule.label) }}</h3> <!-- <h3 class="text-18px text-primary font-medium">{{ $t(activeModule.label) }}</h3> -->
<div class="pt-24px"> <div class="pt-12px">
<Transition :name="themeStore.page.animateMode" mode="out-in" appear> <Transition :name="themeStore.page.animateMode" mode="out-in" appear>
<component :is="activeModule.component" /> <component :is="activeModule.component" />
</Transition> </Transition>
</div> </div>
<div class="pt-12px text-center">
<ButtonIcon tooltip-content="Mail" class="color-#272636 dark:color-#f0f2f5" icon="simple-icons:maildotru" />
<ButtonIcon class="color-#c71d23" tooltip-content="Gitee" icon="simple-icons:gitee" />
<ButtonIcon tooltip-content="Github" class="color-#010409 dark:color-#e6edf3" icon="simple-icons:github" />
</div>
<GlobalFooter />
</main> </main>
</div> </div>
</NCard> </NCard>

View File

@ -2,8 +2,6 @@
import { computed, reactive } from 'vue'; import { computed, reactive } from 'vue';
import { Md5 } from 'ts-md5'; import { Md5 } from 'ts-md5';
import { $t } from '@/locales'; import { $t } from '@/locales';
import { loginModuleRecord } from '@/constants/app';
import { useRouterPush } from '@/hooks/common/router';
import { useFormRules, useNaiveForm } from '@/hooks/common/form'; import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { useAuthStore } from '@/store/modules/auth'; import { useAuthStore } from '@/store/modules/auth';
@ -12,7 +10,6 @@ defineOptions({
}); });
const authStore = useAuthStore(); const authStore = useAuthStore();
const { toggleLoginModule } = useRouterPush();
const { formRef, validate } = useNaiveForm(); const { formRef, validate } = useNaiveForm();
interface FormModel { interface FormModel {
@ -56,21 +53,9 @@ async function handleSubmit() {
/> />
</NFormItem> </NFormItem>
<NSpace vertical :size="24"> <NSpace vertical :size="24">
<div class="flex-y-center justify-between">
<NCheckbox>{{ $t('page.login.pwdLogin.rememberMe') }}</NCheckbox>
<NButton quaternary>{{ $t('page.login.pwdLogin.forgetPassword') }}</NButton>
</div>
<NButton type="primary" size="large" round block :loading="authStore.loginLoading" @click="handleSubmit"> <NButton type="primary" size="large" round block :loading="authStore.loginLoading" @click="handleSubmit">
{{ $t('common.confirm') }} {{ $t('page.login.common.login') }}
</NButton> </NButton>
<div class="flex-y-center justify-between gap-12px">
<NButton class="flex-1" block @click="toggleLoginModule('code-login')">
{{ $t(loginModuleRecord['code-login']) }}
</NButton>
<NButton class="flex-1" block @click="toggleLoginModule('register')">
{{ $t(loginModuleRecord.register) }}
</NButton>
</div>
</NSpace> </NSpace>
</NForm> </NForm>
</template> </template>

View File

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed, ref } from 'vue';
import { useAppStore } from '@/store/modules/app'; import { useAppStore } from '@/store/modules/app';
import { fetchCardCount } from '@/service/api';
import HeaderBanner from './modules/header-banner.vue'; import HeaderBanner from './modules/header-banner.vue';
import CardData from './modules/card-data.vue'; import CardData from './modules/card-data.vue';
import LineChart from './modules/line-chart.vue'; import LineChart from './modules/line-chart.vue';
@ -11,12 +12,23 @@ import CreativityBanner from './modules/creativity-banner.vue';
const appStore = useAppStore(); const appStore = useAppStore();
const gap = computed(() => (appStore.isMobile ? 0 : 16)); const gap = computed(() => (appStore.isMobile ? 0 : 16));
const cardCount = ref<Api.Dashboard.CardCount>();
const getCardData = async () => {
const { data: cardData, error } = await fetchCardCount();
if (!error) {
cardCount.value = cardData;
}
};
getCardData();
</script> </script>
<template> <template>
<NSpace vertical :size="16"> <NSpace vertical :size="16">
<HeaderBanner /> <HeaderBanner />
<CardData /> <CardData class="h-165px" :model-value="cardCount!" />
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive> <NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:14"> <NGi span="24 s:24 m:14">
<NCard :bordered="false" class="card-wrapper"> <NCard :bordered="false" class="card-wrapper">

View File

@ -7,62 +7,138 @@ defineOptions({
name: 'CardData' name: 'CardData'
}); });
interface Props {
modelValue: Api.Dashboard.CardCount;
}
const props = defineProps<Props>();
interface CardData { interface CardData {
key: string; key: string;
title: string; title: string;
tip: string;
value: number; value: number;
unit: string;
color: { color: {
start: string; start: string;
end: string; end: string;
}; };
icon: string; icon: string;
bottom: { label: string; value: number }[];
} }
console.log(props.modelValue);
const cardData = computed<CardData[]>(() => [ const cardData = computed<CardData[]>(() => [
{ {
key: 'visitCount', key: 'retryTask',
title: $t('page.home.visitCount'), title: $t('page.home.retryTask'),
value: 9725, tip: $t('page.home.retryTaskTip'),
value: props.modelValue?.retryTask.totalNum ?? 0,
unit: '', unit: '',
color: { color: {
start: '#ec4786', start: '#40e9c5',
end: '#b955a4' end: '#BEE3DB'
}, },
icon: 'ant-design:bar-chart-outlined' icon: 'ant-design:schedule-outlined',
bottom: [
{
label: $t('common.success'),
value: props.modelValue?.retryTask.finishNum ?? 0
},
{
label: $t('common.running'),
value: props.modelValue?.retryTask.runningNum ?? 0
},
{
label: $t('page.manage.retryTask.status.maxRetryTimes'),
value: props.modelValue?.retryTask.maxCountNum ?? 0
},
{
label: $t('page.manage.retryTask.status.pauseRetry'),
value: props.modelValue?.retryTask.suspendNum ?? 0
}
]
}, },
{ {
key: 'turnover', key: 'jobTask',
title: $t('page.home.turnover'), title: $t('page.home.jobTask'),
value: 1026, tip: $t('page.home.jobTaskTip'),
unit: '$', value: props.modelValue?.jobTask.totalNum ?? 0,
color: { color: {
start: '#865ec0', start: '#f5b386',
end: '#5144b4' end: '#FFD6BA'
}, },
icon: 'ant-design:money-collect-outlined' icon: 'ant-design:profile-outlined',
bottom: [
{
label: $t('common.success'),
value: props.modelValue?.jobTask.successNum ?? 0
},
{
label: $t('common.fail'),
value: props.modelValue?.jobTask.failNum ?? 0
},
{
label: $t('common.stop'),
value: props.modelValue?.jobTask.stopNum ?? 0
},
{
label: $t('common.cancel'),
value: props.modelValue?.jobTask.cancelNum ?? 0
}
]
}, },
{ {
key: 'downloadCount', key: 'onlineServiceCount',
title: $t('page.home.downloadCount'), title: $t('page.home.onlineServiceCount'),
value: 970925, tip: $t('page.home.onlineServiceTip'),
value: props.modelValue?.onLineService.total ?? 0,
unit: '', unit: '',
color: { color: {
start: '#56cdf3', start: '#b686d4',
end: '#719de3' end: '#c5a5d8'
}, },
icon: 'carbon:document-download' icon: 'ant-design:database-outlined',
bottom: [
{
label: $t('page.manage.machine.type.client'),
value: props.modelValue?.onLineService.clientTotal ?? 0
},
{
label: $t('page.manage.machine.type.server'),
value: props.modelValue?.onLineService.serverTotal ?? 0
}
]
}, },
{ {
key: 'dealCount', key: 'workflow',
title: $t('page.home.dealCount'), title: $t('page.home.workflow'),
value: 9527, tip: $t('page.home.workflowTip'),
value: 7,
unit: '', unit: '',
color: { color: {
start: '#fcbc25', start: '#ec6f6f',
end: '#f68057' end: '#f99797'
}, },
icon: 'ant-design:trademark-circle-outlined' icon: 'ant-design:database-outlined',
bottom: [
{
label: $t('common.success'),
value: 185
},
{
label: $t('common.fail'),
value: 37
},
{
label: $t('common.stop'),
value: 5
},
{
label: $t('common.cancel'),
value: 13
}
]
} }
]); ]);
@ -89,21 +165,45 @@ function getGradientColor(color: CardData['color']) {
<NGrid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16"> <NGrid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16">
<NGi v-for="item in cardData" :key="item.key"> <NGi v-for="item in cardData" :key="item.key">
<GradientBg :gradient-color="getGradientColor(item.color)" class="flex-1"> <NSpin :show="false">
<h3 class="text-16px">{{ item.title }}</h3> <GradientBg :gradient-color="getGradientColor(item.color)" class="h-165px flex-1">
<div class="flex justify-between pt-12px"> <div class="flex justify-between">
<SvgIcon :icon="item.icon" class="text-32px" /> <div class="align-center flex">
<CountTo <SvgIcon :icon="item.icon" class="text-26px" />
:prefix="item.unit" <h3 class="ml-2 text-18px">{{ item.title }}</h3>
:start-value="1" </div>
:end-value="item.value" <NPopover trigger="hover">
class="text-30px text-white dark:text-dark" <template #trigger>
/> <NButton text>
</div> <SvgIcon icon="ant-design:info-circle-outlined" class="text-20px color-white" />
</GradientBg> </NButton>
</template>
{{ item.tip }}
</NPopover>
</div>
<div class="flex pt-12px">
<CountTo :start-value="0" :end-value="item.value" class="text-30px text-white" />
</div>
<NProgress type="line" color="#728bf9" rail-color="#ebebeb" :percentage="30" indicator-text-color="#fff" />
<NDivider />
<template v-for="(bottomItem, bottomIndex) in item.bottom" :key="bottomIndex">
<NDivider v-if="bottomIndex !== 0" vertical />
{{ bottomItem.label }}
<CountTo :start-value="0" :end-value="bottomItem.value" class="text-white" />
</template>
</GradientBg>
</NSpin>
</NGi> </NGi>
</NGrid> </NGrid>
</NCard> </NCard>
</template> </template>
<style scoped></style> <style scoped>
.n-divider {
margin: 16px 0 6px;
}
.n-divider--vertical {
margin: 0 1px 0 5px;
}
</style>

View File

@ -22,41 +22,65 @@ interface StatisticData {
const statisticData = computed<StatisticData[]>(() => [ const statisticData = computed<StatisticData[]>(() => [
{ {
id: 0, id: 0,
label: $t('page.home.projectCount'), label: $t('page.home.userCount'),
value: '25' value: '2'
}, },
{ {
id: 1, id: 1,
label: $t('page.home.todo'), label: $t('page.home.jobTaskCount'),
value: '4/16' value: '8'
}, },
{ {
id: 2, id: 2,
label: $t('page.home.message'), label: $t('page.home.retryTaskCount'),
value: '12' value: '3'
} }
]); ]);
const timeFix = () => {
const time = new Date();
const hour = time.getHours();
let text = '';
if (hour > 5 && hour <= 8) {
text = 'morning';
} else if (hour > 8 && hour <= 11) {
text = 'bth';
} else if (hour > 11 && hour <= 14) {
text = 'noon';
} else if (hour > 14 && hour <= 17) {
text = 'ath';
} else if (hour > 17 && hour <= 19) {
text = 'dusk';
} else if (hour > 19 && hour <= 21) {
text = 'evening';
} else if (hour > 21 && hour <= 5) {
text = 'earlyMorning';
}
return text;
};
</script> </script>
<template> <template>
<NCard :bordered="false" class="card-wrapper"> <NCard :bordered="false" class="card-wrapper">
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive> <NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:18"> <NGi class="flex" span="24 s:24 m:18">
<div class="flex-y-center"> <div class="flex-y-center">
<div class="size-72px shrink-0 overflow-hidden rd-1/2">
<img src="@/assets/imgs/soybean.jpg" class="size-full" />
</div>
<div class="pl-12px"> <div class="pl-12px">
<h3 class="text-18px font-semibold"> <h3 class="text-22px font-semibold">
{{ $t('page.home.greeting', { userName: authStore.userInfo.username }) }} {{ $t(`page.home.${timeFix()}Greeting`, { userName: authStore.userInfo.username }) }}
</h3> </h3>
<p class="text-#999 leading-30px">{{ $t('page.home.weatherDesc') }}</p> <!-- <p class="text-#999 leading-30px">{{ $t('system.title') + ' - ' + $t('system.desc') }}</p> -->
</div> </div>
</div> </div>
</NGi> </NGi>
<NGi span="24 s:24 m:6"> <NGi span="24 s:24 m:6">
<NSpace :size="24" justify="end"> <NSpace :size="24" justify="end">
<NStatistic v-for="item in statisticData" :key="item.id" class="whitespace-nowrap" v-bind="item" /> <NStatistic
v-for="item in statisticData"
:key="item.id"
class="whitespace-nowrap text-center"
v-bind="item"
/>
</NSpace> </NSpace>
</NGi> </NGi>
</NGrid> </NGrid>