You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1461 lines
45 KiB

<template>
<div>
<!-- 盘点计划 Section -->
<div class="content-wrapper">
<div class="page-header">
<h1 class="page-title">盘点计划</h1>
<Button type="primary" @click="showPlanModal('new')"></Button>
</div>
<!-- Plan Search Filters -->
<div class="search-filters">
<div class="filter-item">
<span class="selector-item__label">关键词:</span>
<Input v-model="planSearch.keyword" style="width: 180px" :clearable="true" placeholder="计划名称/编号" />
</div>
<div class="filter-item">
<span class="selector-item__label">计划日期:</span>
<DatePicker v-model="planSearch.dateRange" type="daterange" split-panels style="width: 200px" :clearable="true"></DatePicker>
</div>
<div class="filter-item">
<span class="selector-item__label">状态:</span>
<Select v-model="planSearch.status" style="width: 120px" :clearable="true">
<Option value="0">未开始</Option>
<Option value="1">进行中</Option>
<Option value="2">已完成</Option>
</Select>
</div>
<div class="filter-item">
<Button type="primary" @click="searchPlans"></Button>
<Button style="margin-left: 10px" @click="resetPlanSearch"></Button>
</div>
</div>
<!-- Plan Table -->
<Table :columns="planColumns" :data="planList" :loading="planLoading">
<template slot-scope="{ row }" slot="status">
<Tag :color="getStatusColor(row.status)">{{ getStatusText(row.status) }}</Tag>
</template>
<template slot-scope="{ row }" slot="action">
<div style="display: flex; gap: 8px; justify-content: center;">
<Button type="primary" size="small" style="border-radius: 6px;" @click="showInventorySelectModal(row.id)"></Button>
<Button type="primary" size="small" style="border-radius: 6px;" ghost @click="showPlanModal('edit', row)">编辑</Button>
<Button type="error" size="small" style="border-radius: 6px;" ghost @click="deletePlan(row.id)"></Button>
</div>
</template>
</Table>
<!-- Plan Pagination -->
<div class="pagination-container">
<el-pagination
@size-change="handlePlanPageSizeChange"
@current-change="handlePlanPageChange"
:current-page="planSearch.pageIndex"
:page-sizes="[10, 20, 50, 100]"
:page-size="planSearch.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="planTotal"
/>
</div>
</div>
<!-- 盘点列表 Section -->
<div class="content-wrapper" style="margin-top: 20px">
<div class="page-header">
<h1 class="page-title">盘点列表</h1>
</div>
<!-- List Search Filters -->
<div class="search-filters">
<div class="filter-item">
<span class="selector-item__label">关键词:</span>
<Input v-model="listSearch.keyword" :clearable="true" style="width: 180px" placeholder="单号/计划/区域/执行人" />
</div>
<div class="filter-item">
<span class="selector-item__label">所属计划:</span>
<Select v-model="listSearch.planId" style="width: 180px" :clearable="true">
<Option v-for="plan in planList" :key="plan.id" :value="plan.id">{{ plan.name }}</Option>
</Select>
</div>
<div class="filter-item">
<span class="selector-item__label">状态:</span>
<Select v-model="listSearch.status" style="width: 120px" :clearable="true">
<Option value="0">未盘点</Option>
<Option value="1">已盘点</Option>
</Select>
</div>
<div class="filter-item">
<Button type="primary" @click="searchList"></Button>
<Button style="margin-left: 10px" @click="resetListSearch"></Button>
</div>
</div>
<!-- Inventory List Table -->
<Table :columns="listColumns" :data="inventoryList" :loading="listLoading">
<template slot-scope="{ row }" slot="planName">
{{ row.material_infos_plan ? row.material_infos_plan.name : '-' }}
</template>
<template slot-scope="{ row }" slot="area">
{{ row.material_info ? row.material_info.suozaicangku : '-' }}
</template>
<template slot-scope="{ row }" slot="executor">
{{ row.responsible_admin ? row.responsible_admin.name : '-' }}
</template>
<template slot-scope="{ row }" slot="startTime">
{{ row.material_infos_plan ? row.material_infos_plan.start_date : '-' }}
</template>
<template slot-scope="{ row }" slot="endTime">
{{ row.material_infos_plan ? row.material_infos_plan.end_date : '-' }}
</template>
<template slot-scope="{ row }" slot="status">
<Tag :color="getInventoryStatusColor(row.status)">{{ getInventoryStatusText(row.status) }}</Tag>
</template>
<template slot-scope="{ row }" slot="action">
<div style="display: flex; gap: 8px; justify-content: center;">
<Button type="primary" size="small" style="border-radius: 6px;" @click="viewInventoryDetail(row)"></Button>
</div>
</template>
</Table>
<!-- List Pagination -->
<div class="pagination-container">
<el-pagination
@size-change="handleListPageSizeChange"
@current-change="handleListPageChange"
:current-page="listSearch.pageIndex"
:page-sizes="[10, 20, 50, 100]"
:page-size="listSearch.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="listTotal"
/>
</div>
</div>
<!-- Plan Create/Edit Modal -->
<Modal v-model="planModal.visible" :title="planModal.title">
<Form ref="planForm" :model="planModal.form" :rules="planModal.rules" width="80">
<FormItem label="计划名称" prop="name">
<Input v-model="planModal.form.name" placeholder="请输入计划名称" :clearable="true"/>
</FormItem>
<FormItem label="计划日期" prop="dateRange">
<DatePicker
v-model="planModal.form.dateRange"
type="daterange"
split-panels
style="width: 100%"
@on-clear="handleDateClear"
:clearable="true"
:editable="false"
/>
</FormItem>
<FormItem label="备注" prop="remark">
<Input v-model="planModal.form.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
</FormItem>
</Form>
<template slot="footer">
<Button @click="planModal.visible = false">取消</Button>
<Button type="primary" @click="handlePlanSubmit"></Button>
</template>
</Modal>
<!-- Inventory Selection Modal -->
<Modal v-model="materialModal.visible" title="选择盘点物资" width="900">
<div class="material-search">
<Input v-model="materialModal.keyword" placeholder="物资名称" style="width: 180px; margin-right: 10px;" />
<Select v-model="materialModal.storehouses_id" @on-change="onStorehouseTypeChange" clearable placeholder="仓库类型" style="width: 120px; margin-right: 10px;">
<Option v-for="item in storehouseTypeOptions" :key="item.value" :value="item.value">{{ item.label }}</Option>
</Select>
<Select v-model="materialModal.area" @on-change="onAreaChange" clearable placeholder="所在区域" style="width: 120px; margin-right: 10px;">
<Option v-for="item in areaOptions" :key="item.value" :value="item.value">{{ item.value }}</Option>
</Select>
<Select v-model="materialModal.warehouseName" clearable placeholder="仓库名称" style="width: 140px; margin-right: 10px;">
<Option v-for="item in warehouseNameOptions" :key="item.value" :value="item.value">{{ item.label }}</Option>
</Select>
<Button type="primary" style="margin-left: 10px" @click="searchMaterials"></Button>
<Button style="margin-left: 10px" @click="resetMaterialSearch"></Button>
</div>
<el-table
ref="materialTable"
:data="materialList"
v-loading="materialModal.loading"
@select="handleSelect"
@select-all="handleSelectAll"
row-key="id"
>
<el-table-column
type="selection"
width="55">
</el-table-column>
<el-table-column
prop="zichanmingcheng"
label="物资名称">
</el-table-column>
<el-table-column
prop="guigexinghao"
label="规格型号">
</el-table-column>
<el-table-column
prop="jiliangdanwei"
label="单位">
</el-table-column>
<el-table-column
prop="inventorys_total"
label="当前库存">
<template slot-scope="scope">
{{ scope.row.inventorys_total === null ? '0' : scope.row.inventorys_total }}
</template>
</el-table-column>
<el-table-column
label="操作"
width="100">
<template slot-scope="scope">
<div style="display: flex; gap: 8px; justify-content: center;">
<el-button
:type="isSelected(scope.row) ? 'warning' : 'success'"
size="small"
style="border-radius: 6px;"
@click="toggleMaterialSelection(scope.row, scope.$index)"
>
{{ isSelected(scope.row) ? '移出计划' : '加入计划' }}
</el-button>
</div>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination
@size-change="handleMaterialPageSizeChange"
@current-change="handleMaterialPageChange"
:current-page="materialModal.pageIndex"
:page-sizes="[10, 20, 50, 100]"
:page-size="materialModal.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="materialModal.total"
/>
</div>
<template slot="footer">
<Button @click="materialModal.visible = false">取消</Button>
<Button type="primary" @click="handleMaterialSubmit"></Button>
</template>
</Modal>
<!-- 盘点任务详情弹窗 -->
<Modal
v-model="detailModal.visible"
title="盘点任务详情"
width="1000"
:mask-closable="true"
>
<div class="detail-content" v-if="detailModal.data">
<div class="detail-header">
<div class="detail-item">
<span class="label">盘点单号</span>
<span class="value">{{ detailModal.data.no }}</span>
</div>
<div class="detail-item">
<span class="label">所属计划</span>
<span class="value">{{ detailModal.data.planName }}</span>
</div>
<div class="detail-item">
<span class="label">盘点区域</span>
<span class="value">{{ detailModal.data.area }}</span>
</div>
<div class="detail-item">
<span class="label">执行人</span>
<span class="value">{{ detailModal.data.executor }}</span>
</div>
<div class="detail-item">
<span class="label">开始时间</span>
<span class="value">{{ detailModal.data.startTime }}</span>
</div>
<div class="detail-item">
<span class="label">完成时间</span>
<span class="value">{{ detailModal.data.endTime || '-' }}</span>
</div>
<div class="detail-item">
<span class="label">状态</span>
<span class="value">
<Tag :color="getInventoryStatusColor(detailModal.data.status)">
{{ getInventoryStatusText(detailModal.data.status) }}
</Tag>
</span>
</div>
</div>
<div class="material-info-section">
<h3>物资基本信息</h3>
<div class="material-info-content">
<div class="info-item">
<span class="label">物资名称</span>
<span class="value">{{ detailModal.data.material_info && detailModal.data.material_info.zichanmingcheng || '-' }}</span>
</div>
<div class="info-item">
<span class="label">物资代码</span>
<span class="value">{{ detailModal.data.material_info && detailModal.data.material_info.wuzibianma || '-' }}</span>
</div>
<div class="info-item">
<span class="label">规格型号</span>
<span class="value">{{ detailModal.data.material_info && detailModal.data.material_info.guigexinghao || '-' }}</span>
</div>
<div class="info-item">
<span class="label">计量单位</span>
<span class="value">{{ detailModal.data.material_info && detailModal.data.material_info.jiliangdanwei || '-' }}</span>
</div>
<div class="info-item">
<span class="label">当前库存</span>
<span class="value">{{ detailModal.data.material_info && detailModal.data.material_info.inventorys_total || '0' }}</span>
</div>
</div>
</div>
<div class="history-section">
<h3>盘点历史记录</h3>
<Table
:columns="detailColumns"
:data="detailModal.data.history || []"
:loading="detailModal.loading"
border
>
<template slot-scope="{ row }" slot="status">
<Tag :color="getInventoryStatusColor(row.status)">{{ getInventoryStatusText(row.status) }}</Tag>
</template>
<template slot-scope="{ row }" slot="photos">
<div class="photo-list" v-if="row.files && row.files.length">
<div class="photo-item" v-for="(file, index) in row.files.slice(0, 3)" :key="index">
<img :src="file.url" @click="previewImage(file.url)" />
</div>
<div class="photo-more" v-if="row.files.length > 3">
+{{ row.files.length - 3 }}
</div>
</div>
<span v-else>-</span>
</template>
</Table>
<div class="pagination-container">
<el-pagination
@size-change="handleDetailPageSizeChange"
@current-change="handleDetailPageChange"
:current-page="detailModal.pageIndex"
:page-sizes="[10, 20, 50, 100]"
:page-size="detailModal.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="detailModal.total"
/>
</div>
</div>
</div>
<template slot="footer">
<Button @click="detailModal.visible = false">关闭</Button>
</template>
</Modal>
<!-- 预览弹窗 -->
<div v-if="previewUrl" class="image-preview-modal" @click="closePreview">
<img :src="previewUrl" class="image-preview-large" />
</div>
</div>
</template>
<script>
import { saveStocktakingPlan, getStocktakingPlanList, deleteStocktakingPlan, getStocktakingPlanLinkList, getStocktakingPlanLinkDetail, getStocktakingHistoryList } from '@/api/system/stocktaking'
import { getStorehouseTypeList } from '@/api/system/storehouseType'
import { getparameteritem } from '@/api/system/dictionary'
import { index as getWarehouseList } from '@/api/system/baseForm'
import qs from 'qs'
import request from '@/utils/request'
export default {
data() {
return {
planSearch: {
keyword: '',
dateRange: [],
status: '',
pageIndex: 1,
pageSize: 10
},
planList: [],
planTotal: 0,
planLoading: false,
planColumns: [
{ title: '计划编号', key: 'no' },
{ title: '计划名称', key: 'name' },
{ title: '开始日期', key: 'start_date' },
{ title: '结束日期', key: 'end_date' },
{
title: '状态',
slot: 'status',
key: 'status'
},
{
title: '操作',
slot: 'action',
width: 200
}
],
listSearch: {
keyword: '',
planId: '',
status: '',
pageIndex: 1,
pageSize: 10
},
inventoryList: [],
listTotal: 0,
listLoading: false,
listColumns: [
{ title: '盘点单号', key: 'no' },
{
title: '所属计划',
slot: 'planName',
key: 'material_infos_plan.name'
},
{
title: '盘点区域',
slot: 'area',
key: 'material_info.suozaicangku'
},
{
title: '执行人',
slot: 'executor',
key: 'responsible_admin.name'
},
{
title: '开始时间',
slot: 'startTime',
key: 'material_infos_plan.start_date'
},
{
title: '完成时间',
slot: 'endTime',
key: 'material_infos_plan.end_date'
},
{
title: '状态',
slot: 'status',
key: 'status'
},
{
title: '操作',
slot: 'action',
width: 100
}
],
planModal: {
visible: false,
title: '新建盘点计划',
form: {
id: '',
name: '',
dateRange: [],
remark: ''
},
rules: {
name: [{ required: true, message: '请输入计划名称', trigger: 'blur' }],
dateRange: [
{
required: true,
type: 'array',
min: 2,
message: '请选择计划日期',
trigger: 'change',
validator: (rule, value, callback) => {
if (!value || value.length !== 2) {
callback(new Error('请选择计划日期'));
} else {
callback();
}
}
}
]
}
},
materialModal: {
visible: false,
loading: false,
keyword: '',
storehouses_id: '',
area: '',
warehouseName: '',
currentPlanId: '',
pageIndex: 1,
pageSize: 10,
total: 0,
selectedMaterialIds: new Set(),
isInitialLoad: true
},
materialList: [],
materialColumns: [
{ type: 'selection', width: 60 },
{ title: '物资名称', key: 'zichanmingcheng' },
{ title: '规格型号', key: 'guigexinghao' },
{ title: '单位', key: 'jiliangdanwei' },
{
title: '当前库存',
key: 'inventorys_total',
render: (h, params) => {
return h('span', params.row.inventorys_total === null ? '0' : params.row.inventorys_total)
}
},
{
title: '操作',
slot: 'action',
width: 100
}
],
planMaterials: {
'P202504001': ['M002', 'M004'],
'P202505001': ['M001', 'M003', 'M006']
},
storehouseTypeOptions: [],
areaOptions: [],
warehouseNameOptions: [],
selectedRows: [],
detailModal: {
visible: false,
loading: false,
data: null,
pageIndex: 1,
pageSize: 10,
total: 0
},
detailColumns: [
{
title: '盘点日期',
key: 'check_date',
width: 180,
align: 'center'
},
{
title: '盘点数量',
key: 'check_num',
width: 100,
align: 'center'
},
{
title: '状态',
slot: 'status',
key: 'status',
width: 100,
align: 'center'
},
{
title: '备注',
key: 'remark',
minWidth: 200,
tooltip: true,
align: 'center'
},
{
title: '照片',
slot: 'photos',
width: 200,
align: 'center'
}
],
previewUrl: ''
}
},
mounted() {
this.searchPlans()
this.searchList()
this.initMaterialSelectOptions()
},
methods: {
getStatusColor(status) {
const colors = {
'0': 'red',
'1': 'orange',
'2': 'green'
}
return colors[status] || 'default'
},
getStatusText(status) {
const texts = {
'0': '未开始',
'1': '进行中',
'2': '已完成'
}
return texts[status] || status
},
getInventoryStatusColor(status) {
const colors = {
'0': 'red',
'1': 'green'
}
return colors[status] || 'default'
},
getInventoryStatusText(status) {
const texts = {
'0': '未盘点',
'1': '已盘点'
}
return texts[status] || status
},
async searchPlans() {
this.planLoading = true;
try {
const formData = new FormData();
formData.append('page', this.planSearch.pageIndex);
formData.append('page_size', this.planSearch.pageSize);
// 只添加非空的搜索条件
if (this.planSearch.keyword) {
formData.append('keyword', this.planSearch.keyword);
}
// 添加状态过滤
if (this.planSearch.status) {
const filterIndex = this.planSearch.keyword ? 1 : 0;
formData.append(`filter[${filterIndex}][key]`, 'status');
formData.append(`filter[${filterIndex}][op]`, 'eq');
formData.append(`filter[${filterIndex}][value]`, this.planSearch.status);
}
// 确保日期范围有效且不为空
if (this.planSearch.dateRange &&
this.planSearch.dateRange.length === 2 &&
this.planSearch.dateRange[0] &&
this.planSearch.dateRange[1]) {
const formatDate = (date) => {
const d = new Date(date);
if (isNaN(d.getTime())) return null; // 检查日期是否有效
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
const startDate = formatDate(this.planSearch.dateRange[0]);
const endDate = formatDate(this.planSearch.dateRange[1]);
// 只有当两个日期都有效时才添加过滤条件
if (startDate && endDate) {
const filterIndex = (this.planSearch.keyword ? 1 : 0) + (this.planSearch.status ? 1 : 0);
formData.append(`filter[${filterIndex}][key]`, 'start_date');
formData.append(`filter[${filterIndex}][op]`, 'range');
formData.append(`filter[${filterIndex}][value]`, startDate+','+endDate);
}
}
const res = await getStocktakingPlanList(formData);
if (res && res.list) {
this.planList = res.list.data;
this.planTotal = res.list.total;
}
} catch (error) {
this.$Message.error('获取盘点计划列表失败');
console.error('获取盘点计划列表失败:', error);
} finally {
this.planLoading = false;
}
},
resetPlanSearch() {
this.planSearch = {
keyword: '',
dateRange: [],
status: '',
pageIndex: 1,
pageSize: 10
}
this.searchPlans()
},
handlePlanPageChange(page) {
this.planSearch.pageIndex = page
this.searchPlans()
},
handlePlanPageSizeChange(size) {
this.planSearch.pageSize = size
this.planSearch.pageIndex = 1
this.searchPlans()
},
showPlanModal(mode, plan) {
this.planModal.title = mode === 'new' ? '新建盘点计划' : '编辑盘点计划'
if (mode === 'edit' && plan) {
this.planModal.form = {
id: plan.id,
name: plan.name,
dateRange: [plan.start_date, plan.end_date],
remark: plan.remark
}
this.planModal.visible = true
} else {
this.planModal.form = {
id: '',
name: '',
dateRange: [],
remark: ''
}
this.planModal.visible = true
this.$nextTick(() => {
if (this.$refs.planForm) {
this.$refs.planForm.resetFields();
}
});
}
},
handleDateClear() {
this.planModal.form.dateRange = null;
this.$nextTick(() => {
this.planModal.form.dateRange = [];
this.$refs.planForm.validateField('dateRange');
});
},
async handlePlanSubmit() {
this.$refs.planForm.validateField('dateRange', (errorMessage) => {
if (errorMessage) {
this.$Message.error(errorMessage);
return;
}
});
this.$refs.planForm.validate(async (valid) => {
if (!valid) {
this.$Message.error('请完整填写必填项');
return;
}
const { id, name, dateRange, remark } = this.planModal.form;
const formatDate = (date) => {
const d = new Date(date);
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
// 获取当前计划选中的物资ID
const selectedMaterialIds = this.planMaterials[id] || [];
// 构建 formdata 格式的数据
const formData = new FormData();
formData.append('name', name);
formData.append('start_date', formatDate(dateRange[0]));
formData.append('end_date', formatDate(dateRange[1]));
if (remark) formData.append('remark', remark);
if (id) formData.append('id', id);
// 添加选中的物资ID数组
selectedMaterialIds.forEach((materialId, index) => {
formData.append(`material_infos_plan_links[${index}][material_info_id]`, materialId);
});
try {
await saveStocktakingPlan(formData);
this.$Message.success('保存成功');
this.planModal.visible = false;
this.$refs.planForm.resetFields();
this.searchPlans();
} catch (e) {
this.$Message.error('保存失败');
}
});
},
deletePlan(id) {
this.$confirm('确认要删除该计划吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
await deleteStocktakingPlan({ id })
this.$message.success('删除成功')
this.searchPlans()
}).catch(() => {});
},
async searchList() {
this.listLoading = true;
try {
const formData = new FormData();
formData.append('page', this.listSearch.pageIndex);
formData.append('page_size', this.listSearch.pageSize);
// 只添加非空的搜索条件
if (this.listSearch.keyword) {
formData.append('keyword', this.listSearch.keyword);
}
// 计划筛选
if (this.listSearch.planId) {
const filterIndex = this.listSearch.keyword ? 1 : 0;
formData.append(`filter[${filterIndex}][key]`, 'material_infos_plan_id');
formData.append(`filter[${filterIndex}][op]`, 'eq');
formData.append(`filter[${filterIndex}][value]`, this.listSearch.planId);
}
// 状态筛选
if (this.listSearch.status) {
const filterIndex = (this.listSearch.keyword ? 1 : 0) + (this.listSearch.planId ? 1 : 0);
formData.append(`filter[${filterIndex}][key]`, 'status');
formData.append(`filter[${filterIndex}][op]`, 'eq');
formData.append(`filter[${filterIndex}][value]`, this.listSearch.status);
}
const res = await getStocktakingPlanLinkList(formData);
if (res && res.list) {
this.inventoryList = res.list.data;
this.listTotal = res.list.total;
}
} catch (error) {
this.$Message.error('获取盘点列表失败');
console.error('获取盘点列表失败:', error);
} finally {
this.listLoading = false;
}
},
resetListSearch() {
this.listSearch = {
keyword: '',
planId: '',
status: '',
pageIndex: 1,
pageSize: 10
}
this.searchList()
},
handleListPageChange(page) {
this.listSearch.pageIndex = page
this.searchList()
},
handleListPageSizeChange(size) {
this.listSearch.pageSize = size
this.listSearch.pageIndex = 1
this.searchList()
},
async viewInventoryDetail(row) {
this.detailModal.loading = true;
this.detailModal.visible = true;
this.detailModal.pageIndex = 1;
// 先用列表行数据初始化基本信息
this.detailModal.data = {
id: row.id,
no: row.no,
planName: row.material_infos_plan ? row.material_infos_plan.name : '-',
area: row.material_info ? row.material_info.suozaicangku : '-',
executor: row.responsible_admin ? row.responsible_admin.name : '-',
startTime: row.material_infos_plan ? row.material_infos_plan.start_date : '-',
endTime: row.material_infos_plan ? row.material_infos_plan.end_date : '-',
status: row.status,
material_info: row.material_info || {},
history: []
};
try {
// 1. 获取物资详情
const materialRes = await request({
url: '/api/admin/material-infos/show',
method: 'get',
params: { id: row.material_info.id }
});
if (materialRes) {
this.detailModal.data.material_info = materialRes;
}
// 2. 获取盘点历史记录
const params = {};
params['filter[0][key]'] = 'material_info_id';
params['filter[0][op]'] = 'eq';
params['filter[0][value]'] = row.material_info.id;
params['page'] = this.detailModal.pageIndex || 1;
params['page_size'] = this.detailModal.pageSize || 10;
const res = await getStocktakingHistoryList(qs.stringify(params));
if (res) {
// 更新物资信息和历史记录数据
this.detailModal.data = {
...this.detailModal.data,
history: res.data || []
};
this.detailModal.total = res.data ? res.data.length : 0;
}
} catch (error) {
this.$Message.error('获取盘点任务详情失败');
console.error('获取盘点任务详情失败:', error);
} finally {
this.detailModal.loading = false;
}
},
showInventorySelectModal(planId) {
this.materialModal.currentPlanId = planId
this.materialModal.visible = true
this.materialModal.pageIndex = 1
this.materialModal.selectedMaterialIds = new Set();
this.searchMaterials()
},
isSelected(item) {
return this.materialModal.selectedMaterialIds.has(item.id);
},
handleSelect(selection, item) {
console.log('handleSelect called', selection, item);
// 更新选中状态集合
const planId = this.materialModal.currentPlanId;
if (planId) {
if (selection.includes(item)) {
this.materialModal.selectedMaterialIds.add(item.id);
} else {
this.materialModal.selectedMaterialIds.delete(item.id);
}
// 更新界面
this.$nextTick(() => {
if (this.$refs.materialTable) {
this.materialList.forEach(row => {
this.$refs.materialTable.toggleRowSelection(row, this.materialModal.selectedMaterialIds.has(row.id));
});
}
});
}
},
handleSelectAll(selection) {
console.log('handleSelectAll called', selection);
const planId = this.materialModal.currentPlanId;
if (planId) {
// 更新选中状态集合
if (selection.length > 0) {
// 全选:将所有当前页的项添加到选中集合
this.materialList.forEach(item => {
this.materialModal.selectedMaterialIds.add(item.id);
});
} else {
// 取消全选:从选中集合中移除当前页的所有项
this.materialList.forEach(item => {
this.materialModal.selectedMaterialIds.delete(item.id);
});
}
// 更新界面
this.$nextTick(() => {
if (this.$refs.materialTable) {
// 更新复选框状态
this.materialList.forEach(row => {
this.$refs.materialTable.toggleRowSelection(row, this.materialModal.selectedMaterialIds.has(row.id));
});
}
});
}
},
toggleMaterialSelection(item, index) {
console.log('toggleMaterialSelection called', item, index);
// 更新选中状态
if (!this.isSelected(item)) {
console.log('Adding to plan');
this.materialModal.selectedMaterialIds.add(item.id);
} else {
console.log('Removing from plan');
this.materialModal.selectedMaterialIds.delete(item.id);
}
// 更新界面
this.$nextTick(() => {
if (this.$refs.materialTable) {
this.materialList.forEach(row => {
this.$refs.materialTable.toggleRowSelection(row, this.materialModal.selectedMaterialIds.has(row.id));
});
}
});
},
async searchMaterials() {
this.materialModal.loading = true;
this.materialModal.isInitialLoad = true; // 重置标志
try {
const data = {
page_size: this.materialModal.pageSize,
page: this.materialModal.pageIndex,
'materialstorages_cangkumingcheng': this.materialModal.warehouseName || '',
'materialstorages_suozaiquyu': this.materialModal.area || '',
'storehouses_id': this.materialModal.storehouses_id || '',
'filter[0][key]': 'zichanmingcheng',
'filter[0][op]': 'like',
'filter[0][value]': this.materialModal.keyword || '',
'filter[1][key]': 'wuzileixing',
'filter[1][op]': 'eq',
'filter[1][value]': '一物一码'
};
const res = await request({
url: '/api/admin/material-infos/index',
method: 'post',
data: qs.stringify(data),
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
if (res && res.data) {
this.materialList = res.data;
this.materialModal.total = res.total || 0;
// 初始化选中状态
const planId = this.materialModal.currentPlanId;
if (planId) {
// 如果是第一次加载,初始化选中状态
if (this.materialModal.selectedMaterialIds.size === 0) {
this.materialList.forEach(item => {
if (item.material_infos_plan_link && item.material_infos_plan_link.length > 0) {
// 遍历物资绑定的所有盘点计划
const hasMatchingPlan = item.material_infos_plan_link.some(link =>
link.material_infos_plan_id === planId
);
if (hasMatchingPlan) {
console.log('item.id', item.id);
this.materialModal.selectedMaterialIds.add(item.id);
}
}
});
}
console.log('this.materialModal.selectedMaterialIds', this.materialModal.selectedMaterialIds)
}
}
} finally {
this.materialModal.loading = false;
}
},
async batchAddMaterials() {
const selection = this.materialList.filter(m => m.selected)
if (selection.length === 0) {
this.$Message.warning('请至少选择一项物资')
return
}
const planId = this.materialModal.currentPlanId
if (!this.planMaterials[planId]) {
this.planMaterials[planId] = []
}
selection.forEach(material => {
if (!this.planMaterials[planId].includes(material.id)) {
this.planMaterials[planId].push(material.id)
}
})
this.$Message.success(`已批量加入 ${selection.length} 项物资`)
this.searchMaterials()
},
async batchRemoveMaterials() {
const selection = this.materialList.filter(m => m.selected)
if (selection.length === 0) {
this.$Message.warning('请至少选择一项物资')
return
}
const planId = this.materialModal.currentPlanId
if (this.planMaterials[planId]) {
this.planMaterials[planId] = this.planMaterials[planId].filter(
id => !selection.some(m => m.id === id)
)
}
this.$Message.success(`已批量移出 ${selection.length} 项物资`)
this.searchMaterials()
},
async initMaterialSelectOptions() {
// 仓库类型
try {
const res = await getStorehouseTypeList({ page: 1, page_size: 999 })
this.storehouseTypeOptions = (res.data || []).map(item => ({
value: item.id,
label: item.name
}))
} catch (e) {
this.storehouseTypeOptions = []
}
// 区域
try {
const res = await getparameteritem('area')
this.areaOptions = (res.detail || []).map(item => ({
value: item.value,
label: item.label
}))
} catch (e) {
this.areaOptions = []
}
// 仓库名称
try {
const res = await getWarehouseList({
page: 1,
page_size: 999,
table_name: 'materialstorages'
})
this.warehouseNameOptions = (res.data || []).map(item => ({
value: item.id,
label: item.cangkumingcheng
}))
} catch (e) {
this.warehouseNameOptions = []
}
},
resetMaterialSearch() {
this.materialModal.keyword = ''
this.materialModal.storehouses_id = ''
this.materialModal.area = ''
this.materialModal.warehouseName = ''
this.materialModal.pageIndex = 1
this.searchMaterials()
},
async onStorehouseTypeChange(val) {
this.materialModal.storehouses_id = val
await this.updateWarehouseNameOptions()
},
async onAreaChange(val) {
this.materialModal.area = val
await this.updateWarehouseNameOptions()
},
async updateWarehouseNameOptions() {
// 联动过滤仓库名称
try {
const res = await getWarehouseList({
page: 1,
page_size: 999,
table_name: 'materialstorages',
filter: [
{ key: 'storehouses_id', op: 'eq', value: this.materialModal.storehouses_id || '' },
{ key: 'quyu_id', op: 'eq', value: this.materialModal.area || '' }
]
})
this.warehouseNameOptions = (res.data || []).map(item => ({
value: item.id,
label: item.cangkumingcheng
}))
} catch (e) {
this.warehouseNameOptions = []
}
},
handleMaterialPageChange(page) {
this.materialModal.pageIndex = page
this.searchMaterials()
},
handleMaterialPageSizeChange(size) {
this.materialModal.pageSize = size
this.materialModal.pageIndex = 1
this.searchMaterials()
},
async handleMaterialSubmit() {
const planId = this.materialModal.currentPlanId;
if (!planId) {
this.$Message.error('未找到计划ID');
return;
}
// 获取当前选中的物资ID
const selectedMaterialIds = Array.from(this.materialModal.selectedMaterialIds);
// 构建formdata
const formData = new FormData();
formData.append('id', planId);
selectedMaterialIds.forEach((materialId, index) => {
formData.append(`material_infos_plan_links[${index}][material_info_id]`, materialId);
});
try {
await saveStocktakingPlan(formData);
this.$Message.success('物资关联成功');
this.materialModal.visible = false;
this.searchPlans();
} catch (e) {
this.$Message.error('物资关联失败');
}
},
handleDetailPageChange(page) {
this.detailModal.pageIndex = page;
this.viewInventoryDetail(this.detailModal.data.id);
},
handleDetailPageSizeChange(size) {
this.detailModal.pageSize = size;
this.detailModal.pageIndex = 1;
this.viewInventoryDetail(this.detailModal.data.id);
},
previewImage(url) {
this.previewUrl = url;
},
closePreview() {
this.previewUrl = '';
}
},
watch: {
'materialModal.loading': {
handler(newVal, oldVal) {
// 当loading从true变为false时说明表格加载完成
if (oldVal === true && newVal === false) {
this.$nextTick(() => {
if (this.$refs.materialTable) {
this.materialList.forEach((item, index) => {
if (this.materialModal.selectedMaterialIds.has(item.id)) {
this.$refs.materialTable.toggleRowSelection(item, true);
console.log('item.id1111111111111111111111', item.id)
}
});
}
});
}
}
}
}
}
</script>
<style scoped lang="scss">
.code {
position: absolute;
left: 0;
top: 0;
background: rgba(0, 0, 0, 0.5);
width: 100%;
height: 100%;
z-index: 9999
}
#qrCode {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.content-wrapper {
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.page-header {
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #e8e8e8;
display: flex;
justify-content: space-between;
align-items: center;
}
.page-title {
font-size: 1.3rem;
font-weight: 600;
color: #333;
margin: 0;
}
.search-filters {
padding: 15px 0;
margin-bottom: 20px;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 15px 20px;
}
.filter-item {
display: flex;
align-items: center;
gap: 8px;
}
.pagination-container {
display: flex;
justify-content: flex-end;
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #f0f0f0;
}
.el-pagination {
padding: 0;
font-weight: normal;
.el-pagination__sizes {
margin-right: 16px;
}
.el-pagination__jump {
margin-left: 16px;
}
.el-pagination__total {
margin-right: 16px;
}
.btn-prev,
.btn-next {
background: #fff;
border: 1px solid #dcdfe6;
&:hover {
color: #409eff;
}
}
.el-pager li {
background: #fff;
border: 1px solid #dcdfe6;
&:hover {
color: #409eff;
}
&.active {
background-color: #409eff;
color: #fff;
border-color: #409eff;
}
}
}
.material-search {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 10px;
}
.ivu-modal {
.ivu-modal-content {
width: 900px;
max-width: 90vw;
}
.ivu-modal-header {
padding: 16px 24px;
border-bottom: 1px solid #e8eaec;
.ivu-modal-header-inner {
font-size: 16px;
font-weight: 500;
color: #17233d;
}
}
.ivu-modal-body {
padding: 24px;
}
}
.ivu-table {
.ivu-table-cell {
padding: 8px 16px;
}
.ivu-table-header {
th {
background: #f8f8f9;
font-weight: 600;
color: #17233d;
}
}
.ivu-table-tbody {
tr {
&:hover {
td {
background: #f5f7fa;
}
}
}
}
}
.detail-content {
padding: 20px;
.detail-header {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
margin-bottom: 30px;
.detail-item {
.label {
font-weight: bold;
color: #666;
margin-right: 10px;
}
.value {
color: #333;
}
}
}
.material-info-section {
margin-bottom: 30px;
h3 {
margin: 0 0 15px 0;
font-size: 16px;
color: #17233d;
}
.material-info-content {
background: #f8f8f9;
padding: 20px;
border-radius: 8px;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
.info-item {
.label {
font-weight: bold;
color: #666;
margin-right: 10px;
}
.value {
color: #333;
}
}
}
}
.history-section {
h3 {
margin: 0 0 15px 0;
font-size: 16px;
color: #17233d;
}
}
}
.photo-list {
display: flex;
gap: 8px;
align-items: center;
.photo-item {
width: 40px;
height: 40px;
border-radius: 4px;
overflow: hidden;
cursor: pointer;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.photo-more {
color: #666;
font-size: 12px;
}
}
.image-preview-modal {
position: fixed;
z-index: 2000;
left: 0; top: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.7);
display: flex;
align-items: center;
justify-content: center;
}
.image-preview-large {
max-width: 90vw;
max-height: 90vh;
border-radius: 8px;
box-shadow: 0 4px 24px rgba(0,0,0,0.3);
background: #fff;
}
</style>