### 主要改动

1.  **删除“数据管理”**:
    *   从 `UI/src/App.vue` 的导航菜单中移除了“数据管理”项。
    *   从 `UI/src/router/index.js` 中删除了对应的 `/data` 路由。
    *   删除了视图文件 `UI/src/views/DataView.vue`。

2.  **提升“店铺管理”**:
    *   将“店铺管理”菜单项在 `UI/src/App.vue` 中的位置提升,以填补原“数据管理”的位置,使其在导航中更加突出。

### 涉及文件
*   `UI/src/App.vue`
*   `UI/src/router/index.js`
*   `UI/src/views/DataView.vue` (已删除)
This commit is contained in:
xz2000 2025-07-14 19:59:59 +08:00
parent 484f39e12f
commit cfb50d0573
4 changed files with 26 additions and 471 deletions

View File

@ -31,8 +31,8 @@
<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 index="/store-management">
<el-icon><Shop /></el-icon>店铺管理
</el-menu-item>
<el-sub-menu index="training-submenu">
<template #title>
@ -70,9 +70,6 @@
<el-menu-item index="/management">
<el-icon><Files /></el-icon>模型管理
</el-menu-item>
<el-menu-item index="/store-management">
<el-icon><Shop /></el-icon>店铺管理
</el-menu-item>
</el-sub-menu>
</el-menu>
</el-scrollbar>

View File

