Compare commits

...

2 Commits

Author SHA1 Message Date
f4903d9eec Refactor UI components for improved layout and styling
- Updated VoiceConversation.vue to set a fixed font size for the header.
- Changed index.vue to use an aside element for the header and adjusted layout to a grid system.
- Enhanced lock.vue by restructuring the layout and improving visual elements for better user experience.
- Revamped login.vue to introduce a visual section and reorganized form elements for clarity.
- Redesigned register.vue to include a visual brand section and improved form layout for user registration.
2026-08-28 10:25:05 +08:00
5777d64821 refactor: update styles to use CSS variables for consistency and theming
- Replaced hardcoded colors and styles with CSS variables in multiple components including `index.vue`, `lock.vue`, `login.vue`, and `register.vue`.
- Improved layout and spacing in forms and containers for better responsiveness.
- Removed unused particle background animation from `lock.vue`.
- Added new SCSS file for Element Plus theme customization.
- Introduced a minimalist CSS file for global styles and variables.
2026-08-27 15:40:05 +08:00
59 changed files with 3524 additions and 1378 deletions

View File

@ -7,8 +7,8 @@ VITE_APP_ENV = 'development'
# 招商车研物联网平台/开发环境 # 招商车研物联网平台/开发环境
VITE_APP_BASE_API = '/dev-api' VITE_APP_BASE_API = '/dev-api'
VITE_API_URL = http://192.168.0.222:13080 VITE_API_URL = http://192.168.0.199:13080
VITE_WS_URL = ws://192.168.0.222:13080/ws VITE_WS_URL = ws://192.168.0.199:13080/ws
VITE_PORT = 80 VITE_PORT = 80
VITE_INSPECTION_TYPE = "inspection" VITE_INSPECTION_TYPE = "inspection"
VITE_MINIO_URL = "http://192.168.28.10:9000/cmvr-iot/" VITE_MINIO_URL = "http://192.168.28.10:9000/cmvr-iot/"

View File

