feat(purchase): 新增「待采购清单」页面
路由 router/index.ts:
/purchase 下新增子路由 pending(name: PurchasePendingPool)。
★ 父路由刻意不加 permissions —— 加了会让没有 pending_pool 权限的用户
整个「采购管理」模块从侧边栏消失。
页面 views/purchase/PendingPool.vue:
筛选栏 + 表格 + 分页,列含产品图、规格、当前库存、在途、有效供给、
预警、建议采购量(tooltip 展示完整算式)、参考单价、采购链接、操作。
采购链接渲染走 safeHref 白名单,非法地址不渲染成可点击链接。
api/purchase.ts:
新增 getPendingPurchasePool 与 PendingPoolItem 类型。
★ 需告知业务方的可见变化:「采购管理」从扁平单项变为可展开子菜单
(Sidebar 在子路由只有 1 个时渲染 el-menu-item,2 个时渲染 el-sub-menu)。
This commit is contained in:
@ -163,3 +163,46 @@ export function getApprovedUnstockedRequests(params: {
|
|||||||
params
|
params
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 待采购池的单条物料(防重复采购)
|
||||||
|
export interface PendingPoolItem {
|
||||||
|
material_id: number
|
||||||
|
name: string
|
||||||
|
spec_model: string
|
||||||
|
unit: string
|
||||||
|
category: string
|
||||||
|
material_type: string
|
||||||
|
company_name: string
|
||||||
|
image: string
|
||||||
|
purchase_link: string
|
||||||
|
reference_price: number | null
|
||||||
|
inventory_count: number
|
||||||
|
available_count: number
|
||||||
|
// 在途量 = 该物料所有活跃采购单的剩余待入库量之和
|
||||||
|
in_transit_qty: number
|
||||||
|
// 有效供给 = 库存 + 在途。判缺货与算建议量都基于它
|
||||||
|
effective_supply: number
|
||||||
|
warning_status: number // 0=正常 1=黄色 2=红色(进池的必然非 0)
|
||||||
|
warning_red: number | null
|
||||||
|
warning_yellow: number | null
|
||||||
|
target_threshold: number | null
|
||||||
|
suggested_qty: number | null
|
||||||
|
is_ordered: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// 待采购清单:库存已触及预警线、且没有活跃采购单的物料。
|
||||||
|
// 物料一旦有在途采购单(待审批/已通过/部分到货未齐)就会从这里消失。
|
||||||
|
export function getPendingPurchasePool(params: {
|
||||||
|
page?: number
|
||||||
|
limit?: number
|
||||||
|
keyword?: string
|
||||||
|
category?: string
|
||||||
|
type?: string
|
||||||
|
warning_status?: number
|
||||||
|
}) {
|
||||||
|
return request({
|
||||||
|
url: '/purchase/pending-pool',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -213,6 +213,8 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
{
|
{
|
||||||
path: '/purchase',
|
path: '/purchase',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
|
// ★ 父路由**不加** permissions:加了会让没有 pending_pool 权限的用户
|
||||||
|
// 整个「采购管理」模块从侧边栏消失。
|
||||||
meta: { title: '采购管理', icon: 'ShoppingCart' },
|
meta: { title: '采购管理', icon: 'ShoppingCart' },
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@ -220,6 +222,12 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
name: 'PurchaseList',
|
name: 'PurchaseList',
|
||||||
component: () => import('@/views/purchase/index.vue'),
|
component: () => import('@/views/purchase/index.vue'),
|
||||||
meta: { title: '采购申请' }
|
meta: { title: '采购申请' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'pending',
|
||||||
|
name: 'PurchasePendingPool',
|
||||||
|
component: () => import('@/views/purchase/PendingPool.vue'),
|
||||||
|
meta: { title: '待采购清单', permissions: ['inbound_purchase:pending_pool'] }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
458
inventory-web/src/views/purchase/PendingPool.vue
Normal file
458
inventory-web/src/views/purchase/PendingPool.vue
Normal file
@ -0,0 +1,458 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
|
||||||
|
<el-alert
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
style="margin-bottom: 14px;"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
按<b>有效供给</b>(当前库存 + 在途量)判断:只有「有效供给仍低于预警线」的物料才会出现在这里。
|
||||||
|
在途量取该物料所有活跃采购单(待审批 / 已通过 / 部分到货未齐)的<b>剩余待入库量之和</b>,
|
||||||
|
所以已经买过一部分的物料不会消失 —— 它会继续留在清单里,建议采购量只补剩下的缺口。
|
||||||
|
</template>
|
||||||
|
</el-alert>
|
||||||
|
|
||||||
|
<!-- 顶部筛选 -->
|
||||||
|
<el-form :inline="true" class="filter-form" @submit.prevent>
|
||||||
|
<el-form-item label="搜索">
|
||||||
|
<el-input
|
||||||
|
v-model="filters.keyword"
|
||||||
|
placeholder="物料名称 / 规格型号 / 公司"
|
||||||
|
style="width: 240px"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
@clear="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="类别">
|
||||||
|
<el-input
|
||||||
|
v-model="filters.category"
|
||||||
|
placeholder="类别前缀"
|
||||||
|
style="width: 180px"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
@clear="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="类型">
|
||||||
|
<el-input
|
||||||
|
v-model="filters.type"
|
||||||
|
placeholder="物料类型"
|
||||||
|
style="width: 150px"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
@clear="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="预警等级">
|
||||||
|
<el-radio-group v-model="filters.warningStatus" @change="handleQuery">
|
||||||
|
<el-radio-button label="">全部</el-radio-button>
|
||||||
|
<el-radio-button :label="2">红色</el-radio-button>
|
||||||
|
<el-radio-button :label="1">黄色</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" :icon="Refresh" @click="handleQuery">查询</el-button>
|
||||||
|
<el-button @click="resetFilter">重置</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="userStore.hasPermission('inbound_purchase:operation')"
|
||||||
|
type="success"
|
||||||
|
:icon="ShoppingCart"
|
||||||
|
:disabled="selectedRows.length === 0"
|
||||||
|
@click="handleBatchGenerate"
|
||||||
|
>批量生成采购申请{{ selectedRows.length ? `(${selectedRows.length})` : '' }}</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="list"
|
||||||
|
border
|
||||||
|
stripe
|
||||||
|
fit
|
||||||
|
style="width: 100%; margin-top: 16px;"
|
||||||
|
row-key="material_id"
|
||||||
|
empty-text="暂无待采购物料(所有缺货物料都已有在途采购单)"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
>
|
||||||
|
<!-- ★ 刻意不加 reserve-selection:它会让勾选跨页保留,但按钮上的数量
|
||||||
|
与实际可见的勾选状态可能不一致(甚至保留已离开池子的物料),
|
||||||
|
用户看到「已选 5 项」而屏幕上一个勾都没有。待采购池是小工作台,
|
||||||
|
跨页批量的收益远小于这种静默不一致的代价。 -->
|
||||||
|
<el-table-column type="selection" width="46" align="center" />
|
||||||
|
|
||||||
|
<el-table-column label="产品图" width="90" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-image
|
||||||
|
v-if="row.image"
|
||||||
|
:src="getImageUrl(row.image)"
|
||||||
|
:preview-src-list="[getImageUrl(row.image)]"
|
||||||
|
preview-teleported
|
||||||
|
fit="cover"
|
||||||
|
style="width: 56px; height: 56px; border-radius: 4px;"
|
||||||
|
>
|
||||||
|
<template #error>
|
||||||
|
<div class="img-fallback">无图</div>
|
||||||
|
</template>
|
||||||
|
</el-image>
|
||||||
|
<span v-else style="color: #c0c4cc; font-size: 12px;">无图</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="name" label="物料名称" min-width="220" show-overflow-tooltip />
|
||||||
|
|
||||||
|
<el-table-column prop="spec_model" label="规格型号" min-width="160" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row.spec_model">{{ row.spec_model }}</span>
|
||||||
|
<span v-else style="color: #c0c4cc;">-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="unit" label="单位" width="80" align="center">
|
||||||
|
<template #default="{ row }">{{ row.unit || '-' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="category" label="类别" min-width="170" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">{{ row.category || '-' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="当前库存" width="100" align="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span class="money-text">{{ row.inventory_count }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="在途" width="100" align="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tooltip
|
||||||
|
v-if="row.in_transit_qty > 0"
|
||||||
|
content="该物料所有活跃采购单的剩余待入库量之和"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<span class="in-transit">+{{ row.in_transit_qty }}</span>
|
||||||
|
</el-tooltip>
|
||||||
|
<span v-else style="color: #c0c4cc;">0</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="有效供给" width="110" align="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tooltip
|
||||||
|
:content="`库存 ${row.inventory_count} + 在途 ${row.in_transit_qty} = ${row.effective_supply}(判缺货用的就是它)`"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<span class="effective">{{ row.effective_supply }}</span>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="可用库存" width="100" align="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span :class="{ 'avail-warn': row.available_count <= 0 }">
|
||||||
|
{{ row.available_count }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="预警" width="110" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag v-if="row.warning_status === 2" type="danger" size="small">红色预警</el-tag>
|
||||||
|
<el-tag v-else-if="row.warning_status === 1" type="warning" size="small">黄色预警</el-tag>
|
||||||
|
<span v-else style="color: #c0c4cc;">-</span>
|
||||||
|
<div style="font-size: 11px; color: #909399; margin-top: 2px;">
|
||||||
|
红 {{ fmt(row.warning_red) }} / 黄 {{ fmt(row.warning_yellow) }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="建议采购量" width="120" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tooltip
|
||||||
|
v-if="row.suggested_qty != null"
|
||||||
|
:content="suggestedTip(row)"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<span class="suggested">{{ row.suggested_qty }}</span>
|
||||||
|
</el-tooltip>
|
||||||
|
<span v-else style="color: #c0c4cc;">-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="参考单价" width="110" align="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row.reference_price != null">{{ row.reference_price.toFixed(2) }}</span>
|
||||||
|
<span v-else style="color: #c0c4cc;">-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="采购链接" width="120" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<!-- ★ 必须过 safeHref 白名单:地址由用户自由填写,直接绑 href 会让
|
||||||
|
javascript: 之类的伪协议被当成可点击链接执行 -->
|
||||||
|
<el-link
|
||||||
|
v-if="safeHref(row.purchase_link)"
|
||||||
|
type="primary"
|
||||||
|
:href="safeHref(row.purchase_link)"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>🔗 前往采购</el-link>
|
||||||
|
<span v-else-if="row.purchase_link" style="color: #909399;" :title="row.purchase_link">格式无效</span>
|
||||||
|
<span v-else style="color: #c0c4cc;">-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="操作" width="150" fixed="right" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button
|
||||||
|
v-if="userStore.hasPermission('inbound_purchase:operation')"
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
@click="handleGenerate(row)"
|
||||||
|
>生成采购申请</el-button>
|
||||||
|
<span v-else style="color: #c0c4cc; font-size: 12px;">无建单权限</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<div class="pagination-container">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="page"
|
||||||
|
v-model:page-size="pageSize"
|
||||||
|
:page-sizes="[20, 50, 100]"
|
||||||
|
:background="true"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
:total="total"
|
||||||
|
@size-change="handleSizeChange"
|
||||||
|
@current-change="handlePageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { Refresh, ShoppingCart } from '@element-plus/icons-vue'
|
||||||
|
import { useUserStore } from '@/stores/user'
|
||||||
|
import { getPendingPurchasePool, type PendingPoolItem } from '@/api/purchase'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const list = ref<PendingPoolItem[]>([])
|
||||||
|
const total = ref(0)
|
||||||
|
const page = ref(1)
|
||||||
|
const pageSize = ref(20)
|
||||||
|
const selectedRows = ref<PendingPoolItem[]>([])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量交接数据走 localStorage,而不是 URL query。
|
||||||
|
*
|
||||||
|
* 原因:批量可能勾选十几个物料,每个还带商家链接(material_base.purchase_link
|
||||||
|
* 是 Text,实测单条已 516 字符)。塞进 URL 会撞上长度上限并被**静默截断** ——
|
||||||
|
* 前端拿到半截 JSON 只会报解析失败,排查成本很高。localStorage 没有这个上限。
|
||||||
|
* query 里只传一个一次性 key,取完即删。
|
||||||
|
*/
|
||||||
|
const BATCH_KEY_PREFIX = 'MOM_PURCHASE_BATCH_'
|
||||||
|
|
||||||
|
const filters = reactive({
|
||||||
|
keyword: '',
|
||||||
|
category: '',
|
||||||
|
type: '',
|
||||||
|
warningStatus: '' as '' | 1 | 2
|
||||||
|
})
|
||||||
|
|
||||||
|
const fmt = (v: number | null) => (v == null ? '-' : v)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 建议采购量的说明文案。
|
||||||
|
*
|
||||||
|
* ★ 为什么要分两种:有效供给**恰好等于**阈值时,`阈值 - 有效供给` 是 0,
|
||||||
|
* 但后端有 `max(1, ...)` 保底,结果是 1。若直接套「阈值 − 有效供给 = 建议量」
|
||||||
|
* 的模板,页面上会出现「4 − 4 = 1」这种**算不对的式子** —— 采购员一旦发现
|
||||||
|
* 算式不成立,就不会再信这个数字。所以到线的情况单独讲清楚。
|
||||||
|
*
|
||||||
|
* 等于阈值仍算不足,是与物料列表预警、预警邮件一致的口径(见后端
|
||||||
|
* purchase_service.get_pending_purchase_pool 的说明)。
|
||||||
|
*/
|
||||||
|
const suggestedTip = (row: PendingPoolItem) => {
|
||||||
|
const target = row.target_threshold
|
||||||
|
const eff = row.effective_supply
|
||||||
|
if (target != null && target - eff >= 1) {
|
||||||
|
return `补至 ${target}:目标阈值 ${target} − 有效供给 ${eff} = ${row.suggested_qty}(在途已计入,不会重复买)`
|
||||||
|
}
|
||||||
|
return `有效供给 ${eff} 已达阈值线 ${target},建议补 ${row.suggested_qty} 个作缓冲。` +
|
||||||
|
`(等于阈值仍算不足 —— 与物料列表的预警口径一致)`
|
||||||
|
}
|
||||||
|
|
||||||
|
const getImageUrl = (url: string) => (!url ? '' : url)
|
||||||
|
|
||||||
|
/** 采购链接白名单:只放行 http/https,其余一律不渲染成链接 */
|
||||||
|
const safeHref = (url: any): string | null => {
|
||||||
|
const s = String(url || '').trim()
|
||||||
|
return /^https?:\/\//i.test(s) ? s : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const params: any = {
|
||||||
|
page: page.value,
|
||||||
|
limit: pageSize.value
|
||||||
|
}
|
||||||
|
if (filters.keyword) params.keyword = filters.keyword
|
||||||
|
if (filters.category) params.category = filters.category
|
||||||
|
if (filters.type) params.type = filters.type
|
||||||
|
if (filters.warningStatus !== '') params.warning_status = filters.warningStatus
|
||||||
|
|
||||||
|
const res: any = await getPendingPurchasePool(params)
|
||||||
|
const data = res?.data ?? {}
|
||||||
|
list.value = data.items || []
|
||||||
|
total.value = data.total || 0
|
||||||
|
} catch (e: any) {
|
||||||
|
ElMessage.error(e?.msg || '加载待采购清单失败')
|
||||||
|
list.value = []
|
||||||
|
total.value = 0
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleQuery = () => {
|
||||||
|
page.value = 1
|
||||||
|
fetchData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetFilter = () => {
|
||||||
|
filters.keyword = ''
|
||||||
|
filters.category = ''
|
||||||
|
filters.type = ''
|
||||||
|
filters.warningStatus = ''
|
||||||
|
page.value = 1
|
||||||
|
fetchData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePageChange = (p: number) => { page.value = p; fetchData() }
|
||||||
|
const handleSizeChange = (s: number) => { pageSize.value = s; page.value = 1; fetchData() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成采购申请:新标签页打开采购申请页并自动回填。
|
||||||
|
*
|
||||||
|
* 用 window.open 而非 router.push 是刻意的 —— 待采购清单的工作流是**连续建单**,
|
||||||
|
* 同标签跳转会卸载本页,采购员每开一单都要点回来重新筛选、丢失页码;
|
||||||
|
* 新标签页保留列表状态。这与「基础信息」页既有的跳转方式也保持一致。
|
||||||
|
*/
|
||||||
|
const handleSelectionChange = (rows: PendingPoolItem[]) => {
|
||||||
|
selectedRows.value = rows || []
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 把池中一行整理成弹窗明细行的预填数据(单行与批量共用同一套字段映射) */
|
||||||
|
const toPrefillItem = (row: PendingPoolItem) => ({
|
||||||
|
materialId: row.material_id,
|
||||||
|
name: row.name || '',
|
||||||
|
spec: row.spec_model || '',
|
||||||
|
// 建议采购量作为默认数量带入
|
||||||
|
quantity: row.suggested_qty != null && row.suggested_qty > 0 ? row.suggested_qty : undefined,
|
||||||
|
// 供应商链接(基础表的采购链接)带入,采购员无需再去查
|
||||||
|
supplierLink: String(row.purchase_link || '').trim() || undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把预填数据交给采购申请页,开新标签页。
|
||||||
|
*
|
||||||
|
* ★ 单行与批量都走这里、都经 localStorage 中转 —— 曾经单行走 URL query,
|
||||||
|
* 而 material_base.purchase_link 是 text(实测已有 516 字符的真实链接),
|
||||||
|
* URL 长度受限会逼出一个「多长就不带」的硬编码阈值,导致同一个物料
|
||||||
|
* 单行跳转带链接、批量跳转不带,行为不一致。现在两条路径统一,无长度上限。
|
||||||
|
*
|
||||||
|
* 开新标签页是刻意的:待采购清单的工作流是连续建单,同标签跳转会卸载本页、
|
||||||
|
* 丢失筛选与勾选状态。
|
||||||
|
*/
|
||||||
|
const openPurchaseWithItems = (items: ReturnType<typeof toPrefillItem>[]) => {
|
||||||
|
const key = BATCH_KEY_PREFIX + Date.now()
|
||||||
|
try {
|
||||||
|
localStorage.setItem(key, JSON.stringify(items))
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('暂存待建单数据失败,请重试')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const routeUrl = router.resolve({ path: '/purchase', query: { batch_key: key } })
|
||||||
|
window.open(routeUrl.href, '_blank')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 单行:生成采购申请 */
|
||||||
|
const handleGenerate = (row: PendingPoolItem) => {
|
||||||
|
openPurchaseWithItems([toPrefillItem(row)])
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 批量:勾选 N 个物料 → 一次性铺成 N 行,一单多品合并采购 */
|
||||||
|
const handleBatchGenerate = () => {
|
||||||
|
if (selectedRows.value.length === 0) return
|
||||||
|
openPurchaseWithItems(selectedRows.value.map(toPrefillItem))
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(fetchData)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-container { padding: 20px; }
|
||||||
|
|
||||||
|
/* 搜索区版式:与采购申请 / 出库记录 / 报废记录保持一致 */
|
||||||
|
.filter-form {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.filter-form :deep(.el-form-item) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggested {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 在途量与有效供给:本次改造新增的核心数字,用不同色区分于纯库存 */
|
||||||
|
.in-transit {
|
||||||
|
color: #e6a23c;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.effective {
|
||||||
|
font-weight: 600;
|
||||||
|
border-bottom: 1px dashed #c0c4cc;
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avail-warn {
|
||||||
|
color: #f56c6c;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-fallback {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #f5f7fa;
|
||||||
|
color: #c0c4cc;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user