销售看板和供货看板
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="renderer" content="webkit">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<link rel="icon" href="/favicon.png">
|
||||
<title>创高家具销售系统</title>
|
||||
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
|
||||
<style>
|
||||
|
||||
BIN
gear-ui3/public/favicon.png
Normal file
BIN
gear-ui3/public/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
@@ -5,7 +5,7 @@
|
||||
<el-tab-pane
|
||||
v-for="menu in filteredMenus"
|
||||
:key="menu.path"
|
||||
:label="menu.meta.title"
|
||||
:label="menu.meta?.title"
|
||||
:name="menu.path"
|
||||
>
|
||||
<div class="app-grid">
|
||||
@@ -20,7 +20,7 @@
|
||||
<svg-icon :icon-class="child.meta.icon || 'documentation'" class="app-icon" />
|
||||
</div>
|
||||
<!-- 文字区域 -->
|
||||
<span class="app-name">{{ child.meta.title }}</span>
|
||||
<span class="app-name">{{ child.meta?.title }}</span>
|
||||
|
||||
<!-- 三点菜单区域 -->
|
||||
<div class="app-actions">
|
||||
|
||||
481
gear-ui3/src/views/oms/dashboard/index.vue
Normal file
481
gear-ui3/src/views/oms/dashboard/index.vue
Normal file
@@ -0,0 +1,481 @@
|
||||
<template>
|
||||
<div class="sales-dashboard">
|
||||
<el-loading v-if="loading" fullscreen text="数据加载中..." />
|
||||
|
||||
<!-- 指标卡区域 -->
|
||||
<el-row :gutter="20" class="mb-6">
|
||||
<el-col :span="6">
|
||||
<el-card class="stat-card">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<p class="text-gray-500 text-sm">总客户数</p>
|
||||
<h3 class="text-2xl font-bold">{{ totalCustomers }}</h3>
|
||||
</div>
|
||||
<el-icon class="text-primary text-xl"><UserFilled /></el-icon>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card class="stat-card">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<p class="text-gray-500 text-sm">总订单数</p>
|
||||
<h3 class="text-2xl font-bold">{{ totalOrders }}</h3>
|
||||
</div>
|
||||
<el-icon class="text-success text-xl"><ShoppingCart /></el-icon>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card class="stat-card">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<p class="text-gray-500 text-sm">总销售额</p>
|
||||
<h3 class="text-2xl font-bold">¥{{ totalSales.toFixed(2) }}</h3>
|
||||
</div>
|
||||
<el-icon class="text-warning text-xl"><Money /></el-icon>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card class="stat-card">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<p class="text-gray-500 text-sm">退换货率</p>
|
||||
<h3 class="text-2xl font-bold">{{ returnExchangeRate.toFixed(2) }}%</h3>
|
||||
</div>
|
||||
<el-icon class="text-danger text-xl"><RefreshRight /></el-icon>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 图表区域 -->
|
||||
<el-row :gutter="20" class="mb-6">
|
||||
<el-col :span="12">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span>订单趋势分析</span>
|
||||
<el-select v-model="orderTrendRange" style="width: 100px;" placeholder="时间范围" size="small" @change="renderOrderTrendChart">
|
||||
<el-option label="近7天" value="7" />
|
||||
<el-option label="近30天" value="30" />
|
||||
<el-option label="近90天" value="90" />
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
<div ref="orderTrendChart" class="chart-container" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span>产品销售排行</span>
|
||||
<el-select v-model="productRankType" style="width: 100px;" placeholder="统计类型" size="small" @change="renderProductRankChart">
|
||||
<el-option label="销量" value="quantity" />
|
||||
<el-option label="销售额" value="amount" />
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
<div ref="productRankChart" class="chart-container" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" class="mb-6">
|
||||
<el-col :span="12">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<span>客户跟进状态分布</span>
|
||||
</template>
|
||||
<div ref="followUpStatusChart" class="chart-container" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<span>订单状态分布</span>
|
||||
</template>
|
||||
<div ref="orderStatusChart" class="chart-container" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 订单明细表 -->
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span>最近订单列表</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-table
|
||||
:data="paginatedOrders"
|
||||
border
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#f5f7fa' }"
|
||||
>
|
||||
<el-table-column prop="orderCode" label="订单编号" />
|
||||
<el-table-column prop="customerName" label="客户名称" />
|
||||
<el-table-column prop="salesManager" label="销售经理" />
|
||||
<el-table-column
|
||||
prop="taxAmount"
|
||||
label="订单金额"
|
||||
align="right"
|
||||
:formatter="(row) => `¥${Number(row.taxAmount).toFixed(2)}`"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="orderStatus"
|
||||
label="订单状态"
|
||||
align="center"
|
||||
>
|
||||
<template #default="scope">
|
||||
<dict-tag :options="order_status" :value="scope.row.orderStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:total="filteredOrders.length"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
class="mt-4 flex justify-end"
|
||||
/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import {
|
||||
UserFilled, ShoppingCart, Money, RefreshRight
|
||||
} from '@element-plus/icons-vue'
|
||||
import { listOrder } from '@/api/wms/order'
|
||||
import { listOrderDetail } from '@/api/wms/orderDetail'
|
||||
import { listReturnExchange } from '@/api/oa/returnExchange'
|
||||
import { listCustomer } from '@/api/wms/customer'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const { order_status } = proxy.useDict("order_status")
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(true)
|
||||
const customers = ref([])
|
||||
const orders = ref([])
|
||||
const orderDetails = ref([])
|
||||
const returnExchanges = ref([])
|
||||
|
||||
// 图表容器引用
|
||||
const orderTrendChart = ref(null)
|
||||
const productRankChart = ref(null)
|
||||
const followUpStatusChart = ref(null)
|
||||
const orderStatusChart = ref(null)
|
||||
|
||||
// 筛选与分页参数
|
||||
const orderTrendRange = ref('30') // 默认近30天
|
||||
const productRankType = ref('quantity') // 默认按销量统计
|
||||
const orderSearch = ref('')
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
|
||||
const followUpStatusLabel = {
|
||||
0: '未跟进',
|
||||
1: '跟进中',
|
||||
2: '已跟进'
|
||||
}
|
||||
|
||||
// 指标计算(computed确保响应式)
|
||||
const totalCustomers = computed(() => customers.value.length)
|
||||
const totalOrders = computed(() => orders.value.length)
|
||||
const totalSales = computed(() => {
|
||||
return orders.value.reduce(
|
||||
(sum, order) => sum + Number(order.taxAmount || 0),
|
||||
0
|
||||
)
|
||||
})
|
||||
const returnExchangeRate = computed(() => {
|
||||
if (totalOrders.value === 0) return 0
|
||||
return (returnExchanges.value.length / totalOrders.value) * 100
|
||||
})
|
||||
|
||||
// 筛选后的订单(支持搜索)
|
||||
const filteredOrders = computed(() => {
|
||||
return orders.value.filter(order =>
|
||||
order.orderCode.includes(orderSearch.value)
|
||||
)
|
||||
})
|
||||
|
||||
// 分页后的订单
|
||||
const paginatedOrders = computed(() => {
|
||||
const start = (currentPage.value - 1) * pageSize.value
|
||||
return filteredOrders.value.slice(start, start + pageSize.value)
|
||||
})
|
||||
|
||||
// 页面加载时初始化数据
|
||||
onMounted(async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
// 并行请求所有数据(提升加载效率)
|
||||
const [customerRes, orderRes, orderDetailRes, returnExchangeRes] = await Promise.all([
|
||||
listCustomer({pageNum: 1, pageSize: 1000}),
|
||||
listOrder({pageNum: 1, pageSize: 1000}),
|
||||
listOrderDetail({pageNum: 1, pageSize: 1000}),
|
||||
listReturnExchange({pageNum: 1, pageSize: 1000})
|
||||
])
|
||||
// 赋值数据(假设接口返回格式为 { data: [...] })
|
||||
customers.value = customerRes.rows || []
|
||||
orders.value = orderRes.rows || []
|
||||
orderDetails.value = orderDetailRes.rows || []
|
||||
returnExchanges.value = returnExchangeRes.rows || []
|
||||
|
||||
// 初始化所有图表
|
||||
renderOrderTrendChart()
|
||||
renderProductRankChart()
|
||||
renderFollowUpStatusChart()
|
||||
renderOrderStatusChart()
|
||||
} catch (error) {
|
||||
console.error('销售数据加载失败:', error)
|
||||
// 可添加全局错误提示
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
// ---------- 图表渲染函数 ----------
|
||||
|
||||
// 订单趋势图(双轴:订单数+销售额)
|
||||
const renderOrderTrendChart = () => {
|
||||
const chartDom = orderTrendChart.value
|
||||
if (!chartDom) return
|
||||
const chart = echarts.init(chartDom)
|
||||
|
||||
// 生成时间范围日期
|
||||
const days = Number(orderTrendRange.value)
|
||||
const dates = []
|
||||
const now = new Date()
|
||||
for (let i = days - 1; i >= 0; i--) {
|
||||
const date = new Date(now)
|
||||
date.setDate(now.getDate() - i)
|
||||
dates.push(date.toLocaleDateString())
|
||||
}
|
||||
|
||||
// 按日期统计订单数和销售额
|
||||
const orderCountMap = {}
|
||||
const salesMap = {}
|
||||
dates.forEach(date => {
|
||||
orderCountMap[date] = 0
|
||||
salesMap[date] = 0
|
||||
})
|
||||
orders.value.forEach(order => {
|
||||
// 假设订单有createTime字段,需与dates格式匹配
|
||||
const orderDate = new Date(order.createTime).toLocaleDateString()
|
||||
if (orderCountMap[orderDate] !== undefined) {
|
||||
orderCountMap[orderDate]++
|
||||
salesMap[orderDate] += Number(order.taxAmount || 0)
|
||||
}
|
||||
})
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'cross' }
|
||||
},
|
||||
legend: { data: ['订单数', '销售额'] },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: dates,
|
||||
axisLabel: { rotate: 30, fontSize: 12 }
|
||||
},
|
||||
yAxis: [
|
||||
{ type: 'value', name: '订单数', min: 0 },
|
||||
{
|
||||
type: 'value',
|
||||
name: '销售额',
|
||||
min: 0,
|
||||
axisLabel: { formatter: '¥{value}' }
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '订单数',
|
||||
type: 'bar',
|
||||
data: dates.map(date => orderCountMap[date]),
|
||||
barWidth: '40%'
|
||||
},
|
||||
{
|
||||
name: '销售额',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
data: dates.map(date => salesMap[date]),
|
||||
smooth: true,
|
||||
lineStyle: { width: 2 }
|
||||
}
|
||||
]
|
||||
}
|
||||
chart.setOption(option)
|
||||
}
|
||||
|
||||
// 产品销售排行图(饼图:支持销量/销售额切换)
|
||||
const renderProductRankChart = () => {
|
||||
const chartDom = productRankChart.value
|
||||
if (!chartDom) return
|
||||
const chart = echarts.init(chartDom)
|
||||
|
||||
// 统计产品数据
|
||||
const productStats = {}
|
||||
orderDetails.value.forEach(detail => {
|
||||
const productName = detail.productName
|
||||
if (!productStats[productName]) {
|
||||
productStats[productName] = {
|
||||
quantity: 0,
|
||||
amount: 0
|
||||
}
|
||||
}
|
||||
productStats[productName].quantity += Number(detail.quantity || 0)
|
||||
productStats[productName].amount +=
|
||||
Number(detail.quantity || 0) * Number(detail.taxPrice || 0)
|
||||
})
|
||||
|
||||
// 转换为图表数据(取Top10)
|
||||
const chartData = Object.entries(productStats)
|
||||
.map(([name, stats]) => ({
|
||||
name,
|
||||
value: productRankType.value === 'quantity'
|
||||
? stats.quantity
|
||||
: stats.amount
|
||||
}))
|
||||
.sort((a, b) => b.value - a.value)
|
||||
.slice(0, 10)
|
||||
|
||||
const option = {
|
||||
tooltip: { trigger: 'item' },
|
||||
series: [
|
||||
{
|
||||
name: productRankType.value === 'quantity' ? '销量' : '销售额',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
data: chartData,
|
||||
label: { formatter: '{b}: {c}' },
|
||||
emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' } }
|
||||
}
|
||||
]
|
||||
}
|
||||
chart.setOption(option)
|
||||
}
|
||||
|
||||
// 客户跟进状态分布图(饼图)
|
||||
const renderFollowUpStatusChart = () => {
|
||||
const chartDom = followUpStatusChart.value
|
||||
if (!chartDom) return
|
||||
const chart = echarts.init(chartDom)
|
||||
|
||||
// 统计跟进状态
|
||||
const statusStats = { 0: 0, 1: 0, 2: 0 }
|
||||
customers.value.forEach(customer => {
|
||||
const status = customer.followUpStatus
|
||||
if (statusStats[status] !== undefined) {
|
||||
statusStats[status]++
|
||||
}
|
||||
})
|
||||
|
||||
// 转换为图表数据
|
||||
const chartData = Object.entries(statusStats).map(([key, value]) => ({
|
||||
name: followUpStatusLabel[key],
|
||||
value
|
||||
}))
|
||||
|
||||
const option = {
|
||||
tooltip: { trigger: 'item' },
|
||||
series: [
|
||||
{
|
||||
name: '客户跟进状态',
|
||||
type: 'pie',
|
||||
radius: '60%',
|
||||
data: chartData,
|
||||
label: { formatter: '{b}: {c} ({d}%)' }
|
||||
}
|
||||
]
|
||||
}
|
||||
chart.setOption(option)
|
||||
}
|
||||
|
||||
// 订单状态分布图(饼图)
|
||||
const renderOrderStatusChart = () => {
|
||||
const chartDom = orderStatusChart.value
|
||||
if (!chartDom) return
|
||||
const chart = echarts.init(chartDom)
|
||||
|
||||
// 统计订单状态
|
||||
const statusStats = {}
|
||||
orders.value.forEach(order => {
|
||||
const status = order.orderStatus
|
||||
if (!statusStats[status]) {
|
||||
statusStats[status] = 0
|
||||
}
|
||||
statusStats[status]++
|
||||
})
|
||||
|
||||
// 转换为图表数据
|
||||
const chartData = Object.entries(statusStats).map(([key, value]) => ({
|
||||
name: key,
|
||||
value
|
||||
}))
|
||||
|
||||
const option = {
|
||||
tooltip: { trigger: 'item' },
|
||||
series: [
|
||||
{
|
||||
name: '订单状态',
|
||||
type: 'pie',
|
||||
radius: '60%',
|
||||
data: chartData,
|
||||
label: { formatter: '{b}: {c} ({d}%)' }
|
||||
}
|
||||
]
|
||||
}
|
||||
chart.setOption(option)
|
||||
}
|
||||
|
||||
// ---------- 分页与操作函数 ----------
|
||||
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
}
|
||||
const handleCurrentChange = (val) => {
|
||||
currentPage.value = val
|
||||
}
|
||||
|
||||
const viewOrderDetail = (row) => {
|
||||
console.log('查看订单详情:', row)
|
||||
// 可跳转至订单详情页,例:router.push(`/order/detail/${row.orderId}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-row {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.sales-dashboard {
|
||||
padding: 16px;
|
||||
}
|
||||
.stat-card {
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.stat-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
</style>
|
||||
715
gear-ui3/src/views/purchase/dashboard/index.vue
Normal file
715
gear-ui3/src/views/purchase/dashboard/index.vue
Normal file
@@ -0,0 +1,715 @@
|
||||
<template>
|
||||
<!-- 根容器:移除Tailwind,使用Element原生布局 -->
|
||||
<div class="page-container" v-loading="loading" element-loading-text="正在加载数据...">
|
||||
<!-- 错误提示 -->
|
||||
<el-alert
|
||||
v-if="errorMsg"
|
||||
title="数据加载失败"
|
||||
:description="errorMsg"
|
||||
type="error"
|
||||
show-icon
|
||||
style="margin-bottom: 20px;"
|
||||
/>
|
||||
|
||||
<!-- 页面标题:补充基础样式 -->
|
||||
<div style="font-size: 18px; font-weight: 500; margin-bottom: 20px;">
|
||||
数据分析页面
|
||||
</div>
|
||||
|
||||
<!-- 主内容区(数据加载成功才显示) -->
|
||||
<div v-if="!loading && !errorMsg">
|
||||
<!-- 第一行:四个指标卡 -->
|
||||
<el-row :gutter="20" style="margin-bottom: 20px;">
|
||||
<el-col :span="6">
|
||||
<el-card class="stat-card" :body-style="{ padding: '15px' }">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<p style="color: #666; font-size: 12px;">总供应商数</p>
|
||||
<h3 style="font-size: 20px; font-weight: bold; margin-top: 5px;">{{ totalSuppliers }}</h3>
|
||||
</div>
|
||||
<div style="width: 48px; height: 48px; border-radius: 50%; background: #e6f4ff; display: flex; align-items: center; justify-content: center; color: #409eff;">
|
||||
<el-icon><user /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 8px; font-size: 12px; color: #666;">
|
||||
<span :class="supplierTrendClass">
|
||||
<el-icon v-if="supplierTrend > 0"><arrow-up /></el-icon>
|
||||
<el-icon v-if="supplierTrend < 0"><arrow-down /></el-icon>
|
||||
{{ Math.abs(supplierTrend) }}%
|
||||
</span>
|
||||
<span> 较上月</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="6">
|
||||
<el-card class="stat-card" :body-style="{ padding: '15px' }">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<p style="color: #666; font-size: 12px;">总供货类型</p>
|
||||
<h3 style="font-size: 20px; font-weight: bold; margin-top: 5px;">{{ totalSupplyTypes }}</h3>
|
||||
</div>
|
||||
<div style="width: 48px; height: 48px; border-radius: 50%; background: #f0f9eb; display: flex; align-items: center; justify-content: center; color: #67c23a;">
|
||||
<el-icon><goods /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="6">
|
||||
<el-card class="stat-card" :body-style="{ padding: '15px' }">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<p style="color: #666; font-size: 12px;">总采购金额</p>
|
||||
<h3 style="font-size: 20px; font-weight: bold; margin-top: 5px;">¥{{ totalPurchaseAmount.toLocaleString() }}</h3>
|
||||
</div>
|
||||
<div style="width: 48px; height: 48px; border-radius: 50%; background: #f9f0ff; display: flex; align-items: center; justify-content: center; color: #9400d3;">
|
||||
<el-icon><shoppingCart /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 8px; font-size: 12px; color: #666;">
|
||||
<span :class="amountTrendClass">
|
||||
<el-icon v-if="amountTrend > 0"><arrow-up /></el-icon>
|
||||
<el-icon v-if="amountTrend < 0"><arrow-down /></el-icon>
|
||||
{{ Math.abs(amountTrend) }}%
|
||||
</span>
|
||||
<span> 较上月</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="6">
|
||||
<el-card class="stat-card" :body-style="{ padding: '15px' }">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<p style="color: #666; font-size: 12px;">已完成计划</p>
|
||||
<h3 style="font-size: 20px; font-weight: bold; margin-top: 5px;">{{ completedPlans }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 8px; font-size: 12px; color: #666;">
|
||||
<span style="color: #67c23a;">
|
||||
{{ completionRate }}% 完成率
|
||||
</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 第二行:两个图表 -->
|
||||
<el-row :gutter="20" style="margin-bottom: 20px;">
|
||||
<el-col :span="12">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span>供货类型分类占比</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 图表容器:确保固定高度,避免图表不显示 -->
|
||||
<div class="chart-container">
|
||||
<div ref="typePieChart" style="width: 100%; height: 320px;"></div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="font-size: 16px; font-weight: bold;">采购计划趋势</div>
|
||||
<el-select v-model="planChartTimeRange" style="width: 100px;" placeholder="选择时间范围" size="small" @change="initPlanLineChart">
|
||||
<el-option label="近7天" value="7days" />
|
||||
<el-option label="近30天" value="30days" />
|
||||
<el-option label="近90天" value="90days" />
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
<div class="chart-container">
|
||||
<div ref="planLineChart" style="width: 100%; height: 320px; border: 1px solid #eee; border-radius: 4px;"></div>
|
||||
</div>
|
||||
<div style="margin-top: 10px; font-size: 12px; color: #999;">
|
||||
数据状态:采购计划{{ purchasePlans.length }}条 / 状态字典{{ purchase_status.length }}种
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 第三行:采购计划表格 -->
|
||||
<el-row style="margin-bottom: 20px;">
|
||||
<el-col :span="24">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<!-- 表格头部:替换flex-wrap为自适应布局 -->
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px;">
|
||||
<span>采购计划列表</span>
|
||||
<div style="display: flex; gap: 10px; align-items: center;">
|
||||
<el-select v-model="statusFilter" placeholder="筛选状态" size="small">
|
||||
<el-option label="全部状态" value="" />
|
||||
<el-option
|
||||
v-for="(item, index) in purchase_status"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索材料名称/供应商"
|
||||
size="small"
|
||||
:prefix-icon="Search"
|
||||
style="width: 200px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-table
|
||||
:data="paginatedPlans"
|
||||
border
|
||||
style="width: 100%; height: 500px;"
|
||||
:header-cell-style="{ background: '#f5f7fa' }"
|
||||
v-loading="tableLoading"
|
||||
>
|
||||
<el-table-column
|
||||
prop="detailCode"
|
||||
label="计划编号"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="rawMaterialName"
|
||||
label="材料名称"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="supplierName"
|
||||
label="供应商"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="quantity"
|
||||
label="采购数量"
|
||||
align="right"
|
||||
:formatter="(row) => `${row.quantity} ${row.unit || ''}`"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="unitPrice"
|
||||
label="单价"
|
||||
align="right"
|
||||
:formatter="(row) => `¥${Number(row.unitPrice).toFixed(2)}`"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="totalAmount"
|
||||
label="总金额"
|
||||
align="right"
|
||||
:formatter="(row) => `¥${Number(row.totalAmount).toFixed(2)}`"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="owner"
|
||||
label="负责人"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="status"
|
||||
label="状态"
|
||||
align="center"
|
||||
>
|
||||
<template #default="scope">
|
||||
<!-- 确保DictTag组件正确导入 -->
|
||||
<dict-tag
|
||||
:options="purchase_status"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column
|
||||
label="操作"
|
||||
width="180"
|
||||
align="center"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
size="small"
|
||||
type="text"
|
||||
@click="handleViewDetail(scope.row)"
|
||||
>
|
||||
查看详情
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="text"
|
||||
v-if="scope.row.annex"
|
||||
@click="handleDownloadAnnex(scope.row)"
|
||||
>
|
||||
下载附件
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column> -->
|
||||
</el-table>
|
||||
|
||||
<!-- 分页:固定右对齐 -->
|
||||
<div style="display: flex; justify-content: flex-end; margin-top: 16px;">
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:total="filteredPurchasePlans.length"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 1. 补充缺失的Vue核心API导入(关键:之前未导入导致响应式失效)
|
||||
import { ref, computed, onMounted, watch, getCurrentInstance, onUnmounted } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
// 2. 导入Element图标和组件
|
||||
import {
|
||||
User, Goods, ShoppingCart,
|
||||
ArrowUp, ArrowDown, Search
|
||||
} from '@element-plus/icons-vue'
|
||||
import { ElMessage, ElAlert } from 'element-plus'
|
||||
|
||||
// 4. 导入API(修正拼写:listPurchasePlanDetail)
|
||||
import { listSupplier } from '@/api/oa/supplier'
|
||||
import { listSupplyType } from '@/api/oa/supplyType'
|
||||
import { listPurchasePlanDetail } from '@/api/oa/purchasePlanDetail'
|
||||
|
||||
// 5. 获取当前实例和字典数据(确保proxy存在)
|
||||
const { proxy } = getCurrentInstance()
|
||||
// 获取采购状态字典(容错:避免字典未加载导致报错)
|
||||
const { purchase_status = [] } = proxy?.useDict('purchase_status') || {}
|
||||
|
||||
// 6. 状态管理(初始化默认值,避免undefined)
|
||||
const loading = ref(true)
|
||||
const tableLoading = ref(false)
|
||||
const errorMsg = ref('')
|
||||
const suppliers = ref([])
|
||||
const supplyTypes = ref([])
|
||||
const purchasePlans = ref([])
|
||||
|
||||
// 图表相关(初始化ref为空)
|
||||
const typePieChart = ref(null)
|
||||
const planLineChart = ref(null)
|
||||
const typeChartTimeRange = ref('month')
|
||||
const planChartTimeRange = ref('30days')
|
||||
|
||||
// 表格相关
|
||||
const searchKeyword = ref('')
|
||||
const statusFilter = ref('')
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
|
||||
// 7. 指标卡数据(容错处理:避免数组空值)
|
||||
const totalSuppliers = computed(() => suppliers.value?.length || 0)
|
||||
const totalSupplyTypes = computed(() => supplyTypes.value?.length || 0)
|
||||
const totalPurchasePlans = computed(() => purchasePlans.value?.length || 0)
|
||||
const totalPurchaseAmount = computed(() => {
|
||||
return purchasePlans.value?.reduce((sum, plan) => {
|
||||
return sum + Number(plan.totalAmount || 0)
|
||||
}, 0) || 0
|
||||
})
|
||||
const completedPlans = computed(() => {
|
||||
const completedValue = 4
|
||||
return purchasePlans.value?.filter(plan => plan.status === completedValue).length || 0
|
||||
})
|
||||
|
||||
// 趋势数据(模拟)
|
||||
const supplierTrend = ref(5.2)
|
||||
const typeTrend = ref(-2.1)
|
||||
const amountTrend = ref(8.7)
|
||||
|
||||
// 完成率(容错:避免除以0)
|
||||
const completionRate = computed(() => {
|
||||
if (totalPurchasePlans.value === 0) return 0
|
||||
return Math.round((completedPlans.value / totalPurchasePlans.value) * 100)
|
||||
})
|
||||
|
||||
// 趋势样式(computed正常工作)
|
||||
const supplierTrendClass = computed(() => {
|
||||
return supplierTrend.value > 0 ? 'color: #67c23a;' : 'color: #f56c6c;'
|
||||
})
|
||||
const typeTrendClass = computed(() => {
|
||||
return typeTrend.value > 0 ? 'color: #67c23a;' : 'color: #f56c6c;'
|
||||
})
|
||||
const amountTrendClass = computed(() => {
|
||||
return amountTrend.value > 0 ? 'color: #67c23a;' : 'color: #f56c6c;'
|
||||
})
|
||||
|
||||
// 8. 筛选后的采购计划(容错:处理空数组)
|
||||
const filteredPurchasePlans = computed(() => {
|
||||
const plans = purchasePlans.value || []
|
||||
return plans.filter(plan => {
|
||||
// 状态筛选(容错:statusFilter为空时不筛选)
|
||||
if (statusFilter.value !== '' && plan.status !== Number(statusFilter.value)) {
|
||||
return false
|
||||
}
|
||||
// 关键词搜索(容错:处理null/undefined)
|
||||
if (searchKeyword.value) {
|
||||
const keyword = searchKeyword.value.toLowerCase()
|
||||
const materialName = (plan.rawMaterialName || '').toLowerCase()
|
||||
const supplierName = (plan.supplierName || '').toLowerCase()
|
||||
return materialName.includes(keyword) || supplierName.includes(keyword)
|
||||
}
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
// 分页数据(容错:避免slice越界)
|
||||
const paginatedPlans = computed(() => {
|
||||
const plans = filteredPurchasePlans.value || []
|
||||
const startIndex = (currentPage.value - 1) * pageSize.value
|
||||
return plans.slice(startIndex, startIndex + pageSize.value)
|
||||
})
|
||||
|
||||
// 9. 页面加载逻辑(关键:修复异步错误处理)
|
||||
onMounted(async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
console.log('开始获取数据')
|
||||
|
||||
// 并行调用API(带分页参数,确保获取全量数据)
|
||||
const [supplierRes, typeRes, planRes] = await Promise.all([
|
||||
listSupplier({ pageNum: 1, pageSize: 1000 }),
|
||||
listSupplyType({ pageNum: 1, pageSize: 1000 }),
|
||||
listPurchasePlanDetail({ pageNum: 1, pageSize: 1000 })
|
||||
])
|
||||
|
||||
console.log('API返回数据:', supplierRes, typeRes, planRes)
|
||||
|
||||
// 处理返回数据(容错:检查code和rows字段)
|
||||
if (supplierRes?.code === 200) {
|
||||
suppliers.value = supplierRes.rows || []
|
||||
} else {
|
||||
throw new Error(`供应商数据错误:${supplierRes?.msg || '未知错误'}`)
|
||||
}
|
||||
if (typeRes?.code === 200) {
|
||||
supplyTypes.value = typeRes.rows || []
|
||||
} else {
|
||||
throw new Error(`供货类型数据错误:${typeRes?.msg || '未知错误'}`)
|
||||
}
|
||||
if (planRes?.code === 200) {
|
||||
purchasePlans.value = planRes.rows || []
|
||||
} else {
|
||||
throw new Error(`采购计划数据错误:${planRes?.msg || '未知错误'}`)
|
||||
}
|
||||
|
||||
// 初始化图表(确保DOM已渲染)
|
||||
setTimeout(() => {
|
||||
initTypePieChart()
|
||||
initPlanLineChart()
|
||||
}, 100)
|
||||
|
||||
} catch (error) {
|
||||
console.error('页面加载失败:', error)
|
||||
errorMsg.value = error.message || '数据加载失败,请刷新页面重试'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
// 10. 初始化供货类型饼图(容错:避免DOM未加载)
|
||||
const initTypePieChart = () => {
|
||||
if (!typePieChart.value || !purchasePlans.value.length) return
|
||||
|
||||
const chart = echarts.init(typePieChart.value)
|
||||
// 清除已有图表(避免重复渲染)
|
||||
chart.clear()
|
||||
|
||||
// 处理数据:按供应商类型统计采购金额
|
||||
const typeMap = {}
|
||||
purchasePlans.value.forEach(plan => {
|
||||
const supplier = suppliers.value.find(s => s.supplierId === plan.supplierId)
|
||||
const typeName = supplier?.typeName || '其他'
|
||||
typeMap[typeName] = (typeMap[typeName] || 0) + Number(plan.totalAmount || 0)
|
||||
})
|
||||
|
||||
const chartData = Object.entries(typeMap).map(([name, value]) => ({ name, value }))
|
||||
|
||||
// 图表配置(简化配置,确保显示)
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a}<br/>{b}: ¥{c} ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 10,
|
||||
data: chartData.map(item => item.name),
|
||||
textStyle: { fontSize: 12 }
|
||||
},
|
||||
series: [{
|
||||
name: '采购金额',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
data: chartData,
|
||||
itemStyle: { borderRadius: 8 },
|
||||
label: { show: false },
|
||||
labelLine: { show: false }
|
||||
}]
|
||||
}
|
||||
|
||||
chart.setOption(option)
|
||||
|
||||
// 窗口resize监听(添加防抖)
|
||||
const handleResize = debounce(() => chart.resize(), 100)
|
||||
window.addEventListener('resize', handleResize)
|
||||
|
||||
// 组件卸载时清理
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
chart.dispose() // 销毁图表,避免内存泄漏
|
||||
})
|
||||
}
|
||||
|
||||
// 11. 初始化采购计划折线图(修复类型匹配与日期逻辑)
|
||||
const initPlanLineChart = async () => {
|
||||
try {
|
||||
// 1. 确认DOM元素存在
|
||||
console.log('折线图DOM元素:', planLineChart.value);
|
||||
if (!planLineChart.value) {
|
||||
console.error('折线图DOM容器不存在!');
|
||||
ElMessage.warning('图表容器未加载,请刷新页面重试');
|
||||
return;
|
||||
}
|
||||
|
||||
const purchase_status = [
|
||||
{ label: '待提交', value: 0 },
|
||||
{ label: '在途', value: 1 },
|
||||
{ label: '到货', value: 2 },
|
||||
{ label: '待审核', value: 3 },
|
||||
{ label: '已完成', value: 4 }
|
||||
]
|
||||
// 2. 确认核心数据存在
|
||||
console.log('采购计划数据:', purchasePlans.value);
|
||||
console.log('采购状态字典:', purchase_status);
|
||||
if (!purchasePlans.value.length) {
|
||||
console.warn('无采购计划数据,无法渲染折线图');
|
||||
const chart = echarts.init(planLineChart.value);
|
||||
chart.setOption({
|
||||
tooltip: { trigger: 'axis' },
|
||||
legend: { data: [] },
|
||||
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||||
xAxis: { type: 'category', data: [] },
|
||||
yAxis: { type: 'value', min: 0 },
|
||||
series: [],
|
||||
graphic: {
|
||||
type: 'text',
|
||||
left: 'center',
|
||||
top: 'center',
|
||||
style: { text: '暂无采购计划数据', fill: '#999', fontSize: 14 }
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 确保echarts实例正确
|
||||
let chart = echarts.getInstanceByDom(planLineChart.value);
|
||||
if (!chart) chart = echarts.init(planLineChart.value);
|
||||
chart.clear();
|
||||
|
||||
// 4. 生成日期范围(统一格式:年-月-日)
|
||||
const getDateRange = (rangeType) => {
|
||||
const dates = [];
|
||||
const now = new Date();
|
||||
let days = 7;
|
||||
if (rangeType === '30days') days = 30;
|
||||
if (rangeType === '90days') days = 90;
|
||||
|
||||
for (let i = days - 1; i >= 0; i--) {
|
||||
const date = new Date(now);
|
||||
date.setDate(now.getDate() - i);
|
||||
const formatDate = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;
|
||||
dates.push(formatDate);
|
||||
}
|
||||
console.log('生成的日期范围:', dates);
|
||||
return dates;
|
||||
};
|
||||
const dateRange = getDateRange(planChartTimeRange.value);
|
||||
|
||||
// 5. 初始化日期-状态映射(统一状态为数字类型)
|
||||
const dateMap = {};
|
||||
dateRange.forEach(date => {
|
||||
dateMap[date] = {};
|
||||
purchase_status.forEach(status => {
|
||||
const statusNum = Number(status.value); // 字典value转数字
|
||||
dateMap[date][statusNum] = 0; // 用数字作为键
|
||||
});
|
||||
});
|
||||
|
||||
// 6. 填充采购计划数据(状态转数字匹配)
|
||||
purchasePlans.value.forEach(plan => {
|
||||
let planDate = '';
|
||||
// 处理计划日期
|
||||
if (plan.createTime) {
|
||||
try {
|
||||
const createDate = new Date(plan.createTime);
|
||||
planDate = `${createDate.getFullYear()}-${(createDate.getMonth() + 1).toString().padStart(2, '0')}-${createDate.getDate().toString().padStart(2, '0')}`;
|
||||
} catch (e) {
|
||||
console.warn(`计划${plan.detailId}的createTime格式错误:`, plan.createTime);
|
||||
planDate = new Date().toISOString().split('T')[0];
|
||||
}
|
||||
} else {
|
||||
console.warn(`计划${plan.detailId}无createTime,使用当前日期`);
|
||||
planDate = new Date().toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
// 统计金额(状态转数字匹配)
|
||||
if (dateMap[planDate] && plan.status !== undefined) {
|
||||
const planStatusNum = Number(plan.status); // 计划status转数字
|
||||
const amount = Number(plan.totalAmount || 0);
|
||||
if (dateMap[planDate].hasOwnProperty(planStatusNum)) {
|
||||
dateMap[planDate][planStatusNum] += amount;
|
||||
console.log(`计划${plan.detailId}:${planDate} / 状态${planStatusNum} / 金额${amount}`);
|
||||
} else {
|
||||
console.warn(`计划${plan.detailId}的状态${plan.status}(转数字为${planStatusNum})不在字典中,跳过统计`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 7. 生成图表系列数据(基于数字状态)
|
||||
const series = purchase_status.map(status => {
|
||||
const statusNum = Number(status.value); // 字典value转数字
|
||||
const data = dateRange.map(date => {
|
||||
return dateMap[date][statusNum] || 0; // 容错:默认0
|
||||
});
|
||||
console.log(`状态${status.label}(值${statusNum})的数据:`, data);
|
||||
return {
|
||||
name: status.label,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: data,
|
||||
itemStyle: { lineStyle: { width: 2 } } // 加粗线条便于观察
|
||||
};
|
||||
});
|
||||
|
||||
// 8. 图表配置与渲染
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: '{b}<br/>{a}:¥{c}',
|
||||
backgroundColor: 'rgba(255,255,255,0.9)',
|
||||
borderColor: '#eee',
|
||||
borderWidth: 1
|
||||
},
|
||||
legend: {
|
||||
data: purchase_status.map(s => s.label),
|
||||
top: 0,
|
||||
left: 'center',
|
||||
textStyle: { fontSize: 12 },
|
||||
itemWidth: 10,
|
||||
itemHeight: 10
|
||||
},
|
||||
grid: {
|
||||
left: '5%',
|
||||
right: '5%',
|
||||
bottom: '15%',
|
||||
top: '20%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: dateRange,
|
||||
axisLabel: {
|
||||
fontSize: 11,
|
||||
rotate: 45,
|
||||
interval: 0
|
||||
},
|
||||
axisLine: { lineStyle: { color: '#eee' } }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
min: 0,
|
||||
axisLabel: {
|
||||
formatter: '¥{value}',
|
||||
fontSize: 11
|
||||
},
|
||||
splitLine: { lineStyle: { color: '#f5f5f5' } }
|
||||
},
|
||||
series: series,
|
||||
graphic: {
|
||||
type: 'text',
|
||||
left: 'center',
|
||||
top: '10%',
|
||||
style: { text: '图表加载中...', fill: '#666', fontSize: 12 },
|
||||
silent: true
|
||||
}
|
||||
};
|
||||
chart.setOption(option);
|
||||
console.log('折线图配置已生效:', option);
|
||||
|
||||
// 9. 窗口resize监听与清理
|
||||
const handleResize = debounce(() => {
|
||||
console.log('窗口resize,重绘折线图');
|
||||
chart.resize();
|
||||
}, 100);
|
||||
window.addEventListener('resize', handleResize);
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
chart.dispose();
|
||||
console.log('折线图已销毁');
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('折线图初始化失败:', error);
|
||||
ElMessage.error(`图表加载失败:${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 12. 工具函数:防抖(避免频繁resize)
|
||||
const debounce = (fn, delay = 100) => {
|
||||
let timer = null
|
||||
return (...args) => {
|
||||
clearTimeout(timer)
|
||||
timer = setTimeout(() => fn.apply(this, args), delay)
|
||||
}
|
||||
}
|
||||
|
||||
// 分页事件
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
currentPage.value = 1 // 重置页码
|
||||
}
|
||||
const handleCurrentChange = (val) => {
|
||||
currentPage.value = val
|
||||
}
|
||||
|
||||
// 监听筛选条件变化,重置分页
|
||||
watch([searchKeyword, statusFilter], () => {
|
||||
currentPage.value = 1
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 13. 基础样式:移除Tailwind,使用原生CSS */
|
||||
.page-container {
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 修复图标垂直居中 */
|
||||
.el-icon {
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
@@ -91,6 +91,22 @@
|
||||
<el-table-column label="单价" align="center" prop="unitPrice" />
|
||||
<el-table-column label="总金额" align="center" prop="totalAmount" />
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<el-select
|
||||
v-model="scope.row.status"
|
||||
placeholder="请选择状态"
|
||||
@change="handleStatusChange(scope.row)"
|
||||
>
|
||||
<el-option
|
||||
v-for="value in purchase_status"
|
||||
:value="parseInt(value.value)"
|
||||
:label="value.label"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
|
||||
@@ -138,9 +154,9 @@
|
||||
<el-form-item label="单价" prop="unitPrice">
|
||||
<el-input v-model="form.unitPrice" placeholder="请输入单价" />
|
||||
</el-form-item>
|
||||
<el-form-item label="总金额" prop="totalAmount">
|
||||
<!-- <el-form-item label="总金额" prop="totalAmount">
|
||||
<el-input v-model="form.totalAmount" placeholder="请输入总金额" />
|
||||
</el-form-item>
|
||||
</el-form-item> -->
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
@@ -161,6 +177,8 @@ import { listSupplier } from "@/api/oa/supplier";
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const { purchase_status } = proxy.useDict('purchase_status')
|
||||
|
||||
const purchasePlanDetailList = ref([]);
|
||||
const open = ref(false);
|
||||
const buttonLoading = ref(false);
|
||||
@@ -176,6 +194,14 @@ const formatterTime = (time) => {
|
||||
return proxy.parseTime(time, '{y}-{m}-{d}')
|
||||
}
|
||||
|
||||
const handleStatusChange = (row) => {
|
||||
const { totalAmount, ...payload } = row;
|
||||
updatePurchasePlanDetail(payload).then(response => {
|
||||
proxy.$modal.msgSuccess("修改成功");
|
||||
getList();
|
||||
});
|
||||
}
|
||||
|
||||
const data = reactive({
|
||||
form: {},
|
||||
queryParams: {
|
||||
|
||||
Reference in New Issue
Block a user