| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665 |
- <template>
- <!-- 物料导入弹窗 -->
- <el-dialog
- title="导入物料"
- :visible.sync="dialogVisible"
- width="1400px"
- top="5vh"
- append-to-body
- :close-on-click-modal="false"
- class="material-import-dialog"
- @close="handleDialogClose"
- >
- <!-- 弹窗内容 -->
- <div class="import-dialog-content">
- <!-- 搜索区域 -->
- <div class="search-section">
- <el-form
- ref="searchForm"
- :model="searchParams"
- :inline="true"
- class="search-form"
- >
- <el-form-item label="物料名称" prop="itemName">
- <el-input
- v-model="searchParams.itemName"
- placeholder="请输入物料名称"
- clearable
- style="width: 200px;"
- @keyup.enter.native="handleSearch"
- />
- </el-form-item>
- <el-form-item label="物料编码" prop="itemCode">
- <el-input
- v-model="searchParams.itemCode"
- placeholder="请输入物料编码"
- clearable
- style="width: 200px;"
- @keyup.enter.native="handleSearch"
- />
- </el-form-item>
- <el-form-item label="规格型号" prop="specification">
- <el-input
- v-model="searchParams.specification"
- placeholder="请输入规格型号"
- clearable
- style="width: 200px;"
- @keyup.enter.native="handleSearch"
- />
- </el-form-item>
- <el-form-item>
- <el-button
- type="primary"
- icon="el-icon-search"
- :loading="searchLoading"
- @click="handleSearch"
- >
- 搜索
- </el-button>
- <el-button
- icon="el-icon-refresh"
- @click="handleReset"
- >
- 重置
- </el-button>
- </el-form-item>
- </el-form>
- </div>
- <!-- 统计信息 -->
- <div class="statistics-section">
- <div class="stats-item">
- <span class="stats-label">搜索结果:</span>
- <span class="stats-value">{{ materialList.length }} 条</span>
- </div>
- <div class="stats-item">
- <span class="stats-label">已选择:</span>
- <span class="stats-value selected">{{ selectedMaterials.length }} 条</span>
- </div>
- </div>
- <!-- 物料列表表格 -->
- <div class="material-list-section">
- <avue-crud
- ref="materialCrud"
- :data="materialList"
- :option="tableOption"
- :page.sync="page"
- :loading="tableLoading"
- @selection-change="handleSelectionChange"
- @current-change="handleCurrentChange"
- @size-change="handleSizeChange"
- @refresh-change="handleRefresh"
- >
- <!-- 自定义可用数量列 -->
- <template slot="availableQuantity" slot-scope="{ row }">
- <span :class="{ 'low-stock': row.availableQuantity < 10 }">
- {{ row.availableQuantity }}
- </span>
- </template>
- </avue-crud>
- </div>
- <!-- 已选择物料预览 -->
- <div v-if="selectedMaterials.length > 0" class="selected-preview-section">
- <div class="preview-header">
- <span class="preview-title">已选择物料预览</span>
- <el-button
- type="text"
- size="small"
- icon="el-icon-delete"
- @click="handleClearSelection"
- >
- 清空选择
- </el-button>
- </div>
- <div class="preview-content">
- <el-tag
- v-for="material in selectedMaterials"
- :key="material.id"
- closable
- class="material-tag"
- @close="handleRemoveSelection(material)"
- >
- {{ material.itemName }} ({{ material.itemCode }})
- </el-tag>
- </div>
- </div>
- </div>
- <!-- 底部操作按钮 -->
- <div slot="footer" class="dialog-footer">
- <el-button @click="handleCancel">取消</el-button>
- <el-button
- type="primary"
- :disabled="selectedMaterials.length === 0"
- :loading="confirmLoading"
- @click="handleConfirm"
- >
- 确认导入 ({{ selectedMaterials.length }})
- </el-button>
- </div>
- </el-dialog>
- </template>
- <script>
- import { getMaterialImportOption, DEFAULT_QUERY_PARAMS } from './material-detail-option'
- import { MaterialDetailStatus } from './constants'
- import { getItemList } from '@/api/common'
- import { generateUniqueId } from './utils'
- /**
- * @typedef {import('./material-detail-option').MaterialDetailItem} MaterialDetailItem
- * @typedef {import('./material-detail-option').MaterialDetailQueryParams} MaterialDetailQueryParams
- */
- /**
- * API返回的原始物料数据项类型
- * @typedef {Object} RawItemRecord
- * @property {number} Item_ID - 物料ID
- * @property {string} Item_Code - 物料编码
- * @property {string} Item_Name - 物料名称
- * @property {string} Item_PECS - 物料规格
- * @property {number} MainItemCategory_ID - 主物料分类ID
- * @property {string} MainItemCategory_Name - 主物料分类名称
- * @property {number|null} Warehouse_ID - 仓库ID
- * @property {string|null} Warehouse_Name - 仓库名称
- * @property {number} [availableQuantity] - 可用数量
- */
- /**
- * 转换后的物料数据项类型
- * @typedef {Object} TransformedMaterialItem
- * @property {string|number} id - 物料唯一标识
- * @property {number} itemId - 物料ID
- * @property {string} itemCode - 物料编码
- * @property {string} itemName - 物料名称
- * @property {string} specification - 规格型号
- * @property {string} itemDescription - 物料描述
- * @property {number} mainCategoryId - 主物料分类ID
- * @property {string} mainCategoryName - 主物料分类名称
- * @property {string} mainCategoryCode - 主物料分类编码
- * @property {number} inventoryInfoId - 库存信息ID
- * @property {string} inventoryInfoName - 库存信息名称(单位)
- * @property {number|null} warehouseId - 仓库ID
- * @property {string|null} warehouseName - 仓库名称
- * @property {string} warehouseCode - 仓库编码
- * @property {number} saleserId - 销售员ID
- * @property {string} saleserName - 销售员名称
- * @property {number} shipmentWarehouseId - 发货仓库ID
- * @property {string} shipmentWarehouseName - 发货仓库名称
- * @property {number} orgId - 组织ID
- * @property {string} orgName - 组织名称
- * @property {string} createTime - 创建时间
- * @property {number} availableQuantity - 可用数量
- * @property {RawItemRecord} _raw - 原始数据备份
- */
- /**
- * 物料导入弹窗组件
- * @description 用于从明细管理接口搜索和选择物料进行导入
- * @author 系统开发团队
- * @version 1.0.0
- * @since 2024-01-15
- */
- export default {
- name: 'MaterialImportDialog',
- /**
- * 组件属性定义
- */
- props: {
- /**
- * 弹窗显示状态
- * @type {boolean}
- */
- visible: {
- type: Boolean,
- default: false
- }
- },
- /**
- * 组件数据
- */
- data() {
- return {
- /**
- * 弹窗显示状态(内部)
- * @type {boolean}
- */
- dialogVisible: false,
- /**
- * 搜索参数
- * @type {MaterialDetailQueryParams}
- */
- searchParams: { ...DEFAULT_QUERY_PARAMS },
- /**
- * 物料列表
- * @type {Array<TransformedMaterialItem>}
- */
- materialList: [],
- /**
- * 选中的物料列表
- * @type {Array<TransformedMaterialItem>}
- */
- selectedMaterials: [],
- /**
- * 分页配置
- * @type {Object}
- */
- page: {
- currentPage: 1,
- pageSize: 10,
- total: 0
- },
- /**
- * 搜索加载状态
- * @type {boolean}
- */
- searchLoading: false,
- /**
- * 表格加载状态
- * @type {boolean}
- */
- tableLoading: false,
- /**
- * 确认加载状态
- * @type {boolean}
- */
- confirmLoading: false
- }
- },
- /**
- * 计算属性
- */
- computed: {
- /**
- * 表格配置选项
- * @returns {Object} AvueJS表格配置
- */
- tableOption() {
- return getMaterialImportOption()
- }
- },
- /**
- * 监听器
- */
- watch: {
- /**
- * 监听弹窗显示状态
- * @param {boolean} newVal - 新的显示状态
- */
- visible: {
- handler(newVal) {
- this.dialogVisible = newVal
- if (newVal) {
- this.initDialog()
- }
- },
- immediate: true
- },
- /**
- * 监听内部弹窗状态
- * @param {boolean} newVal - 新的显示状态
- */
- dialogVisible(newVal) {
- this.$emit('update:visible', newVal)
- }
- },
- /**
- * 组件方法
- */
- methods: {
- /**
- * 初始化弹窗
- * @description 重置搜索参数和选中状态,不自动加载数据
- */
- initDialog() {
- this.resetSearchParams()
- this.selectedMaterials = []
- this.materialList = []
- this.page.total = 0
- },
- /**
- * 重置搜索参数
- */
- resetSearchParams() {
- this.searchParams = { ...DEFAULT_QUERY_PARAMS }
- this.page.currentPage = 1
- },
- /**
- * 加载物料列表
- * @description 调用明细管理接口获取物料数据,并转换字段格式
- */
- async loadMaterialList() {
- try {
- this.tableLoading = true
- const params = {
- ...this.searchParams,
- current: this.page.currentPage,
- size: this.page.pageSize
- }
- const response = await getItemList(params.current, params.size, {
- itemName: params.itemName,
- itemCode: params.itemCode,
- specification: params.specification
- })
- if (response && response.data && response.data.data) {
- const rawRecords = response.data.data.records || []
- // 转换API返回的字段名称为表格所需的格式
- this.materialList = rawRecords.map(item => ({
- id: item.Item_ID || item.id,
- itemId: item.Item_ID,
- itemCode: item.Item_Code,
- itemName: item.Item_Name,
- specification: item.Item_PECS || item.specification,
- itemDescription: item.Item_Description,
- mainCategoryId: item.MainItemCategory_ID,
- mainCategoryName: item.MainItemCategory_Name,
- mainCategoryCode: item.MainItemCategory_Code,
- inventoryInfoId: item.InventoryInfo_ID,
- inventoryInfoName: item.InventoryInfo_Name,
- warehouseId: item.Warehouse_ID,
- warehouseName: item.Warehouse_Name,
- warehouseCode: item.Warehouse_Code,
- saleserId: item.Saleser_ID,
- saleserName: item.Saleser_NAME,
- shipmentWarehouseId: item.ShipmentWarehouse_ID,
- shipmentWarehouseName: item.ShipmentWarehouse_Name,
- orgId: item.ORG_ID,
- orgName: item.ORG_NAME,
- createTime: item.createTime,
- availableQuantity: item.availableQuantity || 0,
- // 保留原始数据以备后用
- _raw: item
- }))
- this.page.total = response.data.data.total || 0
- } else {
- this.materialList = []
- this.page.total = 0
- }
- } catch (error) {
- this.$message.error('加载物料列表失败,请重试')
- this.materialList = []
- this.page.total = 0
- } finally {
- this.tableLoading = false
- }
- },
- /**
- * 处理搜索
- */
- async handleSearch() {
- this.searchLoading = true
- this.page.currentPage = 1
- await this.loadMaterialList()
- this.searchLoading = false
- },
- /**
- * 处理重置
- * @description 重置搜索参数并清空表格数据
- */
- handleReset() {
- this.resetSearchParams()
- this.materialList = []
- this.page.total = 0
- this.selectedMaterials = []
- },
- /**
- * 处理选择变化
- * @param {Array<TransformedMaterialItem>} selection - 选中的行数据
- */
- handleSelectionChange(selection) {
- this.selectedMaterials = selection
- },
- /**
- * 处理页码变化
- * @param {number} currentPage - 当前页码
- */
- async handleCurrentChange(currentPage) {
- this.page.currentPage = currentPage
- await this.loadMaterialList()
- },
- /**
- * 处理页大小变化
- * @param {number} pageSize - 页大小
- */
- async handleSizeChange(pageSize) {
- this.page.pageSize = pageSize
- this.page.currentPage = 1
- await this.loadMaterialList()
- },
- /**
- * 处理刷新
- */
- async handleRefresh() {
- await this.loadMaterialList()
- },
- /**
- * 处理清空选择
- */
- handleClearSelection() {
- this.selectedMaterials = []
- this.$refs.materialCrud.toggleSelection()
- },
- /**
- * 处理移除单个选择
- * @param {TransformedMaterialItem} material - 要移除的物料
- */
- handleRemoveSelection(material) {
- const index = this.selectedMaterials.findIndex(item => item.id === material.id)
- if (index > -1) {
- this.selectedMaterials.splice(index, 1)
- this.$refs.materialCrud.toggleRowSelection(material, false)
- }
- },
- /**
- * 处理确认导入
- */
- async handleConfirm() {
- if (this.selectedMaterials.length === 0) {
- this.$message.warning('请选择要导入的物料')
- return
- }
- try {
- this.confirmLoading = true
- // 转换为物料明细格式
- const importedMaterials = this.selectedMaterials.map((material) => ({
- id: generateUniqueId(),
- itemId: material.itemId || material.id,
- itemCode: material.itemCode,
- itemName: material.itemName,
- specification: material.specification,
- mainCategoryId: material.mainCategoryId,
- mainCategoryName: material.mainCategoryName,
- warehouseId: material.warehouseId,
- warehouseName: material.warehouseName,
- availableQuantity: material.availableQuantity || 0,
- orderQuantity: 1, // 默认订单数量为1
- confirmQuantity: 0,
- unitPrice: 0,
- taxRate: 0,
- taxAmount: 0,
- totalAmount: 0,
- detailStatus: MaterialDetailStatus.PENDING, // 默认状态为待确认
- createTime: new Date().toISOString(),
- updateTime: new Date().toISOString()
- }))
- this.$emit('confirm', importedMaterials)
- this.dialogVisible = false
- } catch (error) {
- this.$message.error('导入物料失败,请重试')
- } finally {
- this.confirmLoading = false
- }
- },
- /**
- * 处理取消
- */
- handleCancel() {
- this.dialogVisible = false
- this.$emit('cancel')
- },
- /**
- * 处理弹窗关闭
- */
- handleDialogClose() {
- this.selectedMaterials = []
- this.materialList = []
- this.resetSearchParams()
- }
- }
- }
- </script>
- <style scoped>
- .material-import-dialog {
- .el-dialog__body {
- padding: 20px;
- }
- }
- .import-dialog-content {
- max-height: 70vh;
- overflow-y: auto;
- }
- .search-section {
- background-color: #f8f9fa;
- padding: 16px;
- border-radius: 4px;
- margin-bottom: 16px;
- }
- .search-form {
- margin-bottom: 0;
- }
- .statistics-section {
- display: flex;
- gap: 24px;
- margin-bottom: 16px;
- padding: 12px 16px;
- background-color: #f0f9ff;
- border-radius: 4px;
- border-left: 4px solid #409eff;
- }
- .stats-item {
- display: flex;
- align-items: center;
- gap: 4px;
- }
- .stats-label {
- font-size: 14px;
- color: #606266;
- }
- .stats-value {
- font-size: 14px;
- font-weight: 600;
- color: #303133;
- }
- .stats-value.selected {
- color: #409eff;
- }
- .material-list-section {
- margin-bottom: 16px;
- }
- .low-stock {
- color: #f56c6c;
- font-weight: 600;
- }
- .selected-preview-section {
- border-top: 1px solid #ebeef5;
- padding-top: 16px;
- }
- .preview-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 12px;
- }
- .preview-title {
- font-size: 14px;
- font-weight: 600;
- color: #303133;
- }
- .preview-content {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- max-height: 120px;
- overflow-y: auto;
- padding: 8px;
- background-color: #f8f9fa;
- border-radius: 4px;
- }
- .material-tag {
- margin: 0;
- }
- .dialog-footer {
- text-align: right;
- padding-top: 16px;
- border-top: 1px solid #ebeef5;
- }
- /* 响应式设计 */
- @media (max-width: 768px) {
- .search-form {
- .el-form-item {
- margin-bottom: 12px;
- }
- }
- .statistics-section {
- flex-direction: column;
- gap: 8px;
- }
- .preview-content {
- max-height: 80px;
- }
- }
- </style>
|