<template>
|
<div class="app-container">
|
<!-- 搜索表单 -->
|
<el-form ref="queryForm" :model="queryParams" :inline="true" size="small">
|
<el-form-item label="委托编号" prop="entrustCode">
|
<el-input v-model="queryParams.entrustCode" placeholder="请输入委托编号" clearable style="width: 200px" @keyup.enter.native="handleQuery" />
|
</el-form-item>
|
<el-form-item label="样品编号" prop="sampleCode">
|
<el-input v-model="queryParams.sampleCode" placeholder="请输入样品编号" clearable style="width: 200px" @keyup.enter.native="handleQuery" />
|
</el-form-item>
|
<el-form-item label="样品名称" prop="sampleName">
|
<el-input v-model="queryParams.sampleName" placeholder="请输入样品名称" clearable style="width: 200px" @keyup.enter.native="handleQuery" />
|
</el-form-item>
|
<el-form-item label="检测状态" prop="insState">
|
<el-select v-model="queryParams.insState" placeholder="请选择检测状态" clearable style="width: 150px">
|
<el-option label="待检" :value="0" />
|
<el-option label="检验中" :value="1" />
|
<el-option label="已检验" :value="2" />
|
<el-option label="待审核" :value="3" />
|
<el-option label="审核未通过" :value="4" />
|
<el-option label="审核通过" :value="5" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="时间范围" prop="timeRange">
|
<el-date-picker
|
v-model="timeRange"
|
type="daterange"
|
range-separator="-"
|
start-placeholder="开始时间"
|
end-placeholder="结束时间"
|
value-format="yyyy-MM-dd"
|
style="width: 240px"
|
/>
|
</el-form-item>
|
<el-form-item>
|
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
|
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
|
<el-button type="success" icon="el-icon-download" @click="handleExport">导出</el-button>
|
</el-form-item>
|
</el-form>
|
|
<!-- 统计卡片 -->
|
<el-row :gutter="20" style="margin-bottom: 20px;">
|
<el-col :span="6">
|
<el-card shadow="hover" class="stat-card-wrapper">
|
<div class="stat-card">
|
<div class="stat-icon" style="background: #409EFF;">
|
<i class="el-icon-time" />
|
</div>
|
<div class="stat-content">
|
<div class="stat-title">待检样品</div>
|
<div class="stat-value">{{ statistics.waitInspection || 0 }}</div>
|
</div>
|
</div>
|
</el-card>
|
</el-col>
|
<el-col :span="6">
|
<el-card shadow="hover" class="stat-card-wrapper">
|
<div class="stat-card">
|
<div class="stat-icon" style="background: #E6A23C;">
|
<i class="el-icon-loading" />
|
</div>
|
<div class="stat-content">
|
<div class="stat-title">检验中</div>
|
<div class="stat-value">{{ statistics.inspecting || 0 }}</div>
|
</div>
|
</div>
|
</el-card>
|
</el-col>
|
<el-col :span="6">
|
<el-card shadow="hover" class="stat-card-wrapper">
|
<div class="stat-card">
|
<div class="stat-icon" style="background: #909399;">
|
<i class="el-icon-document-checked" />
|
</div>
|
<div class="stat-content">
|
<div class="stat-title">待审核</div>
|
<div class="stat-value">{{ statistics.waitAudit || 0 }}</div>
|
</div>
|
</div>
|
</el-card>
|
</el-col>
|
<el-col :span="6">
|
<el-card shadow="hover" class="stat-card-wrapper">
|
<div class="stat-card">
|
<div class="stat-icon" style="background: #67C23A;">
|
<i class="el-icon-circle-check" />
|
</div>
|
<div class="stat-content">
|
<div class="stat-title">已完成</div>
|
<div class="stat-value">{{ statistics.finished || 0 }}</div>
|
</div>
|
</div>
|
</el-card>
|
</el-col>
|
</el-row>
|
|
<!-- 进度图表 -->
|
<el-card shadow="hover" style="margin-bottom: 20px;">
|
<div slot="header">
|
<span>检测进度趋势</span>
|
<el-radio-group v-model="chartTimeType" size="mini" style="float: right;" @change="getChart">
|
<el-radio-button label="week">近一周</el-radio-button>
|
<el-radio-button label="month">近一月</el-radio-button>
|
</el-radio-group>
|
</div>
|
<Echart
|
ref="progressChart"
|
:xAxis="chartXAxis"
|
:yAxis="chartYAxis"
|
:series="chartSeries"
|
:tooltip="chartTooltip"
|
:grid="chartGrid"
|
:chartStyle="{ height: '300px' }"
|
/>
|
</el-card>
|
|
<!-- 数据表格 -->
|
<lims-table
|
:tableData="tableData"
|
:column="tableColumn"
|
:page="page"
|
:tableLoading="tableLoading"
|
@pagination="handlePagination"
|
/>
|
</div>
|
</template>
|
|
<script>
|
import Echart from '@/components/echarts/echarts.vue'
|
import limsTable from '@/components/Table/lims-table.vue'
|
import { pageSampleProgress, getStatistics, getChartData, exportSampleProgress } from '@/api/report/sampleProgress'
|
|
export default {
|
name: 'SampleProgress',
|
components: { Echart, limsTable },
|
data() {
|
return {
|
queryParams: {},
|
timeRange: [],
|
tableData: [],
|
tableLoading: false,
|
statistics: {},
|
page: { total: 0, size: 10, current: 1 },
|
chartTimeType: 'week',
|
tableColumn: [
|
{ label: '委托编号', prop: 'entrustCode', minWidth: '140px' },
|
{ label: '样品编号', prop: 'sampleCode', minWidth: '140px' },
|
{ label: '样品名称', prop: 'sampleName', minWidth: '150px' },
|
{ label: '报告编号', prop: 'reportCode', minWidth: '140px' },
|
{
|
label: '检测状态',
|
prop: 'insState',
|
minWidth: '100px',
|
dataType: 'tag',
|
formatData: (val) => this.formatInsState(val),
|
formatType: (val) => this.formatInsStateType(val)
|
},
|
{ label: '进度', prop: 'progressPercent', minWidth: '120px', dataType: 'progress' },
|
{
|
label: '已完成/总数',
|
prop: 'itemCount',
|
minWidth: '100px',
|
formatData: (val, row) => `${row.finishedItems || 0}/${row.totalItems || 0}`
|
},
|
{ label: '负责人', prop: 'chargeUser', minWidth: '80px' },
|
{ label: '客户名称', prop: 'custom', minWidth: '150px' },
|
{ label: '创建时间', prop: 'createTime', minWidth: '160px' }
|
],
|
// 图表配置
|
chartXAxis: [{ type: 'category', data: [] }],
|
chartYAxis: [{ type: 'value' }],
|
chartSeries: [
|
{ name: '样品数量', type: 'bar', data: [] },
|
{ name: '完成数量', type: 'line', data: [] }
|
],
|
chartTooltip: { trigger: 'axis' },
|
chartGrid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }
|
}
|
},
|
mounted() {
|
this.getList()
|
this.getStatisticsData()
|
this.getChart()
|
},
|
methods: {
|
getList() {
|
this.tableLoading = true
|
const params = { ...this.queryParams }
|
if (this.timeRange && this.timeRange.length === 2) {
|
params.startTime = this.timeRange[0]
|
params.endTime = this.timeRange[1]
|
}
|
pageSampleProgress({ ...params, ...this.page })
|
.then(res => {
|
this.tableData = res.data.records || []
|
this.page.total = res.data.total || 0
|
})
|
.finally(() => (this.tableLoading = false))
|
},
|
getStatisticsData() {
|
const params = { ...this.queryParams }
|
if (this.timeRange && this.timeRange.length === 2) {
|
params.startTime = this.timeRange[0]
|
params.endTime = this.timeRange[1]
|
}
|
getStatistics(params).then(res => {
|
this.statistics = res.data || {}
|
})
|
},
|
getChart() {
|
const params = { timeType: this.chartTimeType }
|
getChartData(params).then(res => {
|
this.chartXAxis[0].data = res.data.dates || []
|
this.chartSeries[0].data = res.data.totalCounts || []
|
this.chartSeries[1].data = res.data.finishedCounts || []
|
})
|
},
|
handleQuery() {
|
this.page.current = 1
|
this.getList()
|
this.getStatisticsData()
|
this.getChart()
|
},
|
resetQuery() {
|
this.queryParams = {}
|
this.timeRange = []
|
this.handleQuery()
|
},
|
handleExport() {
|
const params = { ...this.queryParams }
|
if (this.timeRange && this.timeRange.length === 2) {
|
params.startTime = this.timeRange[0]
|
params.endTime = this.timeRange[1]
|
}
|
exportSampleProgress(params).then(res => {
|
this.downloadFile(res, '样品进度报表.xlsx')
|
})
|
},
|
handlePagination({ page, limit }) {
|
this.page.current = page
|
this.page.size = limit
|
this.getList()
|
},
|
formatInsState(val) {
|
const map = { 0: '待检', 1: '检验中', 2: '已检验', 3: '待审核', 4: '审核未通过', 5: '审核通过' }
|
return map[val] || ''
|
},
|
formatInsStateType(val) {
|
const map = { 0: 'warning', 1: 'primary', 2: 'info', 3: 'warning', 4: 'danger', 5: 'success' }
|
return map[val] || ''
|
},
|
downloadFile(data, fileName) {
|
const blob = new Blob([data])
|
const url = window.URL.createObjectURL(blob)
|
const link = document.createElement('a')
|
link.href = url
|
link.download = fileName
|
link.click()
|
window.URL.revokeObjectURL(url)
|
}
|
}
|
}
|
</script>
|
|
<style scoped>
|
.stat-card-wrapper {
|
cursor: pointer;
|
}
|
.stat-card {
|
display: flex;
|
align-items: center;
|
padding: 10px;
|
}
|
.stat-icon {
|
width: 60px;
|
height: 60px;
|
border-radius: 8px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
.stat-icon i {
|
font-size: 28px;
|
color: #fff;
|
}
|
.stat-content {
|
margin-left: 15px;
|
}
|
.stat-title {
|
font-size: 14px;
|
color: #909399;
|
}
|
.stat-value {
|
font-size: 28px;
|
font-weight: bold;
|
color: #303133;
|
margin-top: 5px;
|
}
|
</style>
|