|
|
|
|
@ -62,44 +62,82 @@ v-for="item in summaryCards" :key="item.title" class="stock-overview__card-wrap"
|
|
|
|
|
|
|
|
|
|
<div class="stock-overview__tables">
|
|
|
|
|
<ContentWrap :title="t('ErpStock.Overview.stockDetail')" class="stock-overview__stock">
|
|
|
|
|
<el-tabs v-model="activeName" @tab-click="handleTabClick">
|
|
|
|
|
<el-tab-pane label="全部" name="" />
|
|
|
|
|
<el-tab-pane
|
|
|
|
|
v-for="item in categoryTypeOptions"
|
|
|
|
|
:key="item.value"
|
|
|
|
|
:label="item.label"
|
|
|
|
|
:name="String(item.value)"
|
|
|
|
|
/>
|
|
|
|
|
</el-tabs>
|
|
|
|
|
<el-table
|
|
|
|
|
v-loading="stockLoading" :data="stockList" :stripe="true" :show-overflow-tooltip="true" row-key="id"
|
|
|
|
|
height="520">
|
|
|
|
|
<el-table-column :label="t('ErpStock.Stock.code')" align="center" sortable prop="barCode" min-width="130" />
|
|
|
|
|
<el-table-column :label="t('ErpStock.Stock.name')" align="center" sortable prop="name" min-width="150">
|
|
|
|
|
<el-table-column :label="t('ErpStock.Stock.code')" align="center" sortable prop="barCode" min-width="160" />
|
|
|
|
|
<el-table-column :label="t('ErpStock.Stock.name')" align="center" sortable prop="name" min-width="180">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
{{ row.name || row.productName || '-' }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column
|
|
|
|
|
:label="t('ErpStock.Overview.materialCategory')" align="center" prop="categoryType"
|
|
|
|
|
min-width="100">
|
|
|
|
|
<el-table-column :label="t('ErpStock.Stock.subCategory')" align="center" prop="categoryName" min-width="120" sortable />
|
|
|
|
|
<el-table-column :label="t('ErpStock.Stock.packagingRule')" align="center" prop="packagingRule" min-width="180" />
|
|
|
|
|
<el-table-column :label="t('ErpStock.Stock.warehouse')" align="center" prop="warehouseName" min-width="140" sortable />
|
|
|
|
|
<el-table-column :label="t('ErpStock.Stock.stockDisplay')" align="center" prop="stockDisplay" min-width="220">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-tag :type="getCategoryTagType(row.categoryType)">
|
|
|
|
|
{{ getCategoryLabel(row.categoryType) }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
<span
|
|
|
|
|
v-if="formatStockDisplay(row.stockDisplay).length"
|
|
|
|
|
class="stock-display"
|
|
|
|
|
:style="getStockDisplayStyle(row.categoryType)"
|
|
|
|
|
>
|
|
|
|
|
<span
|
|
|
|
|
v-for="item in formatStockDisplay(row.stockDisplay)"
|
|
|
|
|
:key="item"
|
|
|
|
|
class="stock-display__item"
|
|
|
|
|
>
|
|
|
|
|
{{ item }}
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
<span v-else>-</span>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column
|
|
|
|
|
:label="t('ErpStock.Stock.subCategory')" align="center" prop="categoryName"
|
|
|
|
|
min-width="110" />
|
|
|
|
|
<el-table-column :label="t('ErpStock.Stock.warehouse')" align="center" prop="warehouseName" min-width="110" />
|
|
|
|
|
<el-table-column :label="t('ErpStock.Overview.location')" align="center" prop="areaName" min-width="110" />
|
|
|
|
|
<el-table-column :label="t('ErpStock.Stock.stockDisplay')" align="center" prop="stockDisplay" min-width="180">
|
|
|
|
|
<el-table-column :label="t('ErpStock.Stock.areaStockDisplay')" align="center" min-width="260">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<span v-if="row.stockDisplay" class="stock-overview__display">{{ row.stockDisplay }}</span>
|
|
|
|
|
<div
|
|
|
|
|
v-if="getAreaStockDisplayList(row.areaStocks).length"
|
|
|
|
|
class="area-stock-list"
|
|
|
|
|
:style="getStockDisplayStyle(row.categoryType)"
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
v-for="item in getAreaStockDisplayList(row.areaStocks)"
|
|
|
|
|
:key="item"
|
|
|
|
|
class="area-stock-list__item"
|
|
|
|
|
>
|
|
|
|
|
{{ item }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<span v-else>-</span>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column :label="t('ErpStock.Stock.count')" align="center" sortable prop="count" min-width="110">
|
|
|
|
|
<el-table-column :label="t('ErpStock.Stock.totalPackageCount')" align="center" sortable prop="totalPackageCount" min-width="120">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
{{ formatStockCount(row.totalPackageCount) }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column :label="t('ErpStock.Stock.totalBaseCount')" align="center" sortable prop="totalBaseCount" min-width="120">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
{{ formatStockCount(row.totalBaseCount) }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column :label="t('ErpStock.Stock.unit')" align="center" prop="unitName" min-width="90" sortable />
|
|
|
|
|
<el-table-column :label="t('ErpStock.Stock.latestInTime')" align="center" min-width="180">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
{{ formatNumber(row.count) }}
|
|
|
|
|
{{ formatStockTime(getLatestInTime(row)) }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column :label="t('ErpStock.Stock.unit')" align="center" prop="unitName" min-width="80" />
|
|
|
|
|
<el-table-column :label="t('ErpStock.Overview.latestChangeTime')" align="center" min-width="170">
|
|
|
|
|
<el-table-column :label="t('ErpStock.Stock.latestOutTime')" align="center" min-width="180">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
{{ formatStockTime(getLatestChangeTime(row)) }}
|
|
|
|
|
{{ formatStockTime(getLatestOutTime(row)) }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
@ -161,6 +199,7 @@ v-loading="recordLoading" :data="recordList" :stripe="true" :show-overflow-toolt
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { DICT_TYPE, getDictObj, getIntDictOptions } from '@/utils/dict'
|
|
|
|
|
import { isHexColor } from '@/utils/color'
|
|
|
|
|
import { formatDate } from '@/utils/formatTime'
|
|
|
|
|
import { StockApi, StockVO } from '@/api/erp/stock/stock'
|
|
|
|
|
import { StockRecordApi, StockRecordVO } from '@/api/erp/stock/record'
|
|
|
|
|
@ -175,6 +214,7 @@ const dictStore = useDictStoreWithOut()
|
|
|
|
|
|
|
|
|
|
const queryFormRef = ref()
|
|
|
|
|
const warehouseList = ref<WarehouseVO[]>([])
|
|
|
|
|
const activeName = ref('')
|
|
|
|
|
const categoryTypeOptions = computed(() => getIntDictOptions(DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE))
|
|
|
|
|
const queryParams = reactive<{
|
|
|
|
|
categoryType?: number
|
|
|
|
|
@ -345,6 +385,7 @@ const getList = async () => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleQuery = () => {
|
|
|
|
|
activeName.value = queryParams.categoryType !== undefined ? String(queryParams.categoryType) : ''
|
|
|
|
|
stockQuery.pageNo = 1
|
|
|
|
|
recordQuery.pageNo = 1
|
|
|
|
|
getList()
|
|
|
|
|
@ -352,24 +393,23 @@ const handleQuery = () => {
|
|
|
|
|
|
|
|
|
|
const resetQuery = () => {
|
|
|
|
|
queryFormRef.value?.resetFields()
|
|
|
|
|
activeName.value = ''
|
|
|
|
|
queryParams.categoryType = undefined
|
|
|
|
|
handleQuery()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const goRecordPage = () => {
|
|
|
|
|
router.push({ path: '/warehouse/record' })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getCategoryLabel = (value?: number | string) => {
|
|
|
|
|
return getDictObj(DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE, value)?.label || '-'
|
|
|
|
|
const handleTabClick = (tab: any) => {
|
|
|
|
|
const value = String(tab.paneName || '')
|
|
|
|
|
activeName.value = value
|
|
|
|
|
queryParams.categoryType = value ? Number(value) : undefined
|
|
|
|
|
handleQuery()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getCategoryTagType = (value?: number | string) => {
|
|
|
|
|
const dict = getDictObj(DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE, value)
|
|
|
|
|
const colorType = String(dict?.colorType || '')
|
|
|
|
|
return ['success', 'info', 'warning', 'danger'].includes(colorType) ? colorType : 'primary'
|
|
|
|
|
const goRecordPage = () => {
|
|
|
|
|
router.push({ path: '/warehouse/record' })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const formatNumber = (value: number | string | undefined) => {
|
|
|
|
|
const formatStockCount = (value: number | string | undefined) => {
|
|
|
|
|
if (value === undefined || value === null || value === '') return '-'
|
|
|
|
|
const num = Number(value)
|
|
|
|
|
return Number.isFinite(num) ? num.toLocaleString() : String(value)
|
|
|
|
|
@ -379,20 +419,38 @@ const formatStockTime = (value: any) => {
|
|
|
|
|
return value ? formatDate(value, 'YYYY-MM-DD HH:mm:ss') : '-'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getLatestChangeTime = (row: any) => {
|
|
|
|
|
return (
|
|
|
|
|
row.latestChangeTime ??
|
|
|
|
|
row.latestInTime ??
|
|
|
|
|
row.lastInTime ??
|
|
|
|
|
row.recentInTime ??
|
|
|
|
|
row.latestStockInTime ??
|
|
|
|
|
row.lastStockInTime ??
|
|
|
|
|
row.latestOutTime ??
|
|
|
|
|
row.lastOutTime ??
|
|
|
|
|
row.recentOutTime ??
|
|
|
|
|
row.latestStockOutTime ??
|
|
|
|
|
row.lastStockOutTime
|
|
|
|
|
)
|
|
|
|
|
const formatStockDisplay = (value: string | undefined) => {
|
|
|
|
|
if (!value) return []
|
|
|
|
|
return value
|
|
|
|
|
.split(',')
|
|
|
|
|
.map((item) => item.trim())
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getAreaStockDisplayList = (value: StockVO['areaStocks']) => {
|
|
|
|
|
if (!Array.isArray(value)) return []
|
|
|
|
|
return value
|
|
|
|
|
.map((item) => item?.stockDisplay?.trim())
|
|
|
|
|
.filter((item): item is string => Boolean(item))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getStockDisplayStyle = (categoryType: number | string | undefined) => {
|
|
|
|
|
const dict = getDictObj(DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE, categoryType)
|
|
|
|
|
if (dict?.cssClass && isHexColor(dict.cssClass)) {
|
|
|
|
|
return { color: dict.cssClass }
|
|
|
|
|
}
|
|
|
|
|
const colorType = dict?.colorType && !['primary', 'default'].includes(String(dict.colorType))
|
|
|
|
|
? dict.colorType
|
|
|
|
|
: 'primary'
|
|
|
|
|
return { color: `var(--el-color-${colorType})` }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getLatestInTime = (row: any) => {
|
|
|
|
|
return row.latestInTime ?? row.lastInTime ?? row.recentInTime ?? row.latestStockInTime ?? row.lastStockInTime
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getLatestOutTime = (row: any) => {
|
|
|
|
|
return row.latestOutTime ?? row.lastOutTime ?? row.recentOutTime ?? row.latestStockOutTime ?? row.lastStockOutTime
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isStockIn = (direction?: string) => ['入库', 'Inbound'].includes(String(direction || ''))
|
|
|
|
|
@ -519,9 +577,30 @@ onMounted(async () => {
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stock-overview__display {
|
|
|
|
|
.stock-display {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
gap: 18px;
|
|
|
|
|
color: var(--el-color-primary);
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stock-display__item {
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.area-stock-list {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 4px;
|
|
|
|
|
line-height: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.area-stock-list__item {
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
word-break: break-all;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stock-overview__material {
|
|
|
|
|
|