9/28
1. 修改登录页面中:星期展示方法 2. 登录页面 3. 首页
This commit is contained in:
@@ -1,42 +1,42 @@
|
||||
# 页面标题
|
||||
VITE_APP_TITLE = 智净云 · 智能家居管理后台
|
||||
VITE_APP_LOGO_TITLE = RuoYi-Vue-Plus
|
||||
VITE_APP_TITLE='智净云 · 智能家居管理后台'
|
||||
VITE_APP_LOGO_TITLE=RuoYi-Vue-Plus
|
||||
|
||||
# 开发环境配置
|
||||
VITE_APP_ENV = 'development'
|
||||
VITE_APP_ENV='development'
|
||||
|
||||
# 开发环境
|
||||
VITE_APP_BASE_API = '/dev-api'
|
||||
VITE_APP_BASE_API='/dev-api'
|
||||
|
||||
# 应用访问路径 例如使用前缀 /admin/
|
||||
VITE_APP_CONTEXT_PATH = '/'
|
||||
VITE_APP_CONTEXT_PATH='/'
|
||||
|
||||
# 监控地址
|
||||
VITE_APP_MONITOR_ADMIN = 'http://localhost:9090/admin/applications'
|
||||
VITE_APP_MONITOR_ADMIN='http://localhost:9090/admin/applications'
|
||||
|
||||
# SnailJob 控制台地址
|
||||
VITE_APP_SNAILJOB_ADMIN = 'http://localhost:8800/snail-job'
|
||||
VITE_APP_SNAILJOB_ADMIN='http://localhost:8800/snail-job'
|
||||
|
||||
# SnailAI 控制台地址
|
||||
VITE_APP_SNAILAI_ADMIN = 'http://localhost:8900/snail-ai'
|
||||
VITE_APP_SNAILAI_ADMIN='http://localhost:8900/snail-ai'
|
||||
|
||||
VITE_APP_PORT = 54123
|
||||
VITE_APP_PORT=5412
|
||||
|
||||
# 接口加密功能开关(如需关闭 后端也必须对应关闭)
|
||||
VITE_APP_ENCRYPT = true
|
||||
VITE_APP_ENCRYPT=true
|
||||
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
|
||||
VITE_APP_RSA_PUBLIC_KEY = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvEDuRIOM3oZPWj9Ukoc5pQklR4PFH6/clnjeFqjDLIgDyQvjxhgqAZQA+E9eD6qu6FsXPmK8djcL+nh3cFHz4pX473jDvO3Sve+8yL3VRQ0n2pRgQ2a01MJsy+WwTZCBYWf0VnLRIvANUoWQgy9vz94q7Va44dg7A1/3ICf+xAwIDAQAB'
|
||||
VITE_APP_RSA_PUBLIC_KEY='MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvEDuRIOM3oZPWj9Ukoc5pQklR4PFH6/clnjeFqjDLIgDyQvjxhgqAZQA+E9eD6qu6FsXPmK8djcL+nh3cFHz4pX473jDvO3Sve+8yL3VRQ0n2pRgQ2a01MJsy+WwTZCBYWf0VnLRIvANUoWQgy9vz94q7Va44dg7A1/3ICf+xAwIDAQAB'
|
||||
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换
|
||||
VITE_APP_RSA_PRIVATE_KEY = 'MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAObq7yrxfvyieZtTjAYyrdvi59tYTXxjO5ajmPCRSXBY9M9wQ1tli297JN6mnY53UJMNyOFNSZVi8WSFoIXjpR87FmvChJlzeN/dZdd3SEs48Ee66XKeSePYqxa8oO5GKDsnajgpsOHKXSeeVSIysiIPS2/WsEqk0In9P4w3RsRFAgMBAAECgYBiMEWwce24SPICnRzuScBpvmsudrbEDIH7BOd0a6LZlcnLJwZNJ7mJlshPsHNQb+WgEf135+BBGEhioPtn0yuTdEuKP4kB9UdYUKiayWCoWhJpesv7sAD4RDClV7dhuV+gcd1AXD+YzyRIPbGm0VC2U+4q8/+UPRpVjqskbLVTgQJBAPRpou7g3S8n4XB527kq0D8I3+ZYwMxZhszwhrCDpJU319+ucmpLVwYIzDmZVeID2QQdUaDfIEViFHu95xDrGiUCQQDx3YOKn3yaEctk/ERVn7hDAyAXUbd8/pv2b24/M/l1ZevlsFem8U4Jk5Mu64t3z3YGJoymEjQmbucwT01iKhehAkEAxlnccsRmfFh/KkqauKE4M4++NTAd9zlInpUsmZ+cN8UEGnF2RTEzRKBrLOt1uWCqBB7PGiE6DVTVjr7FAQPrSQJAT5yeY87DcONSk9cFlzmPqV8p/QME5rvYEnHzVBKDlkUKNPyqnWToTvaoh9U4fyNmsfeWbEOprszqhFhWHG3GgQJAK8+ynmyFhaw63+Hx2KU5zR4hVuQso2IzrEurGxCxybV6mR7VBerb4502+EPx3PmOgxQL+niUFhcMWxcvBFP9+A=='
|
||||
VITE_APP_RSA_PRIVATE_KEY='MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAObq7yrxfvyieZtTjAYyrdvi59tYTXxjO5ajmPCRSXBY9M9wQ1tli297JN6mnY53UJMNyOFNSZVi8WSFoIXjpR87FmvChJlzeN/dZdd3SEs48Ee66XKeSePYqxa8oO5GKDsnajgpsOHKXSeeVSIysiIPS2/WsEqk0In9P4w3RsRFAgMBAAECgYBiMEWwce24SPICnRzuScBpvmsudrbEDIH7BOd0a6LZlcnLJwZNJ7mJlshPsHNQb+WgEf135+BBGEhioPtn0yuTdEuKP4kB9UdYUKiayWCoWhJpesv7sAD4RDClV7dhuV+gcd1AXD+YzyRIPbGm0VC2U+4q8/+UPRpVjqskbLVTgQJBAPRpou7g3S8n4XB527kq0D8I3+ZYwMxZhszwhrCDpJU319+ucmpLVwYIzDmZVeID2QQdUaDfIEViFHu95xDrGiUCQQDx3YOKn3yaEctk/ERVn7hDAyAXUbd8/pv2b24/M/l1ZevlsFem8U4Jk5Mu64t3z3YGJoymEjQmbucwT01iKhehAkEAxlnccsRmfFh/KkqauKE4M4++NTAd9zlInpUsmZ+cN8UEGnF2RTEzRKBrLOt1uWCqBB7PGiE6DVTVjr7FAQPrSQJAT5yeY87DcONSk9cFlzmPqV8p/QME5rvYEnHzVBKDlkUKNPyqnWToTvaoh9U4fyNmsfeWbEOprszqhFhWHG3GgQJAK8+ynmyFhaw63+Hx2KU5zR4hVuQso2IzrEurGxCxybV6mR7VBerb4502+EPx3PmOgxQL+niUFhcMWxcvBFP9+A=='
|
||||
|
||||
# 客户端id
|
||||
VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
|
||||
VITE_APP_CLIENT_ID='e5cd7e4891bf95d1d19206ce24a7b32e'
|
||||
|
||||
# 统一消息推送开关
|
||||
VITE_APP_MESSAGE_ENABLED = true
|
||||
VITE_APP_MESSAGE_ENABLED=true
|
||||
|
||||
# sse / websocket
|
||||
VITE_APP_MESSAGE_TRANSPORT = 'sse'
|
||||
VITE_APP_MESSAGE_TRANSPORT='sse'
|
||||
|
||||
# 统一消息推送路径
|
||||
VITE_APP_MESSAGE_PATH = '/resource/message'
|
||||
VITE_APP_MESSAGE_PATH='/resource/message'
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
# 页面标题
|
||||
VITE_APP_TITLE = RuoYi-Vue-Plus后台管理系统
|
||||
VITE_APP_LOGO_TITLE = RuoYi-Vue-Plus
|
||||
VITE_APP_TITLE=RuoYi-Vue-Plus后台管理系统
|
||||
VITE_APP_LOGO_TITLE=RuoYi-Vue-Plus
|
||||
|
||||
# 生产环境配置
|
||||
VITE_APP_ENV = 'production'
|
||||
VITE_APP_ENV='production'
|
||||
|
||||
# 应用访问路径 例如使用前缀 /admin/
|
||||
VITE_APP_CONTEXT_PATH = '/'
|
||||
VITE_APP_CONTEXT_PATH='/'
|
||||
|
||||
# 监控地址
|
||||
VITE_APP_MONITOR_ADMIN = '/admin/applications'
|
||||
VITE_APP_MONITOR_ADMIN='/admin/applications'
|
||||
|
||||
# SnailJob 控制台地址
|
||||
VITE_APP_SNAILJOB_ADMIN = '/snail-job'
|
||||
VITE_APP_SNAILJOB_ADMIN='/snail-job'
|
||||
|
||||
# SnailAI 控制台地址
|
||||
VITE_APP_SNAILAI_ADMIN = '/snail-ai'
|
||||
VITE_APP_SNAILAI_ADMIN='/snail-ai'
|
||||
|
||||
# 生产环境
|
||||
VITE_APP_BASE_API = '/prod-api'
|
||||
VITE_APP_BASE_API='/prod-api'
|
||||
|
||||
# 是否在打包时开启压缩,支持 gzip 和 brotli
|
||||
VITE_BUILD_COMPRESS = gzip
|
||||
VITE_BUILD_COMPRESS=gzip
|
||||
|
||||
VITE_APP_PORT = 80
|
||||
VITE_APP_PORT=80
|
||||
|
||||
# 接口加密功能开关(如需关闭 后端也必须对应关闭)
|
||||
VITE_APP_ENCRYPT = true
|
||||
VITE_APP_ENCRYPT=true
|
||||
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
|
||||
VITE_APP_RSA_PUBLIC_KEY = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvEDuRIOM3oZPWj9Ukoc5pQklR4PFH6/clnjeFqjDLIgDyQvjxhgqAZQA+E9eD6qu6FsXPmK8djcL+nh3cFHz4pX473jDvO3Sve+8yL3VRQ0n2pRgQ2a01MJsy+WwTZCBYWf0VnLRIvANUoWQgy9vz94q7Va44dg7A1/3ICf+xAwIDAQAB'
|
||||
VITE_APP_RSA_PUBLIC_KEY='MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvEDuRIOM3oZPWj9Ukoc5pQklR4PFH6/clnjeFqjDLIgDyQvjxhgqAZQA+E9eD6qu6FsXPmK8djcL+nh3cFHz4pX473jDvO3Sve+8yL3VRQ0n2pRgQ2a01MJsy+WwTZCBYWf0VnLRIvANUoWQgy9vz94q7Va44dg7A1/3ICf+xAwIDAQAB'
|
||||
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换
|
||||
VITE_APP_RSA_PRIVATE_KEY = 'MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAObq7yrxfvyieZtTjAYyrdvi59tYTXxjO5ajmPCRSXBY9M9wQ1tli297JN6mnY53UJMNyOFNSZVi8WSFoIXjpR87FmvChJlzeN/dZdd3SEs48Ee66XKeSePYqxa8oO5GKDsnajgpsOHKXSeeVSIysiIPS2/WsEqk0In9P4w3RsRFAgMBAAECgYBiMEWwce24SPICnRzuScBpvmsudrbEDIH7BOd0a6LZlcnLJwZNJ7mJlshPsHNQb+WgEf135+BBGEhioPtn0yuTdEuKP4kB9UdYUKiayWCoWhJpesv7sAD4RDClV7dhuV+gcd1AXD+YzyRIPbGm0VC2U+4q8/+UPRpVjqskbLVTgQJBAPRpou7g3S8n4XB527kq0D8I3+ZYwMxZhszwhrCDpJU319+ucmpLVwYIzDmZVeID2QQdUaDfIEViFHu95xDrGiUCQQDx3YOKn3yaEctk/ERVn7hDAyAXUbd8/pv2b24/M/l1ZevlsFem8U4Jk5Mu64t3z3YGJoymEjQmbucwT01iKhehAkEAxlnccsRmfFh/KkqauKE4M4++NTAd9zlInpUsmZ+cN8UEGnF2RTEzRKBrLOt1uWCqBB7PGiE6DVTVjr7FAQPrSQJAT5yeY87DcONSk9cFlzmPqV8p/QME5rvYEnHzVBKDlkUKNPyqnWToTvaoh9U4fyNmsfeWbEOprszqhFhWHG3GgQJAK8+ynmyFhaw63+Hx2KU5zR4hVuQso2IzrEurGxCxybV6mR7VBerb4502+EPx3PmOgxQL+niUFhcMWxcvBFP9+A=='
|
||||
VITE_APP_RSA_PRIVATE_KEY='MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAObq7yrxfvyieZtTjAYyrdvi59tYTXxjO5ajmPCRSXBY9M9wQ1tli297JN6mnY53UJMNyOFNSZVi8WSFoIXjpR87FmvChJlzeN/dZdd3SEs48Ee66XKeSePYqxa8oO5GKDsnajgpsOHKXSeeVSIysiIPS2/WsEqk0In9P4w3RsRFAgMBAAECgYBiMEWwce24SPICnRzuScBpvmsudrbEDIH7BOd0a6LZlcnLJwZNJ7mJlshPsHNQb+WgEf135+BBGEhioPtn0yuTdEuKP4kB9UdYUKiayWCoWhJpesv7sAD4RDClV7dhuV+gcd1AXD+YzyRIPbGm0VC2U+4q8/+UPRpVjqskbLVTgQJBAPRpou7g3S8n4XB527kq0D8I3+ZYwMxZhszwhrCDpJU319+ucmpLVwYIzDmZVeID2QQdUaDfIEViFHu95xDrGiUCQQDx3YOKn3yaEctk/ERVn7hDAyAXUbd8/pv2b24/M/l1ZevlsFem8U4Jk5Mu64t3z3YGJoymEjQmbucwT01iKhehAkEAxlnccsRmfFh/KkqauKE4M4++NTAd9zlInpUsmZ+cN8UEGnF2RTEzRKBrLOt1uWCqBB7PGiE6DVTVjr7FAQPrSQJAT5yeY87DcONSk9cFlzmPqV8p/QME5rvYEnHzVBKDlkUKNPyqnWToTvaoh9U4fyNmsfeWbEOprszqhFhWHG3GgQJAK8+ynmyFhaw63+Hx2KU5zR4hVuQso2IzrEurGxCxybV6mR7VBerb4502+EPx3PmOgxQL+niUFhcMWxcvBFP9+A=='
|
||||
|
||||
# 客户端id
|
||||
VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
|
||||
VITE_APP_CLIENT_ID='e5cd7e4891bf95d1d19206ce24a7b32e'
|
||||
|
||||
# 统一消息推送开关
|
||||
VITE_APP_MESSAGE_ENABLED = true
|
||||
VITE_APP_MESSAGE_ENABLED=true
|
||||
|
||||
# sse / websocket
|
||||
VITE_APP_MESSAGE_TRANSPORT = 'sse'
|
||||
VITE_APP_MESSAGE_TRANSPORT='sse'
|
||||
|
||||
# 统一消息推送路径
|
||||
VITE_APP_MESSAGE_PATH = '/resource/message'
|
||||
VITE_APP_MESSAGE_PATH='/resource/message'
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
& .el-sub-menu {
|
||||
overflow: hidden;
|
||||
border-radius: var(--app-radius-md);
|
||||
//border-radius: var(--app-radius-md);
|
||||
|
||||
.el-sub-menu__title,
|
||||
.el-sub-menu__title.el-tooltip__trigger {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
.sidebar-container {
|
||||
.theme-dark .submenu-title-noDropdown,
|
||||
.theme-dark .el-sub-menu__title {
|
||||
border-radius: var(--app-radius-md);
|
||||
//border-radius: var(--app-radius-md);
|
||||
margin: 3px 8px;
|
||||
height: 40px;
|
||||
|
||||
@@ -18,11 +18,10 @@
|
||||
|
||||
.submenu-title-noDropdown,
|
||||
.el-sub-menu__title {
|
||||
border-radius: var(--app-radius-md);
|
||||
margin: 3px 8px;
|
||||
//border-radius: var(--app-radius-md);
|
||||
margin: 0;
|
||||
height: 40px;
|
||||
color: var(--side-menu-text) !important;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--side-menu-hover-bg) !important;
|
||||
color: var(--side-menu-hover-text) !important;
|
||||
@@ -36,10 +35,11 @@
|
||||
& .nest-menu .el-sub-menu > .el-sub-menu__title,
|
||||
& .el-sub-menu .el-menu-item {
|
||||
min-width: calc($base-sidebar-width - 16px) !important;
|
||||
border-radius: var(--app-radius-md);
|
||||
//border-radius: var(--app-radius-md);
|
||||
height: 40px;
|
||||
margin: 3px 8px;
|
||||
background: transparent !important;
|
||||
//margin: 3px 8px;
|
||||
//background: transparent !important;
|
||||
background: #f7f7f7 !important;
|
||||
|
||||
&:not(.is-active):hover {
|
||||
background-color: var(--side-menu-hover-bg) !important;
|
||||
@@ -49,11 +49,11 @@
|
||||
|
||||
& .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
|
||||
& .theme-dark .el-sub-menu .el-menu-item {
|
||||
border-radius: var(--app-radius-md);
|
||||
//border-radius: var(--app-radius-md);
|
||||
height: 40px;
|
||||
margin: 3px 8px;
|
||||
background: transparent !important;
|
||||
|
||||
//margin: 3px 8px;
|
||||
//background: transparent !important;
|
||||
background: #f7f7f7 !important;
|
||||
&.is-active {
|
||||
background: var(--side-menu-active-bg) !important;
|
||||
color: var(--side-menu-active-text) !important;
|
||||
@@ -70,11 +70,11 @@
|
||||
|
||||
& .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
|
||||
& .theme-dark .el-menu-item {
|
||||
border-radius: var(--app-radius-md);
|
||||
//border-radius: var(--app-radius-md);
|
||||
height: 40px;
|
||||
margin: 3px 8px;
|
||||
background: transparent !important;
|
||||
|
||||
//margin: 3px 8px;
|
||||
//background: transparent !important;
|
||||
background: #f7f7f7 !important;
|
||||
&.is-active {
|
||||
background: var(--side-menu-active-bg) !important;
|
||||
color: var(--side-menu-active-text) !important;
|
||||
@@ -91,17 +91,17 @@
|
||||
|
||||
& .nest-menu .el-sub-menu > .el-sub-menu__title,
|
||||
& .el-menu-item {
|
||||
border-radius: var(--app-radius-md);
|
||||
//border-radius: var(--app-radius-md);
|
||||
height: 40px;
|
||||
margin: 3px 8px;
|
||||
//margin: 0 8px;
|
||||
background: transparent !important;
|
||||
|
||||
&.is-active {
|
||||
background: var(--side-menu-active-bg) !important;
|
||||
color: var(--side-menu-active-text) !important;
|
||||
box-shadow:
|
||||
inset 3px 0 0 var(--side-menu-active-line),
|
||||
inset 0 0 0 1px var(--side-menu-active-border);
|
||||
//box-shadow:
|
||||
// inset 3px 0 0 var(--side-menu-active-line),
|
||||
// inset 0 0 0 1px var(--side-menu-active-border);
|
||||
}
|
||||
|
||||
&:not(.is-active):hover {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
position: fixed;
|
||||
font-size: 0;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
//left: 12px;
|
||||
z-index: 1001;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
|
||||
@@ -39,4 +39,4 @@ $--color-warning: $color-warning;
|
||||
$--color-danger: $color-danger;
|
||||
$--color-info: $color-info;
|
||||
|
||||
$base-sidebar-width: 200px;
|
||||
$base-sidebar-width: 244px;
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
<el-breadcrumb class="app-breadcrumb" separator="/">
|
||||
<transition-group name="breadcrumb">
|
||||
<el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
|
||||
<span v-if="item.redirect === 'noRedirect' || index == levelList.length - 1" class="no-redirect">
|
||||
{{ item.meta?.title }}
|
||||
</span>
|
||||
<span v-if="item.redirect === 'noRedirect' || index == levelList.length - 1"
|
||||
class="no-redirect"> {{ item.meta?.title }} </span>
|
||||
<a v-else @click.prevent="handleLink(item)">{{ item.meta?.title }}</a>
|
||||
</el-breadcrumb-item>
|
||||
</transition-group>
|
||||
@@ -37,7 +36,7 @@ const getBreadcrumb = () => {
|
||||
}
|
||||
// 判断是否为首页
|
||||
if (!isDashboard(matched[0])) {
|
||||
matched = [{ path: '/index', meta: { title: '首页' } }].concat(matched);
|
||||
// matched = [{ path: '/index', meta: { title: '首页' } }].concat(matched);
|
||||
}
|
||||
levelList.value = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false);
|
||||
};
|
||||
@@ -82,14 +81,22 @@ onMounted(() => {
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
line-height: 1.2;
|
||||
margin-left: 4px;
|
||||
color: var(--app-text-muted);
|
||||
|
||||
:deep(.el-breadcrumb__separator) {
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
:deep(.el-breadcrumb__inner) {
|
||||
color: inherit;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
:deep(.el-breadcrumb__item .no-redirect) {
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.no-redirect {
|
||||
color: var(--app-text-title);
|
||||
cursor: text;
|
||||
|
||||
118
src/components/Echarts/index.vue
Normal file
118
src/components/Echarts/index.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div ref="chartRef" :style="{ width, height }" class="echarts-container"></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, onMounted, onBeforeUnmount, watch, nextTick, shallowRef} from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
import type {EChartsOption, ECharts} from 'echarts';
|
||||
|
||||
interface Props {
|
||||
width?: string;
|
||||
height?: string;
|
||||
options: EChartsOption;
|
||||
theme?: string;
|
||||
autoResize?: boolean;
|
||||
immediate?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
theme: '',
|
||||
autoResize: true,
|
||||
immediate: true
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
ready: [chart: ECharts];
|
||||
click: [params: echarts.ECElementEvent];
|
||||
rendered: [];
|
||||
}>();
|
||||
|
||||
const chartRef = ref<HTMLDivElement>();
|
||||
// 3. 使用 shallowRef 优化性能,因为 ECharts 实例不需要深层响应式
|
||||
const chartInstance = shallowRef<ECharts | null>(null);
|
||||
let resizeObserver: ResizeObserver | null = null;
|
||||
|
||||
const initChart = () => {
|
||||
if (!chartRef.value) return;
|
||||
|
||||
if (chartInstance.value) {
|
||||
chartInstance.value.dispose();
|
||||
chartInstance.value = null;
|
||||
}
|
||||
|
||||
const instance = echarts.init(chartRef.value, props.theme);
|
||||
chartInstance.value = instance;
|
||||
chartInstance.value.setOption(props.options);
|
||||
|
||||
emit('ready', instance);
|
||||
emit('rendered');
|
||||
|
||||
instance.on('click', (params: any) => {
|
||||
emit('click', params);
|
||||
});
|
||||
};
|
||||
|
||||
const updateChart = () => {
|
||||
if (chartInstance.value) {
|
||||
chartInstance.value.setOption(props.options, {
|
||||
notMerge: false,
|
||||
lazyUpdate: false
|
||||
});
|
||||
emit('rendered');
|
||||
}
|
||||
};
|
||||
|
||||
// ✅ 核心修复:支持 scale 缩放后重绘
|
||||
const resizeChart = () => {
|
||||
if (!chartInstance.value) return;
|
||||
chartInstance.value.resize();
|
||||
};
|
||||
|
||||
const getInstance = () => chartInstance.value;
|
||||
|
||||
const exportAsImage = (type: 'png' | 'svg' | 'jpeg' = 'png') => {
|
||||
return (
|
||||
chartInstance.value?.getDataURL({
|
||||
type,
|
||||
pixelRatio: 2,
|
||||
backgroundColor: '#fff'
|
||||
}) || ''
|
||||
);
|
||||
};
|
||||
|
||||
watch(() => props.options, updateChart, {deep: true});
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => initChart());
|
||||
if (props.autoResize && chartRef.value) {
|
||||
resizeObserver = new ResizeObserver(resizeChart);
|
||||
resizeObserver.observe(chartRef.value);
|
||||
}
|
||||
window.addEventListener('resize', resizeChart);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', resizeChart);
|
||||
resizeObserver?.disconnect();
|
||||
chartInstance.value?.dispose();
|
||||
chartInstance.value = null;
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
resizeChart,
|
||||
updateChart,
|
||||
getInstance,
|
||||
exportAsImage
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.echarts-container {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
24
src/components/Pagecrumb/index.vue
Normal file
24
src/components/Pagecrumb/index.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="router_title">{{ route.meta.title }}</div>
|
||||
<Breadcrumb class="breadcrumb"></Breadcrumb>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Breadcrumb from '@/components/Breadcrumb/index.vue'
|
||||
|
||||
const route = useRoute();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.router_title {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
border-radius: 0 0 0 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -53,7 +53,7 @@ function addIframe() {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: 12px;
|
||||
//padding: 12px;
|
||||
|
||||
&:fullscreen,
|
||||
&:-webkit-full-screen,
|
||||
|
||||
@@ -12,25 +12,17 @@
|
||||
<router-link v-else-if="showLogo" to="/" class="navtop-logo-shell">
|
||||
<img :src="appLogo" class="navtop-logo-icon" alt="logo" />
|
||||
</router-link>
|
||||
|
||||
<div class="nav-context">
|
||||
<breadcrumb v-if="navType == NavTypeEnum.LEFT" id="breadcrumb-container" class="breadcrumb-container" />
|
||||
<top-nav v-if="navType == NavTypeEnum.MIX" id="topmenu-container" class="topmenu-container" />
|
||||
|
||||
<template v-if="navType == NavTypeEnum.TOP">
|
||||
<top-bar id="topbar-container" class="topbar-container" />
|
||||
</template>
|
||||
<breadcrumb v-if="navType == NavTypeEnum.LEFT && isDashboard(route)" id="breadcrumb-container"
|
||||
class="breadcrumb-container"/>
|
||||
<!-- <top-nav v-if="navType == NavTypeEnum.MIX" id="topmenu-container" class="topmenu-container" />-->
|
||||
<!-- <template v-if="navType == NavTypeEnum.TOP">-->
|
||||
<!-- <top-bar id="topbar-container" class="topbar-container" />-->
|
||||
<!-- </template>-->
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-menu flex align-center">
|
||||
<template v-if="appStore.device !== 'mobile'">
|
||||
<search-menu ref="searchMenuRef" />
|
||||
<el-tooltip content="搜索" effect="dark" placement="bottom">
|
||||
<div class="right-menu-item hover-effect" @click="openSearchMenu">
|
||||
<svg-icon class-name="search-icon" icon-class="search" />
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<!-- 消息 -->
|
||||
<el-tooltip :content="$t('navbar.message')" effect="dark" placement="bottom">
|
||||
<div>
|
||||
<el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false">
|
||||
@@ -47,25 +39,6 @@
|
||||
</el-popover>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="Github" effect="dark" placement="bottom">
|
||||
<ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip :content="$t('navbar.document')" effect="dark" placement="bottom">
|
||||
<ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" />
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip :content="$t('navbar.full')" effect="dark" placement="bottom">
|
||||
<screenfull id="screenfull" class="right-menu-item hover-effect" />
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip :content="$t('navbar.language')" effect="dark" placement="bottom">
|
||||
<lang-select id="lang-select" class="right-menu-item hover-effect" />
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip :content="$t('navbar.layoutSize')" effect="dark" placement="bottom">
|
||||
<size-select id="size-select" class="right-menu-item hover-effect" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<div class="avatar-container">
|
||||
<el-dropdown class="avatar-dropdown" trigger="click" @command="handleCommand">
|
||||
@@ -82,9 +55,9 @@
|
||||
<router-link to="/user/profile">
|
||||
<el-dropdown-item>{{ $t('navbar.personalCenter') }}</el-dropdown-item>
|
||||
</router-link>
|
||||
<el-dropdown-item v-if="settingsStore.showSettings" command="setLayout">
|
||||
<span>{{ $t('navbar.layoutSetting') }}</span>
|
||||
</el-dropdown-item>
|
||||
<!-- <el-dropdown-item v-if="settingsStore.showSettings" command="setLayout">-->
|
||||
<!-- <span>{{ $t('navbar.layoutSetting') }}</span>-->
|
||||
<!-- </el-dropdown-item>-->
|
||||
<el-dropdown-item divided command="logout">
|
||||
<span>{{ $t('navbar.logout') }}</span>
|
||||
</el-dropdown-item>
|
||||
@@ -110,7 +83,9 @@ import { useUserStore } from '@/store/modules/user';
|
||||
import notice from './notice/index.vue';
|
||||
import TopBar from './TopBar/index.vue';
|
||||
import SearchMenu from './TopBar/search.vue';
|
||||
import {RouteLocationMatched, RouteLocationNormalizedLoadedGeneric} from "vue-router";
|
||||
|
||||
const route = useRoute();
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
@@ -120,6 +95,13 @@ const navType = computed(() => settingsStore.navType);
|
||||
const showLogo = computed(() => settingsStore.sidebarLogo);
|
||||
const displayName = computed(() => userStore.nickname || '管理员');
|
||||
|
||||
const isDashboard = (route: RouteLocationNormalizedLoadedGeneric) => {
|
||||
const name = route && (route.name as string);
|
||||
if (!name) {
|
||||
return false;
|
||||
}
|
||||
return name.trim() === 'Index';
|
||||
};
|
||||
// 搜索菜单
|
||||
const searchMenuRef = ref<InstanceType<typeof SearchMenu>>();
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ const logoTextColor = computed(() => (isDarkSide.value ? '#f8fbff' : 'var(--app-
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
height: 46px;
|
||||
line-height: 46px;
|
||||
//line-height: 46px;
|
||||
padding: 0 8px;
|
||||
margin-top: 8px;
|
||||
background: transparent;
|
||||
@@ -70,8 +70,8 @@ const logoTextColor = computed(() => (isDarkSide.value ? '#f8fbff' : 'var(--app-
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
border-radius: 14px;
|
||||
background: v-bind(logoSurface);
|
||||
border: 1px solid v-bind(logoBorder);
|
||||
//background: v-bind(logoSurface);
|
||||
//border: 1px solid v-bind(logoBorder);
|
||||
|
||||
& .sidebar-logo {
|
||||
width: 28px;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
>
|
||||
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
|
||||
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
|
||||
<svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" />
|
||||
<!-- <svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" />-->
|
||||
<template #title>
|
||||
<span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{ onlyOneChild.meta.title }}</span>
|
||||
</template>
|
||||
|
||||
@@ -63,8 +63,8 @@ const menuStyle = computed(() => ({
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 10px 8px 12px;
|
||||
border: 1px solid var(--app-sidebar-border);
|
||||
border-radius: var(--app-radius-base);
|
||||
//border: 1px solid var(--app-sidebar-border);
|
||||
//border-radius: var(--app-radius-base);
|
||||
box-shadow: var(--app-shadow-sm);
|
||||
background: v-bind(bgColor) !important;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -86,7 +86,8 @@ const setLayout = () => {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: var(--app-shell-bg);
|
||||
//background: var(--app-shell-bg);
|
||||
background: #fff;
|
||||
|
||||
&.mobile.openSidebar {
|
||||
position: fixed;
|
||||
@@ -110,7 +111,7 @@ const setLayout = () => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
padding: 12px 12px 0;
|
||||
//padding: 12px 12px 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ export const constantRoutes: RouteRecordRaw[] = [
|
||||
children: [
|
||||
{
|
||||
path: '/index',
|
||||
component: () => import('@/views/index.vue'),
|
||||
component: () => import('@/views/index/index.vue'),
|
||||
name: 'Index',
|
||||
meta: { title: '首页', icon: 'dashboard', affix: true }
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ const setting: DefaultSettings = {
|
||||
/**
|
||||
* 侧边栏主题 深色主题theme-dark,浅色主题theme-light
|
||||
*/
|
||||
sideTheme: 'theme-dark',
|
||||
sideTheme: 'theme-light',
|
||||
/**
|
||||
* 是否系统布局配置
|
||||
*/
|
||||
@@ -26,7 +26,7 @@ const setting: DefaultSettings = {
|
||||
/**
|
||||
* 是否显示 tagsView
|
||||
*/
|
||||
tagsView: true,
|
||||
tagsView: false,
|
||||
|
||||
/**
|
||||
* 持久化标签页
|
||||
@@ -36,12 +36,12 @@ const setting: DefaultSettings = {
|
||||
/**
|
||||
* 显示页签图标
|
||||
*/
|
||||
tagsIcon: true,
|
||||
tagsIcon: false,
|
||||
|
||||
/**
|
||||
* 是否固定头部
|
||||
*/
|
||||
fixedHeader: true,
|
||||
fixedHeader: false,
|
||||
|
||||
/**
|
||||
* 是否显示logo
|
||||
|
||||
@@ -1,370 +0,0 @@
|
||||
<template>
|
||||
<div class="home">
|
||||
<section class="hero-panel">
|
||||
<div class="hero-copy">
|
||||
<h1>RuoYi-Vue-Plus 控制台</h1>
|
||||
<p>
|
||||
企业级后台管理系统 重写 RuoYi-Vue 所有功能 集成 Sa-Token、Mybatis-Plus、WarmFlow、SpringDoc、Hutool、OSS
|
||||
等组件
|
||||
</p>
|
||||
<div class="hero-actions">
|
||||
<el-button type="primary" @click="goTarget('https://gitee.com/dromara/RuoYi-Vue-Plus')">查看源码</el-button>
|
||||
<el-button plain @click="goTarget('https://plus-doc.dromara.org/#/ruoyi-vue-plus/changlog')">
|
||||
更新日志
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="content-grid">
|
||||
<section class="section-card">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h2>项目矩阵</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="product-list">
|
||||
<article v-for="product in products" :key="product.name" class="product-card">
|
||||
<div class="product-top">
|
||||
<div>
|
||||
<h3>{{ product.name }}</h3>
|
||||
<p>{{ product.summary }}</p>
|
||||
</div>
|
||||
<span class="product-version">{{ product.version }}</span>
|
||||
</div>
|
||||
<div class="product-tags">
|
||||
<el-tag v-for="tag in product.tags" :key="tag" effect="plain">{{ tag }}</el-tag>
|
||||
</div>
|
||||
<div class="product-actions">
|
||||
<el-button type="primary" plain @click="goTarget(product.primaryUrl)">
|
||||
{{ product.primaryLabel }}
|
||||
</el-button>
|
||||
<el-button plain @click="goTarget(product.secondaryUrl)">{{ product.secondaryLabel }}</el-button>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section-card capability-card">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h2>能力地图</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="capability-groups">
|
||||
<article v-for="group in capabilityGroups" :key="group.title" class="capability-group">
|
||||
<h3>{{ group.title }}</h3>
|
||||
<ul>
|
||||
<li v-for="item in group.items" :key="item">{{ item }}</li>
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="Index" lang="ts">
|
||||
const products = [
|
||||
{
|
||||
name: 'RuoYi-Vue-Plus',
|
||||
version: 'v6.0.0',
|
||||
summary: '面向分布式集群场景的后台管理系统,保持现有业务接口与权限逻辑,适合先完成前端壳升级。',
|
||||
tags: ['Vue 3', 'Element Plus', 'Spring Boot', 'Sa-Token'],
|
||||
primaryLabel: '访问 GitHub',
|
||||
primaryUrl: 'https://github.com/dromara/RuoYi-Vue-Plus',
|
||||
secondaryLabel: '查看更新日志',
|
||||
secondaryUrl: 'https://plus-doc.dromara.org/#/ruoyi-vue-plus/changlog'
|
||||
},
|
||||
{
|
||||
name: 'RuoYi-Cloud-Plus',
|
||||
version: 'v6.0.0',
|
||||
summary: '微服务通用权限管理系统,适合更复杂的服务治理场景,也可以沿用同样的前端升级思路。',
|
||||
tags: ['Spring Cloud', 'Gateway', 'Nacos', 'Dubbo'],
|
||||
primaryLabel: '访问 GitHub',
|
||||
primaryUrl: 'https://github.com/dromara/RuoYi-Cloud-Plus',
|
||||
secondaryLabel: '查看更新日志',
|
||||
secondaryUrl: 'https://plus-doc.dromara.org/#/ruoyi-cloud-plus/changlog'
|
||||
}
|
||||
];
|
||||
|
||||
const capabilityGroups = [
|
||||
{
|
||||
title: '后端基建',
|
||||
items: ['Spring Boot / Spring Cloud', 'Sa-Token 认证与权限', 'MySQL / Redis', '代码生成器']
|
||||
},
|
||||
{
|
||||
title: '平台能力',
|
||||
items: ['动态菜单与按钮权限', '监控、日志、在线用户', '任务调度与工作流', '文件存储与多云适配']
|
||||
},
|
||||
{
|
||||
title: '前端方向',
|
||||
items: ['UI 卡片化', '主题与布局统一', '通用页面容器规范化', '企业化布局']
|
||||
}
|
||||
];
|
||||
|
||||
const goTarget = (url: string) => {
|
||||
window.open(url, '__blank');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.home {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.hero-panel,
|
||||
.section-card {
|
||||
border-radius: 28px;
|
||||
border: 1px solid var(--app-surface-border);
|
||||
background: var(--app-surface-bg);
|
||||
box-shadow: var(--app-shadow-sm);
|
||||
backdrop-filter: blur(18px);
|
||||
}
|
||||
|
||||
.hero-panel {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.3fr) minmax(280px, 0.7fr);
|
||||
gap: 18px;
|
||||
padding: 30px;
|
||||
background: radial-gradient(circle at top left, rgba(53, 109, 255, 0.16), transparent 30%), var(--app-surface-bg);
|
||||
}
|
||||
|
||||
.hero-copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: clamp(30px, 4vw, 46px);
|
||||
line-height: 1.06;
|
||||
letter-spacing: -0.04em;
|
||||
color: var(--app-text-title);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
max-width: 760px;
|
||||
color: var(--app-text-muted);
|
||||
font-size: 15px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-badge {
|
||||
display: inline-flex;
|
||||
width: fit-content;
|
||||
padding: 8px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(53, 109, 255, 0.12);
|
||||
color: var(--app-accent-strong);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.hero-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.hero-stats {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
padding: 18px 20px;
|
||||
border-radius: 22px;
|
||||
background: var(--app-elevated-soft-bg);
|
||||
border: 1px solid var(--app-surface-border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
|
||||
strong {
|
||||
color: var(--app-text-title);
|
||||
font-size: 24px;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
|
||||
span {
|
||||
color: var(--app-text-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.content-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.15fr) minmax(300px, 0.85fr);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.section-card {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.section-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 18px;
|
||||
|
||||
h2 {
|
||||
margin: 6px 0 0;
|
||||
color: var(--app-text-title);
|
||||
font-size: 26px;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
}
|
||||
|
||||
.section-kicker {
|
||||
color: var(--app-text-muted);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.product-list,
|
||||
.capability-groups {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.product-card {
|
||||
padding: 22px;
|
||||
border-radius: 24px;
|
||||
background: var(--app-elevated-soft-bg);
|
||||
border: 1px solid var(--app-surface-border);
|
||||
transition:
|
||||
transform 0.25s ease,
|
||||
box-shadow 0.25s ease,
|
||||
border-color 0.25s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--app-shadow-sm);
|
||||
border-color: rgba(53, 109, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.product-top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
|
||||
h3 {
|
||||
margin: 0 0 8px;
|
||||
color: var(--app-text-title);
|
||||
font-size: 22px;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: var(--app-text-muted);
|
||||
line-height: 1.8;
|
||||
}
|
||||
}
|
||||
|
||||
.product-version {
|
||||
flex-shrink: 0;
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(53, 109, 255, 0.12);
|
||||
color: var(--app-accent-strong);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.product-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.product-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.capability-card {
|
||||
background: radial-gradient(circle at top right, rgba(14, 165, 233, 0.12), transparent 28%), var(--app-surface-bg);
|
||||
}
|
||||
|
||||
.capability-group {
|
||||
padding: 18px 18px 18px 20px;
|
||||
border-radius: 22px;
|
||||
background: var(--app-elevated-soft-bg);
|
||||
border: 1px solid var(--app-surface-border);
|
||||
|
||||
h3 {
|
||||
margin: 0 0 12px;
|
||||
color: var(--app-text-title);
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
padding-left: 16px;
|
||||
color: var(--app-text-muted);
|
||||
line-height: 1.7;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 10px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: var(--app-accent-strong);
|
||||
box-shadow: 0 0 0 5px rgba(53, 109, 255, 0.12);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.hero-panel,
|
||||
.content-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.hero-panel,
|
||||
.section-card {
|
||||
padding: 20px;
|
||||
border-radius: 22px;
|
||||
}
|
||||
|
||||
.product-top {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
html.dark {
|
||||
.hero-panel {
|
||||
background: radial-gradient(circle at top left, rgba(53, 109, 255, 0.18), transparent 30%), var(--app-surface-bg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
99
src/views/index/compontents/activeDevices.vue
Normal file
99
src/views/index/compontents/activeDevices.vue
Normal file
@@ -0,0 +1,99 @@
|
||||
<script lang="ts" name="activeDevices" setup>
|
||||
import Echarts from '@/components/Echarts/index.vue';
|
||||
|
||||
const options = {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [120, 200, 150, 80, 70, 110, 130],
|
||||
type: 'bar'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="Box">
|
||||
<div class="NavBox">
|
||||
<span>近7天设备激活量</span>
|
||||
<div class="actions">
|
||||
<div>
|
||||
<div class="title">本周新增</div>
|
||||
<div class="num">3005</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="title">日均激活</div>
|
||||
<div class="num day">573</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ChartBox">
|
||||
<Echarts :options="options"></Echarts>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.Box {
|
||||
padding: 25px;
|
||||
height: 372px;
|
||||
border-radius: 16px 16px 16px 16px;
|
||||
border: 1px solid rgba(238, 240, 243, 1);
|
||||
|
||||
.NavBox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 36px;
|
||||
|
||||
span {
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
border-radius: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 25px;
|
||||
|
||||
.title {
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
border-radius: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.num {
|
||||
width: 49px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
border-radius: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 18px;
|
||||
|
||||
&.day {
|
||||
color: rgba(37, 99, 235, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ChartBox {
|
||||
height: 330px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
98
src/views/index/compontents/areaDevices.vue
Normal file
98
src/views/index/compontents/areaDevices.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<script lang="ts" name="AreaDevices" setup>
|
||||
import {ref} from 'vue';
|
||||
import {ElProgress} from 'element-plus';
|
||||
|
||||
const list = ref([
|
||||
{
|
||||
areaName: '华东区',
|
||||
num: '3002台',
|
||||
percent: 23,
|
||||
},
|
||||
{
|
||||
areaName: '华北区',
|
||||
num: '8002台',
|
||||
percent: 50,
|
||||
},
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="Box">
|
||||
<div class="NavBox">
|
||||
<div class="TypeNavBox">区域设备分布</div>
|
||||
<div class="subTypeNavBox">共 12,860 台在线设备</div>
|
||||
</div>
|
||||
<div class="ChartBox">
|
||||
<div v-for="(item,index) in list" :key="index" style="margin-bottom: 18px">
|
||||
<div class="flex justify-between progressInfo">
|
||||
<div>{{ item.areaName }}</div>
|
||||
<div class="flex">
|
||||
<div class="num">{{ item.num }}</div>
|
||||
<div class="percent">{{ item.percent }}%</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-progress :percentage="item.percent" :show-text="false"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.Box {
|
||||
padding: 25px;
|
||||
height: 372px;
|
||||
border-radius: 16px 16px 16px 16px;
|
||||
border: 1px solid rgba(238, 240, 243, 1);
|
||||
|
||||
.NavBox {
|
||||
height: 36px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.TypeNavBox {
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
border-radius: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.subTypeNavBox {
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
border-radius: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.ChartBox {
|
||||
height: 310px;
|
||||
overflow-y: auto;
|
||||
|
||||
.progressInfo {
|
||||
margin-bottom: 8px;
|
||||
|
||||
.num {
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
border-radius: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.percent {
|
||||
margin-left: 12px;
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
154
src/views/index/compontents/card.vue
Normal file
154
src/views/index/compontents/card.vue
Normal file
@@ -0,0 +1,154 @@
|
||||
<script lang="ts" name="Cards" setup>
|
||||
const cardList = ref([
|
||||
{
|
||||
title: '环境数据',
|
||||
num: 12.3,
|
||||
unit: '台',
|
||||
rate: '32.2%',
|
||||
upRate: true,
|
||||
circleColor: "#2563eb",
|
||||
color: "rgba(22, 163, 74, 1)",
|
||||
rateColor: "rgba(22, 163, 74, 0.1)",
|
||||
},
|
||||
{
|
||||
title: '设备在线率',
|
||||
num: 0.08,
|
||||
unit: '台',
|
||||
rate: '98.6%',
|
||||
upRate: false,
|
||||
circleColor: "#6366f1",
|
||||
color: "#ed3822",
|
||||
rateColor: "rgb(217 80 64 / 0.27)",
|
||||
},
|
||||
{
|
||||
title: '平均TVOC原始值',
|
||||
num: 0.08,
|
||||
unit: '台',
|
||||
rate: '98.6%',
|
||||
upRate: false,
|
||||
circleColor: "#6366f1",
|
||||
color: "#ed3822",
|
||||
rateColor: "rgb(217 80 64 / 0.27)",
|
||||
},
|
||||
{
|
||||
title: '今日设备激活',
|
||||
num: 0.08,
|
||||
unit: '台',
|
||||
rate: '98.6%',
|
||||
upRate: false,
|
||||
circleColor: "#6366f1",
|
||||
color: "#ed3822",
|
||||
rateColor: "rgb(217 80 64 / 0.27)",
|
||||
},
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="CardBox">
|
||||
<div v-for="(item,index) in cardList" :key="index" class="card">
|
||||
<div class="cardNav">
|
||||
<div class="cardTitle">
|
||||
<i :style="{backgroundColor:item.circleColor}" class="circle"></i>
|
||||
<span>{{ item.title }}</span>
|
||||
</div>
|
||||
<div :style="{backgroundColor:item.rateColor}" class="cardRate">
|
||||
<span v-if="item.upRate" :style="{color:item.color}" class="up">+{{ item.rate }}</span>
|
||||
<span v-else :style="{color:item.color}" class="down">-{{ item.rate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cardContent">
|
||||
<div class="cardNum">{{ item.num }}</div>
|
||||
<span>{{ item.unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.CardBox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
|
||||
.card {
|
||||
flex: 389px;
|
||||
max-width: 389px;
|
||||
height: 120px;
|
||||
border-radius: 16px 16px 16px 16px;
|
||||
border: 1px solid rgba(238, 240, 243, 1);
|
||||
padding: 21px;
|
||||
|
||||
.cardNav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.cardTitle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100px;
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: rgba(107, 114, 128, 1);
|
||||
font-size: 13px;
|
||||
|
||||
.circle {
|
||||
display: block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(0, 0, 0);
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.cardRate {
|
||||
width: 52px;
|
||||
height: 24px;
|
||||
border-radius: 12px;
|
||||
background-color: rgba(232, 247, 238, 1);
|
||||
|
||||
line-height: 24px;
|
||||
color: rgba(22, 163, 74, 1);
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
|
||||
|
||||
.up {
|
||||
color: rgba(22, 163, 74, 1);
|
||||
}
|
||||
|
||||
.down {
|
||||
color: rgba(22, 163, 74, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cardContent {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
margin-top: 20px;
|
||||
|
||||
.cardNum {
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
border-radius: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
span {
|
||||
width: 15px;
|
||||
height: 17px;
|
||||
line-height: 17px;
|
||||
border-radius: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: rgba(107, 114, 128, 1);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
113
src/views/index/compontents/tvoc.vue
Normal file
113
src/views/index/compontents/tvoc.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<script lang="ts" name="Tvoc" setup>
|
||||
import Echarts from '@/components/Echarts/index.vue';
|
||||
|
||||
const options = {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [150, 230, 224, 218, 135, 147, 260],
|
||||
type: 'line'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
const list = [
|
||||
{
|
||||
label: '24小时',
|
||||
value: 24,
|
||||
},
|
||||
{
|
||||
label: '7天',
|
||||
value: 7,
|
||||
},
|
||||
{
|
||||
label: '30天',
|
||||
value: 30,
|
||||
},
|
||||
// +30 300, 99/1000 69, 59/20g/500
|
||||
]
|
||||
const activeDay = ref(24);
|
||||
|
||||
function changeActive(val: number) {
|
||||
activeDay.value = val;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="TvocBox">
|
||||
<div class="TvocNavBox">
|
||||
<span>平均TVOC原始值</span>
|
||||
<div class="actions">
|
||||
<div v-for="(item,index) in list" :key="index" :class="{
|
||||
active: activeDay === item.value,
|
||||
}" class="actionItem" @click="changeActive(item.value)">{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="TvocChartBox">
|
||||
<Echarts :options="options"></Echarts>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.TvocBox {
|
||||
padding: 25px;
|
||||
height: 372px;
|
||||
border-radius: 16px 16px 16px 16px;
|
||||
border: 1px solid rgba(238, 240, 243, 1);
|
||||
|
||||
.TvocNavBox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 36px;
|
||||
|
||||
span {
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
border-radius: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
width: 175px;
|
||||
height: 36px;
|
||||
border-radius: 8px 8px 8px 8px;
|
||||
background-color: rgba(243, 245, 248, 1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
padding: 4px;
|
||||
|
||||
.actionItem {
|
||||
max-width: 64px;
|
||||
height: 28px;
|
||||
padding: 7px 12px;
|
||||
color: rgba(107, 114, 128, 1);
|
||||
font-size: 12px;
|
||||
|
||||
&.active {
|
||||
color: rgba(37, 99, 235, 1);
|
||||
border-radius: 6px 6px 6px 6px;
|
||||
background-color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.TvocChartBox {
|
||||
height: 330px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
108
src/views/index/compontents/type.vue
Normal file
108
src/views/index/compontents/type.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<script lang="ts" name="Type" setup>
|
||||
import Echarts from '@/components/Echarts/index.vue';
|
||||
|
||||
const data = genData(7);
|
||||
const options = ref({
|
||||
legend: {
|
||||
type: 'scroll',
|
||||
orient: 'vertical',
|
||||
right: 10,
|
||||
top: 20,
|
||||
bottom: 20,
|
||||
data: data.legendData
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '姓名',
|
||||
type: 'pie',
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
center: ['38%', '50%'],
|
||||
radius: ['40%', '70%'],
|
||||
data: data.seriesData,
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
function genData(count: number) {
|
||||
// prettier-ignore
|
||||
const nameList = [
|
||||
'赵', '钱', '孙', '李', '周', '吴', '郑', '王', '冯', '陈', '褚', '卫', '蒋', '沈', '韩', '杨', '朱', '秦', '尤', '许', '何', '吕', '施', '张', '孔', '曹', '严', '华', '金', '魏', '陶', '姜', '戚', '谢', '邹', '喻', '柏', '水', '窦', '章', '云', '苏', '潘', '葛', '奚', '范', '彭', '郎', '鲁', '韦', '昌', '马', '苗', '凤', '花', '方', '俞', '任', '袁', '柳', '酆', '鲍', '史', '唐', '费', '廉', '岑', '薛', '雷', '贺', '倪', '汤', '滕', '殷', '罗', '毕', '郝', '邬', '安', '常', '乐', '于', '时', '傅', '皮', '卞', '齐', '康', '伍', '余', '元', '卜', '顾', '孟', '平', '黄', '和', '穆', '萧', '尹', '姚', '邵', '湛', '汪', '祁', '毛', '禹', '狄', '米', '贝', '明', '臧', '计', '伏', '成', '戴', '谈', '宋', '茅', '庞', '熊', '纪', '舒', '屈', '项', '祝', '董', '梁', '杜', '阮', '蓝', '闵', '席', '季', '麻', '强', '贾', '路', '娄', '危'
|
||||
];
|
||||
const legendData = [];
|
||||
const seriesData = [];
|
||||
for (var i = 0; i < count; i++) {
|
||||
var name =
|
||||
Math.random() > 0.65
|
||||
? makeWord(4, 1) + '·' + makeWord(3, 0)
|
||||
: makeWord(2, 1);
|
||||
legendData.push(name);
|
||||
seriesData.push({
|
||||
name: name,
|
||||
value: Math.round(Math.random() * 100000)
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
legendData: legendData,
|
||||
seriesData: seriesData
|
||||
};
|
||||
|
||||
function makeWord(max: number, min: number) {
|
||||
const nameLen = Math.ceil(Math.random() * max + min);
|
||||
const name = [];
|
||||
for (var i = 0; i < nameLen; i++) {
|
||||
name.push(nameList[Math.round(Math.random() * nameList.length - 1)]);
|
||||
}
|
||||
return name.join('');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="TypeBox">
|
||||
<div class="TypeNavBox">设备类型分布</div>
|
||||
<div class="subTypeNavBox">共 12,860 台在线设备</div>
|
||||
<div class="TypeChartBox">
|
||||
<Echarts :options="options"></Echarts>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.TypeBox {
|
||||
padding: 25px;
|
||||
height: 372px;
|
||||
border-radius: 16px 16px 16px 16px;
|
||||
border: 1px solid rgba(238, 240, 243, 1);
|
||||
|
||||
.TypeNavBox {
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
border-radius: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.subTypeNavBox {
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
border-radius: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.TypeChartBox {
|
||||
height: 330px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
83
src/views/index/index.vue
Normal file
83
src/views/index/index.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div class="home">
|
||||
<div class=" updateTimeBox">
|
||||
<div class="updateTime">数据更新于 {{ updateTime }}</div>
|
||||
<div class="tag">
|
||||
<div>环境数据为原始读数</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 卡片-->
|
||||
<Cards></Cards>
|
||||
<el-row :gutter="20">
|
||||
<el-col :sm="14" :xs="24">
|
||||
<Tvoc/>
|
||||
</el-col>
|
||||
<el-col :sm="10" :xs="24">
|
||||
<Type/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :sm="14" :xs="24">
|
||||
<ActiveDevices></ActiveDevices>
|
||||
</el-col>
|
||||
<el-col :sm="10" :xs="24">
|
||||
<AreaDevices></AreaDevices>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="Index" setup>
|
||||
import Cards from './compontents/card.vue';
|
||||
import Tvoc from './compontents/tvoc.vue';
|
||||
import Type from './compontents/type.vue';
|
||||
import AreaDevices from './compontents/areaDevices.vue';
|
||||
import ActiveDevices from './compontents/activeDevices.vue';
|
||||
|
||||
|
||||
const updateTime = ref('12:50');
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.home {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
|
||||
|
||||
.updateTimeBox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.updateTime {
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
border-radius: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: rgba(107, 114, 128, 1);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
width: 116px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
border-radius: 10px;
|
||||
background-color: rgba(240, 253, 250, 1);
|
||||
margin-left: 4px;
|
||||
|
||||
div {
|
||||
text-align: center;
|
||||
color: rgba(15, 118, 110, 1);
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.echartsBox {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -110,7 +110,7 @@
|
||||
</el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item v-if="captchaEnabled" prop="code" class="captcha-row">
|
||||
<el-form-item v-if="captchaEnabled" class="captcha-row" prop="code">
|
||||
<el-input
|
||||
v-model="loginForm.code"
|
||||
size="large"
|
||||
@@ -127,7 +127,7 @@
|
||||
<div class="login-code">
|
||||
<img :src="codeUrl" class="login-code-img" @click="getCode" />
|
||||
</div>
|
||||
</el-form-item> -->
|
||||
</el-form-item>
|
||||
<div class="form-meta">
|
||||
<el-checkbox v-model="loginForm.rememberMe">{{
|
||||
$t("login.rememberPassword")
|
||||
@@ -550,6 +550,7 @@ onMounted(() => {
|
||||
background-color: rgba(247, 248, 250, 1);
|
||||
border: 1px solid rgba(238, 240, 243, 1);
|
||||
.actions {
|
||||
cursor: pointer;
|
||||
color: rgba(37, 99, 235, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="p-2 app-container system-client-page">
|
||||
<Pagecrumb></Pagecrumb>
|
||||
<div class="search-wrap">
|
||||
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
|
||||
<template #header>
|
||||
@@ -26,7 +27,7 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="状态" clearable>
|
||||
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
@@ -36,7 +37,6 @@
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<el-card shadow="hover" class="table-panel">
|
||||
<template #header>
|
||||
<div class="toolbar-shell">
|
||||
@@ -82,19 +82,19 @@
|
||||
class="data-table"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column v-if="false" label="id" align="center" prop="id" />
|
||||
<el-table-column label="客户端id" align="center" prop="clientId" />
|
||||
<el-table-column label="客户端key" align="center" prop="clientKey" />
|
||||
<el-table-column label="客户端秘钥" align="center" prop="clientSecret" />
|
||||
<el-table-column align="center" type="selection" width="55"/>
|
||||
<el-table-column v-if="false" align="center" label="id" prop="id"/>
|
||||
<el-table-column align="center" label="客户端id" prop="clientId"/>
|
||||
<el-table-column align="center" label="客户端key" prop="clientKey"/>
|
||||
<el-table-column align="center" label="客户端秘钥" prop="clientSecret"/>
|
||||
<el-table-column label="授权类型" align="center">
|
||||
<template #default="scope">
|
||||
<dict-tag class="grant-type-tag" :options="sys_grant_type" :value="scope.row.grantTypeList" />
|
||||
<dict-tag :options="sys_grant_type" :value="scope.row.grantTypeList" class="grant-type-tag"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="设备类型" align="center">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sys_device_type" :value="scope.row.deviceType" />
|
||||
<dict-tag :options="sys_device_type" :value="scope.row.deviceType"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="白名单路径" align="center">
|
||||
@@ -132,8 +132,8 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Token活跃超时时间" align="center" prop="activeTimeout" />
|
||||
<el-table-column label="Token固定超时时间" align="center" prop="timeout" />
|
||||
<el-table-column align="center" label="Token活跃超时时间" prop="activeTimeout"/>
|
||||
<el-table-column align="center" label="Token固定超时时间" prop="timeout"/>
|
||||
<el-table-column key="status" label="状态" align="center">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
@@ -180,10 +180,10 @@
|
||||
<el-dialog v-model="dialog.visible" :title="dialog.title" width="760px" append-to-body>
|
||||
<el-form ref="clientFormRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="客户端key" prop="clientKey">
|
||||
<el-input v-model="form.clientKey" :disabled="form.id != null" placeholder="请输入客户端key" />
|
||||
<el-input v-model="form.clientKey" :disabled="form.id != null" placeholder="请输入客户端key"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="客户端秘钥" prop="clientSecret">
|
||||
<el-input v-model="form.clientSecret" :disabled="form.id != null" placeholder="请输入客户端秘钥" />
|
||||
<el-input v-model="form.clientSecret" :disabled="form.id != null" placeholder="请输入客户端秘钥"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="授权类型" prop="grantTypeList">
|
||||
<el-select v-model="form.grantTypeList" multiple placeholder="请输入授权类型">
|
||||
@@ -214,7 +214,7 @@
|
||||
允许访问路径
|
||||
</span>
|
||||
</template>
|
||||
<el-input v-model="form.accessPath" type="textarea" :rows="4" placeholder="示例:/app/**" />
|
||||
<el-input v-model="form.accessPath" :rows="4" placeholder="示例:/app/**" type="textarea"/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="ipWhitelist" label-width="auto">
|
||||
<template #label>
|
||||
@@ -244,7 +244,7 @@
|
||||
Token活跃超时时间
|
||||
</span>
|
||||
</template>
|
||||
<el-input v-model="form.activeTimeout" placeholder="请输入Token活跃超时时间" />
|
||||
<el-input v-model="form.activeTimeout" placeholder="请输入Token活跃超时时间"/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="timeout" label-width="auto">
|
||||
<template #label>
|
||||
@@ -255,7 +255,7 @@
|
||||
Token固定超时时间
|
||||
</span>
|
||||
</template>
|
||||
<el-input v-model="form.timeout" placeholder="请输入Token固定超时时间" />
|
||||
<el-input v-model="form.timeout" placeholder="请输入Token固定超时时间"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-radio-group v-model="form.status">
|
||||
@@ -286,6 +286,8 @@ import { useTableSelection } from '@/hooks/table/useTableSelection';
|
||||
import modal from '@/plugins/modal';
|
||||
import { useDict } from '@/utils/dict';
|
||||
import { download as requestDownload } from '@/utils/request';
|
||||
import Pagecrumb from '@/components/Pagecrumb/index.vue';
|
||||
|
||||
|
||||
const { sys_normal_disable } = toRefs<any>(useDict('sys_normal_disable'));
|
||||
const { sys_grant_type } = toRefs<any>(useDict('sys_grant_type'));
|
||||
|
||||
@@ -30,7 +30,7 @@ export default defineConfig(({ mode, command }) => {
|
||||
open: true,
|
||||
proxy: {
|
||||
[env.VITE_APP_BASE_API]: {
|
||||
target: 'http://localhost:8080',
|
||||
target: 'http://localhost:5660',
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
rewrite: path => path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '')
|
||||
|
||||
Reference in New Issue
Block a user