@ -9,11 +9,6 @@ const router = createRouter({
name: 'dashboard',
component: DashboardView
},
{
path: '/data',
name: 'data',
component: () => import('../views/DataView.vue')
},
{
path: '/training',
name: 'training',

View File

@ -1,461 +0,0 @@
<template>
<el-card>
<template #header>
<div class="card-header">
<span>销售数据管理</span>
<el-upload
:show-file-list="false"
:http-request="handleUpload"
>
<el-button type="primary">上传销售数据</el-button>
</el-upload>
</div>
</template>
<!-- 查询过滤条件 -->
<div class="filter-section">
<el-row :gutter="20">
<el-col :span="6">
<el-select v-model="filters.store_id" placeholder="选择店铺" clearable @change="handleFilterChange">
<el-option label="全部店铺" value=""></el-option>
<el-option
v-for="store in stores"
:key="store.store_id"
:label="store.store_name"
:value="store.store_id">
</el-option>
</el-select>
</el-col>
<el-col :span="6">
<el-select v-model="filters.product_id" placeholder="选择产品" clearable @change="handleFilterChange">
<el-option label="全部产品" value=""></el-option>
<el-option
v-for="product in allProducts"
:key="product.product_id"
:label="product.product_name"
:value="product.product_id">
</el-option>
</el-select>
</el-col>
<el-col :span="8">
<el-date-picker
v-model="filters.dateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
@change="handleFilterChange"
/>
</el-col>
<el-col :span="4">
<el-button type="primary" @click="handleFilterChange">查询</el-button>
</el-col>
</el-row>
</div>
<!-- 销售数据表格 -->
<el-table :data="salesData" stripe v-loading="loading" class="mt-4">
<el-table-column prop="date" label="日期" width="120"></el-table-column>
<el-table-column prop="store_name" label="店铺名称" width="150"></el-table-column>
<el-table-column prop="store_id" label="店铺ID" width="100"></el-table-column>
<el-table-column prop="product_name" label="产品名称" width="150"></el-table-column>
<el-table-column prop="product_id" label="产品ID" width="100"></el-table-column>
<el-table-column prop="quantity_sold" label="销量" width="80" align="right"></el-table-column>
<el-table-column prop="unit_price" label="单价" width="80" align="right">
<template #default="{ row }">
¥{{ row.unit_price?.toFixed(2) }}
</template>
</el-table-column>
<el-table-column prop="sales_amount" label="销售额" width="100" align="right">
<template #default="{ row }">
¥{{ row.sales_amount?.toFixed(2) }}
</template>
</el-table-column>
<el-table-column prop="store_type" label="店铺类型" width="100"></el-table-column>
<el-table-column label="操作" width="120">
<template #default="{ row }">
<el-button link @click="viewStoreDetails(row.store_id)">店铺详情</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-if="total > 0"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
@current-change="handlePageChange"
@size-change="handleSizeChange"
class="mt-4"
/>
<!-- 统计信息 -->
<div class="statistics-section mt-4" v-if="statistics">
<el-row :gutter="20">
<el-col :span="6">
<el-statistic title="总记录数" :value="statistics.total_records" />
</el-col>
<el-col :span="6">
<el-statistic title="总销售额" :value="statistics.total_sales_amount" :precision="2" prefix="¥" />
</el-col>
<el-col :span="6">
<el-statistic title="总销量" :value="statistics.total_quantity" />
</el-col>
<el-col :span="6">
<el-statistic title="店铺数量" :value="statistics.stores" />
</el-col>
</el-row>
</div>
<!-- 产品详情对话框 -->
<el-dialog
v-model="dialogVisible"
:title="`${selectedProduct?.product_name} - 销售详情`"
width="60%"
>
<div v-loading="detailLoading">
<div v-if="salesData.length > 0">
<div class="chart-container">
<canvas ref="salesChartCanvas"></canvas>
</div>
<el-table :data="paginatedSalesData" stripe>
<el-table-column prop="date" label="日期"></el-table-column>
<el-table-column prop="sales" label="销量"></el-table-column>
<el-table-column prop="price" label="价格"></el-table-column>
</el-table>
<el-pagination
layout="prev, pager, next"
:total="salesData.length"
:page-size="pageSize"
@current-change="handlePageChange"
class="mt-4"
/>
</div>
<el-empty v-else description="暂无销售数据"></el-empty>
</div>
</el-dialog>
</el-card>
</template>
<script setup>
import { ref, onMounted, nextTick } from 'vue'
import axios from 'axios'
import { ElMessage } from 'element-plus'
import Chart from 'chart.js/auto';
import zoomPlugin from 'chartjs-plugin-zoom';
Chart.register(zoomPlugin);
//
const stores = ref([])
const allProducts = ref([])
const salesData = ref([])
const statistics = ref(null)
const loading = ref(true)
//
const pageSize = ref(20)
const currentPage = ref(1)
const total = ref(0)
//
const filters = ref({
store_id: '',
product_id: '',
dateRange: null
})
//
const dialogVisible = ref(false)
const detailLoading = ref(false)
const selectedProduct = ref(null)
const paginatedSalesData = ref([])
const salesChartCanvas = ref(null)
let salesChart = null;
//
const fetchStores = async () => {
try {
const response = await axios.get('/api/stores')
if (response.data.status === 'success') {
stores.value = response.data.data
} else {
ElMessage.error('获取店铺列表失败')
}
} catch (error) {
console.error('获取店铺列表失败:', error)
}
}
//
const fetchProducts = async () => {
try {
const response = await axios.get('/api/products')
if (response.data.status === 'success') {
allProducts.value = response.data.data
} else {
ElMessage.error('获取产品列表失败')
}
} catch (error) {
console.error('获取产品列表失败:', error)
}
}
//
const fetchSalesData = async () => {
try {
loading.value = true
//
const params = {
page: currentPage.value,
page_size: pageSize.value
}
if (filters.value.store_id) {
params.store_id = filters.value.store_id
}
if (filters.value.product_id) {
params.product_id = filters.value.product_id
}
if (filters.value.dateRange && filters.value.dateRange.length === 2) {
params.start_date = filters.value.dateRange[0]
params.end_date = filters.value.dateRange[1]
}
const response = await axios.get('/api/sales/data', { params })
if (response.data.status === 'success') {
salesData.value = response.data.data
total.value = response.data.total || 0
statistics.value = response.data.statistics
} else {
ElMessage.error('获取销售数据失败')
salesData.value = []
total.value = 0
statistics.value = null
}
} catch (error) {
ElMessage.error('请求销售数据时出错')
console.error(error)
salesData.value = []
total.value = 0
statistics.value = null
} finally {
loading.value = false
}
}
//
const handleFilterChange = () => {
currentPage.value = 1
fetchSalesData()
}
//
const handlePageChange = (page) => {
currentPage.value = page
fetchSalesData()
}
//
const handleSizeChange = (size) => {
pageSize.value = size
currentPage.value = 1
fetchSalesData()
}
//
const viewStoreDetails = async (storeId) => {
try {
const response = await axios.get(`/api/stores/${storeId}`)
if (response.data.status === 'success') {
const store = response.data.data
ElMessage.info(`店铺:${store.store_name},位置:${store.location},类型:${store.type}`)
}
} catch (error) {
ElMessage.error('获取店铺详情失败')
}
}
//
const handleUpload = async (options) => {
const formData = new FormData()
formData.append('file', options.file)
try {
const response = await axios.post('/api/data/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
if (response.data.status === 'success') {
ElMessage.success('数据上传成功')
await fetchStores()
await fetchProducts()
await fetchSalesData()
} else {
ElMessage.error(response.data.message || '数据上传失败')
}
} catch (error) {
ElMessage.error('数据上传请求失败')
console.error(error)
}
}
const viewDetails = async (product) => {
selectedProduct.value = product;
dialogVisible.value = true;
detailLoading.value = true;
try {
const response = await axios.get(`/api/products/${product.product_id}/sales`);
if (response.data.status === 'success') {
salesData.value = response.data.data;
handlePageChange(1); // Show first page
await nextTick();
renderChart();
} else {
ElMessage.error('获取销售详情失败');
salesData.value = [];
}
} catch (error) {
ElMessage.error('请求销售详情时出错');
salesData.value = [];
} finally {
detailLoading.value = false;
}
}
// handlePageChange
const renderChart = () => {
if (salesChart) {
salesChart.destroy();
}
if (!salesChartCanvas.value || salesData.value.length === 0) return;
const labels = salesData.value.map(d => d.date);
const data = salesData.value.map(d => d.sales);
salesChart = new Chart(salesChartCanvas.value, {
type: 'line',
data: {
labels,
datasets: [{
label: '每日销量',
data,
borderColor: '#409EFF',
tension: 0.1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
tooltip: {
callbacks: {
title: function(context) {
return `日期: ${context[0].label}`;
},
label: (context) => {
const label = context.dataset.label || '';
const value = context.parsed.y;
const fullData = salesData.value[context.dataIndex];
let tooltipText = `${label}: ${value}`;
if (fullData) {
tooltipText += ` | 温度: ${fullData.temperature}°C`;
}
return tooltipText;
}
}
},
zoom: {
pan: {
enabled: true,
mode: 'x',
},
zoom: {
wheel: {
enabled: true,
},
pinch: {
enabled: true
},
mode: 'x',
}
}
}
}
});
}
//
onMounted(async () => {
await fetchStores()
await fetchProducts()
await fetchSalesData()
})
</script>
<style scoped>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.filter-section {
padding: 20px;
background-color: #f8f9fa;
border-radius: 8px;
margin-bottom: 20px;
}
.statistics-section {
padding: 20px;
background-color: #f0f9ff;
border-radius: 8px;
border: 1px solid #e0f2fe;
}
.mt-4 {
margin-top: 24px;
}
.chart-container {
width: 100%;
height: 400px;
margin-bottom: 20px;
}
.el-statistic {
text-align: center;
}
.el-table .el-table__cell {
padding: 8px 0;
}
.filter-section .el-row {
align-items: center;
}
.filter-section .el-col {
margin-bottom: 10px;
}
@media (max-width: 768px) {
.filter-section .el-col {
margin-bottom: 15px;
}
.statistics-section .el-col {
margin-bottom: 15px;
}
}
</style>

View File

@ -636,4 +636,28 @@
---
**日期**: 2025-07-14
**主题**: UI导航栏重构
### 描述
根据用户请求,对左侧功能导航栏进行了调整。
### 主要改动
1. **删除“数据管理”**:
* 从 `UI/src/App.vue` 的导航菜单中移除了“数据管理”项。
* 从 `UI/src/router/index.js` 中删除了对应的 `/data` 路由。
* 删除了视图文件 `UI/src/views/DataView.vue`
2. **提升“店铺管理”**:
* 将“店铺管理”菜单项在 `UI/src/App.vue` 中的位置提升,以填补原“数据管理”的位置,使其在导航中更加突出。
### 涉及文件
* `UI/src/App.vue`
* `UI/src/router/index.js`
* `UI/src/views/DataView.vue` (已删除)
**按药品模型预测**