Files
mom-web/src/views/erp/stock/record/index.vue

563 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- ERP 产品库存明细列表 - 移动端适配 -->
<template>
<div class="mobile-record">
<!-- 搜索操作栏 -->
<div class="mobile-header">
<div class="mobile-header__search">
<el-input
v-model="searchKeyword"
placeholder="搜索产品名称"
clearable
@keyup.enter="handleQuery"
:prefix-icon="Search"
/>
</div>
<div class="mobile-header__actions">
<el-button :icon="Filter" circle @click="showSearch = true" />
<el-button
type="primary"
:icon="Plus"
circle
@click="openForm('create')"
v-hasPermi="['erp:stock-record:create']"
/>
</div>
</div>
<!-- 快捷操作 -->
<div class="mobile-quick-actions">
<el-button
size="small"
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['erp:stock-record:export']"
>
导出
</el-button>
<el-button
size="small"
type="success"
plain
@click="handleExportProduct"
:loading="exportBatchLoading"
v-hasPermi="['erp:stock-record:export-product']"
>
导出批次
</el-button>
</div>
<!-- 选中提示 -->
<div v-if="multipleSelection.length > 0" class="mobile-selection-tip">
已选择 {{ multipleSelection.length }} 条记录
</div>
<!-- 列表内容 -->
<div class="mobile-list" v-loading="loading">
<div v-if="list.length === 0 && !loading" class="mobile-empty">
<el-empty description="暂无数据" />
</div>
<div v-for="item in list" :key="item.id" class="mobile-card">
<!-- 选择框 -->
<div class="mobile-card__checkbox">
<el-checkbox
:model-value="multipleSelection.some(i => i.id === item.id)"
@change="(val) => handleCheckboxChange(val, item)"
/>
</div>
<div class="mobile-card__content">
<div class="mobile-card__header">
<span class="mobile-card__name">{{ item.productName }}</span>
<dict-tag :type="DICT_TYPE.ERP_STOCK_RECORD_BIZ_TYPE" :value="item.bizType" />
</div>
<div class="mobile-card__body">
<div class="mobile-card__row">
<span class="mobile-card__label">产品分类</span>
<span class="mobile-card__value">{{ item.categoryName || '-' }}</span>
</div>
<div class="mobile-card__row">
<span class="mobile-card__label">产品单位</span>
<span class="mobile-card__value">{{ item.unitName || '-' }}</span>
</div>
<div class="mobile-card__row">
<span class="mobile-card__label">仓库</span>
<span class="mobile-card__value">{{ item.warehouseName || '-' }}</span>
</div>
<div class="mobile-card__row">
<span class="mobile-card__label">出入库单号</span>
<span class="mobile-card__value mobile-card__value--ellipsis">{{ item.bizNo || '-' }}</span>
</div>
<div class="mobile-card__row">
<span class="mobile-card__label">出入库日期</span>
<span class="mobile-card__value">{{ dateFormatter({ createTime: item.createTime }) }}</span>
</div>
<div class="mobile-card__row mobile-card__row--highlight">
<span class="mobile-card__label">出入库数量</span>
<span class="mobile-card__value">{{ erpCountTableColumnFormatter({ count: item.count }) }}</span>
</div>
<div class="mobile-card__row mobile-card__row--highlight">
<span class="mobile-card__label">库存量</span>
<span class="mobile-card__value">{{ erpCountTableColumnFormatter({ count: item.totalCount }) }}</span>
</div>
<div class="mobile-card__row" v-hasPermi="['erp:stock:goods-value']">
<span class="mobile-card__label">单价</span>
<span class="mobile-card__value">{{ item.unitPrice != null ? item.unitPrice.toFixed(4) : '-' }}</span>
</div>
<div class="mobile-card__row" v-hasPermi="['erp:stock:goods-value']">
<span class="mobile-card__label">货值</span>
<span class="mobile-card__value mobile-card__value--price">{{ item.stockValue != null ? item.stockValue.toFixed(4) : '-' }}</span>
</div>
<div class="mobile-card__row">
<span class="mobile-card__label">操作人</span>
<span class="mobile-card__value">{{ item.creatorName || '-' }}</span>
</div>
</div>
<div class="mobile-card__footer" v-if="item.bizType === 90">
<el-button
size="small"
type="primary"
@click="handleDetail(item.bizId, item)"
v-hasPermi="['erp:stock-record:review']"
>
查看详情
</el-button>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<div class="mobile-pagination" v-if="total > 0">
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
:page-sizes="[10, 20]"
layout="total, prev, pager, next"
:pager-count="5"
@pagination="getList"
/>
</div>
<!-- 筛选抽屉 -->
<el-drawer v-model="showSearch" title="筛选条件" direction="btt" size="60%">
<el-form :model="queryParams" ref="queryFormRef" label-position="top">
<el-form-item label="产品" prop="productId">
<el-select
v-model="queryParams.productId"
clearable
filterable
placeholder="请选择产品"
style="width: 100%"
>
<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="产品分类" prop="categoryId">
<el-select
v-model="queryParams.categoryId"
clearable
filterable
placeholder="请选择产品分类"
style="width: 100%"
>
<el-option
v-for="item in categoryList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="仓库" prop="warehouseId">
<el-select
v-model="queryParams.warehouseId"
clearable
filterable
placeholder="请选择仓库"
style="width: 100%"
>
<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="操作类型" prop="bizType">
<el-select
v-model="queryParams.bizType"
placeholder="请选择操作类型"
clearable
style="width: 100%"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.ERP_STOCK_RECORD_BIZ_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="业务单号" prop="bizNo">
<el-input
v-model="queryParams.bizNo"
placeholder="请输入业务单号"
clearable
@keyup.enter="handleQuery"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
style="width: 100%"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="resetQuery">重置</el-button>
<el-button type="primary" @click="handleFilterConfirm">确认筛选</el-button>
</template>
</el-drawer>
<!-- 质检详情弹窗 -->
<QualityDetailDialog v-model:visible="dialogVisible" :quality-id="selectedQualityId" />
</div>
</template>
<script setup lang="ts">
import { Search, Filter, Plus } from '@element-plus/icons-vue'
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { StockRecordApi, StockRecordVO } from '@/api/erp/stock/record'
import { ProductApi, ProductVO } from '@/api/erp/product/product'
import { WarehouseApi, WarehouseVO } from '@/api/erp/stock/warehouse'
import { ProductCategoryApi } from '@/api/erp/product/category'
import { erpCountTableColumnFormatter } from '@/utils'
import QualityDetailDialog from './QualityDetailDialog.vue'
/** ERP 产品库存明细列表 */
defineOptions({ name: 'ErpStockRecord' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const list = ref<StockRecordVO[]>([]) // 列表的数据
const total = ref(0) // 列表的总页数
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
productId: undefined,
categoryId: undefined,
warehouseId: undefined,
bizType: undefined,
bizNo: undefined,
createTime: []
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
const exportBatchLoading = ref(false) // 导出批次的加载中
const productList = ref<ProductVO[]>([]) // 产品列表
const warehouseList = ref<WarehouseVO[]>([]) // 仓库列表
const categoryList = ref<any[]>([]) // 产品分类列表
const isFinishedProduct = ref(false) // 是否是成品分类
const multipleSelection = ref<StockRecordVO[]>([]) // 选中的行
const showSearch = ref(false) // 搜索抽屉显示状态
const searchKeyword = ref('') // 搜索关键词
// 质检详情弹窗
const dialogVisible = ref(false)
const selectedQualityId = ref<number | undefined>(undefined)
/** 处理表格选择变化 */
const handleSelectionChange = (selection: StockRecordVO[]) => {
multipleSelection.value = selection
}
/** 处理单个复选框变化 */
const handleCheckboxChange = (checked: boolean, item: StockRecordVO) => {
if (checked) {
multipleSelection.value.push(item)
} else {
const index = multipleSelection.value.findIndex(i => i.id === item.id)
if (index > -1) {
multipleSelection.value.splice(index, 1)
}
}
}
/** 查看详情 */
const handleDetail = (bizId: number, row: StockRecordVO) => {
// 传递行的ID而不是bizId
selectedQualityId.value = row.id
dialogVisible.value = true
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await StockRecordApi.getStockRecordPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
showSearch.value = false
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields()
handleQuery()
}
/** 确认筛选 */
const handleFilterConfirm = () => {
showSearch.value = false
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
// 导出的二次确认
await message.exportConfirm()
// 发起导出
exportLoading.value = true
const data = await StockRecordApi.exportStockRecord(queryParams)
download.excel(data, '产品库存明细.xls')
} catch (error) {
console.error(error)
} finally {
exportLoading.value = false
}
}
/** 导出批次按钮操作 */
const handleExportProduct = async () => {
try {
// 检查是否有选中的行
if (multipleSelection.value.length === 0) {
message.warning('请至少选择一条记录')
return
}
// 导出的二次确认
await message.exportConfirm()
// 获取选中行的ID
const ids = multipleSelection.value.map(item => item.id)
// 发起导出
exportBatchLoading.value = true
const data = await StockRecordApi.exportStockRecordWithQuality(ids)
download.excel(data, '产品批次质检明细.xls')
} catch (error) {
console.error(error)
} finally {
exportBatchLoading.value = false
}
}
/** 初始化 **/
onMounted(async () => {
// 产品列表
const res = await ProductApi.getProductSimpleList()
productList.value = res
// 获取分类列表
const categoryRes = await ProductCategoryApi.getProductCategorySimpleList()
categoryList.value = categoryRes
// 仓库列表
const warehouseRes = await WarehouseApi.getWarehouseSimpleList()
warehouseList.value = warehouseRes
// 加载列表
await getList()
})
</script>
<style lang="scss" scoped>
.mobile-record {
padding: 12px;
background: #f5f5f5;
min-height: 100vh;
}
.mobile-header {
display: flex;
gap: 8px;
align-items: center;
margin-bottom: 12px;
&__search {
flex: 1;
}
&__actions {
display: flex;
gap: 4px;
}
}
.mobile-quick-actions {
display: flex;
gap: 8px;
margin-bottom: 12px;
}
.mobile-selection-tip {
padding: 8px 12px;
margin-bottom: 12px;
background: #ecf5ff;
color: #409eff;
border-radius: 8px;
font-size: 13px;
text-align: center;
}
.mobile-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.mobile-empty {
padding: 40px 0;
}
.mobile-card {
background: #fff;
border-radius: 10px;
padding: 14px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
display: flex;
gap: 10px;
&__checkbox {
display: flex;
align-items: flex-start;
padding-top: 2px;
}
&__content {
flex: 1;
min-width: 0;
}
&__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
&__name {
font-weight: 600;
font-size: 15px;
color: #303133;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 10px;
}
&__body {
font-size: 13px;
}
&__row {
display: flex;
justify-content: space-between;
padding: 3px 0;
&--highlight {
background: #f0f9ff;
padding: 5px 8px;
margin: 2px -8px;
border-radius: 4px;
.mobile-card__value {
color: #409eff;
font-weight: 600;
}
}
}
&__label {
color: #909399;
flex-shrink: 0;
margin-right: 12px;
}
&__value {
color: #606266;
text-align: right;
&--ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 200px;
}
&--price {
color: #f56c6c;
font-weight: 600;
}
}
&__footer {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid #f0f0f0;
text-align: center;
.el-button {
width: 100%;
}
}
}
.mobile-pagination {
margin-top: 12px;
display: flex;
justify-content: center;
:deep(.el-pagination) {
flex-wrap: wrap;
justify-content: center;
}
}
</style>