完善使用手册 修复一些bug
343
UI/src/App.vue
@ -1,63 +1,316 @@
|
||||
<template>
|
||||
<el-container class="layout-container">
|
||||
<el-aside width="200px">
|
||||
<el-scrollbar>
|
||||
<el-menu :default-openeds="['1']" router>
|
||||
<el-sub-menu index="1">
|
||||
<template #title>
|
||||
<el-icon><DataLine /></el-icon>功能模块
|
||||
</template>
|
||||
<el-menu-item index="/">
|
||||
<el-icon><House /></el-icon>首页
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/data">
|
||||
<el-icon><FolderOpened /></el-icon>数据管理
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/training">
|
||||
<el-icon><Cpu /></el-icon>模型训练
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/prediction">
|
||||
<el-icon><MagicStick /></el-icon>模型预测
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/management">
|
||||
<el-icon><Files /></el-icon>模型管理
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
</el-aside>
|
||||
|
||||
<el-container>
|
||||
<el-header style="text-align: right; font-size: 12px">
|
||||
<div class="toolbar">
|
||||
<span>一树药品销售量预测系统</span>
|
||||
<div class="app-container">
|
||||
<div class="background-effects">
|
||||
<div class="glow-circle circle-1"></div>
|
||||
<div class="glow-circle circle-2"></div>
|
||||
<div class="glow-circle circle-3"></div>
|
||||
</div>
|
||||
|
||||
<el-container class="layout-container">
|
||||
<el-aside width="220px" class="sidebar">
|
||||
<div class="logo-container">
|
||||
<div class="logo-icon">
|
||||
<el-icon size="24"><DataAnalysis /></el-icon>
|
||||
</div>
|
||||
<h2 class="logo-text">药品销售预测</h2>
|
||||
</div>
|
||||
</el-header>
|
||||
|
||||
<el-scrollbar>
|
||||
<el-menu
|
||||
:default-openeds="['1']"
|
||||
router
|
||||
class="futuristic-menu"
|
||||
background-color="transparent"
|
||||
text-color="#e0e6ff"
|
||||
active-text-color="#5d9cff">
|
||||
<el-sub-menu index="1">
|
||||
<template #title>
|
||||
<el-icon><DataLine /></el-icon>
|
||||
<span>功能导航</span>
|
||||
</template>
|
||||
<el-menu-item index="/">
|
||||
<el-icon><House /></el-icon>首页概览
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/data">
|
||||
<el-icon><FolderOpened /></el-icon>数据管理
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/training">
|
||||
<el-icon><Cpu /></el-icon>模型训练
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/prediction">
|
||||
<el-icon><MagicStick /></el-icon>预测分析
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/management">
|
||||
<el-icon><Files /></el-icon>模型管理
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<el-tooltip content="系统版本: v1.0.0">
|
||||
<div class="version-badge">v1.0.0</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-aside>
|
||||
|
||||
<el-main>
|
||||
<router-view></router-view>
|
||||
</el-main>
|
||||
<el-container>
|
||||
<el-header class="header">
|
||||
<div class="header-title">
|
||||
<span class="glowing-text">智能药品销售预测系统</span>
|
||||
</div>
|
||||
<div class="header-controls">
|
||||
<el-tooltip content="数据刷新">
|
||||
<el-button circle size="small">
|
||||
<el-icon><Refresh /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-avatar size="small" src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png" />
|
||||
</div>
|
||||
</el-header>
|
||||
|
||||
<el-main class="main-content">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="fade" mode="out-in">
|
||||
<component :is="Component" />
|
||||
</transition>
|
||||
</router-view>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { DataAnalysis, Refresh } from '@element-plus/icons-vue'
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 全局样式 */
|
||||
:root {
|
||||
--primary-dark: #0c1e35;
|
||||
--primary-light: #1c3a64;
|
||||
--accent-color: #5d9cff;
|
||||
--accent-glow: #43a5f5;
|
||||
--accent-purple: #ad7bee;
|
||||
--text-light: #e0e6ff;
|
||||
--card-bg: rgba(18, 36, 65, 0.6);
|
||||
--card-border: rgba(93, 156, 255, 0.2);
|
||||
--neon-glow: 0 0 10px rgba(93, 156, 255, 0.5), 0 0 20px rgba(93, 156, 255, 0.3);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Roboto', 'Helvetica Neue', sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: var(--primary-dark);
|
||||
color: var(--text-light);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 页面过渡动画 */
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
background-color: transparent !important;
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.app-container {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.background-effects {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.glow-circle {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
filter: blur(60px);
|
||||
}
|
||||
|
||||
.circle-1 {
|
||||
top: -100px;
|
||||
right: -100px;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
background: radial-gradient(circle, rgba(93, 156, 255, 0.3) 0%, rgba(18, 36, 65, 0) 70%);
|
||||
animation: pulse 15s infinite alternate;
|
||||
}
|
||||
|
||||
.circle-2 {
|
||||
bottom: -150px;
|
||||
left: -100px;
|
||||
width: 600px;
|
||||
height: 600px;
|
||||
background: radial-gradient(circle, rgba(173, 123, 238, 0.2) 0%, rgba(18, 36, 65, 0) 70%);
|
||||
animation: pulse 20s infinite alternate-reverse;
|
||||
}
|
||||
|
||||
.circle-3 {
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
background: radial-gradient(circle, rgba(67, 165, 245, 0.15) 0%, rgba(18, 36, 65, 0) 70%);
|
||||
animation: pulse 12s infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
opacity: 0.5;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1.5);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-container {
|
||||
height: 100vh;
|
||||
background-color: transparent;
|
||||
}
|
||||
.el-header {
|
||||
position: relative;
|
||||
background-color: var(--el-color-primary-light-7);
|
||||
color: var(--el-text-color-primary);
|
||||
|
||||
.sidebar {
|
||||
background: linear-gradient(180deg, var(--primary-dark) 0%, var(--primary-light) 100%);
|
||||
border-right: 1px solid var(--card-border);
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.toolbar {
|
||||
display: inline-flex;
|
||||
|
||||
.logo-container {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
border-bottom: 1px solid var(--card-border);
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
right: 20px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(135deg, var(--accent-color), var(--accent-purple));
|
||||
box-shadow: var(--neon-glow);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: var(--text-light);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.futuristic-menu {
|
||||
border-right: none !important;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.futuristic-menu :deep(.el-menu-item) {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
margin: 5px 15px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.futuristic-menu :deep(.el-menu-item.is-active) {
|
||||
background: linear-gradient(90deg, rgba(93, 156, 255, 0.2) 0%, rgba(93, 156, 255, 0) 100%);
|
||||
box-shadow: -2px 0 0 var(--accent-color);
|
||||
}
|
||||
|
||||
.futuristic-menu :deep(.el-menu-item:hover) {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
margin-top: auto;
|
||||
padding: 15px;
|
||||
border-top: 1px solid var(--card-border);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.version-badge {
|
||||
display: inline-block;
|
||||
padding: 2px 10px;
|
||||
border-radius: 12px;
|
||||
background-color: rgba(93, 156, 255, 0.1);
|
||||
border: 1px solid var(--accent-color);
|
||||
color: var(--accent-color);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: rgba(18, 36, 65, 0.4);
|
||||
backdrop-filter: blur(10px);
|
||||
border-bottom: 1px solid var(--card-border);
|
||||
padding: 0 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.glowing-text {
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
color: var(--text-light);
|
||||
text-shadow: 0 0 5px rgba(93, 156, 255, 0.5);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
/* 自定义滚动条 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(93, 156, 255, 0.3);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(93, 156, 255, 0.5);
|
||||
}
|
||||
</style>
|
264
UI/src/assets/element-theme.css
Normal file
@ -0,0 +1,264 @@
|
||||
/* Element Plus Dark Theme Overrides */
|
||||
|
||||
/* 根级别全局样式覆盖 - 最高优先级 */
|
||||
:root {
|
||||
--el-color-white: #0c1e35 !important;
|
||||
--el-bg-color: #0c1e35 !important;
|
||||
--el-bg-color-overlay: #0c1e35 !important;
|
||||
--el-text-color-primary: #e0e6ff !important;
|
||||
--el-text-color-regular: #e0e6ff !important;
|
||||
--el-border-color: rgba(93, 156, 255, 0.2) !important;
|
||||
--el-border-color-light: rgba(93, 156, 255, 0.1) !important;
|
||||
--el-fill-color: #0c1e35 !important;
|
||||
--el-fill-color-light: rgba(93, 156, 255, 0.1) !important;
|
||||
--el-fill-color-blank: #0c1e35 !important;
|
||||
}
|
||||
|
||||
/* 全局盒子模型重置 */
|
||||
* {
|
||||
transition: background-color 0.3s, border-color 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
/* --- 通用元素全局覆盖 --- */
|
||||
|
||||
/* 直接覆盖所有可能的白色背景组件 */
|
||||
[class*="el-"],
|
||||
.el-select-dropdown,
|
||||
.el-dropdown-menu,
|
||||
.el-cascader__dropdown,
|
||||
.el-date-picker,
|
||||
.el-date-table,
|
||||
.el-picker-panel,
|
||||
.el-popper,
|
||||
.el-dialog,
|
||||
.el-message-box,
|
||||
.el-popover,
|
||||
.el-menu,
|
||||
.el-dropdown,
|
||||
.el-table__header,
|
||||
.el-table__body,
|
||||
.el-time-panel,
|
||||
body > .el-popper,
|
||||
body > .el-select-dropdown,
|
||||
body > .el-picker__popper,
|
||||
body > .el-tooltip__popper {
|
||||
color: #e0e6ff !important;
|
||||
}
|
||||
|
||||
/* --- 弹出式组件和对话框 --- */
|
||||
|
||||
/* 白色背景最强制覆盖 */
|
||||
.el-popper,
|
||||
.el-select-dropdown,
|
||||
.el-cascader__dropdown,
|
||||
.el-date-picker,
|
||||
.el-picker-panel,
|
||||
.el-time-panel,
|
||||
.el-dropdown-menu,
|
||||
.el-tooltip__popper,
|
||||
body > .el-popper,
|
||||
[role="tooltip"],
|
||||
[role="dialog"],
|
||||
.el-dialog,
|
||||
.el-message-box,
|
||||
.el-popover,
|
||||
.el-card {
|
||||
background-color: #0c1e35 !important;
|
||||
border-color: rgba(93, 156, 255, 0.2) !important;
|
||||
color: #e0e6ff !important;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.5) !important;
|
||||
}
|
||||
|
||||
/* 强制覆盖所有下拉列表的背景 */
|
||||
.el-select .el-popper,
|
||||
.el-popper[data-popper-placement],
|
||||
.el-select-dropdown__wrap,
|
||||
.el-select-dropdown__list,
|
||||
.el-select-dropdown .el-scrollbar__wrap,
|
||||
.el-dropdown-menu__item,
|
||||
.el-cascader-menu,
|
||||
.el-time-spinner,
|
||||
.el-time-spinner__wrapper,
|
||||
.el-picker-panel .el-scrollbar,
|
||||
.el-picker-panel .el-picker-panel__content,
|
||||
.el-date-picker .el-time-panel {
|
||||
background-color: #0c1e35 !important;
|
||||
color: #e0e6ff !important;
|
||||
}
|
||||
|
||||
/* 所有表格和相关元素 */
|
||||
.el-table,
|
||||
.el-table__empty-block,
|
||||
.el-table__header-wrapper,
|
||||
.el-table__body-wrapper,
|
||||
.el-table--border,
|
||||
.el-table--group,
|
||||
.el-table__footer-wrapper,
|
||||
.el-table__fixed,
|
||||
.el-table__fixed-right,
|
||||
.el-table__append-wrapper,
|
||||
.el-table tr,
|
||||
.el-table td,
|
||||
.el-table th,
|
||||
.el-table .el-table__cell {
|
||||
background-color: transparent !important;
|
||||
color: #e0e6ff !important;
|
||||
border-color: rgba(93, 156, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
/* 表头和斑马纹 */
|
||||
.el-table thead th.el-table__cell {
|
||||
background-color: rgba(93, 156, 255, 0.1) !important;
|
||||
color: #e0e6ff !important;
|
||||
}
|
||||
|
||||
.el-table--striped .el-table__body tr.el-table__row--striped td.el-table__cell {
|
||||
background-color: rgba(93, 156, 255, 0.05) !important;
|
||||
}
|
||||
|
||||
/* 所有输入框和表单元素 */
|
||||
.el-input__inner,
|
||||
.el-input__wrapper,
|
||||
.el-input input,
|
||||
.el-textarea__inner,
|
||||
.el-input-number,
|
||||
.el-input-number__decrease,
|
||||
.el-input-number__increase,
|
||||
.el-select .el-input,
|
||||
.el-select .el-input__wrapper,
|
||||
.el-date-editor,
|
||||
.el-date-editor .el-input__wrapper,
|
||||
.el-cascader .el-input__wrapper,
|
||||
.el-cascader-menu__item,
|
||||
.el-cascader-menu__item:hover,
|
||||
.el-cascader-node {
|
||||
background-color: rgba(13, 37, 63, 0.6) !important;
|
||||
color: #e0e6ff !important;
|
||||
border-color: rgba(93, 156, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
/* 下拉选项 */
|
||||
.el-select-dropdown__item,
|
||||
.el-cascader-node,
|
||||
.el-dropdown-menu__item,
|
||||
.el-date-table td,
|
||||
.el-time-spinner__item,
|
||||
.el-month-table td,
|
||||
.el-year-table td {
|
||||
color: #e0e6ff !important;
|
||||
}
|
||||
|
||||
.el-select-dropdown__item.hover,
|
||||
.el-select-dropdown__item:hover,
|
||||
.el-dropdown-menu__item:hover,
|
||||
.el-cascader-node:not(.is-disabled):hover,
|
||||
.el-cascader-node:not(.is-disabled):focus {
|
||||
background-color: rgba(93, 156, 255, 0.1) !important;
|
||||
}
|
||||
|
||||
.el-select-dropdown__item.selected,
|
||||
.el-cascader-node.is-active,
|
||||
.el-cascader-node.in-active-path {
|
||||
color: #5d9cff !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 表单标签 */
|
||||
.el-form-item__label {
|
||||
color: #e0e6ff !important;
|
||||
}
|
||||
|
||||
/* 按钮 */
|
||||
.el-button--primary {
|
||||
background-color: #5d9cff !important;
|
||||
border-color: #5d9cff !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.el-button--success {
|
||||
background-color: #28a745 !important;
|
||||
border-color: #28a745 !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.el-button--warning {
|
||||
background-color: #e6a23c !important;
|
||||
border-color: #e6a23c !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.el-button--danger {
|
||||
background-color: #f56c6c !important;
|
||||
border-color: #f56c6c !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.el-button--link {
|
||||
color: #5d9cff !important;
|
||||
}
|
||||
|
||||
/* 对话框内容和头部 */
|
||||
.el-dialog__header,
|
||||
.el-dialog__title,
|
||||
.el-dialog__headerbtn .el-dialog__close,
|
||||
.el-dialog__body,
|
||||
.el-dialog__footer,
|
||||
.el-message-box__header,
|
||||
.el-message-box__title,
|
||||
.el-message-box__content,
|
||||
.el-message-box__container,
|
||||
.el-message-box__btns {
|
||||
background-color: #0c1e35 !important;
|
||||
color: #e0e6ff !important;
|
||||
border-color: rgba(93, 156, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
/* 遮罩层 */
|
||||
.el-overlay {
|
||||
background-color: rgba(6, 15, 28, 0.7) !important;
|
||||
backdrop-filter: blur(2px) !important;
|
||||
}
|
||||
|
||||
/* 分页控件 */
|
||||
.el-pagination,
|
||||
.el-pagination button,
|
||||
.el-pagination span,
|
||||
.el-pagination .el-input__inner,
|
||||
.el-pagination .el-select .el-input,
|
||||
.el-pagination .el-select .el-input__inner,
|
||||
.el-pager li {
|
||||
background-color: transparent !important;
|
||||
color: #e0e6ff !important;
|
||||
}
|
||||
|
||||
.el-pager li.is-active {
|
||||
color: #5d9cff !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 标签 */
|
||||
.el-tag {
|
||||
background: rgba(93, 156, 255, 0.1) !important;
|
||||
border-color: rgba(93, 156, 255, 0.2) !important;
|
||||
color: #5d9cff !important;
|
||||
}
|
||||
|
||||
/* 滚动条美化 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(93, 156, 255, 0.3);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(93, 156, 255, 0.5);
|
||||
}
|
65
UI/src/assets/fonts.css
Normal file
@ -0,0 +1,65 @@
|
||||
/* 引入Google Roboto字体 */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap');
|
||||
|
||||
/* 科幻主题CSS变量 */
|
||||
:root {
|
||||
/* 主色调 */
|
||||
--primary-dark: #0c1e35;
|
||||
--primary-light: #1c3a64;
|
||||
--accent-color: #5d9cff;
|
||||
--accent-glow: #43a5f5;
|
||||
--accent-purple: #ad7bee;
|
||||
--text-light: #e0e6ff;
|
||||
|
||||
/* 卡片和边框 */
|
||||
--card-bg: rgba(18, 36, 65, 0.6);
|
||||
--card-border: rgba(93, 156, 255, 0.2);
|
||||
|
||||
/* 特效 */
|
||||
--neon-glow: 0 0 10px rgba(93, 156, 255, 0.5), 0 0 20px rgba(93, 156, 255, 0.3);
|
||||
|
||||
/* 状态颜色 */
|
||||
--success-color: #2ecc71;
|
||||
--warning-color: #ff9500;
|
||||
--error-color: #ff3b30;
|
||||
--info-color: #5d9cff;
|
||||
}
|
||||
|
||||
/* 全局样式 */
|
||||
body {
|
||||
font-family: 'Roboto', 'Helvetica Neue', sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: var(--primary-dark);
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
/* 自定义滚动条 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(93, 156, 255, 0.3);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(93, 156, 255, 0.5);
|
||||
}
|
||||
|
||||
/* 页面过渡动画 */
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
@ -6,8 +6,13 @@ import 'element-plus/dist/index.css'
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
import axios from 'axios'
|
||||
|
||||
// 导入Google Roboto字体
|
||||
import '@/assets/fonts.css'
|
||||
// 导入自定义的Element Plus主题
|
||||
import '@/assets/element-theme.css'
|
||||
|
||||
// 配置axios基础URL
|
||||
axios.defaults.baseURL = 'http://localhost:5000'
|
||||
axios.defaults.baseURL = 'http://127.0.0.1:5000'
|
||||
console.log('API基础URL已设置为:', axios.defaults.baseURL)
|
||||
|
||||
const app = createApp(App)
|
||||
|
@ -1,53 +1,539 @@
|
||||
<template>
|
||||
<el-card>
|
||||
<template #header>
|
||||
<h1>欢迎使用 一树药品销售量预测系统</h1>
|
||||
</template>
|
||||
<p>
|
||||
您可以通过左侧的导航栏访问系统的各个功能模块。
|
||||
</p>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" @click="$router.push('/data')">
|
||||
<div class="card-content">
|
||||
<el-icon><FolderOpened /></el-icon>
|
||||
<h3>数据管理</h3>
|
||||
<p>管理产品和销售数据</p>
|
||||
<div class="dashboard-container">
|
||||
<div class="dashboard-header">
|
||||
<div class="dashboard-title">
|
||||
<h1>数据智能中心</h1>
|
||||
<div class="dashboard-subtitle">智能药品销售预测与分析平台</div>
|
||||
</div>
|
||||
<div class="dashboard-stats">
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ data.products }}</div>
|
||||
<div class="stat-label">产品</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ data.models }}</div>
|
||||
<div class="stat-label">训练模型</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ data.accuracy }}%</div>
|
||||
<div class="stat-label">平均准确率</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 功能卡片区域 -->
|
||||
<el-row :gutter="20" class="feature-cards">
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" v-for="(card, index) in featureCards" :key="index">
|
||||
<div class="feature-card" @click="$router.push(card.path)">
|
||||
<div class="card-icon" :class="`icon-${card.type}`">
|
||||
<el-icon><component :is="card.icon" /></el-icon>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" @click="$router.push('/training')">
|
||||
<div class="card-content">
|
||||
<el-icon><Cpu /></el-icon>
|
||||
<h3>模型训练</h3>
|
||||
<p>训练新的销售预测模型</p>
|
||||
<h3>{{ card.title }}</h3>
|
||||
<p>{{ card.description }}</p>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" @click="$router.push('/prediction')">
|
||||
<div class="card-content">
|
||||
<el-icon><MagicStick /></el-icon>
|
||||
<h3>模型预测</h3>
|
||||
<p>使用模型进行销售预测</p>
|
||||
<div class="card-action">
|
||||
<el-button type="primary" text>
|
||||
进入 <el-icon><ArrowRight /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
<div class="card-glow"></div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<!-- 系统状态面板 -->
|
||||
<div class="system-status">
|
||||
<div class="status-header">
|
||||
<h2>系统状态</h2>
|
||||
<div class="status-time">更新时间: {{ currentTime }}</div>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<div class="status-card">
|
||||
<h3>最近预测</h3>
|
||||
<div class="status-list">
|
||||
<div class="status-item" v-for="(pred, index) in recentPredictions" :key="index">
|
||||
<div class="item-icon" :class="{ 'success': pred.accuracy > 90, 'warning': pred.accuracy <= 90 && pred.accuracy > 75, 'danger': pred.accuracy <= 75 }">
|
||||
<el-icon><component :is="getAccuracyIcon(pred.accuracy)" /></el-icon>
|
||||
</div>
|
||||
<div class="item-content">
|
||||
<div class="item-title">{{ pred.product }}</div>
|
||||
<div class="item-subtitle">准确率: {{ pred.accuracy }}%</div>
|
||||
</div>
|
||||
<div class="item-time">{{ pred.time }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<div class="status-card">
|
||||
<h3>活跃模型</h3>
|
||||
<div class="model-grid">
|
||||
<div class="model-item" v-for="(model, index) in activeModels" :key="index">
|
||||
<div class="model-type" :class="model.type">{{ model.type }}</div>
|
||||
<div class="model-name">{{ model.name }}</div>
|
||||
<div class="model-accuracy">准确率: {{ model.accuracy }}%</div>
|
||||
<div class="model-progress">
|
||||
<div class="progress-bar" :style="{width: `${model.accuracy}%`, background: getAccuracyColor(model.accuracy)}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ArrowRight, DataAnalysis, TrendCharts, CircleCheckFilled, WarningFilled, CircleCloseFilled } from '@element-plus/icons-vue'
|
||||
|
||||
// 模拟数据
|
||||
const data = ref({
|
||||
products: 18,
|
||||
models: 35,
|
||||
accuracy: 92.6
|
||||
})
|
||||
|
||||
// 功能卡片数据
|
||||
const featureCards = [
|
||||
{
|
||||
title: '数据管理',
|
||||
description: '管理产品和销售数据',
|
||||
icon: 'FolderOpened',
|
||||
path: '/data',
|
||||
type: 'data'
|
||||
},
|
||||
{
|
||||
title: '模型训练',
|
||||
description: '训练新的销售预测模型',
|
||||
icon: 'Cpu',
|
||||
path: '/training',
|
||||
type: 'train'
|
||||
},
|
||||
{
|
||||
title: '预测分析',
|
||||
description: '使用模型进行销售预测',
|
||||
icon: 'MagicStick',
|
||||
path: '/prediction',
|
||||
type: 'predict'
|
||||
},
|
||||
{
|
||||
title: '模型管理',
|
||||
description: '管理和对比训练模型',
|
||||
icon: 'Files',
|
||||
path: '/management',
|
||||
type: 'manage'
|
||||
}
|
||||
]
|
||||
|
||||
// 最近预测数据
|
||||
const recentPredictions = [
|
||||
{ product: '阿司匹林', accuracy: 94.2, time: '今天 09:45' },
|
||||
{ product: '布洛芬', accuracy: 88.7, time: '今天 08:30' },
|
||||
{ product: '维生素C', accuracy: 72.4, time: '昨天 16:20' },
|
||||
{ product: '复方感冒药', accuracy: 96.1, time: '昨天 15:15' }
|
||||
]
|
||||
|
||||
// 活跃模型数据
|
||||
const activeModels = [
|
||||
{ name: '阿司匹林销量预测', type: 'mlstm', accuracy: 94.2 },
|
||||
{ name: '布洛芬销量预测', type: 'kan', accuracy: 88.7 },
|
||||
{ name: '维生素C销量预测', type: 'transformer', accuracy: 91.5 },
|
||||
{ name: '复方感冒药销量预测', type: 'mlstm', accuracy: 96.1 }
|
||||
]
|
||||
|
||||
// 当前时间
|
||||
const currentTime = ref('')
|
||||
|
||||
// 根据准确率获取图标
|
||||
const getAccuracyIcon = (accuracy) => {
|
||||
if (accuracy > 90) return CircleCheckFilled
|
||||
if (accuracy > 75) return WarningFilled
|
||||
return CircleCloseFilled
|
||||
}
|
||||
|
||||
// 根据准确率获取颜色
|
||||
const getAccuracyColor = (accuracy) => {
|
||||
if (accuracy > 90) return 'linear-gradient(90deg, #34C759, #00B2A5)'
|
||||
if (accuracy > 75) return 'linear-gradient(90deg, #FF9500, #FFCC00)'
|
||||
return 'linear-gradient(90deg, #FF3B30, #FF6482)'
|
||||
}
|
||||
|
||||
// 更新当前时间
|
||||
const updateTime = () => {
|
||||
const now = new Date()
|
||||
currentTime.value = now.toLocaleString()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateTime()
|
||||
// 每分钟更新一次时间
|
||||
setInterval(updateTime, 60000)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-content {
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
.dashboard-container {
|
||||
width: 100%;
|
||||
}
|
||||
.card-content .el-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.dashboard-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 30px;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.dashboard-title h1 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 36px;
|
||||
background: linear-gradient(to right, var(--text-light), var(--accent-color));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.dashboard-subtitle {
|
||||
color: var(--text-light);
|
||||
opacity: 0.7;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.dashboard-stats {
|
||||
display: flex;
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--card-border);
|
||||
box-shadow: var(--neon-glow);
|
||||
border-radius: 10px;
|
||||
padding: 15px 25px;
|
||||
text-align: center;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 5px;
|
||||
background: linear-gradient(to right, var(--accent-color), var(--accent-purple));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: var(--text-light);
|
||||
font-size: 14px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.feature-cards {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
position: relative;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
height: 200px;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
cursor: pointer;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: var(--neon-glow);
|
||||
}
|
||||
|
||||
.feature-card:hover .card-glow {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.card-glow {
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
right: -20px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(93, 156, 255, 0.4) 0%, rgba(18, 36, 65, 0) 70%);
|
||||
opacity: 0.3;
|
||||
transition: opacity 0.3s ease;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
margin-bottom: 15px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.card-icon .el-icon {
|
||||
font-size: 28px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.icon-data {
|
||||
background: linear-gradient(135deg, #2C88D9, #1A60A0);
|
||||
}
|
||||
|
||||
.icon-train {
|
||||
background: linear-gradient(135deg, #8E44AD, #5E3A7E);
|
||||
}
|
||||
|
||||
.icon-predict {
|
||||
background: linear-gradient(135deg, #2ECC71, #1D8348);
|
||||
}
|
||||
|
||||
.icon-manage {
|
||||
background: linear-gradient(135deg, #E67E22, #BA4A00);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.card-content h3 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 20px;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.card-content p {
|
||||
margin: 0;
|
||||
color: var(--text-light);
|
||||
opacity: 0.7;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.card-action {
|
||||
margin-top: 15px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.system-status {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.status-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.status-header h2 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.status-time {
|
||||
color: var(--text-light);
|
||||
opacity: 0.7;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.status-card {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.status-card h3 {
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 18px;
|
||||
color: var(--text-light);
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid var(--card-border);
|
||||
}
|
||||
|
||||
.status-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.status-item:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.item-icon .el-icon {
|
||||
font-size: 16px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.item-icon.success {
|
||||
background: linear-gradient(135deg, #34C759, #00B2A5);
|
||||
}
|
||||
|
||||
.item-icon.warning {
|
||||
background: linear-gradient(135deg, #FF9500, #FFCC00);
|
||||
}
|
||||
|
||||
.item-icon.danger {
|
||||
background: linear-gradient(135deg, #FF3B30, #FF6482);
|
||||
}
|
||||
|
||||
.item-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 16px;
|
||||
color: var(--text-light);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.item-subtitle {
|
||||
font-size: 12px;
|
||||
color: var(--text-light);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.item-time {
|
||||
font-size: 12px;
|
||||
color: var(--text-light);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.model-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.model-item {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.model-item:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.model-type {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.model-type.mlstm {
|
||||
background: rgba(93, 156, 255, 0.2);
|
||||
color: #5d9cff;
|
||||
}
|
||||
|
||||
.model-type.transformer {
|
||||
background: rgba(173, 123, 238, 0.2);
|
||||
color: #ad7bee;
|
||||
}
|
||||
|
||||
.model-type.kan {
|
||||
background: rgba(67, 165, 245, 0.2);
|
||||
color: #43a5f5;
|
||||
}
|
||||
|
||||
.model-name {
|
||||
font-size: 14px;
|
||||
color: var(--text-light);
|
||||
margin-bottom: 8px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.model-accuracy {
|
||||
font-size: 12px;
|
||||
color: var(--text-light);
|
||||
opacity: 0.7;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.model-progress {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
border-radius: 3px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.dashboard-header {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dashboard-stats {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.model-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -203,20 +203,4 @@ const renderChart = () => {
|
||||
onMounted(() => {
|
||||
fetchProducts()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.chart-container {
|
||||
position: relative;
|
||||
height: 300px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.mt-4 {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
</style>
|
||||
</script>
|
@ -139,12 +139,4 @@ const handleImport = async (options) => {
|
||||
onMounted(() => {
|
||||
fetchModels()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
</script>
|
@ -642,88 +642,4 @@ onMounted(() => {
|
||||
fetchProducts()
|
||||
fetchAvailableModels()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.prediction-form .el-form-item {
|
||||
margin-right: 15px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.chart-container {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
border: 1px solid #ebeef5;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.chart-placeholder {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 400px;
|
||||
border: 1px dashed #dcdfe6;
|
||||
border-radius: 4px;
|
||||
color: #909399;
|
||||
}
|
||||
.loading-container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.metrics-display {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
.metric-item {
|
||||
background-color: #f5f7fa;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.metric-label {
|
||||
font-weight: bold;
|
||||
margin-right: 4px;
|
||||
}
|
||||
.metric-value {
|
||||
color: #409EFF;
|
||||
}
|
||||
.pagination-container {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.prediction-dialog-content {
|
||||
padding: 10px;
|
||||
max-height: calc(100vh - 120px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
.prediction-summary {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.prediction-chart-container {
|
||||
height: 100%;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.stat-cards {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.stat-cards .el-card {
|
||||
flex: 1;
|
||||
}
|
||||
.stat-header {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
color: #409EFF;
|
||||
}
|
||||
</style>
|
||||
</script>
|
@ -3,6 +3,7 @@ import vue from '@vitejs/plugin-vue'
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
@ -15,6 +16,11 @@ export default defineConfig({
|
||||
resolvers: [ElementPlusResolver()],
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
}
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
|
45
api.py
@ -1434,36 +1434,19 @@ def get_prediction_file(model_type, product_id, filename):
|
||||
|
||||
@app.route('/api/predictions/compare/<filename>')
|
||||
def get_compare_file(filename):
|
||||
"""获取模型比较结果文件"""
|
||||
try:
|
||||
file_path = os.path.join('predictions', 'compare')
|
||||
return send_from_directory(file_path, filename)
|
||||
except FileNotFoundError:
|
||||
return jsonify({"status": "error", "error": f"比较结果文件 {filename} 未找到"}), 404
|
||||
"""
|
||||
提供比较结果文件的下载
|
||||
"""
|
||||
directory = os.path.join(current_dir, 'predictions', 'compare')
|
||||
return send_from_directory(directory, filename)
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 命令行参数解析
|
||||
parser = argparse.ArgumentParser(description='药店销售预测系统API服务')
|
||||
parser.add_argument('--host', type=str, default='0.0.0.0', help='API服务监听的主机地址')
|
||||
parser.add_argument('--port', type=int, default=5000, help='API服务监听的端口')
|
||||
parser.add_argument('--swagger', action='store_true', default=True, help='是否启用Swagger UI')
|
||||
parser.add_argument('--debug', action='store_true', default=True, help='是否启用调试模式')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# 如果不启用Swagger,则关闭Swagger UI
|
||||
if not args.swagger:
|
||||
app.config['SWAGGER'] = {'enabled': False}
|
||||
print("Swagger UI已禁用")
|
||||
else:
|
||||
print(f"Swagger UI已启用,访问: http://{args.host}:{args.port}/swagger/")
|
||||
|
||||
# 确保 models 目录存在
|
||||
if not os.path.exists('models'):
|
||||
os.makedirs('models')
|
||||
|
||||
# 确保预测结果目录存在
|
||||
if not os.path.exists('predictions'):
|
||||
os.makedirs('predictions')
|
||||
|
||||
app.run(debug=args.debug, host=args.host, port=args.port)
|
||||
# 检查可用的设备
|
||||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||
print(f"使用设备: {device}")
|
||||
|
||||
# 运行Flask应用
|
||||
# 在生产环境中,应使用Gunicorn或uWSGI等WSGI服务器
|
||||
# 例如: gunicorn --workers 4 --bind 0.0.0.0:5000 api:app
|
||||
# 使用--host=0.0.0.0可以使服务在局域网内可访问
|
||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
BIN
docs/assets/数据管理-历史数据查看.png
Normal file
After Width: | Height: | Size: 136 KiB |
BIN
docs/assets/数据管理页.png
Normal file
After Width: | Height: | Size: 151 KiB |
BIN
docs/assets/模型管理.png
Normal file
After Width: | Height: | Size: 172 KiB |
BIN
docs/assets/模型训练.png
Normal file
After Width: | Height: | Size: 91 KiB |
BIN
docs/assets/预测分析.png
Normal file
After Width: | Height: | Size: 159 KiB |
BIN
docs/assets/预测结果1.png
Normal file
After Width: | Height: | Size: 142 KiB |
BIN
docs/assets/预测结果2.png
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
docs/assets/首页截图.png
Normal file
After Width: | Height: | Size: 490 KiB |
392
docs/user_manual.md
Normal file
@ -0,0 +1,392 @@
|
||||
# 药店销售预测系统用户手册
|
||||
|
||||
## 目录
|
||||
|
||||
1. [系统介绍](#系统介绍)
|
||||
2. [系统架构](#系统架构)
|
||||
3. [系统安装与配置](#系统安装与配置)
|
||||
4. [功能模块说明](#功能模块说明)
|
||||
- [首页概览](#首页概览)
|
||||
- [数据管理](#数据管理)
|
||||
- [模型训练](#模型训练)
|
||||
- [预测分析](#预测分析)
|
||||
- [模型管理](#模型管理)
|
||||
5. [命令行操作指南](#命令行操作指南)
|
||||
6. [常见问题解答](#常见问题解答)
|
||||
7. [技术支持](#技术支持)
|
||||
|
||||
## 系统介绍
|
||||
|
||||
药店销售预测系统是一款基于人工智能的药品销售预测工具,通过深度学习算法分析历史销售数据,为药店提供精准的销售预测服务,帮助药店优化库存管理,提高经营效率。
|
||||
|
||||
系统采用前后端分离的架构,前端基于Vue.js和Element Plus构建现代化的用户界面,后端使用Flask提供RESTful API服务,支持多种预测模型,包括mLSTM、Transformer和KAN(Kolmogorov-Arnold Network)。
|
||||
|
||||

|
||||
|
||||
## 系统架构
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph "前端 UI 层"
|
||||
A[浏览器客户端] --> B[Vue.js 应用]
|
||||
B --> C1[数据管理视图]
|
||||
B --> C2[模型训练视图]
|
||||
B --> C3[预测分析视图]
|
||||
B --> C4[模型管理视图]
|
||||
end
|
||||
|
||||
subgraph "后端 API 层"
|
||||
D[Flask 服务器] --> E1[数据管理API]
|
||||
D --> E2[模型训练API]
|
||||
D --> E3[预测分析API]
|
||||
D --> E4[模型管理API]
|
||||
end
|
||||
|
||||
subgraph "模型层"
|
||||
F1[mLSTM 模型]
|
||||
F2[Transformer 模型]
|
||||
F3[KAN 模型]
|
||||
end
|
||||
|
||||
subgraph "数据存储层"
|
||||
G1[销售数据 Excel文件]
|
||||
G2[模型文件 .pt]
|
||||
G3[预测结果文件]
|
||||
end
|
||||
|
||||
%% 连接各层
|
||||
C1 <--> E1
|
||||
C2 <--> E2
|
||||
C3 <--> E3
|
||||
C4 <--> E4
|
||||
|
||||
E1 <--> G1
|
||||
E2 --> F1
|
||||
E2 --> F2
|
||||
E2 --> F3
|
||||
E3 --> F1
|
||||
E3 --> F2
|
||||
E3 --> F3
|
||||
E4 --> G2
|
||||
|
||||
F1 --> G2
|
||||
F2 --> G2
|
||||
F3 --> G2
|
||||
|
||||
E3 --> G3
|
||||
```
|
||||
|
||||
系统由以下几部分组成:
|
||||
|
||||
1. **前端界面**:基于Vue.js和Element Plus构建的用户交互界面
|
||||
2. **后端API**:基于Flask的RESTful API服务
|
||||
3. **预测模型**:包含mLSTM、Transformer和KAN三种深度学习模型
|
||||
4. **数据存储**:使用文件系统存储模型和预测结果
|
||||
|
||||
## 系统安装与配置
|
||||
|
||||
### 前端部署
|
||||
|
||||
1. 确保已安装Node.js环境(推荐v16.0.0以上版本)
|
||||
2. 进入UI目录:`cd UI`
|
||||
3. 安装依赖:`npm install`
|
||||
4. 开发模式运行:`npm run dev`
|
||||
5. 构建生产版本:`npm run build`
|
||||
|
||||
### 后端部署
|
||||
|
||||
1. 确保已安装Python环境(推荐Python 3.10以上版本)
|
||||
2. 安装依赖:`pip install -r requirements.txt`
|
||||
3. 启动API服务:`python api.py`
|
||||
|
||||
服务器将在默认端口5000上运行。
|
||||
|
||||
### 访问前端界面
|
||||
在浏览器中访问:
|
||||
```
|
||||
http://localhost:5000/ui/
|
||||
```
|
||||
|
||||
## 功能模块说明
|
||||
|
||||
### 首页概览
|
||||
|
||||
首页提供系统的整体概况,包括产品数量、已训练模型数量、平均预测准确率等关键指标,以及最近的预测结果和活跃模型列表。
|
||||
|
||||
**操作步骤**:
|
||||
1. 登录系统后,默认进入首页
|
||||
2. 查看关键统计指标和最近活动
|
||||
|
||||

|
||||
|
||||
### 数据管理
|
||||
|
||||
数据管理模块允许用户上传、查看和管理药品销售数据。系统支持Excel格式的数据上传,并提供数据可视化功能。
|
||||
|
||||
**操作步骤**:
|
||||
1. 点击左侧菜单的"数据管理"
|
||||
2. 查看现有产品列表
|
||||
3. 点击"上传销售数据"按钮上传新数据
|
||||
4. 点击产品名称查看详细销售数据和趋势图
|
||||
|
||||

|
||||
|
||||
**历史数据查看**:
|
||||
|
||||

|
||||
|
||||
### 模型训练
|
||||
|
||||
模型训练模块允许用户选择产品和算法模型,启动训练任务,并查看训练进度和结果。
|
||||
|
||||
**操作步骤**:
|
||||
1. 点击左侧菜单的"模型训练"
|
||||
2. 在左侧面板选择产品、模型类型和训练参数
|
||||
3. 点击"启动训练"按钮
|
||||
4. 在右侧任务列表查看训练状态和结果
|
||||
|
||||

|
||||
|
||||
### 预测分析
|
||||
|
||||
预测分析模块允许用户使用已训练的模型进行销售预测,并提供预测结果的可视化展示。
|
||||
|
||||
**操作步骤**:
|
||||
1. 点击左侧菜单的"预测分析"
|
||||
2. 选择产品、模型类型和预测参数
|
||||
3. 点击"查询可用模型"按钮
|
||||
4. 从模型列表中选择一个模型,点击"执行预测"
|
||||
5. 查看预测结果图表和数据
|
||||
|
||||

|
||||
|
||||
**预测结果展示**:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 模型管理
|
||||
|
||||
模型管理模块允许用户查看、导出和删除已训练的模型,也支持导入外部模型。
|
||||
|
||||
**操作步骤**:
|
||||
1. 点击左侧菜单的"模型管理"
|
||||
2. 查看模型列表
|
||||
3. 使用过滤器筛选特定产品或模型类型
|
||||
4. 点击"详情"查看模型详细信息
|
||||
5. 点击"导出"下载模型文件
|
||||
6. 点击"删除"移除不需要的模型
|
||||
7. 点击"导入模型"上传外部训练的模型文件
|
||||
|
||||

|
||||
|
||||
## 系统组件
|
||||
|
||||
### 1. 前端UI
|
||||
- 基于Vue.js和Element Plus构建的现代化界面
|
||||
- 蓝色主题的沉浸式用户体验
|
||||
- 响应式设计,适配不同设备屏幕
|
||||
|
||||
### 2. 后端API
|
||||
- 基于Flask的RESTful API
|
||||
- 支持数据上传、模型训练、销售预测和模型管理
|
||||
- Swagger API文档支持
|
||||
|
||||
### 3. 预测模型
|
||||
- **mLSTM模型**:多层长短期记忆网络,适合序列数据预测
|
||||
- **Transformer模型**:基于自注意力机制,捕捉长期依赖关系
|
||||
- **KAN模型**:Kolmogorov-Arnold网络,具有高精度的函数拟合能力
|
||||
|
||||
### 4. 数据管理
|
||||
- 支持Excel格式的销售数据导入导出
|
||||
- 历史销售数据可视化
|
||||
- 预测结果可视化与导出
|
||||
|
||||
## 命令行操作指南
|
||||
|
||||
除了图形界面外,系统也提供命令行操作方式,适合高级用户和自动化脚本使用。
|
||||
|
||||
### 快速入门
|
||||
|
||||
在项目根目录下,运行以下命令启动命令行界面:
|
||||
|
||||
```bash
|
||||
python run_pharmacy_prediction.py
|
||||
```
|
||||
|
||||
### 主菜单导航
|
||||
|
||||
启动后,您将看到主菜单界面:
|
||||
|
||||
```
|
||||
==========================================
|
||||
🏪 药店单品销售预测系统 🏪
|
||||
==========================================
|
||||
1. 训练所有药品的销售预测模型
|
||||
2. 训练单个药品的销售预测模型(Transformer)
|
||||
3. 训练单个药品的销售预测模型(mLSTM)
|
||||
4. 训练单个药品的销售预测模型(KAN)
|
||||
5. 查看已有预测结果
|
||||
6. 使用已训练的模型进行预测
|
||||
7. 比较不同模型的预测结果
|
||||
8. 模型管理
|
||||
0. 退出
|
||||
==========================================
|
||||
```
|
||||
|
||||
### 功能详解
|
||||
|
||||
#### 训练模型
|
||||
|
||||
系统支持三种主要的模型训练方式:
|
||||
|
||||
- **训练所有药品模型**:选择主菜单中的选项`1`
|
||||
- **训练单个药品模型**:选择选项`2`、`3`或`4`,分别使用Transformer、mLSTM或KAN模型
|
||||
|
||||
#### 查看预测结果
|
||||
|
||||
选择主菜单中的选项`5`,系统会显示已有的预测结果列表。
|
||||
|
||||
#### 使用模型预测
|
||||
|
||||
选择主菜单中的选项`6`,可以使用已训练的模型进行预测。
|
||||
|
||||
#### 比较模型预测结果
|
||||
|
||||
选择主菜单中的选项`7`,可以比较不同模型对同一产品的预测结果。
|
||||
|
||||
#### 模型管理
|
||||
|
||||
选择主菜单中的选项`8`,进入模型管理子菜单:
|
||||
|
||||
```
|
||||
==========================================
|
||||
📊 药店销售预测系统 - 模型管理工具 📊
|
||||
==========================================
|
||||
1. 查看所有模型
|
||||
2. 查看特定产品的模型
|
||||
3. 查看特定模型的详细信息
|
||||
4. 使用模型进行预测
|
||||
5. 比较不同模型的预测结果
|
||||
6. 删除模型
|
||||
7. 导出模型
|
||||
8. 导入模型
|
||||
0. 退出
|
||||
==========================================
|
||||
```
|
||||
|
||||
### 命令行参数
|
||||
|
||||
许多功能也可以通过命令行参数直接调用,例如:
|
||||
|
||||
```bash
|
||||
# 使用mLSTM模型训练P001产品的销售预测模型
|
||||
python run_pharmacy_prediction.py --train P001 --model mlstm
|
||||
|
||||
# 比较P001产品的不同模型预测结果
|
||||
python run_pharmacy_prediction.py --compare P001
|
||||
```
|
||||
|
||||
### 模型管理命令行工具
|
||||
|
||||
模型管理功能也可以通过独立的命令行工具使用:
|
||||
|
||||
```bash
|
||||
# 列出所有模型
|
||||
python model_management.py --action list
|
||||
|
||||
# 查看特定产品的模型详情
|
||||
python model_management.py --action details --product_id P001 --model_type mlstm
|
||||
|
||||
# 使用特定模型进行预测
|
||||
python model_management.py --action predict --product_id P001 --model_type mlstm
|
||||
```
|
||||
|
||||
### API服务使用
|
||||
|
||||
#### 启动API服务
|
||||
|
||||
运行以下命令启动API服务:
|
||||
|
||||
```bash
|
||||
python api.py
|
||||
```
|
||||
|
||||
默认情况下,API服务会在 http://localhost:5000 上运行。
|
||||
|
||||
#### 访问API文档
|
||||
|
||||
启动服务后,访问 http://localhost:5000/swagger/ 可以查看所有API接口说明并进行测试。
|
||||
|
||||
#### 使用API示例
|
||||
|
||||
以下是一些基本的API使用示例:
|
||||
|
||||
```bash
|
||||
# 获取产品列表
|
||||
curl -X GET "http://localhost:5000/api/products"
|
||||
|
||||
# 获取特定产品销售数据
|
||||
curl -X GET "http://localhost:5000/api/products/P001/sales"
|
||||
|
||||
# 启动模型训练
|
||||
curl -X POST "http://localhost:5000/api/training" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"product_id": "P001", "model_type": "mlstm"}'
|
||||
|
||||
# 获取预测结果
|
||||
curl -X POST "http://localhost:5000/api/prediction" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"product_id": "P001", "model_type": "mlstm", "days": 7}'
|
||||
```
|
||||
|
||||
## 常见问题解答
|
||||
|
||||
### 问题1:如何选择最合适的预测模型?
|
||||
|
||||
**回答**:三种模型各有特点:
|
||||
- mLSTM:适合较短期的预测,训练速度快
|
||||
- Transformer:适合中长期预测,对季节性变化敏感
|
||||
- KAN:适合复杂模式识别,通常有最高的准确率但训练时间较长
|
||||
|
||||
根据预测周期和数据特点选择合适的模型。一般情况下,如果不确定,可以使用KAN模型获得最佳效果。
|
||||
|
||||
### 问题2:为什么模型训练失败?
|
||||
|
||||
**回答**:常见原因包括:
|
||||
- 数据量不足:确保至少有30天以上的销售数据
|
||||
- 数据异常:检查数据中是否有缺失值或异常值
|
||||
- 服务器资源不足:大型模型训练需要足够的计算资源
|
||||
|
||||
### 问题3:如何提高预测准确率?
|
||||
|
||||
**回答**:
|
||||
- 提供更多历史数据
|
||||
- 增加训练轮次(epochs)
|
||||
- 结合多个模型的预测结果
|
||||
- 加入更多相关特征(如节假日、天气等)
|
||||
|
||||
### 问题4:系统支持哪些数据格式?
|
||||
|
||||
**回答**:目前仅支持Excel(.xlsx)格式的销售数据文件。
|
||||
|
||||
### 问题5:训练速度慢
|
||||
|
||||
**回答**:
|
||||
- 检查是否正在使用GPU加速
|
||||
- 减小批大小(batch_size)
|
||||
- 减少训练轮次(epochs)
|
||||
- 考虑使用更简单的模型
|
||||
|
||||
### 问题6:模型保存失败
|
||||
|
||||
**回答**:
|
||||
- 检查磁盘空间是否充足
|
||||
- 确保有写入权限
|
||||
- 尝试手动创建predictions目录
|
||||
|
||||
## 系统要求
|
||||
|
||||
- **后端**:Python 3.10或更高版本,安装所有requirements.txt中的依赖
|
||||
- **前端**:现代浏览器(Chrome, Firefox, Edge等)
|
BIN
docs/user_manual.pdf
Normal file
@ -1,4 +1,4 @@
|
||||
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/DataView-oizPc7x1.js","assets/DataView-Ck4S5DFw.css","assets/el-button-C6xmlTaP.css","assets/el-pagination-BNQcHhjS.css","assets/el-table-column-BNWCz8qi.css","assets/el-select-CvzM3W2w.css","assets/el-progress-DBWeHy1f.css","assets/TrainingView-D_nALww6.js","assets/el-form-item-BqrJjMte.css","assets/el-input-number-DUUPPWGj.css","assets/PredictionView-auuFloEo.js","assets/PredictionView-Cx1B83Kv.css","assets/ManagementView-LHQVyhCM.js","assets/ManagementView-DBnuPvrh.css"])))=>i.map(i=>d[i]);
|
||||
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./assets/DataView-oizPc7x1.js","./assets/DataView-Ck4S5DFw.css","./assets/el-button-C6xmlTaP.css","./assets/el-pagination-BNQcHhjS.css","./assets/el-table-column-BNWCz8qi.css","./assets/el-select-CvzM3W2w.css","./assets/el-progress-DBWeHy1f.css","./assets/TrainingView-D_nALww6.js","./assets/el-form-item-BqrJjMte.css","./assets/el-input-number-DUUPPWGj.css","./assets/PredictionView-auuFloEo.js","./assets/PredictionView-Cx1B83Kv.css","./assets/ManagementView-LHQVyhCM.js","./assets/ManagementView-DBnuPvrh.css"])))=>i.map(i=>d[i]);
|
||||
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const a of document.querySelectorAll('link[rel="modulepreload"]'))o(a);new MutationObserver(a=>{for(const l of a)if(l.type==="childList")for(const r of l.addedNodes)r.tagName==="LINK"&&r.rel==="modulepreload"&&o(r)}).observe(document,{childList:!0,subtree:!0});function n(a){const l={};return a.integrity&&(l.integrity=a.integrity),a.referrerPolicy&&(l.referrerPolicy=a.referrerPolicy),a.crossOrigin==="use-credentials"?l.credentials="include":a.crossOrigin==="anonymous"?l.credentials="omit":l.credentials="same-origin",l}function o(a){if(a.ep)return;a.ep=!0;const l=n(a);fetch(a.href,l)}})();/**
|
||||
* @vue/shared v3.5.16
|
||||
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
||||
|
@ -1,14 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>一树药品销售量预测系统</title>
|
||||
<script type="module" crossorigin src="/assets/index-CtyWzmh7.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CxLqWDGJ.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="./favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>一树药品销售量预测系统</title>
|
||||
<script type="module" crossorigin src="./assets/index-CtyWzmh7.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-CxLqWDGJ.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
</body>
|
||||
</html>
|