@ -16,7 +16,7 @@ onMounted(() => {
<style lang="scss"> <style lang="scss">
//自定义样式覆盖el-table //自定义样式覆盖el-table
.el-table .el-table__header-wrapper th, .el-table .el-table__fixed-header-wrapper th { .el-table .el-table__header-wrapper th, .el-table .el-table__fixed-header-wrapper th {
background-color: var(--el-color-primary-light-9) !important; background-color: var(--min-bg-page) !important;
} }
//自定义搜索加载动画 //自定义搜索加载动画
@ -36,14 +36,14 @@ onMounted(() => {
.el-dialog { .el-dialog {
.task-title { .task-title {
height: 30px; height: 30px;
font-weight: 900; font-weight: 600;
} }
.custom-block { .custom-block {
padding: 8px 16px; padding: 8px 16px;
background-color: rgba(var(--el-color-primary-rgb), .1); background-color: rgba(var(--el-color-primary-rgb), .1);
border-radius: 4px; border-radius: 4px;
border-left: 5px solid var(--el-color-primary); border-left: 2px solid var(--min-primary);
margin-top: 16px; margin-top: 16px;
} }
@ -87,7 +87,7 @@ onMounted(() => {
&.file-upload { &.file-upload {
.el-upload { .el-upload {
border: 1px dashed var(--el-border-color); border: 1px dashed var(--el-border-color);
border-radius: 6px; border-radius: var(--min-radius-base);
cursor: pointer; cursor: pointer;
position: relative; position: relative;
overflow: hidden; overflow: hidden;

View File

@ -100,7 +100,7 @@ div:focus {
} }
aside { aside {
background: #eef1f6; background: var(--min-bg-page);
padding: 8px 24px; padding: 8px 24px;
margin-bottom: 20px; margin-bottom: 20px;
border-radius: 2px; border-radius: 2px;
@ -108,16 +108,16 @@ aside {
line-height: 32px; line-height: 32px;
font-size: 16px; font-size: 16px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
color: #2c3e50; color: var(--min-text-regular);
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
a { a {
color: #337ab7; color: var(--min-primary);
cursor: pointer; cursor: pointer;
&:hover { &:hover {
color: rgb(32, 160, 255); color: var(--min-primary-hover);
} }
} }
} }
@ -151,7 +151,7 @@ aside {
text-align: right; text-align: right;
padding-right: 20px; padding-right: 20px;
transition: 600ms ease position; transition: 600ms ease position;
background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%); background: var(--min-primary);
.subtitle { .subtitle {
font-size: 20px; font-size: 20px;
@ -169,11 +169,11 @@ aside {
.link-type, .link-type,
.link-type:focus { .link-type:focus {
color: #337ab7; color: var(--min-primary);
cursor: pointer; cursor: pointer;
&:hover { &:hover {
color: rgb(32, 160, 255); color: var(--min-primary-hover);
} }
} }

View File

@ -160,7 +160,7 @@
/** 表格更多操作下拉样式 */ /** 表格更多操作下拉样式 */
.el-table .el-dropdown-link { .el-table .el-dropdown-link {
cursor: pointer; cursor: pointer;
color: #409EFF; color: var(--min-primary);
margin-left: 10px; margin-left: 10px;
} }
@ -279,7 +279,8 @@
width: 200px; width: 200px;
height: 200px; height: 200px;
border-radius: 50%; border-radius: 50%;
box-shadow: 0 0 4px #ccc; border: 1px solid var(--min-border-light);
box-shadow: none;
overflow: hidden; overflow: hidden;
} }
@ -287,7 +288,7 @@
.sortable-ghost{ .sortable-ghost{
opacity: .8; opacity: .8;
color: #fff!important; color: #fff!important;
background: #42b983!important; background: var(--min-primary)!important;
} }
/* 表格右侧工具栏样式 */ /* 表格右侧工具栏样式 */

View File

@ -25,8 +25,8 @@
left: 0; left: 0;
z-index: 1001; z-index: 1001;
overflow: hidden; overflow: hidden;
-webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35); border-right: 1px solid var(--min-border-light);
box-shadow: 2px 0 6px rgba(0,21,41,.35); box-shadow: none;
// reset element-ui css // reset element-ui css
.horizontal-collapse-transition { .horizontal-collapse-transition {
@ -47,7 +47,7 @@
&.has-logo { &.has-logo {
.el-scrollbar { .el-scrollbar {
height: calc(100% - 50px); height: auto;
} }
} }
@ -62,7 +62,7 @@
} }
.svg-icon { .svg-icon {
margin-right: 16px; margin-right: 14px;
} }
.el-menu { .el-menu {
@ -85,7 +85,7 @@
.sub-menu-title-noDropdown, .sub-menu-title-noDropdown,
.el-sub-menu__title { .el-sub-menu__title {
&:hover { &:hover {
background-color: rgba(0, 0, 0, 0.06) !important; background-color: var(--menu-hover) !important;
} }
} }
@ -98,7 +98,7 @@
min-width: $base-sidebar-width !important; min-width: $base-sidebar-width !important;
&:hover { &:hover {
background-color: rgba(0, 0, 0, 0.06) !important; background-color: var(--menu-hover) !important;
} }
} }
@ -114,11 +114,11 @@
.hideSidebar { .hideSidebar {
.sidebar-container { .sidebar-container {
width: 54px !important; width: 56px !important;
} }
.main-container { .main-container {
margin-left: 54px; margin-left: 56px;
} }
.sub-menu-title-noDropdown { .sub-menu-title-noDropdown {
@ -129,7 +129,7 @@
padding: 0 !important; padding: 0 !important;
.svg-icon { .svg-icon {
margin-left: 20px; margin-left: 21px;
} }
} }
} }
@ -141,7 +141,7 @@
padding: 0 !important; padding: 0 !important;
.svg-icon { .svg-icon {
margin-left: 20px; margin-left: 21px;
} }
} }
@ -206,7 +206,7 @@
.el-menu--vertical { .el-menu--vertical {
&>.el-menu { &>.el-menu {
.svg-icon { .svg-icon {
margin-right: 16px; margin-right: 12px;
} }
} }
@ -214,7 +214,7 @@
.el-menu-item { .el-menu-item {
&:hover { &:hover {
// you can use $sub-menuHover // you can use $sub-menuHover
background-color: rgba(0, 0, 0, 0.06) !important; background-color: var(--menu-hover) !important;
} }
} }
@ -224,7 +224,7 @@
overflow-y: auto; overflow-y: auto;
&::-webkit-scrollbar-track-piece { &::-webkit-scrollbar-track-piece {
background: #d3dce6; background: var(--min-bg-page);
} }
&::-webkit-scrollbar { &::-webkit-scrollbar {
@ -232,8 +232,8 @@
} }
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
background: #99a9bf; background: var(--min-border-strong);
border-radius: 20px; border-radius: var(--min-radius-sm);
} }
} }
} }

View File

@ -9,30 +9,30 @@ $yellow: #fec171;
$panGreen: #30b08f; $panGreen: #30b08f;
// 默认主题变量 // 默认主题变量
$menuText: #bfcbd9; $menuText: #374151;
$menuActiveText: #409eff; $menuActiveText: #0052d9;
$menuBg: #304156; $menuBg: #ffffff;
$menuHover: #263445; $menuHover: #f8f9fa;
// 浅色主题theme-light // 浅色主题theme-light
$menuLightBg: #ffffff; $menuLightBg: #ffffff;
$menuLightHover: #f0f1f5; $menuLightHover: #f8f9fa;
$menuLightText: #303133; $menuLightText: #374151;
$menuLightActiveText: #409eff; $menuLightActiveText: #0052d9;
// 基础变量 // 基础变量
$base-sidebar-width: 200px; $base-sidebar-width: 248px;
$sideBarWidth: 200px; $sideBarWidth: 248px;
// 菜单暗色变量 // 菜单暗色变量
$base-menu-color: #bfcbd9; $base-menu-color: #374151;
$base-menu-color-active: #f4f4f5; $base-menu-color-active: #0052d9;
$base-menu-background: #304156; $base-menu-background: #ffffff;
$base-sub-menu-background: #1f2d3d; $base-sub-menu-background: #ffffff;
$base-sub-menu-hover: #001528; $base-sub-menu-hover: #f8f9fa;
// 组件变量 // 组件变量
$--color-primary: #409eff; $--color-primary: #0052d9;
$--color-success: #67c23a; $--color-success: #67c23a;
$--color-warning: #e6a23c; $--color-warning: #e6a23c;
$--color-danger: #f56c6c; $--color-danger: #f56c6c;
@ -78,7 +78,7 @@ $--color-info: #909399;
/* splitpanes default-theme 变量 */ /* splitpanes default-theme 变量 */
--splitpanes-default-bg: #ffffff; --splitpanes-default-bg: #ffffff;
--main-fill-bg: rgb(242, 243, 245); --main-fill-bg: #f8f9fa;
} }
// 暗黑模式变量 // 暗黑模式变量

View File

@ -85,13 +85,13 @@ getBreadcrumb()
<style lang='scss' scoped> <style lang='scss' scoped>
.app-breadcrumb.el-breadcrumb { .app-breadcrumb.el-breadcrumb {
display: inline-block; display: block;
font-size: 14px; margin-top: 4px;
line-height: 50px; font-size: 11px;
margin-left: 8px; line-height: 16px;
.no-redirect { .no-redirect {
color: #97a8be; color: var(--min-text-secondary);
cursor: text; cursor: text;
} }
} }

View File

@ -258,13 +258,14 @@ getDetectList()
<style scoped> <style scoped>
.custom-table { .custom-table {
border-collapse: collapse; border-collapse: collapse;
font-size: 16px; font-size: 14px;
background: #fff; background: var(--min-bg-white);
color: var(--min-text-regular);
} }
.custom-table th, .custom-table th,
.custom-table td { .custom-table td {
border: 1px solid #ebeef5; border: 1px solid var(--min-border-light);
padding: 12px 16px; padding: 12px 16px;
text-align: left; text-align: left;
height: 48px; height: 48px;
@ -272,28 +273,28 @@ getDetectList()
} }
.custom-table th { .custom-table th {
background: #f5f7fa; background: var(--min-bg-subtle);
font-weight: bold; font-weight: 600;
color: #333; color: var(--min-text-primary);
} }
.custom-table tbody tr:hover { .custom-table tbody tr:hover {
background: #f0f9eb; background: var(--min-primary-soft);
} }
.drag-icon { .drag-icon {
cursor: move; cursor: move;
color: #409eff; color: var(--min-primary);
font-size: 20px; font-size: 20px;
padding: 2px 8px; padding: 2px 8px;
border-radius: 4px; border-radius: 4px;
background: #f4f8ff; background: var(--min-primary-soft);
border: 1px solid #d9ecff; border: 1px solid var(--min-border-light);
transition: box-shadow 0.2s, background 0.2s; transition: box-shadow 0.2s, background 0.2s;
} }
.dragging-row { .dragging-row {
box-shadow: 0 4px 16px 0 rgba(64, 158, 255, 0.15); box-shadow: none;
background: #e6f7ff !important; background: var(--min-primary-soft) !important;
} }
</style> </style>

View File

@ -271,7 +271,7 @@ watch(searchPool, (list) => {
padding-left: 0; padding-left: 0;
padding-right: 0; padding-right: 0;
box-shadow: none !important; box-shadow: none !important;
border-bottom: 1px solid #d9d9d9; border-bottom: 1px solid var(--min-border-light);
vertical-align: middle; vertical-align: middle;
} }
} }

View File

@ -69,14 +69,16 @@ const realHeight = computed(() =>
<style lang="scss" scoped> <style lang="scss" scoped>
.el-image { .el-image {
border-radius: 5px; border: 1px solid var(--min-border-light);
background-color: #ebeef5; border-radius: var(--min-radius-base);
box-shadow: 0 0 5px 1px #ccc; background-color: var(--min-bg-subtle);
box-shadow: none;
overflow: hidden;
:deep(.el-image__inner) { :deep(.el-image__inner) {
transition: all 0.3s; transition: opacity 0.2s ease;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
transform: scale(1.2); opacity: 0.88;
} }
} }
:deep(.image-slot) { :deep(.image-slot) {
@ -85,7 +87,7 @@ const realHeight = computed(() =>
align-items: center; align-items: center;
width: 100%; width: 100%;
height: 100%; height: 100%;
color: #909399; color: var(--min-text-secondary);
font-size: 30px; font-size: 30px;
} }
} }

View File

@ -37,17 +37,22 @@ const resetQuery = () => {
</script> </script>
<template> <template>
<div v-show="showSearch" class="table-search-shell">
<div class="table-search-heading">
<span>FILTER</span>
<strong>条件筛选</strong>
</div>
<el-form <el-form
:model="queryParams" :model="queryParams"
:ref="queryRef" :ref="queryRef"
:inline="inline" :inline="inline"
v-show="showSearch"
:label-width="labelWidth" :label-width="labelWidth"
class="table-search-form"
> >
<el-row :gutter="16"> <el-row :gutter="16">
<slot name="one"></slot> <slot name="one"></slot>
<slot name="other" v-if="formData.search"></slot> <slot name="other" v-if="formData.search"></slot>
<el-col :xs="24" :sm="24" :md="12" :lg="6" :xl="6" :xxl="6" style="padding-bottom: 10px;"> <el-col :xs="24" :sm="24" :md="12" :lg="6" :xl="6" :xxl="6" class="table-search-actions">
<el-space> <el-space>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button> <el-button icon="Refresh" @click="resetQuery">重置</el-button>
@ -62,8 +67,45 @@ const resetQuery = () => {
</el-col> </el-col>
</el-row> </el-row>
</el-form> </el-form>
</div>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.table-search-shell {
margin-bottom: 16px;
border: 1px solid var(--min-border-light);
background: var(--min-bg-white);
}
.table-search-heading {
min-height: 44px;
padding: 0 18px;
border-bottom: 1px solid var(--min-border-light);
display: flex;
align-items: center;
gap: 10px;
span {
color: var(--min-primary);
font-size: 9px;
font-weight: 600;
}
strong {
color: var(--min-text-primary);
font-size: 13px;
font-weight: 600;
}
}
.table-search-form {
padding: 18px 18px 2px;
}
.table-search-actions {
padding-bottom: 16px;
display: flex;
align-items: flex-end;
}
</style> </style>

View File

@ -551,8 +551,8 @@ onBeforeUnmount(() => {
.tree-sidebar { .tree-sidebar {
flex-shrink: 0; flex-shrink: 0;
width: 220px; width: 220px;
background: #fff; background: var(--min-bg-white);
border-right: 1px solid #e8eaed; border-right: 1px solid var(--min-border-light);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
@ -589,11 +589,11 @@ onBeforeUnmount(() => {
transition: background 0.2s; transition: background 0.2s;
&:hover { &:hover {
background: rgba(64, 158, 255, 0.3); background: color-mix(in srgb, var(--min-primary) 24%, transparent);
} }
&.active { &.active {
background: rgba(64, 158, 255, 0.5); background: color-mix(in srgb, var(--min-primary) 40%, transparent);
} }
} }
@ -608,14 +608,15 @@ onBeforeUnmount(() => {
justify-content: center; justify-content: center;
width: 15px; width: 15px;
height: 20px; height: 20px;
background: #fff; background: var(--min-bg-white);
border-radius: 0 4px 4px 0; border-radius: 0 4px 4px 0;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); border: 1px solid var(--min-border-light);
box-shadow: none;
transition: all 0.2s ease; transition: all 0.2s ease;
.tree-sidebar.collapsed & { .tree-sidebar.collapsed & {
right: 0; right: 0;
background: #f7f8fa; background: var(--min-bg-subtle);
border-radius: 0 4px 4px 0; border-radius: 0 4px 4px 0;
} }
@ -626,15 +627,15 @@ onBeforeUnmount(() => {
.collapse-button { .collapse-button {
font-size: 20px; font-size: 20px;
color: #909399; color: var(--min-text-secondary);
cursor: pointer; cursor: pointer;
padding: 4px; padding: 4px;
border-radius: 4px; border-radius: 4px;
transition: all 0.2s; transition: all 0.2s;
&:hover { &:hover {
color: #409eff; color: var(--min-primary);
background: #ecf5ff; background: var(--min-primary-soft);
} }
} }
@ -644,14 +645,14 @@ onBeforeUnmount(() => {
justify-content: space-between; justify-content: space-between;
padding: 0 10px; padding: 0 10px;
height: 40px; height: 40px;
border-bottom: 1px solid #e8eaed; border-bottom: 1px solid var(--min-border-light);
background: #f7f8fa; background: var(--min-bg-subtle);
flex-shrink: 0; flex-shrink: 0;
.tree-title { .tree-title {
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;
color: #303133; color: var(--min-text-primary);
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
@ -659,7 +660,7 @@ onBeforeUnmount(() => {
gap: 5px; gap: 5px;
.el-icon { .el-icon {
color: #409eff; color: var(--min-primary);
font-size: 16px; font-size: 16px;
} }
} }
@ -674,15 +675,15 @@ onBeforeUnmount(() => {
.tree-action-icon { .tree-action-icon {
font-size: 20px; font-size: 20px;
color: #909399; color: var(--min-text-secondary);
cursor: pointer; cursor: pointer;
padding: 4px; padding: 4px;
border-radius: 4px; border-radius: 4px;
transition: all 0.2s; transition: all 0.2s;
&:hover { &:hover {
color: #409eff; color: var(--min-primary);
background: #ecf5ff; background: var(--min-primary-soft);
} }
} }
@ -705,11 +706,11 @@ onBeforeUnmount(() => {
} }
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
background: #dcdfe6; background: var(--min-border-light);
border-radius: 4px; border-radius: 4px;
&:hover { &:hover {
background: #c0c4cc; background: var(--min-text-secondary);
} }
} }
@ -719,17 +720,17 @@ onBeforeUnmount(() => {
margin-bottom: 1px; margin-bottom: 1px;
&:hover { &:hover {
background: #f0f7ff; background: var(--min-bg-subtle);
} }
} }
:deep(.el-tree-node.is-current > .el-tree-node__content) { :deep(.el-tree-node.is-current > .el-tree-node__content) {
background: #e6f0fd; background: var(--min-primary-soft);
color: #409eff; color: var(--min-primary);
font-weight: 600; font-weight: 600;
.node-icon { .node-icon {
color: #409eff !important; color: var(--min-primary) !important;
} }
} }
} }
@ -743,7 +744,7 @@ onBeforeUnmount(() => {
.node-icon { .node-icon {
font-size: 14px; font-size: 14px;
color: #f5a623; color: var(--min-text-secondary);
flex-shrink: 0; flex-shrink: 0;
} }

View File

@ -1,12 +1,31 @@
<template> <template>
<section class="app-main"> <section class="app-main" :class="{ 'app-main--immersive': isImmersive }">
<router-view v-slot="{ Component, route }"> <div class="workspace-stage">
<header v-if="!isImmersive" class="workspace-context">
<div class="workspace-context__marker" aria-hidden="true">
<span>{{ sectionCode }}</span>
<i></i>
</div>
<div class="workspace-context__copy">
<span class="workspace-context__eyebrow">{{ sectionLabel }}</span>
<h1>{{ routeTitle }}</h1>
</div>
<div class="workspace-context__path">
<span>当前位置</span>
<strong>{{ routeTrail }}</strong>
</div>
</header>
<div class="workspace-view">
<router-view v-slot="{ Component, route: currentRoute }">
<transition name="fade-transform" mode="out-in"> <transition name="fade-transform" mode="out-in">
<keep-alive :include="tagsViewStore.cachedViews"> <keep-alive :include="tagsViewStore.cachedViews">
<component v-if="!route.meta.link" :is="Component" :key="route.path"/> <component v-if="!currentRoute.meta.link" :is="Component" :key="currentRoute.path"/>
</keep-alive> </keep-alive>
</transition> </transition>
</router-view> </router-view>
</div>
</div>
<iframe-toggle /> <iframe-toggle />
<copyright /> <copyright />
</section> </section>
@ -20,6 +39,33 @@ import useTagsViewStore from '@/store/modules/tagsView'
const route = useRoute() const route = useRoute()
const tagsViewStore = useTagsViewStore() const tagsViewStore = useTagsViewStore()
const sectionMap = {
system: ['SYS', '系统管理'],
monitor: ['OPS', '运行监控'],
tool: ['DEV', '开发工具'],
device: ['DEV', '设备中心'],
inspection: ['INS', '巡检业务'],
intelligenceTest: ['AIT', '智能测试'],
test: ['TST', '测试管理'],
corpusManger: ['COR', '语料中心'],
vi: ['VOC', '语音测试'],
is: ['HMI', '交互测试'],
evaluate: ['EVA', '评价中心'],
}
const pathSection = computed(() => route.path.split('/').filter(Boolean)[0] || 'index')
const sectionMeta = computed(() => sectionMap[pathSection.value] || ['CMV', '业务工作台'])
const sectionCode = computed(() => sectionMeta.value[0])
const sectionLabel = computed(() => sectionMeta.value[1])
const routeTitle = computed(() => route.meta?.title || route.name || '工作台')
const routeTrail = computed(() => {
const titles = route.matched
.map(item => item.meta?.title)
.filter(Boolean)
return titles.length ? titles.join(' / ') : routeTitle.value
})
const isImmersive = computed(() => route.path === '/index')
onMounted(() => { onMounted(() => {
addIframe() addIframe()
}) })
@ -38,29 +84,159 @@ function addIframe() {
<style lang="scss" scoped> <style lang="scss" scoped>
@use "@/assets/styles/variables.module" as *; @use "@/assets/styles/variables.module" as *;
.app-main { .app-main {
/* 50= navbar 50 */ height: calc(100% - 72px);
height: calc(100% - 84px); padding: 0;
padding: 20px;
width: 100%; width: 100%;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
//background-color: rgba(242, 243, 245);
background-color: var(--main-fill-bg); background-color: var(--main-fill-bg);
display: flex; display: flex;
} }
.workspace-stage {
width: 100%;
min-width: 0;
display: flex;
flex-direction: column;
overflow: hidden;
}
.workspace-context {
min-height: 112px;
flex: 0 0 112px;
display: grid;
grid-template-columns: 72px minmax(0, 1fr) minmax(220px, auto);
align-items: stretch;
border-bottom: 1px solid var(--min-border-light);
background: var(--min-bg-white);
}
.workspace-context__marker {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
border-right: 1px solid var(--min-border-light);
color: var(--min-primary);
font-size: 11px;
font-weight: 600;
}
.workspace-context__marker i {
width: 24px;
height: 2px;
background: var(--min-primary);
}
.workspace-context__copy {
min-width: 0;
padding: 24px 32px;
display: flex;
flex-direction: column;
justify-content: center;
}
.workspace-context__eyebrow,
.workspace-context__path span {
color: var(--min-text-secondary);
font-size: 11px;
font-weight: 500;
}
.workspace-context__copy h1 {
margin: 6px 0 0;
overflow: hidden;
color: var(--min-text-primary);
font-size: 24px;
line-height: 1.25;
font-weight: 600;
text-overflow: ellipsis;
white-space: nowrap;
}
.workspace-context__path {
min-width: 220px;
padding: 24px 32px;
border-left: 1px solid var(--min-border-light);
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-end;
text-align: right;
}
.workspace-context__path strong {
max-width: 360px;
margin-top: 7px;
overflow: hidden;
color: var(--min-text-regular);
font-size: 13px;
font-weight: 500;
text-overflow: ellipsis;
white-space: nowrap;
}
.workspace-view {
flex: 1;
min-width: 0;
min-height: 0;
padding: 24px 32px 32px;
overflow: auto;
}
.app-main--immersive .workspace-view {
padding: 0;
overflow: hidden;
}
.fixed-header + .app-main { .fixed-header + .app-main {
padding-top: 50px; padding-top: 72px;
} }
.hasTagsView { .hasTagsView {
.app-main { .app-main {
/* 84 = navbar + tags-view = 50 + 34 */ height: calc(100% - 116px);
height: calc(100% - 84px);
} }
.fixed-header + .app-main { .fixed-header + .app-main {
padding-top: 84px; padding-top: 116px;
}
}
@media (max-width: 768px) {
.app-main {
height: calc(100% - 64px);
}
.hasTagsView .app-main {
height: calc(100% - 106px);
}
.workspace-context {
min-height: 88px;
flex-basis: 88px;
grid-template-columns: 48px minmax(0, 1fr);
}
.workspace-context__marker {
font-size: 9px;
}
.workspace-context__copy {
padding: 16px 20px;
}
.workspace-context__copy h1 {
font-size: 20px;
}
.workspace-context__path {
display: none;
}
.workspace-view {
padding: 16px;
} }
} }
</style> </style>
@ -78,12 +254,4 @@ function addIframe() {
height: 6px; height: 6px;
} }
::-webkit-scrollbar-track {
background-color: #f1f1f1;
}
::-webkit-scrollbar-thumb {
background-color: #c0c0c0;
border-radius: 3px;
}
</style> </style>

View File

@ -22,10 +22,10 @@ const content = computed(() => settingsStore.footerContent)
height: 36px; height: 36px;
padding: 10px 20px; padding: 10px 20px;
text-align: right; text-align: right;
background-color: #f8f8f8; background-color: var(--min-bg-page);
color: #666; color: var(--min-text-secondary);
font-size: 14px; font-size: 14px;
border-top: 1px solid #e7e7e7; border-top: 1px solid var(--min-border-light);
z-index: 999; z-index: 999;
} }
</style> </style>

View File

@ -1,7 +1,13 @@
<template> <template>
<div class="navbar" :class="'nav' + settingsStore.navType"> <div class="navbar" :class="'nav' + settingsStore.navType">
<hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" /> <hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<breadcrumb v-if="settingsStore.navType == 1" id="breadcrumb-container" class="breadcrumb-container" /> <div v-if="settingsStore.navType == 1" class="navbar-context">
<div class="navbar-context__title">
<span>CONTROL DESK</span>
<strong>{{ currentTitle }}</strong>
</div>
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" />
</div>
<top-nav v-if="settingsStore.navType == 2" id="topmenu-container" class="topmenu-container" /> <top-nav v-if="settingsStore.navType == 2" id="topmenu-container" class="topmenu-container" />
<template v-if="settingsStore.navType == 3"> <template v-if="settingsStore.navType == 3">
<logo v-show="settingsStore.sidebarLogo" :collapse="false"></logo> <logo v-show="settingsStore.sidebarLogo" :collapse="false"></logo>
@ -88,6 +94,7 @@ const appStore = useAppStore()
const userStore = useUserStore() const userStore = useUserStore()
const lockStore = useLockStore() const lockStore = useLockStore()
const settingsStore = useSettingsStore() const settingsStore = useSettingsStore()
const currentTitle = computed(() => route.meta?.title || route.name || '工作台')
function toggleSideBar() { function toggleSideBar() {
appStore.toggleSideBar() appStore.toggleSideBar()
@ -175,32 +182,67 @@ async function toggleTheme(event) {
<style lang='scss' scoped> <style lang='scss' scoped>
.navbar { .navbar {
height: 50px; height: 72px;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
background: var(--navbar-bg); background: var(--navbar-bg);
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); border-bottom: 1px solid var(--min-border-light);
box-shadow: none;
.hamburger-container { .hamburger-container {
line-height: 46px; width: 64px;
line-height: 68px;
height: 100%; height: 100%;
float: left; float: left;
cursor: pointer; cursor: pointer;
transition: background 0.3s; border-right: 1px solid var(--min-border-light);
transition: background 0.2s;
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
&:hover { &:hover {
background: rgba(0, 0, 0, 0.025); background: var(--navbar-hover);
} }
} }
.breadcrumb-container { .breadcrumb-container {
display: block;
}
.navbar-context {
height: 100%;
min-width: 0;
padding: 10px 20px;
float: left; float: left;
display: flex;
flex-direction: column;
justify-content: center;
}
.navbar-context__title {
min-width: 0;
display: flex;
align-items: center;
gap: 10px;
span {
color: var(--min-primary);
font-size: 10px;
font-weight: 600;
}
strong {
overflow: hidden;
color: var(--min-text-primary);
font-size: 14px;
font-weight: 600;
text-overflow: ellipsis;
white-space: nowrap;
}
} }
.topmenu-container { .topmenu-container {
position: absolute; position: absolute;
left: 50px; left: 56px;
} }
.errLog-container { .errLog-container {
@ -211,7 +253,7 @@ async function toggleTheme(event) {
.right-menu { .right-menu {
float: right; float: right;
height: 100%; height: 100%;
line-height: 50px; line-height: 72px;
display: flex; display: flex;
&:focus { &:focus {
@ -219,19 +261,24 @@ async function toggleTheme(event) {
} }
.right-menu-item { .right-menu-item {
display: inline-block; width: 40px;
padding: 0 8px; padding: 0;
height: 100%; height: 100%;
font-size: 18px; display: inline-flex;
align-items: center;
justify-content: center;
font-size: 17px;
color: var(--navbar-text); color: var(--navbar-text);
vertical-align: text-bottom; vertical-align: text-bottom;
&.hover-effect { &.hover-effect {
cursor: pointer; cursor: pointer;
transition: background 0.3s; border-left: 1px solid transparent;
transition: color 0.2s, background 0.2s;
&:hover { &:hover {
background: rgba(0, 0, 0, 0.025); background: var(--navbar-hover);
color: var(--min-primary);
} }
} }
@ -243,24 +290,42 @@ async function toggleTheme(event) {
transition: transform 0.3s; transition: transform 0.3s;
&:hover { &:hover {
transform: scale(1.15); color: var(--min-primary);
} }
} }
} }
} }
.avatar-container { .avatar-container {
margin-right: 40px; width: auto;
margin: 0;
padding: 0 24px 0 16px;
border-left: 1px solid var(--min-border-light);
.avatar-wrapper { .avatar-wrapper {
margin-top: 5px; height: 72px;
margin-top: 0;
display: flex;
align-items: center;
position: relative; position: relative;
.user-avatar { .user-avatar {
cursor: pointer; cursor: pointer;
width: 40px; width: 32px;
height: 40px; height: 32px;
border-radius: 10px; border: 1px solid var(--min-border-light);
border-radius: 50%;
}
.user-nickname {
max-width: 120px;
margin-left: 10px;
overflow: hidden;
color: var(--min-text-primary);
font-size: 13px;
font-weight: 500;
text-overflow: ellipsis;
white-space: nowrap;
} }
i { i {
@ -274,4 +339,36 @@ async function toggleTheme(event) {
} }
} }
} }
@media (max-width: 768px) {
.navbar {
height: 64px;
.hamburger-container {
width: 56px;
line-height: 60px;
}
.navbar-context {
padding: 8px 14px;
}
.navbar-context__title span,
.breadcrumb-container {
display: none;
}
.right-menu .avatar-container {
padding: 0 14px;
.avatar-wrapper {
height: 64px;
}
.user-nickname {
display: none;
}
}
}
}
</style> </style>

View File

@ -139,7 +139,7 @@ const theme = ref(settingsStore.theme)
const sideTheme = ref(settingsStore.sideTheme) const sideTheme = ref(settingsStore.sideTheme)
const tagsViewPersist = ref(settingsStore.tagsViewPersist) const tagsViewPersist = ref(settingsStore.tagsViewPersist)
const storeSettings = computed(() => settingsStore) const storeSettings = computed(() => settingsStore)
const predefineColors = ref(["#409EFF", "#ff4500", "#ff8c00", "#ffd700", "#90ee90", "#00ced1", "#1e90ff", "#c71585"]) const predefineColors = ref(["#0052D9", "#FF6B00", "#00A86B"])
/** 是否需要dynamicTitle */ /** 是否需要dynamicTitle */
function dynamicTitleChange() { function dynamicTitleChange() {

View File

@ -6,8 +6,13 @@
<h1 v-else class="sidebar-title">{{ title }}</h1> <h1 v-else class="sidebar-title">{{ title }}</h1>
</router-link> </router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/"> <router-link v-else key="expand" class="sidebar-logo-link" to="/">
<span class="sidebar-brand-mark">
<img v-if="logo" :src="logo" class="sidebar-logo" /> <img v-if="logo" :src="logo" class="sidebar-logo" />
</span>
<span class="sidebar-brand-copy">
<h1 class="sidebar-title">{{ title }}</h1> <h1 class="sidebar-title">{{ title }}</h1>
<small>TEST OPERATIONS</small>
</span>
</router-link> </router-link>
</transition> </transition>
</div> </div>
@ -67,36 +72,71 @@ const getLogoTextColor = computed(() => {
.sidebar-logo-container { .sidebar-logo-container {
position: relative; position: relative;
width: 100%; width: 100%;
height: 50px; height: 72px;
line-height: 50px; line-height: 72px;
background: v-bind(getLogoBackground); background: v-bind(getLogoBackground);
text-align: center; text-align: left;
overflow: hidden; overflow: hidden;
& .sidebar-logo-link { & .sidebar-logo-link {
height: 100%; height: 100%;
width: 100%; width: 100%;
padding: 0 18px;
display: flex;
align-items: center;
& .sidebar-logo { & .sidebar-logo {
width: 32px; width: 28px;
height: 32px; height: 28px;
vertical-align: middle; object-fit: contain;
margin-right: 12px;
} }
& .sidebar-title { & .sidebar-title {
display: inline-block; display: block;
margin: 0; margin: 0;
color: v-bind(getLogoTextColor); color: v-bind(getLogoTextColor);
font-weight: 600; font-weight: 600;
line-height: 50px; overflow: hidden;
font-size: 14px; line-height: 1.25;
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif; font-size: 13px;
vertical-align: middle; font-family: var(--min-font-family);
text-overflow: ellipsis;
white-space: nowrap;
}
}
.sidebar-brand-mark {
width: 36px;
height: 36px;
flex: 0 0 36px;
border: 1px solid var(--min-border-light);
display: grid;
place-items: center;
}
.sidebar-brand-copy {
min-width: 0;
margin-left: 12px;
display: flex;
flex-direction: column;
gap: 3px;
small {
color: var(--min-text-secondary);
font-size: 9px;
line-height: 1.2;
font-weight: 500;
} }
} }
&.collapse { &.collapse {
text-align: center;
.sidebar-logo-link {
padding: 0;
justify-content: center;
}
.sidebar-logo { .sidebar-logo {
margin-right: 0px; margin-right: 0px;
} }

View File

@ -1,6 +1,10 @@
<template> <template>
<div :class="['sidebar-theme-wrapper', {'has-logo':showLogo}, sideTheme]" class="sidebar-container"> <div :class="['sidebar-theme-wrapper', {'has-logo':showLogo}, sideTheme]" class="sidebar-container">
<logo v-if="showLogo" :collapse="isCollapse" /> <logo v-if="showLogo" :collapse="isCollapse" />
<div v-show="!isCollapse" class="sidebar-section-label">
<span>工作导航</span>
<strong>01</strong>
</div>
<el-scrollbar wrap-class="scrollbar-wrapper"> <el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu <el-menu
:default-active="activeMenu" :default-active="activeMenu"
@ -21,6 +25,13 @@
/> />
</el-menu> </el-menu>
</el-scrollbar> </el-scrollbar>
<div v-show="!isCollapse" class="sidebar-status">
<span class="sidebar-status__signal"></span>
<span>
<strong>CMVR CONTROL OS</strong>
<small>系统在线</small>
</span>
</div>
</div> </div>
</template> </template>
@ -71,19 +82,57 @@ const activeMenu = computed(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.sidebar-container { .sidebar-container {
background-color: v-bind(getMenuBackground); background-color: v-bind(getMenuBackground);
display: flex;
flex-direction: column;
.scrollbar-wrapper { .scrollbar-wrapper {
background-color: v-bind(getMenuBackground); background-color: v-bind(getMenuBackground);
} }
> .el-scrollbar {
min-height: 0;
flex: 1;
}
.sidebar-section-label {
height: 48px;
padding: 0 20px;
flex: 0 0 48px;
border-top: 1px solid var(--min-border-light);
border-bottom: 1px solid var(--min-border-light);
display: flex;
align-items: center;
justify-content: space-between;
color: var(--min-text-secondary);
font-size: 10px;
font-weight: 500;
strong {
color: var(--min-primary);
font-size: 11px;
font-weight: 600;
}
}
.el-menu { .el-menu {
border: none; border: none;
height: 100%; height: 100%;
width: 100% !important; width: 100% !important;
.el-menu-item,
.el-sub-menu__title {
height: 52px;
line-height: 52px;
margin: 0;
border-radius: 0;
border-bottom: 1px solid var(--min-border-light);
font-size: 13px;
font-weight: 500;
}
.el-menu-item, .el-sub-menu__title { .el-menu-item, .el-sub-menu__title {
&:hover { &:hover {
background-color: var(--menu-hover, rgba(0, 0, 0, 0.06)) !important; background-color: var(--menu-hover) !important;
} }
} }
@ -91,8 +140,19 @@ const activeMenu = computed(() => {
color: v-bind(getMenuTextColor); color: v-bind(getMenuTextColor);
&.is-active { &.is-active {
color: var(--menu-active-text, #409eff); color: var(--menu-active-text);
background-color: var(--menu-hover, rgba(0, 0, 0, 0.06)) !important; background-color: var(--min-bg-white) !important;
font-weight: 600;
&::before {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 2px;
background: var(--min-primary);
}
} }
} }
@ -100,5 +160,42 @@ const activeMenu = computed(() => {
color: v-bind(getMenuTextColor); color: v-bind(getMenuTextColor);
} }
} }
.sidebar-status {
min-height: 72px;
padding: 16px 20px;
flex: 0 0 72px;
border-top: 1px solid var(--min-border-light);
display: flex;
align-items: center;
gap: 10px;
background: var(--min-bg-subtle);
> span:last-child {
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
strong {
color: var(--min-text-primary);
font-size: 10px;
font-weight: 600;
}
small {
color: var(--min-text-secondary);
font-size: 10px;
}
}
.sidebar-status__signal {
width: 8px;
height: 8px;
flex: 0 0 8px;
border-radius: var(--min-radius-sm);
background: var(--min-primary);
}
} }
</style> </style>

View File

@ -1,5 +1,9 @@
<template> <template>
<div id="tags-view-container" class="tags-view-container"> <div id="tags-view-container" class="tags-view-container">
<div class="tags-view-index">
<span>OPEN</span>
<strong>{{ String(visitedViews.length).padStart(2, '0') }}</strong>
</div>
<scroll-pane ref="scrollPaneRef" class="tags-view-wrapper" @scroll="handleScroll"> <scroll-pane ref="scrollPaneRef" class="tags-view-wrapper" @scroll="handleScroll">
<router-link <router-link
v-for="tag in visitedViews" v-for="tag in visitedViews"
@ -8,7 +12,6 @@
:class="isActive(tag) ? 'active' : ''" :class="isActive(tag) ? 'active' : ''"
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }" :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
class="tags-view-item" class="tags-view-item"
:style="activeStyle(tag)"
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''" @click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
@contextmenu.prevent="openMenu(tag, $event)" @contextmenu.prevent="openMenu(tag, $event)"
> >
@ -45,7 +48,6 @@
import ScrollPane from './ScrollPane' import ScrollPane from './ScrollPane'
import { getNormalPath } from '@/utils/ruoyi' import { getNormalPath } from '@/utils/ruoyi'
import useTagsViewStore from '@/store/modules/tagsView' import useTagsViewStore from '@/store/modules/tagsView'
import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission' import usePermissionStore from '@/store/modules/permission'
const visible = ref(false); const visible = ref(false);
@ -61,7 +63,6 @@ const router = useRouter();
const visitedViews = computed(() => useTagsViewStore().visitedViews); const visitedViews = computed(() => useTagsViewStore().visitedViews);
const routes = computed(() => usePermissionStore().routes); const routes = computed(() => usePermissionStore().routes);
const theme = computed(() => useSettingsStore().theme);
watch(route, () => { watch(route, () => {
addTags() addTags()
@ -85,14 +86,6 @@ function isActive(r) {
return r.path === route.path return r.path === route.path
} }
function activeStyle(tag) {
if (!isActive(tag)) return {};
return {
"background-color": theme.value,
"border-color": theme.value
};
}
function isAffix(tag) { function isAffix(tag) {
return tag.meta && tag.meta.affix return tag.meta && tag.meta.affix
} }
@ -259,50 +252,63 @@ function handleScroll() {
<style lang="scss" scoped> <style lang="scss" scoped>
.tags-view-container { .tags-view-container {
height: 34px; height: 44px;
width: 100%; width: 100%;
background: var(--tags-bg, #fff); display: flex;
background: var(--min-bg-subtle);
border-bottom: 1px solid var(--tags-item-border, #d8dce5); border-bottom: 1px solid var(--tags-item-border, #d8dce5);
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04); box-shadow: none;
.tags-view-index {
width: 72px;
flex: 0 0 72px;
border-right: 1px solid var(--tags-item-border);
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
color: var(--min-text-secondary);
font-size: 9px;
strong {
color: var(--min-primary);
font-size: 10px;
font-weight: 600;
}
}
.tags-view-wrapper { .tags-view-wrapper {
min-width: 0;
flex: 1;
.tags-view-item { .tags-view-item {
display: inline-block; display: inline-block;
position: relative; position: relative;
cursor: pointer; cursor: pointer;
height: 26px; height: 43px;
line-height: 26px; line-height: 43px;
border: 1px solid var(--tags-item-border, #d8dce5); border: 0;
color: var(--tags-item-text, #495060); border-right: 1px solid var(--tags-item-border);
background: var(--tags-item-bg, #fff); border-bottom: 2px solid transparent;
padding: 0 8px; color: var(--tags-item-text);
background: transparent;
padding: 0 16px;
font-size: 12px; font-size: 12px;
margin-left: 5px; margin: 0;
margin-top: 4px;
&:first-of-type { &:first-of-type {
margin-left: 15px; margin-left: 0;
} }
&:last-of-type { &:last-of-type {
margin-right: 15px; margin-right: 12px;
} }
&.active { &.active {
background-color: #42b983; color: var(--min-primary);
color: #fff; border-bottom-color: var(--min-primary);
border-color: #42b983; background: var(--min-bg-white);
font-weight: 600;
&::before {
content: '';
background: #fff;
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
position: relative;
margin-right: 5px;
}
} }
} }
} }
@ -314,11 +320,11 @@ function handleScroll() {
position: absolute; position: absolute;
list-style-type: none; list-style-type: none;
padding: 5px 0; padding: 5px 0;
border-radius: 4px; border-radius: var(--min-radius-base);
font-size: 12px; font-size: 12px;
font-weight: 400; font-weight: 400;
color: var(--tags-item-text, #333); color: var(--tags-item-text, #333);
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3); box-shadow: var(--min-shadow-overlay);
border: 1px solid var(--el-border-color-light, #e4e7ed); border: 1px solid var(--el-border-color-light, #e4e7ed);
li { li {
@ -332,6 +338,22 @@ function handleScroll() {
} }
} }
} }
@media (max-width: 768px) {
.tags-view-container {
height: 42px;
.tags-view-index {
display: none;
}
.tags-view-wrapper .tags-view-item {
height: 41px;
line-height: 41px;
padding: 0 12px;
}
}
}
</style> </style>
<style lang="scss"> <style lang="scss">

View File

@ -65,9 +65,9 @@ onMounted(() => {
.topbar-menu.el-menu--horizontal > .el-menu-item { .topbar-menu.el-menu--horizontal > .el-menu-item {
float: left; float: left;
height: 50px !important; height: 56px !important;
line-height: 50px !important; line-height: 56px !important;
color: #303133 !important; color: var(--min-text-regular) !important;
padding: 0 5px !important; padding: 0 5px !important;
margin: 0 10px !important; margin: 0 10px !important;
} }
@ -79,8 +79,8 @@ onMounted(() => {
/* sub-menu item */ /* sub-menu item */
.topbar-menu.el-menu--horizontal > .el-sub-menu .el-sub-menu__title { .topbar-menu.el-menu--horizontal > .el-sub-menu .el-sub-menu__title {
float: left; float: left;
line-height: 50px !important; line-height: 56px !important;
color: #303133 !important; color: var(--min-text-regular) !important;
margin: 0 15px -3px!important; margin: 0 15px -3px!important;
} }
@ -94,6 +94,6 @@ onMounted(() => {
/* menu__title el-menu-item */ /* menu__title el-menu-item */
.topbar-menu.el-menu--horizontal .el-sub-menu__title, .topbar-menu.el-menu--horizontal .el-menu-item { .topbar-menu.el-menu--horizontal .el-sub-menu__title, .topbar-menu.el-menu--horizontal .el-menu-item {
height: 60px; height: 56px;
} }
</style> </style>

View File

@ -172,37 +172,37 @@ onMounted(() => {
<style lang="scss"> <style lang="scss">
.topmenu-container.el-menu--horizontal { .topmenu-container.el-menu--horizontal {
height: 50px !important; height: 56px !important;
border-bottom: none; border-bottom: none;
} }
.topmenu-container.el-menu--horizontal > .el-menu-item { .topmenu-container.el-menu--horizontal > .el-menu-item {
float: left; float: left;
height: 50px !important; height: 56px !important;
line-height: 50px !important; line-height: 56px !important;
color: #303133 !important; color: var(--min-text-regular) !important;
padding: 0 5px !important; padding: 0 4px !important;
margin: 0 10px !important; margin: 0 12px !important;
} }
.topmenu-container.el-menu--horizontal > .el-menu-item.is-active, .el-menu--horizontal > .el-sub-menu.is-active .el-submenu__title { .topmenu-container.el-menu--horizontal > .el-menu-item.is-active, .el-menu--horizontal > .el-sub-menu.is-active .el-submenu__title {
border-bottom: 2px solid #{'var(--theme)'} !important; border-bottom: 2px solid #{'var(--theme)'} !important;
color: #303133; color: var(--min-primary) !important;
} }
/* sub-menu item */ /* sub-menu item */
.topmenu-container.el-menu--horizontal > .el-sub-menu .el-sub-menu__title { .topmenu-container.el-menu--horizontal > .el-sub-menu .el-sub-menu__title {
float: left; float: left;
height: 50px !important; height: 56px !important;
line-height: 50px !important; line-height: 56px !important;
color: #303133 !important; color: var(--min-text-regular) !important;
padding: 0 5px !important; padding: 0 4px !important;
margin: 0 10px !important; margin: 0 12px !important;
} }
/* 背景色隐藏 */ /* 背景色隐藏 */
.topmenu-container.el-menu--horizontal>.el-menu-item:not(.is-disabled):focus, .topmenu-container.el-menu--horizontal>.el-menu-item:not(.is-disabled):hover, .topmenu-container.el-menu--horizontal>.el-submenu .el-submenu__title:hover { .topmenu-container.el-menu--horizontal>.el-menu-item:not(.is-disabled):focus, .topmenu-container.el-menu--horizontal>.el-menu-item:not(.is-disabled):hover, .topmenu-container.el-menu--horizontal>.el-submenu .el-submenu__title:hover {
background-color: #ffffff; background-color: var(--min-bg-white);
} }
/* 图标右间距 */ /* 图标右间距 */

View File

@ -71,6 +71,7 @@ function setLayout() {
position: relative; position: relative;
height: 100%; height: 100%;
width: 100%; width: 100%;
background: var(--min-bg-page);
&.mobile.openSidebar { &.mobile.openSidebar {
position: fixed; position: fixed;
@ -79,8 +80,7 @@ function setLayout() {
} }
.drawer-bg { .drawer-bg {
background: #000; background: rgba(31, 41, 55, 0.28);
opacity: 0.3;
width: 100%; width: 100%;
top: 0; top: 0;
height: 100%; height: 100%;
@ -98,7 +98,7 @@ function setLayout() {
} }
.hideSidebar .fixed-header { .hideSidebar .fixed-header {
width: calc(100% - 54px); width: calc(100% - 56px);
} }
.sidebarHide .fixed-header { .sidebarHide .fixed-header {

View File

@ -8,6 +8,7 @@ import 'element-plus/theme-chalk/dark/css-vars.css'
import locale from 'element-plus/es/locale/lang/zh-cn' import locale from 'element-plus/es/locale/lang/zh-cn'
import '@/assets/styles/index.scss' // global css import '@/assets/styles/index.scss' // global css
import '@/styles/minimalist.css'
import App from './App' import App from './App'
import store from './store' import store from './store'

View File

@ -7,7 +7,7 @@ export default {
/** /**
* 侧边栏主题 深色主题theme-dark,浅色主题theme-light * 侧边栏主题 深色主题theme-dark,浅色主题theme-light
*/ */
sideTheme: 'theme-dark', sideTheme: 'theme-light',
/** /**
* 是否系统布局配置 * 是否系统布局配置

View File

@ -15,7 +15,7 @@ const useSettingsStore = defineStore(
{ {
state: () => ({ state: () => ({
title: '', title: '',
theme: storageSetting.theme || '#409EFF', theme: storageSetting.theme || '#0052D9',
sideTheme: storageSetting.sideTheme || sideTheme, sideTheme: storageSetting.sideTheme || sideTheme,
showSettings: showSettings, showSettings: showSettings,
navType: storageSetting.navType === undefined ? navType : storageSetting.navType, navType: storageSetting.navType === undefined ? navType : storageSetting.navType,

View File

@ -1,5 +1,5 @@
$br: 1px solid #e4e7ed; $br: 1px solid #e5e7eb;
$brdeg: 8px; $brdeg: 4px;
@mixin br6 { @mixin br6 {
border: $br; border: $br;
border-radius: $brdeg; border-radius: $brdeg;
@ -44,7 +44,7 @@ $brdeg: 8px;
} }
.bg { .bg {
background-color: #409eff; background-color: #0052d9;
} }
.scrollable { .scrollable {

View File

@ -0,0 +1,68 @@
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
$colors: (
'white': #ffffff,
'primary': (
'base': #0052d9,
),
'success': (
'base': #4f6b5f,
),
'warning': (
'base': #8a6b36,
),
'danger': (
'base': #9a4545,
),
'error': (
'base': #9a4545,
),
'info': (
'base': #6b7280,
),
),
$bg-color: (
'': #ffffff,
'page': #f8f9fa,
'overlay': #ffffff,
),
$text-color: (
'primary': #1f2937,
'regular': #374151,
'secondary': #6b7280,
'placeholder': #9ca3af,
'disabled': #c4c9d1,
),
$border-color: (
'': #e5e7eb,
'light': #e5e7eb,
'lighter': #edf0f3,
'extra-light': #f3f4f6,
'dark': #d1d5db,
'darker': #c4c9d1,
),
$fill-color: (
'': #f3f4f6,
'light': #f8f9fa,
'lighter': #fafbfc,
'extra-light': #fcfcfd,
'dark': #eceff3,
'darker': #e5e7eb,
'blank': #ffffff,
),
$border-radius: (
'base': 4px,
'small': 2px,
'round': 4px,
),
$box-shadow: (
'': 0 8px 24px rgba(31, 41, 55, 0.06),
'light': 0 4px 16px rgba(31, 41, 55, 0.06),
'lighter': 0 2px 8px rgba(31, 41, 55, 0.05),
'dark': 0 12px 32px rgba(31, 41, 55, 0.08),
),
$font-family: (
'': "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif",
)
);
@use 'element-plus/theme-chalk/src/index.scss' as *;

507
src/styles/minimalist.css Normal file
View File

@ -0,0 +1,507 @@
:root {
/* Foundation */
--min-bg-page: #f8f9fa;
--min-bg-white: #ffffff;
--min-bg-subtle: #f3f4f6;
--min-border-light: #e5e7eb;
--min-border-strong: #d1d5db;
/* Typography */
--min-text-primary: #1f2937;
--min-text-regular: #374151;
--min-text-secondary: #6b7280;
--min-text-placeholder: #9ca3af;
/* Single accent */
--min-primary: #0052d9;
--min-primary-hover: #0046b8;
--min-primary-soft: #eef4ff;
--min-primary-border: #cbdcf8;
/* 4px / 8px grid */
--min-radius-sm: 2px;
--min-radius-base: 4px;
--min-space-1: 4px;
--min-space-base: 8px;
--min-space-3: 12px;
--min-space-4: 16px;
--min-space-5: 20px;
--min-space-6: 24px;
--min-space-8: 32px;
--min-font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica,
"PingFang SC", "Microsoft YaHei", Arial, sans-serif;
--min-shadow-overlay: 0 8px 24px rgba(31, 41, 55, 0.06);
/* Element Plus */
--el-bg-color: var(--min-bg-white);
--el-bg-color-page: var(--min-bg-page);
--el-bg-color-overlay: var(--min-bg-white);
--el-fill-color: var(--min-bg-subtle);
--el-fill-color-light: var(--min-bg-page);
--el-fill-color-lighter: #fafbfc;
--el-fill-color-extra-light: #fcfcfd;
--el-fill-color-blank: var(--min-bg-white);
--el-text-color-primary: var(--min-text-primary);
--el-text-color-regular: var(--min-text-regular);
--el-text-color-secondary: var(--min-text-secondary);
--el-text-color-placeholder: var(--min-text-placeholder);
--el-border-color: var(--min-border-light);
--el-border-color-light: var(--min-border-light);
--el-border-color-lighter: #edf0f3;
--el-border-color-extra-light: #f3f4f6;
--el-border-color-dark: var(--min-border-strong);
--el-border-radius-base: var(--min-radius-base);
--el-border-radius-small: var(--min-radius-sm);
--el-border-radius-round: var(--min-radius-base);
--el-color-primary: var(--min-primary);
--el-color-success: var(--min-primary);
--el-color-success-light-3: #4d86e5;
--el-color-success-light-5: #80a8ec;
--el-color-success-light-7: var(--min-primary-border);
--el-color-success-light-8: #dfe9fa;
--el-color-success-light-9: var(--min-primary-soft);
--el-color-success-dark-2: var(--min-primary-hover);
--el-color-warning: var(--min-primary);
--el-color-warning-light-3: #4d86e5;
--el-color-warning-light-5: #80a8ec;
--el-color-warning-light-7: var(--min-primary-border);
--el-color-warning-light-8: #dfe9fa;
--el-color-warning-light-9: var(--min-primary-soft);
--el-color-warning-dark-2: var(--min-primary-hover);
--el-color-danger: var(--min-primary);
--el-color-danger-light-3: #4d86e5;
--el-color-danger-light-5: #80a8ec;
--el-color-danger-light-7: var(--min-primary-border);
--el-color-danger-light-8: #dfe9fa;
--el-color-danger-light-9: var(--min-primary-soft);
--el-color-danger-dark-2: var(--min-primary-hover);
--el-color-error: var(--min-primary);
--el-color-error-light-9: var(--min-primary-soft);
--el-box-shadow: var(--min-shadow-overlay);
--el-box-shadow-light: 0 4px 16px rgba(31, 41, 55, 0.06);
--el-box-shadow-lighter: 0 2px 8px rgba(31, 41, 55, 0.05);
--el-font-family: var(--min-font-family);
/* Application shell */
--sidebar-bg: var(--min-bg-white);
--sidebar-text: var(--min-text-regular);
--sidebar-logo-text: var(--min-text-primary);
--menu-hover: var(--min-bg-page);
--menu-active-text: var(--min-primary);
--navbar-bg: var(--min-bg-white);
--navbar-text: var(--min-text-regular);
--navbar-hover: var(--min-bg-page);
--tags-bg: var(--min-bg-white);
--tags-item-bg: var(--min-bg-white);
--tags-item-border: var(--min-border-light);
--tags-item-text: var(--min-text-secondary);
--tags-item-hover: var(--min-bg-page);
--tags-close-hover: var(--min-border-strong);
--main-fill-bg: var(--min-bg-page);
--splitpanes-default-bg: var(--min-bg-white);
}
html.dark {
--min-bg-page: #111315;
--min-bg-white: #181b1f;
--min-bg-subtle: #20242a;
--min-border-light: #30353d;
--min-border-strong: #454b55;
--min-text-primary: #f3f4f6;
--min-text-regular: #d1d5db;
--min-text-secondary: #9ca3af;
--min-text-placeholder: #737b87;
--min-primary: #4d8cff;
--min-primary-hover: #70a3ff;
--min-primary-soft: #1a2b49;
--min-shadow-overlay: 0 8px 24px rgba(0, 0, 0, 0.22);
--el-bg-color: var(--min-bg-white);
--el-bg-color-page: var(--min-bg-page);
--el-bg-color-overlay: var(--min-bg-white);
--el-fill-color: var(--min-bg-subtle);
--el-fill-color-light: var(--min-bg-page);
--el-fill-color-lighter: #1d2126;
--el-fill-color-extra-light: #1a1e22;
--el-text-color-primary: var(--min-text-primary);
--el-text-color-regular: var(--min-text-regular);
--el-text-color-secondary: var(--min-text-secondary);
--el-text-color-placeholder: var(--min-text-placeholder);
--el-border-color: var(--min-border-light);
--el-border-color-light: var(--min-border-light);
--el-border-color-lighter: #292e35;
--el-border-color-extra-light: #242930;
--sidebar-bg: var(--min-bg-white);
--sidebar-text: var(--min-text-regular);
--sidebar-logo-text: var(--min-text-primary);
--menu-hover: var(--min-bg-subtle);
--menu-active-text: var(--min-primary);
--navbar-bg: var(--min-bg-white);
--navbar-text: var(--min-text-regular);
--navbar-hover: var(--min-bg-subtle);
--tags-bg: var(--min-bg-white);
--tags-item-bg: var(--min-bg-white);
--tags-item-border: var(--min-border-light);
--tags-item-text: var(--min-text-secondary);
--tags-item-hover: var(--min-bg-subtle);
--tags-close-hover: var(--min-border-strong);
--main-fill-bg: var(--min-bg-page);
}
html,
body {
background: var(--min-bg-page);
color: var(--min-text-primary);
font-family: var(--min-font-family);
font-size: 14px;
letter-spacing: 0;
}
body {
line-height: 1.5;
}
::selection {
background: var(--min-primary);
color: #ffffff;
}
:focus-visible {
outline: 2px solid var(--min-primary);
outline-offset: 2px;
}
label {
color: var(--min-text-regular);
font-weight: 500;
}
.app-container {
padding: var(--min-space-6);
border: 1px solid var(--min-border-light);
border-radius: var(--min-radius-base);
background: var(--min-bg-white);
}
/* Structural workspace system shared by standard management pages. */
.workspace-view > .app-container {
min-height: 100%;
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
}
.workspace-view .app-container > .el-form.el-form--inline,
.workspace-view .app-container > div:first-child > .el-form.el-form--inline,
.workspace-view .tree-sidebar-content .content-inner > .el-form.el-form--inline {
position: relative;
margin: 0 0 16px;
padding: 20px 20px 4px;
border: 1px solid var(--min-border-light);
border-top: 2px solid var(--min-primary);
background: var(--min-bg-white);
}
.workspace-view .app-container > .el-row.mb8,
.workspace-view .app-container > div:first-child > .el-row.mb8,
.workspace-view .tree-sidebar-content .content-inner > .el-row.mb8 {
min-height: 58px;
margin: 0 0 16px !important;
padding: 12px 16px;
border: 1px solid var(--min-border-light);
display: flex;
align-items: center;
background: var(--min-bg-white);
}
.workspace-view .app-container > .el-table,
.workspace-view .app-container > div:not([class]) > .el-table,
.workspace-view .tree-sidebar-content .content-inner > .el-table {
border: 1px solid var(--min-border-light);
border-bottom: 0;
}
.workspace-view .tree-sidebar-manage-wrap {
min-height: 100%;
border: 1px solid var(--min-border-light);
display: flex;
overflow: hidden;
background: var(--min-bg-white);
}
.workspace-view .tree-sidebar-manage-wrap .tree-sidebar-content {
min-width: 0;
flex: 1;
background: var(--min-bg-page);
}
.workspace-view .tree-sidebar-manage-wrap .content-inner {
height: 100%;
padding: 20px;
overflow: auto;
}
.workspace-view .pagination-container {
min-height: 58px;
margin: 0 !important;
padding: 12px 16px !important;
border: 1px solid var(--min-border-light);
background: var(--min-bg-white);
}
.el-card {
border: 1px solid var(--min-border-light);
border-radius: var(--min-radius-base);
box-shadow: none !important;
}
.el-card__header {
padding: var(--min-space-4) var(--min-space-5);
border-bottom-color: var(--min-border-light);
font-weight: 600;
}
.el-card__body {
padding: var(--min-space-5);
}
.el-button {
border-radius: var(--min-radius-base);
font-weight: 500;
letter-spacing: 0;
box-shadow: none !important;
}
.el-button + .el-button {
margin-left: var(--min-space-base);
}
.el-button--primary:not(.is-plain, .is-link, .is-text):hover,
.el-button--primary:not(.is-plain, .is-link, .is-text):focus {
border-color: var(--min-primary-hover);
background: var(--min-primary-hover);
}
.el-button--success,
.el-button--warning,
.el-button--danger,
.el-button--info {
--el-button-text-color: var(--min-text-regular);
--el-button-bg-color: var(--min-bg-white);
--el-button-border-color: var(--min-border-light);
--el-button-hover-text-color: var(--min-primary);
--el-button-hover-bg-color: var(--min-primary-soft);
--el-button-hover-border-color: var(--min-primary);
--el-button-active-text-color: var(--min-primary);
--el-button-active-border-color: var(--min-primary);
--el-button-active-bg-color: var(--min-primary-soft);
}
.el-button--success.is-disabled,
.el-button--warning.is-disabled,
.el-button--danger.is-disabled,
.el-button--info.is-disabled {
--el-button-disabled-text-color: var(--min-text-placeholder);
--el-button-disabled-bg-color: var(--min-bg-page);
--el-button-disabled-border-color: var(--min-border-light);
}
.el-message-box__status.el-message-box-icon--warning,
.el-message-box__status.el-message-box-icon--error,
.el-message-box__status.el-message-box-icon--success {
color: var(--min-primary);
}
.el-button--cyan,
.el-button--cyan:hover,
.el-button--cyan:focus,
.el-button--cyan:active {
border-color: var(--min-primary) !important;
background: var(--min-primary) !important;
color: #ffffff !important;
}
.el-input__wrapper,
.el-select__wrapper,
.el-textarea__inner,
.el-date-editor.el-input__wrapper,
.el-range-editor.el-input__wrapper {
border-radius: var(--min-radius-base);
box-shadow: 0 0 0 1px var(--min-border-light) inset;
}
.el-input__wrapper:hover,
.el-select__wrapper:hover,
.el-textarea__inner:hover {
box-shadow: 0 0 0 1px var(--min-border-strong) inset;
}
.el-input__wrapper.is-focus,
.el-select__wrapper.is-focused,
.el-textarea__inner:focus {
box-shadow: 0 0 0 1px var(--min-primary) inset !important;
}
.el-form-item {
margin-bottom: var(--min-space-4);
}
.el-form-item__label {
color: var(--min-text-regular);
font-weight: 500;
}
.el-table {
--el-table-header-bg-color: var(--min-bg-page);
--el-table-header-text-color: var(--min-text-secondary);
--el-table-border-color: var(--min-border-light);
--el-table-row-hover-bg-color: var(--min-bg-page);
color: var(--min-text-regular);
background: var(--min-bg-white);
}
.el-table th.el-table__cell {
height: 48px;
padding: var(--min-space-base) 0;
font-size: 12px;
font-weight: 600;
}
.el-table td.el-table__cell {
height: 52px;
padding: 10px 0;
}
.el-table .cell {
line-height: 1.5;
}
.el-dialog,
.el-message-box,
.el-drawer {
border: 1px solid var(--min-border-light);
border-radius: var(--min-radius-base);
box-shadow: var(--min-shadow-overlay);
}
.el-dialog__header,
.el-message-box__header {
padding: var(--min-space-5) var(--min-space-6) var(--min-space-4);
border-bottom: 1px solid var(--min-border-light);
border-left: 4px solid var(--min-primary);
}
.el-dialog__title,
.el-message-box__title {
color: var(--min-text-primary);
font-size: 16px;
font-weight: 600;
}
.el-dialog__body,
.el-message-box__content {
padding: var(--min-space-6);
}
.el-dialog__footer,
.el-message-box__btns {
padding: var(--min-space-4) var(--min-space-6) var(--min-space-5);
border-top: 1px solid var(--min-border-light);
}
.el-drawer {
border-width: 0 0 0 1px;
}
.el-popper.is-light,
.el-dropdown__popper,
.el-select__popper,
.el-picker__popper {
border: 1px solid var(--min-border-light) !important;
border-radius: var(--min-radius-base);
box-shadow: var(--min-shadow-overlay) !important;
}
.el-dropdown-menu,
.el-select-dropdown__list {
padding: var(--min-space-base);
}
.el-dropdown-menu__item,
.el-select-dropdown__item {
border-radius: var(--min-radius-sm);
}
.el-tag,
.el-radio-button__inner,
.el-pagination button,
.el-pagination .el-pager li {
border-radius: var(--min-radius-sm);
}
.el-tabs__nav-wrap::after {
height: 1px;
background: var(--min-border-light);
}
.el-tabs__item {
color: var(--min-text-secondary);
font-weight: 500;
}
.el-collapse,
.el-collapse-item__header,
.el-collapse-item__wrap {
border-color: var(--min-border-light);
}
.el-loading-mask {
background: color-mix(in srgb, var(--min-bg-white) 88%, transparent);
}
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
border-radius: var(--min-radius-sm);
background: var(--min-border-strong);
}
@media (max-width: 768px) {
.app-container {
padding: var(--min-space-4);
}
.el-dialog {
max-width: calc(100vw - 32px);
}
.workspace-view .app-container > .el-form.el-form--inline,
.workspace-view .app-container > div:first-child > .el-form.el-form--inline,
.workspace-view .tree-sidebar-content .content-inner > .el-form.el-form--inline {
padding: 16px 16px 0;
}
.workspace-view .tree-sidebar-manage-wrap {
border: 0;
flex-direction: column;
overflow: visible;
}
.workspace-view .tree-sidebar-manage-wrap .tree-sidebar {
width: 100% !important;
max-height: 280px;
border: 1px solid var(--min-border-light);
}
.workspace-view .tree-sidebar-manage-wrap .content-inner {
padding: 16px 0 0;
}
}

View File

@ -3,6 +3,9 @@ export function handleThemeStyle(theme) {
const isDark = typeof document !== 'undefined' && document.documentElement.classList.contains('dark') const isDark = typeof document !== 'undefined' && document.documentElement.classList.contains('dark')
const primary = isDark ? softenPrimaryForDark(theme) : theme const primary = isDark ? softenPrimaryForDark(theme) : theme
document.documentElement.style.setProperty('--el-color-primary', primary) document.documentElement.style.setProperty('--el-color-primary', primary)
document.documentElement.style.setProperty('--min-primary', primary)
document.documentElement.style.setProperty('--min-primary-hover', isDark ? getLightColor(primary, 0.18) : getDarkColor(primary, 0.15))
document.documentElement.style.setProperty('--min-primary-soft', isDark ? getDarkColor(primary, 0.72) : getLightColor(primary, 0.9))
for (let i = 1; i <= 9; i++) { for (let i = 1; i <= 9; i++) {
document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, `${getLightColor(primary, i / 10)}`) document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, `${getLightColor(primary, i / 10)}`)
} }

View File

@ -235,8 +235,9 @@ onMounted(() => {
.category-tree { .category-tree {
height: 100%; height: 100%;
padding: 20px; padding: 20px;
border-radius: 12px; border: 1px solid var(--min-border-light);
background: #fff; border-radius: var(--min-radius-base);
background: var(--min-bg-white);
.tree-header { .tree-header {
display: flex; display: flex;
@ -245,9 +246,9 @@ onMounted(() => {
margin-bottom: 12px; margin-bottom: 12px;
.stat-label { .stat-label {
font-size: 16px; font-size: 14px;
color: #000; color: var(--min-text-primary);
font-weight: bold; font-weight: 600;
} }
} }
@ -264,7 +265,7 @@ onMounted(() => {
.node-count { .node-count {
margin-left: 8px; margin-left: 8px;
font-size: 12px; font-size: 12px;
color: #94a3b8; color: var(--min-text-secondary);
flex: 0 0 auto; flex: 0 0 auto;
} }

View File

@ -274,9 +274,10 @@ onBeforeUnmount(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.corpus-list { .corpus-list {
background: #fff; background: var(--min-bg-white);
border: 1px solid var(--min-border-light);
padding: 20px; padding: 20px;
border-radius: 12px; border-radius: var(--min-radius-base);
.list-toolbar { .list-toolbar {
display: flex; display: flex;
@ -300,7 +301,7 @@ onBeforeUnmount(() => {
} }
.text-muted { .text-muted {
color: #94a3b8; color: var(--min-text-secondary);
} }
.text-sm { .text-sm {
@ -310,7 +311,7 @@ onBeforeUnmount(() => {
.wave-container { .wave-container {
border-radius: 12px; border-radius: var(--min-radius-base);
padding: 16px; padding: 16px;
min-height: 140px; min-height: 140px;
position: relative; position: relative;

View File

@ -3,7 +3,7 @@
<div class="card" v-for="item in stats" :key="item.label" shadow="never"> <div class="card" v-for="item in stats" :key="item.label" shadow="never">
<div class="flex-between"> <div class="flex-between">
<div class="icon-container" :class="item.iconName"> <div class="icon-container" :class="item.iconName">
<SvgIcon :name="item.iconName" :size="28" color="#fff" /> <SvgIcon :name="item.iconName" :size="24" color="var(--min-primary)" />
</div> </div>
<div class="right"> <div class="right">
<span class="stat-label">{{ item.label }}</span> <span class="stat-label">{{ item.label }}</span>
@ -75,62 +75,47 @@ onMounted(() => {
gap: 16px; gap: 16px;
.card { .card {
background: #fff; min-width: 0;
border-radius: 12px; background: var(--min-bg-white);
padding: 8px 32px; border: 1px solid var(--min-border-light);
border-radius: var(--min-radius-base);
padding: 20px;
.flex-between { .flex-between {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 32px; gap: 16px;
.icon-container { .icon-container {
width: 52px; width: 44px;
height: 52px; height: 44px;
flex: 0 0 44px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-radius: 12px; border-radius: var(--min-radius-sm);
} background: var(--min-primary-soft);
.task {
background: #196afd;
}
.add {
background: #16cb8d;
}
.language {
background: #735df6;
}
.consuming {
background: #fd9131;
}
.timbre {
background: #2f7bf9;
} }
.right { .right {
flex: 1; flex: 1;
min-width: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 8px;
.stat-label { .stat-label {
font-size: 16px; font-size: 13px;
color: #64748b; color: var(--min-text-secondary);
font-weight: 500; font-weight: 400;
} }
.stat-number { .stat-number {
font-size: 24px; font-size: 24px;
font-weight: 700; font-weight: 600;
letter-spacing: -0.02em; letter-spacing: 0;
color: #0f172a; color: var(--min-text-primary);
} }
.stat-trend { .stat-trend {
@ -143,19 +128,31 @@ onMounted(() => {
} }
&.up { &.up {
color: #22c55e; color: var(--el-color-success);
} }
&.down { &.down {
color: #ef4444; color: var(--el-color-danger);
} }
&.neutral { &.neutral {
color: #94a3b8; color: var(--min-text-secondary);
} }
} }
} }
} }
} }
} }
@media (max-width: 1200px) {
.row-cards {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 640px) {
.row-cards {
grid-template-columns: minmax(0, 1fr);
}
}
</style> </style>

View File

@ -1,17 +1,21 @@
<template> <template>
<div class="page-container"> <div class="page-container">
<!-- 统计卡片 --> <aside class="insight-panel">
<header>
<span>CORPUS / INDEX</span>
<strong>语料数据概况</strong>
</header>
<StatCards /> <StatCards />
</aside>
<!-- 主体:目录树 + 列表 + 详情 --> <main class="main-layout">
<div class="main-layout">
<div class="left-panel"> <div class="left-panel">
<CategoryTree @select-category="onSelectCategory" /> <CategoryTree @select-category="onSelectCategory" />
</div> </div>
<div class="right-panel"> <div class="right-panel">
<CorpusList :categoryId="categoryId" /> <CorpusList :categoryId="categoryId" />
</div> </div>
</div> </main>
</div> </div>
</template> </template>
<script setup> <script setup>
@ -29,32 +33,82 @@ const onSelectCategory = (node) => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.page-container { .page-container {
display: flex; display: grid;
flex-direction: column; grid-template-columns: 272px minmax(0, 1fr);
gap: 20px; gap: 20px;
width: 100%; width: 100%;
height: 100%; height: 100%;
min-height: 0;
overflow: hidden;
}
.insight-panel {
min-width: 0;
overflow: auto; overflow: auto;
> header {
display: flex;
min-height: 82px;
flex-direction: column;
justify-content: center;
gap: 5px;
padding: 16px 20px;
border: 1px solid var(--min-border-light);
border-bottom: 0;
background: var(--min-bg-white);
span {
color: var(--min-primary);
font-size: 10px;
font-weight: 600;
}
strong {
color: var(--min-text-primary);
font-size: 15px;
font-weight: 600;
}
}
:deep(.row-cards) {
grid-template-columns: minmax(0, 1fr);
gap: 0;
}
:deep(.row-cards .card) {
min-height: 132px;
border-bottom: 0;
border-radius: 0;
}
:deep(.row-cards .card:last-child) {
border-bottom: 1px solid var(--min-border-light);
}
} }
.main-layout { .main-layout {
display: grid; display: grid;
grid-template-columns: 260px 1fr; grid-template-columns: 260px 1fr;
gap: 20px; gap: 20px;
align-items: start; min-width: 0;
flex: 1; min-height: 0;
align-items: stretch;
overflow: auto;
} }
.left-panel { .left-panel {
position: sticky; position: sticky;
top: 20px; top: 20px;
height: 100%; height: 100%;
min-height: 0;
} }
.right-panel { .right-panel {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 20px; gap: 20px;
min-width: 0;
min-height: 0;
} }
.detail-wrapper { .detail-wrapper {
@ -62,8 +116,27 @@ const onSelectCategory = (node) => {
} }
@media (max-width: 1200px) { @media (max-width: 1200px) {
.page-container {
grid-template-columns: minmax(0, 1fr);
height: auto;
overflow: auto;
}
.insight-panel {
overflow: visible;
}
.insight-panel :deep(.row-cards) {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.insight-panel :deep(.row-cards .card) {
border-bottom: 1px solid var(--min-border-light);
}
.main-layout { .main-layout {
grid-template-columns: 1fr; grid-template-columns: 1fr;
overflow: visible;
} }
.left-panel { .left-panel {

View File

@ -7,8 +7,8 @@
<!-- 播放器主体 --> <!-- 播放器主体 -->
<div class="player-main"> <div class="player-main">
<!-- 播放/暂停按钮 --> <!-- 播放/暂停按钮 -->
<SvgIcon class="btn" v-if="!isPlaying" name="start" :size="32" color="#136cfc" @click="togglePlay" /> <SvgIcon class="btn" v-if="!isPlaying" name="start" :size="32" color="var(--min-primary)" @click="togglePlay" />
<SvgIcon class="btn" v-if="isPlaying" name="pause" :size="32" color="#d33257" @click="togglePlay" /> <SvgIcon class="btn" v-if="isPlaying" name="pause" :size="32" color="var(--min-primary)" @click="togglePlay" />
<!-- 当前时间 --> <!-- 当前时间 -->
<span class="time-text">{{ formatTime(currentTime) }}</span> <span class="time-text">{{ formatTime(currentTime) }}</span>
@ -211,9 +211,10 @@ onBeforeUnmount(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.audio-player { .audio-player {
width: 100%; width: 100%;
border-radius: 12px; border-radius: var(--min-radius-base);
padding: 12px 28px; padding: 12px 20px;
border: 1px solid #afafaf; border: 1px solid var(--min-border-light);
background: var(--min-bg-white);
.player-main { .player-main {
display: flex; display: flex;
@ -224,7 +225,7 @@ onBeforeUnmount(() => {
.time-text { .time-text {
flex-shrink: 0; flex-shrink: 0;
font-size: 13px; font-size: 13px;
color: #94a3b8; color: var(--min-text-secondary);
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
width: 42px; width: 42px;
text-align: center; text-align: center;
@ -236,8 +237,8 @@ onBeforeUnmount(() => {
position: relative; position: relative;
flex: 1; flex: 1;
height: 6px; height: 6px;
background: #e5e5e5; background: var(--min-bg-subtle);
border-radius: 3px; border-radius: var(--min-radius-sm);
cursor: pointer; cursor: pointer;
overflow: visible; overflow: visible;
@ -246,8 +247,8 @@ onBeforeUnmount(() => {
top: 0; top: 0;
left: 0; left: 0;
height: 100%; height: 100%;
background: rgba(255, 255, 255, 0.1); background: var(--min-border-light);
border-radius: 3px; border-radius: var(--min-radius-sm);
transition: width 0.3s ease; transition: width 0.3s ease;
} }
@ -256,8 +257,8 @@ onBeforeUnmount(() => {
top: 0; top: 0;
left: 0; left: 0;
height: 100%; height: 100%;
background: linear-gradient(90deg, #3b82f6, #60a5fa); background: var(--min-primary);
border-radius: 3px; border-radius: var(--min-radius-sm);
transition: width 0.05s linear; transition: width 0.05s linear;
.progress-thumb { .progress-thumb {
@ -267,9 +268,10 @@ onBeforeUnmount(() => {
transform: translateY(-50%) scale(0); transform: translateY(-50%) scale(0);
width: 14px; width: 14px;
height: 14px; height: 14px;
background: #fff; background: var(--min-bg-white);
border-radius: 50%; border-radius: 50%;
box-shadow: 0 0 8px rgba(59, 130, 246, 0.6); border: 2px solid var(--min-primary);
box-shadow: none;
transition: transform 0.15s ease; transition: transform 0.15s ease;
} }
} }
@ -289,8 +291,8 @@ onBeforeUnmount(() => {
position: absolute; position: absolute;
top: -32px; top: -32px;
transform: translateX(-50%); transform: translateX(-50%);
background: rgba(0, 0, 0, 0.85); background: var(--min-text-primary);
color: #e2e8f0; color: var(--min-bg-white);
font-size: 12px; font-size: 12px;
padding: 3px 8px; padding: 3px 8px;
border-radius: 4px; border-radius: 4px;
@ -318,7 +320,7 @@ onBeforeUnmount(() => {
} }
.volume-wrapper .el-button { .volume-wrapper .el-button {
color: #94a3b8 !important; color: var(--min-text-secondary) !important;
} }
.volume-slider { .volume-slider {
@ -327,12 +329,12 @@ onBeforeUnmount(() => {
:deep(.volume-slider .el-slider__runway) { :deep(.volume-slider .el-slider__runway) {
height: 4px; height: 4px;
background: rgba(255, 255, 255, 0.1); background: var(--min-bg-subtle);
} }
:deep(.volume-slider .el-slider__bar) { :deep(.volume-slider .el-slider__bar) {
height: 4px; height: 4px;
background: #3b82f6; background: var(--min-primary);
} }
:deep(.volume-slider .el-slider__button-wrapper) { :deep(.volume-slider .el-slider__button-wrapper) {
@ -342,12 +344,18 @@ onBeforeUnmount(() => {
:deep(.volume-slider .el-slider__button) { :deep(.volume-slider .el-slider__button) {
width: 12px; width: 12px;
height: 12px; height: 12px;
border: 2px solid #3b82f6; border: 2px solid var(--min-primary);
background: #fff; background: var(--min-bg-white);
} }
/* 进度条 hover 加粗效果 */ /* 进度条 hover 加粗效果 */
.progress-wrapper:hover { @media (max-width: 640px) {
height: 8px; .audio-player {
padding: 12px;
}
.volume-slider {
display: none;
}
} }
</style> </style>

View File

@ -325,18 +325,22 @@ const cancel = () => {
<style lang="scss" scoped> <style lang="scss" scoped>
.main-workspace { .main-workspace {
.workspace-container { .workspace-container {
display: flex; display: grid;
gap: 24px; grid-template-columns: minmax(320px, 0.8fr) minmax(440px, 1.2fr);
gap: 20px;
.right { .right {
flex: 1; flex: 1;
background: #fff; min-width: 0;
background: var(--min-bg-white);
border: 1px solid var(--min-border-light);
border-top: 2px solid var(--min-primary);
padding: 20px; padding: 20px;
border-radius: 12px; border-radius: 0;
.title { .title {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: 600;
margin-bottom: 10px; margin-bottom: 10px;
} }
@ -357,13 +361,16 @@ const cancel = () => {
.left { .left {
flex: 1; flex: 1;
background: #fff; min-width: 0;
background: var(--min-bg-white);
border: 1px solid var(--min-border-light);
border-top: 2px solid var(--min-primary);
padding: 20px; padding: 20px;
border-radius: 12px; border-radius: 0;
.title { .title {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: 600;
margin-bottom: 10px; margin-bottom: 10px;
} }
@ -386,4 +393,11 @@ const cancel = () => {
} }
} }
} }
@media (max-width: 980px) {
.main-workspace .workspace-container {
grid-template-columns: minmax(0, 1fr);
gap: 16px;
}
}
</style> </style>

View File

@ -3,7 +3,7 @@
<div class="card" v-for="item in stats" :key="item.label" shadow="never"> <div class="card" v-for="item in stats" :key="item.label" shadow="never">
<div class="flex-between"> <div class="flex-between">
<div class="icon-container" :class="item.iconName"> <div class="icon-container" :class="item.iconName">
<SvgIcon :name="item.iconName" :size="28" color="#fff" /> <SvgIcon :name="item.iconName" :size="24" color="var(--min-primary)" />
</div> </div>
<div class="right"> <div class="right">
<span class="stat-label">{{ item.label }}</span> <span class="stat-label">{{ item.label }}</span>
@ -81,66 +81,51 @@ onMounted(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.row-cards { .row-cards {
display: grid; display: grid;
grid-template-columns: repeat(5, 1fr); grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 16px; gap: 16px;
.card { .card {
background: #fff; min-width: 0;
border-radius: 12px; background: var(--min-bg-white);
padding: 8px 32px; border: 1px solid var(--min-border-light);
border-radius: var(--min-radius-base);
padding: 20px;
.flex-between { .flex-between {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 32px; gap: 16px;
.icon-container { .icon-container {
width: 52px; width: 44px;
height: 52px; height: 44px;
flex: 0 0 44px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-radius: 12px; border-radius: var(--min-radius-sm);
} background: var(--min-primary-soft);
.task {
background: #196afd;
}
.add {
background: #16cb8d;
}
.language {
background: #735df6;
}
.consuming {
background: #fd9131;
}
.timbre {
background: #2f7bf9;
} }
.right { .right {
flex: 1; flex: 1;
min-width: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 8px;
.stat-label { .stat-label {
font-size: 16px; font-size: 13px;
color: #64748b; color: var(--min-text-secondary);
font-weight: 500; font-weight: 400;
} }
.stat-number { .stat-number {
font-size: 24px; font-size: 24px;
font-weight: 700; font-weight: 600;
letter-spacing: -0.02em; letter-spacing: 0;
color: #0f172a; color: var(--min-text-primary);
} }
.stat-trend { .stat-trend {
@ -153,19 +138,37 @@ onMounted(() => {
} }
&.up { &.up {
color: #22c55e; color: var(--el-color-success);
} }
&.down { &.down {
color: #ef4444; color: var(--el-color-danger);
} }
&.neutral { &.neutral {
color: #94a3b8; color: var(--min-text-secondary);
} }
} }
} }
} }
} }
} }
@media (max-width: 1440px) {
.row-cards {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (max-width: 900px) {
.row-cards {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 640px) {
.row-cards {
grid-template-columns: minmax(0, 1fr);
}
}
</style> </style>

View File

@ -278,9 +278,10 @@ defineExpose({
<style lang="scss" scoped> <style lang="scss" scoped>
.taskList-container { .taskList-container {
background: #fff; background: var(--min-bg-white);
border: 1px solid var(--min-border-light);
padding: 20px; padding: 20px;
border-radius: 12px; border-radius: var(--min-radius-base);
} }
.flex-between { .flex-between {
@ -304,7 +305,7 @@ defineExpose({
} }
.text-muted { .text-muted {
color: #94a3b8; color: var(--min-text-secondary);
} }
.text-sm { .text-sm {
@ -317,13 +318,13 @@ defineExpose({
.stat-label { .stat-label {
font-size: 13px; font-size: 13px;
color: #64748b; color: var(--min-text-secondary);
font-weight: 500; font-weight: 400;
} }
.wave-container { .wave-container {
border-radius: 12px; border-radius: var(--min-radius-base);
padding: 16px; padding: 16px;
min-height: 140px; min-height: 140px;
position: relative; position: relative;

View File

@ -1,17 +1,16 @@
<template> <template>
<div class="page-container"> <div class="page-container">
<!-- 统计卡片 --> <aside class="statCards">
<div class="statCards"> <header><span>SPEECH / METRICS</span><strong>合成运行指标</strong></header>
<StatCards /> <StatCards />
</div> </aside>
<!-- 主工作区 --> <main class="synthesis-workspace">
<div class="mainWorkspace"> <div class="mainWorkspace">
<MainWorkspace @refresh="refresh" /> <MainWorkspace @refresh="refresh" />
</div> </div>
<!-- 任务列表 -->
<TaskList ref="taskListRef" /> <TaskList ref="taskListRef" />
</main>
</div> </div>
</template> </template>
<script setup> <script setup>
@ -27,16 +26,70 @@ const refresh = () => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.page-container { .page-container {
display: grid;
grid-template-columns: 252px minmax(0, 1fr);
gap: 20px;
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: auto; min-height: 0;
overflow: hidden;
.statCards { .statCards {
margin-bottom: 12px; min-width: 0;
overflow: auto;
> header {
display: flex;
min-height: 78px;
flex-direction: column;
justify-content: center;
gap: 5px;
padding: 16px 20px;
border: 1px solid var(--min-border-light);
border-bottom: 0;
background: var(--min-bg-white);
span { color: var(--min-primary); font-size: 10px; font-weight: 600; }
strong { color: var(--min-text-primary); font-size: 15px; font-weight: 600; }
}
:deep(.row-cards) {
grid-template-columns: minmax(0, 1fr);
gap: 0;
}
:deep(.row-cards .card) {
min-height: 118px;
border-bottom: 0;
border-radius: 0;
}
:deep(.row-cards .card:last-child) {
border-bottom: 1px solid var(--min-border-light);
}
}
.synthesis-workspace {
min-width: 0;
min-height: 0;
overflow: auto;
} }
.mainWorkspace { .mainWorkspace {
margin-bottom: 12px; margin-bottom: 20px;
} }
} }
@media (max-width: 1180px) {
.page-container {
grid-template-columns: minmax(0, 1fr);
height: auto;
overflow: auto;
}
.page-container .statCards { overflow: visible; }
.page-container .statCards :deep(.row-cards) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.page-container .statCards :deep(.row-cards .card) { border-bottom: 1px solid var(--min-border-light); }
.page-container .synthesis-workspace { overflow: visible; }
}
</style> </style>

View File

@ -2,8 +2,8 @@
<div class="app-container robot-device-page"> <div class="app-container robot-device-page">
<section class="page-heading"> <section class="page-heading">
<div> <div>
<h2>机器人设备</h2> <span>DEVICE / INVENTORY</span>
<p>设备由机器人在线上报,切换机器人后自动刷新。</p> <h2>机器人设备台账</h2>
</div> </div>
<div class="robot-selector"> <div class="robot-selector">
<span>当前机器人</span> <span>当前机器人</span>
@ -16,20 +16,24 @@
</div> </div>
</section> </section>
<section class="status-strip"> <div class="device-workspace">
<aside class="status-strip">
<header><span>STATUS</span><strong>库存状态</strong></header>
<div><strong>{{ devices.length }}</strong><span>设备总数</span></div> <div><strong>{{ devices.length }}</strong><span>设备总数</span></div>
<div><strong class="online">{{ onlineCount }}</strong><span>在线设备</span></div> <div><strong class="online">{{ onlineCount }}</strong><span>在线设备</span></div>
<div><strong class="fault">{{ faultCount }}</strong><span>故障设备</span></div> <div><strong class="fault">{{ faultCount }}</strong><span>故障设备</span></div>
</aside>
<section class="device-data">
<div class="filters"> <div class="filters">
<span>DEVICE LIST</span>
<el-input v-model="keyword" clearable :prefix-icon="Search" placeholder="搜索名称或设备 ID" /> <el-input v-model="keyword" clearable :prefix-icon="Search" placeholder="搜索名称或设备 ID" />
<el-select v-model="deviceKind" clearable placeholder="全部类型"> <el-select v-model="deviceKind" clearable placeholder="全部类型">
<el-option v-for="item in deviceKinds" :key="item.value" <el-option v-for="item in deviceKinds" :key="item.value"
:label="item.label" :value="item.value" /> :label="item.label" :value="item.value" />
</el-select> </el-select>
</div> </div>
</section> <el-table v-loading="loading" :data="filteredDevices" height="100%"
<el-table v-loading="loading" :data="filteredDevices" height="calc(100vh - 285px)"
empty-text="当前机器人尚未上报设备"> empty-text="当前机器人尚未上报设备">
<el-table-column label="设备" min-width="220"> <el-table-column label="设备" min-width="220">
<template #default="{ row }"> <template #default="{ row }">
@ -68,6 +72,8 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</section>
</div>
</div> </div>
</template> </template>
@ -155,30 +161,45 @@ onMounted(async () => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.robot-device-page { min-height: 100%; background: #f5f7fa; } .robot-device-page { display: flex; height: 100%; min-height: 560px; flex-direction: column; background: transparent; }
.page-heading { display: flex; align-items: center; justify-content: space-between; gap: 24px; padding: 4px 0 20px; } .page-heading { display: flex; align-items: center; justify-content: space-between; gap: 24px; padding: 4px 0 20px; }
.page-heading h2 { margin: 0 0 5px; color: #1f2937; font-size: 22px; letter-spacing: 0; } .page-heading > div:first-child { display: flex; align-items: baseline; gap: 14px; }
.page-heading p { margin: 0; color: #8492a6; font-size: 13px; } .page-heading > div:first-child > span { color: var(--min-primary); font-size: 10px; font-weight: 600; }
.robot-selector { display: flex; align-items: center; gap: 10px; color: #606266; font-size: 13px; } .page-heading h2 { margin: 0 0 4px; color: var(--min-text-primary); font-size: 20px; font-weight: 600; letter-spacing: 0; }
.robot-selector { display: flex; align-items: center; gap: 8px; color: var(--min-text-regular); font-size: 13px; }
.robot-selector .el-select { width: 330px; } .robot-selector .el-select { width: 330px; }
.status-strip { display: flex; align-items: center; gap: 34px; margin-bottom: 14px; padding: 14px 18px; border: 1px solid #e4e7ed; border-radius: 6px; background: #fff; } .device-workspace { display: grid; flex: 1; min-height: 0; grid-template-columns: 196px minmax(0, 1fr); border: 1px solid var(--min-border-light); background: var(--min-bg-white); }
.status-strip > div:not(.filters) { display: grid; min-width: 74px; } .status-strip { display: flex; min-width: 0; flex-direction: column; border-right: 1px solid var(--min-border-light); background: var(--min-bg-white); }
.status-strip strong { color: #303133; font-size: 24px; line-height: 28px; } .status-strip header { display: flex; min-height: 74px; flex-direction: column; justify-content: center; gap: 4px; padding: 16px 20px; border-bottom: 1px solid var(--min-border-light); }
.status-strip strong.online { color: #16a34a; } .status-strip header span { color: var(--min-primary); font-size: 10px; font-weight: 600; }
.status-strip strong.fault { color: #dc2626; } .status-strip header strong { color: var(--min-text-primary); font-size: 15px; font-weight: 600; }
.status-strip span { color: #909399; font-size: 12px; } .status-strip > div { display: flex; min-height: 112px; flex-direction: column; justify-content: center; gap: 6px; padding: 20px; border-bottom: 1px solid var(--min-border-light); }
.filters { display: flex; flex: 1; justify-content: flex-end; gap: 10px; } .status-strip strong { color: var(--min-text-primary); font-size: 28px; font-weight: 600; line-height: 32px; }
.status-strip strong.online { color: var(--min-primary); }
.status-strip strong.fault { color: var(--min-text-primary); }
.status-strip span { color: var(--min-text-secondary); font-size: 12px; }
.device-data { display: grid; min-width: 0; min-height: 0; grid-template-rows: 74px minmax(0, 1fr); }
.filters { display: flex; min-width: 0; align-items: center; justify-content: flex-end; gap: 10px; padding: 14px 16px; border-bottom: 1px solid var(--min-border-light); }
.filters > span { margin-right: auto; color: var(--min-text-secondary); font-size: 10px; font-weight: 600; }
.filters .el-input { width: 240px; } .filters .el-input { width: 240px; }
.filters .el-select { width: 150px; } .filters .el-select { width: 150px; }
.device-name { display: flex; align-items: center; gap: 12px; } .device-name { display: flex; align-items: center; gap: 12px; }
.device-icon { display: grid; width: 34px; height: 34px; place-items: center; border-radius: 6px; background: #ecf5ff; color: #409eff; } .device-icon { display: grid; width: 34px; height: 34px; place-items: center; border-radius: var(--min-radius-sm); background: var(--min-primary-soft); color: var(--min-primary); }
.device-name div { display: grid; min-width: 0; } .device-name div { display: grid; min-width: 0; }
.device-name b { color: #303133; font-weight: 600; } .device-name b { color: var(--min-text-primary); font-weight: 600; }
.device-name small { overflow: hidden; color: #909399; text-overflow: ellipsis; } .device-name small { overflow: hidden; color: var(--min-text-secondary); text-overflow: ellipsis; }
.muted { color: #c0c4cc; } .muted { color: var(--min-text-placeholder); }
@media (max-width: 900px) { @media (max-width: 900px) {
.page-heading, .status-strip { align-items: stretch; flex-direction: column; } .robot-device-page { height: auto; }
.page-heading { align-items: stretch; flex-direction: column; }
.device-workspace { grid-template-columns: minmax(0, 1fr); }
.status-strip { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); border-right: 0; border-bottom: 1px solid var(--min-border-light); }
.status-strip header { grid-column: 1 / -1; }
.status-strip > div { min-height: 90px; border-right: 1px solid var(--min-border-light); }
.device-data { min-height: 620px; }
.robot-selector, .filters { justify-content: flex-start; flex-wrap: wrap; } .robot-selector, .filters { justify-content: flex-start; flex-wrap: wrap; }
.robot-selector .el-select, .filters .el-input { width: 100%; } .robot-selector .el-select, .filters .el-input { width: 100%; }
.filters { height: auto; }
.filters > span { width: 100%; }
} }
</style> </style>

View File

@ -68,22 +68,23 @@ onUnmounted(() => {
.context-menu { .context-menu {
position: fixed; /* 使用 fixed 定位,不受父容器 scroll 影响 */ position: fixed; /* 使用 fixed 定位,不受父容器 scroll 影响 */
z-index: 9999; z-index: 9999;
background-color: #fff; background-color: var(--min-bg-white);
border: 1px solid #eee; border: 1px solid var(--min-border-light);
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); box-shadow: var(--min-shadow-overlay);
border-radius: 4px; border-radius: var(--min-radius-base);
padding: 5px 0; padding: 4px 0;
} }
.menu-item { .menu-item {
padding: 8px 15px; padding: 8px 15px;
cursor: pointer; cursor: pointer;
font-size: 14px; font-size: 14px;
color: #606266; color: var(--min-text-regular);
white-space: nowrap; white-space: nowrap;
} }
.menu-item:hover { .menu-item:hover {
background-color: #f5f7fa; color: var(--min-primary);
background-color: var(--min-primary-soft);
} }
</style> </style>

View File

@ -90,18 +90,18 @@ const props = defineProps({
linePadding: { type: Number, default: 20 }, linePadding: { type: Number, default: 20 },
// --- 仅用于样式的 Props --- // --- 仅用于样式的 Props ---
buttonGradient: { type: Array, default: () => ['#00f0ff', '#00d0ff'] }, // 按钮背景渐变色 buttonGradient: { type: Array, default: () => ['var(--min-primary)', 'var(--min-primary)'] },
buttonHoverGradient: { type: Array, default: () => ['#00d0ff', '#00f0ff'] }, // 悬停时的渐变色 buttonHoverGradient: { type: Array, default: () => ['var(--min-primary-hover)', 'var(--min-primary-hover)'] },
buttonBorderColor: { type: String, default: 'rgba(0, 255, 255, 0.6)' }, buttonBorderColor: { type: String, default: 'var(--min-primary)' },
lineColor: { type: String, default: 'rgba(0, 150, 200, 0.4)' }, // 细线基础颜色 lineColor: { type: String, default: 'var(--min-primary)' },
lineHoverColor: { type: String, default: 'rgba(0, 255, 255, 1)' }, // 扫描线高亮颜色 lineHoverColor: { type: String, default: 'var(--min-primary)' },
lineStrokeWidth: { type: Number, default: 3 }, lineStrokeWidth: { type: Number, default: 3 },
neonGlowIntensity: { type: Number, default: 3 }, // 霓虹光效强度 neonGlowIntensity: { type: Number, default: 3 }, // 霓虹光效强度
scanLineSpeed: { type: Number, default: 8 }, // 扫描线动画速度 scanLineSpeed: { type: Number, default: 8 }, // 扫描线动画速度
animationDuration: { type: Number, default: 0.4 }, // 基础动画时长 animationDuration: { type: Number, default: 0.4 }, // 基础动画时长
hoverScale: { type: Number, default: 1.03 }, // 悬停时的放大比例 hoverScale: { type: Number, default: 1 },
// --- New Props for Flowing Light Effect --- // --- New Props for Flowing Light Effect ---
flowLineColor: { type: String, default: 'rgba(0, 255, 255, 0.8)' }, // 流光颜色 flowLineColor: { type: String, default: 'var(--min-primary)' },
flowLineAnimationSpeed: { type: Number, default: 7 }, // 流光动画速度 flowLineAnimationSpeed: { type: Number, default: 7 }, // 流光动画速度
}); });
@ -167,14 +167,11 @@ const buttonStyle = computed(() => {
const buttonWidthPx = typeof props.buttonWidth === 'number' ? `${props.buttonWidth}px` : props.buttonWidth; const buttonWidthPx = typeof props.buttonWidth === 'number' ? `${props.buttonWidth}px` : props.buttonWidth;
const buttonHeightPx = typeof props.buttonHeight === 'number' ? `${props.buttonHeight}px` : props.buttonHeight; const buttonHeightPx = typeof props.buttonHeight === 'number' ? `${props.buttonHeight}px` : props.buttonHeight;
const currentGradient = isHovering.value ? props.buttonHoverGradient : props.buttonGradient; return {
const style = {
position: 'absolute', position: 'absolute',
left: `${calculatedButtonX.value}px`, left: `${calculatedButtonX.value}px`,
top: `${calculatedButtonY.value}px`, top: `${calculatedButtonY.value}px`,
transform: 'translate(-50%, -50%)', // 居中对齐 transform: 'translate(-50%, -50%)',
cursor: 'pointer', cursor: 'pointer',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
@ -182,88 +179,44 @@ const buttonStyle = computed(() => {
boxSizing: 'border-box', boxSizing: 'border-box',
width: buttonWidthPx, width: buttonWidthPx,
height: buttonHeightPx, height: buttonHeightPx,
borderRadius: '15px', // 强制平直四角 border: `1px solid ${props.buttonBorderColor}`,
// 更硬朗的背景,减少模糊,增加纹理感 borderRadius: '4px',
background: `linear-gradient(135deg, ${currentGradient[0]}, ${currentGradient[1]})`, background: isHovering.value ? 'var(--min-primary-hover)' : 'var(--min-primary)',
// 模拟金属/磨砂质感,但更锐利 boxShadow: 'none',
boxShadow: ` color: 'var(--min-bg-white)',
inset 0 0 0 2px ${props.buttonBorderColor}, /* 内部边框 */
0 0 5px rgba(0, 150, 200, 0.5), /* 较弱的主光晕 */
0 0 12px rgba(0, 200, 255, 0.6), /* 第二层光晕 */
0 3px 10px rgba(0, 0, 0, 0.4) /* 底部阴影 */
`,
color: '#ffffff',
fontSize: '14px', fontSize: '14px',
fontWeight: 'bold', // 更粗的字体 fontWeight: '600',
letterSpacing: '0.7px', letterSpacing: '0',
textShadow: '0 0 5px rgba(0, 150, 200, 0.9)', // 文本的锐利光效 textShadow: 'none',
transition: `all ${props.animationDuration}s ease-in-out`, transition: `background ${props.animationDuration}s ease`,
overflow: 'visible', // 允许流光伪元素溢出 overflow: 'visible',
zIndex: 1, zIndex: 1,
}; };
if (isHovering.value) {
style.transform = `translate(-50%, -50%) scale(${props.hoverScale})`;
style.boxShadow = `
inset 0 0 0 3px ${props.buttonBorderColor}, /* 悬停时内部边框变宽 */
0 0 8px rgba(0, 200, 255, 0.7),
0 0 18px rgba(0, 230, 255, 0.8),
0 5px 15px rgba(0, 0, 0, 0.5) /* 底部阴影增强 */
`;
}
return style;
}); });
// 按钮形状裁剪器的样式 // 按钮形状裁剪器的样式
const clipperStyle = computed(() => { const clipperStyle = computed(() => {
const currentGradient = isHovering.value ? props.buttonHoverGradient : props.buttonGradient; return {
position: 'relative',
const style = {
position: 'relative', // 相对于 techButtonContainer 定位
width: '100%', width: '100%',
height: '100%', height: '100%',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
boxSizing: 'border-box', boxSizing: 'border-box',
borderRadius: '0px', // **clipper 的形状由 clip-path 定义** borderRadius: '3px',
background: `linear-gradient(135deg, ${currentGradient[0]}, ${currentGradient[1]})`, background: isHovering.value ? 'var(--min-primary-hover)' : 'var(--min-primary)',
boxShadow: ` boxShadow: 'none',
inset 0 0 0 2px ${props.buttonBorderColor}, /* 内部边框 */ color: 'var(--min-bg-white)',
0 0 5px rgba(0, 150, 200, 0.5), /* 较弱的主光晕 */
0 0 12px rgba(0, 200, 255, 0.6), /* 第二层光晕 */
0 3px 10px rgba(0, 0, 0, 0.4) /* 底部阴影 */
`,
color: '#ffffff',
fontSize: '14px', fontSize: '14px',
fontWeight: 'bold', fontWeight: '600',
letterSpacing: '0.7px', letterSpacing: '0',
textShadow: '0 0 5px rgba(0, 150, 200, 0.9)', // 文本的锐利光效 textShadow: 'none',
transition: `all ${props.animationDuration}s ease-in-out`, transition: `background ${props.animationDuration}s ease`,
overflow: 'hidden', // **clipper 内部的内容会被 clip-path 裁剪** overflow: 'hidden',
clipPath: `polygon( clipPath: 'none',
6px 0, /* 左上角内缩进5px */ zIndex: 1,
calc(100% - 6px) 0, /* 右上角内缩进5px */
100% 6px, /* 右上角外延5px */
100% calc(100% - 6px), /* 右下角内缩进5px */
calc(100% - 6px) 100%,
6px 100%, /* 左下角内缩进5px */
0 calc(100% - 6px),
0 6px, /* 左下角外延5px */
6px 0px /* 左上角内缩进5px(重复起点,闭合多边形) */
)`,
zIndex: 1, // 确保clipper在流光之上
}; };
if (isHovering.value) {
style.boxShadow = `
inset 0 0 0 3px ${props.buttonBorderColor}, /* 悬停时内部边框变宽 */
0 0 8px rgba(0, 200, 255, 0.7),
0 0 18px rgba(0, 230, 255, 0.8),
0 5px 15px rgba(0, 0, 0, 0.5) /* 底部阴影增强 */
`;
}
return style;
}); });
// 线的三个关键点 (相对于 SVG 内部坐标系 (0,0) 是按钮中心) // 线的三个关键点 (相对于 SVG 内部坐标系 (0,0) 是按钮中心)
@ -428,7 +381,7 @@ watch(
box-sizing: border-box; box-sizing: border-box;
overflow: visible; /* 允许 SVG 滤镜和动画效果溢出 */ overflow: visible; /* 允许 SVG 滤镜和动画效果溢出 */
z-index: 1; z-index: 1;
border-radius: 8px; /* 科技感圆角 */ border-radius: var(--min-radius-base);
/* 基础过渡效果,用于缩放和阴影 */ /* 基础过渡效果,用于缩放和阴影 */
/* transition: transform v-bind('`${props.animationDuration}s`') ease-in-out, box-shadow v-bind('`${props.animationDuration}s`') ease-in-out, background v-bind('`${props.animationDuration}s`') ease-in-out; */ /* transition: transform v-bind('`${props.animationDuration}s`') ease-in-out, box-shadow v-bind('`${props.animationDuration}s`') ease-in-out, background v-bind('`${props.animationDuration}s`') ease-in-out; */
} }
@ -457,12 +410,12 @@ watch(
} }
.button-text { .button-text {
font-family: 'Orbitron', sans-serif; /* 科技感字体 */ font-family: var(--min-font-family);
font-weight: bold; font-weight: 600;
text-transform: uppercase; text-transform: none;
letter-spacing: 1px; letter-spacing: 0;
/* color: v-bind('props.buttonBorderColor'); 文本颜色与边框颜色呼应 */ /* color: v-bind('props.buttonBorderColor'); 文本颜色与边框颜色呼应 */
color: #fff; color: var(--min-bg-white);
} }
/* --- SVG Line Styles --- */ /* --- SVG Line Styles --- */
@ -476,7 +429,8 @@ watch(
/* 主线条的动画 */ /* 主线条的动画 */
.main-line { .main-line {
animation: scanline v-bind('`${props.scanLineSpeed}s`') linear infinite; display: none;
animation: none;
/* 调整 stroke-dasharray 来控制扫描线的长度和间隔 */ /* 调整 stroke-dasharray 来控制扫描线的长度和间隔 */
stroke-dasharray: 15px, 80px; /* 15px 显示, 100px 隐藏 */ stroke-dasharray: 15px, 80px; /* 15px 显示, 100px 隐藏 */
stroke-dashoffset: 0; /* 初始位置 */ stroke-dashoffset: 0; /* 初始位置 */
@ -491,7 +445,7 @@ watch(
/* 细微基线样式 */ /* 细微基线样式 */
.base-line { .base-line {
opacity: 0.6; opacity: 0.5;
transition: all v-bind('`${props.animationDuration}s`') ease-in-out; transition: all v-bind('`${props.animationDuration}s`') ease-in-out;
} }
.tech-button-with-line:hover .base-line { .tech-button-with-line:hover .base-line {
@ -501,7 +455,7 @@ watch(
/* --- Flowing Light Border Effect --- */ /* --- Flowing Light Border Effect --- */
/* 使用 ::before 伪元素实现四周的流光 */ /* 使用 ::before 伪元素实现四周的流光 */
.tech-button-with-line::before { .tech-button-with-line::before {
content: ''; content: none;
position: absolute; position: absolute;
top: -4px; /* 稍向外延伸 */ top: -4px; /* 稍向外延伸 */
left: -4px; left: -4px;
@ -509,12 +463,7 @@ watch(
bottom: -4px; bottom: -4px;
/* 关键:创建动态的流光渐变 */ /* 关键:创建动态的流光渐变 */
/* 渐变方向是从左到右,颜色从透明到高亮再到透明 */ /* 渐变方向是从左到右,颜色从透明到高亮再到透明 */
background: linear-gradient( background: var(--min-primary);
90deg,
transparent,
v-bind('props.flowLineColor'),
transparent
);
border-radius: 0px; /* **与按钮一致的平直四角** */ border-radius: 0px; /* **与按钮一致的平直四角** */
opacity: 0.8; /* 初始可见度 */ opacity: 0.8; /* 初始可见度 */
filter: blur(5px); /* 轻微模糊,模拟光晕 */ filter: blur(5px); /* 轻微模糊,模拟光晕 */

View File

@ -659,19 +659,20 @@ onMounted(() => {
display: flex; display: flex;
overflow: hidden; overflow: hidden;
padding: 0; padding: 0;
background-color: #fff; background-color: var(--min-bg-white);
border-radius: 10px; border: 1px solid var(--min-border-light);
border-radius: var(--min-radius-base);
height: calc(100vh - 120px); /* 示例高度,请根据实际布局调整 */ height: calc(100vh - 120px); /* 示例高度,请根据实际布局调整 */
box-sizing: border-box; box-sizing: border-box;
} }
.tree-panel { .tree-panel {
flex-shrink: 0; flex-shrink: 0;
border-right: 1px solid #ddd; border-right: 1px solid var(--min-border-light);
overflow-y: auto; overflow-y: auto;
height: 100%; /* 填充父容器高度 */ height: 100%; /* 填充父容器高度 */
transition: width 0.3s ease; transition: width 0.3s ease;
background-color: #f8f8f8; /* 示例背景色 */ background-color: var(--min-bg-subtle);
} }
.tree-panel:deep(.el-tree) { .tree-panel:deep(.el-tree) {
@ -713,14 +714,17 @@ onMounted(() => {
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
font-size: 24px; font-size: 24px;
background-color: #e0e0e0; color: var(--min-text-regular);
border-radius: 4px; background-color: var(--min-bg-white);
border: 1px solid var(--min-border-light);
border-radius: var(--min-radius-base);
z-index: 2001; z-index: 2001;
box-shadow: 0 2px 4px rgba(0,0,0,.1); box-shadow: none;
} }
.toggle-button:hover { .toggle-button:hover {
background-color: #d0d0d0; color: var(--min-primary);
background-color: var(--min-primary-soft);
} }
.clickable-area { .clickable-area {
@ -735,11 +739,14 @@ onMounted(() => {
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
color: #fff; padding: 4px 8px;
text-shadow: 0 0 5px #000; color: var(--min-bg-white);
animation: blink 1.5s infinite; background: var(--min-primary);
border-radius: var(--min-radius-sm);
text-shadow: none;
animation: none;
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: 600;
pointer-events: none; /* 防止文本本身捕获点击事件 */ pointer-events: none; /* 防止文本本身捕获点击事件 */
} }
@ -752,12 +759,13 @@ onMounted(() => {
.popup { .popup {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background: white; background: var(--min-bg-white);
user-select: none; user-select: none;
overflow: hidden; /* 整体 overflow hidden,内容由子组件处理 */ overflow: hidden; /* 整体 overflow hidden,内容由子组件处理 */
box-sizing: border-box; box-sizing: border-box;
transition: width 0.2s, height 0.2s, top 0.2s, left 0.2s; transition: width 0.2s, height 0.2s, top 0.2s, left 0.2s;
border: 1px solid #eee; border: 1px solid var(--min-border-light);
border-radius: var(--min-radius-base);
position: absolute; /* 确保 popup 是绝对定位 */ position: absolute; /* 确保 popup 是绝对定位 */
} }
@ -768,20 +776,20 @@ onMounted(() => {
.drag-handle { .drag-handle {
height: 30px; height: 30px;
background: #e0e0e0; background: var(--min-bg-subtle);
cursor: move; cursor: move;
position: relative; position: relative;
z-index: 1; z-index: 1;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-bottom: 1px solid #eee; /* 增加分隔线 */ border-bottom: 1px solid var(--min-border-light);
} }
.popup-title { .popup-title {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: 600;
color: #333; color: var(--min-text-primary);
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -803,7 +811,9 @@ onMounted(() => {
cursor: pointer; cursor: pointer;
font-size: 18px; font-size: 18px;
padding: 4px 8px; padding: 4px 8px;
background: #f0f0f0; color: var(--min-text-secondary);
background: var(--min-bg-white);
border: 1px solid var(--min-border-light);
border-radius: 4px; border-radius: 4px;
display: flex; display: flex;
align-items: center; align-items: center;
@ -812,15 +822,17 @@ onMounted(() => {
} }
.control-icon:hover { .control-icon:hover {
background: #d0d0d0; color: var(--min-primary);
background: var(--min-primary-soft);
} }
.close-icon { .close-icon {
color: #ff4444; color: var(--el-color-danger);
} }
.close-icon:hover { .close-icon:hover {
background: #ff6666; color: var(--min-bg-white);
background: var(--el-color-danger);
} }
.resize-edge, .resize-corner { .resize-edge, .resize-corner {
@ -856,7 +868,7 @@ onMounted(() => {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
padding-top: 10px; padding-top: 10px;
border-top: 1px solid #eee; border-top: 1px solid var(--min-border-light);
} }
.el-dialog { .el-dialog {
display: flex; display: flex;

View File

@ -96,13 +96,13 @@ const handleDragCustom = (event, node) => {
height: 100%; height: 100%;
min-height: 0; min-height: 0;
flex-direction: column; flex-direction: column;
color: #1f2937; color: var(--min-text-primary);
} }
.library-header { .library-header {
padding: 16px 14px 14px; padding: 16px 14px 14px;
border-bottom: 1px solid #e4e8ef; border-bottom: 1px solid var(--min-border-light);
background: #fff; background: var(--min-bg-white);
} }
.library-heading { .library-heading {
@ -114,12 +114,12 @@ const handleDragCustom = (event, node) => {
.library-title { .library-title {
font-size: 15px; font-size: 15px;
font-weight: 650; font-weight: 600;
} }
.library-subtitle { .library-subtitle {
margin-top: 2px; margin-top: 2px;
color: #8a94a6; color: var(--min-text-secondary);
font-size: 12px; font-size: 12px;
} }
@ -138,16 +138,16 @@ const handleDragCustom = (event, node) => {
.el-collapse-item__header { .el-collapse-item__header {
height: 46px; height: 46px;
padding: 0 14px; padding: 0 14px;
border-bottom-color: #e8ebf0; border-bottom-color: var(--min-border-light);
background: #f8fafc; background: var(--min-bg-page);
color: #344054; color: var(--min-text-regular);
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;
} }
.el-collapse-item__wrap { .el-collapse-item__wrap {
border-bottom-color: #e8ebf0; border-bottom-color: var(--min-border-light);
background: #f8fafc; background: var(--min-bg-page);
} }
.el-collapse-item__content { .el-collapse-item__content {
@ -170,10 +170,10 @@ const handleDragCustom = (event, node) => {
height: 20px; height: 20px;
padding: 0 6px; padding: 0 6px;
place-items: center; place-items: center;
border: 1px solid #dfe4ec; border: 1px solid var(--min-border-light);
border-radius: 4px; border-radius: var(--min-radius-sm);
background: #fff; background: var(--min-bg-white);
color: #7a8597; color: var(--min-text-secondary);
font-size: 11px; font-size: 11px;
font-weight: 500; font-weight: 500;
line-height: 18px; line-height: 18px;
@ -192,16 +192,15 @@ const handleDragCustom = (event, node) => {
gap: 9px; gap: 9px;
align-items: center; align-items: center;
padding: 9px 8px 9px 10px; padding: 9px 8px 9px 10px;
border: 1px solid #e1e6ed; border: 1px solid var(--min-border-light);
border-radius: 7px; border-radius: var(--min-radius-base);
background: #fff; background: var(--min-bg-white);
cursor: grab; cursor: grab;
transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease; transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease;
&:hover { &:hover {
border-color: #f2a071; border-color: var(--min-primary);
box-shadow: 0 5px 14px rgba(31, 41, 55, 0.08); box-shadow: none;
transform: translateX(2px);
} }
&:active { cursor: grabbing; } &:active { cursor: grabbing; }
@ -212,8 +211,8 @@ const handleDragCustom = (event, node) => {
width: 34px; width: 34px;
height: 34px; height: 34px;
place-items: center; place-items: center;
border-radius: 6px; border-radius: var(--min-radius-sm);
background: #f1f5f9; background: var(--min-bg-page);
img { img {
width: 21px; width: 21px;
@ -226,7 +225,7 @@ const handleDragCustom = (event, node) => {
.node-name { .node-name {
overflow: hidden; overflow: hidden;
color: #202938; color: var(--min-text-primary);
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -237,7 +236,7 @@ const handleDragCustom = (event, node) => {
display: -webkit-box; display: -webkit-box;
overflow: hidden; overflow: hidden;
margin-top: 4px; margin-top: 4px;
color: #7a8597; color: var(--min-text-secondary);
font-size: 11px; font-size: 11px;
line-height: 16px; line-height: 16px;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
@ -245,7 +244,7 @@ const handleDragCustom = (event, node) => {
} }
.drag-handle { .drag-handle {
color: #a8b0bf; color: var(--min-text-placeholder);
font-size: 16px; font-size: 16px;
} }
</style> </style>

View File

@ -39,12 +39,12 @@ onMounted(() => {
.node__box { .node__box {
width: 320px; width: 320px;
height: auto; height: auto;
background: #fff; background: var(--min-bg-white);
padding: 12px; padding: 12px;
cursor: default; cursor: default;
border-radius: 12px; border-radius: var(--min-radius-base);
border: 2px solid white; border: 1px solid var(--min-border-light);
box-shadow: 0 5px 15px 0#00000008; box-shadow: none;
position: relative; position: relative;
} }
</style> </style>

View File

@ -4,9 +4,11 @@
--> -->
<template> <template>
<div class="page"> <div class="page">
<el-container class="page__container"> <el-container class="page__container" :class="{ 'page__container--log': flowInfoData.isLog }">
<el-header v-if="!flowInfoData.isLog"> <el-header v-if="!flowInfoData.isLog">
<div class="flow-heading"> <div class="flow-heading">
<span class="flow-heading__index">FLOW / 01</span>
<div class="flow-heading__identity">
<div class="flow-heading__name">{{ flowInfoData.name || "未命名流程" }}</div> <div class="flow-heading__name">{{ flowInfoData.name || "未命名流程" }}</div>
<div class="flow-status" :class="`flow-status--${flowState}`"> <div class="flow-status" :class="`flow-status--${flowState}`">
<span class="flow-status__dot"></span> <span class="flow-status__dot"></span>
@ -19,6 +21,7 @@
正在查看 V{{ previewingVersionNo }} 正在查看 V{{ previewingVersionNo }}
</el-tag> </el-tag>
</div> </div>
</div>
<div v-if="!versionPreviewMode" class="btn__container"> <div v-if="!versionPreviewMode" class="btn__container">
<div class="btn flow-actions"> <div class="btn flow-actions">
<el-tag v-if="hasRunResult && ['testRunError', 'testRunFinish'].includes(flowState)" :type="flowState === 'testRunFinish' ? 'success' : 'danger'"> <el-tag v-if="hasRunResult && ['testRunError', 'testRunFinish'].includes(flowState)" :type="flowState === 'testRunFinish' ? 'success' : 'danger'">
@ -121,6 +124,7 @@
<Aside /> <Aside />
</el-aside> </el-aside>
<el-main> <el-main>
<div class="canvas-stage">
<div class="canvas-toolbar"> <div class="canvas-toolbar">
<el-tooltip v-if="!flowStore.disableForm" content="撤销" placement="bottom"> <el-tooltip v-if="!flowStore.disableForm" content="撤销" placement="bottom">
<el-button aria-label="撤销" @click="lf.undo()"><el-icon><RefreshLeft /></el-icon></el-button> <el-button aria-label="撤销" @click="lf.undo()"><el-icon><RefreshLeft /></el-icon></el-button>
@ -176,6 +180,27 @@
<Background color="#d9dee8" :gap="24" :size="1" /> <Background color="#d9dee8" :gap="24" :size="1" />
<Controls position="bottom-right" /> <Controls position="bottom-right" />
</VueFlow> </VueFlow>
</div>
<aside class="flow-summary">
<header>
<span>FLOW INDEX</span>
<strong>流程概况</strong>
</header>
<div class="flow-summary__metrics">
<div><strong>{{ flowMetrics.nodeCount }}</strong><span>节点</span></div>
<div><strong>{{ flowMetrics.edgeCount }}</strong><span>连线</span></div>
<div><strong>{{ nodeErrorList.length }}</strong><span>异常</span></div>
</div>
<dl class="flow-summary__list">
<div><dt>当前状态</dt><dd>{{ stateMap[flowState] }}</dd></div>
<div><dt>编辑状态</dt><dd>{{ draftDirty ? '有未保存修改' : '已同步' }}</dd></div>
<div><dt>发布版本</dt><dd>{{ publishedVersionNo ? `V${publishedVersionNo}` : '未发布' }}</dd></div>
<div><dt>画布模式</dt><dd>{{ flowStore.disableForm ? '只读' : '编辑' }}</dd></div>
</dl>
<button class="flow-summary__fit" type="button" @click="fitView">
<el-icon><FullScreen /></el-icon><span>适应全部节点</span>
</button>
</aside>
</el-main> </el-main>
</el-container> </el-container>
</el-container> </el-container>
@ -849,6 +874,10 @@ const flowInfoData = ref({
isLog: false, isLog: false,
name: "流程", name: "流程",
}); });
const flowMetrics = computed(() => ({
nodeCount: (vueFlow.getNodes.value || []).length,
edgeCount: (vueFlow.getEdges.value || []).length,
}));
/** /**
* 试运行 * 试运行
@ -2723,10 +2752,10 @@ onUnmounted(() => {
gap: 12px; gap: 12px;
margin-bottom: 16px; margin-bottom: 16px;
padding: 10px 12px; padding: 10px 12px;
border: 1px solid #dfe6ef; border: 1px solid var(--min-border-light);
border-radius: 6px; border-radius: var(--min-radius-base);
background: #f7f9fc; background: var(--min-bg-page);
color: #65758b; color: var(--min-text-secondary);
font-size: 13px; font-size: 13px;
} }
@ -2744,32 +2773,52 @@ onUnmounted(() => {
overflow: hidden; overflow: hidden;
.page__container { .page__container {
display: flex;
flex-direction: column;
width: 100%; width: 100%;
height: 100%; height: 100%;
position: relative; position: relative;
.el-header { .el-header {
display: flex; display: flex;
height: auto;
min-height: 92px;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 0 16px; padding: 14px 20px;
border-bottom: 1px solid #e3e7ee; border-bottom: 1px solid var(--min-border-light);
background: #fff; background: var(--min-bg-white);
gap: 18px; gap: 24px;
.flow-heading { .flow-heading {
display: flex;
flex: 0 0 230px;
min-width: 0;
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.flow-heading__index {
color: var(--min-primary);
font-size: 10px;
font-weight: 600;
}
.flow-heading__identity {
display: flex; display: flex;
min-width: 0; min-width: 0;
align-items: center; align-items: center;
gap: 10px; flex-wrap: wrap;
gap: 8px;
} }
.flow-heading__name { .flow-heading__name {
overflow: hidden; overflow: hidden;
max-width: 320px; max-width: 320px;
color: #1f2937; color: var(--min-text-primary);
font-size: 16px; font-size: 16px;
font-weight: 650; font-weight: 600;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
@ -2780,10 +2829,10 @@ onUnmounted(() => {
align-items: center; align-items: center;
gap: 6px; gap: 6px;
padding: 0 8px; padding: 0 8px;
border: 1px solid #dfe4ec; border: 1px solid var(--min-border-light);
border-radius: 5px; border-radius: var(--min-radius-sm);
background: #f8fafc; background: var(--min-bg-page);
color: #687386; color: var(--min-text-secondary);
font-size: 11px; font-size: 11px;
white-space: nowrap; white-space: nowrap;
} }
@ -2796,37 +2845,26 @@ onUnmounted(() => {
} }
.flow-status--release, .flow-status--release,
.flow-status--testRunFinish {
border-color: #c8ead5;
background: #effaf3;
color: #1e7d49;
}
.flow-status--testRunning, .flow-status--testRunning,
.flow-status--updateData { .flow-status--testRunFinish,
border-color: #cbdcf8; .flow-status--updateData,
background: #f0f6ff;
color: #2f6fca;
}
.flow-status--testRunError, .flow-status--testRunError,
.flow-status--stop { .flow-status--stop,
border-color: #f1cecb;
background: #fff3f2;
color: #c33b32;
}
.flow-status--pause { .flow-status--pause {
border-color: #ead9b8; border-color: var(--min-primary-border);
background: #fff9eb; background: var(--min-primary-soft);
color: #9a6700; color: var(--min-primary);
} }
.btn__container { .btn__container {
display: flex; display: flex;
flex: 1;
min-width: 0; min-width: 0;
align-items: center; align-items: center;
justify-content: flex-end;
gap: 10px; gap: 10px;
overflow-x: auto;
scrollbar-width: thin;
.btn { .btn {
display: flex; display: flex;
@ -2838,27 +2876,38 @@ onUnmounted(() => {
.publish-actions { .publish-actions {
padding-left: 10px; padding-left: 10px;
border-left: 1px solid #e4e8ef; border-left: 1px solid var(--min-border-light);
} }
} }
} }
.container { .container {
height: calc(100% - 60px); flex: 1;
min-height: 0;
} }
.el-main { .el-main {
display: grid;
grid-template-columns: minmax(0, 1fr) 216px;
padding: 0; padding: 0;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
background: var(--min-bg-white);
} }
.el-aside { .el-aside {
margin: 0; margin: 0;
width: 272px; width: 272px;
padding: 0; padding: 0;
border-right: 1px solid #e3e7ee; border-right: 1px solid var(--min-border-light);
background: #f8fafc; background: var(--min-bg-page);
}
.canvas-stage {
position: relative;
min-width: 0;
overflow: hidden;
border-right: 1px solid var(--min-border-light);
} }
.canvas-toolbar { .canvas-toolbar {
@ -2869,26 +2918,26 @@ onUnmounted(() => {
z-index: 20; z-index: 20;
align-items: center; align-items: center;
padding: 4px; padding: 4px;
border: 1px solid #dfe4ec; border: 1px solid var(--min-border-light);
border-radius: 7px; border-radius: var(--min-radius-base);
background: rgba(255, 255, 255, 0.96); background: var(--min-bg-white);
box-shadow: 0 6px 18px rgba(31, 41, 55, 0.1); box-shadow: none;
.el-button { .el-button {
width: 30px; width: 30px;
height: 30px; height: 30px;
padding: 0; padding: 0;
border: 0; border: 0;
border-radius: 5px; border-radius: var(--min-radius-sm);
background: transparent; background: transparent;
color: #566174; color: var(--min-text-secondary);
} }
.el-button + .el-button { margin-left: 2px; } .el-button + .el-button { margin-left: 2px; }
.el-button:hover { .el-button:hover {
background: #f1f5f9; background: var(--min-bg-page);
color: #1f2937; color: var(--min-text-primary);
} }
} }
@ -2896,7 +2945,7 @@ onUnmounted(() => {
width: 1px; width: 1px;
height: 18px; height: 18px;
margin: 0 4px; margin: 0 4px;
background: #e1e6ed; background: var(--min-border-light);
} }
.flow__container { .flow__container {
@ -2923,7 +2972,7 @@ onUnmounted(() => {
:deep(.vue-flow__edge-path), :deep(.vue-flow__edge-path),
:deep(.vue-flow__connection-path) { :deep(.vue-flow__connection-path) {
stroke: #ff6b35; stroke: var(--min-primary);
stroke-width: 2; stroke-width: 2;
} }
@ -2936,14 +2985,14 @@ onUnmounted(() => {
} }
:deep(.vue-flow__selection) { :deep(.vue-flow__selection) {
border: 1px solid #2f80ed; border: 1px solid var(--min-primary);
background: rgba(47, 128, 237, 0.1); background: var(--min-primary-soft);
box-shadow: 0 0 0 1px rgba(47, 128, 237, 0.08); box-shadow: none;
} }
:deep(.vue-flow__nodesselection-rect) { :deep(.vue-flow__nodesselection-rect) {
border: 1px dashed rgba(47, 128, 237, 0.72); border: 1px dashed var(--min-primary);
background: rgba(47, 128, 237, 0.04); background: var(--min-primary-soft);
} }
:deep(.vue-flow__handle) { :deep(.vue-flow__handle) {
@ -2960,18 +3009,141 @@ onUnmounted(() => {
} }
:deep(.vue-flow__edge.selected .vue-flow__edge-path) { :deep(.vue-flow__edge.selected .vue-flow__edge-path) {
stroke: #1677ff; stroke: var(--min-primary);
stroke-width: 4; stroke-width: 3;
filter: drop-shadow(0 0 3px rgba(22, 119, 255, 0.45)); filter: none;
} }
:deep(.vue-flow__controls) { :deep(.vue-flow__controls) {
overflow: hidden; overflow: hidden;
border: 1px solid #dfe4ec; border: 1px solid var(--min-border-light);
border-radius: 6px; border-radius: var(--min-radius-base);
box-shadow: 0 6px 18px rgba(20, 33, 61, 0.12); box-shadow: none;
} }
} }
.flow-summary {
display: flex;
min-width: 0;
flex-direction: column;
background: var(--min-bg-white);
> header {
display: flex;
min-height: 74px;
flex-direction: column;
justify-content: center;
gap: 4px;
padding: 14px 18px;
border-bottom: 1px solid var(--min-border-light);
span {
color: var(--min-primary);
font-size: 10px;
font-weight: 600;
}
strong {
color: var(--min-text-primary);
font-size: 15px;
font-weight: 600;
}
}
}
.flow-summary__metrics {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
border-bottom: 1px solid var(--min-border-light);
> div {
display: flex;
min-width: 0;
height: 86px;
align-items: center;
justify-content: center;
flex-direction: column;
border-right: 1px solid var(--min-border-light);
&:last-child { border-right: 0; }
}
strong {
color: var(--min-text-primary);
font-size: 20px;
font-weight: 600;
}
span {
margin-top: 4px;
color: var(--min-text-secondary);
font-size: 10px;
}
}
.flow-summary__list {
margin: 0;
padding: 8px 18px;
> div {
display: flex;
min-height: 48px;
align-items: center;
justify-content: space-between;
gap: 12px;
border-bottom: 1px solid var(--min-border-light);
}
dt,
dd { margin: 0; }
dt {
color: var(--min-text-secondary);
font-size: 11px;
}
dd {
overflow: hidden;
color: var(--min-text-primary);
font-size: 11px;
font-weight: 600;
text-align: right;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.flow-summary__fit {
display: flex;
height: 44px;
align-items: center;
justify-content: center;
gap: 8px;
margin: auto 18px 18px;
border: 1px solid var(--min-border-light);
border-radius: var(--min-radius-sm);
background: var(--min-bg-white);
color: var(--min-text-regular);
cursor: pointer;
&:hover {
border-color: var(--min-primary);
color: var(--min-primary);
}
}
}
@media (max-width: 1280px) {
.page__container .el-main { grid-template-columns: minmax(0, 1fr) 192px; }
.page__container .el-aside { width: 236px; }
}
@media (max-width: 1080px) {
.page__container .el-main { grid-template-columns: minmax(0, 1fr); }
.page__container .flow-summary { display: none; }
.page__container .el-header { align-items: flex-start; flex-direction: column; }
.page__container .el-header .flow-heading { flex-basis: auto; }
.page__container .el-header .btn__container { width: 100%; justify-content: flex-start; }
} }
} }
</style> </style>
@ -3011,7 +3183,7 @@ onUnmounted(() => {
gap: 16px; gap: 16px;
min-height: 82px; min-height: 82px;
padding: 14px 0; padding: 14px 0;
border-bottom: 1px solid #ebeef5; border-bottom: 1px solid var(--min-border-light);
} }
.version-history__main { .version-history__main {
@ -3029,7 +3201,7 @@ onUnmounted(() => {
.version-history__meta, .version-history__meta,
.version-history__note { .version-history__note {
margin-top: 6px; margin-top: 6px;
color: #606266; color: var(--min-text-secondary);
font-size: 13px; font-size: 13px;
line-height: 1.4; line-height: 1.4;
} }

View File

@ -47,12 +47,12 @@ const setNodeName = (name) => {
.node__container { .node__container {
width: 300px; width: 300px;
height: auto; height: auto;
background: #fff; background: var(--min-bg-white);
padding: 12px; padding: 12px;
cursor: default; cursor: default;
border-radius: 12px; border-radius: var(--min-radius-base);
border: 2px solid white; border: 1px solid var(--min-border-light);
box-shadow: 0 5px 15px 0#00000008; box-shadow: none;
position: relative; position: relative;
.title__container { .title__container {
@ -67,13 +67,13 @@ const setNodeName = (name) => {
.text { .text {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: 600;
margin-left: 12px; margin-left: 12px;
} }
} }
.subTitle { .subTitle {
color: #737a87; color: var(--min-text-secondary);
font-size: 12px; font-size: 12px;
margin: 8px 0; margin: 8px 0;
} }
@ -82,9 +82,9 @@ const setNodeName = (name) => {
.input__container { .input__container {
width: 100%; width: 100%;
background-color: #fafbfc; background-color: var(--min-bg-page);
padding: 0 16px; padding: 0 16px;
border-radius: 8px; border-radius: var(--min-radius-base);
box-sizing: border-box; box-sizing: border-box;
.title { .title {
@ -100,15 +100,15 @@ const setNodeName = (name) => {
.tag { .tag {
width: 3px; width: 3px;
height: 16px; height: 16px;
background: #1664ff; background: var(--min-primary);
border-radius: 0 4px 4px 0; border-radius: 0 4px 4px 0;
margin-right: 12px; margin-right: 12px;
} }
.text { .text {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 600;
color: #0c0d0e; color: var(--min-text-primary);
} }
} }
@ -196,7 +196,7 @@ const setNodeName = (name) => {
margin-bottom: 12px; margin-bottom: 12px;
.paramsTitle { .paramsTitle {
color: #0c0d0e; color: var(--min-text-primary);
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
} }

View File

@ -260,56 +260,41 @@ onMounted(() => bindComponent(componentRef.value));
<style scoped lang="scss"> <style scoped lang="scss">
.flow-node-shell { .flow-node-shell {
--node-accent: #4f6b8a; --node-accent: var(--min-primary);
width: 100%; width: 100%;
min-height: 100%; min-height: 100%;
position: relative; position: relative;
box-sizing: border-box; box-sizing: border-box;
&--queue-drop-active .flow-node-content { &--queue-drop-active .flow-node-content {
border-radius: 8px; border-radius: var(--min-radius-base);
box-shadow: 0 0 0 3px rgba(47, 128, 237, 0.35), 0 10px 24px rgba(47, 128, 237, 0.18); box-shadow: 0 0 0 2px var(--min-primary-soft);
} }
&--start,
&--subStart { --node-accent: #239565; }
&--end,
&--subEnd,
&--stopLoop { --node-accent: #d45252; }
&--branch { --node-accent: #b7791f; }
&--loop,
&--currentLoop { --node-accent: #df6b35; }
&--http,
&--code { --node-accent: #3978c5; }
&--sdAgent,
&--recognize { --node-accent: #7a5ab8; }
&--loop, &--loop,
&--selectArea { &--selectArea {
height: 100%; height: 100%;
} }
&:hover .flow-node-card { &:hover .flow-node-card {
border-color: #cbd3df; border-color: var(--min-border-strong);
box-shadow: 0 10px 26px rgba(31, 41, 55, 0.11); box-shadow: none;
} }
&--selected .flow-node-card { &--selected .flow-node-card {
border-color: #2f80ed; border-color: var(--min-primary);
box-shadow: 0 0 0 2px rgba(47, 128, 237, 0.2), 0 10px 26px rgba(31, 41, 55, 0.12); box-shadow: 0 0 0 1px var(--min-primary);
} }
&--selected.flow-node-shell--branch :deep(.node__container) { &--selected.flow-node-shell--branch :deep(.node__container) {
border-color: #2f80ed; border-color: var(--min-primary);
box-shadow: none; box-shadow: none;
} }
&--selected.flow-node-shell--loop :deep(.group__container), &--selected.flow-node-shell--loop :deep(.group__container),
&--selected.flow-node-shell--selectArea :deep(.group__container) { &--selected.flow-node-shell--selectArea :deep(.group__container) {
border-color: #2f80ed; border-color: var(--min-primary);
box-shadow: 0 0 0 2px rgba(47, 128, 237, 0.18); box-shadow: 0 0 0 1px var(--min-primary);
} }
} }
@ -322,7 +307,7 @@ onMounted(() => bindComponent(componentRef.value));
.flow-node-executor { .flow-node-executor {
overflow: hidden; overflow: hidden;
margin-top: 8px; margin-top: 8px;
color: #667085; color: var(--min-text-secondary);
font-size: 11px; font-size: 11px;
line-height: 16px; line-height: 16px;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -334,11 +319,11 @@ onMounted(() => bindComponent(componentRef.value));
height: auto; height: auto;
padding: 13px 14px 13px 16px; padding: 13px 14px 13px 16px;
position: relative; position: relative;
background: #fff; background: var(--min-bg-white);
border: 1px solid #dfe4ec; border: 1px solid var(--min-border-light);
border-left: 4px solid var(--node-accent); border-left: 2px solid var(--node-accent);
border-radius: 8px; border-radius: var(--min-radius-base);
box-shadow: 0 7px 20px rgba(31, 41, 55, 0.08); box-shadow: none;
box-sizing: border-box; box-sizing: border-box;
transition: border-color 0.16s ease, box-shadow 0.16s ease; transition: border-color 0.16s ease, box-shadow 0.16s ease;
@ -366,8 +351,8 @@ onMounted(() => bindComponent(componentRef.value));
height: 38px; height: 38px;
flex: 0 0 38px; flex: 0 0 38px;
place-items: center; place-items: center;
border-radius: 50%; border-radius: var(--min-radius-sm);
background: #f1f5f9; background: var(--min-bg-page);
} }
:deep(.terminal-node__icon img) { :deep(.terminal-node__icon img) {
@ -378,16 +363,16 @@ onMounted(() => bindComponent(componentRef.value));
:deep(.terminal-node__content) { min-width: 0; } :deep(.terminal-node__content) { min-width: 0; }
:deep(.terminal-node__title) { :deep(.terminal-node__title) {
color: #1f2937; color: var(--min-text-primary);
font-size: 15px; font-size: 15px;
font-weight: 650; font-weight: 600;
line-height: 21px; line-height: 21px;
} }
:deep(.terminal-node__description) { :deep(.terminal-node__description) {
overflow: hidden; overflow: hidden;
margin-top: 3px; margin-top: 3px;
color: #7a8494; color: var(--min-text-secondary);
font-size: 11px; font-size: 11px;
line-height: 16px; line-height: 16px;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -398,19 +383,19 @@ onMounted(() => bindComponent(componentRef.value));
.flow-handle { .flow-handle {
width: 12px; width: 12px;
height: 12px; height: 12px;
border: 2px solid #fff; border: 2px solid var(--min-bg-white);
background: var(--node-accent); background: var(--node-accent);
box-shadow: 0 0 0 1px var(--node-accent), 0 2px 5px rgba(31, 41, 55, 0.18); box-shadow: 0 0 0 1px var(--node-accent);
z-index: 10; z-index: 10;
transition: transform 0.14s ease, box-shadow 0.14s ease; transition: transform 0.14s ease, box-shadow 0.14s ease;
&:hover { &:hover {
box-shadow: 0 0 0 3px rgba(47, 128, 237, 0.18), 0 2px 6px rgba(31, 41, 55, 0.2); box-shadow: 0 0 0 2px var(--min-primary-soft);
} }
&--target { &--target {
left: -6px; left: -6px;
background: #fff; background: var(--min-bg-white);
border-color: var(--node-accent); border-color: var(--node-accent);
} }

View File

@ -8,7 +8,7 @@
</div> </div>
<h1>招商车研机器人智能测试管理系统</h1> <h1>招商车研机器人智能测试管理系统</h1>
<p class="hero-description"> <p class="hero-description">
连接设备、编排流程、执行测试与巡检任务,集中管理机器人全生命周期测试业务。 DEVICE {{ stats.device.value }} / FLOW {{ stats.flow.value }} / INSPECTION {{ stats.inspection.value }} / INSTANCE {{ stats.instance.value }}
</p> </p>
<div class="hero-actions"> <div class="hero-actions">
<el-button type="primary" size="large" @click="router.push('/flow')"> <el-button type="primary" size="large" @click="router.push('/flow')">
@ -29,6 +29,7 @@
</div> </div>
</section> </section>
<div class="operations-layout">
<section class="overview-section" aria-labelledby="overview-title"> <section class="overview-section" aria-labelledby="overview-title">
<div class="section-heading"> <div class="section-heading">
<div> <div>
@ -57,6 +58,7 @@
</div> </div>
</section> </section>
<main class="operations-main">
<section class="workspace-grid"> <section class="workspace-grid">
<div class="dashboard-panel task-panel"> <div class="dashboard-panel task-panel">
<div class="panel-heading"> <div class="panel-heading">
@ -121,7 +123,6 @@
</span> </span>
<span class="feature-copy"> <span class="feature-copy">
<strong>{{ feature.title }}</strong> <strong>{{ feature.title }}</strong>
<small>{{ feature.description }}</small>
</span> </span>
<el-icon class="feature-arrow"><ArrowRight /></el-icon> <el-icon class="feature-arrow"><ArrowRight /></el-icon>
</button> </button>
@ -160,6 +161,8 @@
<span>{{ recentInstancesRestricted ? '暂无测试实例查看权限' : '暂无测试执行记录' }}</span> <span>{{ recentInstancesRestricted ? '暂无测试实例查看权限' : '暂无测试执行记录' }}</span>
</div> </div>
</section> </section>
</main>
</div>
</div> </div>
</template> </template>
@ -219,8 +222,8 @@ const features = [
title: '设备接入', title: '设备接入',
description: '配置机器人与终端设备', description: '配置机器人与终端设备',
icon: SetUp, icon: SetUp,
color: '#2563eb', color: 'var(--min-primary)',
background: '#eaf2ff', background: 'var(--min-primary-soft)',
menuTitles: ['设备注册', '设备配置管理', '设备管理'], menuTitles: ['设备注册', '设备配置管理', '设备管理'],
fallback: '/device/register', fallback: '/device/register',
}, },
@ -228,8 +231,8 @@ const features = [
title: '流程编排', title: '流程编排',
description: '构建可视化测试工作流', description: '构建可视化测试工作流',
icon: Operation, icon: Operation,
color: '#0f8a73', color: 'var(--min-primary)',
background: '#e7f6f2', background: 'var(--min-primary-soft)',
menuTitles: ['任务流程编排', '流程编排'], menuTitles: ['任务流程编排', '流程编排'],
fallback: '/flow', fallback: '/flow',
}, },
@ -237,8 +240,8 @@ const features = [
title: '巡检执行', title: '巡检执行',
description: '调度机器人巡检任务', description: '调度机器人巡检任务',
icon: Monitor, icon: Monitor,
color: '#c56a16', color: 'var(--min-primary)',
background: '#fff3e5', background: 'var(--min-primary-soft)',
menuTitles: ['执行任务', '运行任务', '巡检任务执行'], menuTitles: ['执行任务', '运行任务', '巡检任务执行'],
fallback: '/inspection/runTask', fallback: '/inspection/runTask',
}, },
@ -246,8 +249,8 @@ const features = [
title: '巡检配置', title: '巡检配置',
description: '管理地图、点位与任务', description: '管理地图、点位与任务',
icon: Grid, icon: Grid,
color: '#6b57c7', color: 'var(--min-primary)',
background: '#f0edff', background: 'var(--min-primary-soft)',
menuTitles: ['巡检任务', '巡检地图', '地图管理'], menuTitles: ['巡检任务', '巡检地图', '地图管理'],
fallback: '/inspection/task', fallback: '/inspection/task',
}, },
@ -255,8 +258,8 @@ const features = [
title: '测试记录', title: '测试记录',
description: '追踪执行结果与日志', description: '追踪执行结果与日志',
icon: DocumentChecked, icon: DocumentChecked,
color: '#d14343', color: 'var(--min-primary)',
background: '#ffeded', background: 'var(--min-primary-soft)',
menuTitles: ['任务历史', '测试记录', '任务实例', '执行记录'], menuTitles: ['任务历史', '测试记录', '任务实例', '执行记录'],
fallback: '/test/history', fallback: '/test/history',
}, },
@ -264,8 +267,8 @@ const features = [
title: '语料中心', title: '语料中心',
description: '管理语音与合成资源', description: '管理语音与合成资源',
icon: Headset, icon: Headset,
color: '#087f9c', color: 'var(--min-primary)',
background: '#e8f7fa', background: 'var(--min-primary-soft)',
menuTitles: ['语料库', '语料管理', '车载语料'], menuTitles: ['语料库', '语料管理', '车载语料'],
fallback: '/corpusManger/corpus', fallback: '/corpusManger/corpus',
}, },
@ -445,31 +448,43 @@ onBeforeUnmount(() => {
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow-y: auto; overflow-y: auto;
padding: 0 2px 24px; padding: 0 32px 32px;
color: var(--el-text-color-primary); color: var(--el-text-color-primary);
} }
.hero-panel { .hero-panel {
position: relative; position: relative;
min-height: 236px; min-height: 320px;
padding: 36px 42px; padding: 48px;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 32px; gap: 32px;
border-radius: 6px; border: 0;
background-image: url('../assets/images/index-background.jpg'); border-radius: 0;
background-position: center 48%; background: #18202a;
background-size: cover; color: #ffffff;
color: #fff;
} }
.hero-panel::before { .hero-panel::before {
content: ''; content: '';
position: absolute; position: absolute;
inset: 0; inset: 0;
background: rgba(8, 24, 37, 0.72); z-index: 0;
background-image: url('../assets/images/index-background.jpg');
background-position: center 48%;
background-size: cover;
filter: grayscale(1) contrast(1.08);
opacity: 0.78;
}
.hero-panel::after {
content: '';
position: absolute;
inset: 0;
z-index: 0;
background: rgba(17, 24, 39, 0.62);
} }
.hero-content, .hero-content,
@ -479,7 +494,7 @@ onBeforeUnmount(() => {
} }
.hero-content { .hero-content {
max-width: 760px; max-width: 720px;
} }
.platform-state { .platform-state {
@ -487,7 +502,7 @@ onBeforeUnmount(() => {
align-items: center; align-items: center;
gap: 9px; gap: 9px;
margin-bottom: 13px; margin-bottom: 13px;
color: rgba(255, 255, 255, 0.82); color: rgba(255, 255, 255, 0.74);
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;
} }
@ -495,17 +510,16 @@ onBeforeUnmount(() => {
.state-dot { .state-dot {
width: 8px; width: 8px;
height: 8px; height: 8px;
border-radius: 50%; border-radius: var(--min-radius-sm);
background: #35d2a0; background: var(--min-primary);
box-shadow: 0 0 0 5px rgba(53, 210, 160, 0.16);
animation: status-pulse 2.2s ease-in-out infinite;
} }
.hero-content h1 { .hero-content h1 {
margin: 0; margin: 0;
font-size: 32px; max-width: 680px;
font-size: 36px;
line-height: 1.28; line-height: 1.28;
font-weight: 700; font-weight: 600;
letter-spacing: 0; letter-spacing: 0;
} }
@ -514,7 +528,7 @@ onBeforeUnmount(() => {
margin: 12px 0 22px; margin: 12px 0 22px;
color: rgba(255, 255, 255, 0.76); color: rgba(255, 255, 255, 0.76);
font-size: 15px; font-size: 15px;
line-height: 1.7; line-height: 1.65;
} }
.hero-actions { .hero-actions {
@ -531,25 +545,26 @@ onBeforeUnmount(() => {
} }
.hero-actions :deep(.el-button--primary) { .hero-actions :deep(.el-button--primary) {
border-color: #1688e7; border-color: var(--min-primary);
background: #1688e7; background: var(--min-primary);
} }
.cockpit-button { .cockpit-button {
border-color: rgba(255, 255, 255, 0.42) !important; border-color: rgba(255, 255, 255, 0.62) !important;
background: rgba(255, 255, 255, 0.1) !important; background: transparent !important;
color: #fff !important; color: #ffffff !important;
} }
.cockpit-button:hover { .cockpit-button:hover {
border-color: #fff !important; border-color: #ffffff !important;
background: rgba(255, 255, 255, 0.18) !important; background: rgba(255, 255, 255, 0.12) !important;
color: #ffffff !important;
} }
.hero-time { .hero-time {
min-width: 190px; min-width: 190px;
padding-left: 26px; padding-left: 26px;
border-left: 1px solid rgba(255, 255, 255, 0.24); border-left: 1px solid rgba(255, 255, 255, 0.36);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-end; align-items: flex-end;
@ -569,8 +584,21 @@ onBeforeUnmount(() => {
font-size: 13px; font-size: 13px;
} }
.operations-layout {
display: grid;
grid-template-columns: 292px minmax(0, 1fr);
gap: 24px;
padding-top: 24px;
}
.operations-main {
min-width: 0;
}
.overview-section { .overview-section {
padding: 24px 0 20px; padding: 0;
border-top: 2px solid var(--min-primary);
background: var(--min-bg-white);
} }
.section-heading, .section-heading,
@ -582,7 +610,12 @@ onBeforeUnmount(() => {
} }
.section-heading { .section-heading {
margin-bottom: 14px; min-height: 72px;
margin: 0;
padding: 14px 16px;
border-right: 1px solid var(--min-border-light);
border-bottom: 1px solid var(--min-border-light);
border-left: 1px solid var(--min-border-light);
} }
.section-kicker { .section-kicker {
@ -591,7 +624,7 @@ onBeforeUnmount(() => {
color: var(--el-color-primary); color: var(--el-color-primary);
font-size: 10px; font-size: 10px;
line-height: 1.2; line-height: 1.2;
font-weight: 700; font-weight: 600;
} }
.section-heading h2, .section-heading h2,
@ -609,27 +642,29 @@ onBeforeUnmount(() => {
.metric-grid { .metric-grid {
display: grid; display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-columns: minmax(0, 1fr);
gap: 14px; gap: 0;
} }
.metric-card { .metric-card {
position: relative; position: relative;
min-height: 116px; min-height: 104px;
padding: 20px; padding: 16px;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 16px; gap: 16px;
border: 1px solid var(--el-border-color-lighter); border: 0;
border-radius: 6px; border-right: 1px solid var(--min-border-light);
border-bottom: 1px solid var(--min-border-light);
border-left: 1px solid var(--min-border-light);
border-radius: 0;
background: var(--el-bg-color); background: var(--el-bg-color);
transition: border-color 180ms ease, box-shadow 180ms ease, transform 180ms ease; transition: border-color 160ms ease, background-color 160ms ease;
} }
.metric-card:hover { .metric-card:hover {
transform: translateY(-2px); border-color: var(--min-border-strong);
border-color: var(--el-border-color); background: var(--min-bg-page);
box-shadow: 0 10px 24px rgba(28, 44, 64, 0.08);
} }
.metric-icon { .metric-icon {
@ -638,19 +673,22 @@ onBeforeUnmount(() => {
flex: 0 0 48px; flex: 0 0 48px;
display: grid; display: grid;
place-items: center; place-items: center;
border-radius: 6px; border-radius: var(--min-radius-base);
font-size: 24px; font-size: 24px;
} }
.metric-icon--blue { color: #2563eb; background: #eaf2ff; } .metric-icon--blue,
.metric-icon--green { color: #0f8a73; background: #e7f6f2; } .metric-icon--green,
.metric-icon--orange { color: #c56a16; background: #fff3e5; } .metric-icon--orange,
.metric-icon--red { color: #d14343; background: #ffeded; } .metric-icon--red {
color: var(--min-primary);
background: var(--min-primary-soft);
}
.metric-content { .metric-content {
min-width: 0; min-width: 0;
display: grid; display: grid;
grid-template-columns: 1fr auto; grid-template-columns: minmax(0, 1fr) auto;
align-items: baseline; align-items: baseline;
column-gap: 10px; column-gap: 10px;
} }
@ -666,7 +704,7 @@ onBeforeUnmount(() => {
color: var(--el-text-color-primary); color: var(--el-text-color-primary);
font-size: 28px; font-size: 28px;
line-height: 1; line-height: 1;
font-weight: 650; font-weight: 600;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
} }
@ -686,16 +724,17 @@ onBeforeUnmount(() => {
.workspace-grid { .workspace-grid {
display: grid; display: grid;
grid-template-columns: minmax(0, 1.25fr) minmax(360px, 0.75fr); grid-template-columns: minmax(0, 1.35fr) minmax(320px, 0.65fr);
gap: 16px; gap: 24px;
margin-bottom: 16px; margin-bottom: 24px;
} }
.dashboard-panel { .dashboard-panel {
min-width: 0; min-width: 0;
padding: 20px; padding: 20px;
border: 1px solid var(--el-border-color-lighter); border: 1px solid var(--min-border-light);
border-radius: 6px; border-top: 2px solid var(--min-primary);
border-radius: 0;
background: var(--el-bg-color); background: var(--el-bg-color);
} }
@ -836,7 +875,7 @@ onBeforeUnmount(() => {
min-height: 76px; min-height: 76px;
padding: 10px; padding: 10px;
border: 1px solid transparent; border: 1px solid transparent;
border-radius: 5px; border-radius: 0;
display: grid; display: grid;
grid-template-columns: 38px minmax(0, 1fr) 14px; grid-template-columns: 38px minmax(0, 1fr) 14px;
align-items: center; align-items: center;
@ -858,7 +897,7 @@ onBeforeUnmount(() => {
height: 38px; height: 38px;
display: grid; display: grid;
place-items: center; place-items: center;
border-radius: 5px; border-radius: var(--min-radius-sm);
font-size: 20px; font-size: 20px;
} }
@ -884,7 +923,7 @@ onBeforeUnmount(() => {
} }
.recent-panel { .recent-panel {
margin-bottom: 2px; margin-bottom: 0;
} }
.recent-list { .recent-list {
@ -955,11 +994,7 @@ onBeforeUnmount(() => {
} }
:global(html.dark) .hero-panel::before { :global(html.dark) .hero-panel::before {
background: rgba(4, 13, 22, 0.82); opacity: 0.04;
}
:global(html.dark) .metric-card:hover {
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.22);
} }
:global(html.dark) .metric-icon, :global(html.dark) .metric-icon,
@ -967,14 +1002,18 @@ onBeforeUnmount(() => {
background: var(--el-fill-color) !important; background: var(--el-fill-color) !important;
} }
@keyframes status-pulse { @media (max-width: 1200px) {
0%, 100% { box-shadow: 0 0 0 4px rgba(53, 210, 160, 0.12); } .operations-layout {
50% { box-shadow: 0 0 0 7px rgba(53, 210, 160, 0.04); } grid-template-columns: 1fr;
} }
@media (max-width: 1200px) {
.metric-grid { .metric-grid {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(4, minmax(0, 1fr));
}
.metric-card {
border-top: 1px solid var(--min-border-light);
border-left: 0;
} }
.workspace-grid { .workspace-grid {
@ -984,12 +1023,12 @@ onBeforeUnmount(() => {
@media (max-width: 768px) { @media (max-width: 768px) {
.home-dashboard { .home-dashboard {
padding-bottom: 16px; padding: 0 16px 16px;
} }
.hero-panel { .hero-panel {
min-height: 0; min-height: 0;
padding: 28px 22px; padding: 36px 24px;
align-items: flex-start; align-items: flex-start;
flex-direction: column; flex-direction: column;
} }
@ -1005,7 +1044,7 @@ onBeforeUnmount(() => {
.hero-time { .hero-time {
width: 100%; width: 100%;
padding: 18px 0 0; padding: 18px 0 0;
border-top: 1px solid rgba(255, 255, 255, 0.24); border-top: 1px solid var(--min-border-light);
border-left: 0; border-left: 0;
align-items: flex-start; align-items: flex-start;
} }
@ -1020,6 +1059,10 @@ onBeforeUnmount(() => {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.metric-card {
border-left: 1px solid var(--min-border-light);
}
.recent-item:nth-child(2) { .recent-item:nth-child(2) {
border-top: 1px solid var(--el-border-color-extra-light); border-top: 1px solid var(--el-border-color-extra-light);
} }

View File

@ -1,20 +1,25 @@
<template> <template>
<div class="header-container"> <div class="header-container">
<div class="left"> <div class="cockpit-brand">
<div class="date"> <span>CMVR / INS</span>
<div>{{ currentTime }}</div> <strong>两江鱼复智能网联新能源汽车产业基地数字化平台</strong>
<div class="week">{{ weekText }}</div>
</div>
<div class="binary-strip">{{ binaryText }}</div>
</div>
<div class="application-title">两江鱼复智能网联新能源汽车产业基地数字化平台</div>
<div class="right">
<div class="binary-strip">{{ binaryText }}</div>
<div class="btn-container">
<button class="nav-btn" :class="{ active: activePage === 'home' }" @click="handleBtn('home')">地图总览</button>
<button class="nav-btn" :class="{ active: activePage === 'monitoring' }" @click="handleBtn('monitoring')">智能监控</button>
<button class="nav-btn" :class="{ active: activePage === 'manualTakeover' }" @click="handleBtn('manualTakeover')">人工接管</button>
</div> </div>
<div class="cockpit-nav-label"><span>工作视图</span><b>03</b></div>
<nav class="btn-container" aria-label="巡检驾驶舱视图">
<button class="nav-btn" :class="{ active: activePage === 'home' }" @click="handleBtn('home')">
<span>01</span><strong>地图总览</strong><small>MAP OVERVIEW</small>
</button>
<button class="nav-btn" :class="{ active: activePage === 'monitoring' }" @click="handleBtn('monitoring')">
<span>02</span><strong>智能监控</strong><small>LIVE MONITOR</small>
</button>
<button class="nav-btn" :class="{ active: activePage === 'manualTakeover' }" @click="handleBtn('manualTakeover')">
<span>03</span><strong>人工接管</strong><small>MANUAL CONTROL</small>
</button>
</nav>
<div class="cockpit-clock">
<span class="clock-status"><i></i> SYSTEM LIVE</span>
<strong>{{ currentTime }}</strong>
<small>{{ weekText }}</small>
</div> </div>
</div> </div>
</template> </template>
@ -26,8 +31,6 @@ const emits = defineEmits(['activePage'])
const currentTime = ref('') const currentTime = ref('')
const weekText = ref('') const weekText = ref('')
const binaryText = ref('13513034532438213')
const weekNames = ['星期日','星期一','星期二','星期三','星期四','星期五','星期六']; const weekNames = ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'];
const updateClock = () => { const updateClock = () => {
const now = new Date(); const now = new Date();
@ -41,121 +44,181 @@ const updateClock = () => {
weekText.value = weekNames[now.getDay()] weekText.value = weekNames[now.getDay()]
} }
const genBinary = (len) => {
let str = '';
for (let i = 0; i < len; i++) str += Math.round(Math.random()).toString();
return str;
}
const animateBinary = () => {
binaryText.value = genBinary(30)
}
const activePage = ref('home') const activePage = ref('home')
const handleBtn = (page) => { const handleBtn = (page) => {
activePage.value = page activePage.value = page
emits("activePage", page); emits("activePage", page);
} }
let clockTimer, binaryTimer; let clockTimer;
onMounted(() => { onMounted(() => {
updateClock(); updateClock();
clockTimer = setInterval(updateClock, 1000); clockTimer = setInterval(updateClock, 1000);
animateBinary()
binaryTimer = setInterval(animateBinary, 400);
}) })
onUnmounted(() => { onUnmounted(() => {
clearInterval(clockTimer) clearInterval(clockTimer)
clearInterval(binaryTimer)
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.header-container { .header-container {
// width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
gap: 20px; min-height: 0;
margin: 0 20px; flex-direction: column;
margin: 0;
background: var(--min-bg-white);
} }
.left { .cockpit-brand {
flex: 1;
display: flex; display: flex;
justify-content: space-evenly; min-height: 164px;
flex-direction: column;
.date {
display: flex;
align-items: center;
justify-content: center; justify-content: center;
gap: 20px; gap: 14px;
color: #00c2ff; padding: 24px;
border-bottom: 1px solid var(--min-border-light);
span {
color: var(--min-primary);
font-size: 10px;
font-weight: 600;
}
strong {
color: var(--min-text-primary);
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
letter-spacing: 2px; line-height: 1.65;
.week {
font-size: 18px;
}
} }
} }
.application-title { .cockpit-nav-label {
flex: 1;
display: flex; display: flex;
height: 52px;
align-items: center; align-items: center;
justify-content: center; justify-content: space-between;
font-size: 26px; padding: 0 24px;
font-weight: 700; border-bottom: 1px solid var(--min-border-light);
text-align: center; color: var(--min-text-secondary);
color: #00c2ff; font-size: 11px;
letter-spacing: 4px;
white-space: nowrap;
}
.right { b {
flex: 1; color: var(--min-text-primary);
display: flex; font-size: 12px;
justify-content: space-evenly; font-weight: 600;
}
}
.btn-container { .btn-container {
display: flex; display: flex;
align-items: center; flex-direction: column;
justify-content: space-between;
gap: 12px ;
.nav-btn { .nav-btn {
background: rgba(0, 60, 140, 0.5); display: grid;
border: 1px solid rgba(0, 194, 255, 0.35); position: relative;
color: #a0d8f0; min-height: 88px;
padding: 6px 20px; grid-template-columns: 22px minmax(0, 1fr);
border-radius: 3px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
}
.active {
background: rgba(0, 120, 220, 0.7);
border-color: #00c2ff;
color: #fff;
box-shadow: 0 0 10px rgba(0, 194, 255, 0.5);
}
}
}
.binary-strip {
display: flex;
align-items: center; align-items: center;
justify-content: center; column-gap: 10px;
font-family: 'Orbitron', monospace; row-gap: 3px;
padding: 16px 24px;
border: 0;
border-bottom: 1px solid var(--min-border-light);
background: var(--min-bg-white);
color: var(--min-text-secondary);
text-align: left;
cursor: pointer;
> span {
width: 22px;
grid-row: 1 / 3;
color: var(--min-text-secondary);
font-size: 10px; font-size: 10px;
color: rgba(0, 194, 255, 0.3);
letter-spacing: 2px;
white-space: nowrap;
overflow: hidden;
} }
strong {
grid-column: 2;
color: var(--min-text-primary);
font-size: 14px;
font-weight: 600;
}
small {
grid-column: 2;
margin-left: 0;
color: var(--min-text-secondary);
font-size: 9px;
}
&::before {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 3px;
background: transparent;
}
&:hover {
background: var(--min-bg-page);
}
&.active {
background: var(--min-primary-soft);
&::before { background: var(--min-primary); }
> span,
strong { color: var(--min-primary); }
}
}
}
.cockpit-clock {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: auto;
padding: 20px 24px;
border-top: 1px solid var(--min-border-light);
.clock-status {
color: var(--min-primary);
font-size: 9px;
font-weight: 600;
i {
display: inline-block;
width: 5px;
height: 5px;
margin-right: 6px;
border-radius: 50%;
background: var(--min-primary);
}
}
strong {
color: var(--min-text-primary);
font-size: 13px;
font-weight: 600;
}
small {
color: var(--min-text-secondary);
font-size: 11px;
}
}
@media (max-width: 900px) {
.cockpit-brand { min-height: 112px; }
.cockpit-brand strong { font-size: 14px; line-height: 1.4; }
.cockpit-nav-label,
.cockpit-clock { display: none; }
.btn-container { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
.btn-container .nav-btn { min-height: 64px; padding: 12px; }
.btn-container .nav-btn small { display: none; }
}
</style> </style>

View File

@ -322,4 +322,58 @@ function onRelease() {
} }
} }
} }
.panel {
.card {
border-bottom-color: var(--min-border-light);
&__head h3 {
color: var(--min-text-secondary);
font-family: var(--min-font-family);
font-weight: 600;
letter-spacing: 0;
}
:deep(.el-tag) {
border-color: var(--min-border-light);
background: var(--min-primary-soft);
color: var(--min-primary);
}
.dpad {
&__btn,
&__core {
border-color: var(--min-border-light);
border-radius: var(--min-radius-base);
background: var(--min-bg-page);
color: var(--min-text-regular);
box-shadow: none;
}
&__btn:hover,
&__btn:active {
border-color: var(--min-primary);
background: var(--min-primary-soft);
color: var(--min-primary);
box-shadow: none;
transform: none;
}
&__core span { background: var(--min-primary); }
}
.param {
&__head { color: var(--min-text-secondary); }
&__val { color: var(--min-primary); }
:deep(.el-slider__runway) { background: var(--min-border-light); }
:deep(.el-slider__bar) { background: var(--min-primary); }
:deep(.el-slider__button) {
border-color: var(--min-primary);
background: var(--min-bg-white);
box-shadow: none;
}
}
}
}
</style> </style>

View File

@ -822,7 +822,7 @@ $panel-border: rgb(112 192 255 / 24%);
h1 { h1 {
margin: 0; margin: 0;
font-size: clamp(23px, 3vw, 30px); font-size: 28px;
line-height: 1.2; line-height: 1.2;
letter-spacing: 5px; letter-spacing: 5px;
text-shadow: 0 0 25px rgb(79 159 255 / 25%); text-shadow: 0 0 25px rgb(79 159 255 / 25%);
@ -1083,6 +1083,148 @@ $panel-border: rgb(112 192 255 / 24%);
audio { display: none; } audio { display: none; }
} }
.voice-conversation {
color: var(--min-text-primary);
background: var(--min-bg-white);
&::before,
&::after,
.tech-grid,
.orbit-glow {
display: none;
}
.corner-title {
color: var(--min-text-primary);
font-size: 16px;
font-weight: 600;
letter-spacing: 0;
text-shadow: none;
}
.hero-title {
h1 {
color: var(--min-text-primary);
font-size: 20px;
font-weight: 600;
letter-spacing: 0;
text-shadow: none;
}
p {
color: var(--min-text-secondary);
letter-spacing: 0;
}
}
.microphone-orbit {
border: 1px solid var(--min-border-light);
border-radius: var(--min-radius-base);
background: var(--min-primary-soft);
box-shadow: none;
animation: none;
&::before {
border-color: var(--min-primary);
border-radius: var(--min-radius-sm);
box-shadow: none;
}
}
.microphone-core {
border-radius: var(--min-radius-sm);
background: var(--min-primary);
box-shadow: none;
}
.sound-wave i {
border-radius: var(--min-radius-sm);
background: var(--min-primary);
box-shadow: none;
}
.floating-status {
border: 1px solid var(--min-border-light);
border-radius: var(--min-radius-sm);
color: var(--min-text-regular);
background: var(--min-bg-page);
box-shadow: none;
}
.robot-volume {
color: var(--min-text-secondary);
&__value { color: var(--min-text-primary); }
:deep(.el-slider__runway) { background: var(--min-border-light); }
:deep(.el-slider__bar) { background: var(--min-primary); }
:deep(.el-slider__button) {
border-color: var(--min-primary);
background: var(--min-bg-white);
box-shadow: none;
}
}
:deep(.call-button) {
border: 1px solid var(--min-primary);
border-radius: var(--min-radius-base);
color: #fff;
background: var(--min-primary);
box-shadow: none;
font-weight: 600;
letter-spacing: 0;
&:hover,
&:focus {
border-color: var(--min-primary-hover);
background: var(--min-primary-hover);
transform: none;
}
&.call-button--danger {
border-color: var(--el-color-danger);
background: var(--el-color-danger);
}
}
:deep(.mute-button) {
border-color: var(--min-border-light);
color: var(--min-text-regular);
background: var(--min-bg-white);
&:hover {
border-color: var(--min-primary);
color: var(--min-primary);
background: var(--min-primary-soft);
}
}
.status-card {
border: 1px solid var(--min-border-light);
border-radius: var(--min-radius-base);
background: var(--min-bg-page);
box-shadow: none;
backdrop-filter: none;
.status-copy {
p,
p strong { color: var(--min-text-primary); }
span { color: var(--min-text-secondary); }
}
}
.feature-tags {
border-top-color: var(--min-border-light);
span {
border: 1px solid var(--min-border-light);
border-radius: var(--min-radius-sm);
color: var(--min-text-secondary);
background: var(--min-bg-white);
}
}
}
@keyframes orbit-breathe { @keyframes orbit-breathe {
0%, 100% { transform: scale(1); filter: brightness(1); } 0%, 100% { transform: scale(1); filter: brightness(1); }
50% { transform: scale(1.035); filter: brightness(1.16); } 50% { transform: scale(1.035); filter: brightness(1.16); }

View File

@ -5,7 +5,7 @@
<div class="robot-state">机器人状态</div> <div class="robot-state">机器人状态</div>
<div class="robot-info" v-for="item, index in robotList"> <div class="robot-info" v-for="item, index in robotList">
<div class="title-box"> <div class="title-box">
<div><SvgIcon name="bot" :color="index === 0 ? '#00D4FF' : '#FFB300'" /></div> <div><SvgIcon name="bot" color="var(--min-primary)" /></div>
<div class="title">{{ item.robotName }}</div> <div class="title">{{ item.robotName }}</div>
<div class="state" :class="agvRuntimeStatusClass(item.status)">{{ agvRuntimeStatusLabel(item.status) }}</div> <div class="state" :class="agvRuntimeStatusClass(item.status)">{{ agvRuntimeStatusLabel(item.status) }}</div>
</div> </div>
@ -44,18 +44,18 @@
<div class="map-container"> <div class="map-container">
<div class="title-container"> <div class="title-container">
<div class="title"> <div class="title">
<SvgIcon name="map" color='#00D4FF' /> <SvgIcon name="map" color="var(--min-primary)" />
<span>实时地图 — 工厂园区</span> <span>实时地图 — 工厂园区</span>
</div> </div>
<div class="map-controller"> <div class="map-controller">
<div class="btn-container"> <div class="btn-container">
<SvgIcon name="zoom-in" color='#00D4FF' :size="16" @click="zoomCanvas(true)" /> <SvgIcon name="zoom-in" color="var(--min-primary)" :size="16" @click="zoomCanvas(true)" />
</div> </div>
<div class="btn-container"> <div class="btn-container">
<SvgIcon name="zoom-out" color='#00D4FF' :size="16" @click="zoomCanvas(false)" /> <SvgIcon name="zoom-out" color="var(--min-primary)" :size="16" @click="zoomCanvas(false)" />
</div> </div>
<div class="btn-container"> <div class="btn-container">
<SvgIcon name="locate-fixed" color='#00D4FF' :size="16" @click="centerCanvasView" /> <SvgIcon name="locate-fixed" color="var(--min-primary)" :size="16" @click="centerCanvasView" />
</div> </div>
</div> </div>
</div> </div>
@ -169,7 +169,7 @@ onUnmounted(() => {
}) })
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.page-container { .page-container {
height: 100%; height: 100%;
width: 100%; width: 100%;

View File

@ -46,7 +46,7 @@ import { agvRuntimeStatusClass, agvRuntimeStatusLabel } from '@/utils/agvRuntime
const videoUrl = ref('https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8') const videoUrl = ref('https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8')
const colorList = ['#00D4FF', '#FFB300', '#00FF88', '#fd00ff'] const colorList = Array(4).fill('var(--min-primary)')
const robotList = ref([]) const robotList = ref([])

View File

@ -1,8 +1,8 @@
<template> <template>
<div class="container"> <div class="inspection-cockpit">
<div class="header"> <aside class="header">
<Header @active-page="activePage" /> <Header @active-page="activePage" />
</div> </aside>
<div class="body-box"> <div class="body-box">
<MapOverview v-if="currentPage === 'home'" /> <MapOverview v-if="currentPage === 'home'" />
<ManualTakeover v-if="currentPage === 'manualTakeover'" /> <ManualTakeover v-if="currentPage === 'manualTakeover'" />
@ -28,23 +28,203 @@ const activePage = (page) => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.container { .inspection-cockpit {
width: 100%; width: 100%;
height: 100%; height: 100%;
position: relative; position: relative;
z-index: 1; z-index: 1;
background-color: #000d2e; background-color: var(--min-bg-page);
display: flex; display: grid;
flex-direction: column; grid-template-columns: 248px minmax(0, 1fr);
.header { .header {
height: 64px; min-width: 0;
background: linear-gradient(180deg, rgba(0, 20, 70, 0.98) 0%, rgba(0, 10, 50, 0.95) 100%); height: 100%;
border-bottom: 1px solid rgba(0, 194, 255, 0.3); background: var(--min-bg-white);
border-right: 1px solid var(--min-border-light);
} }
.body-box { .body-box {
flex: 1; min-height: 0;
min-width: 0;
padding: 20px;
overflow: hidden;
}
@media (max-width: 900px) {
grid-template-columns: minmax(0, 1fr);
grid-template-rows: 176px minmax(0, 1fr);
.header {
height: 176px;
border-right: 0;
border-bottom: 1px solid var(--min-border-light);
}
}
}
</style>
<style lang="scss">
.inspection-cockpit {
color: var(--min-text-primary);
.binary-strip,
.tech-grid,
.orbit-glow {
display: none !important;
}
.page-container,
> .body-box > .monitor-container {
border: 1px solid var(--min-border-light);
border-radius: var(--min-radius-base);
background: var(--min-bg-white);
}
.left-container,
.right-container {
background: var(--min-bg-white);
}
.robot,
.robot-info,
.task-box,
.alarm-box,
.mode-item,
.control-panel,
.joint-item,
.panel .card,
.status-card {
border: 1px solid var(--min-border-light) !important;
border-radius: var(--min-radius-base) !important;
background: var(--min-bg-white) !important;
box-shadow: none !important;
color: var(--min-text-regular) !important;
}
.robot.active,
.mode-item.activeMode {
border-color: var(--min-primary) !important;
background: var(--min-primary-soft) !important;
}
.robot svg,
.mode-item svg {
color: var(--min-primary) !important;
fill: currentColor !important;
}
.xgplayer-error .xgplayer-error-refresh {
color: var(--min-primary) !important;
}
.title,
.container-title,
.robot-state,
.bot-name,
.item-detail,
.task-info,
.controller-panel,
.panel h3,
.status-copy p,
.status-copy strong {
color: var(--min-text-primary) !important;
}
.item-title,
.alarm-info,
.bot-local,
.label,
.param__head,
.status-copy span {
color: var(--min-text-secondary) !important;
}
.map-container,
.video-container {
border-color: var(--min-border-light) !important;
background: var(--min-bg-white);
}
.map-controller .btn-container {
border: 1px solid var(--min-border-light) !important;
border-radius: var(--min-radius-sm) !important;
background: var(--min-bg-page) !important;
}
.split-line {
background: var(--min-border-light) !important;
}
.panel .card,
.direction-panel .card {
border-width: 0 0 1px !important;
}
.dpad__btn,
.dpad__core {
border: 1px solid var(--min-border-light) !important;
border-radius: var(--min-radius-base) !important;
background: var(--min-bg-page) !important;
color: var(--min-text-regular) !important;
box-shadow: none !important;
}
.dpad__btn:hover,
.dpad__btn:active {
border-color: var(--min-primary) !important;
background: var(--min-primary-soft) !important;
color: var(--min-primary) !important;
transform: none !important;
}
.joint-item .el-input-group__prepend,
.joint-item .el-input-group__append,
.joint-item .el-input__wrapper {
border-color: var(--min-border-light) !important;
background: var(--min-bg-white) !important;
box-shadow: 0 0 0 1px var(--min-border-light) inset !important;
color: var(--min-text-regular) !important;
}
.voice-conversation {
border: 1px solid var(--min-border-light);
border-radius: var(--min-radius-base);
}
@media (max-width: 900px) {
> .body-box {
overflow: auto;
}
> .body-box > .page-container,
> .body-box > .monitor-container {
height: auto !important;
min-height: 100%;
flex-direction: column;
}
.left-container,
.right-container {
width: 100% !important;
height: auto !important;
min-height: 320px;
}
.map-container,
.video-container {
min-height: 420px;
}
}
@media (max-width: 640px) {
> .body-box {
padding: 12px;
}
.map-container,
.video-container {
min-height: 320px;
}
} }
} }
</style> </style>

View File

@ -664,7 +664,7 @@ onUnmounted(() => {
.left { .left {
width: 25%; width: 25%;
height: 100%; height: 100%;
background: #fff; background: var(--min-bg-white);
padding: 16px; padding: 16px;
.header { .header {
@ -672,7 +672,7 @@ onUnmounted(() => {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding-bottom: 16px; padding-bottom: 16px;
border-bottom: 1px solid #eee; border-bottom: 1px solid var(--min-border-light);
} }
.taskList-container { .taskList-container {
@ -682,9 +682,9 @@ onUnmounted(() => {
.task-item { .task-item {
padding: 12px; padding: 12px;
background: #fafafa; background: var(--min-bg-page);
box-shadow: inset 0 0 0 1px #eee; box-shadow: inset 0 0 0 1px var(--min-border-light);
border-radius: 8px; border-radius: var(--min-radius-base);
margin-bottom: 16px; margin-bottom: 16px;
cursor: pointer; cursor: pointer;
transition: background-color 0.2s ease, box-shadow 0.2s ease; transition: background-color 0.2s ease, box-shadow 0.2s ease;
@ -705,7 +705,7 @@ onUnmounted(() => {
.task-title { .task-title {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: 600;
} }
.task-actions { .task-actions {
@ -720,7 +720,7 @@ onUnmounted(() => {
.robot { .robot {
margin-top: 8px; margin-top: 8px;
color: #666; color: var(--min-text-secondary);
} }
} }
} }
@ -729,26 +729,26 @@ onUnmounted(() => {
.right { .right {
flex: 1; flex: 1;
padding: 16px; padding: 16px;
background: #fff; background: var(--min-bg-white);
overflow: hidden; overflow: hidden;
.header { .header {
display: flex; display: flex;
margin-bottom: 16px; margin-bottom: 16px;
border-bottom: 1px solid #eee; border-bottom: 1px solid var(--min-border-light);
.label { .label {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: 600;
color: #333; color: var(--min-text-regular);
cursor: pointer; cursor: pointer;
box-shadow: inset 0 0 0 1px #eee; box-shadow: inset 0 0 0 1px var(--min-border-light);
padding: 10px; padding: 10px;
} }
.active { .active {
color: #409eff; color: var(--min-primary);
box-shadow: inset 0 0 0 1px #409eff; box-shadow: inset 0 -2px 0 var(--min-primary);
} }
} }
@ -759,8 +759,8 @@ onUnmounted(() => {
.task { .task {
padding: 16px; padding: 16px;
background: #fafafa; background: var(--min-bg-page);
border-radius: 20px; border-radius: var(--min-radius-base);
margin-bottom: 12px; margin-bottom: 12px;
.task-header { .task-header {
@ -774,11 +774,11 @@ onUnmounted(() => {
.title { .title {
font-size: 18px; font-size: 18px;
font-weight: bold; font-weight: 600;
} }
.id { .id {
color: #666; color: var(--min-text-secondary);
font-size: 14px; font-size: 14px;
margin: 8px 0; margin: 8px 0;
} }
@ -788,7 +788,7 @@ onUnmounted(() => {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 50px; gap: 50px;
color: #666; color: var(--min-text-secondary);
font-size: 14px; font-size: 14px;
} }
} }
@ -797,9 +797,9 @@ onUnmounted(() => {
.progress-label { .progress-label {
margin-top: 12px; margin-top: 12px;
color: #666; color: var(--min-text-secondary);
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: 600;
} }
} }
} }

View File

@ -716,7 +716,7 @@ onUnmounted(() => {
.left { .left {
width: 25%; width: 25%;
height: 100%; height: 100%;
background: #fff; background: var(--min-bg-white);
padding: 16px; padding: 16px;
.header { .header {
@ -724,7 +724,7 @@ onUnmounted(() => {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding-bottom: 16px; padding-bottom: 16px;
border-bottom: 1px solid #eee; border-bottom: 1px solid var(--min-border-light);
} }
.taskList-container { .taskList-container {
@ -734,9 +734,9 @@ onUnmounted(() => {
.task-item { .task-item {
padding: 12px; padding: 12px;
background: #fafafa; background: var(--min-bg-page);
box-shadow: inset 0 0 0 1px #eee; box-shadow: inset 0 0 0 1px var(--min-border-light);
border-radius: 8px; border-radius: var(--min-radius-base);
margin-bottom: 16px; margin-bottom: 16px;
cursor: pointer; cursor: pointer;
transition: background-color 0.2s ease, box-shadow 0.2s ease; transition: background-color 0.2s ease, box-shadow 0.2s ease;
@ -757,7 +757,7 @@ onUnmounted(() => {
.task-title { .task-title {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: 600;
} }
.task-actions { .task-actions {
@ -772,7 +772,7 @@ onUnmounted(() => {
.robot { .robot {
margin-top: 8px; margin-top: 8px;
color: #666; color: var(--min-text-secondary);
} }
} }
} }
@ -781,26 +781,26 @@ onUnmounted(() => {
.right { .right {
flex: 1; flex: 1;
padding: 16px; padding: 16px;
background: #fff; background: var(--min-bg-white);
overflow: hidden; overflow: hidden;
.header { .header {
display: flex; display: flex;
margin-bottom: 16px; margin-bottom: 16px;
border-bottom: 1px solid #eee; border-bottom: 1px solid var(--min-border-light);
.label { .label {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: 600;
color: #333; color: var(--min-text-regular);
cursor: pointer; cursor: pointer;
box-shadow: inset 0 0 0 1px #eee; box-shadow: inset 0 0 0 1px var(--min-border-light);
padding: 10px; padding: 10px;
} }
.active { .active {
color: #409eff; color: var(--min-primary);
box-shadow: inset 0 0 0 1px #409eff; box-shadow: inset 0 -2px 0 var(--min-primary);
} }
} }
@ -811,8 +811,8 @@ onUnmounted(() => {
.task { .task {
padding: 16px; padding: 16px;
background: #fafafa; background: var(--min-bg-page);
border-radius: 20px; border-radius: var(--min-radius-base);
margin-bottom: 12px; margin-bottom: 12px;
.task-header { .task-header {
@ -826,11 +826,11 @@ onUnmounted(() => {
.title { .title {
font-size: 18px; font-size: 18px;
font-weight: bold; font-weight: 600;
} }
.id { .id {
color: #666; color: var(--min-text-secondary);
font-size: 14px; font-size: 14px;
margin: 8px 0; margin: 8px 0;
} }
@ -840,7 +840,7 @@ onUnmounted(() => {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 50px; gap: 50px;
color: #666; color: var(--min-text-secondary);
font-size: 14px; font-size: 14px;
} }
} }
@ -849,9 +849,9 @@ onUnmounted(() => {
.progress-label { .progress-label {
margin-top: 12px; margin-top: 12px;
color: #666; color: var(--min-text-secondary);
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: 600;
} }
} }
} }

View File

@ -1,25 +1,31 @@
<template> <template>
<div class="lock-container"> <div class="lock-container">
<!-- 动态粒子背景 --> <section class="lock-visual">
<canvas ref="particleCanvas" class="particle-bg"></canvas> <div class="lock-brand"><span>CMVR / SECURE</span><strong>CONTROL DESK</strong></div>
<div class="clock-group">
<!-- 时钟 --> <span>LOCAL TIME</span>
<div class="lock-time">{{ currentTime }}</div> <div class="lock-time">{{ currentTime }}</div>
<div class="lock-date">{{ currentDate }}</div> <div class="lock-date">{{ currentDate }}</div>
</div>
<div class="lock-state"><i></i><span>SESSION LOCKED</span></div>
</section>
<!-- 锁屏卡片 --> <main class="lock-access">
<div class="access-index"><span>ACCESS</span><strong>03</strong></div>
<div class="lock-card"> <div class="lock-card">
<div class="account-row">
<div class="avatar-wrap"> <div class="avatar-wrap">
<img :src="userStore.avatar" class="lock-avatar" @error="onAvatarError" /> <img :src="userStore.avatar" class="lock-avatar" @error="onAvatarError" />
<div class="lock-icon">🔒</div> <div class="lock-icon"><el-icon><Lock /></el-icon></div>
</div>
<div><span>LOCKED ACCOUNT</span><div class="lock-username">{{ userStore.nickName }}</div></div>
</div> </div>
<div class="lock-username">{{ userStore.nickName }}</div>
<div class="lock-hint">系统已锁定,请输入密码解锁</div>
<div class="input-label">账户密码</div>
<div class="input-wrap" :class="{ shake: isShaking }"> <div class="input-wrap" :class="{ shake: isShaking }">
<input ref="passwordInput" v-model="password" type="password" placeholder="请输入登录密码" class="lock-input" @keydown.enter="handleUnlock" autocomplete="off" /> <input ref="passwordInput" v-model="password" type="password" placeholder="请输入登录密码" class="lock-input" @keydown.enter="handleUnlock" autocomplete="off" />
<button class="unlock-btn" @click="handleUnlock" :disabled="loading"> <button class="unlock-btn" title="解锁" aria-label="解锁" @click="handleUnlock" :disabled="loading">
<span v-if="!loading">→</span> <el-icon v-if="!loading"><Right /></el-icon>
<span v-else class="loading-dot">···</span> <span v-else class="loading-dot">···</span>
</button> </button>
</div> </div>
@ -30,6 +36,7 @@
<a href="javascript:;" @click="goLogin">退出重新登录</a> <a href="javascript:;" @click="goLogin">退出重新登录</a>
</div> </div>
</div> </div>
</main>
</div> </div>
</template> </template>
@ -39,6 +46,7 @@ import useUserStore from '@/store/modules/user'
import useLockStore from '@/store/modules/lock' import useLockStore from '@/store/modules/lock'
import { unlockScreen } from '@/api/login' import { unlockScreen } from '@/api/login'
import defAva from '@/assets/images/profile.jpg' import defAva from '@/assets/images/profile.jpg'
import { Lock, Right } from '@element-plus/icons-vue'
const router = useRouter() const router = useRouter()
const userStore = useUserStore() const userStore = useUserStore()
@ -51,11 +59,8 @@ const isShaking = ref(false)
const currentTime = ref('') const currentTime = ref('')
const currentDate = ref('') const currentDate = ref('')
const passwordInput = ref(null) const passwordInput = ref(null)
const particleCanvas = ref(null)
let timer = null let timer = null
let animationId = null
let particles = []
const onAvatarError = (e) => { const onAvatarError = (e) => {
e.target.src = defAva e.target.src = defAva
@ -108,137 +113,127 @@ const goLogin = () => {
}) })
} }
const initParticles = () => {
const canvas = particleCanvas.value
if (!canvas) return
const ctx = canvas.getContext('2d')
const resize = () => {
canvas.width = window.innerWidth
canvas.height = window.innerHeight
}
resize()
window.addEventListener('resize', resize)
particles = Array.from({ length: 80 }, () => ({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
r: Math.random() * 2 + 1,
dx: (Math.random() - 0.5) * 0.6,
dy: (Math.random() - 0.5) * 0.6,
alpha: Math.random() * 0.5 + 0.2
}))
const draw = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height)
particles.forEach(p => {
ctx.beginPath()
ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2)
ctx.fillStyle = `rgba(255,255,255,${p.alpha})`
ctx.fill()
p.x += p.dx
p.y += p.dy
if (p.x < 0 || p.x > canvas.width) p.dx *= -1
if (p.y < 0 || p.y > canvas.height) p.dy *= -1
})
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; j < particles.length; j++) {
const a = particles[i], b = particles[j]
const dist = Math.hypot(a.x - b.x, a.y - b.y)
if (dist < 120) {
ctx.beginPath()
ctx.moveTo(a.x, a.y)
ctx.lineTo(b.x, b.y)
ctx.strokeStyle = `rgba(255,255,255,${0.15 * (1 - dist / 120)})`
ctx.lineWidth = 0.5
ctx.stroke()
}
}
}
animationId = requestAnimationFrame(draw)
}
draw()
}
onMounted(() => { onMounted(() => {
startClock() startClock()
initParticles()
nextTick(() => passwordInput.value?.focus()) nextTick(() => passwordInput.value?.focus())
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
clearInterval(timer) clearInterval(timer)
cancelAnimationFrame(animationId)
}) })
</script> </script>
<style scoped> <style lang="scss" scoped>
/* 样式与原文件完全一致,无需改动 */
.lock-container { .lock-container {
position: fixed; position: fixed;
inset: 0; inset: 0;
background: linear-gradient(135deg, #0f0c29, #302b63, #24243e); display: grid;
display: flex; grid-template-columns: minmax(0, 1fr) minmax(440px, 36vw);
flex-direction: column; background: var(--min-bg-page);
align-items: center;
justify-content: center;
z-index: 9999; z-index: 9999;
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif; font-family: var(--min-font-family);
overflow: hidden; overflow: hidden;
} }
.particle-bg { .lock-visual {
position: relative;
isolation: isolate;
display: flex;
min-width: 0;
flex-direction: column;
justify-content: space-between;
padding: 42px 48px;
background-image: url('../assets/images/login-background.jpg');
background-position: center;
background-size: cover;
}
.lock-visual::before {
content: '';
position: absolute; position: absolute;
inset: 0; inset: 0;
z-index: 0; z-index: -1;
background: rgba(255, 255, 255, 0.7);
}
.lock-brand {
display: flex;
align-items: center;
gap: 16px;
span { padding-right: 16px; border-right: 2px solid var(--min-primary); color: var(--min-primary); font-size: 10px; font-weight: 600; }
strong { color: var(--min-text-primary); font-size: 13px; font-weight: 600; }
}
.clock-group {
> span { color: var(--min-primary); font-size: 10px; font-weight: 600; }
} }
.lock-time { .lock-time {
position: relative; margin: 12px 0 4px;
z-index: 1; color: var(--min-text-primary);
font-size: 72px; font-size: 72px;
font-weight: 200; font-weight: 500;
color: #fff;
letter-spacing: 4px;
text-shadow: 0 0 40px rgba(255,255,255,0.3);
margin-bottom: 8px;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
} }
.lock-date { .lock-date { color: var(--min-text-secondary); font-size: 14px; }
.lock-state {
display: flex;
align-items: center;
gap: 8px;
color: var(--min-text-regular);
font-size: 10px;
font-weight: 600;
i { width: 6px; height: 6px; border-radius: 50%; background: var(--min-primary); }
}
.lock-access {
position: relative; position: relative;
z-index: 1; display: flex;
font-size: 15px; align-items: center;
color: rgba(255,255,255,0.6); justify-content: center;
margin-bottom: 48px; min-width: 0;
letter-spacing: 2px; padding: 56px 48px;
background: var(--min-bg-white);
}
.access-index {
display: flex;
position: absolute;
top: 28px;
right: 32px;
gap: 10px;
color: var(--min-text-secondary);
font-size: 10px;
strong { color: var(--min-primary); font-size: 12px; font-weight: 600; }
} }
.lock-card { .lock-card {
position: relative; width: min(360px, 100%);
z-index: 1; }
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(20px); .account-row {
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 24px;
padding: 40px 48px;
width: 360px;
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
box-shadow: 0 25px 60px rgba(0,0,0,0.4); gap: 16px;
margin-bottom: 36px;
> div:last-child > span { color: var(--min-primary); font-size: 10px; font-weight: 600; }
} }
.avatar-wrap { .avatar-wrap {
position: relative; position: relative;
margin-bottom: 16px;
} }
.lock-avatar { .lock-avatar {
width: 80px; width: 64px;
height: 80px; height: 64px;
border-radius: 50%; border-radius: 50%;
border: 3px solid rgba(255,255,255,0.3); border: 1px solid var(--min-border-light);
object-fit: cover; object-fit: cover;
display: block; display: block;
} }
@ -247,45 +242,45 @@ onBeforeUnmount(() => {
position: absolute; position: absolute;
bottom: -4px; bottom: -4px;
right: -4px; right: -4px;
background: rgba(255,255,255,0.15); border: 1px solid var(--min-border-light);
border-radius: 50%; border-radius: 50%;
background: var(--min-bg-white);
color: var(--min-primary);
width: 26px; width: 26px;
height: 26px; height: 26px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 13px; font-size: 13px;
backdrop-filter: blur(8px);
} }
.lock-username { .lock-username {
color: #fff; color: var(--min-text-primary);
font-size: 18px; font-size: 18px;
font-weight: 600; font-weight: 600;
margin-bottom: 6px; margin-top: 5px;
letter-spacing: 1px; letter-spacing: 0;
} }
.lock-hint { .input-label {
color: rgba(255,255,255,0.5); color: var(--min-text-secondary);
font-size: 13px; font-size: 11px;
margin-bottom: 28px; margin-bottom: 8px;
} }
.input-wrap { .input-wrap {
width: 100%; width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
background: rgba(255,255,255,0.1); padding: 3px 3px 3px 12px;
border: 1px solid rgba(255,255,255,0.2); border: 1px solid var(--min-border-light);
border-radius: 50px; border-radius: var(--min-radius-base);
padding: 4px 4px 4px 20px; background: var(--min-bg-white);
transition: border-color 0.3s; transition: border-color 0.3s;
} }
.input-wrap:focus-within { .input-wrap:focus-within {
border-color: rgba(255,255,255,0.6); border-color: var(--min-primary);
background: rgba(255,255,255,0.13);
} }
.input-wrap.shake { .input-wrap.shake {
@ -305,20 +300,20 @@ onBeforeUnmount(() => {
background: transparent; background: transparent;
border: none; border: none;
outline: none; outline: none;
color: #fff; color: var(--min-text-primary);
font-size: 15px; font-size: 15px;
padding: 10px 0; padding: 10px 0;
} }
.lock-input::placeholder { .lock-input::placeholder {
color: rgba(255,255,255,0.35); color: var(--min-text-placeholder);
} }
.unlock-btn { .unlock-btn {
width: 42px; width: 42px;
height: 42px; height: 42px;
border-radius: 50%; border-radius: var(--min-radius-sm);
background: linear-gradient(135deg, #667eea, #764ba2); background: var(--min-primary);
border: none; border: none;
color: #fff; color: #fff;
font-size: 18px; font-size: 18px;
@ -331,7 +326,7 @@ onBeforeUnmount(() => {
} }
.unlock-btn:hover:not(:disabled) { .unlock-btn:hover:not(:disabled) {
transform: scale(1.08); background: var(--min-primary-hover);
} }
.unlock-btn:disabled { .unlock-btn:disabled {
@ -346,7 +341,7 @@ onBeforeUnmount(() => {
.error-msg { .error-msg {
margin-top: 14px; margin-top: 14px;
color: #ff7675; color: var(--el-color-danger);
font-size: 13px; font-size: 13px;
text-align: center; text-align: center;
animation: fadeIn 0.3s ease; animation: fadeIn 0.3s ease;
@ -362,13 +357,22 @@ onBeforeUnmount(() => {
} }
.lock-footer a { .lock-footer a {
color: rgba(255,255,255,0.4); color: var(--min-text-secondary);
font-size: 13px; font-size: 13px;
text-decoration: none; text-decoration: none;
transition: color 0.2s; transition: color 0.2s;
} }
.lock-footer a:hover { .lock-footer a:hover {
color: rgba(255,255,255,0.8); color: var(--min-primary);
}
@media (max-width: 520px) {
.lock-container { grid-template-columns: 1fr; grid-template-rows: 220px minmax(0, 1fr); }
.lock-visual { padding: 24px; }
.lock-time { margin-top: 8px; font-size: 44px; }
.lock-state { display: none; }
.lock-access { padding: 64px 24px; align-items: flex-start; }
.lock-card { width: 100%; }
} }
</style> </style>

View File

@ -1,7 +1,31 @@
<template> <template>
<div class="login"> <div class="login">
<section class="login-visual">
<div class="visual-brand">
<span class="visual-brand__index">CMVR / 2026</span>
<strong>{{ title }}</strong>
</div>
<div class="visual-copy">
<span>INTELLIGENT TEST OPERATIONS</span>
<h1>机器人智能测试<br>运行中枢</h1>
<div class="visual-modules">
<span>设备接入</span>
<span>流程编排</span>
<span>巡检执行</span>
</div>
</div>
</section>
<main class="login-access">
<div class="access-index">
<span>SECURE ACCESS</span>
<strong>01</strong>
</div>
<el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form"> <el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
<h3 class="title">{{ title }}</h3> <div class="form-heading">
<span>ACCOUNT</span>
<h2>登录工作台</h2>
</div>
<el-form-item prop="username"> <el-form-item prop="username">
<el-input <el-input
v-model="loginForm.username" v-model="loginForm.username"
@ -40,27 +64,26 @@
<img :src="codeUrl" @click="getCode" class="login-code-img"/> <img :src="codeUrl" @click="getCode" class="login-code-img"/>
</div> </div>
</el-form-item> </el-form-item>
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox> <div class="form-options">
<el-form-item style="width:100%;"> <el-checkbox v-model="loginForm.rememberMe">记住密码</el-checkbox>
<router-link v-if="register" class="link-type" :to="'/register'">立即注册</router-link>
</div>
<el-form-item class="submit-row">
<el-button <el-button
:loading="loading" :loading="loading"
size="large" size="large"
type="primary" type="primary"
style="width:100%;"
@click.prevent="handleLogin" @click.prevent="handleLogin"
> >
<span v-if="!loading">登 录</span> <span v-if="!loading">进入系统</span>
<span v-else>登 录 中...</span> <span v-else>正在登录...</span>
</el-button> </el-button>
<div style="float: right;" v-if="register">
<router-link class="link-type" :to="'/register'">立即注册</router-link>
</div>
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 底部 -->
<div class="el-login-footer"> <div class="el-login-footer">
<span>{{ footerContent }}</span> <span>{{ footerContent }}</span>
</div> </div>
</main>
</div> </div>
</template> </template>
@ -167,33 +190,152 @@ getCookie()
<style lang='scss' scoped> <style lang='scss' scoped>
.login { .login {
display: flex; display: grid;
justify-content: center; grid-template-columns: minmax(0, 1fr) minmax(440px, 36vw);
align-items: center;
height: 100%; height: 100%;
overflow: hidden;
background: var(--min-bg-page);
} }
.login::before {
content: ""; .login-visual {
position: relative;
isolation: isolate;
min-width: 0;
padding: 42px 48px;
display: flex;
flex-direction: column;
justify-content: space-between;
overflow: hidden;
background-image: url("../assets/images/login-background.jpg");
background-position: center;
background-size: cover;
}
.login-visual::before {
content: '';
position: absolute; position: absolute;
inset: 0; inset: 0;
width: 100%;
background-image: url("../assets/images/login-background.jpg");
height: 100%;
background-size: cover;
opacity: 0.3; /* 只在这层做透明 */
z-index: -1; z-index: -1;
background: rgba(255, 255, 255, 0.68);
} }
.visual-brand {
display: flex;
align-items: center;
gap: 18px;
strong {
max-width: 420px;
color: var(--min-text-primary);
font-size: 14px;
font-weight: 600;
}
}
.visual-brand__index {
padding-right: 18px;
border-right: 2px solid var(--min-primary);
color: var(--min-primary);
font-size: 11px;
font-weight: 600;
}
.visual-copy {
max-width: 620px;
> span {
color: var(--min-primary);
font-size: 11px;
font-weight: 600;
}
h1 {
margin: 14px 0 28px;
color: var(--min-text-primary);
font-size: 52px;
line-height: 1.14;
font-weight: 600;
}
}
.visual-modules {
display: flex;
flex-wrap: wrap;
gap: 0;
span {
min-width: 104px;
padding: 10px 14px;
border: 1px solid rgba(31, 41, 55, 0.28);
border-right: 0;
color: var(--min-text-regular);
background: rgba(255, 255, 255, 0.52);
font-size: 12px;
&:last-child {
border-right: 1px solid rgba(31, 41, 55, 0.28);
}
}
}
.login-access {
position: relative;
min-width: 0;
padding: 42px clamp(36px, 5vw, 76px);
border-left: 1px solid var(--min-border-light);
display: flex;
flex-direction: column;
justify-content: center;
background: var(--min-bg-white);
}
.access-index {
position: absolute;
top: 32px;
right: 36px;
display: flex;
align-items: center;
gap: 12px;
color: var(--min-text-secondary);
font-size: 9px;
strong {
color: var(--min-primary);
font-size: 11px;
font-weight: 600;
}
}
.form-heading {
margin-bottom: 40px;
span {
color: var(--min-primary);
font-size: 10px;
font-weight: 600;
}
h2 {
margin: 8px 0 0;
color: var(--min-text-primary);
font-size: 28px;
line-height: 1.25;
font-weight: 600;
}
}
.title { .title {
margin: 0px auto 30px auto; margin: 0 0 32px;
text-align: center; color: var(--min-text-primary);
color: #707070; font-size: 20px;
font-weight: 600;
text-align: left;
} }
.login-form { .login-form {
border-radius: 6px; width: 100%;
background: #ffffff; max-width: 440px;
width: 400px; margin: 0 auto;
padding: 25px 25px 5px 25px;
.el-input { .el-input {
height: 40px; height: 40px;
input { input {
@ -206,10 +348,31 @@ getCookie()
margin-left: 0px; margin-left: 0px;
} }
} }
.form-options {
min-height: 48px;
margin-top: 4px;
display: flex;
align-items: flex-start;
justify-content: space-between;
}
.submit-row {
margin-bottom: 0;
:deep(.el-form-item__content),
:deep(.el-button) {
width: 100%;
}
:deep(.el-button) {
height: 44px;
}
}
.login-tip { .login-tip {
font-size: 13px; font-size: 13px;
text-align: center; text-align: center;
color: #bfbfbf; color: var(--min-text-secondary);
} }
.login-code { .login-code {
width: 33%; width: 33%;
@ -221,19 +384,81 @@ getCookie()
} }
} }
.el-login-footer { .el-login-footer {
height: 40px; position: absolute;
line-height: 40px; right: 0;
position: fixed; bottom: 24px;
bottom: 0; left: 0;
width: 100%;
text-align: center; text-align: center;
color: #fff; color: var(--min-text-secondary);
font-family: Arial; font-family: var(--min-font-family);
font-size: 12px; font-size: 12px;
letter-spacing: 1px; letter-spacing: 0;
} }
.login-code-img { .login-code-img {
height: 40px; height: 40px;
padding-left: 12px; padding-left: 12px;
} }
@media (max-width: 520px) {
.login {
grid-template-columns: 1fr;
grid-template-rows: 220px minmax(0, 1fr);
}
.login-visual {
padding: 24px;
}
.visual-brand strong,
.visual-modules {
display: none;
}
.visual-copy h1 {
margin: 8px 0 0;
font-size: 28px;
}
.login-access {
padding: 54px 24px 48px;
justify-content: flex-start;
}
.form-heading {
margin-bottom: 28px;
h2 {
font-size: 24px;
}
}
.access-index {
top: 18px;
right: 24px;
}
.el-login-footer {
position: static;
margin-top: auto;
padding-top: 24px;
}
}
@media (max-width: 900px) and (min-width: 521px) {
.login {
grid-template-columns: minmax(0, 1fr) 440px;
}
.login-visual {
padding: 32px;
}
.visual-copy h1 {
font-size: 36px;
}
.visual-modules {
display: none;
}
}
</style> </style>

View File

@ -1,7 +1,9 @@
<template> <template>
<div class="register"> <div class="register">
<main class="register-access">
<div class="access-index"><span>ACCOUNT</span><strong>02</strong></div>
<el-form ref="registerRef" :model="registerForm" :rules="registerRules" class="register-form"> <el-form ref="registerRef" :model="registerForm" :rules="registerRules" class="register-form">
<h3 class="title">{{ title }}</h3> <div class="form-heading"><span>USER REGISTRATION</span><h2>创建账户</h2><p>{{ title }}</p></div>
<el-form-item prop="username"> <el-form-item prop="username">
<el-input <el-input
v-model="registerForm.username" v-model="registerForm.username"
@ -52,26 +54,26 @@
<img :src="codeUrl" @click="getCode" class="register-code-img"/> <img :src="codeUrl" @click="getCode" class="register-code-img"/>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item style="width:100%;"> <el-form-item class="register-actions">
<el-button <el-button
:loading="loading" :loading="loading"
size="large" size="large"
type="primary" type="primary"
style="width:100%;"
@click.prevent="handleRegister" @click.prevent="handleRegister"
> >
<span v-if="!loading">注 册</span> <span v-if="!loading">注册账户</span>
<span v-else>注册中...</span> <span v-else>注册中...</span>
</el-button> </el-button>
<div style="float: right;"> <router-link class="link-type" :to="'/login'">返回登录</router-link>
<router-link class="link-type" :to="'/login'">使用已有账户登录</router-link>
</div>
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 底部 --> <div class="el-register-footer"><span>{{ footerContent }}</span></div>
<div class="el-register-footer"> </main>
<span>{{ footerContent }}</span> <section class="register-visual">
</div> <div class="visual-brand"><span>CMVR / BID</span><strong>{{ title }}</strong></div>
<div class="visual-copy"><span>CONTROL ENVIRONMENT</span><h1>统一测试运营<br>与设备控制</h1></div>
<div class="visual-index"><span>FLOW</span><span>INSPECTION</span><span>CORPUS</span></div>
</section>
</div> </div>
</template> </template>
@ -156,24 +158,61 @@ getCode()
<style lang='scss' scoped> <style lang='scss' scoped>
.register { .register {
display: flex; display: grid;
justify-content: center; grid-template-columns: minmax(440px, 36vw) minmax(0, 1fr);
align-items: center;
height: 100%; height: 100%;
overflow: hidden;
background: var(--min-bg-page);
}
.register-visual {
position: relative;
isolation: isolate;
display: flex;
min-width: 0;
flex-direction: column;
justify-content: space-between;
padding: 42px 48px;
overflow: hidden;
background-image: url("../assets/images/login-background.jpg"); background-image: url("../assets/images/login-background.jpg");
background-position: center;
background-size: cover; background-size: cover;
} }
.title {
margin: 0px auto 30px auto; .register-visual::before {
text-align: center; content: '';
color: #707070; position: absolute;
inset: 0;
z-index: -1;
background: rgba(255, 255, 255, 0.68);
}
.register-access {
position: relative;
display: flex;
min-width: 0;
align-items: center;
justify-content: center;
padding: 56px 48px 68px;
background: var(--min-bg-white);
}
.access-index {
display: flex;
position: absolute;
top: 28px;
right: 32px;
align-items: center;
gap: 10px;
color: var(--min-text-secondary);
font-size: 10px;
strong { color: var(--min-primary); font-size: 12px; font-weight: 600; }
} }
.register-form { .register-form {
border-radius: 6px; width: min(360px, 100%);
background: #ffffff;
width: 400px;
padding: 25px 25px 5px 25px;
.el-input { .el-input {
height: 40px; height: 40px;
input { input {
@ -186,10 +225,42 @@ getCode()
margin-left: 0px; margin-left: 0px;
} }
} }
.form-heading {
margin-bottom: 32px;
> span { color: var(--min-primary); font-size: 10px; font-weight: 600; }
h2 { margin: 8px 0 5px; color: var(--min-text-primary); font-size: 28px; font-weight: 600; }
p { margin: 0; color: var(--min-text-secondary); font-size: 12px; }
}
.visual-brand {
display: flex;
align-items: center;
gap: 18px;
span { padding-right: 18px; border-right: 2px solid var(--min-primary); color: var(--min-primary); font-size: 11px; font-weight: 600; }
strong { max-width: 420px; color: var(--min-text-primary); font-size: 14px; font-weight: 600; }
}
.visual-copy {
max-width: 620px;
span { color: var(--min-primary); font-size: 10px; font-weight: 600; }
h1 { margin: 14px 0 0; color: var(--min-text-primary); font-size: 52px; font-weight: 600; line-height: 1.14; }
}
.visual-index {
display: flex;
flex-wrap: wrap;
span { min-width: 108px; padding: 10px 14px; border: 1px solid rgba(31, 41, 55, 0.28); border-right: 0; background: rgba(255, 255, 255, 0.52); color: var(--min-text-regular); font-size: 11px; }
span:last-child { border-right: 1px solid rgba(31, 41, 55, 0.28); }
}
.register-tip { .register-tip {
font-size: 13px; font-size: 13px;
text-align: center; text-align: center;
color: #bfbfbf; color: var(--min-text-secondary);
} }
.register-code { .register-code {
width: 33%; width: 33%;
@ -201,19 +272,43 @@ getCode()
} }
} }
.el-register-footer { .el-register-footer {
height: 40px; position: absolute;
line-height: 40px; right: 32px;
position: fixed; bottom: 20px;
bottom: 0; color: var(--min-text-secondary);
width: 100%; font-family: var(--min-font-family);
text-align: center;
color: #fff;
font-family: Arial;
font-size: 12px; font-size: 12px;
letter-spacing: 1px; letter-spacing: 0;
} }
.register-actions :deep(.el-form-item__content) { display: flex; align-items: center; justify-content: space-between; gap: 20px; }
.register-actions .el-button { flex: 1; margin: 0; }
.register-actions .link-type { white-space: nowrap; }
.register-code-img { .register-code-img {
height: 40px; height: 40px;
padding-left: 12px; padding-left: 12px;
} }
@media (max-width: 520px) {
.register {
grid-template-columns: 1fr;
grid-template-rows: 220px minmax(0, 1fr);
}
.register-visual {
grid-row: 1;
padding: 24px;
}
.visual-brand strong,
.visual-index { display: none; }
.visual-copy h1 { font-size: 28px; }
.register-access { grid-row: 2; padding: 54px 24px 60px; align-items: flex-start; }
.register-form {
width: 100%;
}
.form-heading h2 { font-size: 24px; }
.el-register-footer { right: 24px; left: 24px; overflow: hidden; text-align: center; text-overflow: ellipsis; white-space: nowrap; }
}
</style> </style>