refactor(layout): 调整布局样式并优化轮播卡片组件
- 更新AppMain.vue中的导航栏高度从50px调整为60px
- 替换轮播卡片组件中的图片箭头为SVG图标并添加aria标签
- 移除卡片图标背景图,使用CSS变量替代硬编码颜色
- 将轮播卡片组件的样式统一到BI设计系统变量
- 重构数据仪表板中的饼图组件,添加中心数值显示
- 优化生产分析页面的进度表格样式和颜色配置
- 调整PSI数据分析页面的图表配色和轴线样式
- 删除已废弃的质量分析轮播卡片组件文件
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // æè²å¤§å±è®¾è®¡ä»¤çï¼æ·±èåçé£ï¼å¼åºç»å½é¡µ #09183a æé¨ + åçè #2c51d9ï¼ |
| | | // ç¨æ³ï¼å¤§å±æ ¹å®¹å¨æ .dashboard-dark ç±»ï¼å
é¨ç¨ .bi-panel / .bi-title-bar çå·¥å
·ç±» |
| | | // ä¾ reportAnalysis ä¸ 1920x1080 缩æ¾å¼å¤§å±å¤ç¨ï¼ECharts é
è²è§åé¡µé¢ theme.js |
| | | .dashboard-dark { |
| | | --bi-bg-from: #0a1120; |
| | | --bi-bg-to: #0e1a33; |
| | | --bi-surface: #111c33; |
| | | --bi-surface-2: #16233d; |
| | | --bi-border: #1f2f4d; |
| | | --bi-divider: rgba(159, 176, 207, 0.14); |
| | | --bi-text: #e8eefc; |
| | | --bi-text-secondary: #9fb0cf; |
| | | --bi-text-tertiary: #64748f; |
| | | --bi-primary: #2c51d9; |
| | | --bi-accent: #5b8cff; |
| | | --bi-success: #2dbd85; |
| | | --bi-warning: #f5a524; |
| | | --bi-danger: #f2635b; |
| | | --bi-radius: 10px; |
| | | |
| | | // ç»ä¸é¢æ¿å®¹å¨ï¼æ¿ä»£æ¨¡æ¿å¾çè¾¹æ¡/éè²æè¾¹ï¼ |
| | | .bi-panel { |
| | | background: var(--bi-surface); |
| | | border: 1px solid var(--bi-border); |
| | | border-radius: var(--bi-radius); |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | // 颿¿æ é¢ï¼å·¦ç«æ¡ + åºé¨ç»åé线ï¼ç»æå¯¹é½é¦é¡µ HomePanel |
| | | .bi-title-bar { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | height: 36px; |
| | | padding: 0 12px; |
| | | font-weight: 600; |
| | | font-size: 16px; |
| | | color: var(--bi-text); |
| | | border-bottom: 1px solid var(--bi-divider); |
| | | |
| | | &::before { |
| | | content: ''; |
| | | width: 3px; |
| | | height: 16px; |
| | | border-radius: 2px; |
| | | background: var(--bi-accent); |
| | | flex-shrink: 0; |
| | | } |
| | | } |
| | | |
| | | // ç宽æ°åï¼é£æ ¼å¯¹é½ .app-num |
| | | .bi-num { |
| | | font-variant-numeric: tabular-nums; |
| | | font-feature-settings: 'tnum'; |
| | | } |
| | | } |
| | |
| | | @import './tokens.scss';
|
| | | @import './dashboard-dark.scss';
|
| | | @import './variables.module.scss';
|
| | | @import './mixin.scss';
|
| | | @import './transition.scss';
|
| | |
| | | left: 0;
|
| | | z-index: 1001;
|
| | | overflow: hidden;
|
| | | -webkit-box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
|
| | | box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
| | | -webkit-box-shadow: 2px 0 8px rgba(0, 21, 41, 0.12);
|
| | | box-shadow: 2px 0 8px rgba(0, 21, 41, 0.12);
|
| | |
|
| | | // reset element-ui css
|
| | | .horizontal-collapse-transition {
|
| | |
| | |
|
| | | &.has-logo {
|
| | | .el-scrollbar {
|
| | | height: calc(100% - 50px);
|
| | | height: calc(100% - 60px);
|
| | | }
|
| | | }
|
| | |
|
| | |
| | | display: inline-block !important;
|
| | | }
|
| | |
|
| | | // menu hover
|
| | | // menu hoverï¼ç»ä¸èµ°æ·±åºäº®è² token
|
| | | .sub-menu-title-noDropdown,
|
| | | .el-sub-menu__title {
|
| | | &:hover {
|
| | | background-color: rgba(212, 221, 255, 0.8) !important;
|
| | | background-color: var(--menu-hover) !important;
|
| | | }
|
| | | }
|
| | | & .theme-light .is-active > .el-sub-menu__title {
|
| | | color: #fff !important;
|
| | |
|
| | | .is-active > .el-sub-menu__title {
|
| | | color: var(--menu-active-text) !important;
|
| | | }
|
| | |
|
| | | & .nest-menu .el-sub-menu > .el-sub-menu__title,
|
| | | & .el-sub-menu .el-menu-item {
|
| | | min-width: $base-sidebar-width !important;
|
| | | background-color: var(--sidebar-sub-bg) !important;
|
| | |
|
| | | &:hover {
|
| | | background-color: rgba(212, 221, 255, 0.8) !important;
|
| | | background-color: var(--menu-hover) !important;
|
| | | }
|
| | | &.is-active {
|
| | | background-color: #fff !important;
|
| | | }
|
| | | }
|
| | |
|
| | | & .theme-light .nest-menu .el-sub-menu > .el-sub-menu__title,
|
| | | & .theme-light .el-sub-menu .el-menu-item {
|
| | | //background-color: transparent;
|
| | |
|
| | | &:hover {
|
| | | background-color: rgba(212, 221, 255, 0.8) !important;
|
| | | background-color: var(--menu-active-bg) !important;
|
| | | color: var(--menu-active-text) !important;
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | .hideSidebar {
|
| | | .sidebar-container {
|
| | | width: 54px !important;
|
| | | width: 64px !important;
|
| | | }
|
| | |
|
| | | .main-container {
|
| | | margin-left: 54px;
|
| | | margin-left: 64px;
|
| | | }
|
| | |
|
| | | // æå æèå项æ¹åè§åã徿 å±
ä¸
|
| | | .el-menu-item,
|
| | | .el-sub-menu__title {
|
| | | margin: 4px 8px;
|
| | | border-radius: 8px;
|
| | | text-align: center;
|
| | | }
|
| | |
|
| | | .sub-menu-title-noDropdown {
|
| | |
| | | padding: 0 !important;
|
| | |
|
| | | .svg-icon {
|
| | | margin-left: 20px;
|
| | | margin-left: 16px;
|
| | | }
|
| | | }
|
| | | }
|
| | |
| | | padding: 0 !important;
|
| | |
|
| | | .svg-icon {
|
| | | margin-left: 20px;
|
| | | margin-left: 16px;
|
| | | }
|
| | | }
|
| | | }
|
| | |
| | | }
|
| | | }
|
| | |
|
| | | // æå å¼¹åºçäºçº§èåï¼è·éä¾§æ æ·±åº
|
| | | .nest-menu .el-sub-menu > .el-sub-menu__title,
|
| | | .el-menu-item {
|
| | | &:hover {
|
| | | // you can use $sub-menuHover
|
| | | background-color: rgba(212, 221, 255, 0.56) !important;
|
| | | background-color: var(--menu-hover) !important;
|
| | | }
|
| | | &.is-active {
|
| | | background-color: rgba(212, 221, 255, 0.56) !important;
|
| | | background-color: var(--menu-active-bg) !important;
|
| | | color: var(--menu-active-text) !important;
|
| | | }
|
| | | }
|
| | |
|
| | |
| | | > .el-menu--popup {
|
| | | max-height: 100vh;
|
| | | overflow-y: auto;
|
| | | background-color: var(--sidebar-bg) !important;
|
| | |
|
| | | &::-webkit-scrollbar-track-piece {
|
| | | background: #d3dce6;
|
| | | background: transparent;
|
| | | }
|
| | |
|
| | | &::-webkit-scrollbar {
|
| | |
| | | }
|
| | |
|
| | | &::-webkit-scrollbar-thumb {
|
| | | background: #99a9bf;
|
| | | background: rgba(255, 255, 255, 0.18);
|
| | | border-radius: 20px;
|
| | | }
|
| | | }
|
| | |
| | | $menuLightActiveText: #002fa7;
|
| | |
|
| | | // åºç¡åé
|
| | | $base-sidebar-width: 200px;
|
| | | $sideBarWidth: 200px;
|
| | | $base-sidebar-width: 220px;
|
| | | $sideBarWidth: 220px;
|
| | |
|
| | | // èåæè²åé
|
| | | $base-menu-color: #bfcbd9;
|
| | |
| | |
|
| | | // CSSåéå®ä¹
|
| | | :root {
|
| | | /* äº®è²æ¨¡å¼åé */
|
| | | --sidebar-bg: #{$menuBg};
|
| | | --sidebar-text: #{$menuText};
|
| | | --menu-hover: #{$menuHover};
|
| | | /* ä¾§è¾¹æ ï¼ç§æèæ¸åï¼å¼åºç»å½é¡µ hero å®èâæ·±èï¼ */
|
| | | --sidebar-bg: #1b3a94;
|
| | | --sidebar-sub-bg: rgba(0, 0, 0, 0.16);
|
| | | --sidebar-grad-from: #2f57e0;
|
| | | --sidebar-grad-to: #14264f;
|
| | | --sidebar-text: rgba(255, 255, 255, 0.72);
|
| | | --menu-hover: rgba(255, 255, 255, 0.10);
|
| | | --menu-active-bg: rgba(255, 255, 255, 0.18);
|
| | | --menu-active-text: #ffffff;
|
| | | --menu-active-indicator: #ffffff;
|
| | |
|
| | | --navbar-bg: #ffffff;
|
| | | --navbar-text: #303133;
|
| | | --navbar-border: #e8ecf1;
|
| | |
|
| | | /* é¡µç¾æ */
|
| | | --tags-bg: #ffffff;
|
| | | --tags-item-bg: #ffffff;
|
| | | --tags-item-border: #dfe5ee;
|
| | | --tags-item-text: #4e5969;
|
| | | --tags-item-hover: #f2f5fa;
|
| | |
|
| | | /* splitpanes default-theme åé */
|
| | | --splitpanes-default-bg: #ffffff;
|
| | |
| | |
|
| | | /* ä¾§è¾¹æ */
|
| | | --sidebar-bg: #141414;
|
| | | --sidebar-sub-bg: #1d1e1f;
|
| | | --sidebar-grad-from: #1d1e1f;
|
| | | --sidebar-grad-to: #141414;
|
| | | --sidebar-text: #ffffff;
|
| | | --menu-hover: #2d2d2d;
|
| | | --menu-active-bg: #2d2d2d;
|
| | | --menu-active-text: #{$menuActiveText};
|
| | | --menu-active-indicator: #{$menuActiveText};
|
| | |
|
| | | /* é¡¶é¨å¯¼èªæ */
|
| | | --navbar-bg: #141414;
|
| | | --navbar-text: #ffffff;
|
| | | --navbar-hover: #141414;
|
| | | --navbar-border: #303030;
|
| | |
|
| | | /* æ ç¾æ */
|
| | | --tags-bg: #141414;
|
| | |
| | |
|
| | | <style lang="scss" scoped>
|
| | | .app-main {
|
| | | /* 50= navbar 50 */
|
| | | min-height: calc(100vh - 50px);
|
| | | /* 60 = navbar */
|
| | | min-height: calc(100vh - 60px);
|
| | | width: 100%;
|
| | | position: relative;
|
| | | overflow: hidden;
|
| | |
| | | }
|
| | |
|
| | | .fixed-header + .app-main {
|
| | | padding-top: 50px;
|
| | | padding-top: 60px;
|
| | | }
|
| | |
|
| | | .hasTagsView {
|
| | | .app-main {
|
| | | /* 84 = navbar + tags-view = 50 + 34 */
|
| | | min-height: calc(100vh - 84px);
|
| | | /* 100 = navbar + tags-view = 60 + 40 */
|
| | | min-height: calc(100vh - 100px);
|
| | | }
|
| | |
|
| | | .fixed-header + .app-main {
|
| | | padding-top: 84px;
|
| | | padding-top: 100px;
|
| | | }
|
| | | }
|
| | | </style>
|
| | |
| | | <template>
|
| | | <div class="navbar">
|
| | | <div>
|
| | | <div class="left-menu">
|
| | | <hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container"
|
| | | @toggleClick="toggleSideBar" />
|
| | | <breadcrumb v-if="!settingsStore.topNav" id="breadcrumb-container" class="breadcrumb-container" />
|
| | | </div>
|
| | | <!-- <top-nav v-if="settingsStore.topNav" id="topmenu-container" class="topmenu-container" />-->
|
| | | <div class="right-menu">
|
| | | <!-- æ¶æ¯éç¥ -->
|
| | | <el-popover
|
| | |
| | | <template #reference>
|
| | | <div class="notification-container right-menu-item hover-effect">
|
| | | <el-badge :value="unreadCount" :hidden="unreadCount === 0" class="notification-badge">
|
| | | <el-icon :size="20" style="cursor: pointer;">
|
| | | <el-icon :size="20">
|
| | | <Bell />
|
| | | </el-icon>
|
| | | </el-badge>
|
| | |
| | | ref="notificationCenterRef"
|
| | | />
|
| | | </el-popover>
|
| | | <div class="avatar-container">
|
| | | <el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
|
| | | <div class="avatar-wrapper">
|
| | | <img :src="userStore.avatar" class="user-avatar" />
|
| | | <el-icon><caret-bottom /></el-icon>
|
| | | </div>
|
| | | <template #dropdown>
|
| | | <el-dropdown-menu>
|
| | | <router-link to="/user/profile">
|
| | | <el-dropdown-item>个人ä¸å¿</el-dropdown-item>
|
| | | </router-link>
|
| | | <el-dropdown-item command="setLayout" v-if="settingsStore.showSettings">
|
| | | <span>å¸å±è®¾ç½®</span>
|
| | | </el-dropdown-item>
|
| | | <el-dropdown-item divided command="logout">
|
| | | <span>éåºç»å½</span>
|
| | | </el-dropdown-item>
|
| | | </el-dropdown-menu>
|
| | | </template>
|
| | | </el-dropdown>
|
| | | </div>
|
| | | <el-dropdown @command="handleCommand" trigger="click">
|
| | | <div class="avatar-container right-menu-item hover-effect">
|
| | | <img :src="userStore.avatar" class="user-avatar" />
|
| | | <span class="user-name">{{ userStore.nickName || userStore.name }}</span>
|
| | | <el-icon class="user-caret"><caret-bottom /></el-icon>
|
| | | </div>
|
| | | <template #dropdown>
|
| | | <el-dropdown-menu>
|
| | | <router-link to="/user/profile">
|
| | | <el-dropdown-item>个人ä¸å¿</el-dropdown-item>
|
| | | </router-link>
|
| | | <el-dropdown-item command="setLayout" v-if="settingsStore.showSettings">
|
| | | <span>å¸å±è®¾ç½®</span>
|
| | | </el-dropdown-item>
|
| | | <el-dropdown-item divided command="logout">
|
| | | <span>éåºç»å½</span>
|
| | | </el-dropdown-item>
|
| | | </el-dropdown-menu>
|
| | | </template>
|
| | | </el-dropdown>
|
| | | </div>
|
| | | </div>
|
| | | </template>
|
| | |
| | | import { ElMessageBox } from 'element-plus'
|
| | | import { Bell } from '@element-plus/icons-vue'
|
| | | import Breadcrumb from '@/components/Breadcrumb'
|
| | | import TopNav from '@/components/TopNav'
|
| | | import Hamburger from '@/components/Hamburger'
|
| | | import Screenfull from '@/components/Screenfull'
|
| | | import SizeSelect from '@/components/SizeSelect'
|
| | | import HeaderSearch from '@/components/HeaderSearch'
|
| | | import RuoYiGit from '@/components/RuoYi/Git'
|
| | | import RuoYiDoc from '@/components/RuoYi/Doc'
|
| | | import NotificationCenter from './NotificationCenter/index.vue'
|
| | | import useAppStore from '@/store/modules/app'
|
| | | import useUserStore from '@/store/modules/user'
|
| | |
| | | emits('setLayout')
|
| | | }
|
| | |
|
| | | function toggleTheme() {
|
| | | settingsStore.toggleTheme()
|
| | | }
|
| | |
|
| | | // æ¶æ¯éç¥ç¸å
³
|
| | | function handleUnreadCountChange(count) {
|
| | | unreadCount.value = count
|
| | |
| | |
|
| | | <style lang='scss' scoped>
|
| | | .navbar {
|
| | | height: 50px;
|
| | | overflow: hidden;
|
| | | height: 60px;
|
| | | position: relative;
|
| | | display: flex;
|
| | | align-items: center;
|
| | | justify-content: space-between;
|
| | | padding-right: 16px;
|
| | | background: var(--navbar-bg);
|
| | | box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
| | | border-bottom: 1px solid var(--navbar-border);
|
| | |
|
| | | .left-menu {
|
| | | display: flex;
|
| | | align-items: center;
|
| | | height: 100%;
|
| | | }
|
| | |
|
| | | .hamburger-container {
|
| | | line-height: 46px;
|
| | | display: flex;
|
| | | align-items: center;
|
| | | height: 100%;
|
| | | float: left;
|
| | | padding: 0 14px;
|
| | | cursor: pointer;
|
| | | transition: background 0.3s;
|
| | | transition: background 0.2s;
|
| | | -webkit-tap-highlight-color: transparent;
|
| | |
|
| | | &:hover {
|
| | | background: rgba(0, 0, 0, 0.025);
|
| | | background: var(--app-surface-hover);
|
| | | }
|
| | | }
|
| | |
|
| | | .breadcrumb-container {
|
| | | float: left;
|
| | | }
|
| | |
|
| | | .topmenu-container {
|
| | | position: absolute;
|
| | | left: 50px;
|
| | | }
|
| | |
|
| | | .errLog-container {
|
| | | display: inline-block;
|
| | | vertical-align: top;
|
| | | }
|
| | |
|
| | | .right-menu {
|
| | | float: right;
|
| | | height: 100%;
|
| | | line-height: 50px;
|
| | | display: flex;
|
| | | align-items: center;
|
| | | gap: 6px;
|
| | | height: 100%;
|
| | |
|
| | | &:focus {
|
| | | outline: none;
|
| | | }
|
| | |
|
| | | // æ¯ä¸ªæé®ç»ä¸çåè§æ¬ååº
|
| | | .right-menu-item {
|
| | | display: inline-block;
|
| | | padding: 0 8px;
|
| | | height: 100%;
|
| | | display: flex;
|
| | | align-items: center;
|
| | | height: 40px;
|
| | | padding: 0 12px;
|
| | | font-size: 18px;
|
| | | color: var(--navbar-text);
|
| | | vertical-align: text-bottom;
|
| | | border-radius: 10px;
|
| | | cursor: pointer;
|
| | | transition: background 0.2s;
|
| | |
|
| | | &.hover-effect {
|
| | | cursor: pointer;
|
| | | transition: background 0.3s;
|
| | |
|
| | | &:hover {
|
| | | background: rgba(0, 0, 0, 0.025);
|
| | | }
|
| | | }
|
| | |
|
| | | &.theme-switch-wrapper {
|
| | | display: flex;
|
| | | align-items: center;
|
| | |
|
| | | svg {
|
| | | transition: transform 0.3s;
|
| | |
|
| | | &:hover {
|
| | | transform: scale(1.15);
|
| | | }
|
| | | }
|
| | | &.hover-effect:hover {
|
| | | background: var(--app-surface-hover);
|
| | | }
|
| | | }
|
| | |
|
| | | .notification-container {
|
| | | margin-right: 20px;
|
| | | .notification-badge {
|
| | | display: flex;
|
| | | align-items: center;
|
| | | cursor: pointer;
|
| | |
|
| | | .notification-badge {
|
| | | :deep(.el-badge__content) {
|
| | | border: none;
|
| | | }
|
| | | :deep(.el-badge__content) {
|
| | | border: none;
|
| | | }
|
| | | }
|
| | |
|
| | | .avatar-container {
|
| | | margin-right: 40px;
|
| | | gap: 8px;
|
| | |
|
| | | .avatar-wrapper {
|
| | | margin-top: 5px;
|
| | | position: relative;
|
| | | .user-avatar {
|
| | | width: 32px;
|
| | | height: 32px;
|
| | | border-radius: 50%;
|
| | | object-fit: cover;
|
| | | background: var(--app-surface-hover);
|
| | | }
|
| | |
|
| | | .user-avatar {
|
| | | cursor: pointer;
|
| | | width: 40px;
|
| | | height: 40px;
|
| | | border-radius: 50px;
|
| | | }
|
| | | .user-name {
|
| | | font-size: 14px;
|
| | | font-weight: 500;
|
| | | color: var(--app-text);
|
| | | max-width: 120px;
|
| | | overflow: hidden;
|
| | | text-overflow: ellipsis;
|
| | | white-space: nowrap;
|
| | | }
|
| | |
|
| | | i {
|
| | | cursor: pointer;
|
| | | position: absolute;
|
| | | right: -20px;
|
| | | top: 14px;
|
| | | font-size: 12px;
|
| | | }
|
| | | .user-caret {
|
| | | font-size: 12px;
|
| | | color: var(--app-text-tertiary);
|
| | | }
|
| | | }
|
| | | }
|
| | |
| | | <template>
|
| | | <div class="sidebar-logo-container" :class="{ 'collapse': collapse }">
|
| | | <transition name="sidebarLogoFade">
|
| | | <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
|
| | | <img v-if="logoUrl" :src="logoUrl" class="sidebar-logo" @error="handleImageError" alt="å
¬å¸Logo" />
|
| | | <h1 class="sidebar-title">{{ title }}</h1>
|
| | | </router-link>
|
| | | <router-link v-else key="expand" class="sidebar-logo-link" to="/">
|
| | | <img v-if="logoUrl" :src="logoUrl" class="sidebar-logo" @error="handleImageError" alt="å
¬å¸Logo" />
|
| | | <h1 class="sidebar-title">{{ title }}</h1>
|
| | | </router-link>
|
| | | </transition>
|
| | | </div>
|
| | | </template>
|
| | |
|
| | | <script setup>
|
| | | import { ref, computed, onMounted, watch } from 'vue'
|
| | | import useUserStore from '@/store/modules/user'
|
| | | import defaultLogo from '@/assets/logo/logo.png' // 导å
¥é»è®¤logo
|
| | |
|
| | | defineProps({
|
| | | collapse: {
|
| | | type: Boolean,
|
| | | required: true
|
| | | }
|
| | | })
|
| | |
|
| | | const title = import.meta.env.VITE_APP_TITLE
|
| | | const userStore = useUserStore()
|
| | |
|
| | | // å¤çå·¥ååç§°ï¼çæåæ³çæä»¶å
|
| | | const cleanFactoryName = computed(() => {
|
| | | if (!userStore.currentFactoryName) return ''
|
| | | return userStore.currentFactoryName.trim()
|
| | | })
|
| | |
|
| | | // 卿logoè·¯å¾
|
| | | const logoUrl = ref('')
|
| | |
|
| | | // æ£æ¥logoæ¯å¦åå¨å¹¶è®¾ç½®url
|
| | | const updateLogoUrl = () => {
|
| | | if (!cleanFactoryName.value) {
|
| | | logoUrl.value = defaultLogo
|
| | | return
|
| | | }
|
| | |
|
| | | // 使ç¨Viteç卿坼å
¥
|
| | | try {
|
| | | const dynamicLogo = import.meta.glob('/src/assets/logo/*.png', { eager: true })
|
| | | const logoPath = `/src/assets/logo/${cleanFactoryName.value}.png`
|
| | |
|
| | | if (dynamicLogo[logoPath]) {
|
| | | logoUrl.value = dynamicLogo[logoPath].default
|
| | | } else {
|
| | | logoUrl.value = defaultLogo
|
| | | }
|
| | | } catch (error) {
|
| | | console.error('å 载工åLogo失败:', error)
|
| | | logoUrl.value = defaultLogo
|
| | | }
|
| | | }
|
| | |
|
| | | // åå§ååçå¬åå
|
| | | onMounted(() => {
|
| | | updateLogoUrl()
|
| | |
|
| | | // çå¬å·¥ååç§°åå
|
| | | watch(() => userStore.currentFactoryName, updateLogoUrl)
|
| | | })
|
| | |
|
| | | // å¾çå è½½é误å¤ç
|
| | | const handleImageError = (event) => {
|
| | | console.warn('Logoå 载失败ï¼ä½¿ç¨é»è®¤Logo')
|
| | | logoUrl.value = defaultLogo
|
| | | }
|
| | | </script>
|
| | |
|
| | | <style lang="scss" scoped>
|
| | | @import '@/assets/styles/variables.module.scss';
|
| | |
|
| | | .sidebarLogoFade-enter-active {
|
| | | transition: opacity 1.5s;
|
| | | }
|
| | |
|
| | | .sidebarLogoFade-enter,
|
| | | .sidebarLogoFade-leave-to {
|
| | | opacity: 0;
|
| | | }
|
| | |
|
| | | .sidebar-logo-container {
|
| | | position: relative;
|
| | | width: 100% !important;
|
| | | height: 50px !important;
|
| | | line-height: 50px;
|
| | | background: #fff;
|
| | | text-align: center;
|
| | | overflow: hidden;
|
| | |
|
| | | & .sidebar-logo-link {
|
| | | height: 100%;
|
| | | width: 100%;
|
| | |
|
| | | & .sidebar-logo {
|
| | | width: 100%;
|
| | | height: 100%;
|
| | | // height: 32px;
|
| | | vertical-align: middle;
|
| | | margin-right: 12px;
|
| | | }
|
| | |
|
| | | & .sidebar-title {
|
| | | display: inline-block;
|
| | | margin: 0;
|
| | | color: v-bind(getLogoTextColor);
|
| | | font-weight: 600;
|
| | | line-height: 50px;
|
| | | font-size: 14px;
|
| | | font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
|
| | | vertical-align: middle;
|
| | | }
|
| | | }
|
| | |
|
| | | &.collapse {
|
| | | .sidebar-logo {
|
| | | margin-right: 0px;
|
| | | }
|
| | | }
|
| | | }
|
| | | </style>
|
| | | <template> |
| | | <div class="sidebar-logo-container" :class="{ 'collapse': collapse }"> |
| | | <transition name="sidebarLogoFade"> |
| | | <router-link key="logo" class="sidebar-logo-link" to="/"> |
| | | <span class="sidebar-logo-patch"> |
| | | <img v-if="logoUrl" :src="logoUrl" class="sidebar-logo" @error="handleImageError" alt="å
¬å¸Logo" /> |
| | | </span> |
| | | </router-link> |
| | | </transition> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted, watch } from 'vue' |
| | | import useUserStore from '@/store/modules/user' |
| | | import defaultLogo from '@/assets/login/JCCKLogo.png' // ä¸ç»å½é¡µä¿æä¸è´çåç Logo |
| | | |
| | | defineProps({ |
| | | collapse: { |
| | | type: Boolean, |
| | | required: true |
| | | } |
| | | }) |
| | | |
| | | const userStore = useUserStore() |
| | | |
| | | // å¤çå·¥ååç§°ï¼çæåæ³çæä»¶å |
| | | const cleanFactoryName = computed(() => { |
| | | if (!userStore.currentFactoryName) return '' |
| | | return userStore.currentFactoryName.trim() |
| | | }) |
| | | |
| | | // 卿logoè·¯å¾ |
| | | const logoUrl = ref('') |
| | | |
| | | // æ£æ¥logoæ¯å¦åå¨å¹¶è®¾ç½®url |
| | | const updateLogoUrl = () => { |
| | | if (!cleanFactoryName.value) { |
| | | logoUrl.value = defaultLogo |
| | | return |
| | | } |
| | | |
| | | // 使ç¨Viteç卿坼å
¥ |
| | | try { |
| | | const dynamicLogo = import.meta.glob('/src/assets/logo/*.png', { eager: true }) |
| | | const logoPath = `/src/assets/logo/${cleanFactoryName.value}.png` |
| | | |
| | | if (dynamicLogo[logoPath]) { |
| | | logoUrl.value = dynamicLogo[logoPath].default |
| | | } else { |
| | | logoUrl.value = defaultLogo |
| | | } |
| | | } catch (error) { |
| | | console.error('å 载工åLogo失败:', error) |
| | | logoUrl.value = defaultLogo |
| | | } |
| | | } |
| | | |
| | | // åå§ååçå¬åå |
| | | onMounted(() => { |
| | | updateLogoUrl() |
| | | |
| | | // çå¬å·¥ååç§°åå |
| | | watch(() => userStore.currentFactoryName, updateLogoUrl) |
| | | }) |
| | | |
| | | // å¾çå è½½é误å¤ç |
| | | const handleImageError = (event) => { |
| | | console.warn('Logoå 载失败ï¼ä½¿ç¨é»è®¤Logo') |
| | | logoUrl.value = defaultLogo |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .sidebarLogoFade-enter-active { |
| | | transition: opacity 1.5s; |
| | | } |
| | | |
| | | .sidebarLogoFade-enter, |
| | | .sidebarLogoFade-leave-to { |
| | | opacity: 0; |
| | | } |
| | | |
| | | .sidebar-logo-container { |
| | | position: relative; |
| | | width: 100%; |
| | | height: 60px; |
| | | background: transparent; |
| | | overflow: hidden; |
| | | |
| | | & .sidebar-logo-link { |
| | | display: flex; |
| | | align-items: center; |
| | | height: 100%; |
| | | width: 100%; |
| | | padding: 7px 10px; |
| | | box-sizing: border-box; |
| | | |
| | | // logo åå¾èªå¸¦ç½åºï¼ç¨ç½åè§è´´çå¡«æ»¡æ´æ¡ Logo æ |
| | | & .sidebar-logo-patch { |
| | | flex: 1; |
| | | width: 100%; |
| | | height: 100%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 2px 6px; |
| | | box-sizing: border-box; |
| | | background: #fff; |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | & .sidebar-logo { |
| | | display: block; |
| | | width: 100%; |
| | | height: 100%; |
| | | object-fit: contain; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <div :class="{ 'has-logo': showLogo }" class="sidebar-container">
|
| | | <logo v-if="showLogo" :collapse="isCollapse" />
|
| | | <el-scrollbar wrap-class="scrollbar-wrapper">
|
| | | <el-menu :default-active="activeMenu" :collapse="isCollapse" :background-color="getMenuBackground"
|
| | | :text-color="getMenuTextColor" :unique-opened="true" :active-text-color="theme" :collapse-transition="false"
|
| | | mode="vertical" :class="sideTheme">
|
| | | <el-menu :default-active="activeMenu" :collapse="isCollapse" background-color="transparent"
|
| | | text-color="var(--sidebar-text)" :unique-opened="true" active-text-color="var(--menu-active-text)"
|
| | | :collapse-transition="false" mode="vertical" :class="sideTheme">
|
| | | <sidebar-item v-for="(route, index) in sidebarRouters" :key="route.path + index" :item="route"
|
| | | :base-path="route.path" />
|
| | | </el-menu>
|
| | |
| | | <script setup>
|
| | | import Logo from './Logo'
|
| | | import SidebarItem from './SidebarItem'
|
| | | import variables from '@/assets/styles/variables.module.scss'
|
| | | import useAppStore from '@/store/modules/app'
|
| | | import useSettingsStore from '@/store/modules/settings'
|
| | | import usePermissionStore from '@/store/modules/permission'
|
| | |
| | | const sidebarRouters = computed(() => permissionStore.sidebarRouters)
|
| | | const showLogo = computed(() => settingsStore.sidebarLogo)
|
| | | const sideTheme = computed(() => settingsStore.sideTheme)
|
| | | const theme = computed(() => settingsStore.theme)
|
| | | const isCollapse = computed(() => !appStore.sidebar.opened)
|
| | |
|
| | | // è·åèåèæ¯è²
|
| | | const getMenuBackground = computed(() => {
|
| | | if (settingsStore.isDark) {
|
| | | return 'var(--sidebar-bg)'
|
| | | }
|
| | | // æµ
è²ä¸»é¢æ¶ï¼ç´æ¥ç¨ä¸»é¢è²
|
| | | return sideTheme.value === 'theme-dark' ? variables.menuBg : settingsStore.theme
|
| | | })
|
| | |
|
| | | // è·åèåæåé¢è²
|
| | | const getMenuTextColor = computed(() => {
|
| | | if (settingsStore.isDark) {
|
| | | return 'var(--sidebar-text)'
|
| | | }
|
| | | return sideTheme.value === 'theme-dark' ? variables.menuText : variables.menuLightText
|
| | | })
|
| | |
|
| | | const activeMenu = computed(() => {
|
| | | const { meta, path } = route
|
| | |
| | |
|
| | | <style lang="scss" scoped>
|
| | | .sidebar-container {
|
| | | background-color: v-bind(getMenuBackground);
|
| | | background: linear-gradient(180deg, var(--sidebar-grad-from) 0%, var(--sidebar-grad-to) 100%);
|
| | |
|
| | | .scrollbar-wrapper {
|
| | | background-color: v-bind(getMenuBackground);
|
| | | background: transparent;
|
| | | }
|
| | |
|
| | | .el-menu {
|
| | | border: none;
|
| | | height: 100%;
|
| | | width: 100% !important;
|
| | | --el-menu-item-height: 46px;
|
| | | --el-menu-sub-item-height: 42px;
|
| | | padding: 6px 0;
|
| | |
|
| | | .el-menu-item,
|
| | | .el-sub-menu__title {
|
| | | // åè§è¡å + hover/æ¿æ´»æ
|
| | | :deep(.el-menu-item),
|
| | | :deep(.el-sub-menu__title) {
|
| | | position: relative;
|
| | | margin: 3px 10px;
|
| | | border-radius: 8px;
|
| | | color: var(--sidebar-text);
|
| | | background-color: transparent;
|
| | |
|
| | | &:hover {
|
| | | background-color: var(--menu-hover, rgba(0, 0, 0, 0.06)) !important;
|
| | | background-color: var(--menu-hover) !important;
|
| | | color: var(--menu-active-text);
|
| | | }
|
| | | }
|
| | |
|
| | | .el-menu-item {
|
| | | color: v-bind(getMenuTextColor);
|
| | | :deep(.el-menu-item.is-active) {
|
| | | background-color: var(--menu-active-bg) !important;
|
| | | color: var(--menu-active-text) !important;
|
| | | font-weight: 600;
|
| | |
|
| | | &.is-active {
|
| | | color: var(--menu-active-text, #409eff);
|
| | | background-color: var(--menu-hover, rgba(0, 0, 0, 0.06)) !important;
|
| | | // è´´ä¾§æ å·¦ç¼ç亮èæç¤ºæ¡
|
| | | &::before {
|
| | | content: '';
|
| | | position: absolute;
|
| | | left: -10px;
|
| | | top: 9px;
|
| | | bottom: 9px;
|
| | | width: 3px;
|
| | | border-radius: 0 3px 3px 0;
|
| | | background: var(--menu-active-indicator);
|
| | | }
|
| | | }
|
| | |
|
| | | .el-sub-menu__title {
|
| | | color: v-bind(getMenuTextColor);
|
| | | // åèå屿´æ·±ä¸æ¡£åºè²
|
| | | :deep(.el-sub-menu .el-menu) {
|
| | | background-color: var(--sidebar-sub-bg) !important;
|
| | |
|
| | | .el-menu-item {
|
| | | background-color: transparent;
|
| | | }
|
| | |
|
| | | .el-menu-item.is-active {
|
| | | background-color: var(--menu-active-bg) !important;
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
| | |
| | | :class="isActive(tag) ? 'active' : ''"
|
| | | :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
|
| | | class="tags-view-item"
|
| | | :style="activeStyle(tag)"
|
| | | @click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
|
| | | @contextmenu.prevent="openMenu(tag, $event)"
|
| | | >
|
| | |
| | | import ScrollPane from './ScrollPane'
|
| | | import { getNormalPath } from '@/utils/ruoyi'
|
| | | import useTagsViewStore from '@/store/modules/tagsView'
|
| | | import useSettingsStore from '@/store/modules/settings'
|
| | | import usePermissionStore from '@/store/modules/permission'
|
| | |
|
| | | const visible = ref(false)
|
| | |
| | |
|
| | | const visitedViews = computed(() => useTagsViewStore().visitedViews)
|
| | | const routes = computed(() => usePermissionStore().routes)
|
| | | const theme = computed(() => useSettingsStore().theme)
|
| | |
|
| | | watch(route, () => {
|
| | | addTags()
|
| | |
| | |
|
| | | function isActive(r) {
|
| | | return r.path === route.path
|
| | | }
|
| | |
|
| | | function activeStyle(tag) {
|
| | | if (!isActive(tag)) return {}
|
| | | return {
|
| | | "background-color": theme.value,
|
| | | "border-color": theme.value
|
| | | }
|
| | | }
|
| | |
|
| | | function isAffix(tag) {
|
| | |
| | |
|
| | | <style lang="scss" scoped>
|
| | | .tags-view-container {
|
| | | height: 34px;
|
| | | height: 40px;
|
| | | width: 100%;
|
| | | background: transparent;
|
| | | background: var(--tags-bg);
|
| | | border-bottom: 1px solid var(--navbar-border);
|
| | |
|
| | | .tags-view-wrapper {
|
| | | .tags-view-item {
|
| | | display: inline-block;
|
| | | display: inline-flex;
|
| | | align-items: center;
|
| | | gap: 6px;
|
| | | position: relative;
|
| | | cursor: pointer;
|
| | | height: 32px;
|
| | | line-height: 32px;
|
| | | //border: 1px solid var(--tags-item-border, #d8dce5);
|
| | | color: #4E5463;
|
| | | background: #E5E7EA;
|
| | | padding: 0 16px;
|
| | | height: 26px;
|
| | | margin: 7px 0 7px 8px;
|
| | | padding: 0 12px;
|
| | | font-size: 12px;
|
| | | //margin-left: 5px;
|
| | | //margin-top: 4px;
|
| | | color: var(--tags-item-text);
|
| | | background: var(--tags-item-bg);
|
| | | border: 1px solid var(--tags-item-border);
|
| | | border-radius: 8px;
|
| | | transition: color 0.2s, background-color 0.2s, border-color 0.2s;
|
| | |
|
| | | //&:first-of-type {
|
| | | // margin-left: 8px;
|
| | | //}
|
| | | //
|
| | | //&:last-of-type {
|
| | | // margin-right: 15px;
|
| | | //}
|
| | | &:last-of-type {
|
| | | margin-right: 8px;
|
| | | }
|
| | |
|
| | | &:not(.active):hover {
|
| | | color: var(--app-primary);
|
| | | background: var(--tags-item-hover);
|
| | | }
|
| | |
|
| | | &.active {
|
| | | background-color: #FFFFFF !important;
|
| | | color: #2C51D9;
|
| | | color: var(--app-primary);
|
| | | background: var(--app-primary-soft);
|
| | | border-color: var(--app-primary);
|
| | | font-weight: 500;
|
| | | }
|
| | | }
|
| | | //.tags-view-item div {
|
| | | // transform: skew(12deg);
|
| | | // display: inline-block;
|
| | | //}
|
| | | }
|
| | |
|
| | | .contextmenu {
|
| | |
| | | position: absolute;
|
| | | list-style-type: none;
|
| | | padding: 5px 0;
|
| | | border-radius: 4px;
|
| | | border-radius: var(--app-radius-sm);
|
| | | font-size: 12px;
|
| | | font-weight: 400;
|
| | | color: var(--tags-item-text, #333);
|
| | | box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
|
| | | box-shadow: var(--app-shadow-md);
|
| | | border: 1px solid var(--el-border-color-light, #e4e7ed);
|
| | |
|
| | | li {
|
| | |
| | | }
|
| | |
|
| | | .hideSidebar .fixed-header {
|
| | | width: calc(100% - 54px);
|
| | | width: calc(100% - 64px);
|
| | | }
|
| | |
|
| | | .sidebarHide .fixed-header {
|
| | |
| | | /**
|
| | | * ä¾§è¾¹æ ä¸»é¢ æ·±è²ä¸»é¢theme-darkï¼æµ
è²ä¸»é¢theme-light
|
| | | */
|
| | | sideTheme: 'theme-light',
|
| | | sideTheme: 'theme-dark',
|
| | | /**
|
| | | * æ¯å¦ç³»ç»å¸å±é
ç½®
|
| | | */
|
| | |
| | | .safety-monitoring { |
| | | padding: 20px; |
| | | background-color: #f5f7fa; |
| | | min-height: calc(100vh - 84px); |
| | | min-height: calc(100vh - 100px); |
| | | } |
| | | |
| | | .monitoring-card, .control-card { |
| | |
| | | <style scoped lang="scss"> |
| | | .app-container { |
| | | background-color: #f5f7fa; |
| | | height: calc(100vh - 84px); |
| | | height: calc(100vh - 100px); |
| | | padding: 20px; |
| | | display: flex; |
| | | flex-direction: column; |
| | |
| | | <template> |
| | | <div class="carousel-cards"> |
| | | <button |
| | | <button |
| | | v-if="canScrollLeft" |
| | | class="nav-button nav-button-left" |
| | | aria-label="åå·¦æ»å¨" |
| | | @click="scrollLeftFn" |
| | | > |
| | | <img src="@/assets/BI/jiantou.png" alt="å·¦ç®å¤´" /> |
| | | <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| | | <path d="M15 18l-6-6 6-6"/> |
| | | </svg> |
| | | </button> |
| | | <div |
| | | class="cards-container" |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <button |
| | | <button |
| | | v-if="canScrollRight" |
| | | class="nav-button nav-button-right" |
| | | aria-label="å峿»å¨" |
| | | @click="scrollRightFn" |
| | | > |
| | | <img src="@/assets/BI/jiantou.png" alt="å³ç®å¤´" /> |
| | | <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| | | <path d="M9 6l6 6-6 6"/> |
| | | </svg> |
| | | </button> |
| | | </div> |
| | | </template> |
| | |
| | | position: absolute; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 32px; |
| | | height: 32px; |
| | | background: rgba(26, 88, 176, 0.6); |
| | | border: 1px solid rgba(26, 88, 176, 0.8); |
| | | width: 28px; |
| | | height: 28px; |
| | | background: var(--bi-panel-bg, rgba(20, 32, 66, 0.55)); |
| | | border: 1px solid var(--bi-panel-border, #1e3160); |
| | | border-radius: 50%; |
| | | color: var(--bi-text, #8fa3c8); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | cursor: pointer; |
| | | z-index: 10; |
| | | transition: all 0.3s ease; |
| | | transition: color 0.2s ease, border-color 0.2s ease; |
| | | padding: 0; |
| | | } |
| | | |
| | | .nav-button:hover { |
| | | background: rgba(26, 88, 176, 0.8); |
| | | transform: translateY(-50%) scale(1.1); |
| | | color: var(--bi-text-strong, #eaf1ff); |
| | | border-color: var(--bi-accent-bright, #4a6cf0); |
| | | } |
| | | |
| | | .nav-button-left { |
| | | left: -16px; |
| | | } |
| | | |
| | | .nav-button-left img { |
| | | width: 16px; |
| | | height: 16px; |
| | | transform: rotate(180deg); |
| | | left: -14px; |
| | | } |
| | | |
| | | .nav-button-right { |
| | | right: -16px; |
| | | } |
| | | |
| | | .nav-button-right img { |
| | | width: 16px; |
| | | height: 16px; |
| | | right: -14px; |
| | | } |
| | | |
| | | .card-item { |
| | |
| | | min-width: calc((100% - (var(--visible-count) - 1) * 12px) / var(--visible-count)); |
| | | display: flex; |
| | | align-items: center; |
| | | background: linear-gradient(269deg, rgba(27,57,126,0.13) 0%, rgba(33,137,206,0.33) 98.13%, #24AFF4 100%); |
| | | border-radius: 8px 8px 8px 8px; |
| | | background: var(--bi-panel-bg, rgba(20, 32, 66, 0.55)); |
| | | border: 1px solid var(--bi-panel-border, #1e3160); |
| | | border-left: 2px solid var(--bi-accent-bright, #4a6cf0); |
| | | border-radius: 6px; |
| | | padding: 12px 16px; |
| | | transition: all 0.3s ease; |
| | | transition: border-color 0.2s ease, background-color 0.2s ease; |
| | | } |
| | | |
| | | .card-item:hover { |
| | | transform: translateY(-2px); |
| | | background: rgba(33, 49, 96, 0.65); |
| | | border-color: var(--bi-accent-bright, #4a6cf0); |
| | | } |
| | | |
| | | .card-icon { |
| | |
| | | .card-label { |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #FFFFFF; |
| | | color: var(--bi-text, #8fa3c8); |
| | | margin-bottom: 4px; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | |
| | | gap: 6px; |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: rgba(255, 255, 255, 0.85); |
| | | color: var(--bi-text, #8fa3c8); |
| | | } |
| | | |
| | | .rate-label { |
| | |
| | | } |
| | | |
| | | .value-number { |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #FFFFFF; |
| | | font-weight: 500; |
| | | font-size: 16px; |
| | | color: var(--bi-text-strong, #eaf1ff); |
| | | line-height: 1; |
| | | font-variant-numeric: tabular-nums; |
| | | } |
| | | |
| | | .value-unit { |
| | | font-size: 14px; |
| | | color: #FFFFFF; |
| | | font-size: 12px; |
| | | color: var(--bi-text, #8fa3c8); |
| | | font-weight: 400; |
| | | } |
| | | </style> |
| | |
| | | const props = defineProps({ |
| | | modelValue: { |
| | | type: Number, |
| | | default: 1, // é»è®¤éä¸"å¨" |
| | | default: 3, // é»è®¤éä¸"å£åº¦" |
| | | }, |
| | | }) |
| | | |
| | |
| | | |
| | | /* æªéä¸ç¶æçæ ·å¼ */ |
| | | .date-type-switch :deep(.el-radio-button__inner) { |
| | | background-color: rgba(26, 88, 176, 0.3); |
| | | color: rgba(184, 200, 224, 0.8); |
| | | border-color: rgba(255, 255, 255, 0.2); |
| | | background-color: rgba(20, 32, 66, 0.55); |
| | | color: #8fa3c8; |
| | | border-color: #1e3160; |
| | | border-radius: 0; |
| | | padding: 6px 20px; |
| | | font-size: 14px; |
| | | transition: all 0.3s; |
| | | transition: color 0.2s ease, background-color 0.2s ease; |
| | | } |
| | | |
| | | /* 第ä¸ä¸ªæé®å·¦ä¾§åè§ */ |
| | | .date-type-switch :deep(.el-radio-button:first-child .el-radio-button__inner) { |
| | | border-top-left-radius: 4px; |
| | | border-bottom-left-radius: 4px; |
| | | border-top-left-radius: 6px; |
| | | border-bottom-left-radius: 6px; |
| | | } |
| | | |
| | | /* æåä¸ä¸ªæé®å³ä¾§åè§ */ |
| | | .date-type-switch :deep(.el-radio-button:last-child .el-radio-button__inner) { |
| | | border-top-right-radius: 4px; |
| | | border-bottom-right-radius: 4px; |
| | | border-top-right-radius: 6px; |
| | | border-bottom-right-radius: 6px; |
| | | } |
| | | |
| | | /* æé®ä¹é´çåé线 */ |
| | | .date-type-switch :deep(.el-radio-button:not(:last-child) .el-radio-button__inner) { |
| | | border-right: 1px solid rgba(255, 255, 255, 0.2); |
| | | border-right: 1px solid #1e3160; |
| | | } |
| | | |
| | | /* éä¸ç¶æçæ ·å¼ */ |
| | | /* éä¸ç¶æçæ ·å¼ï¼åçèå®è² */ |
| | | .date-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) { |
| | | background: linear-gradient(180deg, #3378ff 0%, #00a4ed 100%); |
| | | background: #2c51d9; |
| | | color: #ffffff; |
| | | border-color: rgba(51, 120, 255, 0.8); |
| | | border-color: #2c51d9; |
| | | box-shadow: none; |
| | | } |
| | | |
| | | /* æ¬åææ */ |
| | | .date-type-switch :deep(.el-radio-button__inner:hover) { |
| | | color: rgba(184, 200, 224, 1); |
| | | border-color: rgba(255, 255, 255, 0.3); |
| | | color: #eaf1ff; |
| | | } |
| | | |
| | | /* éä¸ç¶ææ¬å */ |
| | | .date-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner:hover) { |
| | | background: linear-gradient(180deg, #4e8aff 0%, #4ee4ff 100%); |
| | | background: #4a6cf0; |
| | | border-color: #4a6cf0; |
| | | color: #ffffff; |
| | | } |
| | | </style> |
| | |
| | | |
| | | <style scoped> |
| | | .panel-header { |
| | | background-image: url("@/assets/BI/kehuhetongback@2x.png"); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | padding: 0 4px; |
| | | height: 36px; |
| | | } |
| | | |
| | | /* åçèç»ç«æ¡ï¼å¼åº HomePanel æ 颿 ·å¼ */ |
| | | .panel-header::before { |
| | | content: ''; |
| | | width: 3px; |
| | | height: 14px; |
| | | border-radius: 2px; |
| | | background: linear-gradient(180deg, var(--bi-accent-bright, #4a6cf0) 0%, var(--bi-accent, #2c51d9) 100%); |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .panel-title { |
| | | width: 100%; |
| | | font-weight: 500; |
| | | font-size: 16px; |
| | | color: #D9ECFF; |
| | | padding-left: 46px; |
| | | line-height: 36px; |
| | | color: var(--bi-text-strong, #eaf1ff); |
| | | letter-spacing: 1px; |
| | | } |
| | | </style> |
| | |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button__inner) { |
| | | background-color: rgba(26, 88, 176, 0.3); |
| | | color: rgba(184, 200, 224, 0.8); |
| | | border-color: rgba(255, 255, 255, 0.2); |
| | | background-color: rgba(20, 32, 66, 0.55); |
| | | color: #8fa3c8; |
| | | border-color: #1e3160; |
| | | border-radius: 0; |
| | | padding: 6px 20px; |
| | | font-size: 14px; |
| | | transition: all 0.3s; |
| | | transition: color 0.2s ease, background-color 0.2s ease; |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button:first-child .el-radio-button__inner) { |
| | | border-top-left-radius: 4px; |
| | | border-bottom-left-radius: 4px; |
| | | border-top-left-radius: 6px; |
| | | border-bottom-left-radius: 6px; |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button:last-child .el-radio-button__inner) { |
| | | border-top-right-radius: 4px; |
| | | border-bottom-right-radius: 4px; |
| | | border-top-right-radius: 6px; |
| | | border-bottom-right-radius: 6px; |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button:not(:last-child) .el-radio-button__inner) { |
| | | border-right: 1px solid rgba(255, 255, 255, 0.2); |
| | | border-right: 1px solid #1e3160; |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) { |
| | | background: linear-gradient(180deg, #3378ff 0%, #00a4ed 100%); |
| | | background: #2c51d9; |
| | | color: #ffffff; |
| | | border-color: rgba(51, 120, 255, 0.8); |
| | | border-color: #2c51d9; |
| | | box-shadow: none; |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button__inner:hover) { |
| | | color: rgba(184, 200, 224, 1); |
| | | border-color: rgba(255, 255, 255, 0.3); |
| | | color: #eaf1ff; |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner:hover) { |
| | | background: linear-gradient(180deg, #4e8aff 0%, #4ee4ff 100%); |
| | | background: #4a6cf0; |
| | | border-color: #4a6cf0; |
| | | color: #ffffff; |
| | | } |
| | | </style> |
| | |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis1" |
| | | :yAxis="yAxis1" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#8FA3C8' } }" |
| | | style="height: 260px" |
| | | /> |
| | | </div> |
| | |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import ProductTypeSwitch from './ProductTypeSwitch.vue' |
| | | import { productInOutAnalysis } from '@/api/viewIndex.js' |
| | | import { BI, withTooltip } from '../theme.js' |
| | | |
| | | const productType = ref(1) // 1=åææ 2=åæå 3=æå |
| | | const productType = ref(1) // 1=åææ 2=æå 3=åæåï¼ä¸ ProductTypeSwitch ä¸è´ï¼ |
| | | |
| | | const chartStyle = { width: '100%', height: '130%' } |
| | | |
| | |
| | | itemGap: 24, |
| | | itemWidth: 12, |
| | | itemHeight: 12, |
| | | textStyle: { color: '#B8C8E0', fontSize: 14 }, |
| | | textStyle: { color: BI.text, fontSize: 14 }, |
| | | data: [ |
| | | { name: 'åºåº', itemStyle: { color: 'rgba(11, 137, 254, 1)' } }, |
| | | { name: 'å
¥åº', itemStyle: { color: 'rgba(11, 249, 254, 1)' } }, |
| | | { name: 'åºåº', itemStyle: { color: BI.accentBright } }, |
| | | { name: 'å
¥åº', itemStyle: { color: '#35C6D6' } }, |
| | | ], |
| | | } |
| | | |
| | |
| | | type: 'category', |
| | | data: [], |
| | | axisTick: { show: false }, |
| | | axisLine: { show: false, lineStyle: { color: 'rgba(184, 200, 224, 0.3)' } }, |
| | | axisLabel: { color: '#B8C8E0', fontSize: 12 }, |
| | | splitLine: { show: false, lineStyle: { type: 'dashed', color: 'rgba(184, 200, 224, 0.2)' } }, |
| | | axisLine: { show: false }, |
| | | axisLabel: { color: BI.text, fontSize: 12 }, |
| | | splitLine: { show: false }, |
| | | }, |
| | | ]) |
| | | |
| | |
| | | { |
| | | type: 'value', |
| | | name: 'åä½: ä»¶', |
| | | nameTextStyle: { color: '#B8C8E0', fontSize: 12, padding: [0, 0, 0, 0] }, |
| | | nameTextStyle: { color: BI.text, fontSize: 12, padding: [0, 0, 0, 0] }, |
| | | axisLine: { show: false }, |
| | | axisTick: { show: false }, |
| | | axisLabel: { color: '#B8C8E0', fontSize: 12 }, |
| | | splitLine: { lineStyle: { color: '#B8C8E0' } }, |
| | | axisLabel: { color: BI.text, fontSize: 12 }, |
| | | splitLine: { lineStyle: { color: 'rgba(143, 163, 200, 0.15)' } }, |
| | | }, |
| | | ] |
| | | |
| | |
| | | { |
| | | name: 'åºåº', |
| | | type: 'line', |
| | | smooth: false, |
| | | smooth: true, |
| | | showSymbol: true, |
| | | symbol: 'circle', |
| | | symbolSize: 8, |
| | | lineStyle: { color: 'rgba(11, 137, 254, 1)', width: 2 }, |
| | | itemStyle: { color: 'rgba(11, 137, 254, 1)', borderWidth: 0 }, |
| | | symbolSize: 6, |
| | | lineStyle: { color: BI.accentBright, width: 2 }, |
| | | itemStyle: { color: BI.accentBright, borderWidth: 0 }, |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: 'rgba(11, 137, 254, 0.40)' }, |
| | | { offset: 1, color: 'rgba(11, 137, 254, 0.05)' }, |
| | | { offset: 0, color: 'rgba(74, 108, 240, 0.25)' }, |
| | | { offset: 1, color: 'rgba(74, 108, 240, 0)' }, |
| | | ]), |
| | | }, |
| | | data: [], |
| | |
| | | { |
| | | name: 'å
¥åº', |
| | | type: 'line', |
| | | smooth: false, |
| | | smooth: true, |
| | | showSymbol: true, |
| | | symbol: 'circle', |
| | | symbolSize: 8, |
| | | lineStyle: { color: 'rgba(11, 249, 254, 1)', width: 2 }, |
| | | itemStyle: { color: 'rgba(11, 249, 254, 1)', borderWidth: 0 }, |
| | | symbolSize: 6, |
| | | lineStyle: { color: '#35C6D6', width: 2 }, |
| | | itemStyle: { color: '#35C6D6', borderWidth: 0 }, |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: 'rgba(11, 249, 254, 0.5)' }, |
| | | { offset: 1, color: 'rgba(11, 249, 254, 0.05)' }, |
| | | { offset: 0, color: 'rgba(53, 198, 214, 0.25)' }, |
| | | { offset: 1, color: 'rgba(53, 198, 214, 0)' }, |
| | | ]), |
| | | }, |
| | | data: [], |
| | |
| | | }, |
| | | ]) |
| | | |
| | | const tooltip = { |
| | | const tooltip = withTooltip({ |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'line' }, |
| | | borderWidth: 1, |
| | | textStyle: { fontSize: 12 }, |
| | | formatter(params) { |
| | | let result = params[0].axisValue + '<br/>' |
| | | params.forEach((item) => { |
| | |
| | | }) |
| | | return result |
| | | }, |
| | | } |
| | | }) |
| | | |
| | | const fetchData = () => { |
| | | productInOutAnalysis({ type: productType.value }) |
| | |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | border: 1px solid var(--bi-panel-border); |
| | | background: var(--bi-panel-bg); |
| | | border-radius: 8px; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 428px; |
| | |
| | | <template> |
| | | <div> |
| | | <!-- 设å¤ç»è®¡ --> |
| | | <!-- 产åå¨è½¬å¤©æ° --> |
| | | <div class="equipment-stats"> |
| | | <div class="equipment-header"> |
| | | <img |
| | | src="@/assets/BI/shujutongjiicon@2x.png" |
| | | alt="徿 " |
| | | class="equipment-icon" |
| | | /> |
| | | <span class="equipment-title">产åå¨è½¬å¤©æ°</span> |
| | | </div> |
| | | <PanelHeader title="产åå¨è½¬å¤©æ°" /> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis1" |
| | | :yAxis="yAxis1" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }" |
| | | :options="baseOptions" |
| | | style="height: 260px" |
| | | /> |
| | | </div> |
| | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import { productTurnoverDays } from '@/api/viewIndex.js' |
| | | import { BI, baseChartOptions, withTooltip } from '../theme.js' |
| | | |
| | | const chartStyle = { width: '100%', height: '100%' } |
| | | const grid = { left: '3%', right: '4%', bottom: '3%', top: '4%', containLabel: true } |
| | | const barLegend = { show: false, textStyle: { color: '#B8C8E0' }, data: ['å¨è½¬å¤©æ°'] } |
| | | const barLegend = { show: false, textStyle: { color: BI.text }, data: ['å¨è½¬å¤©æ°'] } |
| | | const baseOptions = baseChartOptions |
| | | const barSeries1 = ref([ |
| | | { |
| | | name: 'å¨è½¬å¤©æ°', |
| | |
| | | barWidth: 30, |
| | | emphasis: { focus: 'series' }, |
| | | itemStyle: { |
| | | borderRadius: [4, 4, 0, 0], |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, y: 1, x2: 0, y2: 0, |
| | | colorStops: [ |
| | | { offset: 0, color: 'rgba(0,164,237,0)' }, |
| | | { offset: 1, color: '#4EE4FF' }, |
| | | { offset: 0, color: 'rgba(74, 108, 240, 0.05)' }, |
| | | { offset: 1, color: BI.accentBright }, |
| | | ], |
| | | }, |
| | | }, |
| | | data: [], |
| | | }, |
| | | ]) |
| | | const tooltip = { |
| | | const tooltip = withTooltip({ |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'shadow' }, |
| | | formatter(params) { |
| | |
| | | }) |
| | | return result |
| | | }, |
| | | } |
| | | const xAxis1 = ref([{ type: 'category', axisTick: { show: false }, axisLabel: { color: '#B8C8E0' }, data: [] }]) |
| | | const yAxis1 = [{ type: 'value', axisLabel: { color: '#B8C8E0' } }] |
| | | }) |
| | | const xAxis1 = ref([{ type: 'category', axisTick: { show: false }, axisLabel: { color: BI.text }, data: [] }]) |
| | | const yAxis1 = [{ type: 'value', axisLabel: { color: BI.text } }] |
| | | |
| | | const fetchData = () => { |
| | | productTurnoverDays() |
| | |
| | | |
| | | <style scoped> |
| | | .equipment-stats { |
| | | border: 1px solid #1a58b0; |
| | | padding: 0 18px 18px; |
| | | border: 1px solid var(--bi-panel-border); |
| | | background: var(--bi-panel-bg); |
| | | border-radius: 8px; |
| | | padding: 10px 18px 18px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 16px; |
| | | } |
| | | |
| | | .equipment-header { |
| | | font-weight: 500; |
| | | font-size: 21px; |
| | | display: flex; |
| | | border-bottom: 1px solid; |
| | | border-image: linear-gradient( |
| | | 270deg, |
| | | rgba(0, 126, 255, 0) 0%, |
| | | rgba(0, 126, 255, 0.4549) 35%, |
| | | #007eff 78%, |
| | | #007eff 100% |
| | | ) |
| | | 1; |
| | | padding-bottom: 2px; |
| | | } |
| | | |
| | | .equipment-title { |
| | | font-weight: 500; |
| | | font-size: 18px; |
| | | background: linear-gradient(360deg, #056dff 0%, #43e8fc 100%); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | line-height: 50px; |
| | | } |
| | | |
| | | .equipment-icon { |
| | | width: 50px; |
| | | height: 50px; |
| | | gap: 6px; |
| | | } |
| | | </style> |
| | |
| | | <!-- é¡¶é¨ç»è®¡å¡ç --> |
| | | <div class="stats-cards"> |
| | | <div |
| | | v-for="item in statItems" |
| | | v-for="(item, index) in statItems" |
| | | :key="item.name" |
| | | class="stat-card" |
| | | > |
| | | <img src="@/assets/BI/icon@2x.png" alt="徿 " class="card-icon" /> |
| | | <div class="card-icon" :style="{ color: iconColors[index % iconColors.length] }"> |
| | | <!-- éå® --> |
| | | <svg v-if="index % 3 === 0" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> |
| | | <path d="M3 17l6-6 4 4 8-8" /> |
| | | <path d="M15 7h6v6" /> |
| | | </svg> |
| | | <!-- éè´ --> |
| | | <svg v-else-if="index % 3 === 1" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> |
| | | <path d="M21 8l-9-5-9 5 9 5 9-5z" /> |
| | | <path d="M3 8v8l9 5 9-5V8" /> |
| | | </svg> |
| | | <!-- åºå --> |
| | | <svg v-else width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> |
| | | <rect x="3" y="4" width="18" height="16" rx="2" /> |
| | | <path d="M3 10h18M12 4v16" /> |
| | | </svg> |
| | | </div> |
| | | <div class="card-content"> |
| | | <span class="card-label">{{ item.name }}</span> |
| | | <span class="card-value">{{ item.value }}</span> |
| | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { salesPurchaseStorageProductCount } from '@/api/viewIndex.js' |
| | | import { BI } from '../theme.js' |
| | | |
| | | const statItems = ref([]) |
| | | const iconColors = [BI.accentBright, '#35C6D6', '#8E7CE6'] |
| | | |
| | | const formatPercent = (val) => { |
| | | const num = Number(val) || 0 |
| | |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | background-image: url('@/assets/BI/border@2x.png'); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | background: var(--bi-panel-bg); |
| | | border: 1px solid var(--bi-panel-border); |
| | | border-radius: 8px; |
| | | position: relative; |
| | | overflow: hidden; |
| | | height: 142px; |
| | | } |
| | | |
| | | /* é¡¶é¨åçèäº®çº¿ï¼æ¿ä»£å¾çæè¾¹ */ |
| | | .stat-card::before { |
| | | content: ''; |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | height: 2px; |
| | | background: linear-gradient(90deg, var(--bi-accent) 0%, transparent 70%); |
| | | } |
| | | |
| | | .card-icon { |
| | | width: 100px; |
| | | height: 100px; |
| | | margin: 20px 20px 0 10px; |
| | | width: 52px; |
| | | height: 52px; |
| | | margin: 0 16px 0 20px; |
| | | border-radius: 8px; |
| | | background: rgba(74, 108, 240, 0.12); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .card-content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | gap: 8px; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .card-value { |
| | | font-weight: 500; |
| | | font-size: 40px; |
| | | background: linear-gradient(360deg, #008bfd 0%, #ffffff 100%); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | font-weight: 600; |
| | | font-size: 36px; |
| | | color: var(--bi-text-strong); |
| | | font-variant-numeric: tabular-nums; |
| | | line-height: 1.1; |
| | | } |
| | | |
| | | .card-label { |
| | | font-weight: 400; |
| | | font-size: 19px; |
| | | color: rgba(208, 231, 255, 0.7); |
| | | font-size: 16px; |
| | | color: var(--bi-text); |
| | | } |
| | | |
| | | .card-compare { |
| | |
| | | |
| | | .card-compare > span:first-child { |
| | | font-size: 13px; |
| | | opacity: 0.8; |
| | | color: var(--bi-text); |
| | | } |
| | | |
| | | .compare-value { |
| | | font-weight: 600; |
| | | font-variant-numeric: tabular-nums; |
| | | } |
| | | |
| | | .compare-icon { |
| | |
| | | |
| | | .compare-up .compare-value, |
| | | .compare-up .compare-icon { |
| | | color: #00c853; |
| | | color: var(--bi-up, #34c77b); |
| | | } |
| | | |
| | | .compare-down .compare-value, |
| | | .compare-down .compare-icon { |
| | | color: #ff5252; |
| | | color: var(--bi-down, #ff6b6b); |
| | | } |
| | | |
| | | </style> |
| | |
| | | <PanelHeader title="éè´ååå¸" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <CarouselCards :items="cardItems" :visible-count="3" /> |
| | | <div class="pie-chart-wrapper" ref="pieWrapperRef"> |
| | | <div class="pie-background" ref="pieBackgroundRef"></div> |
| | | <div class="pie-chart-wrapper"> |
| | | <div class="pie-center"> |
| | | <span class="pie-center-value">{{ totalAmount }}</span> |
| | | <span class="pie-center-label">å计(å
)</span> |
| | | </div> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, computed } from 'vue' |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import CarouselCards from './CarouselCards.vue' |
| | | import { rawMaterialPurchaseAmountRatio } from '@/api/viewIndex.js' |
| | | import { useChartBackground } from '@/hooks/useChartBackground.js' |
| | | |
| | | const pieWrapperRef = ref(null) |
| | | const pieBackgroundRef = ref(null) |
| | | import { BI, baseChartOptions, withTooltip, formatAmount } from '../theme.js' |
| | | |
| | | /** |
| | | * @introduction ææ°ç»ä¸keyå¼ç¸åçé£ä¸é¡¹æååºæ¥ï¼ç»æä¸ä¸ªå¯¹è±¡ |
| | |
| | | const cardItems = ref([]) |
| | | |
| | | // é¢è²å表 |
| | | const landColors = ['#26FFCB', '#24CBFF', '#35FBF4', '#2651FF', '#D1E4F5', '#5782F7', '#2F67EF', '#82BAFF'] |
| | | const landColors = BI.series |
| | | |
| | | const landObjData = computed(() => array2obj(dataList.value, 'name')) |
| | | |
| | | const totalAmount = computed(() => |
| | | formatAmount(dataList.value.reduce((sum, d) => sum + (Number(d.value) || 0), 0)) |
| | | ) |
| | | |
| | | // å¾ä¾é
ç½®ï¼å³ä¾§ç«æï¼ |
| | | const landLegend = computed(() => { |
| | |
| | | name: d.name, |
| | | icon: 'circle', |
| | | textStyle: { |
| | | fontSize: 18, |
| | | fontSize: 14, |
| | | color: landColors[idx % landColors.length], |
| | | }, |
| | | })) |
| | |
| | | orient: 'vertical', |
| | | top: 'center', |
| | | left: '52%', |
| | | itemGap: 30, |
| | | itemGap: 24, |
| | | data: data, |
| | | formatter: function (name) { |
| | | const item = landObjData.value[name] |
| | | if (!item) return name |
| | | return `{title|${name}}{value|${item.value}}{unit|å
}{percent|${item.rate}}{unit|%}` |
| | | return `{title|${name}}{value|${formatAmount(item.value)}}{percent|${item.rate}}{unit|%}` |
| | | }, |
| | | textStyle: { |
| | | rich: { |
| | | value: { |
| | | color: '#43e8fc', |
| | | title: { |
| | | fontSize: 14, |
| | | fontWeight: 600, |
| | | padding: [0, 0, 0, 10], |
| | | }, |
| | | unit: { |
| | | color: '#82baff', |
| | | fontSize: 12, |
| | | fontWeight: 600, |
| | | padding: [0, 10, 0, 0], |
| | | }, |
| | | percent: { |
| | | color: '#43e8fc', |
| | | fontSize: 14, |
| | | fontWeight: 600, |
| | | color: BI.text, |
| | | padding: [0, 0, 0, 0], |
| | | }, |
| | | title: { |
| | | value: { |
| | | color: BI.textStrong, |
| | | fontSize: 14, |
| | | fontWeight: 600, |
| | | padding: [0, 6, 0, 10], |
| | | }, |
| | | percent: { |
| | | color: BI.accentBright, |
| | | fontSize: 14, |
| | | fontWeight: 600, |
| | | padding: [0, 0, 0, 6], |
| | | }, |
| | | unit: { |
| | | color: BI.text, |
| | | fontSize: 12, |
| | | padding: [0, 0, 0, 0], |
| | | }, |
| | |
| | | }) |
| | | |
| | | // æç¤ºæ¡ |
| | | const landTooltip = { |
| | | const landTooltip = withTooltip({ |
| | | trigger: 'item', |
| | | formatter: '{a} <br/>{b} : {c}å
({d}%)', |
| | | } |
| | | }) |
| | | |
| | | // åå±ç¯å½¢é¥¼å¾ |
| | | const landSeries = ref([ |
| | | { |
| | | name: '产åéè´éé¢åæ', |
| | | type: 'pie', |
| | | radius: ['40%', '60%'], |
| | | radius: ['48%', '68%'], |
| | | center: ['25%', '50%'], |
| | | itemStyle: { |
| | | borderColor: '#0a1c3a', |
| | | borderColor: BI.sliceBorder, |
| | | borderWidth: 2, |
| | | borderRadius: 4, |
| | | color: function (params) { |
| | | return landColors[params.dataIndex % landColors.length] |
| | | }, |
| | |
| | | }, |
| | | }, |
| | | { |
| | | // å
å |
| | | // å
å轨éç¯ |
| | | type: 'pie', |
| | | radius: ['40%', '45%'], |
| | | radius: ['40%', '43%'], |
| | | center: ['25%', '50%'], |
| | | silent: true, |
| | | label: { |
| | |
| | | show: false, |
| | | }, |
| | | itemStyle: { |
| | | color: 'rgba(0, 127, 255, 0.25)', |
| | | color: 'rgba(74, 108, 240, 0.22)', |
| | | }, |
| | | data: [1], |
| | | }, |
| | |
| | | height: '100%', |
| | | } |
| | | |
| | | const pieOptions = { |
| | | backgroundColor: 'transparent', |
| | | textStyle: { color: '#B8C8E0' }, |
| | | } |
| | | |
| | | // 使ç¨å°è£
çèæ¯ä½ç½®è°æ´æ¹æ³ |
| | | // å¾è¡¨ä¸å¿æ¯ ['25%', '50%']ï¼èæ¯éè¦å¯¹é½å°è¿ä¸ªä½ç½® |
| | | const { init: initBackground, cleanup: cleanupBackground } = useChartBackground({ |
| | | wrapperRef: pieWrapperRef, |
| | | backgroundRef: pieBackgroundRef, |
| | | left: '25%', // å¾è¡¨ä¸å¿ X æ¯ 25% |
| | | top: '50%', // å¾è¡¨ä¸å¿ Y æ¯ 50% |
| | | offsetX: '-51.5%', // X è½´åç§» |
| | | offsetY: '-50%', // Y è½´åç§» |
| | | watchData: dataList // ç嬿°æ®ååï¼èªå¨è°æ´ä½ç½® |
| | | }) |
| | | const pieOptions = baseChartOptions |
| | | |
| | | const fetchData = () => { |
| | | rawMaterialPurchaseAmountRatio() |
| | |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | initBackground() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | cleanupBackground() |
| | | }) |
| | | </script> |
| | | |
| | |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | border: 1px solid var(--bi-panel-border); |
| | | background: var(--bi-panel-bg); |
| | | border-radius: 8px; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | |
| | | background: transparent; |
| | | } |
| | | |
| | | |
| | | .pie-background { |
| | | .pie-center { |
| | | position: absolute; |
| | | width: 310px; |
| | | height: 310px; |
| | | background-image: url('@/assets/BI/ç«ç°å¾è¾¹æ¡.png'); |
| | | background-size: contain; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | z-index: 1; |
| | | pointer-events: none; |
| | | /* ä½ç½®ç± JS å¨æè®¾ç½®ï¼é»è®¤å±
ä¸ */ |
| | | left: 25%; |
| | | top: 50%; |
| | | transform: translate(-51.5%, -50%); |
| | | transform: translate(-50%, -50%); |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | gap: 4px; |
| | | pointer-events: none; |
| | | z-index: 2; |
| | | } |
| | | |
| | | .pie-center-value { |
| | | font-weight: 600; |
| | | font-size: 20px; |
| | | color: var(--bi-text-strong); |
| | | font-variant-numeric: tabular-nums; |
| | | } |
| | | |
| | | .pie-center-label { |
| | | font-size: 12px; |
| | | color: var(--bi-text); |
| | | } |
| | | </style> |
| | |
| | | <PanelHeader title="éå®ååå¸" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <CarouselCards :items="cardItems" :visible-count="3" /> |
| | | <div class="pie-chart-wrapper" ref="pieWrapperRef"> |
| | | <div class="pie-background" ref="pieBackgroundRef"></div> |
| | | <div class="pie-chart-wrapper"> |
| | | <div class="pie-center"> |
| | | <span class="pie-center-value">{{ totalAmount }}</span> |
| | | <span class="pie-center-label">å计(å
)</span> |
| | | </div> |
| | | <Echarts |
| | | ref="echartsRef" |
| | | :chartStyle="chartStyle" |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, computed } from 'vue' |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import { productSalesAnalysis } from '@/api/viewIndex.js' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import CarouselCards from './CarouselCards.vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import { useChartBackground } from '@/hooks/useChartBackground.js' |
| | | |
| | | const pieWrapperRef = ref(null) |
| | | const pieBackgroundRef = ref(null) |
| | | import { BI, baseChartOptions, withTooltip, formatAmount } from '../theme.js' |
| | | |
| | | /** |
| | | * @introduction ææ°ç»ä¸keyå¼ç¸åçé£ä¸é¡¹æååºæ¥ï¼ç»æä¸ä¸ªå¯¹è±¡ |
| | |
| | | |
| | | const echartsRef = ref(null) |
| | | const pieDatas = ref([]) |
| | | const pieColors = ['#D1E4F5', '#5782F7', '#2F67EF', '#82BAFF', '#43e8fc', '#27EBE7'] |
| | | const pieColors = BI.series |
| | | |
| | | const pieObjData = computed(() => array2obj(pieDatas.value, 'name')) |
| | | |
| | | const totalAmount = computed(() => |
| | | formatAmount(pieDatas.value.reduce((sum, d) => sum + (Number(d.value) || 0), 0)) |
| | | ) |
| | | |
| | | const pieLegend = computed(() => { |
| | | const data = pieDatas.value.map((d, idx) => ({ |
| | | name: d.name, |
| | | icon: 'circle', |
| | | textStyle: { |
| | | fontSize: 18, |
| | | fontSize: 14, |
| | | color: pieColors[idx % pieColors.length], |
| | | }, |
| | | })) |
| | |
| | | orient: 'vertical', |
| | | top: 'center', |
| | | left: '52%', |
| | | itemGap: 30, |
| | | itemGap: 24, |
| | | data: data, |
| | | formatter: function (name) { |
| | | const item = pieObjData.value[name] |
| | | if (!item) return name |
| | | return `{title|${name}}{value|${item.value}}{unit|å
}{percent|${item.rate}}{unit|%}` |
| | | return `{title|${name}}{value|${formatAmount(item.value)}}{percent|${item.rate}}{unit|%}` |
| | | }, |
| | | textStyle: { |
| | | rich: { |
| | | value: { |
| | | color: '#43e8fc', |
| | | title: { |
| | | fontSize: 14, |
| | | fontWeight: 600, |
| | | padding: [0, 0, 0, 10], |
| | | }, |
| | | unit: { |
| | | color: '#82baff', |
| | | fontSize: 12, |
| | | fontWeight: 600, |
| | | padding: [0, 10, 0, 0], |
| | | }, |
| | | percent: { |
| | | color: '#43e8fc', |
| | | fontSize: 14, |
| | | fontWeight: 600, |
| | | color: BI.text, |
| | | padding: [0, 0, 0, 0], |
| | | }, |
| | | title: { |
| | | value: { |
| | | color: BI.textStrong, |
| | | fontSize: 14, |
| | | fontWeight: 600, |
| | | padding: [0, 6, 0, 10], |
| | | }, |
| | | percent: { |
| | | color: BI.accentBright, |
| | | fontSize: 14, |
| | | fontWeight: 600, |
| | | padding: [0, 0, 0, 6], |
| | | }, |
| | | unit: { |
| | | color: BI.text, |
| | | fontSize: 12, |
| | | padding: [0, 0, 0, 0], |
| | | }, |
| | |
| | | } |
| | | }) |
| | | |
| | | const pieTooltip = { |
| | | const pieTooltip = withTooltip({ |
| | | trigger: 'item', |
| | | formatter: '{a} <br/>{b} : {c}å
({d}%)', |
| | | } |
| | | }) |
| | | |
| | | const pieSeries = computed(() => [ |
| | | { |
| | | name: '产åéå®éé¢åæ', |
| | | type: 'pie', |
| | | radius: '60%', |
| | | radius: ['48%', '68%'], |
| | | center: ['25%', '50%'], |
| | | itemStyle: { |
| | | borderColor: '#0a1c3a', |
| | | borderColor: BI.sliceBorder, |
| | | borderWidth: 2, |
| | | borderRadius: 4, |
| | | }, |
| | | label: { |
| | | show: false |
| | |
| | | }, |
| | | ]) |
| | | |
| | | const pieOptions = { |
| | | backgroundColor: 'transparent', |
| | | textStyle: { color: '#B8C8E0' }, |
| | | } |
| | | const pieOptions = baseChartOptions |
| | | |
| | | const cardItems = ref([]) |
| | | |
| | | // 使ç¨å°è£
çèæ¯ä½ç½®è°æ´æ¹æ³ï¼ä¸å
¶ä»æä»¶ä¿æä¸è´ï¼ |
| | | const { init: initBackground, cleanup: cleanupBackground } = useChartBackground({ |
| | | wrapperRef: pieWrapperRef, |
| | | backgroundRef: pieBackgroundRef, |
| | | left: '25%', // å¾è¡¨ä¸å¿ X æ¯ 25% |
| | | top: '50%', // å¾è¡¨ä¸å¿ Y æ¯ 50% |
| | | offsetX: '-51.5%', // X è½´åç§» |
| | | offsetY: '-50%', // Y è½´åç§» |
| | | watchData: pieDatas // ç嬿°æ®ååï¼èªå¨è°æ´ä½ç½® |
| | | }) |
| | | |
| | | const fetchData = () => { |
| | | productSalesAnalysis() |
| | |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | initBackground() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | cleanupBackground() |
| | | }) |
| | | </script> |
| | | |
| | |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | border: 1px solid var(--bi-panel-border); |
| | | background: var(--bi-panel-bg); |
| | | border-radius: 8px; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | | } |
| | | |
| | | .pie-chart-wrapper { |
| | | position: relative; |
| | | width: 100%; |
| | | height: 320px; |
| | | } |
| | | .pie-background { |
| | | |
| | | .pie-center { |
| | | position: absolute; |
| | | left: 25%; |
| | | top: 50%; |
| | | transform: translate(-51.5%, -50%); |
| | | width: 310px; |
| | | height: 310px; |
| | | background-image: url('@/assets/BI/ç«ç°å¾è¾¹æ¡.png'); |
| | | background-size: contain; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | z-index: 1; |
| | | transform: translate(-50%, -50%); |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | gap: 4px; |
| | | pointer-events: none; |
| | | z-index: 2; |
| | | } |
| | | |
| | | .pie-center-value { |
| | | font-weight: 600; |
| | | font-size: 20px; |
| | | color: var(--bi-text-strong); |
| | | font-variant-numeric: tabular-nums; |
| | | } |
| | | |
| | | .pie-center-label { |
| | | font-size: 12px; |
| | | color: var(--bi-text); |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="客æ·è´¡ç®æå" /> |
| | | <div class="panel-item-customers"> |
| | | <div class="switch-container"> |
| | | <DateTypeSwitch v-model="dateType" @change="handleDateTypeChange" /> |
| | | </div> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="{ show: false }" |
| | | :series="series" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis" |
| | | :yAxis="yAxis" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: BI.text } }" |
| | | style="height: 360px" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import DateTypeSwitch from './DateTypeSwitch.vue' |
| | | import { customerContributionRanking } from '@/api/viewIndex.js' |
| | | import { BI, withTooltip } from '../theme.js' |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%', |
| | | } |
| | | |
| | | const dateType = ref(3) // 1=å¨ 2=æ 3=å£åº¦ï¼é»è®¤å£åº¦ |
| | | |
| | | // åå§æ°æ®ï¼ç»ä¸æ { NAME, NUM }ï¼ |
| | | const dataArr = ref([]) |
| | | |
| | | const dataArray = computed(() => { |
| | | const sortedAsc = [...dataArr.value].sort((a, b) => a.NUM - b.NUM) |
| | | return sortedAsc.length > 5 ? sortedAsc.slice(-5) : sortedAsc |
| | | }) |
| | | |
| | | const total = computed(() => dataArray.value.reduce((sum, v) => sum + Number(v.NUM || 0), 0)) |
| | | |
| | | const xdataName = computed(() => dataArray.value.map((v) => v.NAME)) |
| | | |
| | | // æåé
è²ï¼Top1 éè²ï¼Top2-3 亮èï¼å
¶ä½æè |
| | | const rankColor = (rank) => { |
| | | if (rank === 1) return '#F0B14A' |
| | | if (rank <= 3) return BI.accentBright |
| | | return '#2C4A9E' |
| | | } |
| | | |
| | | const getRank = (i) => dataArray.value.length - i |
| | | |
| | | const dataNum = computed(() => { |
| | | return dataArray.value.map((v, i) => { |
| | | const rank = getRank(i) |
| | | return { |
| | | value: Number(v.NUM), |
| | | itemStyle: { |
| | | color: rankColor(rank), |
| | | barBorderRadius: [0, 10, 10, 0], |
| | | }, |
| | | } |
| | | }) |
| | | }) |
| | | |
| | | const bgData = computed(() => { |
| | | const maxValue = dataNum.value.length ? Math.max(...dataNum.value.map((v) => v.value)) : 0 |
| | | return dataNum.value.map(() => maxValue * 1.08 || 1) |
| | | }) |
| | | |
| | | const tooltip = withTooltip({ |
| | | trigger: 'axis', |
| | | formatter: function (params) { |
| | | let result = params[0].axisValueLabel + '<br/>' |
| | | result += `<div>${params[0].marker}${params[0].value}å
</div>` |
| | | return result |
| | | }, |
| | | }) |
| | | |
| | | const grid = computed(() => ({ top: 0, left: '20%', right: '10%', bottom: 0 })) |
| | | |
| | | const xAxis = computed(() => [ |
| | | { |
| | | splitLine: { show: false }, |
| | | axisLine: { show: false }, |
| | | axisLabel: { show: false }, |
| | | axisTick: { show: false }, |
| | | }, |
| | | ]) |
| | | |
| | | const yAxis = computed(() => [ |
| | | { |
| | | type: 'category', |
| | | inverse: false, |
| | | data: xdataName.value, |
| | | axisLabel: { |
| | | formatter: (value) => { |
| | | if (!value) return '' |
| | | const maxLen = 6 // æ¯è¡æå¤å符æ°ï¼å¯æéè°æ´ |
| | | if (value.length <= maxLen) return `{a|${value}}` |
| | | |
| | | const lines = [] |
| | | for (let i = 0; i < value.length; i += maxLen) { |
| | | lines.push(value.slice(i, i + maxLen)) |
| | | } |
| | | return lines.map((line) => `{a|${line}}`).join('\n') |
| | | }, |
| | | rich: { |
| | | a: { |
| | | width: 120, |
| | | fontSize: 14, |
| | | color: BI.textStrong, |
| | | padding: [5, 4, 5, 0], |
| | | align: 'right', |
| | | }, |
| | | }, |
| | | }, |
| | | axisLine: { show: false }, |
| | | axisTick: { show: false }, |
| | | splitLine: { show: false }, |
| | | }, |
| | | { |
| | | type: 'category', |
| | | data: dataNum.value.map((item) => item.value), |
| | | axisLabel: { |
| | | formatter: (params, index) => { |
| | | const value = typeof params === 'object' ? params.value : params |
| | | const percent = total.value ? ((value / total.value) * 100).toFixed(0) : 0 |
| | | const rank = getRank(index) |
| | | return `{a${rank}|${percent} }{b${rank}|%}` |
| | | }, |
| | | rich: dataNum.value.reduce((rich, _, i) => { |
| | | const rank = getRank(i) |
| | | const color = rank === 1 ? '#F0B14A' : rank <= 3 ? BI.accentBright : BI.text |
| | | rich[`a${rank}`] = { fontSize: 16, color, verticalAlign: 'bottom' } |
| | | rich[`b${rank}`] = { fontSize: 12, color, verticalAlign: 'bottom' } |
| | | return rich |
| | | }, {}), |
| | | }, |
| | | axisLine: { show: false }, |
| | | axisTick: { show: false }, |
| | | splitLine: { show: false }, |
| | | }, |
| | | ]) |
| | | |
| | | const series = computed(() => [ |
| | | { |
| | | name: 'èæ¯', |
| | | z: 5, |
| | | type: 'bar', |
| | | barWidth: 18, |
| | | tooltip: { show: false }, |
| | | itemStyle: { |
| | | color: 'rgba(143, 163, 200, 0.12)', |
| | | barBorderRadius: [0, 10, 10, 0], |
| | | }, |
| | | data: bgData.value, |
| | | }, |
| | | { |
| | | name: 'éé¢', |
| | | z: 6, |
| | | type: 'bar', |
| | | barWidth: 18, |
| | | barGap: '-100%', |
| | | data: dataNum.value, |
| | | }, |
| | | ]) |
| | | |
| | | const normalizeItem = (item) => { |
| | | const name = |
| | | item?.NAME ?? |
| | | item?.name ?? |
| | | item?.customerName ?? |
| | | item?.customer ?? |
| | | item?.label ?? |
| | | '-' |
| | | |
| | | const num = |
| | | item?.NUM ?? |
| | | item?.num ?? |
| | | item?.value ?? |
| | | item?.amount ?? |
| | | item?.money ?? |
| | | 0 |
| | | |
| | | return { NAME: String(name), NUM: Number(num) || 0 } |
| | | } |
| | | |
| | | const getMockListByType = (type) => { |
| | | // 模æåæ°æ®ï¼éé¢è´¡ç®æåï¼ |
| | | // type: 1=å¨ 2=æ 3=å£åº¦ |
| | | if (type === 2) { |
| | | return [ |
| | | { NAME: 'åä¸ç²¾å¯', NUM: 5120000 }, |
| | | { NAME: 'æè¾°çµå', NUM: 3860000 }, |
| | | { NAME: 'å¯èªç§æ', NUM: 2720000 }, |
| | | { NAME: 'éè¯å¶é ', NUM: 2160000 }, |
| | | { NAME: 'è¿æ¯ææ', NUM: 1430000 }, |
| | | { NAME: '德润贸æ', NUM: 910000 }, |
| | | { NAME: 'å®è¾¾é
å¥', NUM: 680000 }, |
| | | ] |
| | | } |
| | | if (type === 3) { |
| | | return [ |
| | | { NAME: 'åä¸ç²¾å¯', NUM: 16800000 }, |
| | | { NAME: 'æè¾°çµå', NUM: 12960000 }, |
| | | { NAME: 'å¯èªç§æ', NUM: 9720000 }, |
| | | { NAME: 'éè¯å¶é ', NUM: 7560000 }, |
| | | { NAME: 'è¿æ¯ææ', NUM: 5430000 }, |
| | | { NAME: '德润贸æ', NUM: 3910000 }, |
| | | { NAME: 'å®è¾¾é
å¥', NUM: 2680000 }, |
| | | ] |
| | | } |
| | | return [ |
| | | { NAME: 'åä¸ç²¾å¯', NUM: 1280000 }, |
| | | { NAME: 'æè¾°çµå', NUM: 860000 }, |
| | | { NAME: 'å¯èªç§æ', NUM: 720000 }, |
| | | { NAME: 'éè¯å¶é ', NUM: 560000 }, |
| | | { NAME: 'è¿æ¯ææ', NUM: 430000 }, |
| | | { NAME: '德润贸æ', NUM: 310000 }, |
| | | { NAME: 'å®è¾¾é
å¥', NUM: 180000 }, |
| | | ] |
| | | } |
| | | |
| | | const setMockData = (type) => { |
| | | dataArr.value = getMockListByType(type).map(normalizeItem) |
| | | } |
| | | |
| | | const fetchCustomerRanking = () => { |
| | | customerContributionRanking({ type: dateType.value }) |
| | | .then((res) => { |
| | | if (res.code === 200 && Array.isArray(res.data)) { |
| | | dataArr.value = res.data.map(item => ({ |
| | | NAME: item.customerName, |
| | | NUM: item.totalAmount |
| | | })) |
| | | } else { |
| | | setMockData(dateType.value) |
| | | } |
| | | }) |
| | | .catch((error) => { |
| | | console.error('è·å客æ·éé¢è´¡ç®æå失败:', error) |
| | | setMockData(dateType.value) |
| | | }) |
| | | } |
| | | |
| | | const handleDateTypeChange = () => { |
| | | fetchCustomerRanking() |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchCustomerRanking() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .panel-item-customers { |
| | | border: 1px solid var(--bi-panel-border); |
| | | background: var(--bi-panel-bg); |
| | | border-radius: 8px; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | | } |
| | | |
| | | .switch-container { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | margin-bottom: 16px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="ä¾åºåéè´æå" /> |
| | | <div class="panel-item-customers"> |
| | | <div class="switch-container"> |
| | | <DateTypeSwitch v-model="radio1" @change="handleDateTypeChange" /> |
| | | </div> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :series="series" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis" |
| | | :yAxis="yAxis" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: BI.text } }" |
| | | style="height: 360px" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import DateTypeSwitch from './DateTypeSwitch.vue' |
| | | import { supplierPurchaseRanking } from '@/api/viewIndex.js' |
| | | import { BI, withTooltip } from '../theme.js' |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%', |
| | | } |
| | | |
| | | const radio1 = ref(3) |
| | | |
| | | // åå§æ°æ® |
| | | const dataArr = ref([]) |
| | | |
| | | // æåºåçæ°æ®ï¼ååºï¼å¼è¶å¤§è¶é ä¸ï¼ |
| | | const dataArray = computed(() => { |
| | | return [...dataArr.value].sort((a, b) => a.NUM - b.NUM) |
| | | }) |
| | | |
| | | // è®¡ç®æ»æ° |
| | | const total = computed(() => { |
| | | return dataArray.value.reduce((sum, v) => sum + Number(v.NUM), 0) |
| | | }) |
| | | |
| | | // xè½´æ°æ®ï¼åç§°ï¼ |
| | | const xdataName = computed(() => { |
| | | return dataArray.value.map((v) => v.NAME) |
| | | }) |
| | | |
| | | // æåé
è²ï¼Top1 éè²ï¼Top2-3 亮èï¼å
¶ä½æè |
| | | const rankColor = (rank) => { |
| | | if (rank === 1) return '#F0B14A' |
| | | if (rank <= 3) return BI.accentBright |
| | | return '#2C4A9E' |
| | | } |
| | | |
| | | const getRank = (i) => dataArray.value.length - i |
| | | |
| | | // yè½´æ°æ®ï¼æ°å¼ï¼å¸¦æ ·å¼ï¼ |
| | | const dataNum = computed(() => { |
| | | return dataArray.value.map((v, i) => { |
| | | const rank = getRank(i) |
| | | return { |
| | | value: Number(v.NUM), |
| | | itemStyle: { |
| | | color: rankColor(rank), |
| | | barBorderRadius: [0, 10, 10, 0], |
| | | }, |
| | | } |
| | | }) |
| | | }) |
| | | |
| | | // èæ¯è½¨éæ°æ® |
| | | const bgData = computed(() => { |
| | | const maxValue = dataNum.value.length ? Math.max(...dataNum.value.map((v) => v.value)) : 0 |
| | | return dataNum.value.map(() => maxValue * 1.08 || 1) |
| | | }) |
| | | |
| | | // tooltip |
| | | const tooltip = withTooltip({ |
| | | trigger: 'axis', |
| | | formatter: function (params) { |
| | | let result = params[0].axisValueLabel + '<br/>' |
| | | result += `<div>${params[0].marker}${params[0].value}å
</div>` |
| | | return result |
| | | }, |
| | | }) |
| | | |
| | | // grid |
| | | const grid = computed(() => { |
| | | return { top: 0, left: '20%', right: '10%', bottom: 0 } |
| | | }) |
| | | |
| | | // xAxis |
| | | const xAxis = computed(() => { |
| | | return [ |
| | | { |
| | | splitLine: { show: false }, |
| | | axisLine: { show: false }, |
| | | axisLabel: { show: false }, |
| | | axisTick: { show: false }, |
| | | }, |
| | | ] |
| | | }) |
| | | |
| | | // yAxis |
| | | const yAxis = computed(() => { |
| | | return [ |
| | | { |
| | | type: 'category', |
| | | inverse: false, |
| | | data: xdataName.value, |
| | | axisLabel: { |
| | | formatter: (value) => { |
| | | if (!value) return '' |
| | | const maxLen = 6 // æ¯è¡æå¤å符æ°ï¼å¯æéè°æ´ |
| | | if (value.length <= maxLen) return `{a|${value}}` |
| | | |
| | | const lines = [] |
| | | for (let i = 0; i < value.length; i += maxLen) { |
| | | lines.push(value.slice(i, i + maxLen)) |
| | | } |
| | | // å¤è¡ææ¬ï¼æ¯è¡é½å¥åä¸ä¸ª rich æ ·å¼ |
| | | return lines.map((line) => `{a|${line}}`).join('\n') |
| | | }, |
| | | rich: { |
| | | a: { |
| | | width: 120, |
| | | fontSize: 14, |
| | | color: BI.textStrong, |
| | | padding: [5, 4, 5, 0], |
| | | align: 'right', |
| | | }, |
| | | }, |
| | | }, |
| | | axisLine: { show: false }, |
| | | axisTick: { show: false }, |
| | | splitLine: { show: false }, |
| | | }, |
| | | { |
| | | type: 'category', |
| | | data: dataNum.value.map((item) => item.value), |
| | | axisLabel: { |
| | | formatter: (params, index) => { |
| | | const value = typeof params === 'object' ? params.value : params |
| | | const percent = total.value ? ((value / total.value) * 100).toFixed(0) : 0 |
| | | const rank = getRank(index) |
| | | return `{a${rank}|${percent} }{b${rank}|%}` |
| | | }, |
| | | rich: dataNum.value.reduce((rich, _, i) => { |
| | | const rank = getRank(i) |
| | | const color = rank === 1 ? '#F0B14A' : rank <= 3 ? BI.accentBright : BI.text |
| | | rich[`a${rank}`] = { fontSize: 16, color, verticalAlign: 'bottom' } |
| | | rich[`b${rank}`] = { fontSize: 12, color, verticalAlign: 'bottom' } |
| | | return rich |
| | | }, {}), |
| | | }, |
| | | axisLine: { show: false }, |
| | | axisTick: { show: false }, |
| | | splitLine: { show: false }, |
| | | }, |
| | | ] |
| | | }) |
| | | |
| | | // seriesï¼èæ¯è½¨é + å®è²æ¸åæ¡ |
| | | const series = computed(() => [ |
| | | { |
| | | name: 'èæ¯', |
| | | z: 5, |
| | | type: 'bar', |
| | | barWidth: 18, |
| | | tooltip: { show: false }, |
| | | itemStyle: { |
| | | color: 'rgba(143, 163, 200, 0.12)', |
| | | barBorderRadius: [0, 10, 10, 0], |
| | | }, |
| | | data: bgData.value, |
| | | }, |
| | | { |
| | | name: 'éé¢', |
| | | z: 6, |
| | | type: 'bar', |
| | | barWidth: 18, |
| | | barGap: '-100%', |
| | | data: dataNum.value, |
| | | }, |
| | | ]) |
| | | |
| | | // ä¾åºåéè´æå |
| | | const fetchSupplierRanking = () => { |
| | | supplierPurchaseRanking({ type: radio1.value }) |
| | | .then((res) => { |
| | | if (res.code === 200 && Array.isArray(res.data)) { |
| | | dataArr.value = res.data.map(item => ({ |
| | | NAME: item.supplierName, |
| | | NUM: item.totalAmount |
| | | })) |
| | | } else { |
| | | // å¦ææ²¡ææ°æ®ï¼ä½¿ç¨æ¨¡ææ°æ® |
| | | dataArr.value = [ |
| | | { NAME: 'ä¾åºåA', NUM: 102 }, |
| | | { NAME: 'ä¾åºåB', NUM: 122 }, |
| | | { NAME: 'ä¾åºåC', NUM: 282 }, |
| | | { NAME: 'ä¾åºåD', NUM: 453 }, |
| | | { NAME: 'ä¾åºåE', NUM: 753 }, |
| | | ] |
| | | } |
| | | }) |
| | | .catch((error) => { |
| | | console.error('è·åä¾åºåéè´æå失败:', error) |
| | | // ä½¿ç¨æ¨¡ææ°æ® |
| | | dataArr.value = [ |
| | | { NAME: 'ä¾åºåA', NUM: 102 }, |
| | | { NAME: 'ä¾åºåB', NUM: 122 }, |
| | | { NAME: 'ä¾åºåC', NUM: 282 }, |
| | | { NAME: 'ä¾åºåD', NUM: 453 }, |
| | | { NAME: 'ä¾åºåE', NUM: 753 }, |
| | | ] |
| | | }) |
| | | } |
| | | |
| | | // å¤çæ¥æç±»å忢 |
| | | const handleDateTypeChange = () => { |
| | | fetchSupplierRanking() |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchSupplierRanking() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .panel-item-customers { |
| | | border: 1px solid var(--bi-panel-border); |
| | | background: var(--bi-panel-bg); |
| | | border-radius: 8px; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | | } |
| | | |
| | | .switch-container { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | margin-bottom: 16px; |
| | | } |
| | | </style> |
| | |
| | | |
| | | <!-- 顶鍿 颿 --> |
| | | <div class="dashboard-header"> |
| | | <div class="header-line"></div> |
| | | <div class="factory-name">PSI æ°æ®åæ</div> |
| | | <div class="header-line header-line-right"></div> |
| | | </div> |
| | | |
| | | <!-- 主è¦å
容åºå --> |
| | |
| | | import autofit from 'autofit.js' |
| | | import LeftBottom from './components/left-bottom.vue' |
| | | import CenterCenter from './components/center-center.vue' |
| | | import RightTop from '../dataDashboard/components/basic/right-top.vue' |
| | | import RightBottom from '../dataDashboard/components/basic/right-bottom.vue' |
| | | import RightTop from './components/right-top.vue' |
| | | import RightBottom from './components/right-bottom.vue' |
| | | import useUserStore from '@/store/modules/user' |
| | | import LeftTop from './components/left-top.vue' |
| | | import CenterTop from './components/center-top.vue' |
| | |
| | | position: relative; |
| | | width: 100%; |
| | | /* 页é¢å¨å¸¸è§å¸å±ä¸ï¼æé¡¶æ ï¼é»è®¤åå» 84pxï¼é¿å
å
容被è£å */ |
| | | height: calc(100vh - 84px); |
| | | height: calc(100vh - 100px); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | |
| | | position: relative; |
| | | width: 1920px; |
| | | height: 1080px; |
| | | background-image: url("@/assets/BI/backImage@2x.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | transform-origin: center center; |
| | | |
| | | /* 大å±ä¸»é¢ä»¤çï¼åç»ä»¶éè¿ CSS åéç»§æ¿ */ |
| | | --bi-panel-bg: rgba(20, 32, 66, 0.55); |
| | | --bi-panel-border: #1e3160; |
| | | --bi-text: #8fa3c8; |
| | | --bi-text-strong: #eaf1ff; |
| | | --bi-accent: #2c51d9; |
| | | --bi-accent-bright: #4a6cf0; |
| | | --bi-up: #34c77b; |
| | | --bi-down: #ff6b6b; |
| | | |
| | | background: |
| | | radial-gradient(1200px 600px at 50% -10%, rgba(44, 81, 217, 0.18) 0%, transparent 60%), |
| | | linear-gradient(180deg, #0a1229 0%, #0d1834 55%, #0a1430 100%); |
| | | } |
| | | |
| | | /* ææ·¡ç½æ ¼çº¹çï¼æ¿ä»£æ¨¡æ¿æåºå¾ */ |
| | | .data-dashboard::before { |
| | | content: ''; |
| | | position: absolute; |
| | | inset: 0; |
| | | pointer-events: none; |
| | | background-image: |
| | | linear-gradient(rgba(74, 108, 240, 0.045) 1px, transparent 1px), |
| | | linear-gradient(90deg, rgba(74, 108, 240, 0.045) 1px, transparent 1px); |
| | | background-size: 48px 48px; |
| | | mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0.9) 0%, rgba(0, 0, 0, 0.35) 100%); |
| | | } |
| | | |
| | | /* å
¨å±ç¶æçæ ·å¼ - ä½ç¨äºscale-container */ |
| | |
| | | position: relative; |
| | | z-index: 1; |
| | | height: 86px; |
| | | background-image: url("@/assets/BI/biaoti.png"); |
| | | background-size: cover; |
| | | background-repeat: no-repeat; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 28px; |
| | | } |
| | | |
| | | /* æ é¢ä¸¤ä¾§æ¸éåé线 */ |
| | | .header-line { |
| | | width: 420px; |
| | | height: 1px; |
| | | background: linear-gradient(90deg, transparent 0%, rgba(74, 108, 240, 0.5) 100%); |
| | | } |
| | | |
| | | .header-line-right { |
| | | background: linear-gradient(270deg, transparent 0%, rgba(74, 108, 240, 0.5) 100%); |
| | | } |
| | | |
| | | .factory-name { |
| | | font-weight: 600; |
| | | font-size: 52px; |
| | | color: #FFFFFF; |
| | | top: 16px; |
| | | font-size: 30px; |
| | | letter-spacing: 4px; |
| | | color: var(--bi-text-strong); |
| | | position: relative; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .factory-name::after { |
| | | content: ''; |
| | | position: absolute; |
| | | left: 50%; |
| | | bottom: -8px; |
| | | transform: translateX(-50%); |
| | | width: 48px; |
| | | height: 2px; |
| | | background: var(--bi-accent-bright); |
| | | border-radius: 1px; |
| | | } |
| | | |
| | | .fullscreen-btn { |
| | |
| | | left: 20px; |
| | | width: 40px; |
| | | height: 40px; |
| | | background: rgba(0, 20, 60, 0.8); |
| | | border: 1px solid rgba(0, 212, 255, 0.3); |
| | | background: var(--bi-panel-bg); |
| | | border: 1px solid var(--bi-panel-border); |
| | | border-radius: 6px; |
| | | color: #00d4ff; |
| | | color: var(--bi-text); |
| | | cursor: pointer; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | transition: all 0.3s; |
| | | transition: color 0.2s ease, border-color 0.2s ease; |
| | | z-index: 10000; |
| | | } |
| | | |
| | | .fullscreen-btn:hover { |
| | | background: rgba(0, 30, 90, 0.9); |
| | | border-color: rgba(0, 212, 255, 0.5); |
| | | color: var(--bi-text-strong); |
| | | border-color: var(--bi-accent-bright); |
| | | } |
| | | |
| | | .dashboard-content { |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // PSI 大å±ç»ä¸è§è§ä»¤çï¼åçè #2c51d9 ä½ç³»çå
å¶æ·±è²ä¸»é¢ï¼ |
| | | export const BI = { |
| | | // å¾è¡¨åºåè²ï¼å
å¶ä¸ä¸ï¼æ¿ä»£éèè§å
ï¼ |
| | | series: ['#4A6CF0', '#35C6D6', '#8E7CE6', '#F0B14A', '#2FB8A8', '#E86D6D'], |
| | | text: '#8FA3C8', |
| | | textStrong: '#EAF1FF', |
| | | panelBg: 'rgba(20, 32, 66, 0.55)', |
| | | panelBorder: '#1E3160', |
| | | sliceBorder: '#0D1834', |
| | | accent: '#2C51D9', |
| | | accentBright: '#4A6CF0', |
| | | up: '#34C77B', |
| | | down: '#FF6B6B', |
| | | } |
| | | |
| | | export const baseChartOptions = { |
| | | backgroundColor: 'transparent', |
| | | textStyle: { color: BI.text }, |
| | | } |
| | | |
| | | // æ·±è²ç»ä¸ tooltip æ ·å¼ï¼ä¿çåç»ä»¶èªå®ä¹ formatter |
| | | export const withTooltip = (tooltip = {}) => ({ |
| | | backgroundColor: 'rgba(13, 24, 52, 0.92)', |
| | | borderColor: BI.panelBorder, |
| | | borderWidth: 1, |
| | | textStyle: { color: BI.textStrong, fontSize: 12 }, |
| | | ...tooltip, |
| | | }) |
| | | |
| | | // é颿 ¼å¼åï¼å¤§æ°åæç®ä¸ºãä¸ãï¼ç©ºå¼å
åº |
| | | export const formatAmount = (val, unit = '') => { |
| | | const num = Number(val) |
| | | if (!Number.isFinite(num)) return `0${unit}` |
| | | if (Math.abs(num) >= 10000) { |
| | | return `${(num / 10000).toLocaleString('zh-CN', { maximumFractionDigits: 2 })}ä¸${unit}` |
| | | } |
| | | return `${num.toLocaleString('zh-CN', { maximumFractionDigits: 2 })}${unit}` |
| | | } |
| | |
| | | const props = defineProps({ |
| | | modelValue: { |
| | | type: Number, |
| | | default: 1, // é»è®¤éä¸"å¨" |
| | | default: 3, // é»è®¤éä¸"å£åº¦" |
| | | }, |
| | | }) |
| | | |
| | |
| | | |
| | | /* æªéä¸ç¶æçæ ·å¼ */ |
| | | .date-type-switch :deep(.el-radio-button__inner) { |
| | | background-color: rgba(26, 88, 176, 0.3); |
| | | color: rgba(184, 200, 224, 0.8); |
| | | border-color: rgba(255, 255, 255, 0.2); |
| | | background-color: rgba(20, 32, 66, 0.55); |
| | | color: #8fa3c8; |
| | | border-color: #1e3160; |
| | | border-radius: 0; |
| | | padding: 6px 20px; |
| | | font-size: 14px; |
| | | transition: all 0.3s; |
| | | transition: color 0.2s ease, background-color 0.2s ease; |
| | | } |
| | | |
| | | /* 第ä¸ä¸ªæé®å·¦ä¾§åè§ */ |
| | | .date-type-switch :deep(.el-radio-button:first-child .el-radio-button__inner) { |
| | | border-top-left-radius: 4px; |
| | | border-bottom-left-radius: 4px; |
| | | border-top-left-radius: 6px; |
| | | border-bottom-left-radius: 6px; |
| | | } |
| | | |
| | | /* æåä¸ä¸ªæé®å³ä¾§åè§ */ |
| | | .date-type-switch :deep(.el-radio-button:last-child .el-radio-button__inner) { |
| | | border-top-right-radius: 4px; |
| | | border-bottom-right-radius: 4px; |
| | | border-top-right-radius: 6px; |
| | | border-bottom-right-radius: 6px; |
| | | } |
| | | |
| | | /* æé®ä¹é´çåé线 */ |
| | | .date-type-switch :deep(.el-radio-button:not(:last-child) .el-radio-button__inner) { |
| | | border-right: 1px solid rgba(255, 255, 255, 0.2); |
| | | border-right: 1px solid #1e3160; |
| | | } |
| | | |
| | | /* éä¸ç¶æçæ ·å¼ */ |
| | | /* éä¸ç¶æçæ ·å¼ï¼åçèå®è² */ |
| | | .date-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) { |
| | | background: linear-gradient(180deg, #3378ff 0%, #00a4ed 100%); |
| | | background: #2c51d9; |
| | | color: #ffffff; |
| | | border-color: rgba(51, 120, 255, 0.8); |
| | | border-color: #2c51d9; |
| | | box-shadow: none; |
| | | } |
| | | |
| | | /* æ¬åææ */ |
| | | .date-type-switch :deep(.el-radio-button__inner:hover) { |
| | | color: rgba(184, 200, 224, 1); |
| | | border-color: rgba(255, 255, 255, 0.3); |
| | | color: #eaf1ff; |
| | | } |
| | | |
| | | /* éä¸ç¶ææ¬å */ |
| | | .date-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner:hover) { |
| | | background: linear-gradient(180deg, #4e8aff 0%, #4ee4ff 100%); |
| | | background: #4a6cf0; |
| | | border-color: #4a6cf0; |
| | | color: #ffffff; |
| | | } |
| | | </style> |
| | |
| | | |
| | | <style scoped> |
| | | .panel-header { |
| | | background-image: url("@/assets/BI/kehuhetongback@2x.png"); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | padding: 0 4px; |
| | | height: 36px; |
| | | } |
| | | |
| | | /* åçèç»ç«æ¡ï¼å¼åº PSI 大屿 颿 ·å¼ */ |
| | | .panel-header::before { |
| | | content: ''; |
| | | width: 3px; |
| | | height: 14px; |
| | | border-radius: 2px; |
| | | background: linear-gradient(180deg, var(--bi-accent-bright, #4a6cf0) 0%, var(--bi-accent, #2c51d9) 100%); |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .panel-title { |
| | | width: 100%; |
| | | font-weight: 500; |
| | | font-size: 16px; |
| | | color: #D9ECFF; |
| | | padding-left: 46px; |
| | | line-height: 36px; |
| | | color: var(--bi-text-strong, #eaf1ff); |
| | | letter-spacing: 1px; |
| | | } |
| | | </style> |
| | |
| | | <PanelHeader title="人ååå¸" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <div class="pie-chart-wrapper"> |
| | | <div class="pie-background" :style="{ backgroundImage: `url(${roseBorderImg})` }"></div> |
| | | <div class="pie-center"> |
| | | <span class="pie-center-value">{{ totalStaff }}</span> |
| | | <span class="pie-center-label">å计(人)</span> |
| | | </div> |
| | | <Echarts |
| | | ref="echartsRef" |
| | | :chartStyle="chartStyle" |
| | |
| | | :tooltip="pieTooltip" |
| | | :color="pieColors" |
| | | :options="pieOptions" |
| | | style="height: 320px" |
| | | /> |
| | | style="height: 100%" |
| | | > |
| | | <div v-if="loaded && !pieDatas.length" class="chart-empty">ææ æ°æ®</div> |
| | | </Echarts> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | import { deptStaffDistribution } from '@/api/viewIndex.js' |
| | | import PanelHeader from '../PanelHeader.vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import roseBorderImg from '@/assets/BI/ç«ç°å¾è¾¹æ¡.png' |
| | | import { BI, baseChartOptions, withTooltip } from '../../theme.js' |
| | | |
| | | /** |
| | | * @introduction ææ°ç»ä¸keyå¼ç¸åçé£ä¸é¡¹æååºæ¥ï¼ç»æä¸ä¸ªå¯¹è±¡ |
| | | * @param {åæ°ç±»å} array ä¼ å
¥çæ°ç» [{a:"1",b:"2"},{a:"2",b:"3"}] |
| | | * @param {åæ°ç±»å} key 屿§å a |
| | | * @return {è¿åç±»å说æ} |
| | | */ |
| | | function array2obj(array, key) { |
| | | const resObj = {} |
| | | for (let i = 0; i < array.length; i++) { |
| | | resObj[array[i][key]] = array[i] |
| | | } |
| | | return resObj |
| | | } |
| | | const echartsRef = ref(null) |
| | | const pieDatas = ref([]) |
| | | const loaded = ref(false) |
| | | |
| | | const pieColors = BI.series |
| | | |
| | | // äººåæ»æ° |
| | | const totalStaff = computed(() => |
| | | pieDatas.value.reduce((sum, d) => sum + (Number(d.value) || 0), 0) |
| | | ) |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%', |
| | | } |
| | | |
| | | const echartsRef = ref(null) |
| | | const pieDatas = ref([]) |
| | | const pieColors = ['#D1E4F5', '#5782F7', '#2F67EF', '#82BAFF', '#43e8fc', '#27EBE7'] |
| | | const pieObjData = computed(() => { |
| | | const obj = {} |
| | | pieDatas.value.forEach((d) => { |
| | | obj[d.name] = d |
| | | }) |
| | | return obj |
| | | }) |
| | | |
| | | const pieObjData = computed(() => array2obj(pieDatas.value, 'name')) |
| | | |
| | | // å¾ä¾é
ç½®ï¼å³ä¾§ç«æï¼ |
| | | const pieLegend = computed(() => { |
| | | const data = pieDatas.value.map((d, idx) => ({ |
| | | name: d.name, |
| | | icon: 'circle', |
| | | textStyle: { |
| | | fontSize: 18, |
| | | fontSize: 14, |
| | | color: pieColors[idx % pieColors.length], |
| | | }, |
| | | })) |
| | | |
| | | return { |
| | | show: data.length > 0, |
| | | orient: 'vertical', |
| | | top: 'center', |
| | | left: '50%', |
| | | itemGap: 30, |
| | | data: data, |
| | | formatter: function (name) { |
| | | itemGap: 18, |
| | | itemWidth: 8, |
| | | itemHeight: 8, |
| | | data, |
| | | formatter(name) { |
| | | const item = pieObjData.value[name] |
| | | if (!item) return name |
| | | return `{title|${name}}{value|${item.value}}{unit|人}{percent|${item.rate}}{unit|%}` |
| | | const short = name.length > 6 ? `${name.slice(0, 6)}â¦` : name |
| | | return `{title|${short}}{value|${item.value}}{unit|人}` |
| | | }, |
| | | textStyle: { |
| | | rich: { |
| | | value: { |
| | | color: '#43e8fc', |
| | | fontSize: 18, |
| | | fontWeight: 600, |
| | | padding: [0, 10, 0, 30], |
| | | title: { |
| | | fontSize: 13, |
| | | color: BI.text, |
| | | width: 96, |
| | | overflow: 'truncate', |
| | | }, |
| | | unit: { |
| | | color: '#82baff', |
| | | value: { |
| | | color: BI.textStrong, |
| | | fontSize: 14, |
| | | fontWeight: 600, |
| | | padding: [0, 50, 0, 0], |
| | | padding: [0, 0, 0, 10], |
| | | }, |
| | | percent: { |
| | | color: '#43e8fc', |
| | | fontSize: 18, |
| | | fontWeight: 600, |
| | | padding: [0, 10, 0, 0], |
| | | }, |
| | | title: { |
| | | fontSize: 18, |
| | | padding: [0, 0, 0, 0], |
| | | unit: { |
| | | color: BI.text, |
| | | fontSize: 12, |
| | | }, |
| | | }, |
| | | }, |
| | | } |
| | | }) |
| | | |
| | | const pieTooltip = { |
| | | // æç¤ºæ¡ï¼äººæ° + å æ¯ |
| | | const pieTooltip = withTooltip({ |
| | | trigger: 'item', |
| | | formatter: '{a} <br/>{b} : {c} ({d}%)', |
| | | } |
| | | confine: true, |
| | | formatter(params) { |
| | | const d = params.data || {} |
| | | const val = Number(d.value || 0) |
| | | return `${params.marker}<b>${d.name || ''}</b> ${val} 人 · ${params.percent}%` |
| | | }, |
| | | }) |
| | | |
| | | // åå±ç¯å½¢é¥¼å¾ |
| | | const pieSeries = computed(() => [ |
| | | { |
| | | name: '人ååå¸', |
| | | type: 'pie', |
| | | radius: '70%', |
| | | radius: ['48%', '68%'], |
| | | center: ['20%', '50%'], |
| | | itemStyle: { |
| | | borderColor: '#0a1c3a', |
| | | borderWidth:5, |
| | | borderColor: BI.sliceBorder, |
| | | borderWidth: 2, |
| | | borderRadius: 4, |
| | | }, |
| | | label: { |
| | | show: false |
| | | show: false, |
| | | }, |
| | | minAngle: 15, |
| | | minAngle: 8, |
| | | data: pieDatas.value, |
| | | animationType: 'scale', |
| | | animationEasing: 'elasticOut', |
| | | animationDelay: function () { |
| | | return Math.random() * 200 |
| | | }, |
| | | { |
| | | // å
å轨éç¯ |
| | | type: 'pie', |
| | | radius: ['40%', '43%'], |
| | | center: ['20%', '50%'], |
| | | silent: true, |
| | | label: { |
| | | show: false, |
| | | }, |
| | | labelLine: { |
| | | show: false, |
| | | }, |
| | | itemStyle: { |
| | | color: 'rgba(74, 108, 240, 0.22)', |
| | | }, |
| | | data: [1], |
| | | }, |
| | | ]) |
| | | |
| | | const pieOptions = { |
| | | backgroundColor: 'transparent', |
| | | textStyle: { color: '#B8C8E0' }, |
| | | } |
| | | const pieOptions = baseChartOptions |
| | | |
| | | const getDeptStaffDistribution = () => { |
| | | deptStaffDistribution().then(res => { |
| | | if (res.code === 200) { |
| | | pieDatas.value = res.data.items.map(item => ({ |
| | | deptStaffDistribution() |
| | | .then((res) => { |
| | | const items = res.data?.items || [] |
| | | pieDatas.value = items.map((item) => ({ |
| | | name: item.name, |
| | | value: parseInt(item.value), |
| | | rate: item.rate |
| | | value: Number(item.value) || 0, |
| | | rate: item.rate, |
| | | })) |
| | | } |
| | | }).catch(err => { |
| | | console.error('è·åé¨é¨äººåå叿°æ®å¤±è´¥:', err) |
| | | }) |
| | | }) |
| | | .catch((err) => { |
| | | console.error('è·åé¨é¨äººåå叿°æ®å¤±è´¥:', err) |
| | | pieDatas.value = [] |
| | | }) |
| | | .finally(() => { |
| | | loaded.value = true |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | |
| | | gap: 20px; |
| | | } |
| | | |
| | | /* 330 = 䏿 å©ä½é«åº¦ï¼ä¿è¯å·¦ä¸å³ä¸æ åºè¾¹å¯¹é½ */ |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | border: 1px solid var(--bi-panel-border); |
| | | background: var(--bi-panel-bg); |
| | | border-radius: 8px; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 370px; |
| | | height: 330px; |
| | | } |
| | | |
| | | .pie-chart-wrapper { |
| | | position: relative; |
| | | width: 100%; |
| | | height: 320px; |
| | | height: 276px; |
| | | background: transparent; |
| | | } |
| | | |
| | | |
| | | .pie-background { |
| | | .pie-center { |
| | | position: absolute; |
| | | left: 50%; |
| | | left: 20%; |
| | | top: 50%; |
| | | transform: translate(-113%, -50%); |
| | | width: 360px; |
| | | height: 360px; |
| | | background-size: contain; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | z-index: 1; |
| | | transform: translate(-50%, -50%); |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | gap: 4px; |
| | | pointer-events: none; |
| | | z-index: 2; |
| | | } |
| | | |
| | | .pie-center-value { |
| | | font-weight: 600; |
| | | font-size: 22px; |
| | | color: var(--bi-text-strong); |
| | | font-variant-numeric: tabular-nums; |
| | | } |
| | | |
| | | .pie-center-label { |
| | | font-size: 12px; |
| | | color: var(--bi-text); |
| | | } |
| | | |
| | | .chart-empty { |
| | | position: absolute; |
| | | left: 0; |
| | | right: 0; |
| | | top: 0; |
| | | height: 276px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | color: var(--bi-text); |
| | | font-size: 14px; |
| | | letter-spacing: 1px; |
| | | pointer-events: none; |
| | | } |
| | | </style> |
| | |
| | | <!-- é¡¶é¨ç»è®¡å¡ç --> |
| | | <div class="stats-cards"> |
| | | <div class="stat-card"> |
| | | <img src="@/assets/BI/icon@2x.png" alt="徿 " class="card-icon" /> |
| | | <div class="card-icon" style="color: #4a6cf0"> |
| | | <!-- åå·¥ --> |
| | | <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> |
| | | <circle cx="12" cy="8" r="4" /> |
| | | <path d="M4 21v-1a8 8 0 0 1 16 0v1" /> |
| | | </svg> |
| | | </div> |
| | | <div class="card-content"> |
| | | <span class="card-label">åå·¥æ»æ°</span> |
| | | <span class="card-value">{{ totalStaff }}</span> |
| | | <div class="card-compare" :class="compareClass(staffYoY)"> |
| | | <span>忝</span> |
| | | <span class="compare-value">{{ formatPercent(staffYoY) }}</span> |
| | | <span class="compare-icon">{{ staffYoY >= 0 ? 'â' : 'â' }}</span> |
| | | <span v-if="!isZero(staffYoY)" class="compare-icon">{{ staffYoY > 0 ? 'â' : 'â' }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="stat-card"> |
| | | <img src="@/assets/BI/icon@2x.png" alt="徿 " class="card-icon" /> |
| | | <div class="card-icon" style="color: #35c6d6"> |
| | | <!-- å®¢æ· --> |
| | | <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> |
| | | <circle cx="9" cy="8" r="3.5" /> |
| | | <path d="M2.5 20v-1a6.5 6.5 0 0 1 13 0v1" /> |
| | | <path d="M16 5a3.5 3.5 0 0 1 0 6.5" /> |
| | | <path d="M17.5 13.5A6.5 6.5 0 0 1 21.5 20" /> |
| | | </svg> |
| | | </div> |
| | | <div class="card-content"> |
| | | <span class="card-label">å®¢æ·æ»æ°</span> |
| | | <span class="card-value">{{ totalCustomers }}</span> |
| | | <div class="card-compare" :class="compareClass(customersYoY)"> |
| | | <span>忝</span> |
| | | <span class="compare-value">{{ formatPercent(customersYoY) }}</span> |
| | | <span class="compare-icon">{{ customersYoY >= 0 ? 'â' : 'â' }}</span> |
| | | <span v-if="!isZero(customersYoY)" class="compare-icon">{{ customersYoY > 0 ? 'â' : 'â' }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="stat-card"> |
| | | <img src="@/assets/BI/icon@2x.png" alt="徿 " class="card-icon" /> |
| | | <div class="card-icon" style="color: #8e7ce6"> |
| | | <!-- ä¾åºå --> |
| | | <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> |
| | | <rect x="1.5" y="5" width="13" height="11" rx="1.5" /> |
| | | <path d="M14.5 9h4l3 3.5v3.5h-2" /> |
| | | <circle cx="6" cy="18.5" r="1.8" /> |
| | | <circle cx="17.5" cy="18.5" r="1.8" /> |
| | | </svg> |
| | | </div> |
| | | <div class="card-content"> |
| | | <span class="card-label">ä¾åºåæ»æ°</span> |
| | | <span class="card-value">{{ totalSuppliers }}</span> |
| | | <div class="card-compare" :class="compareClass(suppliersYoY)"> |
| | | <span>忝</span> |
| | | <span class="compare-value">{{ formatPercent(suppliersYoY) }}</span> |
| | | <span class="compare-icon">{{ suppliersYoY >= 0 ? 'â' : 'â' }}</span> |
| | | <span v-if="!isZero(suppliersYoY)" class="compare-icon">{{ suppliersYoY > 0 ? 'â' : 'â' }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | |
| | | <!-- 设å¤ç»è®¡ --> |
| | | <div class="equipment-stats"> |
| | | <div class="equipment-header"> |
| | | <img |
| | | src="@/assets/BI/shujutongjiicon@2x.png" |
| | | alt="徿 " |
| | | class="equipment-icon" |
| | | /> |
| | | <span class="equipment-title">设å¤ç»è®¡</span> |
| | | <div class="section-header"> |
| | | <svg class="section-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> |
| | | <circle cx="12" cy="12" r="3" /> |
| | | <path d="M19.4 15a1.7 1.7 0 0 0 .34 1.87l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.7 1.7 0 0 0-1.87-.34 1.7 1.7 0 0 0-1.03 1.55V21a2 2 0 1 1-4 0v-.09a1.7 1.7 0 0 0-1.11-1.55 1.7 1.7 0 0 0-1.87.34l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.7 1.7 0 0 0 .34-1.87 1.7 1.7 0 0 0-1.55-1.03H3a2 2 0 1 1 0-4h.09a1.7 1.7 0 0 0 1.55-1.11 1.7 1.7 0 0 0-.34-1.87l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.7 1.7 0 0 0 1.87.34h.08A1.7 1.7 0 0 0 10.31 3.1V3a2 2 0 1 1 4 0v.09a1.7 1.7 0 0 0 1.03 1.55 1.7 1.7 0 0 0 1.87-.34l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.7 1.7 0 0 0-.34 1.87v.08a1.7 1.7 0 0 0 1.55 1.03H21a2 2 0 1 1 0 4h-.09a1.7 1.7 0 0 0-1.55 1.03z" /> |
| | | </svg> |
| | | <span class="section-title">设å¤ç»è®¡</span> |
| | | </div> |
| | | <div class="equipment-items"> |
| | | <div class="equipment-item"> |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- äºä»¶åç§° --> |
| | | <!-- å¾
åäºä»¶ --> |
| | | <div class="event-info"> |
| | | <div class="event-header"> |
| | | <img |
| | | src="@/assets/BI/shijianmingxiicon@2x.png" |
| | | alt="徿 " |
| | | class="event-icon" |
| | | /> |
| | | <span class="event-title">äºä»¶åç§°</span> |
| | | <div class="section-header"> |
| | | <svg class="section-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> |
| | | <rect x="4" y="4" width="16" height="17" rx="2" /> |
| | | <path d="M9 2.5h6v3H9z" /> |
| | | <path d="M8.5 11h7M8.5 15.5h5" /> |
| | | </svg> |
| | | <span class="section-title">äºä»¶åç§°</span> |
| | | </div> |
| | | <div class="event-content"> |
| | | <ul class="todo-list" v-if="todoList.length > 0" ref="refTodoList"> |
| | | <li v-for="item in todoList" :key="item.id"> |
| | | <div |
| | | style=" |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: space-between; |
| | | width: 100%; |
| | | gap: 20px; |
| | | " |
| | | > |
| | | <div class="todo-division">å¾
åäºç±ï¼{{ item.approveReason }}</div> |
| | | <div style="display: flex;justify-content: space-between;align-items: center;" |
| | | > |
| | | <div class="todo-title">ç³è¯·ç±»åï¼{{ item.approveTypeName }}</div> |
| | | <div class="todo-division">ç³è¯·é¨é¨ï¼{{ item.approveDeptName }}</div> |
| | | <div class="todo-time">{{ item.approveTime }}</div> |
| | | </div> |
| | | |
| | | <div class="todo-row"> |
| | | <span class="todo-division todo-reason">å¾
åäºç±ï¼{{ item.approveReason || '-' }}</span> |
| | | <span class="todo-title">ç³è¯·ç±»åï¼{{ item.approveTypeName || '-' }}</span> |
| | | <span class="todo-division">ç³è¯·é¨é¨ï¼{{ item.approveDeptName || '-' }}</span> |
| | | <span class="todo-time">{{ item.approveTime || '-' }}</span> |
| | | </div> |
| | | </li> |
| | | </ul> |
| | | <div v-else style="text-align: center">ææ æ°æ®</div> |
| | | <div v-else class="event-empty">ææ æ°æ®</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | return `${Math.abs(num).toFixed(2)}%` |
| | | } |
| | | |
| | | const compareClass = (val) => (val >= 0 ? 'compare-up' : 'compare-down') |
| | | const isZero = (val) => !(Number(val) || 0) |
| | | |
| | | const compareClass = (val) => { |
| | | const num = Number(val) || 0 |
| | | if (num > 0) return 'compare-up' |
| | | if (num < 0) return 'compare-down' |
| | | return 'compare-flat' |
| | | } |
| | | |
| | | // è·ååå·¥ã客æ·ãä¾åºåæ°é |
| | | const getNum = () => { |
| | | summaryStatistics().then((res) => { |
| | | totalStaff.value = res.data.totalStaff |
| | | staffYoY.value = res.data.staffGrowthRate |
| | | totalCustomers.value = res.data.totalCustomer |
| | | customersYoY.value = res.data.customerGrowthRate |
| | | totalSuppliers.value = res.data.totalSupplier |
| | | suppliersYoY.value = res.data.supplierGrowthRate |
| | | const d = res.data || {} |
| | | totalStaff.value = Number(d.totalStaff) || 0 |
| | | staffYoY.value = Number(d.staffGrowthRate) || 0 |
| | | totalCustomers.value = Number(d.totalCustomer) || 0 |
| | | customersYoY.value = Number(d.customerGrowthRate) || 0 |
| | | totalSuppliers.value = Number(d.totalSupplier) || 0 |
| | | suppliersYoY.value = Number(d.supplierGrowthRate) || 0 |
| | | }).catch(err => { |
| | | console.error('è·ååºç¡ç»è®¡æ°æ®å¤±è´¥:', err) |
| | | }) |
| | |
| | | pageSize: -1, |
| | | } |
| | | getLedgerPage(params).then((res) => { |
| | | equipmentNum.value = res.data.total |
| | | equipmentNum.value = Number(res.data?.total) || 0 |
| | | }) |
| | | getRepairPage({ ...params, status: 0 }).then((res) => { |
| | | equipmentRepair.value = res.data.total |
| | | equipmentRepair.value = Number(res.data?.total) || 0 |
| | | }) |
| | | getUpkeepPage({ ...params, status: 0 }).then((res) => { |
| | | equipmentMaintain.value = res.data.total |
| | | equipmentMaintain.value = Number(res.data?.total) || 0 |
| | | }) |
| | | measuringInstrumentListPage(params).then((res) => { |
| | | totalMeasuring.value = res.data.total |
| | | totalMeasuring.value = Number(res.data?.total) || 0 |
| | | }) |
| | | } |
| | | |
| | |
| | | // å¾
åäºé¡¹ |
| | | const todoInfoS = () => { |
| | | homeTodos().then((res) => { |
| | | todoList.value = res.data |
| | | todoList.value = res.data || [] |
| | | // å¨è·åå°å¾
åäºé¡¹æ°æ®åï¼åå§åæ»å¨åè½ |
| | | nextTick(() => { |
| | | initTodoListScroll() |
| | | }) |
| | | }).catch(err => { |
| | | console.error('è·åå¾
åäºé¡¹å¤±è´¥:', err) |
| | | todoList.value = [] |
| | | }) |
| | | } |
| | | |
| | |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | background-image: url('@/assets/BI/border@2x.png'); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | background: var(--bi-panel-bg); |
| | | border: 1px solid var(--bi-panel-border); |
| | | border-radius: 8px; |
| | | position: relative; |
| | | overflow: hidden; |
| | | height: 142px; |
| | | } |
| | | |
| | | /* é¡¶é¨åçèäº®çº¿ï¼æ¿ä»£å¾çæè¾¹ */ |
| | | .stat-card::before { |
| | | content: ''; |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | height: 2px; |
| | | background: linear-gradient(90deg, var(--bi-accent) 0%, transparent 70%); |
| | | } |
| | | |
| | | .card-icon { |
| | | width: 100px; |
| | | height: 100px; |
| | | margin: 20px 20px 0 10px; |
| | | width: 52px; |
| | | height: 52px; |
| | | margin: 0 16px 0 20px; |
| | | border-radius: 8px; |
| | | background: rgba(74, 108, 240, 0.12); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .card-content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | gap: 8px; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .card-value { |
| | | font-weight: 500; |
| | | font-size: 40px; |
| | | background: linear-gradient(360deg, #008bfd 0%, #ffffff 100%); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | font-weight: 600; |
| | | font-size: 36px; |
| | | color: var(--bi-text-strong); |
| | | font-variant-numeric: tabular-nums; |
| | | line-height: 1.1; |
| | | } |
| | | |
| | | .card-label { |
| | | font-weight: 400; |
| | | font-size: 19px; |
| | | color: rgba(208, 231, 255, 0.7); |
| | | font-size: 16px; |
| | | color: var(--bi-text); |
| | | } |
| | | |
| | | .card-compare { |
| | |
| | | align-items: center; |
| | | gap: 6px; |
| | | font-size: 15px; |
| | | color: #d0e7ff; |
| | | } |
| | | |
| | | .card-compare > span:first-child { |
| | | font-size: 13px; |
| | | opacity: 0.8; |
| | | color: var(--bi-text); |
| | | } |
| | | |
| | | .compare-value { |
| | | font-weight: 600; |
| | | color: var(--bi-text); |
| | | font-variant-numeric: tabular-nums; |
| | | } |
| | | |
| | | .compare-icon { |
| | |
| | | |
| | | .compare-up .compare-value, |
| | | .compare-up .compare-icon { |
| | | color: #00c853; |
| | | color: var(--bi-up, #34c77b); |
| | | } |
| | | |
| | | .compare-down .compare-value, |
| | | .compare-down .compare-icon { |
| | | color: #ff5252; |
| | | color: var(--bi-down, #ff6b6b); |
| | | } |
| | | |
| | | /* æå¹³ï¼ä¸æ¾ç¤ºç®å¤´ï¼é¢è²ä¸æ§ */ |
| | | .compare-flat .compare-value { |
| | | color: var(--bi-text); |
| | | } |
| | | |
| | | .equipment-stats, |
| | | .event-info { |
| | | background: var(--bi-panel-bg); |
| | | border: 1px solid var(--bi-panel-border); |
| | | border-radius: 8px; |
| | | padding: 14px 18px; |
| | | } |
| | | |
| | | .equipment-stats { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | height: 240px; |
| | | padding-top: 0px; |
| | | margin-top: 20px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .equipment-header { |
| | | font-weight: 500; |
| | | font-size: 21px; |
| | | .event-info { |
| | | height: 186px; |
| | | margin-top: 20px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .section-header { |
| | | display: flex; |
| | | border-bottom: 1px solid; |
| | | border-image: linear-gradient( |
| | | 270deg, |
| | | rgba(0, 126, 255, 0) 0%, |
| | | rgba(0, 126, 255, 0.4549) 35%, |
| | | #007eff 78%, |
| | | #007eff 100% |
| | | ) |
| | | 1; |
| | | padding-bottom: 2px; |
| | | align-items: center; |
| | | gap: 8px; |
| | | color: var(--bi-accent-bright); |
| | | border-bottom: 1px solid rgba(143, 163, 200, 0.15); |
| | | padding-bottom: 10px; |
| | | } |
| | | |
| | | .equipment-title { |
| | | .section-icon { |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .section-title { |
| | | font-weight: 500; |
| | | font-size: 18px; |
| | | background: linear-gradient(360deg, #056dff 0%, #43e8fc 100%); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | line-height: 50px; |
| | | } |
| | | |
| | | .equipment-icon { |
| | | width: 50px; |
| | | height: 50px; |
| | | font-size: 16px; |
| | | color: var(--bi-text-strong); |
| | | letter-spacing: 1px; |
| | | } |
| | | |
| | | .equipment-items { |
| | | display: flex; |
| | | justify-content: space-around; |
| | | gap: 30px; |
| | | gap: 20px; |
| | | padding-top: 22px; |
| | | } |
| | | |
| | | .equipment-item { |
| | | text-align: center; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .equipment-value { |
| | | display: block; |
| | | font-weight: 500; |
| | | font-size: 40px; |
| | | color: #ffffff; |
| | | width: 120px; |
| | | height: 110px; |
| | | line-height: 110px; |
| | | background-image: url('@/assets/BI/shujutongji@2x.png'); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | margin-bottom: 8px; |
| | | font-weight: 600; |
| | | font-size: 36px; |
| | | color: var(--bi-text-strong); |
| | | font-variant-numeric: tabular-nums; |
| | | line-height: 1.1; |
| | | } |
| | | |
| | | .equipment-label { |
| | | font-weight: 500; |
| | | font-size: 16px; |
| | | color: #fffffe; |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: var(--bi-text); |
| | | } |
| | | |
| | | .event-info { |
| | | background-image: url('@/assets/BI/shijianmingchengbeijing@2x.png'); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | padding: 20px; |
| | | padding-top: 10px; |
| | | height: 186px; |
| | | } |
| | | |
| | | .event-header { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .event-icon { |
| | | width: 40px; |
| | | height: 40px; |
| | | } |
| | | |
| | | .event-title { |
| | | font-weight: 500; |
| | | font-size: 18px; |
| | | color: #fffffe; |
| | | line-height: 30px; |
| | | .event-content { |
| | | padding-top: 8px; |
| | | } |
| | | |
| | | .todo-list { |
| | | list-style: none; |
| | | padding: 0; |
| | | margin: 0; |
| | | height: 120px; /* æç¨æ·è¦æ±è°æ´é«åº¦ */ |
| | | height: 96px; |
| | | overflow: hidden; |
| | | font-size: 15px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .todo-list li { |
| | | border-radius: 8px; |
| | | margin-bottom: 12px; |
| | | padding: 12px 40px; |
| | | height: 74px; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .todo-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | gap: 24px; |
| | | padding: 9px 14px; |
| | | background: rgba(13, 24, 52, 0.6); |
| | | border: 1px solid rgba(30, 49, 96, 0.8); |
| | | border-radius: 6px; |
| | | } |
| | | |
| | | .todo-title { |
| | | font-weight: 400; |
| | | font-size: 16px; |
| | | color: #fffffe; |
| | | position: relative; |
| | | font-size: 14px; |
| | | color: var(--bi-text); |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | |
| | | |
| | | .todo-division { |
| | | font-weight: 400; |
| | | font-size: 16px; |
| | | color: #fffffe; |
| | | position: relative; |
| | | font-size: 14px; |
| | | color: var(--bi-text); |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .todo-division::before { |
| | | content: ''; |
| | | position: absolute; |
| | | left: -20px; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 6px; |
| | | height: 6px; |
| | | background: #498ceb; |
| | | border-radius: 50%; |
| | | .todo-reason { |
| | | color: var(--bi-text-strong); |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | min-width: 0; |
| | | flex: 1; |
| | | } |
| | | |
| | | .todo-time { |
| | | font-weight: 400; |
| | | font-size: 16px; |
| | | color: #fffffe; |
| | | background: rgba(24, 93, 190, 0.4); |
| | | border-radius: 5px 5px 5px 5px; |
| | | padding: 5px 10px; |
| | | font-size: 13px; |
| | | color: var(--bi-text); |
| | | background: rgba(74, 108, 240, 0.15); |
| | | border-radius: 4px; |
| | | padding: 3px 10px; |
| | | font-variant-numeric: tabular-nums; |
| | | } |
| | | |
| | | .event-empty { |
| | | height: 96px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | color: var(--bi-text); |
| | | font-size: 14px; |
| | | letter-spacing: 1px; |
| | | } |
| | | </style> |
| | |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis1" |
| | | :yAxis="yAxis1" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }" |
| | | style="height: 260px" |
| | | /> |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#8FA3C8' } }" |
| | | style="height: 370px" |
| | | > |
| | | <div v-if="loaded && !hasRevenueData" class="chart-empty">ææ è¥æ¶æ°æ®</div> |
| | | </Echarts> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { ref, computed, onMounted } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from '../PanelHeader.vue' |
| | | import DateTypeSwitch from '../DateTypeSwitch.vue' |
| | | import { customerRevenueAnalysis } from '@/api/viewIndex.js' |
| | | import { listCustomer } from '@/api/basicData/customerFile.js' |
| | | import { BI, withTooltip } from '../../theme.js' |
| | | |
| | | const dateType = ref(1) // 1=å¨ 2=æ 3=å£åº¦ |
| | | const dateType = ref(3) // 1=å¨ 2=æ 3=å£åº¦ï¼é»è®¤å£åº¦ |
| | | const customerValue = ref(null) |
| | | const customerOptions = ref([]) |
| | | |
| | |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '150%', |
| | | height: '100%', |
| | | } |
| | | |
| | | // æ¯å¦å·²æ¿å°ååºï¼åºåå è½½ä¸ä¸ççæ æ°æ®ï¼ |
| | | const loaded = ref(false) |
| | | |
| | | // ææ°æ®ææ¾ç¤ºæ±å |
| | | const hasRevenueData = computed( |
| | | () => (revenueData.value.items || []).some((i) => Number(i.value) > 0) |
| | | ) |
| | | |
| | | const grid = { |
| | | left: '3%', |
| | |
| | | |
| | | const barLegend = { |
| | | show: false, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | textStyle: { color: BI.text }, |
| | | data: ['è¥æ¶'], |
| | | } |
| | | |
| | |
| | | x2: 0, |
| | | y2: 0, |
| | | colorStops: [ |
| | | // linear-gradient(360deg, rgba(0,164,237,0) 0%, #4EE4FF 100%) |
| | | { offset: 0, color: 'rgba(0,164,237,0)' }, |
| | | { offset: 1, color: '#4EE4FF' }, |
| | | { offset: 0, color: 'rgba(74, 108, 240, 0)' }, |
| | | { offset: 1, color: '#4A6CF0' }, |
| | | ], |
| | | }, |
| | | }, |
| | |
| | | }, |
| | | ]) |
| | | |
| | | const tooltip = { |
| | | const tooltip = withTooltip({ |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'shadow', |
| | |
| | | formatter: function (params) { |
| | | let result = params[0].axisValueLabel + '<br/>' |
| | | params.forEach((item) => { |
| | | result += `<div>${item.marker} ${item.seriesName}: ${item.value}</div>` |
| | | const val = Number(item.value || 0).toLocaleString('zh-CN') |
| | | result += `<div>${item.marker} ${item.seriesName}: ${val} å
</div>` |
| | | }) |
| | | return result |
| | | }, |
| | | } |
| | | }) |
| | | |
| | | const xAxis1 = ref([ |
| | | { |
| | | type: 'category', |
| | | axisTick: { show: false }, |
| | | axisLabel: { color: '#B8C8E0' }, |
| | | axisLine: { show: false }, |
| | | axisLabel: { color: BI.text, fontSize: 12 }, |
| | | data: [], |
| | | }, |
| | | ]) |
| | |
| | | const yAxis1 = [ |
| | | { |
| | | type: 'value', |
| | | axisLabel: { color: '#B8C8E0' }, |
| | | axisLabel: { color: BI.text, fontSize: 12 }, |
| | | axisLine: { show: false }, |
| | | axisTick: { show: false }, |
| | | splitLine: { lineStyle: { color: 'rgba(143, 163, 200, 0.15)' } }, |
| | | }, |
| | | ] |
| | | |
| | |
| | | type: dateType.value |
| | | } |
| | | |
| | | loaded.value = false |
| | | customerRevenueAnalysis(params) |
| | | .then((res) => { |
| | | xAxis1.value[0].data = [] |
| | |
| | | const items = res.data?.items || [] |
| | | items.forEach((item) => { |
| | | xAxis1.value[0].data.push(item.name) |
| | | barSeries1.value[0].data.push(item.value) |
| | | barSeries1.value[0].data.push(Number(item.value) || 0) |
| | | }) |
| | | revenueData.value = res.data |
| | | revenueData.value = res.data || { items: [] } |
| | | }) |
| | | .catch((error) => { |
| | | console.error('è·å客æ·è¥æ¶åæå¤±è´¥:', error) |
| | | revenueData.value = { items: [] } |
| | | }) |
| | | .finally(() => { |
| | | loaded.value = true |
| | | }) |
| | | } |
| | | |
| | |
| | | /* 䏿æ¡é£æ ¼ï¼ä¸ DateTypeSwitch ä¿æä¸è´ï¼æ·±è²åéæãæµ
è²æåãç»è¾¹æ¡ï¼ */ |
| | | .customer-select :deep(.el-input__wrapper), |
| | | .customer-select :deep(.el-select__wrapper) { |
| | | background-color: rgba(26, 88, 176, 0.3); |
| | | border: 1px solid rgba(255, 255, 255, 0.2); |
| | | background-color: rgba(20, 32, 66, 0.55); |
| | | border: 1px solid #1e3160; |
| | | box-shadow: none; |
| | | } |
| | | |
| | | .customer-select :deep(.el-input__inner) { |
| | | color: rgba(184, 200, 224, 0.9); |
| | | color: #8fa3c8; |
| | | } |
| | | |
| | | .customer-select :deep(.el-input__inner::placeholder) { |
| | | color: rgba(184, 200, 224, 0.6); |
| | | color: rgba(143, 163, 200, 0.6); |
| | | } |
| | | |
| | | .customer-select :deep(.el-select__caret), |
| | | .customer-select :deep(.el-icon) { |
| | | color: rgba(184, 200, 224, 0.8); |
| | | color: #8fa3c8; |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | border: 1px solid var(--bi-panel-border); |
| | | background: var(--bi-panel-bg); |
| | | border-radius: 8px; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 478px; |
| | | } |
| | | |
| | | .chart-empty { |
| | | position: absolute; |
| | | left: 0; |
| | | right: 0; |
| | | top: 0; |
| | | height: 370px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | color: var(--bi-text); |
| | | font-size: 14px; |
| | | letter-spacing: 1px; |
| | | pointer-events: none; |
| | | } |
| | | </style> |
| | | |
| | | <style> |
| | |
| | | <div> |
| | | <PanelHeader title="产å大类" /> |
| | | <div class="panel-item-customers"> |
| | | <div class="pie-chart-wrapper" ref="pieWrapperRef"> |
| | | <div class="pie-background" ref="pieBackgroundRef"></div> |
| | | <div class="pie-chart-wrapper"> |
| | | <div class="pie-center"> |
| | | <span class="pie-center-value">{{ totalCategories }}</span> |
| | | <span class="pie-center-label">å计(ç±»)</span> |
| | | </div> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | |
| | | :options="pieOptions" |
| | | style="height: 100%" |
| | | class="land-chart" |
| | | /> |
| | | > |
| | | <div v-if="loaded && !dataList.length" class="chart-empty">ææ æ°æ®</div> |
| | | </Echarts> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount } from 'vue' |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from '../PanelHeader.vue' |
| | | import { productCategoryDistribution } from '@/api/viewIndex.js' |
| | | import { useChartBackground } from '@/hooks/useChartBackground.js' |
| | | import { BI, baseChartOptions, withTooltip } from '../../theme.js' |
| | | |
| | | const pieWrapperRef = ref(null) |
| | | const pieBackgroundRef = ref(null) |
| | | const chart = ref(null) |
| | | |
| | | // æ°æ®åè¡¨ï¼æ¥èªæ¥å£ï¼ |
| | | const dataList = ref([]) |
| | | const loaded = ref(false) |
| | | |
| | | // é¢è²å表 |
| | | const landColors = ['#26FFCB', '#24CBFF', '#35FBF4', '#2651FF', '#D1E4F5', '#5782F7', '#2F67EF', '#82BAFF'] |
| | | const landColors = BI.series |
| | | |
| | | // label 坿æ¬ï¼ä¸ºæ¯ä¸ªé¢è²çæä¸ä¸ªå°åç¹æ ·å¼ï¼ç¡®ä¿å¨ label ä¸å¯è§ï¼ |
| | | const dotRich = landColors.reduce((acc, color, idx) => { |
| | | acc[`dot${idx}`] = { |
| | | width: 8, |
| | | height: 8, |
| | | borderRadius: 8, |
| | | backgroundColor: color, |
| | | align: 'center', |
| | | } |
| | | return acc |
| | | }, {}) |
| | | // å¤§ç±»æ»æ°ï¼ååç±»ä¸çç±»ç®æ°ä¹åï¼ |
| | | const totalCategories = computed(() => |
| | | dataList.value.reduce((sum, d) => sum + (Number(d.value) || 0), 0) |
| | | ) |
| | | |
| | | const landObjData = computed(() => { |
| | | const obj = {} |
| | | dataList.value.forEach((d) => { |
| | | obj[d.name] = d |
| | | }) |
| | | return obj |
| | | }) |
| | | |
| | | // å¾ä¾é
ç½®ï¼å³ä¾§ç«æï¼ |
| | | const landLegend = { |
| | | show: false, |
| | | icon: 'circle', |
| | | data: [], |
| | | right: '8%', |
| | | top: '40%', |
| | | orient: 'vertical', |
| | | itemGap: 14, |
| | | itemWidth: 6, |
| | | itemHeight: 6, |
| | | textStyle: { |
| | | fontSize: 12, |
| | | rich: { |
| | | unit: { |
| | | color: '#fff', |
| | | fontSize: 12, |
| | | padding: [0, 10, 0, 0], |
| | | }, |
| | | text: { |
| | | width: 60, |
| | | color: '#fff', |
| | | fontSize: 12, |
| | | const landLegend = computed(() => { |
| | | const data = dataList.value.map((d, idx) => ({ |
| | | name: d.name, |
| | | icon: 'circle', |
| | | textStyle: { |
| | | fontSize: 14, |
| | | color: landColors[idx % landColors.length], |
| | | }, |
| | | })) |
| | | |
| | | return { |
| | | show: data.length > 0, |
| | | orient: 'vertical', |
| | | top: 'center', |
| | | left: '50%', |
| | | itemGap: 20, |
| | | itemWidth: 8, |
| | | itemHeight: 8, |
| | | data, |
| | | formatter(name) { |
| | | const item = landObjData.value[name] |
| | | if (!item) return name |
| | | const short = name.length > 8 ? `${name.slice(0, 8)}â¦` : name |
| | | return `{title|${short}}{value|${item.value}}{unit|ç±»}` |
| | | }, |
| | | textStyle: { |
| | | rich: { |
| | | title: { |
| | | fontSize: 13, |
| | | color: BI.text, |
| | | width: 108, |
| | | overflow: 'truncate', |
| | | }, |
| | | value: { |
| | | color: BI.textStrong, |
| | | fontSize: 14, |
| | | fontWeight: 600, |
| | | padding: [0, 0, 0, 10], |
| | | }, |
| | | unit: { |
| | | color: BI.text, |
| | | fontSize: 12, |
| | | }, |
| | | }, |
| | | }, |
| | | }, |
| | | formatter: function (name) { |
| | | const list = dataList.value || [] |
| | | const item = list.find((d) => d.name === name) |
| | | if (!item) return name |
| | | const val = Number(item.value || 0) |
| | | const totalValue = list.reduce((sum, it) => sum + Number(it.value || 0), 0) |
| | | const percent = totalValue ? ((val / totalValue) * 100).toFixed(2) : '0.00' |
| | | return `{text|${name}}${val}{unit| å
¬é¡·}${percent}{unit|%}` |
| | | }, |
| | | } |
| | | } |
| | | }) |
| | | |
| | | // æç¤ºæ¡ |
| | | const landTooltip = { |
| | | // triggerOn: 'hover', |
| | | alwaysShowContent: true, |
| | | position: function (pt) { |
| | | return [pt[0], 130] |
| | | // æç¤ºæ¡ï¼å æ¯ + ååç±»æç» |
| | | const landTooltip = withTooltip({ |
| | | trigger: 'item', |
| | | confine: true, |
| | | formatter(params) { |
| | | const d = params.data || {} |
| | | const val = Number(d.value || 0) |
| | | let html = `${params.marker}<b>${d.name || ''}</b> ${val} 类 · ${params.percent}%` |
| | | const children = Array.isArray(d.children) ? d.children : [] |
| | | if (children.length) { |
| | | const shown = children.slice(0, 8).map((c) => c.name).join('ã') |
| | | html += `<br/><span style="color:${BI.text}">åç±»ï¼</span>${shown}${children.length > 8 ? 'â¦' : ''}` |
| | | } |
| | | return html |
| | | }, |
| | | formatter: function (params) { |
| | | return `${params.name} (${params.value}ç±»)` |
| | | }, |
| | | } |
| | | }) |
| | | |
| | | // åå±ç¯å½¢é¥¼å¾ |
| | | const landSeries = ref([ |
| | | const landSeries = computed(() => [ |
| | | { |
| | | name: '产å大类', |
| | | type: 'pie', |
| | | radius: ['35%', '55%'], |
| | | center: ['50%', '50%'], |
| | | label: { |
| | | show: true, |
| | | position: 'outside', |
| | | color: '#fff', |
| | | fontSize: 12, |
| | | lineHeight: 18, |
| | | rich: { |
| | | ...dotRich, |
| | | parent: { fontSize: 14, fontWeight: 600, color: '#fff', lineHeight: 20, overflow: 'break' }, |
| | | child: { fontSize: 12, color: '#fff', lineHeight: 18 }, |
| | | }, |
| | | formatter: function (params) { |
| | | const children = params?.data?.children || [] |
| | | const parentName = params?.data?.name || '' |
| | | const rawVal = params?.data?.value |
| | | const parentValue = typeof rawVal === 'number' && !Number.isNaN(rawVal) ? rawVal : (Number(rawVal) || 0) |
| | | const dotKey = `dot${(params?.dataIndex || 0) % landColors.length}` |
| | | const dot = `{${dotKey}|} ` |
| | | const parentLine = `${dot}{parent|${parentName} (${parentValue}ç±»)}` |
| | | if (!children.length) return parentLine |
| | | // ç¶çº§å
¨é¨æ¾ç¤ºï¼å级æå¤ 5 个ï¼è¶
åºæ¾ç¤ºçç¥å· |
| | | const displayed = children.slice(0, 5).map((c) => `{child|${c.name}}`) |
| | | if (children.length > 5) displayed.push('{child|â¦}') |
| | | return [parentLine, ...displayed].join('\n') |
| | | }, |
| | | }, |
| | | labelLine: { |
| | | show: true, |
| | | length: 20, |
| | | length2: 20, |
| | | lineStyle: { |
| | | color: '#B8C8E0', |
| | | }, |
| | | }, |
| | | radius: ['42%', '58%'], |
| | | center: ['25%', '50%'], |
| | | itemStyle: { |
| | | color: function (params) { |
| | | return landColors[params.dataIndex % landColors.length] |
| | | }, |
| | | borderColor: BI.sliceBorder, |
| | | borderWidth: 2, |
| | | borderRadius: 4, |
| | | }, |
| | | label: { |
| | | show: false, |
| | | }, |
| | | minAngle: 8, |
| | | data: dataList.value, |
| | | animationType: 'scale', |
| | | animationEasing: 'elasticOut', |
| | | }, |
| | | { |
| | | // å
å |
| | | // å
å轨éç¯ |
| | | type: 'pie', |
| | | radius: ['35%', '40%'], |
| | | center: ['50%', '50%'], |
| | | radius: ['33%', '36%'], |
| | | center: ['25%', '50%'], |
| | | silent: true, |
| | | label: { |
| | | show: false, |
| | |
| | | show: false, |
| | | }, |
| | | itemStyle: { |
| | | color: 'rgba(0, 127, 255, 0.25)', |
| | | color: 'rgba(74, 108, 240, 0.22)', |
| | | }, |
| | | data: [1], |
| | | }, |
| | |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '126%', |
| | | height: '100%', |
| | | } |
| | | |
| | | const pieOptions = { |
| | | backgroundColor: 'transparent', |
| | | textStyle: { color: '#B8C8E0' }, |
| | | } |
| | | |
| | | // 使ç¨å°è£
çèæ¯ä½ç½®è°æ´æ¹æ³ï¼å¯èªå®ä¹åç§»å¼ |
| | | const { adjustBackgroundPosition, init: initBackground, cleanup: cleanupBackground } = useChartBackground({ |
| | | wrapperRef: pieWrapperRef, |
| | | backgroundRef: pieBackgroundRef, |
| | | offsetX: '-51.5%', // X è½´åç§»ï¼å¯å¨æè°æ´ |
| | | offsetY: '-39%', // Y è½´åç§»ï¼å¯å¨æè°æ´ |
| | | watchData: dataList // ç嬿°æ®ååï¼èªå¨è°æ´ä½ç½® |
| | | }) |
| | | const pieOptions = baseChartOptions |
| | | |
| | | const loadData = async () => { |
| | | try { |
| | |
| | | rate: it.rate, |
| | | children: Array.isArray(it.children) ? it.children : [], |
| | | })) |
| | | landLegend.data = dataList.value.map((d) => d.name) |
| | | landSeries.value[0].data = dataList.value |
| | | // æ°æ®å è½½å®æåè°æ´èæ¯ä½ç½® |
| | | adjustBackgroundPosition() |
| | | } catch (e) { |
| | | console.error('è·å产å大类åå¸å¤±è´¥:', e) |
| | | dataList.value = [] |
| | | landLegend.data = [] |
| | | landSeries.value[0].data = [] |
| | | } finally { |
| | | loaded.value = true |
| | | } |
| | | } |
| | | |
| | | |
| | | onMounted(() => { |
| | | loadData() |
| | | initBackground() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | cleanupBackground() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | border: 1px solid var(--bi-panel-border); |
| | | background: var(--bi-panel-bg); |
| | | border-radius: 8px; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 420px; |
| | |
| | | .pie-chart-wrapper { |
| | | position: relative; |
| | | width: 100%; |
| | | height: 320px; |
| | | height: 384px; |
| | | background: transparent; |
| | | } |
| | | |
| | | .pie-background { |
| | | .pie-center { |
| | | position: absolute; |
| | | width: 360px; |
| | | height: 360px; |
| | | background-image: url('@/assets/BI/ç«ç°å¾è¾¹æ¡.png'); |
| | | background-size: contain; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | z-index: 1; |
| | | pointer-events: none; |
| | | /* é»è®¤å±
ä¸ï¼ä¼å¨ JS ä¸å¨æè°æ´ */ |
| | | left: 50%; |
| | | left: 25%; |
| | | top: 50%; |
| | | transform: translate(-51.5%, -39%); |
| | | transform: translate(-50%, -50%); |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | gap: 4px; |
| | | pointer-events: none; |
| | | z-index: 2; |
| | | } |
| | | |
| | | .pie-center-value { |
| | | font-weight: 600; |
| | | font-size: 22px; |
| | | color: var(--bi-text-strong); |
| | | font-variant-numeric: tabular-nums; |
| | | } |
| | | |
| | | .pie-center-label { |
| | | font-size: 12px; |
| | | color: var(--bi-text); |
| | | } |
| | | |
| | | .chart-empty { |
| | | position: absolute; |
| | | left: 0; |
| | | right: 0; |
| | | top: 0; |
| | | height: 384px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | color: var(--bi-text); |
| | | font-size: 14px; |
| | | letter-spacing: 1px; |
| | | pointer-events: none; |
| | | } |
| | | </style> |
| | |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis" |
| | | :yAxis="yAxis" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: BI.text } }" |
| | | style="height: 360px" |
| | | /> |
| | | </div> |
| | |
| | | import PanelHeader from '../PanelHeader.vue' |
| | | import DateTypeSwitch from '../DateTypeSwitch.vue' |
| | | import { customerContributionRanking } from '@/api/viewIndex.js' |
| | | import { BI, withTooltip } from '../../theme.js' |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%', |
| | | } |
| | | |
| | | const dateType = ref(1) // 1=å¨ 2=æ 3=å£åº¦ |
| | | |
| | | // 飿ºå¾æ SVG pathï¼ä¸ right-top ä¸è´ï¼ |
| | | const aircraft = |
| | | 'path://M107.000,71.000 C104.936,71.000 102.665,70.806 100.273,70.467 C94.592,76.922 86.275,81.000 77.000,81.000 C70.794,81.000 65.020,79.170 60.172,76.029 C66.952,74.165 72.647,69.714 76.173,63.817 C69.821,61.362 64.063,58.593 60.000,56.039 L60.000,52.813 C70.456,53.950 80.723,55.000 83.000,55.000 C88.972,55.000 93.000,53.723 93.000,50.000 C93.000,47.071 89.222,45.000 83.000,45.000 C80.723,45.000 70.456,46.050 60.000,47.187 L60.000,43.989 C64.057,41.431 69.807,38.644 76.168,36.173 C72.641,30.281 66.948,25.834 60.172,23.971 C65.020,20.830 70.794,19.000 77.000,19.000 C86.270,19.000 94.584,23.074 100.265,29.524 C102.647,29.191 104.918,29.000 107.000,29.000 C129.644,29.000 148.000,50.000 148.000,50.000 C148.000,50.000 129.644,71.000 107.000,71.000 ZM113.000,38.000 C106.373,38.000 101.000,43.373 101.000,50.000 C101.000,56.627 106.373,62.000 113.000,62.000 C119.627,62.000 125.000,56.627 125.000,50.000 C125.000,43.373 119.627,38.000 113.000,38.000 ZM113.000,56.000 C109.686,56.000 107.000,53.314 107.000,50.000 C107.000,46.686 109.686,44.000 113.000,44.000 C116.314,44.000 119.000,46.686 119.000,50.000 C119.000,53.314 116.314,56.000 113.000,56.000 ZM110.500,19.000 C109.567,19.000 108.763,18.483 108.334,17.726 C100.231,9.857 89.187,5.000 77.000,5.000 C64.813,5.000 53.769,9.857 45.666,17.726 C45.237,18.483 44.433,19.000 43.500,19.000 C42.119,19.000 41.000,17.881 41.000,16.500 C41.000,15.847 41.256,15.259 41.665,14.813 L41.575,14.718 C50.629,5.628 63.156,-0.000 77.000,-0.000 C90.844,-0.000 103.371,5.628 112.425,14.718 L112.335,14.813 C112.744,15.259 113.000,15.847 113.000,16.500 C113.000,17.881 111.881,19.000 110.500,19.000 ZM53.000,49.484 C61.406,48.626 77.810,47.000 81.345,47.000 C87.353,47.000 91.000,48.243 91.000,50.000 C91.000,52.234 87.111,53.000 81.345,53.000 C77.810,53.000 61.406,51.374 53.000,50.516 L53.000,49.484 ZM53.000,47.000 L9.000,50.000 L53.000,53.000 L53.000,56.000 L-0.000,50.000 L53.000,44.000 L53.000,47.000 ZM43.500,81.000 C44.433,81.000 45.237,81.517 45.666,82.274 C53.769,90.143 64.813,95.000 77.000,95.000 C89.187,95.000 100.231,90.143 108.334,82.274 C108.763,81.517 109.567,81.000 110.500,81.000 C111.881,81.000 113.000,82.119 113.000,83.500 C113.000,84.153 112.744,84.741 112.335,85.187 L112.425,85.282 C103.371,94.372 90.844,100.000 77.000,100.000 C63.156,100.000 50.629,94.372 41.575,85.282 L41.665,85.187 C41.256,84.741 41.000,84.153 41.000,83.500 C41.000,82.119 42.119,81.000 43.500,81.000 Z' |
| | | |
| | | // é¢è²é
ç½®ï¼ä¸ right-top ä¸è´ï¼ |
| | | const color = { |
| | | 0: '#ff5676', |
| | | 1: '#ffd83e', |
| | | 2: '#fbff94', |
| | | 3: '#7daeff', |
| | | } |
| | | const dateType = ref(3) // 1=å¨ 2=æ 3=å£åº¦ï¼é»è®¤å£åº¦ |
| | | |
| | | // åå§æ°æ®ï¼ç»ä¸æ { NAME, NUM }ï¼ |
| | | const dataArr = ref([]) |
| | |
| | | |
| | | const xdataName = computed(() => dataArray.value.map((v) => v.NAME)) |
| | | |
| | | // æåé
è²ï¼Top1 éè²ï¼Top2-3 亮èï¼å
¶ä½æè |
| | | const rankColor = (rank) => { |
| | | if (rank === 1) return '#F0B14A' |
| | | if (rank <= 3) return BI.accentBright |
| | | return '#2C4A9E' |
| | | } |
| | | |
| | | const getRank = (i) => dataArray.value.length - i |
| | | |
| | | const dataNum = computed(() => { |
| | | return dataArray.value.map((v, i) => { |
| | | const index = dataArray.value.length - i - 1 |
| | | const isTop3 = index < 3 |
| | | |
| | | const rank = getRank(i) |
| | | return { |
| | | value: Number(v.NUM), |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 1, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 0, |
| | | colorStops: [ |
| | | { offset: 0, color: isTop3 ? '#ffdae1' : '#ecf3ff' }, |
| | | { offset: 0.07, color: isTop3 ? color[index] : color[3] }, |
| | | { |
| | | offset: 1, |
| | | color: isTop3 ? 'rgba(255, 86, 118, .1)' : 'rgba(125,174,255, .1)', |
| | | }, |
| | | ], |
| | | global: false, |
| | | }, |
| | | barBorderRadius: [0, 20, 20, 0], |
| | | color: rankColor(rank), |
| | | barBorderRadius: [0, 10, 10, 0], |
| | | }, |
| | | symbol: isTop3 ? aircraft : 'none', |
| | | symbolPosition: 'end', |
| | | symbolSize: [30, 25], |
| | | symbolOffset: [35, 0], |
| | | } |
| | | }) |
| | | }) |
| | | |
| | | const bgData = computed(() => { |
| | | const maxValue = Math.max(0, ...dataNum.value.map((v) => v.value)) |
| | | return dataNum.value.map(() => maxValue + 200) |
| | | const maxValue = dataNum.value.length ? Math.max(...dataNum.value.map((v) => v.value)) : 0 |
| | | return dataNum.value.map(() => maxValue * 1.08 || 1) |
| | | }) |
| | | |
| | | const tooltip = computed(() => ({ |
| | | const tooltip = withTooltip({ |
| | | trigger: 'axis', |
| | | textStyle: { fontSize: '100%' }, |
| | | formatter: function (params) { |
| | | let result = params[0].axisValueLabel + '<br/>' |
| | | params.forEach((item) => { |
| | | result += `<div>${item.marker} ${item.seriesName}: ${item.value}å
</div>` |
| | | }) |
| | | result += `<div>${params[0].marker}${Number(params[0].value || 0).toLocaleString('zh-CN')}å
</div>` |
| | | return result |
| | | }, |
| | | })) |
| | | }) |
| | | |
| | | const grid = computed(() => ({ top: 0, left: '20%', right: '10%', bottom: 0 })) |
| | | |
| | |
| | | a: { |
| | | width: 120, |
| | | fontSize: 14, |
| | | color: '#fff', |
| | | color: BI.textStrong, |
| | | padding: [5, 4, 5, 0], |
| | | align: 'right', |
| | | }, |
| | |
| | | formatter: (params, index) => { |
| | | const value = typeof params === 'object' ? params.value : params |
| | | const percent = total.value ? ((value / total.value) * 100).toFixed(0) : 0 |
| | | const rank = dataArray.value.length - index |
| | | const isTop3 = rank < 4 |
| | | |
| | | return `{a${isTop3 ? rank : ''}|${percent} }{b${isTop3 ? rank : ''}|%}` |
| | | const rank = getRank(index) |
| | | return `{a${rank}|${percent} }{b${rank}|%}` |
| | | }, |
| | | rich: { |
| | | a: { fontSize: 18, color: '#98bfff', verticalAlign: 'bottom' }, |
| | | a1: { fontSize: 18, color: '#ff7f97', verticalAlign: 'bottom' }, |
| | | a2: { fontSize: 18, color: '#ffce64', verticalAlign: 'bottom' }, |
| | | a3: { fontSize: 18, color: '#e8ed66', verticalAlign: 'bottom' }, |
| | | b: { fontSize: 12, color: '#98bfff', verticalAlign: 'bottom' }, |
| | | b1: { fontSize: 12, color: '#ff7f97', verticalAlign: 'bottom' }, |
| | | b2: { fontSize: 12, color: '#ffce64', verticalAlign: 'bottom' }, |
| | | b3: { fontSize: 12, color: '#e8ed66', verticalAlign: 'bottom' }, |
| | | }, |
| | | rich: dataNum.value.reduce((rich, _, i) => { |
| | | const rank = getRank(i) |
| | | const color = rank === 1 ? '#F0B14A' : rank <= 3 ? BI.accentBright : BI.text |
| | | rich[`a${rank}`] = { fontSize: 16, color, verticalAlign: 'bottom' } |
| | | rich[`b${rank}`] = { fontSize: 12, color, verticalAlign: 'bottom' } |
| | | return rich |
| | | }, {}), |
| | | }, |
| | | axisLine: { show: false }, |
| | | axisTick: { show: false }, |
| | |
| | | }, |
| | | ]) |
| | | |
| | | // seriesï¼èæ¯è½¨é + å®è²æ¡ |
| | | const series = computed(() => [ |
| | | { |
| | | name: 'éé¢', |
| | | z: 6, |
| | | type: 'pictorialBar', |
| | | data: dataNum.value, |
| | | }, |
| | | { |
| | | name: 'èæ¯', |
| | | z: 6, |
| | | z: 5, |
| | | type: 'bar', |
| | | barWidth: 25, |
| | | barWidth: 18, |
| | | tooltip: { show: false }, |
| | | itemStyle: { |
| | | color: 'rgba(255,255,255,.1)', |
| | | barBorderRadius: [0, 20, 20, 0], |
| | | color: 'rgba(143, 163, 200, 0.12)', |
| | | barBorderRadius: [0, 10, 10, 0], |
| | | }, |
| | | data: bgData.value, |
| | | }, |
| | | { |
| | | name: 'é颿¸å', |
| | | name: 'éé¢', |
| | | z: 6, |
| | | type: 'bar', |
| | | barWidth: 25, |
| | | barWidth: 18, |
| | | barGap: '-100%', |
| | | tooltip: { show: false }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 1, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 0, |
| | | colorStops: [ |
| | | { offset: 0, color: 'rgba(255, 218, 220)' }, |
| | | { offset: 0.07, color: 'rgba(255, 86, 118)' }, |
| | | { offset: 1, color: 'rgba(255, 86, 118, 0)' }, |
| | | ], |
| | | global: false, |
| | | }, |
| | | barBorderRadius: [0, 20, 20, 0], |
| | | }, |
| | | data: dataNum.value, |
| | | }, |
| | | ]) |
| | |
| | | const fetchCustomerRanking = () => { |
| | | customerContributionRanking({ type: dateType.value }) |
| | | .then((res) => { |
| | | if (res.code === 200 && Array.isArray(res.data)) { |
| | | dataArr.value = res.data.map(item => ({ |
| | | if (res.code === 200 && Array.isArray(res.data) && res.data.length > 0) { |
| | | dataArr.value = res.data.map((item) => ({ |
| | | NAME: item.customerName, |
| | | NUM: item.totalAmount |
| | | NUM: Number(item.totalAmount) || 0, |
| | | })) |
| | | } else { |
| | | setMockData(dateType.value) |
| | |
| | | |
| | | <style scoped> |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | border: 1px solid var(--bi-panel-border); |
| | | background: var(--bi-panel-bg); |
| | | border-radius: 8px; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis" |
| | | :yAxis="yAxis" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: BI.text } }" |
| | | style="height: 360px" |
| | | /> |
| | | </div> |
| | |
| | | import PanelHeader from '../PanelHeader.vue' |
| | | import DateTypeSwitch from '../DateTypeSwitch.vue' |
| | | import { supplierPurchaseRanking } from '@/api/viewIndex.js' |
| | | import { BI, withTooltip } from '../../theme.js' |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%', |
| | | } |
| | | |
| | | const radio1 = ref(1) |
| | | |
| | | // 飿ºå¾æ SVG path |
| | | const aircraft = |
| | | 'path://M107.000,71.000 C104.936,71.000 102.665,70.806 100.273,70.467 C94.592,76.922 86.275,81.000 77.000,81.000 C70.794,81.000 65.020,79.170 60.172,76.029 C66.952,74.165 72.647,69.714 76.173,63.817 C69.821,61.362 64.063,58.593 60.000,56.039 L60.000,52.813 C70.456,53.950 80.723,55.000 83.000,55.000 C88.972,55.000 93.000,53.723 93.000,50.000 C93.000,47.071 89.222,45.000 83.000,45.000 C80.723,45.000 70.456,46.050 60.000,47.187 L60.000,43.989 C64.057,41.431 69.807,38.644 76.168,36.173 C72.641,30.281 66.948,25.834 60.172,23.971 C65.020,20.830 70.794,19.000 77.000,19.000 C86.270,19.000 94.584,23.074 100.265,29.524 C102.647,29.191 104.918,29.000 107.000,29.000 C129.644,29.000 148.000,50.000 148.000,50.000 C148.000,50.000 129.644,71.000 107.000,71.000 ZM113.000,38.000 C106.373,38.000 101.000,43.373 101.000,50.000 C101.000,56.627 106.373,62.000 113.000,62.000 C119.627,62.000 125.000,56.627 125.000,50.000 C125.000,43.373 119.627,38.000 113.000,38.000 ZM113.000,56.000 C109.686,56.000 107.000,53.314 107.000,50.000 C107.000,46.686 109.686,44.000 113.000,44.000 C116.314,44.000 119.000,46.686 119.000,50.000 C119.000,53.314 116.314,56.000 113.000,56.000 ZM110.500,19.000 C109.567,19.000 108.763,18.483 108.334,17.726 C100.231,9.857 89.187,5.000 77.000,5.000 C64.813,5.000 53.769,9.857 45.666,17.726 C45.237,18.483 44.433,19.000 43.500,19.000 C42.119,19.000 41.000,17.881 41.000,16.500 C41.000,15.847 41.256,15.259 41.665,14.813 L41.575,14.718 C50.629,5.628 63.156,-0.000 77.000,-0.000 C90.844,-0.000 103.371,5.628 112.425,14.718 L112.335,14.813 C112.744,15.259 113.000,15.847 113.000,16.500 C113.000,17.881 111.881,19.000 110.500,19.000 ZM53.000,49.484 C61.406,48.626 77.810,47.000 81.345,47.000 C87.353,47.000 91.000,48.243 91.000,50.000 C91.000,52.234 87.111,53.000 81.345,53.000 C77.810,53.000 61.406,51.374 53.000,50.516 L53.000,49.484 ZM53.000,47.000 L9.000,50.000 L53.000,53.000 L53.000,56.000 L-0.000,50.000 L53.000,44.000 L53.000,47.000 ZM43.500,81.000 C44.433,81.000 45.237,81.517 45.666,82.274 C53.769,90.143 64.813,95.000 77.000,95.000 C89.187,95.000 100.231,90.143 108.334,82.274 C108.763,81.517 109.567,81.000 110.500,81.000 C111.881,81.000 113.000,82.119 113.000,83.500 C113.000,84.153 112.744,84.741 112.335,85.187 L112.425,85.282 C103.371,94.372 90.844,100.000 77.000,100.000 C63.156,100.000 50.629,94.372 41.575,85.282 L41.665,85.187 C41.256,84.741 41.000,84.153 41.000,83.500 C41.000,82.119 42.119,81.000 43.500,81.000 Z' |
| | | |
| | | // é¢è²é
ç½® |
| | | const color = { |
| | | 0: '#ff5676', |
| | | 1: '#ffd83e', |
| | | 2: '#fbff94', |
| | | 3: '#7daeff', |
| | | } |
| | | const radio1 = ref(3) // 1=å¨ 2=æ 3=å£åº¦ï¼é»è®¤å£åº¦ |
| | | |
| | | // åå§æ°æ® |
| | | const dataArr = ref([]) |
| | | |
| | | // æåºåçæ°æ® |
| | | // æåºåçæ°æ®ï¼ååºï¼å¼è¶å¤§è¶é ä¸ï¼ |
| | | const dataArray = computed(() => { |
| | | return [...dataArr.value].sort((a, b) => a.NUM - b.NUM) |
| | | }) |
| | |
| | | return dataArray.value.map((v) => v.NAME) |
| | | }) |
| | | |
| | | // æåé
è²ï¼Top1 éè²ï¼Top2-3 亮èï¼å
¶ä½æè |
| | | const rankColor = (rank) => { |
| | | if (rank === 1) return '#F0B14A' |
| | | if (rank <= 3) return BI.accentBright |
| | | return '#2C4A9E' |
| | | } |
| | | |
| | | const getRank = (i) => dataArray.value.length - i |
| | | |
| | | // yè½´æ°æ®ï¼æ°å¼ï¼å¸¦æ ·å¼ï¼ |
| | | const dataNum = computed(() => { |
| | | return dataArray.value.map((v, i) => { |
| | | const index = dataArray.value.length - i - 1 |
| | | const isTop3 = index < 3 |
| | | |
| | | const rank = getRank(i) |
| | | return { |
| | | value: Number(v.NUM), |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 1, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 0, |
| | | colorStops: [ |
| | | { |
| | | offset: 0, |
| | | color: isTop3 ? '#ffdae1' : '#ecf3ff', |
| | | }, |
| | | { |
| | | offset: 0.07, |
| | | color: isTop3 ? color[index] : color[3], |
| | | }, |
| | | { |
| | | offset: 1, |
| | | color: isTop3 |
| | | ? 'rgba(255, 86, 118, .1)' |
| | | : 'rgba(125,174,255, .1)', |
| | | }, |
| | | ], |
| | | global: false, |
| | | }, |
| | | barBorderRadius: [0, 20, 20, 0], |
| | | color: rankColor(rank), |
| | | barBorderRadius: [0, 10, 10, 0], |
| | | }, |
| | | symbol: isTop3 ? aircraft : 'none', |
| | | symbolPosition: 'end', |
| | | symbolSize: [30, 25], |
| | | symbolOffset: [35, 0], |
| | | } |
| | | }) |
| | | }) |
| | | |
| | | // èæ¯æ°æ® |
| | | // èæ¯è½¨éæ°æ® |
| | | const bgData = computed(() => { |
| | | const maxValue = Math.max(...dataNum.value.map((v) => v.value)) |
| | | return dataNum.value.map(() => maxValue + 200) |
| | | const maxValue = dataNum.value.length ? Math.max(...dataNum.value.map((v) => v.value)) : 0 |
| | | return dataNum.value.map(() => maxValue * 1.08 || 1) |
| | | }) |
| | | |
| | | // tooltip |
| | | const tooltip = computed(() => { |
| | | return { |
| | | trigger: 'axis', |
| | | textStyle: { fontSize: '100%' }, |
| | | formatter: function (params) { |
| | | let result = params[0].axisValueLabel + '<br/>' |
| | | result += `<div>${params[0].marker}${params[0].value}å
</div>` |
| | | return result |
| | | }, |
| | | } |
| | | const tooltip = withTooltip({ |
| | | trigger: 'axis', |
| | | formatter: function (params) { |
| | | let result = params[0].axisValueLabel + '<br/>' |
| | | result += `<div>${params[0].marker}${Number(params[0].value || 0).toLocaleString('zh-CN')}å
</div>` |
| | | return result |
| | | }, |
| | | }) |
| | | |
| | | // grid |
| | |
| | | a: { |
| | | width: 120, |
| | | fontSize: 14, |
| | | color: '#fff', |
| | | color: BI.textStrong, |
| | | padding: [5, 4, 5, 0], |
| | | align: 'right', |
| | | }, |
| | |
| | | axisLabel: { |
| | | formatter: (params, index) => { |
| | | const value = typeof params === 'object' ? params.value : params |
| | | const percent = ((value / total.value) * 100).toFixed(0) |
| | | const rank = dataArray.value.length - index |
| | | const isTop3 = rank < 4 |
| | | |
| | | return `{a${isTop3 ? rank : ''}|${percent} }{b${isTop3 ? rank : ''}|%}` |
| | | const percent = total.value ? ((value / total.value) * 100).toFixed(0) : 0 |
| | | const rank = getRank(index) |
| | | return `{a${rank}|${percent} }{b${rank}|%}` |
| | | }, |
| | | rich: { |
| | | a: { |
| | | fontSize: 18, |
| | | color: '#98bfff', |
| | | verticalAlign: 'bottom', |
| | | }, |
| | | a1: { |
| | | fontSize: 18, |
| | | color: '#ff7f97', |
| | | verticalAlign: 'bottom', |
| | | }, |
| | | a2: { |
| | | fontSize: 18, |
| | | color: '#ffce64', |
| | | verticalAlign: 'bottom', |
| | | }, |
| | | a3: { |
| | | fontSize: 18, |
| | | color: '#e8ed66', |
| | | verticalAlign: 'bottom', |
| | | }, |
| | | b: { |
| | | fontSize: 12, |
| | | color: '#98bfff', |
| | | verticalAlign: 'bottom', |
| | | }, |
| | | b1: { |
| | | fontSize: 12, |
| | | color: '#ff7f97', |
| | | verticalAlign: 'bottom', |
| | | }, |
| | | b2: { |
| | | fontSize: 12, |
| | | color: '#ffce64', |
| | | verticalAlign: 'bottom', |
| | | }, |
| | | b3: { |
| | | fontSize: 12, |
| | | color: '#e8ed66', |
| | | verticalAlign: 'bottom', |
| | | }, |
| | | }, |
| | | rich: dataNum.value.reduce((rich, _, i) => { |
| | | const rank = getRank(i) |
| | | const color = rank === 1 ? '#F0B14A' : rank <= 3 ? BI.accentBright : BI.text |
| | | rich[`a${rank}`] = { fontSize: 16, color, verticalAlign: 'bottom' } |
| | | rich[`b${rank}`] = { fontSize: 12, color, verticalAlign: 'bottom' } |
| | | return rich |
| | | }, {}), |
| | | }, |
| | | axisLine: { show: false }, |
| | | axisTick: { show: false }, |
| | |
| | | ] |
| | | }) |
| | | |
| | | // series |
| | | const series = computed(() => { |
| | | return [ |
| | | { |
| | | z: 6, |
| | | type: 'pictorialBar', |
| | | data: dataNum.value, |
| | | // seriesï¼èæ¯è½¨é + å®è²æ¡ |
| | | const series = computed(() => [ |
| | | { |
| | | name: 'èæ¯', |
| | | z: 5, |
| | | type: 'bar', |
| | | barWidth: 18, |
| | | tooltip: { show: false }, |
| | | itemStyle: { |
| | | color: 'rgba(143, 163, 200, 0.12)', |
| | | barBorderRadius: [0, 10, 10, 0], |
| | | }, |
| | | { |
| | | z: 6, |
| | | type: 'bar', |
| | | barWidth: 25, |
| | | tooltip: { show: false }, |
| | | itemStyle: { |
| | | color: 'rgba(255,255,255,.1)', |
| | | barBorderRadius: [0, 20, 20, 0], |
| | | }, |
| | | data: bgData.value, |
| | | }, |
| | | { |
| | | type: 'bar', |
| | | barWidth: 25, |
| | | barGap: '-100%', |
| | | tooltip: { show: false }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 1, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 0, |
| | | colorStops: [ |
| | | { |
| | | offset: 0, |
| | | color: 'rgba(255, 218, 220)', |
| | | }, |
| | | { |
| | | offset: 0.07, |
| | | color: 'rgba(255, 86, 118)', |
| | | }, |
| | | { |
| | | offset: 1, |
| | | color: 'rgba(255, 86, 118, 0)', |
| | | }, |
| | | ], |
| | | global: false, |
| | | }, |
| | | barBorderRadius: [0, 20, 20, 0], |
| | | }, |
| | | data: dataNum.value, |
| | | }, |
| | | ] |
| | | }) |
| | | data: bgData.value, |
| | | }, |
| | | { |
| | | name: 'éé¢', |
| | | z: 6, |
| | | type: 'bar', |
| | | barWidth: 18, |
| | | barGap: '-100%', |
| | | data: dataNum.value, |
| | | }, |
| | | ]) |
| | | |
| | | // ä¾åºåéè´æå |
| | | const fetchSupplierRanking = () => { |
| | | supplierPurchaseRanking({ type: radio1.value }) |
| | | .then((res) => { |
| | | if (res.code === 200 && Array.isArray(res.data)) { |
| | | dataArr.value = res.data.map(item => ({ |
| | | if (res.code === 200 && Array.isArray(res.data) && res.data.length > 0) { |
| | | dataArr.value = res.data.map((item) => ({ |
| | | NAME: item.supplierName, |
| | | NUM: item.totalAmount |
| | | NUM: Number(item.totalAmount) || 0, |
| | | })) |
| | | } else { |
| | | // å¦ææ²¡ææ°æ®ï¼ä½¿ç¨æ¨¡ææ°æ® |
| | | // æ¥å£æ æ°æ®æ¶ç»ä¸ç»æ¨¡æä¾åºåï¼ä¿è¯UIå¯ç¨ |
| | | dataArr.value = [ |
| | | { NAME: 'ä¾åºåA', NUM: 102 }, |
| | | { NAME: 'ä¾åºåB', NUM: 122 }, |
| | |
| | | }) |
| | | .catch((error) => { |
| | | console.error('è·åä¾åºåéè´æå失败:', error) |
| | | // ä½¿ç¨æ¨¡ææ°æ® |
| | | dataArr.value = [ |
| | | { NAME: 'ä¾åºåA', NUM: 102 }, |
| | | { NAME: 'ä¾åºåB', NUM: 122 }, |
| | |
| | | } |
| | | |
| | | // å¤çæ¥æç±»å忢 |
| | | const handleDateTypeChange = (value) => { |
| | | const handleDateTypeChange = () => { |
| | | fetchSupplierRanking() |
| | | } |
| | | |
| | |
| | | |
| | | <style scoped> |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | border: 1px solid var(--bi-panel-border); |
| | | background: var(--bi-panel-bg); |
| | | border-radius: 8px; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .section-title { |
| | | font-weight: 500; |
| | | font-size: 16px; |
| | | color: #d9ecff; |
| | | } |
| | | </style> |
| | |
| | | |
| | | <!-- 顶鍿 颿 --> |
| | | <div class="dashboard-header"> |
| | | <div class="header-line"></div> |
| | | <div class="factory-name">åºç¡æ°æ®</div> |
| | | <div class="header-line header-line-right"></div> |
| | | </div> |
| | | |
| | | <!-- 主è¦å
容åºå --> |
| | |
| | | position: relative; |
| | | width: 100%; |
| | | /* 页é¢å¨å¸¸è§å¸å±ä¸ï¼æé¡¶æ ï¼é»è®¤åå» 84pxï¼é¿å
å
容被è£å */ |
| | | height: calc(100vh - 84px); |
| | | height: calc(100vh - 100px); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | |
| | | position: relative; |
| | | width: 1920px; |
| | | height: 1080px; |
| | | background-image: url("@/assets/BI/backImage@2x.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | transform-origin: center center; |
| | | |
| | | /* 大å±ä¸»é¢ä»¤çï¼åç»ä»¶éè¿ CSS åéç»§æ¿ */ |
| | | --bi-panel-bg: rgba(20, 32, 66, 0.55); |
| | | --bi-panel-border: #1e3160; |
| | | --bi-text: #8fa3c8; |
| | | --bi-text-strong: #eaf1ff; |
| | | --bi-accent: #2c51d9; |
| | | --bi-accent-bright: #4a6cf0; |
| | | --bi-up: #34c77b; |
| | | --bi-down: #ff6b6b; |
| | | |
| | | background: |
| | | radial-gradient(1200px 600px at 50% -10%, rgba(44, 81, 217, 0.18) 0%, transparent 60%), |
| | | linear-gradient(180deg, #0a1229 0%, #0d1834 55%, #0a1430 100%); |
| | | } |
| | | |
| | | /* ææ·¡ç½æ ¼çº¹çï¼æ¿ä»£æ¨¡æ¿æåºå¾ */ |
| | | .data-dashboard::before { |
| | | content: ''; |
| | | position: absolute; |
| | | inset: 0; |
| | | pointer-events: none; |
| | | background-image: |
| | | linear-gradient(rgba(74, 108, 240, 0.045) 1px, transparent 1px), |
| | | linear-gradient(90deg, rgba(74, 108, 240, 0.045) 1px, transparent 1px); |
| | | background-size: 48px 48px; |
| | | mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0.9) 0%, rgba(0, 0, 0, 0.35) 100%); |
| | | } |
| | | |
| | | /* å
¨å±ç¶æçæ ·å¼ - ä½ç¨äºscale-container */ |
| | |
| | | position: relative; |
| | | z-index: 1; |
| | | height: 86px; |
| | | background-image: url("@/assets/BI/biaoti.png"); |
| | | background-size: cover; |
| | | background-repeat: no-repeat; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 28px; |
| | | } |
| | | |
| | | /* æ é¢ä¸¤ä¾§æ¸éåé线 */ |
| | | .header-line { |
| | | width: 420px; |
| | | height: 1px; |
| | | background: linear-gradient(90deg, transparent 0%, rgba(74, 108, 240, 0.5) 100%); |
| | | } |
| | | |
| | | .header-line-right { |
| | | background: linear-gradient(270deg, transparent 0%, rgba(74, 108, 240, 0.5) 100%); |
| | | } |
| | | |
| | | .factory-name { |
| | | font-weight: 600; |
| | | font-size: 52px; |
| | | color: #FFFFFF; |
| | | top: 16px; |
| | | font-size: 30px; |
| | | letter-spacing: 4px; |
| | | color: var(--bi-text-strong); |
| | | position: relative; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .factory-name::after { |
| | | content: ''; |
| | | position: absolute; |
| | | left: 50%; |
| | | bottom: -8px; |
| | | transform: translateX(-50%); |
| | | width: 48px; |
| | | height: 2px; |
| | | background: var(--bi-accent-bright); |
| | | border-radius: 1px; |
| | | } |
| | | |
| | | .fullscreen-btn { |
| | |
| | | left: 20px; |
| | | width: 40px; |
| | | height: 40px; |
| | | background: rgba(0, 20, 60, 0.8); |
| | | border: 1px solid rgba(0, 212, 255, 0.3); |
| | | background: var(--bi-panel-bg); |
| | | border: 1px solid var(--bi-panel-border); |
| | | border-radius: 6px; |
| | | color: #00d4ff; |
| | | color: var(--bi-text); |
| | | cursor: pointer; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | transition: all 0.3s; |
| | | transition: color 0.2s ease, border-color 0.2s ease; |
| | | z-index: 10000; |
| | | } |
| | | |
| | | .fullscreen-btn:hover { |
| | | background: rgba(0, 30, 90, 0.9); |
| | | border-color: rgba(0, 212, 255, 0.5); |
| | | color: var(--bi-text-strong); |
| | | border-color: var(--bi-accent-bright); |
| | | } |
| | | |
| | | .dashboard-content { |
| | |
| | | position: relative; |
| | | width: 100%; |
| | | /* 页é¢å¨å¸¸è§å¸å±ä¸ï¼æé¡¶æ ï¼é»è®¤åå» 84pxï¼é¿å
å
容被è£å */ |
| | | height: calc(100vh - 84px); |
| | | height: calc(100vh - 100px); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // åºç¡æ°æ®å¤§å±ç»ä¸è§è§ä»¤çï¼ä¸ PSI 大å±åä¸å¥åçèå
å¶æ·±è²ä¸»é¢ï¼ |
| | | export const BI = { |
| | | // å¾è¡¨åºåè²ï¼å
å¶ä¸ä¸ï¼æ¿ä»£éèè§å
ï¼ |
| | | series: ['#4A6CF0', '#35C6D6', '#8E7CE6', '#F0B14A', '#2FB8A8', '#E86D6D'], |
| | | text: '#8FA3C8', |
| | | textStrong: '#EAF1FF', |
| | | panelBg: 'rgba(20, 32, 66, 0.55)', |
| | | panelBorder: '#1E3160', |
| | | sliceBorder: '#0D1834', |
| | | accent: '#2C51D9', |
| | | accentBright: '#4A6CF0', |
| | | up: '#34C77B', |
| | | down: '#FF6B6B', |
| | | } |
| | | |
| | | export const baseChartOptions = { |
| | | backgroundColor: 'transparent', |
| | | textStyle: { color: BI.text }, |
| | | } |
| | | |
| | | // æ·±è²ç»ä¸ tooltip æ ·å¼ï¼ä¿çåç»ä»¶èªå®ä¹ formatter |
| | | export const withTooltip = (tooltip = {}) => ({ |
| | | backgroundColor: 'rgba(13, 24, 52, 0.92)', |
| | | borderColor: BI.panelBorder, |
| | | borderWidth: 1, |
| | | textStyle: { color: BI.textStrong, fontSize: 12 }, |
| | | ...tooltip, |
| | | }) |
| | | |
| | | // æ°å¼æ ¼å¼åï¼å¤§æ°åæç®ä¸ºãä¸ãï¼ç©ºå¼å
åº |
| | | export const formatNumber = (val, unit = '') => { |
| | | const num = Number(val) |
| | | if (!Number.isFinite(num)) return `0${unit}` |
| | | if (Math.abs(num) >= 10000) { |
| | | return `${(num / 10000).toLocaleString('zh-CN', { maximumFractionDigits: 2 })}ä¸${unit}` |
| | | } |
| | | return `${num.toLocaleString('zh-CN', { maximumFractionDigits: 2 })}${unit}` |
| | | } |
| | |
| | | position: relative; |
| | | width: 100%; |
| | | /* 页é¢å¨å¸¸è§å¸å±ä¸ï¼æé¡¶æ ï¼é»è®¤åå» 84pxï¼é¿å
å
容被è£å */ |
| | | height: calc(100vh - 84px); |
| | | height: calc(100vh - 100px); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | |
| | | <template> |
| | | <div class="carousel-cards"> |
| | | <button |
| | | <button |
| | | v-if="canScrollLeft" |
| | | class="nav-button nav-button-left" |
| | | aria-label="åå·¦æ»å¨" |
| | | @click="scrollLeftFn" |
| | | > |
| | | <img src="@/assets/BI/jiantou.png" alt="å·¦ç®å¤´" /> |
| | | <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg> |
| | | </button> |
| | | <div |
| | | class="cards-container" |
| | | <div |
| | | class="cards-container" |
| | | :style="{ '--visible-count': visibleCount }" |
| | | ref="cardsContainerRef" |
| | | > |
| | |
| | | :key="index" |
| | | class="card-item" |
| | | > |
| | | <div v-if="item.icon" class="card-icon" :style="{ backgroundImage: `url(${item.icon})` }"></div> |
| | | <div class="card-title"> |
| | | <div class="card-label">{{ item.label }}</div> |
| | | <div class="card-value"> |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <button |
| | | <button |
| | | v-if="canScrollRight" |
| | | class="nav-button nav-button-right" |
| | | aria-label="å峿»å¨" |
| | | @click="scrollRightFn" |
| | | > |
| | | <img src="@/assets/BI/jiantou.png" alt="å³ç®å¤´" /> |
| | | <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"/></svg> |
| | | </button> |
| | | </div> |
| | | </template> |
| | |
| | | type: Array, |
| | | default: () => [], |
| | | validator: (value) => { |
| | | return value.every(item => |
| | | item && typeof item.label !== 'undefined' && |
| | | typeof item.value !== 'undefined' && |
| | | return value.every(item => |
| | | item && typeof item.label !== 'undefined' && |
| | | typeof item.value !== 'undefined' && |
| | | typeof item.unit !== 'undefined' |
| | | ) |
| | | } |
| | |
| | | const updateScrollState = () => { |
| | | const container = cardsContainerRef.value |
| | | if (!container) return |
| | | |
| | | |
| | | currentScrollLeft.value = container.scrollLeft |
| | | maxScrollLeft.value = container.scrollWidth - container.clientWidth |
| | | } |
| | |
| | | const scrollLeftFn = () => { |
| | | const container = cardsContainerRef.value |
| | | if (!container) return |
| | | |
| | | |
| | | const scrollItems = Array.from(container.querySelectorAll('.card-item')) |
| | | if (scrollItems.length === 0) return |
| | | |
| | | |
| | | const itemWidth = scrollItems[0]?.offsetWidth || 0 |
| | | const gap = 12 |
| | | const scrollDistance = itemWidth + gap |
| | | |
| | | |
| | | container.scrollBy({ |
| | | left: -scrollDistance, |
| | | behavior: 'smooth' |
| | | }) |
| | | |
| | | |
| | | // å»¶è¿æ´æ°ç¶æï¼çå¾
æ»å¨å¨ç»å®æ |
| | | setTimeout(() => { |
| | | updateScrollState() |
| | |
| | | const scrollRightFn = () => { |
| | | const container = cardsContainerRef.value |
| | | if (!container) return |
| | | |
| | | |
| | | const scrollItems = Array.from(container.querySelectorAll('.card-item')) |
| | | if (scrollItems.length === 0) return |
| | | |
| | | |
| | | const itemWidth = scrollItems[0]?.offsetWidth || 0 |
| | | const gap = 12 |
| | | const scrollDistance = itemWidth + gap |
| | | |
| | | |
| | | container.scrollBy({ |
| | | left: scrollDistance, |
| | | behavior: 'smooth' |
| | | }) |
| | | |
| | | |
| | | // å»¶è¿æ´æ°ç¶æï¼çå¾
æ»å¨å¨ç»å®æ |
| | | setTimeout(() => { |
| | | updateScrollState() |
| | |
| | | transform: translateY(-50%); |
| | | width: 32px; |
| | | height: 32px; |
| | | background: rgba(26, 88, 176, 0.6); |
| | | border: 1px solid rgba(26, 88, 176, 0.8); |
| | | background: var(--bi-surface-2); |
| | | border: 1px solid var(--bi-border); |
| | | border-radius: 50%; |
| | | color: var(--bi-text-secondary); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | cursor: pointer; |
| | | z-index: 10; |
| | | transition: all 0.3s ease; |
| | | transition: color 0.2s ease, border-color 0.2s ease; |
| | | padding: 0; |
| | | } |
| | | |
| | | .nav-button:hover { |
| | | background: rgba(26, 88, 176, 0.8); |
| | | transform: translateY(-50%) scale(1.1); |
| | | color: var(--bi-accent); |
| | | border-color: var(--bi-accent); |
| | | } |
| | | |
| | | .nav-button-left { |
| | | left: -16px; |
| | | } |
| | | |
| | | .nav-button-left img { |
| | | width: 16px; |
| | | height: 16px; |
| | | transform: rotate(180deg); |
| | | } |
| | | |
| | | .nav-button-right { |
| | | right: -16px; |
| | | } |
| | | |
| | | .nav-button-right img { |
| | | width: 16px; |
| | | height: 16px; |
| | | } |
| | | |
| | | .card-item { |
| | |
| | | min-width: calc((100% - (var(--visible-count) - 1) * 12px) / var(--visible-count)); |
| | | display: flex; |
| | | align-items: center; |
| | | background: linear-gradient(269deg, rgba(27,57,126,0.13) 0%, rgba(33,137,206,0.33) 98.13%, #24AFF4 100%); |
| | | border-radius: 8px 8px 8px 8px; |
| | | background: var(--bi-surface-2); |
| | | border: 1px solid var(--bi-border); |
| | | border-radius: 8px; |
| | | padding: 12px 16px; |
| | | transition: all 0.3s ease; |
| | | transition: border-color 0.2s ease; |
| | | } |
| | | |
| | | .card-item:hover { |
| | | transform: translateY(-2px); |
| | | } |
| | | |
| | | .card-icon { |
| | | width: 80px; |
| | | height: 60px; |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | flex-shrink: 0; |
| | | margin-right: 12px; |
| | | border-color: rgba(91, 140, 255, 0.45); |
| | | } |
| | | |
| | | .card-title { |
| | |
| | | align-items: flex-start; |
| | | flex-direction: column; |
| | | flex: 1; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .card-label { |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #FFFFFF; |
| | | color: var(--bi-text-secondary); |
| | | margin-bottom: 4px; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | |
| | | gap: 6px; |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: rgba(255, 255, 255, 0.85); |
| | | color: var(--bi-text-secondary); |
| | | } |
| | | |
| | | .rate-label { |
| | |
| | | } |
| | | |
| | | .value-number { |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #FFFFFF; |
| | | line-height: 1; |
| | | font-weight: 600; |
| | | font-size: 22px; |
| | | color: var(--bi-text); |
| | | line-height: 1.1; |
| | | font-variant-numeric: tabular-nums; |
| | | font-feature-settings: 'tnum'; |
| | | } |
| | | |
| | | .value-unit { |
| | | font-size: 14px; |
| | | color: #FFFFFF; |
| | | font-size: 12px; |
| | | color: var(--bi-text-secondary); |
| | | font-weight: 400; |
| | | } |
| | | </style> |
| | |
| | | const props = defineProps({ |
| | | modelValue: { |
| | | type: Number, |
| | | default: 1, // é»è®¤éä¸"å¨" |
| | | default: 3, // é»è®¤éä¸"å£åº¦" |
| | | }, |
| | | }) |
| | | |
| | |
| | | |
| | | /* æªéä¸ç¶æçæ ·å¼ */ |
| | | .date-type-switch :deep(.el-radio-button__inner) { |
| | | background-color: rgba(26, 88, 176, 0.3); |
| | | color: rgba(184, 200, 224, 0.8); |
| | | border-color: rgba(255, 255, 255, 0.2); |
| | | background-color: var(--bi-surface); |
| | | color: var(--bi-text-secondary); |
| | | border-color: var(--bi-border); |
| | | border-radius: 0; |
| | | padding: 6px 20px; |
| | | font-size: 14px; |
| | | transition: all 0.3s; |
| | | transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease; |
| | | } |
| | | |
| | | /* 第ä¸ä¸ªæé®å·¦ä¾§åè§ */ |
| | |
| | | |
| | | /* æé®ä¹é´çåé线 */ |
| | | .date-type-switch :deep(.el-radio-button:not(:last-child) .el-radio-button__inner) { |
| | | border-right: 1px solid rgba(255, 255, 255, 0.2); |
| | | border-right: 1px solid var(--bi-border); |
| | | } |
| | | |
| | | /* éä¸ç¶æçæ ·å¼ */ |
| | | /* éä¸ç¶æçæ ·å¼ï¼åçèçº¯è² */ |
| | | .date-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) { |
| | | background: linear-gradient(180deg, #3378ff 0%, #00a4ed 100%); |
| | | background-color: var(--bi-primary); |
| | | color: #ffffff; |
| | | border-color: rgba(51, 120, 255, 0.8); |
| | | border-color: var(--bi-primary); |
| | | box-shadow: none; |
| | | } |
| | | |
| | | /* æ¬åææ */ |
| | | .date-type-switch :deep(.el-radio-button__inner:hover) { |
| | | color: rgba(184, 200, 224, 1); |
| | | border-color: rgba(255, 255, 255, 0.3); |
| | | color: var(--bi-text); |
| | | border-color: var(--bi-accent); |
| | | } |
| | | |
| | | /* éä¸ç¶ææ¬å */ |
| | | .date-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner:hover) { |
| | | background: linear-gradient(180deg, #4e8aff 0%, #4ee4ff 100%); |
| | | background-color: var(--app-primary-hover); |
| | | color: #ffffff; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div class="panel-header"> |
| | | <span class="panel-title">{{ title }}</span> |
| | | </div> |
| | | <div class="panel-header bi-title-bar">{{ title }}</div> |
| | | </template> |
| | | |
| | | <script setup> |
| | |
| | | } |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .panel-header { |
| | | background-image: url("@/assets/BI/kehuhetongback@2x.png"); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | |
| | | .panel-title { |
| | | width: 100%; |
| | | font-weight: 500; |
| | | font-size: 16px; |
| | | color: #D9ECFF; |
| | | padding-left: 46px; |
| | | line-height: 36px; |
| | | } |
| | | </style> |
| | |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button__inner) { |
| | | background-color: rgba(26, 88, 176, 0.3); |
| | | color: rgba(184, 200, 224, 0.8); |
| | | border-color: rgba(255, 255, 255, 0.2); |
| | | background-color: var(--bi-surface); |
| | | color: var(--bi-text-secondary); |
| | | border-color: var(--bi-border); |
| | | border-radius: 0; |
| | | padding: 6px 20px; |
| | | font-size: 14px; |
| | | transition: all 0.3s; |
| | | transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease; |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button:first-child .el-radio-button__inner) { |
| | |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button:not(:last-child) .el-radio-button__inner) { |
| | | border-right: 1px solid rgba(255, 255, 255, 0.2); |
| | | border-right: 1px solid var(--bi-border); |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) { |
| | | background: linear-gradient(180deg, #3378ff 0%, #00a4ed 100%); |
| | | background-color: var(--bi-primary); |
| | | color: #ffffff; |
| | | border-color: rgba(51, 120, 255, 0.8); |
| | | border-color: var(--bi-primary); |
| | | box-shadow: none; |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button__inner:hover) { |
| | | color: rgba(184, 200, 224, 1); |
| | | border-color: rgba(255, 255, 255, 0.3); |
| | | color: var(--bi-text); |
| | | border-color: var(--bi-accent); |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner:hover) { |
| | | background: linear-gradient(180deg, #4e8aff 0%, #4ee4ff 100%); |
| | | background-color: var(--app-primary-hover); |
| | | color: #ffffff; |
| | | } |
| | | </style> |
| | |
| | | <div> |
| | | <PanelHeader title="ç产订å宿è¿åº¦" /> |
| | | <div class="main-panel"> |
| | | <div class="panel-item-customers"> |
| | | <div class="panel-item-customers bi-panel"> |
| | | <CarouselCards :items="cardItems" :visible-count="4" /> |
| | | <div |
| | | class="progress-table-container" |
| | |
| | | import { getProgressStatistics } from '@/api/viewIndex.js' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import CarouselCards from './CarouselCards.vue' |
| | | import { BI_COLORS } from '../theme.js' |
| | | |
| | | const progressTableRef = ref(null) |
| | | const progressTableScrollTimer = ref(null) |
| | |
| | | // æ ¹æ®è¿åº¦ç¾åæ¯è¿åé¢è² |
| | | const progressColor = (percentage) => { |
| | | const p = percentage || 0 |
| | | if (p < 30) return '#f56c6c' |
| | | if (p < 50) return '#e6a23c' |
| | | if (p < 80) return '#409eff' |
| | | return '#67c23a' |
| | | if (p < 30) return BI_COLORS.red |
| | | if (p < 50) return BI_COLORS.amber |
| | | if (p < 80) return BI_COLORS.primary |
| | | return BI_COLORS.green |
| | | } |
| | | |
| | | const setRowRef = (el, index) => { |
| | |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 428px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .progress-table-container { |
| | | height: 320px; |
| | | flex: 1; |
| | | min-height: 0; |
| | | overflow-y: auto; |
| | | overflow-x: hidden; |
| | | margin-top: 10px; |
| | | scrollbar-width: none; |
| | | -ms-overflow-style: none; |
| | | } |
| | |
| | | .progress-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | color: #b8c8e0; |
| | | color: var(--bi-text-secondary); |
| | | font-size: 12px; |
| | | table-layout: fixed; |
| | | } |
| | |
| | | .progress-table thead { |
| | | position: sticky; |
| | | top: 0; |
| | | background-color: rgba(26, 88, 176, 0.9); |
| | | background-color: var(--bi-surface-2); |
| | | z-index: 10; |
| | | } |
| | | |
| | |
| | | padding: 8px 6px; |
| | | text-align: left; |
| | | font-weight: 500; |
| | | border-bottom: 1px solid rgba(184, 200, 224, 0.3); |
| | | color: #b8c8e0; |
| | | border-bottom: 1px solid var(--bi-divider); |
| | | color: var(--bi-text); |
| | | font-size: 12px; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | |
| | | |
| | | .progress-table td { |
| | | padding: 8px 6px; |
| | | border-bottom: 1px solid rgba(184, 200, 224, 0.1); |
| | | border-bottom: 1px solid var(--bi-divider); |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | |
| | | } |
| | | |
| | | .progress-table tbody tr:hover { |
| | | background-color: rgba(184, 200, 224, 0.1); |
| | | background-color: rgba(159, 176, 207, 0.06); |
| | | } |
| | | |
| | | .progress-table tbody tr.row-under-header { |
| | |
| | | } |
| | | |
| | | .progress-table :deep(.el-progress-bar__outer) { |
| | | background-color: rgba(184, 200, 224, 0.2); |
| | | background-color: rgba(159, 176, 207, 0.15); |
| | | } |
| | | |
| | | .progress-table :deep(.el-progress__text) { |
| | | color: #b8c8e0; |
| | | color: var(--bi-text-secondary); |
| | | font-size: 11px; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div> |
| | | <!-- 设å¤ç»è®¡ --> |
| | | <div class="equipment-stats"> |
| | | <div class="equipment-header"> |
| | | <img |
| | | src="@/assets/BI/shujutongjiicon@2x.png" |
| | | alt="徿 " |
| | | class="equipment-icon" |
| | | /> |
| | | <span class="equipment-title">æå
¥äº§åºåæ</span> |
| | | </div> |
| | | <!-- æå
¥äº§åºåæ --> |
| | | <PanelHeader title="æå
¥äº§åºåæ" /> |
| | | <div class="bi-panel chart-box"> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis1" |
| | | :yAxis="yAxis1" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: BI_COLORS.text } }" |
| | | style="height: 260px" |
| | | /> |
| | | </div> |
| | |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import * as echarts from 'echarts' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import { inputOutputAnalysis } from '@/api/viewIndex.js' |
| | | import { BI_COLORS, BI_TOOLTIP, areaGradient } from '../theme.js' |
| | | |
| | | const chartStyle = { width: '100%', height: '100%' } |
| | | const grid = { |
| | |
| | | itemGap: 24, |
| | | itemWidth: 12, |
| | | itemHeight: 12, |
| | | textStyle: { color: '#B8C8E0', fontSize: 14 }, |
| | | textStyle: { color: BI_COLORS.text, fontSize: 14 }, |
| | | data: [ |
| | | { name: '产åº', itemStyle: { color: 'rgba(11, 137, 254, 1)' } }, |
| | | { name: 'æå
¥', itemStyle: { color: 'rgba(11, 249, 254, 1)' } }, |
| | | { name: '产åº', itemStyle: { color: BI_COLORS.primary } }, |
| | | { name: 'æå
¥', itemStyle: { color: BI_COLORS.teal } }, |
| | | ], |
| | | } |
| | | |
| | |
| | | type: 'category', |
| | | data: [], |
| | | axisTick: { show: false }, |
| | | axisLine: { show: false, lineStyle: { color: 'rgba(184, 200, 224, 0.3)' } }, |
| | | axisLabel: { color: '#B8C8E0', fontSize: 12 }, |
| | | splitLine: { show: false, lineStyle: { type: 'dashed', color: 'rgba(184, 200, 224, 0.2)' } }, |
| | | axisLine: { lineStyle: { color: BI_COLORS.axisLine } }, |
| | | axisLabel: { color: BI_COLORS.text, fontSize: 12 }, |
| | | splitLine: { show: false }, |
| | | }, |
| | | ]) |
| | | |
| | |
| | | { |
| | | type: 'value', |
| | | name: 'åä½: ä»¶', |
| | | nameTextStyle: { color: '#B8C8E0', fontSize: 12, padding: [0, 0, 0, 0] }, |
| | | nameTextStyle: { color: BI_COLORS.text, fontSize: 12 }, |
| | | axisLine: { show: false }, |
| | | axisTick: { show: false }, |
| | | axisLabel: { color: '#B8C8E0', fontSize: 12 }, |
| | | splitLine: { lineStyle: { color: '#B8C8E0' } }, |
| | | axisLabel: { color: BI_COLORS.text, fontSize: 12 }, |
| | | splitLine: { lineStyle: { color: BI_COLORS.splitLine } }, |
| | | }, |
| | | ] |
| | | |
| | |
| | | { |
| | | name: '产åº', |
| | | type: 'line', |
| | | smooth: false, |
| | | smooth: true, |
| | | showSymbol: true, |
| | | symbol: 'circle', |
| | | symbolSize: 8, |
| | | lineStyle: { color: 'rgba(11, 137, 254, 1)', width: 2 }, |
| | | itemStyle: { color: 'rgba(11, 137, 254, 1)', borderWidth: 0 }, |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: 'rgba(11, 137, 254, 0.40)' }, |
| | | { offset: 1, color: 'rgba(11, 137, 254, 0.05)' }, |
| | | ]), |
| | | }, |
| | | symbolSize: 6, |
| | | lineStyle: { color: BI_COLORS.primary, width: 2 }, |
| | | itemStyle: { color: BI_COLORS.primary, borderWidth: 0 }, |
| | | areaStyle: { color: areaGradient(BI_COLORS.primary) }, |
| | | data: [], |
| | | emphasis: { focus: 'series' }, |
| | | }, |
| | | { |
| | | name: 'æå
¥', |
| | | type: 'line', |
| | | smooth: false, |
| | | smooth: true, |
| | | showSymbol: true, |
| | | symbol: 'circle', |
| | | symbolSize: 8, |
| | | lineStyle: { color: 'rgba(11, 249, 254, 1)', width: 2 }, |
| | | itemStyle: { color: 'rgba(11, 249, 254, 1)', borderWidth: 0 }, |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: 'rgba(11, 249, 254, 0.5)' }, |
| | | { offset: 1, color: 'rgba(11, 249, 254, 0.05)' }, |
| | | ]), |
| | | }, |
| | | symbolSize: 6, |
| | | lineStyle: { color: BI_COLORS.teal, width: 2 }, |
| | | itemStyle: { color: BI_COLORS.teal, borderWidth: 0 }, |
| | | areaStyle: { color: areaGradient(BI_COLORS.teal) }, |
| | | data: [], |
| | | emphasis: { focus: 'series' }, |
| | | }, |
| | |
| | | const tooltip = { |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'line' }, |
| | | borderWidth: 1, |
| | | textStyle: { fontSize: 12 }, |
| | | ...BI_TOOLTIP, |
| | | formatter(params) { |
| | | let result = params[0].axisValue + '<br/>' |
| | | params.forEach((item) => { |
| | |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .equipment-stats { |
| | | border: 1px solid #1a58b0; |
| | | padding: 0 18px 18px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 16px; |
| | | } |
| | | |
| | | .equipment-header { |
| | | font-weight: 500; |
| | | font-size: 21px; |
| | | display: flex; |
| | | border-bottom: 1px solid; |
| | | border-image: linear-gradient( |
| | | 270deg, |
| | | rgba(0, 126, 255, 0) 0%, |
| | | rgba(0, 126, 255, 0.4549) 35%, |
| | | #007eff 78%, |
| | | #007eff 100% |
| | | ) |
| | | 1; |
| | | padding-bottom: 2px; |
| | | } |
| | | |
| | | .equipment-title { |
| | | font-weight: 500; |
| | | font-size: 18px; |
| | | background: linear-gradient(360deg, #056dff 0%, #43e8fc 100%); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | line-height: 50px; |
| | | } |
| | | |
| | | .equipment-icon { |
| | | width: 50px; |
| | | height: 50px; |
| | | .chart-box { |
| | | padding: 12px 18px 18px; |
| | | margin-top: 4px; |
| | | } |
| | | </style> |
| | |
| | | <div |
| | | v-for="item in statItems" |
| | | :key="item.name" |
| | | class="stat-card" |
| | | class="stat-card bi-panel" |
| | | > |
| | | <img src="@/assets/BI/icon@2x.png" alt="徿 " class="card-icon" /> |
| | | <div class="card-content"> |
| | | <span class="card-label">{{ item.name }}</span> |
| | | <span class="card-value">{{ item.value }}</span> |
| | | <span class="card-value bi-num">{{ item.value }}</span> |
| | | <div class="card-compare" :class="compareClass(Number(item.rate))"> |
| | | <span>忝</span> |
| | | <span class="compare-value">{{ formatPercent(item.rate) }}</span> |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div v-if="!statItems.length" class="stats-empty bi-panel">ææ æ°æ®</div> |
| | | </div> |
| | | |
| | | </div> |
| | |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | background-image: url('@/assets/BI/border@2x.png'); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | position: relative; |
| | | height: 142px; |
| | | } |
| | | |
| | | .card-icon { |
| | | width: 100px; |
| | | height: 100px; |
| | | margin: 20px 20px 0 10px; |
| | | /* 左侧åçèç«æ¡ï¼å¼åºé¦é¡µé¢æ¿æ é¢ */ |
| | | .stat-card::before { |
| | | content: ''; |
| | | position: absolute; |
| | | left: 16px; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 3px; |
| | | height: 64px; |
| | | border-radius: 2px; |
| | | background: var(--bi-accent); |
| | | } |
| | | |
| | | .card-content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | padding-left: 36px; |
| | | } |
| | | |
| | | .card-value { |
| | | font-weight: 500; |
| | | font-weight: 600; |
| | | font-size: 40px; |
| | | background: linear-gradient(360deg, #008bfd 0%, #ffffff 100%); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | line-height: 1.1; |
| | | color: var(--bi-text); |
| | | } |
| | | |
| | | .card-label { |
| | | font-weight: 400; |
| | | font-size: 16px; |
| | | color: rgba(208, 231, 255, 0.7); |
| | | color: var(--bi-text-secondary); |
| | | } |
| | | |
| | | .card-compare { |
| | |
| | | align-items: center; |
| | | gap: 6px; |
| | | font-size: 15px; |
| | | color: #d0e7ff; |
| | | color: var(--bi-text-secondary); |
| | | } |
| | | |
| | | .card-compare > span:first-child { |
| | |
| | | .compare-icon { |
| | | font-size: 14px; |
| | | position: relative; |
| | | top: -1px; /* 轻微ä¸ç§»ï¼è®©ç®å¤´ä¸æååç´å±
ä¸å¯¹é½ */ |
| | | top: -1px; |
| | | } |
| | | |
| | | .compare-up .compare-value, |
| | | .compare-up .compare-icon { |
| | | color: #00c853; |
| | | color: var(--bi-success); |
| | | } |
| | | |
| | | .compare-down .compare-value, |
| | | .compare-down .compare-icon { |
| | | color: #ff5252; |
| | | color: var(--bi-danger); |
| | | } |
| | | |
| | | .stats-empty { |
| | | flex: 1; |
| | | height: 142px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | color: var(--bi-text-tertiary); |
| | | font-size: 14px; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="å¨å¶åç»è®¡åæ" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <div class="main-panel panel-item-customers bi-panel"> |
| | | <CarouselCards :items="cardItems" :visible-count="3" /> |
| | | <div class="chart-wrapper"> |
| | | <Echarts |
| | |
| | | :tooltip="tooltip" |
| | | :xAxis="workInProcessXAxis" |
| | | :yAxis="workInProcessYAxis" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: BI_COLORS.text } }" |
| | | style="height: 100%" |
| | | /> |
| | | </div> |
| | |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import CarouselCards from './CarouselCards.vue' |
| | | import { getWorkInProcessTurnover } from '@/api/viewIndex.js' |
| | | import { BI_COLORS, BI_TOOLTIP, barGradient } from '../theme.js' |
| | | |
| | | // å¨å¶åå¨è½¬ç»è®¡å¯¹è±¡ |
| | | const workInProcessStatistics = ref({ |
| | |
| | | const tooltip = { |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'shadow' }, |
| | | ...BI_TOOLTIP, |
| | | formatter: function (params) { |
| | | let result = params[0].axisValueLabel + '<br/>' |
| | | params.forEach((item) => { |
| | | result += `<div style="color: #B8C8E0">${item.marker} ${item.seriesName}: ${item.value}</div>` |
| | | result += `<div>${item.marker} ${item.seriesName}: ${item.value}</div>` |
| | | }) |
| | | return result |
| | | }, |
| | |
| | | { |
| | | type: 'category', |
| | | axisTick: { show: false }, |
| | | axisLabel: { color: '#B8C8E0' }, |
| | | axisLabel: { color: BI_COLORS.text }, |
| | | data: [], |
| | | }, |
| | | ]) |
| | | const workInProcessYAxis = [ |
| | | { |
| | | type: 'value', |
| | | axisLabel: { color: '#B8C8E0' }, |
| | | axisLabel: { color: BI_COLORS.text }, |
| | | name: '', |
| | | splitLine: { lineStyle: { color: BI_COLORS.splitLine } }, |
| | | }, |
| | | ] |
| | | const workInProcessBarLegend = { |
| | | show: false, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | textStyle: { color: BI_COLORS.text }, |
| | | data: [], |
| | | } |
| | | const workInProcessBarSeries = ref([ |
| | | { |
| | | name: 'å¨å¶åæ°é', |
| | | type: 'bar', |
| | | barWidth: 25, |
| | | barWidth: 20, |
| | | barGap: 0, |
| | | emphasis: { focus: 'series' }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: 'rgba(0,164,237,0)' }, |
| | | { offset: 0, color: '#4EE4FF' }, |
| | | ], |
| | | }, |
| | | borderRadius: [3, 3, 0, 0], |
| | | color: barGradient(BI_COLORS.primary), |
| | | }, |
| | | label: { |
| | | show: true, |
| | | position: 'top', |
| | | color: '#B8C8E0', |
| | | color: BI_COLORS.text, |
| | | }, |
| | | data: [], |
| | | }, |
| | |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="å·¥åºäº§åºåæ" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <div class="panel-item-customers bi-panel"> |
| | | <div class="filters-row"> |
| | | <DateTypeSwitch v-model="dateType" @change="handleDateTypeChange" /> |
| | | </div> |
| | | <div class="pie-chart-wrapper" ref="pieWrapperRef"> |
| | | <div class="pie-background" ref="pieBackgroundRef"></div> |
| | | <Echarts |
| | | ref="echartsRef" |
| | | :chartStyle="chartStyle" |
| | | :legend="pieLegend" |
| | | :series="pieSeries" |
| | | :tooltip="pieTooltip" |
| | | :color="pieColors" |
| | | :options="pieOptions" |
| | | style="height: 320px" |
| | | /> |
| | | <div class="pie-chart-wrapper"> |
| | | <template v-if="hasData"> |
| | | <Echarts |
| | | ref="echartsRef" |
| | | :chartStyle="chartStyle" |
| | | :legend="pieLegend" |
| | | :series="pieSeries" |
| | | :tooltip="pieTooltip" |
| | | :color="pieColors" |
| | | :options="pieOptions" |
| | | style="height: 320px" |
| | | /> |
| | | <!-- ç¯å¿æ±æ»ï¼æ¿ä»£åç«ç°å¾è¾¹æ¡è£
é¥°å¾ --> |
| | | <div class="pie-center"> |
| | | <span class="center-value">{{ totalValue }}</span> |
| | | <span class="center-label">æ»äº§åº(ä»¶)</span> |
| | | </div> |
| | | </template> |
| | | <div v-else class="pie-empty">ææ æ°æ®</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, computed } from 'vue' |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import { processOutputAnalysis } from '@/api/viewIndex.js' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import DateTypeSwitch from '@/views/reportAnalysis/financialAnalysis/components/DateTypeSwitch.vue' |
| | | import { useChartBackground } from '@/hooks/useChartBackground.js' |
| | | import DateTypeSwitch from './DateTypeSwitch.vue' |
| | | import { BI_COLORS, BI_PIE_PALETTE, BI_TOOLTIP } from '../theme.js' |
| | | |
| | | const pieWrapperRef = ref(null) |
| | | const pieBackgroundRef = ref(null) |
| | | |
| | | const dateType = ref(1) // 1=å¨ 2=æ 3=å£åº¦ |
| | | const dateType = ref(3) // 1=å¨ 2=æ 3=å£åº¦ï¼é»è®¤å£åº¦ |
| | | |
| | | function array2obj(array, key) { |
| | | const resObj = {} |
| | |
| | | |
| | | const echartsRef = ref(null) |
| | | const pieDatas = ref([]) |
| | | const pieColors = ['#D1E4F5', '#5782F7', '#2F67EF', '#82BAFF', '#43e8fc', '#27EBE7'] |
| | | const pieColors = BI_PIE_PALETTE |
| | | |
| | | const pieObjData = computed(() => array2obj(pieDatas.value, 'name')) |
| | | |
| | | // ç¯å¿æ»éï¼ç©ºæ°æ®å
åº 0ï¼ |
| | | const totalValue = computed(() => |
| | | pieDatas.value.reduce((sum, d) => sum + (Number(d.value) || 0), 0) |
| | | ) |
| | | |
| | | // å
¨é¶æ°æ®ä¸æ¸²ææåºï¼minAngle 伿 0 å¼ä¹æææ´ç¯ï¼é æè¯¯å¯¼ |
| | | const hasData = computed(() => pieDatas.value.some((d) => Number(d.value) > 0)) |
| | | |
| | | const pieLegend = computed(() => { |
| | | const data = pieDatas.value.map((d, idx) => ({ |
| | |
| | | textStyle: { |
| | | rich: { |
| | | value: { |
| | | color: '#43e8fc', |
| | | color: BI_COLORS.textStrong, |
| | | fontSize: 14, |
| | | fontWeight: 600, |
| | | padding: [0, 0, 0, 10], |
| | | }, |
| | | unit: { |
| | | color: '#82baff', |
| | | color: BI_COLORS.text, |
| | | fontSize: 12, |
| | | fontWeight: 600, |
| | | padding: [0, 10, 0, 0], |
| | | }, |
| | | percent: { |
| | | color: '#43e8fc', |
| | | color: BI_COLORS.primary, |
| | | fontSize: 14, |
| | | fontWeight: 600, |
| | | padding: [0, 0, 0, 0], |
| | | }, |
| | | title: { |
| | | color: BI_COLORS.text, |
| | | fontSize: 12, |
| | | padding: [0, 0, 0, 0], |
| | | }, |
| | |
| | | const pieTooltip = { |
| | | trigger: 'item', |
| | | formatter: '{a} <br/>{b} : {c}ä»¶ ({d}%)', |
| | | ...BI_TOOLTIP, |
| | | } |
| | | |
| | | const pieSeries = computed(() => [ |
| | | { |
| | | name: 'å·¥åºäº§åºåæ', |
| | | type: 'pie', |
| | | radius: '60%', |
| | | center: ['25%', '50%'], |
| | | radius: ['46%', '68%'], |
| | | center: ['27%', '50%'], |
| | | itemStyle: { |
| | | borderColor: '#0a1c3a', |
| | | borderColor: BI_COLORS.surface, |
| | | borderWidth: 2, |
| | | borderRadius: 4, |
| | | }, |
| | | label: { |
| | | show: false |
| | | }, |
| | | minAngle: 15, |
| | | data: pieDatas.value, |
| | | data: hasData.value ? pieDatas.value : [], |
| | | animationType: 'scale', |
| | | animationEasing: 'elasticOut', |
| | | animationDelay: function () { |
| | | return Math.random() * 200 |
| | | }, |
| | | animationEasing: 'cubicOut', |
| | | }, |
| | | ]) |
| | | |
| | | const pieOptions = { |
| | | backgroundColor: 'transparent', |
| | | textStyle: { color: '#B8C8E0' }, |
| | | textStyle: { color: BI_COLORS.text }, |
| | | } |
| | | |
| | | // 使ç¨å°è£
çèæ¯ä½ç½®è°æ´æ¹æ³ |
| | | // å¾è¡¨ä¸å¿æ¯ ['25%', '50%']ï¼èæ¯éè¦å¯¹é½å°è¿ä¸ªä½ç½® |
| | | const { init: initBackground, cleanup: cleanupBackground } = useChartBackground({ |
| | | wrapperRef: pieWrapperRef, |
| | | backgroundRef: pieBackgroundRef, |
| | | left: '25%', // å¾è¡¨ä¸å¿ X æ¯ 25% |
| | | top: '50%', // å¾è¡¨ä¸å¿ Y æ¯ 50% |
| | | offsetX: '-51.5%', // X è½´åç§» |
| | | offsetY: '-50%', // Y è½´åç§» |
| | | watchData: pieDatas // ç嬿°æ®ååï¼èªå¨è°æ´ä½ç½® |
| | | }) |
| | | |
| | | const fetchData = () => { |
| | | processOutputAnalysis({ dateType: dateType.value }) |
| | |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | initBackground() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | cleanupBackground() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .main-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .filters-row { |
| | |
| | | position: relative; |
| | | width: 100%; |
| | | height: 320px; |
| | | background: transparent; |
| | | } |
| | | |
| | | .pie-background { |
| | | /* ç¯å¿æ°åä¸é¥¼å¾åå¿ï¼27%,50%ï¼å¯¹é½ */ |
| | | .pie-center { |
| | | position: absolute; |
| | | width: 310px; |
| | | height: 310px; |
| | | background-image: url('@/assets/BI/ç«ç°å¾è¾¹æ¡.png'); |
| | | background-size: contain; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | z-index: 1; |
| | | pointer-events: none; |
| | | /* ä½ç½®ç± JS å¨æè®¾ç½®ï¼é»è®¤å±
ä¸ */ |
| | | left: 25%; |
| | | left: 27%; |
| | | top: 50%; |
| | | transform: translate(-51.5%, -50%); |
| | | transform: translate(-50%, -50%); |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | gap: 4px; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .center-value { |
| | | font-weight: 600; |
| | | font-size: 26px; |
| | | line-height: 1; |
| | | color: var(--bi-text); |
| | | font-variant-numeric: tabular-nums; |
| | | } |
| | | |
| | | .center-label { |
| | | font-size: 12px; |
| | | color: var(--bi-text-secondary); |
| | | } |
| | | |
| | | .pie-empty { |
| | | position: absolute; |
| | | left: 27%; |
| | | top: 50%; |
| | | transform: translate(-50%, -50%); |
| | | font-size: 14px; |
| | | color: var(--bi-text-tertiary); |
| | | pointer-events: none; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="çäº§æ ¸ç®åæ" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <div class="main-panel panel-item-customers bi-panel"> |
| | | <div class="filters-row"> |
| | | <DateTypeSwitch v-model="dateType" @change="handleDateTypeChange" /> |
| | | </div> |
| | |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis1" |
| | | :yAxis="yAxis1" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: BI_COLORS.text } }" |
| | | style="height: 260px" |
| | | /> |
| | | </div> |
| | |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import DateTypeSwitch from './DateTypeSwitch.vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import { BI_COLORS, BI_TOOLTIP, barGradient } from '../theme.js' |
| | | |
| | | const dateType = ref(1) |
| | | const dateType = ref(3) // 1=å¨ 2=æ 3=å£åº¦ï¼é»è®¤å£åº¦ |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '140%', |
| | | height: '100%', |
| | | } |
| | | |
| | | const grid = { left: '3%', right: '4%', bottom: '3%', top: '10%', containLabel: true } |
| | | |
| | | const barLegend = { |
| | | show: true, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | textStyle: { color: BI_COLORS.text }, |
| | | data: ['宿æ°é', 'å·¥èµéé¢', 'åæ ¼ç'], |
| | | } |
| | | |
| | |
| | | barGap: '40%', |
| | | emphasis: { focus: 'series' }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: 'rgba(0, 164, 237, 0)' }, |
| | | { offset: 0, color: 'rgba(78, 228, 255, 1)' }, |
| | | ], |
| | | }, |
| | | borderRadius: [3, 3, 0, 0], |
| | | color: barGradient(BI_COLORS.primary), |
| | | }, |
| | | data: [], |
| | | }, |
| | |
| | | barWidth: 20, |
| | | emphasis: { focus: 'series' }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: 'rgba(83, 126, 245, 0.19)' }, |
| | | { offset: 0, color: 'rgba(144, 97, 248, 1)' }, |
| | | ], |
| | | }, |
| | | borderRadius: [3, 3, 0, 0], |
| | | color: barGradient(BI_COLORS.violet), |
| | | }, |
| | | data: [], |
| | | }, |
| | |
| | | name: 'åæ ¼ç', |
| | | type: 'line', |
| | | yAxisIndex: 1, |
| | | smooth: true, |
| | | showSymbol: true, |
| | | symbol: 'circle', |
| | | symbolSize: 8, |
| | | lineStyle: { color: 'rgba(90, 216, 166, 1)', width: 2 }, |
| | | itemStyle: { color: 'rgba(90, 216, 166, 1)' }, |
| | | symbolSize: 6, |
| | | lineStyle: { color: BI_COLORS.amber, width: 2 }, |
| | | itemStyle: { color: BI_COLORS.amber }, |
| | | data: [], |
| | | emphasis: { focus: 'series' }, |
| | | }, |
| | | ]) |
| | | |
| | | const xAxis1 = ref([ |
| | | { type: 'category', axisTick: { show: false }, axisLabel: { color: '#B8C8E0' }, data: [] }, |
| | | { type: 'category', axisTick: { show: false }, axisLine: { lineStyle: { color: BI_COLORS.axisLine } }, axisLabel: { color: BI_COLORS.text }, data: [] }, |
| | | ]) |
| | | |
| | | const yAxis1 = [ |
| | | { |
| | | type: 'value', |
| | | name: 'æ°é/éé¢', |
| | | axisLabel: { color: '#B8C8E0' }, |
| | | nameTextStyle: { color: '#B8C8E0' }, |
| | | // splitLine: { lineStyle: { color: 'rgba(184, 200, 224, 0.2)' } }, |
| | | axisLabel: { color: BI_COLORS.text }, |
| | | nameTextStyle: { color: BI_COLORS.text }, |
| | | splitLine: { lineStyle: { color: BI_COLORS.splitLine } }, |
| | | }, |
| | | { |
| | | type: 'value', |
| | | name: 'åæ ¼ç(%)', |
| | | min: 0, |
| | | max: 100, |
| | | axisLabel: { color: '#B8C8E0', formatter: '{value}%' }, |
| | | nameTextStyle: { color: '#B8C8E0' }, |
| | | splitLine: { lineStyle: { color: 'rgba(184, 200, 224, 0.2)' } }, |
| | | axisLabel: { color: BI_COLORS.text, formatter: '{value}%' }, |
| | | nameTextStyle: { color: BI_COLORS.text }, |
| | | splitLine: { show: false }, |
| | | }, |
| | | ] |
| | | |
| | | const tooltip = { |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'cross' }, |
| | | ...BI_TOOLTIP, |
| | | formatter(params) { |
| | | let result = params[0].axisValueLabel + '<br/>' |
| | | params.forEach((item) => { |
| | |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="å·¥åæ§è¡æçåæ" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <div class="main-panel panel-item-customers bi-panel"> |
| | | <div class="filters-row"> |
| | | <DateTypeSwitch v-model="dateType" @change="handleDateTypeChange" /> |
| | | </div> |
| | |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis1" |
| | | :yAxis="yAxis1" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: BI_COLORS.text } }" |
| | | style="height: 260px" |
| | | /> |
| | | </div> |
| | |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import DateTypeSwitch from './DateTypeSwitch.vue' |
| | | import { BI_COLORS, BI_TOOLTIP, barGradient } from '../theme.js' |
| | | |
| | | const dateType = ref(1) // 1=å¨ 2=æ 3=å£åº¦ |
| | | const dateType = ref(3) // 1=å¨ 2=æ 3=å£åº¦ï¼é»è®¤å£åº¦ |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '140%', |
| | | height: '100%', |
| | | } |
| | | |
| | | const grid = { left: '3%', right: '4%', bottom: '3%', top: '10%', containLabel: true } |
| | | |
| | | const barLegend = { |
| | | show: true, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | textStyle: { color: BI_COLORS.text }, |
| | | data: ['å¼å·¥', '宿', 'è¯åç'], |
| | | } |
| | | |
| | |
| | | barGap: '40%', |
| | | emphasis: { focus: 'series' }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: 'rgba(0, 164, 237, 0)' }, |
| | | { offset: 0, color: 'rgba(78, 228, 255, 1)' }, |
| | | ], |
| | | }, |
| | | borderRadius: [3, 3, 0, 0], |
| | | color: barGradient(BI_COLORS.primary), |
| | | }, |
| | | data: [], |
| | | }, |
| | |
| | | barWidth: 20, |
| | | emphasis: { focus: 'series' }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: 'rgba(83, 126, 245, 0.19)' }, |
| | | { offset: 0, color: 'rgba(144, 97, 248, 1)' }, |
| | | ], |
| | | }, |
| | | borderRadius: [3, 3, 0, 0], |
| | | color: barGradient(BI_COLORS.green), |
| | | }, |
| | | data: [], |
| | | }, |
| | |
| | | name: 'è¯åç', |
| | | type: 'line', |
| | | yAxisIndex: 1, |
| | | smooth: true, |
| | | showSymbol: true, |
| | | symbol: 'circle', |
| | | symbolSize: 8, |
| | | lineStyle: { color: 'rgba(90, 216, 166, 1)', width: 2 }, |
| | | itemStyle: { color: 'rgba(90, 216, 166, 1)' }, |
| | | symbolSize: 6, |
| | | lineStyle: { color: BI_COLORS.amber, width: 2 }, |
| | | itemStyle: { color: BI_COLORS.amber }, |
| | | data: [], |
| | | emphasis: { focus: 'series' }, |
| | | }, |
| | |
| | | const tooltip = { |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'cross' }, |
| | | ...BI_TOOLTIP, |
| | | formatter(params) { |
| | | let result = params[0].axisValueLabel + '<br/>' |
| | | params.forEach((item) => { |
| | |
| | | } |
| | | |
| | | const xAxis1 = ref([ |
| | | { type: 'category', axisTick: { show: false }, axisLabel: { color: '#B8C8E0' }, data: [] }, |
| | | { type: 'category', axisTick: { show: false }, axisLine: { lineStyle: { color: BI_COLORS.axisLine } }, axisLabel: { color: BI_COLORS.text }, data: [] }, |
| | | ]) |
| | | |
| | | const yAxis1 = [ |
| | | { type: 'value', name: 'ä»¶', axisLabel: { color: '#B8C8E0' }, nameTextStyle: { color: '#B8C8E0' } }, |
| | | { |
| | | type: 'value', |
| | | name: 'ä»¶', |
| | | axisLabel: { color: BI_COLORS.text }, |
| | | nameTextStyle: { color: BI_COLORS.text }, |
| | | splitLine: { lineStyle: { color: BI_COLORS.splitLine } }, |
| | | }, |
| | | { |
| | | type: 'value', |
| | | name: 'è¯åç(%)', |
| | | min: 0, |
| | | max: 100, |
| | | axisLabel: { color: '#B8C8E0', formatter: '{value}%' }, |
| | | nameTextStyle: { color: '#B8C8E0' }, |
| | | splitLine: { lineStyle: { color: 'rgba(184, 200, 224, 0.2)' } }, |
| | | axisLabel: { color: BI_COLORS.text, formatter: '{value}%' }, |
| | | nameTextStyle: { color: BI_COLORS.text }, |
| | | splitLine: { show: false }, |
| | | }, |
| | | ] |
| | | |
| | |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | |
| | | <template> |
| | | <div class="scale-container"> |
| | | <div class="data-dashboard" :style="{ transform: `scale(${scaleRatio})` }"> |
| | | <div class="data-dashboard dashboard-dark" :style="{ transform: `scale(${scaleRatio})` }"> |
| | | <!-- å
¨å±æé® - ç§»å¨å°å·¦ä¸è§ --> |
| | | <button class="fullscreen-btn" @click="toggleFullscreen" :title="isFullscreen ? 'éåºå
¨å±' : 'å
¨å±æ¾ç¤º'"> |
| | | <svg v-if="!isFullscreen" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| | |
| | | position: relative; |
| | | width: 100%; |
| | | /* 页é¢å¨å¸¸è§å¸å±ä¸ï¼æé¡¶æ ï¼é»è®¤åå» 84pxï¼é¿å
å
容被è£å */ |
| | | height: calc(100vh - 84px); |
| | | height: calc(100vh - 100px); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background-color: #000; |
| | | background-color: #0a1120; |
| | | overflow: hidden; |
| | | } |
| | | |
| | |
| | | position: relative; |
| | | width: 1920px; |
| | | height: 1080px; |
| | | background-image: url("@/assets/BI/backImage@2x.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | background: |
| | | radial-gradient(1200px 520px at 50% -10%, rgba(44, 81, 217, 0.16), transparent 60%), |
| | | linear-gradient(180deg, var(--bi-bg-to) 0%, var(--bi-bg-from) 100%); |
| | | transform-origin: center center; |
| | | } |
| | | |
| | |
| | | position: relative; |
| | | z-index: 1; |
| | | height: 86px; |
| | | background-image: url("@/assets/BI/biaoti.png"); |
| | | background-size: cover; |
| | | background-repeat: no-repeat; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 28px; |
| | | padding: 0 80px; |
| | | } |
| | | |
| | | /* æ é¢ä¸¤ä¾§å¯¹ç§°æ¸éç»çº¿ï¼æ¿ä»£å¾çæ 颿 */ |
| | | .dashboard-header::before, |
| | | .dashboard-header::after { |
| | | content: ''; |
| | | flex: 1; |
| | | height: 1px; |
| | | } |
| | | |
| | | .dashboard-header::before { |
| | | background: linear-gradient(90deg, transparent, rgba(91, 140, 255, 0.4)); |
| | | } |
| | | |
| | | .dashboard-header::after { |
| | | background: linear-gradient(90deg, rgba(91, 140, 255, 0.4), transparent); |
| | | } |
| | | |
| | | .factory-name { |
| | | font-weight: 600; |
| | | font-size: 52px; |
| | | color: #FFFFFF; |
| | | top: 16px; |
| | | position: absolute; |
| | | font-size: 36px; |
| | | letter-spacing: 6px; |
| | | color: var(--bi-text); |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .fullscreen-btn { |
| | |
| | | left: 20px; |
| | | width: 40px; |
| | | height: 40px; |
| | | background: rgba(0, 20, 60, 0.8); |
| | | border: 1px solid rgba(0, 212, 255, 0.3); |
| | | border-radius: 6px; |
| | | color: #00d4ff; |
| | | background: var(--bi-surface); |
| | | border: 1px solid var(--bi-border); |
| | | border-radius: var(--app-radius-sm); |
| | | color: var(--bi-accent); |
| | | cursor: pointer; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | transition: all 0.3s; |
| | | transition: background-color 0.2s ease, border-color 0.2s ease; |
| | | z-index: 10000; |
| | | } |
| | | |
| | | .fullscreen-btn:hover { |
| | | background: rgba(0, 30, 90, 0.9); |
| | | border-color: rgba(0, 212, 255, 0.5); |
| | | background: var(--bi-surface-2); |
| | | border-color: var(--bi-accent); |
| | | } |
| | | |
| | | .dashboard-content { |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // æè²å¤§å± ECharts 主é¢å¸¸éï¼CSS åéæ æ³ç¨äº JS é
ç½®ï¼ç»ä¸å¨æ¤æ¶å£ |
| | | // è²å¼ä¸º tokens.scss è¯ä¹è²å¨æ·±åºä¸çæäº®çï¼ä¸ dashboard-dark.scss ä¿æå¯¹åº |
| | | |
| | | const toRgb = (hex) => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16)) |
| | | |
| | | export const BI_COLORS = { |
| | | primary: '#5b8cff', |
| | | green: '#2dbd85', |
| | | amber: '#f5a524', |
| | | red: '#f2635b', |
| | | violet: '#8b7cf8', |
| | | teal: '#2bc8c8', |
| | | text: '#9fb0cf', |
| | | textStrong: '#e8eefc', |
| | | surface: '#111c33', |
| | | axisLine: 'rgba(159, 176, 207, 0.25)', |
| | | splitLine: 'rgba(159, 176, 207, 0.12)', |
| | | } |
| | | |
| | | // é¦é¡µ PIE_COLORS çæåºæäº®ç |
| | | export const BI_PIE_PALETTE = [ |
| | | BI_COLORS.primary, |
| | | BI_COLORS.green, |
| | | BI_COLORS.amber, |
| | | BI_COLORS.red, |
| | | BI_COLORS.violet, |
| | | BI_COLORS.teal, |
| | | '#ff8a3d', |
| | | '#4a6cf0', |
| | | '#b37feb', |
| | | '#1377ef', |
| | | ] |
| | | |
| | | export const BI_TOOLTIP = { |
| | | backgroundColor: '#16233d', |
| | | borderColor: '#1f2f4d', |
| | | borderWidth: 1, |
| | | textStyle: { color: BI_COLORS.textStrong, fontSize: 12 }, |
| | | } |
| | | |
| | | // æ±é¡¶å®è²ãæ±åºæ¸éï¼æ¿ä»£åéè²è§å
æ¸å |
| | | export const barGradient = (hex) => { |
| | | const rgb = toRgb(hex).join(',') |
| | | return { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 0, color: `rgba(${rgb}, 0.9)` }, |
| | | { offset: 1, color: `rgba(${rgb}, 0.05)` }, |
| | | ], |
| | | } |
| | | } |
| | | |
| | | // æçº¿é¢ç§¯æ¸åï¼é
æ¹å¯¹é½é¦é¡µ HomeInvoiceChartï¼0.22 â 0.02ï¼ |
| | | export const areaGradient = (hex) => { |
| | | const rgb = toRgb(hex).join(',') |
| | | return { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 0, color: `rgba(${rgb}, 0.22)` }, |
| | | { offset: 1, color: `rgba(${rgb}, 0.02)` }, |
| | | ], |
| | | } |
| | | } |
| | |
| | | |
| | | /* æªéä¸ç¶æçæ ·å¼ */ |
| | | .date-type-switch :deep(.el-radio-button__inner) { |
| | | background-color: rgba(26, 88, 176, 0.3); |
| | | color: rgba(184, 200, 224, 0.8); |
| | | border-color: rgba(255, 255, 255, 0.2); |
| | | background-color: rgba(20, 32, 66, 0.55); |
| | | color: #8fa3c8; |
| | | border-color: #1e3160; |
| | | border-radius: 0; |
| | | padding: 6px 20px; |
| | | font-size: 14px; |
| | | transition: all 0.3s; |
| | | transition: color 0.2s ease, background-color 0.2s ease; |
| | | } |
| | | |
| | | /* 第ä¸ä¸ªæé®å·¦ä¾§åè§ */ |
| | | .date-type-switch :deep(.el-radio-button:first-child .el-radio-button__inner) { |
| | | border-top-left-radius: 4px; |
| | | border-bottom-left-radius: 4px; |
| | | border-top-left-radius: 6px; |
| | | border-bottom-left-radius: 6px; |
| | | } |
| | | |
| | | /* æåä¸ä¸ªæé®å³ä¾§åè§ */ |
| | | .date-type-switch :deep(.el-radio-button:last-child .el-radio-button__inner) { |
| | | border-top-right-radius: 4px; |
| | | border-bottom-right-radius: 4px; |
| | | border-top-right-radius: 6px; |
| | | border-bottom-right-radius: 6px; |
| | | } |
| | | |
| | | /* æé®ä¹é´çåé线 */ |
| | | .date-type-switch :deep(.el-radio-button:not(:last-child) .el-radio-button__inner) { |
| | | border-right: 1px solid rgba(255, 255, 255, 0.2); |
| | | border-right: 1px solid #1e3160; |
| | | } |
| | | |
| | | /* éä¸ç¶æçæ ·å¼ */ |
| | | /* éä¸ç¶æçæ ·å¼ï¼åçèå®è² */ |
| | | .date-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) { |
| | | background: linear-gradient(180deg, #3378ff 0%, #00a4ed 100%); |
| | | background: #2c51d9; |
| | | color: #ffffff; |
| | | border-color: rgba(51, 120, 255, 0.8); |
| | | border-color: #2c51d9; |
| | | box-shadow: none; |
| | | } |
| | | |
| | | /* æ¬åææ */ |
| | | .date-type-switch :deep(.el-radio-button__inner:hover) { |
| | | color: rgba(184, 200, 224, 1); |
| | | border-color: rgba(255, 255, 255, 0.3); |
| | | color: #eaf1ff; |
| | | } |
| | | |
| | | /* éä¸ç¶ææ¬å */ |
| | | .date-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner:hover) { |
| | | background: linear-gradient(180deg, #4e8aff 0%, #4ee4ff 100%); |
| | | background: #4a6cf0; |
| | | border-color: #4a6cf0; |
| | | color: #ffffff; |
| | | } |
| | | </style> |
| | |
| | | |
| | | <style scoped> |
| | | .panel-header { |
| | | background-image: url("@/assets/BI/kehuhetongback@2x.png"); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | padding: 0 4px; |
| | | height: 36px; |
| | | } |
| | | |
| | | /* åçèç»ç«æ¡ï¼ä¸åºç¡æ°æ®å¤§å±æ 颿 ·å¼ä¸è´ */ |
| | | .panel-header::before { |
| | | content: ''; |
| | | width: 3px; |
| | | height: 14px; |
| | | border-radius: 2px; |
| | | background: linear-gradient(180deg, var(--bi-accent-bright, #4a6cf0) 0%, var(--bi-accent, #2c51d9) 100%); |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .panel-title { |
| | | width: 100%; |
| | | font-weight: 500; |
| | | font-size: 16px; |
| | | color: #D9ECFF; |
| | | padding-left: 46px; |
| | | line-height: 36px; |
| | | color: var(--bi-text-strong, #eaf1ff); |
| | | letter-spacing: 1px; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div> |
| | | <div class="panel-root"> |
| | | <div class="chart-header"> |
| | | <div class="chart-header-title"> |
| | | <PanelHeader title="宿æ£éªæ°" /> |
| | |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis1" |
| | | :yAxis="yAxis1" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }" |
| | | style="height: 260px" |
| | | :options="baseChartOptions" |
| | | style="height: 100%" |
| | | /> |
| | | </div> |
| | | </div> |
| | |
| | | import { completedInspectionCount } from '@/api/viewIndex.js' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import { BI, baseChartOptions, withTooltip } from '../theme.js' |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '140%', |
| | | height: '100%', |
| | | } |
| | | |
| | | const grid = { left: '3%', right: '4%', bottom: '3%', top: '10%', containLabel: true } |
| | | |
| | | const barLegend = { |
| | | show: true, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | textStyle: { color: BI.text }, |
| | | data: ['åæ ¼', 'ä¸åæ ¼', 'åæ ¼ç'], |
| | | } |
| | | |
| | |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: 'rgba(0, 164, 237, 0)' }, |
| | | { offset: 0, color: 'rgba(78, 228, 255, 1)' }, |
| | | { offset: 1, color: 'rgba(74, 108, 240, 0.05)' }, |
| | | { offset: 0, color: BI.accentBright }, |
| | | ], |
| | | }, |
| | | }, |
| | |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: 'rgba(83, 126, 245, 0.19)' }, |
| | | { offset: 0, color: 'rgba(144, 97, 248, 1)' }, |
| | | { offset: 1, color: 'rgba(142, 124, 230, 0.05)' }, |
| | | { offset: 0, color: BI.series[2] }, |
| | | ], |
| | | }, |
| | | }, |
| | |
| | | showSymbol: true, |
| | | symbol: 'circle', |
| | | symbolSize: 8, |
| | | lineStyle: { color: 'rgba(90, 216, 166, 1)', width: 2 }, |
| | | itemStyle: { color: 'rgba(90, 216, 166, 1)' }, |
| | | lineStyle: { color: BI.up, width: 2 }, |
| | | itemStyle: { color: BI.up }, |
| | | data: [], |
| | | emphasis: { focus: 'series' }, |
| | | }, |
| | | ]) |
| | | |
| | | const tooltip = { |
| | | const tooltip = withTooltip({ |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'cross' }, |
| | | formatter(params) { |
| | |
| | | }) |
| | | return result |
| | | }, |
| | | } |
| | | }) |
| | | |
| | | const xAxis1 = ref([ |
| | | { type: 'category', axisTick: { show: false }, axisLabel: { color: '#B8C8E0' }, data: [] }, |
| | | { type: 'category', axisTick: { show: false }, axisLine: { show: false }, axisLabel: { color: BI.text, fontSize: 12 }, data: [] }, |
| | | ]) |
| | | |
| | | const yAxis1 = [ |
| | | { type: 'value', name: 'ä»¶', axisLabel: { color: '#B8C8E0' }, nameTextStyle: { color: '#B8C8E0' } }, |
| | | { type: 'value', name: 'ä»¶', axisLabel: { color: BI.text, fontSize: 12 }, nameTextStyle: { color: BI.text }, splitLine: { lineStyle: { color: 'rgba(143, 163, 200, 0.15)' } } }, |
| | | { |
| | | type: 'value', |
| | | name: 'åæ ¼ç(%)', |
| | | min: 0, |
| | | max: 100, |
| | | axisLabel: { color: '#B8C8E0', formatter: '{value}%' }, |
| | | nameTextStyle: { color: '#B8C8E0' }, |
| | | splitLine: { lineStyle: { color: 'rgba(184, 200, 224, 0.2)' } }, |
| | | axisLabel: { color: BI.text, fontSize: 12, formatter: '{value}%' }, |
| | | nameTextStyle: { color: BI.text }, |
| | | splitLine: { show: false }, |
| | | }, |
| | | ] |
| | | |
| | |
| | | .warn-range { |
| | | position: absolute; |
| | | right: 0; |
| | | top: 0; |
| | | height: 32px; |
| | | padding: 0 14px; |
| | | top: 2px; |
| | | height: 28px; |
| | | padding: 0 12px; |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | border-radius: 4px; |
| | | color: #ffffff; |
| | | font-weight: 600; |
| | | font-size: 14px; |
| | | background: linear-gradient(180deg, rgba(51, 120, 255, 1) 0%, rgba(0, 164, 237, 1) 100%); |
| | | border: 1px solid rgba(78, 228, 255, 0.25); |
| | | border-radius: 6px; |
| | | color: var(--bi-text-strong); |
| | | font-weight: 500; |
| | | font-size: 13px; |
| | | background: rgba(74, 108, 240, 0.15); |
| | | border: 1px solid rgba(74, 108, 240, 0.35); |
| | | cursor: pointer; |
| | | transition: border-color 0.2s ease; |
| | | z-index: 10; |
| | | } |
| | | |
| | | .warn-range:hover { |
| | | background: linear-gradient(180deg, rgba(51, 140, 255, 1) 0%, rgba(0, 184, 237, 1) 100%); |
| | | border-color: var(--bi-accent-bright); |
| | | } |
| | | |
| | | .panel-root { |
| | | flex: 1; |
| | | height: 100%; |
| | | min-height: 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .main-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | flex: 1; |
| | | min-height: 0; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | border: 1px solid var(--bi-panel-border); |
| | | background: var(--bi-panel-bg); |
| | | border-radius: 8px; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 436px; |
| | | box-sizing: border-box; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div> |
| | | <div class="panel-root"> |
| | | <div class="warn-panel"> |
| | | <div class="warn-header"> |
| | | <div class="warn-header-left"> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, getCurrentInstance, ref, onMounted } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import { getCurrentInstance, ref, onMounted } from 'vue' |
| | | import { nonComplianceWarning } from '@/api/viewIndex.js' |
| | | |
| | | const { proxy } = getCurrentInstance() || {} |
| | | |
| | | const warnings = ref([]) |
| | | |
| | | // å æ¯æ°æ® |
| | | const ratios = ref({ |
| | | rawMaterialRatio: 0, |
| | | semiFinishedProductRatio: 0, |
| | | finishedProductRatio: 0, |
| | | }) |
| | | |
| | | const TAG_COLORS = { |
| | | raw: '#7C4DFF', |
| | | final: '#F5A000', |
| | | semi: '#FF66CC', |
| | | } |
| | | |
| | | const tagClass = (type) => { |
| | | if (type === 'raw') return 'tag-raw' |
| | |
| | | return 'raw' // é»è®¤å¼ |
| | | } |
| | | |
| | | const pieChartStyle = { width: '100%', height: '100%' } |
| | | |
| | | const pieOptions = { |
| | | backgroundColor: 'transparent', |
| | | textStyle: { color: '#B8C8E0' }, |
| | | } |
| | | |
| | | const pieTooltip = { |
| | | trigger: 'item', |
| | | formatter: (p) => `${p.name}ï¼${p.value}%`, |
| | | } |
| | | |
| | | const pieData = computed(() => { |
| | | return [ |
| | | { name: 'åææ', value: ratios.value.rawMaterialRatio, itemStyle: { color: TAG_COLORS.raw } }, |
| | | { name: 'åæå', value: ratios.value.semiFinishedProductRatio, itemStyle: { color: TAG_COLORS.semi } }, |
| | | { name: 'æå', value: ratios.value.finishedProductRatio, itemStyle: { color: TAG_COLORS.final } }, |
| | | ] |
| | | }) |
| | | |
| | | const pieSeries = computed(() => { |
| | | return [ |
| | | { |
| | | type: 'pie', |
| | | radius: ['0%', '68%'], |
| | | center: ['50%', '50%'], |
| | | startAngle: 90, |
| | | clockwise: true, |
| | | avoidLabelOverlap: true, |
| | | label: { show: false }, |
| | | labelLine: { show: false }, |
| | | itemStyle: { |
| | | borderColor: '#071a3a', |
| | | borderWidth: 4, |
| | | shadowBlur: 14, |
| | | shadowColor: 'rgba(0, 0, 0, 0.35)', |
| | | }, |
| | | data: pieData.value, |
| | | }, |
| | | { |
| | | // å
åæç¯ï¼å¢å¼ºå±æ¬¡ |
| | | type: 'pie', |
| | | radius: ['70%', '74%'], |
| | | center: ['50%', '50%'], |
| | | silent: true, |
| | | label: { show: false }, |
| | | labelLine: { show: false }, |
| | | itemStyle: { color: 'rgba(78, 228, 255, 0.12)' }, |
| | | data: [1], |
| | | }, |
| | | ] |
| | | }) |
| | | |
| | | const fetchWarnings = async () => { |
| | | try { |
| | | const res = await nonComplianceWarning() |
| | | if (res?.code === 200 && res?.data) { |
| | | const data = res.data |
| | | |
| | | // æ´æ°å æ¯æ°æ® |
| | | ratios.value = { |
| | | rawMaterialRatio: data.rawMaterialRatio ?? 0, |
| | | semiFinishedProductRatio: data.semiFinishedProductRatio ?? 0, |
| | | finishedProductRatio: data.finishedProductRatio ?? 0, |
| | | } |
| | | |
| | | // æ´æ°è¦åå表 |
| | | const children = data.children || [] |
| | |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .panel-root { |
| | | flex: 1; |
| | | min-height: 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .warn-panel { |
| | | border: 1px solid #1a58b0; |
| | | border: 1px solid var(--bi-panel-border); |
| | | background: var(--bi-panel-bg); |
| | | border-radius: 8px; |
| | | padding: 0 18px 18px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 12px; |
| | | height: 100%; |
| | | flex: 1; |
| | | min-height: 0; |
| | | box-sizing: border-box; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .warn-header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | border-bottom: 1px solid; |
| | | border-image: linear-gradient(270deg, |
| | | rgba(0, 126, 255, 0) 0%, |
| | | rgba(0, 126, 255, 0.4549) 35%, |
| | | #007eff 78%, |
| | | #007eff 100%) 1; |
| | | border-bottom: 1px solid rgba(143, 163, 200, 0.15); |
| | | padding: 10px 0 6px; |
| | | } |
| | | |
| | |
| | | gap: 10px; |
| | | } |
| | | |
| | | /* åçèç»ç«æ¡ï¼ä¸ PanelHeader å¼åº */ |
| | | .warn-badge { |
| | | width: 18px; |
| | | height: 18px; |
| | | background: linear-gradient(180deg, #2aa8ff 0%, #4ee4ff 100%); |
| | | clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); |
| | | box-shadow: 0 0 12px rgba(78, 228, 255, 0.25); |
| | | width: 3px; |
| | | height: 14px; |
| | | border-radius: 2px; |
| | | background: linear-gradient(180deg, var(--bi-accent-bright) 0%, var(--bi-accent) 100%); |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .warn-title { |
| | | font-weight: 600; |
| | | font-size: 18px; |
| | | background: linear-gradient(360deg, #056dff 0%, #43e8fc 100%); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | font-weight: 500; |
| | | font-size: 16px; |
| | | color: var(--bi-text-strong); |
| | | letter-spacing: 1px; |
| | | line-height: 24px; |
| | | } |
| | | |
| | | .warn-range { |
| | | height: 32px; |
| | | padding: 0 14px; |
| | | height: 28px; |
| | | padding: 0 12px; |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | border-radius: 4px; |
| | | color: #ffffff; |
| | | font-weight: 600; |
| | | background: linear-gradient(180deg, rgba(51, 120, 255, 1) 0%, rgba(0, 164, 237, 1) 100%); |
| | | border: 1px solid rgba(78, 228, 255, 0.25); |
| | | border-radius: 6px; |
| | | color: var(--bi-text-strong); |
| | | font-weight: 500; |
| | | font-size: 13px; |
| | | background: rgba(74, 108, 240, 0.15); |
| | | border: 1px solid rgba(74, 108, 240, 0.35); |
| | | cursor: pointer; |
| | | transition: border-color 0.2s ease; |
| | | } |
| | | |
| | | .warn-range:hover { |
| | | border-color: var(--bi-accent-bright); |
| | | } |
| | | |
| | | .warn-body { |
| | | display: grid; |
| | | gap: 18px; |
| | | align-items: stretch; |
| | | min-height: 260px; |
| | | flex: 1; |
| | | min-height: 0; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .warn-list { |
| | |
| | | grid-template-columns: 130px 1fr auto 110px; |
| | | align-items: center; |
| | | gap: 12px; |
| | | color: #b8c8e0; |
| | | color: var(--bi-text); |
| | | font-size: 14px; |
| | | line-height: 1.2; |
| | | padding: 8px 0; |
| | |
| | | } |
| | | |
| | | .warn-item:hover { |
| | | color: #ff4d4f; |
| | | background-color: rgba(255, 77, 79, 0.06); |
| | | color: var(--bi-down); |
| | | background-color: rgba(255, 107, 107, 0.06); |
| | | } |
| | | |
| | | .warn-item:hover .warn-text { |
| | | color: #ff4d4f; |
| | | color: var(--bi-down); |
| | | } |
| | | |
| | | .warn-tag { |
| | | height: 28px; |
| | | height: 24px; |
| | | padding: 0 10px; |
| | | border-radius: 4px; |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-weight: 700; |
| | | font-weight: 500; |
| | | font-size: 12px; |
| | | color: #ffffff; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | /* åºåè²åºåæ£æµç¯èï¼ä¸å¤§å±å¾è¡¨é
è²ä¸è´ */ |
| | | .tag-raw { |
| | | background: #7c4dff; |
| | | background: var(--bi-accent-bright); |
| | | } |
| | | |
| | | .tag-final { |
| | | background: #f5a000; |
| | | background: #8E7CE6; |
| | | } |
| | | |
| | | .tag-semi { |
| | | background: #ff66cc; |
| | | background: #35C6D6; |
| | | } |
| | | |
| | | .warn-text { |
| | | color: #e8f1ff; |
| | | color: var(--bi-text-strong); |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | } |
| | | |
| | | .warn-action { |
| | | color: #ff4d4f; |
| | | font-weight: 700; |
| | | color: var(--bi-down); |
| | | font-weight: 600; |
| | | white-space: nowrap; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .warn-date { |
| | | color: rgba(184, 200, 224, 0.75); |
| | | color: var(--bi-text); |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .warn-chart { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .chart-frame { |
| | | width: 100%; |
| | | height: 260px; |
| | | border: 2px dashed rgba(184, 200, 224, 0.35); |
| | | position: relative; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background: radial-gradient(circle at 50% 50%, rgba(78, 228, 255, 0.08) 0%, rgba(0, 0, 0, 0) 65%); |
| | | } |
| | | |
| | | /* å¤åå»åº¦ç¯ */ |
| | | .chart-frame::before { |
| | | content: ''; |
| | | position: absolute; |
| | | width: 220px; |
| | | height: 220px; |
| | | border-radius: 50%; |
| | | background: repeating-conic-gradient(from 0deg, rgba(78, 228, 255, 0.75) 0 1deg, rgba(78, 228, 255, 0) 1deg 9deg); |
| | | -webkit-mask: radial-gradient(circle, transparent 62%, #000 63%); |
| | | mask: radial-gradient(circle, transparent 62%, #000 63%); |
| | | opacity: 0.5; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | /* ååè¾
å©çº¿ */ |
| | | .chart-frame::after { |
| | | content: ''; |
| | | position: absolute; |
| | | width: 240px; |
| | | height: 240px; |
| | | background: |
| | | linear-gradient(to right, rgba(78, 228, 255, 0) 0%, rgba(78, 228, 255, 0.55) 50%, rgba(78, 228, 255, 0) 100%), |
| | | linear-gradient(to bottom, rgba(78, 228, 255, 0) 0%, rgba(78, 228, 255, 0.55) 50%, rgba(78, 228, 255, 0) 100%); |
| | | background-size: 100% 1px, 1px 100%; |
| | | background-position: center, center; |
| | | background-repeat: no-repeat; |
| | | opacity: 0.35; |
| | | pointer-events: none; |
| | | font-variant-numeric: tabular-nums; |
| | | } |
| | | </style> |
| | |
| | | <div> |
| | | <!-- é¡¶é¨ç»è®¡å¡ç --> |
| | | <div class="stats-cards"> |
| | | <div v-for="item in statItems" :key="item.name" class="stat-card"> |
| | | <img src="@/assets/BI/icon@2x.png" alt="徿 " class="card-icon" /> |
| | | <div v-for="(item, index) in statItems" :key="item.name" class="stat-card"> |
| | | <div class="card-icon" :style="{ color: ICON_COLORS[index % ICON_COLORS.length] }"> |
| | | <!-- æ£éªå --> |
| | | <svg v-if="index % 3 === 0" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> |
| | | <rect x="5" y="4" width="14" height="17" rx="2" /> |
| | | <path d="M9 2.5h6v3H9z" /> |
| | | <path d="M9 13l2 2 4-4" /> |
| | | </svg> |
| | | <!-- å¾
å®æï¼æ¶é --> |
| | | <svg v-else-if="index % 3 === 1" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> |
| | | <circle cx="12" cy="12" r="9" /> |
| | | <path d="M12 7v5l3.5 2" /> |
| | | </svg> |
| | | <!-- 已宿ï¼å¯¹å¾åç¯ --> |
| | | <svg v-else width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> |
| | | <circle cx="12" cy="12" r="9" /> |
| | | <path d="M8.5 12.5l2.5 2.5 5-5.5" /> |
| | | </svg> |
| | | </div> |
| | | <div class="card-content"> |
| | | <span class="card-label">{{ item.name }}</span> |
| | | <span class="card-value">{{ item.value }}</span> |
| | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { qualityInspectionCount } from '@/api/viewIndex.js' |
| | | import { BI } from '../theme.js' |
| | | |
| | | const statItems = ref([]) |
| | | |
| | | // å¡ç徿 ç¹ç¼è²ï¼æ£éª/å¾
宿/å·²å®æï¼ |
| | | const ICON_COLORS = [BI.accentBright, BI.series[3], BI.up] |
| | | |
| | | const formatPercent = (val) => { |
| | | const num = Number(val) || 0 |
| | |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | background-image: url('@/assets/BI/border@2x.png'); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | background: var(--bi-panel-bg); |
| | | border: 1px solid var(--bi-panel-border); |
| | | border-radius: 8px; |
| | | position: relative; |
| | | overflow: hidden; |
| | | height: 142px; |
| | | } |
| | | |
| | | /* é¡¶é¨åçèäº®çº¿ï¼æ¿ä»£å¾çæè¾¹ */ |
| | | .stat-card::before { |
| | | content: ''; |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | height: 2px; |
| | | background: linear-gradient(90deg, var(--bi-accent) 0%, transparent 70%); |
| | | } |
| | | |
| | | .card-icon { |
| | | width: 100px; |
| | | height: 100px; |
| | | margin: 20px 20px 0 10px; |
| | | width: 52px; |
| | | height: 52px; |
| | | margin: 0 16px 0 20px; |
| | | border-radius: 8px; |
| | | background: rgba(74, 108, 240, 0.12); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .card-content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | gap: 8px; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .card-value { |
| | | font-weight: 500; |
| | | font-size: 40px; |
| | | background: linear-gradient(360deg, #008bfd 0%, #ffffff 100%); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | font-weight: 600; |
| | | font-size: 36px; |
| | | color: var(--bi-text-strong); |
| | | font-variant-numeric: tabular-nums; |
| | | line-height: 1.1; |
| | | } |
| | | |
| | | .card-label { |
| | | font-weight: 400; |
| | | font-size: 16px; |
| | | color: rgba(208, 231, 255, 0.7); |
| | | color: var(--bi-text); |
| | | } |
| | | |
| | | .card-compare { |
| | |
| | | align-items: center; |
| | | gap: 6px; |
| | | font-size: 15px; |
| | | color: #d0e7ff; |
| | | color: var(--bi-text); |
| | | white-space: nowrap; |
| | | flex-wrap: nowrap; |
| | | } |
| | |
| | | |
| | | .compare-value { |
| | | font-weight: 600; |
| | | font-variant-numeric: tabular-nums; |
| | | } |
| | | |
| | | .compare-icon { |
| | |
| | | |
| | | .compare-up .compare-value, |
| | | .compare-up .compare-icon { |
| | | color: #00c853; |
| | | color: var(--bi-up); |
| | | } |
| | | |
| | | .compare-down .compare-value, |
| | | .compare-down .compare-icon { |
| | | color: #ff5252; |
| | | color: var(--bi-down); |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div> |
| | | <div class="panel-root"> |
| | | <PanelHeader title="è´¨éææ åæ ¼åæ" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <div v-for="section in sections" :key="section.key" class="inspect-block"> |
| | |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import DateTypeSwitch from './DateTypeSwitch.vue' |
| | | import { rawMaterialDetection, processDetection, factoryDetection } from '@/api/viewIndex.js' |
| | | import { BI, baseChartOptions } from '../theme.js' |
| | | |
| | | const QUALIFIED_COLOR = '#4EE4FF' |
| | | const UNQUALIFIED_COLOR = '#3378FF' |
| | | const TRACK_COLOR = 'rgba(78, 228, 255, 0.12)' |
| | | const QUALIFIED_COLOR = BI.accentBright |
| | | const UNQUALIFIED_COLOR = BI.series[2] |
| | | const TRACK_COLOR = 'rgba(74, 108, 240, 0.12)' |
| | | |
| | | const apiMap = { |
| | | raw: rawMaterialDetection, |
| | |
| | | { |
| | | key: 'raw', |
| | | title: 'åæææ£æµ', |
| | | dateType: 1, |
| | | dateType: 3, |
| | | qualifiedCount: 0, |
| | | unqualifiedCount: 0, |
| | | qualifiedRate: 0, |
| | |
| | | { |
| | | key: 'process', |
| | | title: 'è¿ç¨æ£æµ', |
| | | dateType: 1, |
| | | dateType: 3, |
| | | qualifiedCount: 0, |
| | | unqualifiedCount: 0, |
| | | qualifiedRate: 0, |
| | |
| | | { |
| | | key: 'final', |
| | | title: 'æååºåæ£æµ', |
| | | dateType: 1, |
| | | dateType: 3, |
| | | qualifiedCount: 0, |
| | | unqualifiedCount: 0, |
| | | qualifiedRate: 0, |
| | |
| | | height: '110px', |
| | | } |
| | | |
| | | const ringOptions = { |
| | | backgroundColor: 'transparent', |
| | | textStyle: { color: '#B8C8E0' }, |
| | | } |
| | | const ringOptions = baseChartOptions |
| | | |
| | | const ringTooltip = { |
| | | show: false, |
| | |
| | | const unqualified = Number(section.unqualifiedCount || 0) |
| | | const total = qualified + unqualified |
| | | |
| | | return [ |
| | | const series = [ |
| | | { |
| | | type: 'pie', |
| | | radius: ['68%', '82%'], |
| | |
| | | itemStyle: { color: TRACK_COLOR }, |
| | | data: [1], |
| | | }, |
| | | { |
| | | ] |
| | | |
| | | // æ°æ®ä¸ºç©ºæ¶ä¸æ¸²æä¸»ç¯ï¼é¿å
ECharts ç©ºé¥¼å¾æ¸²æåºç°è²åç¯ |
| | | if (total > 0) series.push({ |
| | | name: section.title, |
| | | type: 'pie', |
| | | radius: ['68%', '82%'], |
| | |
| | | clockwise: true, |
| | | minAngle: total > 0 ? 8 : 0, |
| | | itemStyle: { |
| | | borderColor: 'rgba(10, 28, 58, 0.95)', |
| | | borderColor: BI.sliceBorder, |
| | | borderWidth: 2, |
| | | borderRadius: 4, |
| | | }, |
| | | data: [ |
| | | { |
| | | value: qualified, |
| | | name: 'åæ ¼æ°', |
| | | itemStyle: { |
| | | color: QUALIFIED_COLOR, |
| | | shadowBlur: 16, |
| | | shadowColor: 'rgba(78, 228, 255, 0.45)', |
| | | }, |
| | | itemStyle: { color: QUALIFIED_COLOR }, |
| | | }, |
| | | { |
| | | value: unqualified, |
| | | name: 'ä¸åæ ¼æ°', |
| | | itemStyle: { |
| | | color: UNQUALIFIED_COLOR, |
| | | shadowBlur: 10, |
| | | shadowColor: 'rgba(51, 120, 255, 0.35)', |
| | | }, |
| | | itemStyle: { color: UNQUALIFIED_COLOR }, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | }) |
| | | |
| | | series.push({ |
| | | type: 'pie', |
| | | radius: ['52%', '56%'], |
| | | center: ['50%', '50%'], |
| | | silent: true, |
| | | label: { show: false }, |
| | | labelLine: { show: false }, |
| | | itemStyle: { color: 'rgba(0, 127, 255, 0.22)' }, |
| | | itemStyle: { color: 'rgba(74, 108, 240, 0.22)' }, |
| | | data: [1], |
| | | }, |
| | | ] |
| | | }) |
| | | |
| | | return series |
| | | } |
| | | |
| | | const handleDateTypeChange = (key, dateType) => { |
| | |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .panel-root { |
| | | flex: 1; |
| | | height: 100%; |
| | | min-height: 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .main-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: 100%; |
| | | flex: 1; |
| | | min-height: 0; |
| | | gap: 0; |
| | | } |
| | | |
| | |
| | | |
| | | .filters-row-left { |
| | | width: 50%; |
| | | color: white; |
| | | /* ç¨flexæ¿ä»£floatï¼è®©åå
ç´ å¯¹é½æ´ç¨³å® */ |
| | | color: var(--bi-text-strong); |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | |
| | | /* åçèç»ç«æ¡ï¼ä¸ PanelHeader å¼åº */ |
| | | span { |
| | | /* æ ¸å¿ï¼ç¶çº§ç¸å¯¹å®ä½ï¼ä½ä¸ºä¼ªå
ç´ åºå */ |
| | | position: relative; |
| | | display: inline-block; |
| | | /* ç»ä¼ªå
ç´ åæåçç©ºé´ */ |
| | | padding-left: 22px; |
| | | /* æååç´å±
ä¸ */ |
| | | line-height: 23px; |
| | | margin-right: 8px; |
| | | |
| | | &::after { |
| | | content: ''; |
| | | display: inline-block; |
| | | width: 16px; |
| | | height: 16px; |
| | | clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); |
| | | background: #217AFF; |
| | | position: absolute; |
| | | top: 50%; |
| | | left: 0; |
| | | transform: translateY(-50%); |
| | | /* ç¡®ä¿è±å½¢å¨æ¸åå䏿¹ */ |
| | | z-index: 1; |
| | | } |
| | | |
| | | &::before { |
| | | content: ''; |
| | | display: inline-block; |
| | | width: 18px; |
| | | height: 7px; |
| | | border-radius: 8px; |
| | | background: linear-gradient(360deg, rgba(33, 133, 255, 0.4) 0%, rgba(33, 221, 255, 0) 100%); |
| | | position: absolute; |
| | | top: 50%; |
| | | left: -1px; |
| | | /* ç²¾åè´´å¨è±å½¢æ£ä¸æ¹ */ |
| | | transform: translateY(calc(0% + 8px)); |
| | | z-index: 0; |
| | | } |
| | | flex-shrink: 0; |
| | | width: 3px; |
| | | height: 14px; |
| | | border-radius: 2px; |
| | | background: linear-gradient(180deg, var(--bi-accent-bright) 0%, var(--bi-accent) 100%); |
| | | } |
| | | |
| | | p { |
| | | width: 100px; |
| | | height: 23px; |
| | | /* æ¸åèµ·å§è²åè±å½¢ç»ä¸ï¼æ´åè° */ |
| | | background: linear-gradient(90deg, #217AFF 0%, rgba(33, 221, 255, 0) 100%); |
| | | /* ç²¾ååç´å±
ä¸ */ |
| | | line-height: 26px; |
| | | text-align: center; |
| | | color: white; |
| | | /* ç¨é«åº¦çä¸åååè§ï¼ç¡®ä¿å·¦è¾¹æ¯å®ç¾åå */ |
| | | border-radius: 12px 0 0 12px; |
| | | /* å¯éï¼å ä¸ç¹å·¦å
è¾¹è·ï¼è®©æåä¸è´´è¾¹ */ |
| | | padding-left: 4px; |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | letter-spacing: 1px; |
| | | color: var(--bi-text-strong); |
| | | } |
| | | } |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | border: 1px solid var(--bi-panel-border); |
| | | background: var(--bi-panel-bg); |
| | | border-radius: 8px; |
| | | padding: 14px 18px; |
| | | width: 100%; |
| | | height: 958px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | |
| | | right: 0; |
| | | bottom: 0; |
| | | height: 1px; |
| | | background: linear-gradient(90deg, rgba(33, 122, 255, 0) 0%, rgba(33, 122, 255, 0.55) 50%, rgba(33, 122, 255, 0) 100%); |
| | | background: linear-gradient(90deg, rgba(74, 108, 240, 0) 0%, rgba(74, 108, 240, 0.55) 50%, rgba(74, 108, 240, 0) 100%); |
| | | pointer-events: none; |
| | | } |
| | | |
| | |
| | | justify-content: center; |
| | | } |
| | | |
| | | /* å¤åå»åº¦ï¼ç¹ç¶ç¯ï¼ */ |
| | | .ring::before { |
| | | content: ''; |
| | | position: absolute; |
| | | inset: -8px; |
| | | border-radius: 50%; |
| | | background: repeating-conic-gradient(from 0deg, |
| | | rgba(78, 228, 255, 0.75) 0 1deg, |
| | | rgba(78, 228, 255, 0) 1deg 9deg); |
| | | -webkit-mask: radial-gradient(circle, transparent 62%, #000 63%); |
| | | mask: radial-gradient(circle, transparent 62%, #000 63%); |
| | | opacity: 0.35; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | /* æååå
èæ¯ */ |
| | | .ring::after { |
| | | content: ''; |
| | | position: absolute; |
| | | inset: -20px; |
| | | border-radius: 50%; |
| | | background: radial-gradient(circle, rgba(78, 228, 255, 0.18) 0%, rgba(78, 228, 255, 0.06) 40%, rgba(0, 0, 0, 0) 70%); |
| | | filter: blur(0.2px); |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .stats { |
| | | width: 240px; |
| | | flex: 0 0 240px; |
| | |
| | | align-items: center; |
| | | height: 100%; |
| | | padding: 10px 14px; |
| | | border-radius: 4px; |
| | | border: 1px solid rgba(78, 228, 255, 0.22); |
| | | background: linear-gradient(90deg, rgba(33, 122, 255, 0.28) 0%, rgba(10, 28, 58, 0.35) 55%, rgba(10, 28, 58, 0.2) 100%); |
| | | box-shadow: inset 0 0 18px rgba(16, 45, 95, 0.25); |
| | | border-radius: 6px; |
| | | border: 1px solid var(--bi-panel-border); |
| | | background: rgba(13, 24, 52, 0.6); |
| | | } |
| | | |
| | | .stat-left { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | color: #b8c8e0; |
| | | color: var(--bi-text); |
| | | font-size: 12px; |
| | | } |
| | | |
| | |
| | | height: 10px; |
| | | border-radius: 2px; |
| | | display: inline-block; |
| | | box-shadow: 0 0 10px rgba(78, 228, 255, 0.25); |
| | | } |
| | | |
| | | .dot-qualified { |
| | | background: rgba(184, 200, 224, 0.85); |
| | | background: var(--bi-accent-bright); |
| | | } |
| | | |
| | | .dot-unqualified { |
| | | background: #4ee4ff; |
| | | background: #8E7CE6; |
| | | } |
| | | |
| | | .stat-right { |
| | |
| | | } |
| | | |
| | | .stat-value { |
| | | color: #ffffff; |
| | | color: var(--bi-text-strong); |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | min-width: 40px; |
| | | text-align: right; |
| | | text-shadow: 0 0 10px rgba(78, 228, 255, 0.15); |
| | | font-variant-numeric: tabular-nums; |
| | | } |
| | | |
| | | .stat-percent { |
| | | color: rgba(184, 200, 224, 0.95); |
| | | color: var(--bi-text); |
| | | font-size: 12px; |
| | | min-width: 40px; |
| | | text-align: right; |
| | | font-variant-numeric: tabular-nums; |
| | | } |
| | | |
| | | /* è®©åæ¢æé®æ´è´´è¿æªå¾ï¼æ´ç´§åï¼ */ |
| | |
| | | <template> |
| | | <div> |
| | | <div class="panel-root"> |
| | | <PanelHeader title="ä¸åæ ¼æ£åå¤çåæ" /> |
| | | <div class="panel-item-customers"> |
| | | <div class="pie-chart-wrapper" ref="pieWrapperRef"> |
| | | <div class="pie-background" ref="pieBackgroundRef"></div> |
| | | <div class="pie-chart-wrapper"> |
| | | <Echarts ref="chart" :chartStyle="chartStyle" :legend="landLegend" :series="computedSeries" |
| | | :tooltip="landTooltip" :color="landColors" :options="pieOptions" style="height: 100%" class="land-chart" /> |
| | | </div> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted, onBeforeUnmount } from 'vue' |
| | | import { ref, computed, onMounted } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import { unqualifiedProductProcessingAnalysis } from '@/api/viewIndex.js' |
| | | import { useChartBackground } from '@/hooks/useChartBackground.js' |
| | | import { BI, baseChartOptions, withTooltip } from '../theme.js' |
| | | |
| | | const pieWrapperRef = ref(null) |
| | | const pieBackgroundRef = ref(null) |
| | | const chart = ref(null) |
| | | |
| | | // æ°æ®å表 |
| | | const dataList = ref([]) |
| | | |
| | | // é¢è²å表 |
| | | const landColors = ['#26FFCB', '#24CBFF', '#35FBF4', '#2651FF', '#D1E4F5', '#5782F7', '#2F67EF', '#82BAFF'] |
| | | // é¢è²å表ï¼å¤§å±ç»ä¸åºåè²ï¼ |
| | | const landColors = BI.series |
| | | |
| | | // label å¯ææ¬æ ·å¼ |
| | | const dotRich = landColors.reduce((acc, color, idx) => { |
| | |
| | | top: '40%', |
| | | orient: 'vertical', |
| | | textStyle: { |
| | | color: '#fff', |
| | | color: BI.textStrong, |
| | | rich: { |
| | | unit: { color: '#fff', fontSize: 12, padding: [0, 10, 0, 0] }, |
| | | text: { width: 60, color: '#fff', fontSize: 12 }, |
| | | unit: { color: BI.textStrong, fontSize: 12, padding: [0, 10, 0, 0] }, |
| | | text: { width: 60, color: BI.textStrong, fontSize: 12 }, |
| | | } |
| | | } |
| | | }) |
| | | |
| | | // æç¤ºæ¡é
ç½® |
| | | const landTooltip = { |
| | | const landTooltip = withTooltip({ |
| | | trigger: 'item', |
| | | alwaysShowContent: false, |
| | | position: function (pt) { |
| | |
| | | const { name, value, rate } = params.data |
| | | return `${name}<br/>æ°éï¼${value}个<br/>å æ¯ï¼${rate}%` |
| | | }, |
| | | } |
| | | }) |
| | | |
| | | // 使ç¨è®¡ç®å±æ§å¤ç Series |
| | | const computedSeries = computed(() => { |
| | | const isEmpty = dataList.value.length === 0 |
| | | return [ |
| | | { |
| | | name: 'ä¸åæ ¼æ£åå¤çåæ', |
| | | type: 'pie', |
| | | radius: ['35%', '55%'], |
| | | center: ['50%', '50%'], |
| | | silent: isEmpty, |
| | | itemStyle: isEmpty |
| | | ? { color: 'rgba(74, 108, 240, 0.12)', borderWidth: 0 } |
| | | : { |
| | | borderColor: BI.sliceBorder, |
| | | borderWidth: 2, |
| | | borderRadius: 4, |
| | | }, |
| | | label: { |
| | | show: true, |
| | | show: !isEmpty, |
| | | position: 'outside', |
| | | color: '#fff', |
| | | color: BI.textStrong, |
| | | rich: { |
| | | ...dotRich, |
| | | parent: { fontSize: 14, fontWeight: 600, color: '#fff', lineHeight: 20 }, |
| | | child: { fontSize: 12, color: '#fff', lineHeight: 18 }, |
| | | parent: { fontSize: 14, fontWeight: 600, color: BI.textStrong, lineHeight: 20 }, |
| | | child: { fontSize: 12, color: BI.text, lineHeight: 18 }, |
| | | }, |
| | | formatter: function (params) { |
| | | if (!params.data) return '' |
| | |
| | | }, |
| | | }, |
| | | labelLine: { |
| | | show: true, |
| | | show: !isEmpty, |
| | | length: 20, |
| | | lineStyle: { color: '#B8C8E0' }, |
| | | lineStyle: { color: 'rgba(143, 163, 200, 0.4)' }, |
| | | }, |
| | | data: dataList.value, |
| | | data: isEmpty ? [1] : dataList.value, |
| | | }, |
| | | { |
| | | // å
åè£
饰 |
| | |
| | | center: ['50%', '50%'], |
| | | silent: true, |
| | | label: { show: false }, |
| | | itemStyle: { color: 'rgba(0, 127, 255, 0.25)' }, |
| | | itemStyle: { color: 'rgba(74, 108, 240, 0.22)' }, |
| | | data: [1], |
| | | }, |
| | | ] |
| | | }) |
| | | |
| | | const chartStyle = { width: '100%', height: '126%' } |
| | | const pieOptions = { backgroundColor: 'transparent' } |
| | | |
| | | // èæ¯å¤çé©å |
| | | const { adjustBackgroundPosition, init: initBackground, cleanup: cleanupBackground } = useChartBackground({ |
| | | wrapperRef: pieWrapperRef, |
| | | backgroundRef: pieBackgroundRef, |
| | | offsetX: '-51.5%', |
| | | offsetY: '-39%', |
| | | watchData: dataList |
| | | }) |
| | | const chartStyle = { width: '100%', height: '100%' } |
| | | const pieOptions = baseChartOptions |
| | | |
| | | const loadData = async () => { |
| | | try { |
| | |
| | | dataList.value = (res.data || []).map((it) => ({ |
| | | name: it.name, |
| | | value: Number(it.value || 0), |
| | | rate: it.rate, |
| | | rate: it.rate, |
| | | })) |
| | | landLegend.value.data = dataList.value.map((d) => d.name) |
| | | |
| | | // æ°æ®æ´æ°åå¾®è°èæ¯ |
| | | setTimeout(() => { |
| | | adjustBackgroundPosition() |
| | | }, 100) |
| | | } |
| | | } catch (e) { |
| | | console.error('è·åæ°æ®å¤±è´¥:', e) |
| | |
| | | |
| | | onMounted(() => { |
| | | loadData() |
| | | initBackground() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | cleanupBackground() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .panel-root { |
| | | flex: 1; |
| | | height: 100%; |
| | | min-height: 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | border: 1px solid var(--bi-panel-border); |
| | | background: var(--bi-panel-bg); |
| | | border-radius: 8px; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | | box-sizing: border-box; |
| | | flex: 1; |
| | | min-height: 0; |
| | | } |
| | | |
| | | .pie-chart-wrapper { |
| | | position: relative; |
| | | width: 100%; |
| | | height: 320px; |
| | | height: 100%; |
| | | min-height: 0; |
| | | } |
| | | |
| | | .pie-background { |
| | | position: absolute; |
| | | width: 360px; |
| | | height: 360px; |
| | | background-image: url('@/assets/BI/ç«ç°å¾è¾¹æ¡.png'); |
| | | background-size: contain; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | z-index: 1; |
| | | pointer-events: none; |
| | | left: 50%; |
| | | top: 50%; |
| | | transform: translate(-51.5%, -39%); |
| | | } |
| | | </style> |
| | | </style> |
| | |
| | | <template> |
| | | <div> |
| | | <div class="panel-root"> |
| | | <PanelHeader title="ä¸åæ ¼äº§åæå" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <div class="main-panel-container"> |
| | | <div style="color: white" class="main-panel-box" v-for="(item, index) in panelList" :key="index"> |
| | | <div class="main-panel-box" v-for="(item, index) in panelList" :key="index"> |
| | | <!-- <div style="flex: 1" class="main-panel-box-left">{{ item.rank }}</div> --> |
| | | <div style="flex: 1" class="main-panel-box-left">{{ item.productName }}</div> |
| | | <div style="flex: 3" class="main-panel-box-right"> |
| | |
| | | flex-direction: row; |
| | | align-items: center; |
| | | height: 40px; |
| | | color: var(--bi-text); |
| | | |
| | | .main-panel-box-left { |
| | | background: red; |
| | | border-radius: 20px; |
| | | background: rgba(74, 108, 240, 0.15); |
| | | border: 1px solid rgba(74, 108, 240, 0.35); |
| | | border-radius: 6px; |
| | | color: var(--bi-text-strong); |
| | | text-align: center; |
| | | line-height: 32px; |
| | | margin: 0 20px; |
| | | padding: 0 12px; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .main-panel-box-right { |
| | |
| | | |
| | | .main-panel-box-right-title { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #ffffff; |
| | | font-weight: 500; |
| | | color: var(--bi-text-strong); |
| | | margin-bottom: 6px; |
| | | } |
| | | |
| | |
| | | justify-content: space-between; |
| | | padding-right: 60px; |
| | | margin-bottom: 4px; |
| | | font-variant-numeric: tabular-nums; |
| | | } |
| | | |
| | | .main-panel-box-right-progress { |
| | | :deep(.el-progress__text) { |
| | | color: white !important; |
| | | color: var(--bi-text) !important; |
| | | } |
| | | |
| | | :deep(.el-progress-bar__outer) { |
| | | background-color: rgba(74, 108, 240, 0.12); |
| | | } |
| | | |
| | | :deep(.el-progress-bar__inner) { |
| | | background: var(--bi-accent-bright); |
| | | } |
| | | } |
| | | } |
| | |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .panel-root { |
| | | flex: 1; |
| | | height: 100%; |
| | | min-height: 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .main-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | flex: 1; |
| | | min-height: 0; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | border: 1px solid var(--bi-panel-border); |
| | | background: var(--bi-panel-bg); |
| | | border-radius: 8px; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | | box-sizing: border-box; |
| | | overflow: hidden; |
| | | } |
| | | </style> |
| | |
| | | |
| | | <!-- 顶鍿 颿 --> |
| | | <div class="dashboard-header"> |
| | | <div class="header-line"></div> |
| | | <div class="factory-name">è´¨éæ°æ®åæ</div> |
| | | <div class="header-line header-line-right"></div> |
| | | </div> |
| | | |
| | | <!-- 主è¦å
容åºå --> |
| | |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| | | import autofit from 'autofit.js' |
| | | import CenterCenter from './components/center-center.vue' |
| | | import RightTop from './components/right-top.vue' |
| | | import RightBottom from './components/right-bottom.vue' |
| | |
| | | const container = document.querySelector('.scale-container') |
| | | if (!container) return |
| | | |
| | | // è·å容å¨çå®é
尺寸 |
| | | const rect = container.getBoundingClientRect?.() |
| | | const containerWidth = container.clientWidth || rect?.width || window.innerWidth |
| | | const containerHeight = container.clientHeight || rect?.height || window.innerHeight |
| | | const containerWidth = container.clientWidth || window.innerWidth |
| | | const containerHeight = container.clientHeight || window.innerHeight |
| | | |
| | | // 计ç®å®½é«ç¼©æ¾æ¯ä¾ï¼åè¾å°å¼ä»¥ä¿è¯å
容宿´æ¾ç¤ºï¼çæ¯ç¼©æ¾ï¼ |
| | | const scaleX = containerWidth / designWidth |
| | | const scaleY = containerHeight / designHeight |
| | | scaleRatio.value = Math.min(scaleX, scaleY) |
| | | // çæ¯ç¼©æ¾ï¼åè¾å°å¼ä¿è¯ 1920Ã1080 å
容宿´æ¾ç¤º |
| | | scaleRatio.value = Math.min(containerWidth / designWidth, containerHeight / designHeight) |
| | | } |
| | | |
| | | // çªå£å¤§å°ååå¤ç |
| | | // çªå£å¤§å°ååå¤çï¼rAF èæµï¼é¿å
é«é¢éæï¼ |
| | | let resizeRafId = null |
| | | const handleResize = () => { |
| | | // å»¶è¿æ§è¡ï¼ç¡®ä¿DOMæ´æ°å®æ |
| | | setTimeout(() => { |
| | | if (resizeRafId) cancelAnimationFrame(resizeRafId) |
| | | resizeRafId = requestAnimationFrame(() => { |
| | | resizeRafId = null |
| | | calculateScale() |
| | | }, 100) |
| | | }) |
| | | } |
| | | |
| | | // å
¨å±åè½å®ç° - é对scale-containerå
ç´ |
| | |
| | | window.removeEventListener('fullscreenchange', handleFullscreenChange) |
| | | window.removeEventListener('webkitfullscreenchange', handleFullscreenChange) |
| | | window.removeEventListener('MSFullscreenChange', handleFullscreenChange) |
| | | // ç§»é¤æä»¬æ·»å çautofitå¨æè°æ´çå¬å¨ |
| | | if (window._autofitUpdateHandler) { |
| | | window.removeEventListener('resize', window._autofitUpdateHandler) |
| | | delete window._autofitUpdateHandler |
| | | } |
| | | // å
³éautofit |
| | | autofit.off() |
| | | if (resizeRafId) cancelAnimationFrame(resizeRafId) |
| | | }) |
| | | </script> |
| | | |
| | |
| | | .scale-container { |
| | | position: relative; |
| | | width: 100%; |
| | | /* 页é¢å¨å¸¸è§å¸å±ä¸ï¼æé¡¶æ ï¼é»è®¤åå» 84pxï¼é¿å
å
容被è£å */ |
| | | height: calc(100vh - 84px); |
| | | /* 常è§å¸å±ä¸åå»é¡¶æ 60 + é¡µç¾ 40 = 100pxï¼é¿å
å
容被è£å */ |
| | | height: calc(100vh - 100px); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | |
| | | position: relative; |
| | | width: 1920px; |
| | | height: 1080px; |
| | | background-image: url("@/assets/BI/backImage@2x.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | transform-origin: center center; |
| | | |
| | | /* 大å±ä¸»é¢ä»¤çï¼åç»ä»¶éè¿ CSS åéç»§æ¿ */ |
| | | --bi-panel-bg: rgba(20, 32, 66, 0.55); |
| | | --bi-panel-border: #1e3160; |
| | | --bi-text: #8fa3c8; |
| | | --bi-text-strong: #eaf1ff; |
| | | --bi-accent: #2c51d9; |
| | | --bi-accent-bright: #4a6cf0; |
| | | --bi-up: #34c77b; |
| | | --bi-down: #ff6b6b; |
| | | |
| | | background: |
| | | radial-gradient(1200px 600px at 50% -10%, rgba(44, 81, 217, 0.18) 0%, transparent 60%), |
| | | linear-gradient(180deg, #0a1229 0%, #0d1834 55%, #0a1430 100%); |
| | | } |
| | | |
| | | /* ææ·¡ç½æ ¼çº¹çï¼æ¿ä»£æ¨¡æ¿æåºå¾ */ |
| | | .data-dashboard::before { |
| | | content: ''; |
| | | position: absolute; |
| | | inset: 0; |
| | | pointer-events: none; |
| | | background-image: |
| | | linear-gradient(rgba(74, 108, 240, 0.045) 1px, transparent 1px), |
| | | linear-gradient(90deg, rgba(74, 108, 240, 0.045) 1px, transparent 1px); |
| | | background-size: 48px 48px; |
| | | mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0.9) 0%, rgba(0, 0, 0, 0.35) 100%); |
| | | } |
| | | |
| | | /* å
¨å±ç¶æçæ ·å¼ - ä½ç¨äºscale-container */ |
| | |
| | | position: relative; |
| | | z-index: 1; |
| | | height: 86px; |
| | | background-image: url("@/assets/BI/biaoti.png"); |
| | | background-size: cover; |
| | | background-repeat: no-repeat; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 28px; |
| | | } |
| | | |
| | | /* æ é¢ä¸¤ä¾§æ¸éåé线 */ |
| | | .header-line { |
| | | width: 420px; |
| | | height: 1px; |
| | | background: linear-gradient(90deg, transparent 0%, rgba(74, 108, 240, 0.5) 100%); |
| | | } |
| | | |
| | | .header-line-right { |
| | | background: linear-gradient(270deg, transparent 0%, rgba(74, 108, 240, 0.5) 100%); |
| | | } |
| | | |
| | | .factory-name { |
| | | font-weight: 600; |
| | | font-size: 52px; |
| | | color: #FFFFFF; |
| | | top: 16px; |
| | | font-size: 30px; |
| | | letter-spacing: 4px; |
| | | color: var(--bi-text-strong); |
| | | position: relative; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .factory-name::after { |
| | | content: ''; |
| | | position: absolute; |
| | | left: 50%; |
| | | bottom: -8px; |
| | | transform: translateX(-50%); |
| | | width: 48px; |
| | | height: 2px; |
| | | background: var(--bi-accent-bright); |
| | | border-radius: 1px; |
| | | } |
| | | |
| | | .fullscreen-btn { |
| | |
| | | left: 20px; |
| | | width: 40px; |
| | | height: 40px; |
| | | background: rgba(0, 20, 60, 0.8); |
| | | border: 1px solid rgba(0, 212, 255, 0.3); |
| | | background: var(--bi-panel-bg); |
| | | border: 1px solid var(--bi-panel-border); |
| | | border-radius: 6px; |
| | | color: #00d4ff; |
| | | color: var(--bi-text); |
| | | cursor: pointer; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | transition: all 0.3s; |
| | | transition: color 0.2s ease, border-color 0.2s ease; |
| | | z-index: 10000; |
| | | } |
| | | |
| | | .fullscreen-btn:hover { |
| | | background: rgba(0, 30, 90, 0.9); |
| | | border-color: rgba(0, 212, 255, 0.5); |
| | | color: var(--bi-text-strong); |
| | | border-color: var(--bi-accent-bright); |
| | | } |
| | | |
| | | .dashboard-content { |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // è´¨éæ°æ®åæå¤§å±ç»ä¸è§è§ä»¤çï¼ä¸åºç¡æ°æ®/PSI 大å±åä¸å¥åçèå
å¶æ·±è²ä¸»é¢ï¼ |
| | | export const BI = { |
| | | // å¾è¡¨åºåè²ï¼å
å¶ä¸ä¸ï¼æ¿ä»£éèè§å
ï¼ |
| | | series: ['#4A6CF0', '#35C6D6', '#8E7CE6', '#F0B14A', '#2FB8A8', '#E86D6D'], |
| | | text: '#8FA3C8', |
| | | textStrong: '#EAF1FF', |
| | | panelBg: 'rgba(20, 32, 66, 0.55)', |
| | | panelBorder: '#1E3160', |
| | | sliceBorder: '#0D1834', |
| | | accent: '#2C51D9', |
| | | accentBright: '#4A6CF0', |
| | | up: '#34C77B', |
| | | down: '#FF6B6B', |
| | | } |
| | | |
| | | export const baseChartOptions = { |
| | | backgroundColor: 'transparent', |
| | | textStyle: { color: BI.text }, |
| | | } |
| | | |
| | | // æ·±è²ç»ä¸ tooltip æ ·å¼ï¼ä¿çåç»ä»¶èªå®ä¹ formatter |
| | | export const withTooltip = (tooltip = {}) => ({ |
| | | backgroundColor: 'rgba(13, 24, 52, 0.92)', |
| | | borderColor: BI.panelBorder, |
| | | borderWidth: 1, |
| | | textStyle: { color: BI.textStrong, fontSize: 12 }, |
| | | ...tooltip, |
| | | }) |
| | |
| | | .indicator-stats { |
| | | padding: 20px; |
| | | background: #f5f7fa; |
| | | min-height: calc(100vh - 84px); |
| | | min-height: calc(100vh - 100px); |
| | | } |
| | | |
| | | .page-header { |