工位库存进度条页面

20240912_adapter
yewj 3 weeks ago
parent 52e70ccd76
commit 8db622afa7

@ -6,10 +6,10 @@ ENV = 'development'
# 开发环境
# VUE_APP_BASE_API = 'https://mudi.dsxyy.org/UDI_WMS_MC/'
VUE_APP_BASE_API = 'http://192.168.0.43:9991'
VUE_APP_BASE_API = 'http://192.168.0.188:9991'
# VUE_APP_BASE_SPMS_API = 'http://192.168.0.43:9993'
VUE_APP_BASE_SPMS_API = 'http://192.168.0.43:10001/directToSpms'
VUE_APP_BASE_SPMS_API = 'http://192.168.0.188:10002/directToSpms'
VUE_APP_SYNC_API ="http://127.0.0.1:10001"
# VUE_APP_BASE_API = 'http://dm.xmglxp.com:81/UDI_WMS_MC/'

@ -5,12 +5,12 @@
"author": "Glxp",
"license": "MIT",
"scripts": {
"build:prod": " vue-cli-service build",
"build:prod": "set NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build",
"build:test": "vue-cli-service build --mode test",
"preview": "node build/index.js --preview",
"lint": "eslint --ext .js,.vue src",
"server": "SET NODE_OPTIONS=--openssl-legacy-provider && webpack-dev-server --env.server --env.develop --inline --max-old-space-size=3000",
"dev": " vue-cli-service serve --open"
"dev": "vue-cli-service serve --open"
},
"husky": {
"hooks": {
@ -37,13 +37,13 @@
"url": "https://gitee.com/y_project/RuoYi-Vue.git"
},
"dependencies": {
"@jiaminghi/data-view": "^2.10.0",
"@riophae/vue-treeselect": "0.4.0",
"@tinymce/tinymce-vue": "^3.2.8",
"@jiaminghi/data-view": "^2.10.0",
"axios": "0.24.0",
"clipboard": "2.0.8",
"core-js": "^3.19.1",
"echarts": "^5.5.1",
"echarts": "4.9.0",
"element-china-area-data": "^5.0.2",
"element-ui": "2.15.8",
"file-saver": "2.0.5",
@ -52,6 +52,7 @@
"js-beautify": "1.13.0",
"js-cookie": "3.0.1",
"js-sha256": "^0.10.1",
"jsbarcode": "^3.11.6",
"jsencrypt": "3.0.0-rc.1",
"loadsh": "^0.0.4",
"lodash": "^4.17.21",

@ -0,0 +1,40 @@
import request from '@/utils/request'
/**
* 获取单据记录
* @param {Object} data 查询参数
* @returns {Promise} 请求结果
*/
export function getOrderRecords(data) {
return request({
url: '/udiwms/inout/order/filter',
method: 'post',
data
})
}
/**
* 获取单据记录明细
* @param {Object} data 查询参数
* @returns {Promise} 请求结果
*/
export function getOrderDetails(data) {
return request({
url: '/udiwms/inout/bizDetail/filterList',
method: 'post',
data
})
}
/**
* 获取药品信息
* @param {Object} data 查询参数
* @returns {Promise} 请求结果
*/
export function getDrugInfo(data) {
return request({
url: '/di/udirel/filterUdi',
method: 'post',
data
})
}

@ -256,7 +256,7 @@
<!--</el-col>-->
<el-col :span="8" class="el-col">
<el-form-item label="是否自动解码:" prop="autoDecode" style="margin-bottom: 0">
<el-select v-model="formData.autoDecode" placeholder="选择是否自动解码">
<el-select v-model="formData.autoDecode" placeholder="选择是否自动解码">
<el-option label="否" :value="0"></el-option>
<el-option label="是" :value="1"></el-option>
</el-select>
@ -264,8 +264,8 @@
</el-col>
<el-col :span="8" class="el-col">
<el-form-item label="输出顺序:" prop="outputMode" >
<el-select v-model="formData.outputMode" placeholder="选择输出顺序">
<el-form-item label="输出顺序:" prop="outputMode">
<el-select v-model="formData.outputMode" placeholder="选择输出顺序">
<el-option label="不排序" :value="0"></el-option>
<el-option label="从左到右" :value="1"></el-option>
<el-option label="从右到左" :value="2"></el-option>
@ -305,7 +305,7 @@
import {getSet, updateSet} from "@/api/collect/collectSet";
import ShelfDisplay from "@/components/ShelfDisplay";
import { isBlank } from '@/utils/strUtil'
import {isBlank} from '@/utils/strUtil'
export default {
name: 'timerSetting',
@ -382,20 +382,20 @@ export default {
]
},
],
activeNames: ['0', '1', '2', '3','4'],
activeNames: ['0', '1', '2', '3', '4'],
formData: {
startDownloadTime: null,
lastCodeSplit: true,
isScanCodeCheck: true,
getSplitConfirm: false,
fixedCount:null,
fixedCount: null,
pdaMaxCount: null,
ipcMaxCount: null,
autoDecode:1,
outputMode:1,
autoDecode: 1,
outputMode: 1,
},
systemParam: null,
socket:null,
socket: null,
}
},
@ -424,25 +424,25 @@ export default {
this.ShelfDisplayFlag = true
},
save() {
if (isBlank(this.formData.scanMaxCount)){
if (isBlank(this.formData.scanMaxCount)) {
this.formData.scanMaxCount = 0
}
if (isBlank(this.formData.pdaMaxCount)){
if (isBlank(this.formData.pdaMaxCount)) {
this.formData.pdaMaxCount = 0
}
if (isBlank(this.formData.ipcMaxCount)){
if (isBlank(this.formData.ipcMaxCount)) {
this.formData.ipcMaxCount = 0
}
if (isBlank(this.formData.autoDecode)){
if (isBlank(this.formData.autoDecode)) {
this.formData.autoDecode = 1
}
if (isBlank(this.formData.fixedCount)){
if (isBlank(this.formData.fixedCount)) {
this.formData.fixedCount = 0
}
if (isBlank(this.formData.outputMode)){
if (isBlank(this.formData.outputMode)) {
this.formData.outputMode = 1
}
if (isBlank(this.formData.drugDealConfirm)){
if (isBlank(this.formData.drugDealConfirm)) {
this.formData.drugDealConfirm = 0
}
updateSet(this.formData)
@ -456,12 +456,12 @@ export default {
});
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
const { autoDecode, fixedCount, outputMode } = this.formData;
const {autoDecode, fixedCount, outputMode} = this.formData;
const settings = [
{ key: "AutoDecode", value: autoDecode },
{ key: "FixedCount", value: fixedCount },
{ key: "OutputMode", value: outputMode }
{key: "AutoDecode", value: autoDecode},
{key: "FixedCount", value: fixedCount},
{key: "OutputMode", value: outputMode}
];
settings.forEach(setting => {

@ -0,0 +1,809 @@
<template>
<div class="app-container">
<!-- 页面头部 -->
<div class="page-header">
<div class="header-title">
<i class="el-icon-monitor"></i>
<span>药品追溯系统</span>
</div>
<div class="header-info">
<span>安全可靠 · 全程追溯 · 质量保证</span>
</div>
<div class="header-wave"></div>
</div>
<!-- 扫码查询区域 -->
<el-card class="box-card scan-area" :body-style="{padding: '30px 20px'}">
<div class="scan-title">
<i class="el-icon-search pulse-icon"></i>
<span>药品追溯码查询</span>
</div>
<el-form :inline="true" :model="queryForm" size="mini">
<el-form-item class="query-form-item">
<el-input
v-model="queryForm.traceCode"
style="width: 600px"
placeholder="请扫描或输入药品追溯码"
clearable
@keyup.enter.native="handleSearch"
class="trace-input"
>
<i slot="prefix" class="el-icon-scan" style="font-size: 20px; color: #1976d2;"></i>
</el-input>
</el-form-item>
<el-form-item class="query-form-item">
<el-button-group>
<el-button type="primary" icon="el-icon-search" @click="handleSearch" size="mini" class="search-btn">查询
</el-button>
<el-button type="default" icon="el-icon-refresh" @click="resetForm" size="mini">重置</el-button>
</el-button-group>
</el-form-item>
</el-form>
</el-card>
<!-- 药品基本信息 -->
<transition name="fade-transform" mode="out-in">
<el-card class="box-card info-card" v-if="drugInfo.nameCode" key="info-card">
<div slot="header" class="clearfix">
<span><i class="el-icon-medicine-box"></i> 药品基本信息</span>
</div>
<el-descriptions :column="2" border>
<el-descriptions-item label="产品名称">{{ drugInfo.cpmctymc }}</el-descriptions-item>
<el-descriptions-item label="产品标识">{{ drugInfo.nameCode }}</el-descriptions-item>
<el-descriptions-item label="规格">{{ drugInfo.prepnSpec }}</el-descriptions-item>
<el-descriptions-item label="包装规格">{{ drugInfo.bzgg }}</el-descriptions-item>
<el-descriptions-item label="生产厂家">{{ drugInfo.manufactory }}</el-descriptions-item>
<el-descriptions-item label="注册证号">{{ drugInfo.zczbhhzbapzbh }}</el-descriptions-item>
<el-descriptions-item label="包装材料">{{ drugInfo.packMatrial }}</el-descriptions-item>
<el-descriptions-item label="计量单位">{{ drugInfo.measname }}</el-descriptions-item>
</el-descriptions>
</el-card>
</transition>
<!-- 追溯记录 -->
<transition name="fade-transform" mode="out-in">
<div class="trace-container" v-if="orderRecords.length > 0" key="trace-container">
<el-row :gutter="20">
<el-col :span="16">
<el-card class="box-card" :body-style="{padding: '0 0 15px 0'}">
<div slot="header" class="clearfix">
<span><i class="el-icon-document"></i> 追溯记录</span>
</div>
<el-table
v-loading="tableLoading"
:data="orderRecords"
border
stripe
highlight-current-row
style="width: 100%"
>
<el-table-column type="index" label="序号" width="50" align="center"></el-table-column>
<el-table-column prop="billNo" label="单据号" align="center"></el-table-column>
<el-table-column prop="billTypeName" label="单据类型" align="center"></el-table-column>
<el-table-column prop="createTime" label="单据时间" align="center"></el-table-column>
<el-table-column prop="fromCorpName" label="往来单位" align="center"></el-table-column>
<el-table-column prop="deptName" label="部门" align="center"></el-table-column>
<el-table-column prop="invName" label="库房" align="center"></el-table-column>
<el-table-column label="操作" width="120" align="center">
<template slot-scope="scope">
<el-button
type="text"
size="mini"
@click="viewOrderDetail(scope.row)"
class="detail-btn"
>
<i class="el-icon-view mr-5"></i>查看明细
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
<el-col :span="8">
<el-card class="box-card flow-card">
<div slot="header" class="clearfix">
<span><i class="el-icon-share"></i> 流通流程</span>
</div>
<el-steps :active="orderRecords.length" direction="vertical" finish-status="success" class="custom-steps">
<el-step
v-for="(item, index) in orderRecords"
:key="index"
:title="item.billTypeName"
:description="item.createTime + ' ' + item.billNo + ' ' + item.fromCorpName"
class="flow-step">
<i slot="icon" :class="getStepIcon(item.billTypeName)"></i>
</el-step>
</el-steps>
</el-card>
</el-col>
</el-row>
</div>
</transition>
<!-- 明细记录 -->
<transition name="fade-transform" mode="out-in">
<el-card class="box-card detail-card" v-if="showOrderDetail" key="detail-card">
<div slot="header" class="clearfix">
<span><i class="el-icon-tickets"></i> 单据明细</span>
<el-button
style="float: right; padding: 3px 0"
type="text"
@click="showOrderDetail = false"
>
<i class="el-icon-close"></i>
</el-button>
</div>
<div class="detail-header" v-if="currentOrder">
<div class="detail-info">
<span class="detail-label">单据号</span>
<span class="detail-value">{{ currentOrder.billNo }}</span>
</div>
<div class="detail-info">
<span class="detail-label">单据类型</span>
<span class="detail-value">{{ currentOrder.billTypeName }}</span>
</div>
<div class="detail-info">
<span class="detail-label">单据时间</span>
<span class="detail-value">{{ currentOrder.createTime }}</span>
</div>
</div>
<el-table
v-loading="detailLoading"
:data="orderDetails"
border
stripe
highlight-current-row
style="width: 100%"
>
<el-table-column type="index" label="序号" width="50" align="center"></el-table-column>
<el-table-column prop="orderIdFk" label="单据号" align="center"></el-table-column>
<el-table-column prop="nameCode" label="产品标识" align="center"></el-table-column>
<el-table-column prop="coName" label="产品名称" align="center"></el-table-column>
<el-table-column prop="spec" label="规格" align="center"></el-table-column>
<el-table-column prop="batchNo" label="批号" align="center"></el-table-column>
<el-table-column prop="productDate" label="生产日期" align="center"></el-table-column>
<el-table-column prop="expireDate" label="有效期至" align="center"></el-table-column>
<el-table-column prop="count" label="数量" align="center"></el-table-column>
<el-table-column prop="measname" label="单位" align="center"></el-table-column>
<el-table-column prop="manufacturer" label="生产厂家" align="center"></el-table-column>
</el-table>
<div class="empty-data" v-if="orderDetails.length === 0 && !detailLoading">
<el-empty description="暂无明细数据"></el-empty>
</div>
</el-card>
</transition>
<!-- 无数据提示 -->
<transition name="fade-transform" mode="out-in">
<el-empty
v-if="!drugInfo.nameCode && !tableLoading && queryForm.traceCode"
description="未查询到相关追溯记录"
class="custom-empty"
key="empty-state"
>
<el-button type="primary" @click="resetForm"></el-button>
</el-empty>
</transition>
<!-- 页面底部 -->
<div class="page-footer">
<div class="footer-links">
<a href="#">关于我们</a>
<span>|</span>
<a href="#">使用帮助</a>
<span>|</span>
<a href="#">联系客服</a>
</div>
<div class="footer-copyright">
© 2024 药品追溯系统 版权所有
</div>
</div>
</div>
</template>
<script>
import {getOrderRecords, getOrderDetails, getDrugInfo} from '@/api/biz/traceQuery'
export default {
name: 'DrugTraceQuery',
data() {
return {
//
activeNames: ['1', '2', '3'],
//
queryForm: {
traceCode: '',
page: 1,
limit: 50
},
//
tableLoading: false,
detailLoading: false,
//
drugInfo: {},
//
orderRecords: [],
//
orderDetails: [],
//
showOrderDetail: false,
//
currentOrder: null
}
},
methods: {
//
getStepIcon(billTypeName) {
//
const iconMap = {
'入库': 'el-icon-box',
'出库': 'el-icon-shopping-cart-full',
'采购': 'el-icon-shopping-bag-1',
'销售': 'el-icon-sell',
'调拨': 'el-icon-connection',
'盘点': 'el-icon-notebook-2',
'退货': 'el-icon-back'
}
//
for (const key in iconMap) {
if (billTypeName && billTypeName.includes(key)) {
return iconMap[key]
}
}
//
return 'el-icon-document'
},
//
handleSearch() {
if (!this.queryForm.traceCode) {
this.$message.warning('请输入药品追溯码')
return
}
this.tableLoading = true
this.drugInfo = {}
this.orderRecords = []
this.orderDetails = []
this.showOrderDetail = false
//
this.queryDrugInfo()
//
this.queryOrderRecords()
},
//
queryDrugInfo() {
getDrugInfo({
nameCode: this.queryForm.traceCode,
page: 1,
limit: 1
}).then(response => {
if (response.code === 20000 && response.data.list && response.data.list.length > 0) {
this.drugInfo = response.data.list[0]
} else {
this.drugInfo = {}
}
}).catch(() => {
this.drugInfo = {}
})
},
//
queryOrderRecords() {
getOrderRecords({
traceCode: this.queryForm.traceCode,
page: this.queryForm.page,
limit: this.queryForm.limit
}).then(response => {
this.tableLoading = false
if (response.code === 20000) {
this.orderRecords = response.data.list || []
} else {
this.$message.error(response.message || '查询失败')
this.orderRecords = []
}
}).catch(() => {
this.tableLoading = false
this.orderRecords = []
})
},
//
viewOrderDetail(row) {
this.currentOrder = row
this.detailLoading = true
this.showOrderDetail = true
getOrderDetails({
orderIdFk: row.billNo,
page: 1,
limit: 100
}).then(response => {
this.detailLoading = false
if (response.code === 20000) {
this.orderDetails = response.data.list || []
if (this.orderDetails.length === 0) {
this.$message.info('该单据暂无明细记录')
}
} else {
this.$message.error(response.message || '查询明细失败')
this.orderDetails = []
}
}).catch(() => {
this.detailLoading = false
this.orderDetails = []
})
},
//
resetForm() {
this.queryForm.traceCode = ''
this.drugInfo = {}
this.orderRecords = []
this.orderDetails = []
this.showOrderDetail = false
},
//
formatDate(dateStr) {
if (!dateStr) return ''
// 220110 2022-01-10
if (dateStr.length === 6) {
const year = dateStr.substring(0, 2)
const month = dateStr.substring(2, 4)
const day = dateStr.substring(4, 6)
return `20${year}-${month}-${day}`
}
return dateStr
}
},
mounted() {
//
document.body.ondrop = function (event) {
event.preventDefault()
event.stopPropagation()
}
// URL
const traceCode = this.$route.query.traceCode
if (traceCode) {
this.queryForm.traceCode = traceCode
this.handleSearch()
}
}
}
</script>
<style scoped>
/* 全局容器样式 */
.app-container {
padding: 25px;
background-color: #f0f2f5;
min-height: calc(100vh - 84px);
transition: all 0.3s ease;
}
/* 卡片通用样式 */
.box-card {
margin-bottom: 24px;
border-radius: 12px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
overflow: hidden;
border: none;
}
.box-card:hover {
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
transform: translateY(-2px);
}
/* 卡片头部样式 */
.box-card .el-card__header {
background: linear-gradient(135deg, #1976d2 0%, #42a5f5 100%);
padding: 16px 20px;
border-bottom: none;
}
.box-card .el-card__header span {
color: #fff;
font-size: 18px;
font-weight: 600;
display: flex;
align-items: center;
}
.box-card .el-card__header span i {
margin-right: 10px;
font-size: 20px;
}
/* 页面头部样式 */
.page-header {
background: linear-gradient(135deg, #1976d2 0%, #42a5f5 100%);
padding: 30px;
border-radius: 12px;
margin-bottom: 24px;
color: #fff;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.15);
position: relative;
overflow: hidden;
}
.page-header::before {
content: '';
position: absolute;
top: -50%;
right: -50%;
width: 100%;
height: 200%;
background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0) 70%);
transform: rotate(30deg);
}
.header-title {
font-size: 28px;
font-weight: bold;
margin-bottom: 12px;
display: flex;
align-items: center;
}
.header-title i {
margin-right: 12px;
font-size: 28px;
}
.header-info {
font-size: 16px;
opacity: 0.9;
letter-spacing: 1px;
}
/* 扫码区域样式 */
.scan-area {
text-align: center;
padding: 35px 25px;
background-color: #fff;
}
.scan-title {
font-size: 22px;
color: #1976d2;
margin-bottom: 25px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
}
.scan-title i {
margin-right: 12px;
font-size: 24px;
}
.query-form-item {
margin: 25px 10px;
display: flex;
justify-content: center;
}
.query-form-item .el-input {
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.08);
border-radius: 8px;
transition: all 0.3s ease;
}
.query-form-item .el-input:focus-within {
box-shadow: 0 4px 12px rgba(25, 118, 210, 0.2);
}
.query-form-item .el-input input {
height: 45px;
font-size: 15px;
}
/* 按钮样式 */
.el-button-group {
margin-left: 15px;
}
.el-button-group .el-button {
border-radius: 8px;
padding: 12px 20px;
transition: all 0.3s ease;
font-weight: 500;
height: 45px;
}
.el-button-group .el-button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.el-button-group .el-button.is-disabled:hover {
transform: none;
box-shadow: none;
}
/* 信息卡片样式 */
.info-card {
margin-top: 24px;
}
.el-descriptions {
margin: 20px 0;
background-color: #fff;
border-radius: 8px;
overflow: hidden;
}
.el-descriptions__label {
font-weight: 600;
color: #606266;
}
.el-descriptions__content {
color: #303133;
}
/* 表格样式 */
.el-table {
margin: 15px 0;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
}
.el-table th {
background-color: #f5f7fa;
color: #606266;
font-weight: 600;
padding: 12px 0;
}
.el-table td {
padding: 12px 0;
}
.el-table--striped .el-table__body tr.el-table__row--striped td {
background-color: #fafafa;
}
.el-table__body tr:hover > td {
background-color: #f0f7ff !important;
}
/* 步骤条样式 */
.trace-container {
margin-top: 24px;
}
.flow-card {
height: 100%;
}
.el-steps {
padding: 25px;
background-color: #fff;
border-radius: 8px;
}
.el-step__title {
font-size: 15px;
font-weight: 500;
}
.el-step__title.is-success {
color: #1976d2;
}
.el-step__head.is-success {
color: #1976d2;
border-color: #1976d2;
}
.el-step__description {
font-size: 13px;
color: #909399;
}
.el-step__line {
background-color: #e8eaec;
}
/* 空状态样式 */
.el-empty {
padding: 50px 0;
background-color: #fff;
border-radius: 12px;
margin: 24px 0;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
}
.el-empty__description {
margin-top: 15px;
font-size: 16px;
color: #909399;
}
/* 页脚样式 */
.page-footer {
margin-top: 50px;
text-align: center;
color: #606266;
padding: 25px 0;
border-top: 1px solid #ebeef5;
background-color: #fff;
border-radius: 12px;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
}
.footer-links {
margin-bottom: 12px;
}
.footer-links a {
color: #606266;
text-decoration: none;
margin: 0 15px;
font-size: 14px;
transition: all 0.3s ease;
}
.footer-links span {
color: #dcdfe6;
}
.footer-links a:hover {
color: #1976d2;
}
.footer-copyright {
font-size: 13px;
color: #909399;
}
/* 动画效果 */
.fade-transform-enter-active,
.fade-transform-leave-active {
transition: all 0.5s;
}
.fade-transform-enter {
opacity: 0;
transform: translateY(20px);
}
.fade-transform-leave-to {
opacity: 0;
transform: translateY(-20px);
}
/* 脉冲效果 */
.pulse-icon {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.1);
opacity: 0.8;
}
100% {
transform: scale(1);
opacity: 1;
}
}
/* 按钮悬停效果 */
.detail-btn:hover i {
transform: translateX(-2px);
}
.search-btn {
background: linear-gradient(135deg, #1976d2 0%, #42a5f5 100%);
border: none;
}
.search-btn:hover {
background: linear-gradient(135deg, #1565c0 0%, #1976d2 100%);
}
/* 明细卡片样式 */
.detail-card {
margin-top: 24px;
}
.detail-header {
display: flex;
flex-wrap: wrap;
padding: 15px 20px;
background-color: #f5f7fa;
border-radius: 8px;
margin-bottom: 15px;
}
.detail-info {
margin-right: 30px;
margin-bottom: 10px;
}
.detail-label {
font-weight: 600;
color: #606266;
margin-right: 5px;
}
.detail-value {
color: #303133;
}
.empty-data {
padding: 30px 0;
}
/* 自定义步骤条 */
.custom-steps {
padding: 10px 0;
}
.flow-step {
margin-bottom: 10px;
}
.flow-step .el-step__icon {
background-color: #e6f1fc;
border-color: #1976d2;
}
.flow-step .el-step__icon.is-text {
border-radius: 50%;
border: 2px solid #1976d2;
}
.flow-step .el-step__title {
font-weight: 600;
}
.mr-5 {
margin-right: 5px;
}
/* 自定义空状态 */
.custom-empty {
padding: 40px 0;
}
/* 响应式调整 */
@media screen and (max-width: 768px) {
.app-container {
padding: 15px;
}
.page-header {
padding: 20px;
}
.header-title {
font-size: 22px;
}
.scan-area {
padding: 20px 15px;
}
.query-form-item .el-input {
width: 100% !important;
}
}
</style>
<script lang="ts">
</script>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,349 @@
<template>
<div class="dashboard-container">
<!-- 搜索区域 -->
<div class="search-section">
<div class="search-wrapper">
<el-input
placeholder="请输入产品名称搜索"
prefix-icon="el-icon-search"
v-model="searchQuery"
@keyup.enter.native="handleSearch"
clearable
>
<el-button slot="append" icon="el-icon-search" @click="handleSearch"></el-button>
</el-input>
</div>
</div>
<!-- 展示区域 -->
<div class="display-section">
<div class="card-container">
<el-row :gutter="20">
<el-col :span="8" v-for="(item, index) in displayData" :key="index">
<el-card class="product-card" :body-style="{ padding: '0px' }">
<div class="card-header">
<h3 class="product-name" :title="item.productName">{{ item.productName }}</h3>
<div class="product-info">
<span class="batch-no">{{ item.batchNo }}</span>
<span class="expire-date">{{ item.expireDate }}</span>
</div>
</div>
<div class="card-body">
<div class="progress-item">
<div class="progress-label">
<span>工位存量</span>
<span class="progress-value">{{ item.workplaceStock }}/{{ item.workplaceTotal }}</span>
</div>
<el-progress
:text-inside="true"
:percentage="calculatePercentage(item.workplaceStock, item.workplaceTotal)"
:color="getProgressColor(item.workplaceStock, item.workplaceTotal)"
:stroke-width="12"
></el-progress>
</div>
<div class="progress-item">
<div class="progress-label">
<span>拆零存量</span>
<span class="progress-value">{{ item.splitStock }}/{{ item.splitTotal }}</span>
</div>
<el-progress
:text-inside="true"
:percentage="calculatePercentage(item.splitStock, item.splitTotal)"
:color="getProgressColor(item.splitStock, item.splitTotal)"
:stroke-width="12"
></el-progress>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 30, 50]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
>
</el-pagination>
</div>
</div>
</div>
</template>
<script>
import { splitFifoPage } from "@/api/inout/splitInv";
export default {
name: "ProductStockDashboard",
data() {
return {
searchQuery: "",
currentPage: 1,
pageSize: 10,
total: 0,
displayData: [],
loading: false,
colorRanges: [
{ min: 0, max: 20, color: '#F56C6C' }, // -
{ min: 20, max: 50, color: '#E6A23C' }, // -
{ min: 50, max: 80, color: '#409EFF' }, // -
{ min: 80, max: 101, color: '#67C23A' } // 绿 -
]
};
},
methods: {
handleSearch() {
this.currentPage = 1;
this.fetchData();
},
handleSizeChange(val) {
this.pageSize = val;
this.fetchData();
},
handleCurrentChange(val) {
this.currentPage = val;
this.fetchData();
},
fetchData() {
this.loading = true;
//
const params = {
page: this.currentPage,
limit: this.pageSize,
key: this.searchQuery || undefined
};
// API
splitFifoPage(params)
.then(response => {
if (response.code === 20000) {
//
this.total = response.data.total || 0;
//
this.displayData = (response.data.list || []).map(item => {
return {
productName: item.cpmctymc || '未知产品',
batchNo: item.batchNo || '无批次',
expireDate: item.expireDate || '无效期',
workplaceStock: item.workplaceQty || 0,
workplaceTotal: item.workplaceTotal || 100,
splitStock: item.splitQty || 0,
splitTotal: item.splitTotal || 100
};
});
} else {
this.$message.error(response.message || '获取数据失败');
this.displayData = [];
this.total = 0;
}
this.loading = false;
})
.catch(error => {
console.error('获取数据失败:', error);
this.$message.error('获取数据失败,请稍后重试');
this.displayData = [];
this.total = 0;
this.loading = false;
});
},
calculatePercentage(current, total) {
if (!total || total === 0) return 0;
const percentage = (current / total) * 100;
return Math.min(100, Math.max(0, percentage));
},
getProgressColor(current, total) {
if (!total || total === 0) return this.colorRanges[0].color;
const percentage = this.calculatePercentage(current, total);
//
for (const range of this.colorRanges) {
if (percentage >= range.min && percentage < range.max) {
return range.color;
}
}
return this.colorRanges[0].color;
}
},
mounted() {
this.fetchData();
//
this.refreshTimer = setInterval(() => {
this.fetchData();
}, 60000); //
},
beforeDestroy() {
//
if (this.refreshTimer) {
clearInterval(this.refreshTimer);
}
}
};
</script>
<style scoped>
.dashboard-container {
height: 100vh; /* 使用视口高度单位,确保铺满整个屏幕 */
width: 100%;
background-color: #f5f7fa;
padding: 20px;
display: flex;
flex-direction: column;
box-sizing: border-box; /* 确保padding不会增加总高度 */
overflow: hidden; /* 防止内容溢出 */
}
.search-section {
margin-top: 20px;
margin-bottom: 10px;
}
.search-wrapper {
max-width: 600px;
margin: 0 0; /* 修改为左对齐 */
}
.display-section {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden; /* 防止内容溢出 */
}
.card-container {
flex: 1;
overflow-y: auto;
padding-bottom: 20px; /* 为分页腾出空间 */
}
.product-card {
height: 220px; /* 增加卡片高度 */
margin-bottom: 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
overflow: hidden;
display: flex;
flex-direction: column;
}
.product-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
}
.card-header {
background-color: #409EFF;
color: white;
padding: 15px;
}
.product-name {
margin: 0;
font-size: 18px;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.product-info {
display: flex;
justify-content: space-between;
margin-top: 5px;
font-size: 12px;
opacity: 0.9;
}
.card-body {
padding: 20px; /* 增加内边距 */
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-around; /* 均匀分布进度条 */
}
.progress-item {
margin-bottom: 20px; /* 增加进度条之间的间距 */
}
.progress-item:last-child {
margin-bottom: 0;
}
.progress-label {
display: flex;
justify-content: space-between;
margin-bottom: 8px; /* 增加标签和进度条之间的间距 */
font-size: 14px;
color: #606266;
}
.progress-value {
font-weight: 600;
}
.pagination-container {
margin-top: 20px;
text-align: center;
padding: 10px 0;
}
/* 响应式调整 */
@media screen and (max-width: 1200px) {
.el-col {
width: 100% !important;
}
.product-card {
height: 200px; /* 在小屏幕上稍微减小高度 */
}
}
/* 自定义进度条样式 */
:deep(.el-progress-bar__outer) {
border-radius: 6px;
background-color: #ebeef5;
height: 12px !important; /* 确保进度条高度 */
}
:deep(.el-progress-bar__inner) {
border-radius: 6px;
transition: width 0.6s ease, background-color 0.6s ease;
}
/* 大屏模式下的额外样式 */
@media screen and (min-width: 1920px) {
.dashboard-container {
padding: 30px;
}
.product-card {
height: 240px; /* 在大屏幕上增加高度 */
}
.product-name {
font-size: 22px;
}
.progress-label {
font-size: 16px;
}
.progress-item {
margin-bottom: 25px;
}
}
</style>

@ -0,0 +1,33 @@
├── build // 构建相关
├── bin // 执行脚本
├── build // 编译文件
├── public // 公共文件
│ ├── tinymce // 富文本组件
│ ├── favicon.ico // favicon图标
│ └── index.html // html模板
│ └── robots.txt // 反爬虫
├── src // 源代码
│ ├── api // 所有请求
│ ├── assets // 主题 字体等静态资源
│ ├── components // 全局公用组件
│ ├── directive // 全局指令
│ ├── layout // 布局
│ ├── plugins // 通用方法
│ ├── router // 路由
│ ├── store // 全局 store管理
│ ├── utils // 全局公用方法
│ ├── views // 所有界面组件
│ ├── App.vue // 入口页面
│ ├── main.js // 入口 加载组件 初始化等
│ ├── permission.js // 权限管理
│ └── settings.js // 系统配置
├── .editorconfig // 编码格式
├── .env.development // 开发环境配置
├── .env.production // 生产环境配置
├── .env.staging // 测试环境配置
├── .eslintignore // 忽略语法检查
├── .eslintrc.js // eslint 配置项
├── .gitignore // git 忽略项
├── babel.config.js // babel.config.js
├── package.json // package.json
└── vue.config.js // vue.config.js
Loading…
Cancel
Save