| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476 |
- <template>
- <div class="inventory-manage admin-page">
- <el-card>
- <PageHeader slot="header" title="库存管理">
-
- <el-button type="success" size="mini" icon="el-icon-plus" @click="$router.push('/inventory/inbound-create')">创建入库单</el-button>
-
- </PageHeader>
- <el-tabs v-model="activeTab">
- <!-- 库存流水 -->
- <el-tab-pane label="库存流水" name="transactions">
- <div class="filter-bar" style="margin-bottom:12px;">
- <el-input v-model="txFilters.productName" placeholder="商品名称" clearable style="width:160px;margin-right:8px;" />
- <el-select v-model="txFilters.type" placeholder="类型" clearable style="width:120px;margin-right:8px;">
- <el-option label="全部" value="" />
- <el-option label="入库" value="inbound" />
- <el-option label="出库" value="outbound" />
- <el-option label="调账" value="adjustment" />
- <el-option label="盘点" value="counting" />
- <el-option label="退款入库" value="refund" />
- </el-select>
- <el-date-picker v-model="txFilters.dateRange" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd" style="width:260px;margin-right:8px;" />
- <el-button type="primary" size="mini" @click="loadTransactions">查询</el-button>
- </div>
- <div class="adm-table-wrap">
- <el-table :data="txList" v-loading="txLoading" border stripe height="100%">
- <el-table-column prop="id" label="ID" width="60" />
- <el-table-column label="商品" min-width="150">
- <template slot-scope="{ row }">{{ row.productName || 'ID:' + row.productId }}</template>
- </el-table-column>
- <el-table-column label="类型" width="80">
- <template slot-scope="{ row }">
- <el-tag :type="row.type === 'inbound' || row.type === 'refund' ? 'success' : row.type === 'outbound' ? 'danger' : 'warning'" size="mini">
- {{ {'inbound':'入库','outbound':'出库','adjustment':'调账','counting':'盘点','refund':'退款入库'}[row.type] || row.type }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="数量" width="80">
- <template slot-scope="{ row }">
- <span :style="{ color: row.direction === 1 ? '#67c23a' : '#f56c6c' }">{{ row.direction === 1 ? '+' : '-' }}{{ row.quantity }}</span>
- </template>
- </el-table-column>
- <el-table-column prop="beforeStock" label="变动前" width="70">
- <template slot="header">
- <span class="sort-header" @click.stop="onSortClick('beforeStock', $event)">变动前<span v-if="sortIndex('beforeStock') !== null" class="sort-index" :class="sortClass('beforeStock') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('beforeStock') }}</span>{{ sortIcon('beforeStock') }}</span>
- </template>
- </el-table-column>
- <el-table-column prop="afterStock" label="变动后" width="70">
- <template slot="header">
- <span class="sort-header" @click.stop="onSortClick('afterStock', $event)">变动后<span v-if="sortIndex('afterStock') !== null" class="sort-index" :class="sortClass('afterStock') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('afterStock') }}</span>{{ sortIcon('afterStock') }}</span>
- </template>
- </el-table-column>
- <el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip />
- <el-table-column prop="createdAt" label="时间" width="160">
- <template slot-scope="{ row }">{{ formatTime ? formatTime(row.createdAt) : row.createdAt }}</template>
- <template slot="header">
- <span class="sort-header" @click.stop="onSortClick('createdAt', $event)">时间<span v-if="sortIndex('createdAt') !== null" class="sort-index" :class="sortClass('createdAt') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('createdAt') }}</span>{{ sortIcon('createdAt') }}</span>
- </template>
- </el-table-column>
-
-
- </el-table>
- </div>
- <el-pagination @size-change="txPageSize=$event;loadTransactions()" @current-change="txPage=$event;loadTransactions()" :current-page="txPage" :page-sizes="[10,20,50]" :page-size="txPageSize" :total="txTotal" layout="total, sizes, prev, pager, next, jumper" class="pagination-wrap" />
- </el-tab-pane>
- <!-- 入库单 -->
- <el-tab-pane label="入库单" name="inbound">
- <div class="adm-table-wrap">
- <el-table :data="inboundList" v-loading="inboundLoading" border stripe height="100%">
- <el-table-column prop="orderNo" label="入库单号" width="180">
- <template slot="header">
- <span class="sort-header" @click.stop="onSortClick('orderNo', $event)">入库单号<span v-if="sortIndex('orderNo') !== null" class="sort-index" :class="sortClass('orderNo') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('orderNo') }}</span>{{ sortIcon('orderNo') }}</span>
- </template>
- </el-table-column>
- <el-table-column prop="supplierName" label="供应商" width="120">
- <template slot="header">
- <span class="sort-header" @click.stop="onSortClick('supplierName', $event)">供应商<span v-if="sortIndex('supplierName') !== null" class="sort-index" :class="sortClass('supplierName') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('supplierName') }}</span>{{ sortIcon('supplierName') }}</span>
- </template>
- </el-table-column>
- <el-table-column label="入库日期" width="100">
- <template slot-scope="{ row }">{{ row.inboundDate || '-' }}</template>
- </el-table-column>
- <el-table-column label="总金额" width="100">
- <template slot-scope="{ row }">{{ row.totalAmount ? '¥' + (row.totalAmount / 100).toFixed(2) : '-' }}</template>
- </el-table-column>
- <el-table-column prop="totalItems" label="商品项数" width="80">
- <template slot="header">
- <span class="sort-header" @click.stop="onSortClick('totalItems', $event)">商品项数<span v-if="sortIndex('totalItems') !== null" class="sort-index" :class="sortClass('totalItems') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('totalItems') }}</span>{{ sortIcon('totalItems') }}</span>
- </template>
- </el-table-column>
- <el-table-column label="状态" width="90">
- <template slot-scope="{ row }">
- <el-tag :type="row.status === 'completed' ? 'success' : row.status === 'cancelled' ? 'info' : 'warning'" size="mini">
- {{ {'pending':'待确认','completed':'已完成','cancelled':'已取消'}[row.status] || row.status }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="createdAt" label="创建时间" width="160">
- <template slot-scope="{ row }">{{ formatTime ? formatTime(row.createdAt) : row.createdAt }}</template>
- <template slot="header">
- <span class="sort-header" @click.stop="onSortClick('createdAt', $event)">创建时间<span v-if="sortIndex('createdAt') !== null" class="sort-index" :class="sortClass('createdAt') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('createdAt') }}</span>{{ sortIcon('createdAt') }}</span>
- </template>
- </el-table-column>
- <el-table-column label="操作" width="200" fixed="right">
- <template slot-scope="{ row }">
- <el-button size="mini" @click="showInboundDetail(row)">详情</el-button>
- <el-button v-if="row.status === 'pending'" size="mini" type="success" @click="handleConfirmInbound(row)">确认入库</el-button>
- <el-button v-if="row.status === 'pending'" size="mini" type="danger" @click="handleCancelInbound(row)">取消</el-button>
- </template>
- </el-table-column>
-
-
- </el-table>
- </div>
- <el-pagination @size-change="inboundPageSize=$event;loadInboundOrders()" @current-change="inboundPage=$event;loadInboundOrders()" :current-page="inboundPage" :page-sizes="[10,20,50]" :page-size="inboundPageSize" :total="inboundTotal" layout="total, sizes, prev, pager, next, jumper" class="pagination-wrap" />
- </el-tab-pane>
- <!-- 库存预警 -->
- <el-tab-pane label="库存预警" name="alerts">
- <el-button type="primary" size="mini" @click="loadAlerts" style="margin-bottom:12px;">刷新</el-button>
- <div class="adm-table-wrap">
- <el-table :data="alertList" v-loading="alertLoading" border stripe height="100%">
- <el-table-column prop="productName" label="商品名称" min-width="200" />
- <el-table-column prop="specs" label="SKU规格" width="150">
- <template slot-scope="{ row }">{{ row.specs || '-' }}</template>
- </el-table-column>
- <el-table-column prop="currentStock" label="当前库存" width="80">
- <template slot-scope="{ row }"><span style="color:#f56c6c;font-weight:bold;">{{ row.currentStock }}</span></template>
- </el-table-column>
- <el-table-column prop="minStockAlert" label="预警下限" width="80" />
-
-
- </el-table>
- </div>
- </el-tab-pane>
- <!-- 出库单 -->
- <el-tab-pane label="出库单" name="outbound">
- <el-button type="primary" size="mini" @click="showCreateOutbound = true" style="margin-bottom:12px;">新建出库单</el-button>
- <div class="adm-table-wrap">
- <el-table :data="outboundList" v-loading="outboundLoading" border stripe height="100%">
- <el-table-column prop="id" label="ID" width="60">
- <template slot="header">
- <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
- </template>
- </el-table-column>
- <el-table-column label="商品" min-width="150">
- <template slot-scope="{ row }">{{ row.productName || 'ID:' + row.productId }}</template>
- </el-table-column>
- <el-table-column label="数量" width="70">
- <template slot-scope="{ row }"><span style="color:#f56c6c;">-{{ row.quantity }}</span></template>
- </el-table-column>
- <el-table-column prop="remark" label="出库原因" min-width="200" show-overflow-tooltip>
- <template slot="header">
- <span class="sort-header" @click.stop="onSortClick('remark', $event)">出库原因<span v-if="sortIndex('remark') !== null" class="sort-index" :class="sortClass('remark') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('remark') }}</span>{{ sortIcon('remark') }}</span>
- </template>
- </el-table-column>
- <el-table-column prop="createdAt" label="时间" width="160">
- <template slot="header">
- <span class="sort-header" @click.stop="onSortClick('createdAt', $event)">时间<span v-if="sortIndex('createdAt') !== null" class="sort-index" :class="sortClass('createdAt') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('createdAt') }}</span>{{ sortIcon('createdAt') }}</span>
- </template>
- </el-table-column>
-
-
- </el-table>
- </div>
- <el-pagination @size-change="outboundPageSize=$event;loadOutboundList()" @current-change="outboundPage=$event;loadOutboundList()" :current-page="outboundPage" :page-sizes="[10,20,50]" :page-size="outboundPageSize" :total="outboundTotal" layout="total, sizes, prev, pager, next, jumper" class="pagination-wrap" />
- </el-tab-pane>
- <!-- 盘点记录 -->
- <el-tab-pane label="盘点记录" name="counting">
- <el-button type="primary" size="mini" @click="showCreateCounting = true" style="margin-bottom:12px;">新建盘点</el-button>
- <div class="adm-table-wrap">
- <el-table :data="countingList" v-loading="countingLoading" border stripe height="100%">
- <el-table-column prop="id" label="ID" width="60">
- <template slot="header">
- <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
- </template>
- </el-table-column>
- <el-table-column label="系统库存" width="80">
- <template slot-scope="{ row }">{{ row.expectedStock }}</template>
- </el-table-column>
- <el-table-column label="实际库存" width="80">
- <template slot-scope="{ row }">{{ row.actualStock }}</template>
- </el-table-column>
- <el-table-column label="差异" width="80">
- <template slot-scope="{ row }">
- <span :style="{ color: row.difference > 0 ? '#67c23a' : row.difference < 0 ? '#f56c6c' : '#999' }">{{ row.difference > 0 ? '+' : '' }}{{ row.difference }}</span>
- </template>
- </el-table-column>
- <el-table-column label="状态" width="80">
- <template slot-scope="{ row }">
- <el-tag :type="row.status === 'confirmed' ? 'success' : 'warning'" size="mini">{{ row.status === 'confirmed' ? '已确认' : '待确认' }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip>
- <template slot="header">
- <span class="sort-header" @click.stop="onSortClick('remark', $event)">备注<span v-if="sortIndex('remark') !== null" class="sort-index" :class="sortClass('remark') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('remark') }}</span>{{ sortIcon('remark') }}</span>
- </template>
- </el-table-column>
- <el-table-column prop="createdAt" label="时间" width="160">
- <template slot="header">
- <span class="sort-header" @click.stop="onSortClick('createdAt', $event)">时间<span v-if="sortIndex('createdAt') !== null" class="sort-index" :class="sortClass('createdAt') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('createdAt') }}</span>{{ sortIcon('createdAt') }}</span>
- </template>
- </el-table-column>
- <el-table-column label="操作" width="100" fixed="right">
- <template slot-scope="{ row }">
- <el-button v-if="row.status === 'pending'" size="mini" type="success" @click="handleConfirmCounting(row)">确认</el-button>
- </template>
- </el-table-column>
-
-
- </el-table>
- </div>
- <el-pagination @size-change="countingPageSize=$event;loadCountingRecords()" @current-change="countingPage=$event;loadCountingRecords()" :current-page="countingPage" :page-sizes="[10,20,50]" :page-size="countingPageSize" :total="countingTotal" layout="total, sizes, prev, pager, next, jumper" class="pagination-wrap" />
- </el-tab-pane>
- </el-tabs>
- </el-card>
- <!-- 入库单详情弹窗 -->
- <el-dialog title="入库单详情" :visible.sync="detailDialogVisible" width="700px">
- <el-descriptions :column="2" border v-if="inboundDetail">
- <el-descriptions-item label="入库单号">{{ inboundDetail.order.orderNo }}</el-descriptions-item>
- <el-descriptions-item label="供应商">{{ inboundDetail.order.supplierName || '-' }}</el-descriptions-item>
- <el-descriptions-item label="状态">{{ inboundDetail.order.status }}</el-descriptions-item>
- <el-descriptions-item label="备注">{{ inboundDetail.order.remark || '-' }}</el-descriptions-item>
- </el-descriptions>
- <el-table :data="inboundDetail.items" border stripe style="margin-top:12px;" v-if="inboundDetail">
- <el-table-column prop="productId" label="商品ID" width="70" />
- <el-table-column prop="skuId" label="SKU ID" width="70" />
- <el-table-column prop="quantity" label="数量" width="70" />
- </el-dialog>
- <!-- 新建出库弹窗 -->
- <el-dialog title="新建出库单" :visible.sync="showCreateOutbound" width="500px">
- <el-form label-width="100px">
- <el-form-item label="商品">
- <el-select v-model="outboundForm.productId" filterable remote placeholder="搜索商品" :remote-method="searchOutboundProduct" style="width:300px;">
- <el-option v-for="p in outboundProductOptions" :key="p.id" :label="p.name + ' (库存:' + (p.stock || 0) + ')'" :value="p.id" />
- </el-select>
- </el-form-item>
- <el-form-item label="SKU" v-if="outboundSkuOptions.length">
- <el-select v-model="outboundForm.skuId" placeholder="选择规格" clearable style="width:200px;">
- <el-option v-for="s in outboundSkuOptions" :key="s.id" :label="s.specs || '默认' + ' (库存:' + (s.stock || 0) + ')'" :value="s.id" />
- </el-select>
- </el-form-item>
- <el-form-item label="数量">
- <el-input v-model.number="outboundForm.quantity" type="number" min="1" />
- </el-form-item>
- <el-form-item label="出库原因">
- <el-select v-model="outboundForm.reason" placeholder="选择原因" style="width:200px;">
- <el-option label="赠品" value="赠品" />
- <el-option label="损耗" value="损耗" />
- <el-option label="样品" value="样品" />
- <el-option label="其他" value="其他" />
- </el-select>
- </el-form-item>
- <el-form-item label="备注">
- <el-input v-model="outboundForm.remark" type="textarea" :rows="2" />
- </el-form-item>
- </el-form>
- <span slot="footer">
- <el-button @click="showCreateOutbound = false">取消</el-button>
- <el-button type="danger" @click="handleCreateOutbound">确认出库</el-button>
- </span>
- </el-dialog>
- <!-- 新建盘点弹窗 -->
- <el-dialog title="新建盘点" :visible.sync="showCreateCounting" width="450px">
- <el-form label-width="100px">
- <el-form-item label="商品ID">
- <el-input v-model="countingForm.productId" type="number" />
- </el-form-item>
- <el-form-item label="SKU ID">
- <el-input v-model="countingForm.skuId" type="number" placeholder="选填" />
- </el-form-item>
- <el-form-item label="实际库存">
- <el-input v-model="countingForm.actualStock" type="number" />
- </el-form-item>
- <el-form-item label="备注">
- <el-input v-model="countingForm.remark" type="textarea" :rows="2" />
- </el-form-item>
- </el-form>
- <span slot="footer">
- <el-button @click="showCreateCounting = false">取消</el-button>
- <el-button type="primary" @click="handleCreateCounting">提交</el-button>
- </span>
- </el-dialog>
-
- </el-table>
- </div>
- </template>
- <script>
- import SortMixin from '@/mixins/SortMixin'
- import ListStateMixin from '@/mixins/ListStateMixin'
- import { getInventoryTransactions, getInboundOrderList, getInboundOrderDetail, confirmInbound, cancelInbound, getStockAlerts, getCountingList, createCountingRecord, confirmCounting, createManualOutbound, getManualOutboundList, getProductList, getProductSkuList } from '@/api/admin'
- export default {
- mixins: [SortMixin, ListStateMixin],
- name: 'InventoryManage',
- data() {
- return {
- activeTab: 'transactions',
- txList: [], txLoading: false, txPage: 1, txPageSize: 20, txTotal: 0,
- txFilters: { productName: '', type: '', dateRange: null },
- inboundList: [], inboundLoading: false, inboundPage: 1, inboundPageSize: 20, inboundTotal: 0,
- inboundDetail: null, detailDialogVisible: false,
- alertList: [], alertLoading: false,
- countingList: [], countingLoading: false, countingPage: 1, countingPageSize: 20, countingTotal: 0,
- showCreateCounting: false, countingForm: { productId: '', skuId: '', actualStock: '', remark: '' },
- outboundList: [], outboundLoading: false, outboundPage: 1, outboundPageSize: 20, outboundTotal: 0,
- showCreateOutbound: false, outboundProductOptions: [], outboundSkuOptions: [],
- outboundForm: { productId: '', skuId: '', quantity: 1, reason: '', remark: '' },
- sortableColumns: { id: 'ID', createdAt: '时间', beforeStock: '变动前', afterStock: '变动后', orderNo: '入库单号', supplierName: '供应商', totalItems: '商品项数', remark: '备注' }
- }
- },
- mounted() {
- this.loadTransactions()
- this.loadInboundOrders()
- this.loadAlerts()
- this.loadCountingRecords()
- this.loadOutboundList()
- },
- methods: {
- loadList() { this.loadTransactions() },
- async loadTransactions() {
- this.txLoading = true
- try {
- const params = {
- page: this.txPage, size: this.txPageSize,
- type: this.txFilters.type || null,
- startDate: this.txFilters.dateRange ? this.txFilters.dateRange[0] : null,
- endDate: this.txFilters.dateRange ? this.txFilters.dateRange[1] : null
- }
- if (this.sortState.length > 0) params.sort = this.sortState
- const res = await getInventoryTransactions(params)
- this.txList = res.data.records || []
- this.txTotal = res.data.total || 0
- } catch (e) { this.$message.error('加载失败') }
- finally { this.txLoading = false }
- },
- async loadInboundOrders() {
- this.inboundLoading = true
- try {
- const res = await getInboundOrderList({ page: this.inboundPage, size: this.inboundPageSize })
- this.inboundList = res.data.records || []
- this.inboundTotal = res.data.total || 0
- } catch (e) { this.$message.error('加载失败') }
- finally { this.inboundLoading = false }
- },
- async showInboundDetail(row) {
- try {
- const res = await getInboundOrderDetail(row.id)
- this.inboundDetail = res.data
- this.detailDialogVisible = true
- } catch (e) { this.$message.error('加载失败') }
- },
- async handleConfirmInbound(row) {
- try {
- await this.$confirm('确认入库?库存将被增加,不可撤销。', '提示')
- await confirmInbound(row.id)
- this.$message.success('入库成功')
- this.loadInboundOrders()
- } catch (e) { if (e !== 'cancel') this.$message.error('操作失败') }
- },
- async handleCancelInbound(row) {
- try {
- await this.$confirm('确认取消该入库单?', '提示')
- await cancelInbound(row.id)
- this.$message.success('已取消')
- this.loadInboundOrders()
- } catch (e) { if (e !== 'cancel') this.$message.error('操作失败') }
- },
- async loadAlerts() {
- this.alertLoading = true
- try {
- const res = await getStockAlerts()
- this.alertList = res.data || []
- } catch (e) { this.$message.error('加载失败') }
- finally { this.alertLoading = false }
- },
- async loadCountingRecords() {
- this.countingLoading = true
- try {
- const res = await getCountingList({ page: this.countingPage, size: this.countingPageSize })
- this.countingList = res.data.records || []
- this.countingTotal = res.data.total || 0
- } catch (e) { this.$message.error('加载失败') }
- finally { this.countingLoading = false }
- },
- async handleCreateCounting() {
- if (!this.countingForm.productId || !this.countingForm.actualStock) {
- return this.$message.warning('请填写必填项')
- }
- try {
- await createCountingRecord({
- productId: parseInt(this.countingForm.productId),
- skuId: this.countingForm.skuId ? parseInt(this.countingForm.skuId) : null,
- actualStock: parseInt(this.countingForm.actualStock),
- remark: this.countingForm.remark
- })
- this.$message.success('盘点记录已创建')
- this.showCreateCounting = false
- this.countingForm = { productId: '', skuId: '', actualStock: '', remark: '' }
- this.loadCountingRecords()
- } catch (e) { this.$message.error('操作失败') }
- },
- async handleConfirmCounting(row) {
- try {
- await this.$confirm('确认盘点结果?差异将自动调账。', '提示')
- await confirmCounting(row.id)
- this.$message.success('盘点已确认')
- this.loadCountingRecords()
- } catch (e) { if (e !== 'cancel') this.$message.error('操作失败') }
- },
- async loadOutboundList() {
- this.outboundLoading = true
- try {
- const res = await getManualOutboundList({ page: this.outboundPage, size: this.outboundPageSize })
- this.outboundList = res.data.records || []
- this.outboundTotal = res.data.total || 0
- } catch (e) { this.$message.error('加载失败') }
- finally { this.outboundLoading = false }
- },
- async searchOutboundProduct(query) {
- if (!query || query.length < 1) return
- try {
- const res = await getProductList({ keyword: query, page: 1, size: 20 })
- this.outboundProductOptions = res.data.records || []
- } catch (e) { /* ignore */ }
- },
- async handleCreateOutbound() {
- if (!this.outboundForm.productId || !this.outboundForm.quantity) {
- return this.$message.warning('请填写必填项')
- }
- try {
- await this.$confirm('确认出库?库存将被扣减。', '提示')
- await createManualOutbound({
- productId: parseInt(this.outboundForm.productId),
- skuId: this.outboundForm.skuId ? parseInt(this.outboundForm.skuId) : null,
- quantity: parseInt(this.outboundForm.quantity),
- reason: this.outboundForm.reason || null,
- remark: this.outboundForm.remark || null
- })
- this.$message.success('出库成功')
- this.showCreateOutbound = false
- this.outboundForm = { productId: '', skuId: '', quantity: 1, reason: '', remark: '' }
- this.loadOutboundList()
- } catch (e) { if (e !== 'cancel') this.$message.error('操作失败') }
- }
- }
- }
- </script>
- <style scoped>
- .inventory-manage { padding: 20px; }
- .filter-bar { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
- .pagination-wrap { margin-top: 12px; text-align: right; }
- .sort-header {
- cursor: pointer;
- user-select: none;
- font-weight: 500;
- font-size: 12px;
- }
- .sort-header:hover {
- color: #409eff;
- }
- .sort-tag {
- margin-left: 8px;
- cursor: pointer;
- white-space: nowrap;
- }
- </style>
|