完善使用手册 修复一些bug

This commit is contained in:
gdtiti 2025-06-14 05:00:17 +08:00
parent a51eccd689
commit 7a52c67703
22 changed files with 1586 additions and 239 deletions

View File

@ -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>

View 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
View 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;
}

View File

@ -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)

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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
View File

@ -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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 KiB

392
docs/user_manual.md Normal file
View File

@ -0,0 +1,392 @@
# 药店销售预测系统用户手册
## 目录
1. [系统介绍](#系统介绍)
2. [系统架构](#系统架构)
3. [系统安装与配置](#系统安装与配置)
4. [功能模块说明](#功能模块说明)
- [首页概览](#首页概览)
- [数据管理](#数据管理)
- [模型训练](#模型训练)
- [预测分析](#预测分析)
- [模型管理](#模型管理)
5. [命令行操作指南](#命令行操作指南)
6. [常见问题解答](#常见问题解答)
7. [技术支持](#技术支持)
## 系统介绍
药店销售预测系统是一款基于人工智能的药品销售预测工具,通过深度学习算法分析历史销售数据,为药店提供精准的销售预测服务,帮助药店优化库存管理,提高经营效率。
系统采用前后端分离的架构前端基于Vue.js和Element Plus构建现代化的用户界面后端使用Flask提供RESTful API服务支持多种预测模型包括mLSTM、Transformer和KANKolmogorov-Arnold Network
![系统首页截图](assets/首页截图.png)
## 系统架构
```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. 查看关键统计指标和最近活动
![首页截图](assets/首页截图.png)
### 数据管理
数据管理模块允许用户上传、查看和管理药品销售数据。系统支持Excel格式的数据上传并提供数据可视化功能。
**操作步骤**
1. 点击左侧菜单的"数据管理"
2. 查看现有产品列表
3. 点击"上传销售数据"按钮上传新数据
4. 点击产品名称查看详细销售数据和趋势图
![数据管理页面](assets/数据管理页.png)
**历史数据查看**
![数据详情查看](assets/数据管理-历史数据查看.png)
### 模型训练
模型训练模块允许用户选择产品和算法模型,启动训练任务,并查看训练进度和结果。
**操作步骤**
1. 点击左侧菜单的"模型训练"
2. 在左侧面板选择产品、模型类型和训练参数
3. 点击"启动训练"按钮
4. 在右侧任务列表查看训练状态和结果
![模型训练页面](assets/模型训练.png)
### 预测分析
预测分析模块允许用户使用已训练的模型进行销售预测,并提供预测结果的可视化展示。
**操作步骤**
1. 点击左侧菜单的"预测分析"
2. 选择产品、模型类型和预测参数
3. 点击"查询可用模型"按钮
4. 从模型列表中选择一个模型,点击"执行预测"
5. 查看预测结果图表和数据
![预测分析页面](assets/预测分析.png)
**预测结果展示**
![预测结果1](assets/预测结果1.png)
![预测结果2](assets/预测结果2.png)
### 模型管理
模型管理模块允许用户查看、导出和删除已训练的模型,也支持导入外部模型。
**操作步骤**
1. 点击左侧菜单的"模型管理"
2. 查看模型列表
3. 使用过滤器筛选特定产品或模型类型
4. 点击"详情"查看模型详细信息
5. 点击"导出"下载模型文件
6. 点击"删除"移除不需要的模型
7. 点击"导入模型"上传外部训练的模型文件
![模型管理页面](assets/模型管理.png)
## 系统组件
### 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

Binary file not shown.

View 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

View File

@ -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>