feat:盘点记录页面
parent
056862f6c0
commit
176f373e0f
@ -0,0 +1,430 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:model="queryParams"
|
||||
:inline="true"
|
||||
label-width="auto"
|
||||
class="-mb-15px"
|
||||
>
|
||||
<el-form-item :label="t('ErpStock.Check.no')" prop="no">
|
||||
<el-input
|
||||
v-model="queryParams.no"
|
||||
:placeholder="t('ErpStock.Check.placeholderNo')"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('ErpStock.Check.checkTime')" prop="checkTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.checkTime"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
:start-placeholder="t('common.startTimeText')"
|
||||
:end-placeholder="t('common.endTimeText')"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('ErpStock.Check.sourceType')" prop="sourceType">
|
||||
<el-select
|
||||
v-model="queryParams.sourceType"
|
||||
:placeholder="t('ErpStock.CheckRecord.placeholderSourceType')"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in sourceTypeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('ErpStock.Check.categoryType')" prop="categoryType">
|
||||
<el-select
|
||||
v-model="queryParams.categoryType"
|
||||
:placeholder="t('ErpStock.CheckRecord.placeholderCategoryType')"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
@change="handleCategoryTypeChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in categoryTypeOptions"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('ErpStock.Check.product')" prop="productId">
|
||||
<el-select
|
||||
v-model="queryParams.productId"
|
||||
clearable
|
||||
filterable
|
||||
:placeholder="t('ErpStock.Check.placeholderProduct')"
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in productList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('ErpStock.Check.warehouse')" prop="warehouseId">
|
||||
<el-select
|
||||
v-model="queryParams.warehouseId"
|
||||
clearable
|
||||
filterable
|
||||
:placeholder="t('ErpStock.Check.placeholderWarehouse')"
|
||||
class="!w-240px"
|
||||
@change="handleWarehouseChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in warehouseList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('ErpStock.CheckRecord.area')" prop="areaId" v-show="showAllFilters">
|
||||
<el-select
|
||||
v-model="queryParams.areaId"
|
||||
clearable
|
||||
filterable
|
||||
:placeholder="t('ErpStock.CheckRecord.placeholderArea')"
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in filteredAreaList"
|
||||
:key="item.id"
|
||||
:label="item.areaName"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('ErpStock.Check.creator')" prop="creator" v-show="showAllFilters">
|
||||
<el-select
|
||||
v-model="queryParams.creator"
|
||||
clearable
|
||||
filterable
|
||||
:placeholder="t('ErpStock.Check.placeholderCreator')"
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in userList"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="text" class="text-primary" @click="toggleFilters">
|
||||
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
|
||||
{{
|
||||
showAllFilters
|
||||
? t('FactoryModeling.FactoryStructure.collapseText')
|
||||
: t('FactoryModeling.FactoryStructure.expandText')
|
||||
}}
|
||||
</el-button>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon icon="ep:search" class="mr-5px" />
|
||||
{{ t('common.query') }}
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon icon="ep:refresh" class="mr-5px" />
|
||||
{{ t('common.reset') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
:loading="exportLoading"
|
||||
@click="handleExport"
|
||||
v-hasPermi="['erp:stock-check:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" />
|
||||
{{ t('action.export') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<ContentWrap>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:stripe="true"
|
||||
:show-overflow-tooltip="true"
|
||||
row-key="id"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column width="45" :label="t('action.select')" type="selection" />
|
||||
<el-table-column :label="t('ErpStock.Check.no')" prop="no" align="center" min-width="160" sortable />
|
||||
<el-table-column :label="t('ErpStock.Check.sourceType')" prop="sourceType" align="center" min-width="100">
|
||||
<template #default="{ row }">
|
||||
{{ getSourceTypeLabel(row.sourceType) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('ErpStock.Check.categoryType')" prop="categoryType" align="center" min-width="110">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE" :value="row.categoryType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('ErpStock.CheckRecord.productBarCode')" align="center" min-width="140">
|
||||
<template #default="{ row }">
|
||||
{{ getRowValue(row, ['productBarCode', 'barCode']) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('ErpStock.CheckRecord.productName')" align="left" min-width="180">
|
||||
<template #default="{ row }">
|
||||
{{ getRowValue(row, ['productName', 'name']) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('ErpStock.CheckRecord.warehouseName')" align="center" prop="warehouseName" min-width="130" />
|
||||
<el-table-column :label="t('ErpStock.CheckRecord.areaName')" align="center" prop="areaName" min-width="120" />
|
||||
<el-table-column :label="t('ErpStock.Check.stockCount')" align="right" prop="stockCount" min-width="110">
|
||||
<template #default="{ row }">
|
||||
{{ formatCount(row.stockCount, row) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('ErpStock.Check.actualCount')" align="right" prop="actualCount" min-width="110">
|
||||
<template #default="{ row }">
|
||||
{{ formatCount(row.actualCount, row) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('ErpStock.Check.differenceCount')" align="right" prop="count" min-width="100">
|
||||
<template #default="{ row }">
|
||||
<span :class="getDifferenceClass(row.count)">{{ formatDifference(row.count, row) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="t('ErpStock.Check.checkTime')"
|
||||
prop="checkTime"
|
||||
align="center"
|
||||
:formatter="dateFormatter"
|
||||
min-width="170"
|
||||
sortable
|
||||
/>
|
||||
<el-table-column :label="t('ErpStock.Check.creator')" prop="creatorName" align="center" min-width="110" />
|
||||
<el-table-column
|
||||
:label="t('common.createTime')"
|
||||
prop="createTime"
|
||||
align="center"
|
||||
:formatter="dateFormatter"
|
||||
min-width="170"
|
||||
/>
|
||||
<el-table-column :label="t('ErpStock.Check.remark')" prop="remark" min-width="160" />
|
||||
</el-table>
|
||||
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import { StockCheckApi, type StockCheckRecordVO } from '@/api/erp/stock/check'
|
||||
import { ProductApi, type ProductVO } from '@/api/erp/product/product'
|
||||
import { WarehouseApi, type WarehouseVO } from '@/api/erp/stock/warehouse'
|
||||
import { WarehouseAreaApi, type WarehouseAreaVO } from '@/api/erp/stock/warehousearea'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { useDictStoreWithOut } from '@/store/modules/dict'
|
||||
|
||||
defineOptions({ name: 'ErpStockCheckRecord' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage()
|
||||
const dictStore = useDictStoreWithOut()
|
||||
|
||||
const loading = ref(false)
|
||||
const list = ref<StockCheckRecordVO[]>([])
|
||||
const total = ref(0)
|
||||
const queryFormRef = ref()
|
||||
const productList = ref<ProductVO[]>([])
|
||||
const warehouseList = ref<WarehouseVO[]>([])
|
||||
const areaList = ref<WarehouseAreaVO[]>([])
|
||||
const userList = ref<UserApi.UserVO[]>([])
|
||||
const showAllFilters = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
const selectionList = ref<StockCheckRecordVO[]>([])
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
no: undefined as string | undefined,
|
||||
checkTime: [] as string[],
|
||||
sourceType: undefined as number | undefined,
|
||||
categoryType: undefined as number | undefined,
|
||||
productId: undefined as number | undefined,
|
||||
warehouseId: undefined as number | undefined,
|
||||
areaId: undefined as number | undefined,
|
||||
creator: undefined as string | number | undefined
|
||||
})
|
||||
|
||||
const sourceTypeOptions = computed(() => [
|
||||
{ value: 1, label: t('ErpStock.Check.sourceTypeStock') },
|
||||
{ value: 2, label: t('ErpStock.Check.sourceTypeProduct') }
|
||||
])
|
||||
const categoryTypeOptions = computed(() => getIntDictOptions(DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE))
|
||||
const filteredAreaList = computed(() => {
|
||||
if (!queryParams.warehouseId) return areaList.value
|
||||
return areaList.value.filter((item) => item.warehouseId === queryParams.warehouseId)
|
||||
})
|
||||
|
||||
const toggleFilters = () => {
|
||||
showAllFilters.value = !showAllFilters.value
|
||||
}
|
||||
|
||||
const buildQueryParams = () => {
|
||||
return Object.fromEntries(
|
||||
Object.entries(queryParams).filter(([, value]) => {
|
||||
if (Array.isArray(value)) return value.length > 0
|
||||
return value !== undefined && value !== null && value !== ''
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await StockCheckApi.getStockCheckRecordPage(buildQueryParams())
|
||||
list.value = data?.list || []
|
||||
total.value = data?.total || 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const getProductList = async () => {
|
||||
productList.value = await ProductApi.getProductSimpleList(
|
||||
queryParams.categoryType !== undefined ? { categoryType: queryParams.categoryType } : undefined
|
||||
)
|
||||
if (queryParams.productId && !productList.value.some((item) => item.id === queryParams.productId)) {
|
||||
queryParams.productId = undefined
|
||||
}
|
||||
}
|
||||
|
||||
const handleCategoryTypeChange = async () => {
|
||||
await getProductList()
|
||||
}
|
||||
|
||||
const handleWarehouseChange = () => {
|
||||
queryParams.areaId = undefined
|
||||
}
|
||||
|
||||
const handleSelectionChange = (rows: StockCheckRecordVO[]) => {
|
||||
selectionList.value = rows
|
||||
}
|
||||
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
await message.exportConfirm()
|
||||
exportLoading.value = true
|
||||
const ids = selectionList.value.map((item) => item.id).filter((id) => id != null)
|
||||
const params = {
|
||||
...buildQueryParams(),
|
||||
ids: ids.length ? ids.join(',') : undefined
|
||||
}
|
||||
const data = await StockCheckApi.exportStockCheckRecord(params)
|
||||
download.excel(data, t('ErpStock.CheckRecord.exportName'))
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const resetQuery = async () => {
|
||||
queryFormRef.value?.resetFields()
|
||||
await getProductList()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
const getRowValue = (row: StockCheckRecordVO, keys: string[]) => {
|
||||
for (const key of keys) {
|
||||
const value = row[key]
|
||||
if (value !== undefined && value !== null && value !== '') return value
|
||||
}
|
||||
return '-'
|
||||
}
|
||||
|
||||
const getSourceTypeLabel = (value: number | string | undefined) => {
|
||||
const option = sourceTypeOptions.value.find((item) => Number(item.value) === Number(value))
|
||||
return option?.label || '-'
|
||||
}
|
||||
|
||||
const getUnitName = (row: StockCheckRecordVO) => {
|
||||
return row.productUnitName || row.unitName || ''
|
||||
}
|
||||
|
||||
const formatNumber = (value: number | string | undefined) => {
|
||||
if (value === undefined || value === null || value === '') return '-'
|
||||
const num = Number(value)
|
||||
return Number.isFinite(num) ? num.toLocaleString() : String(value)
|
||||
}
|
||||
|
||||
const formatCount = (value: number | string | undefined, row: StockCheckRecordVO) => {
|
||||
const unitName = getUnitName(row)
|
||||
const formatted = formatNumber(value)
|
||||
return unitName && formatted !== '-' ? formatted + ' ' + unitName : formatted
|
||||
}
|
||||
|
||||
const formatDifference = (value: number | string | undefined, row: StockCheckRecordVO) => {
|
||||
const unitName = getUnitName(row)
|
||||
const num = Number(value)
|
||||
if (!Number.isFinite(num)) return formatCount(value, row)
|
||||
const sign = num > 0 ? '+' : ''
|
||||
const formatted = sign + num.toLocaleString()
|
||||
return unitName ? formatted + ' ' + unitName : formatted
|
||||
}
|
||||
|
||||
const getDifferenceClass = (value: number | string | undefined) => {
|
||||
const num = Number(value)
|
||||
if (num > 0) return 'stock-check-record-profit'
|
||||
if (num < 0) return 'stock-check-record-loss'
|
||||
return ''
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await dictStore.setDictMap()
|
||||
await Promise.all([
|
||||
getProductList(),
|
||||
WarehouseApi.getWarehouseSimpleList().then((data) => {
|
||||
warehouseList.value = data || []
|
||||
}),
|
||||
WarehouseAreaApi.getWarehouseAreaPage({ pageNo: 1, pageSize: 100 }).then((data) => {
|
||||
areaList.value = data?.list || []
|
||||
}),
|
||||
UserApi.getSimpleUserList().then((data) => {
|
||||
userList.value = data || []
|
||||
})
|
||||
])
|
||||
await getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stock-check-record-profit {
|
||||
color: var(--el-color-success);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.stock-check-record-loss {
|
||||
color: var(--el-color-danger);
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue