|
|
@@ -0,0 +1,472 @@
|
|
|
+<!-- 质检物料库 -->
|
|
|
+<!-- eslint-disable vue/no-v-model-argument -->
|
|
|
+<template>
|
|
|
+ <div class="page-container">
|
|
|
+ <header class="page-head">
|
|
|
+ <h1>质检物料库</h1>
|
|
|
+ <p class="page-desc">维护质检物料的编码、名称、规格、库存预警等信息,支持入库操作。</p>
|
|
|
+ </header>
|
|
|
+
|
|
|
+ <section class="panel">
|
|
|
+ <div class="panel-top">
|
|
|
+ <h2>物料列表</h2>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="filter-block">
|
|
|
+ <el-form :model="searchForm" class="filter-form" @submit.prevent>
|
|
|
+ <div class="filter-grid">
|
|
|
+ <el-form-item label="关键词">
|
|
|
+ <el-input v-model="searchForm.keyword" placeholder="物料名称/编码" clearable />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="状态">
|
|
|
+ <el-select v-model="searchForm.status" placeholder="全部" clearable style="width: 100%">
|
|
|
+ <el-option label="启用" value="1" />
|
|
|
+ <el-option label="停用" value="0" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ <div class="filter-actions-row">
|
|
|
+ <span class="filter-hint">按物料名称、编码模糊查询。</span>
|
|
|
+ <div class="filter-actions-end">
|
|
|
+ <el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
|
|
+ <el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="list-toolbar">
|
|
|
+ <div class="list-toolbar-row">
|
|
|
+ <div class="list-toolbar-selected">
|
|
|
+ 已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
|
|
|
+ </div>
|
|
|
+ <el-button class="btn-main" type="primary" @click="handleAdd">
|
|
|
+ <el-icon><Plus /></el-icon>
|
|
|
+ 新增
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ class="btn-ghost"
|
|
|
+ :disabled="selectedRows.length === 0"
|
|
|
+ @click="handleStockInFromToolbar"
|
|
|
+ >
|
|
|
+ 入库
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="list-body list-wrap">
|
|
|
+ <el-table
|
|
|
+ class="list-table"
|
|
|
+ :data="tableData"
|
|
|
+ v-loading="loading"
|
|
|
+ stripe
|
|
|
+ row-key="id"
|
|
|
+ @selection-change="handleSelectionChange"
|
|
|
+ >
|
|
|
+ <el-table-column type="selection" width="55" align="center" header-align="center" />
|
|
|
+ <el-table-column
|
|
|
+ type="index"
|
|
|
+ label="序号"
|
|
|
+ width="68"
|
|
|
+ align="center"
|
|
|
+ header-align="center"
|
|
|
+ :index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
|
|
|
+ />
|
|
|
+ <el-table-column prop="materialCode" label="物料编码" min-width="120" align="center" header-align="center" />
|
|
|
+ <el-table-column prop="materialName" label="物料名称" min-width="140" align="center" header-align="center" />
|
|
|
+ <el-table-column prop="specModel" label="规格型号" min-width="140" align="center" header-align="center" />
|
|
|
+ <el-table-column prop="unit" label="单位" min-width="80" align="center" header-align="center" />
|
|
|
+ <el-table-column label="当前库存" min-width="100" align="right" header-align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ row.stockQty ?? 0 }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="预警库存" min-width="100" align="right" header-align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ row.warningQty ?? 0 }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="库存状态" min-width="100" align="center" header-align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag
|
|
|
+ :type="row.stockQty <= row.warningQty ? 'danger' : 'success'"
|
|
|
+ size="small"
|
|
|
+ >
|
|
|
+ {{ row.stockQty <= row.warningQty ? '库存预警' : '正常' }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="状态" min-width="80" align="center" header-align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag :type="row.status === '1' ? 'success' : 'info'" size="small">
|
|
|
+ {{ row.status === '1' ? '启用' : '停用' }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="remark" label="备注" min-width="140" align="center" header-align="center" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="createTime" label="创建时间" min-width="170" align="center" header-align="center" />
|
|
|
+ <el-table-column label="操作" width="260" fixed="right" align="center" header-align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button class="btn-op-item primary" @click="handleEdit(row)">编辑</el-button>
|
|
|
+ <el-button class="btn-op-item" @click="handleStockIn(row)">入库</el-button>
|
|
|
+ <el-button class="btn-op-item" @click="handleViewUsage(row)">详情</el-button>
|
|
|
+ <el-button class="btn-op-item" @click="handleDelete(row)">删除</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ <div class="pagination list-pager-bar">
|
|
|
+ <el-pagination
|
|
|
+ :current-page="pagination.page"
|
|
|
+ :page-size="pagination.pageSize"
|
|
|
+ :total="pagination.total"
|
|
|
+ :page-sizes="[10, 20, 50]"
|
|
|
+ layout="total, sizes, prev, pager, next"
|
|
|
+ @size-change="(val) => ((pagination.page = 1), (pagination.pageSize = val), loadData())"
|
|
|
+ @current-change="(val) => ((pagination.page = val), loadData())"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 新增/编辑 Dialog -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="dialogVisible"
|
|
|
+ :title="dialogTitle"
|
|
|
+ width="560px"
|
|
|
+ class="dialog-form-layout"
|
|
|
+ align-center
|
|
|
+ destroy-on-close
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ >
|
|
|
+ <el-form
|
|
|
+ ref="formRef"
|
|
|
+ :model="formData"
|
|
|
+ :rules="formRules"
|
|
|
+ label-position="top"
|
|
|
+ class="material-dialog-form"
|
|
|
+ scroll-to-error
|
|
|
+ :scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
|
|
|
+ >
|
|
|
+ <div class="form-grid-2">
|
|
|
+ <el-form-item label="物料编码" prop="materialCode">
|
|
|
+ <el-input v-model="formData.materialCode" placeholder="请输入物料编码" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="物料名称" prop="materialName">
|
|
|
+ <el-input v-model="formData.materialName" placeholder="请输入物料名称" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="规格型号">
|
|
|
+ <el-input v-model="formData.specModel" placeholder="请输入规格型号" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="单位">
|
|
|
+ <el-input v-model="formData.unit" placeholder="请输入单位" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="预警库存">
|
|
|
+ <el-input-number
|
|
|
+ v-model="formData.warningQty"
|
|
|
+ :min="0"
|
|
|
+ :precision="0"
|
|
|
+ style="width: 100%"
|
|
|
+ placeholder="请输入预警库存"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="状态">
|
|
|
+ <el-radio-group v-model="formData.status">
|
|
|
+ <el-radio value="1">启用</el-radio>
|
|
|
+ <el-radio value="0">停用</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="备注" class="form-col-span-2">
|
|
|
+ <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入备注" />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button class="btn-ghost" @click="dialogVisible = false">
|
|
|
+ <el-icon><Close /></el-icon>
|
|
|
+ 取消
|
|
|
+ </el-button>
|
|
|
+ <el-button class="btn-main" type="primary" @click="handleSubmit" :loading="submitLoading">
|
|
|
+ <el-icon><Check /></el-icon>
|
|
|
+ 确定
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 入库 Dialog -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="stockInVisible"
|
|
|
+ title="物料入库"
|
|
|
+ width="480px"
|
|
|
+ class="dialog-form-layout"
|
|
|
+ align-center
|
|
|
+ destroy-on-close
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ >
|
|
|
+ <el-form
|
|
|
+ ref="stockInFormRef"
|
|
|
+ :model="stockInForm"
|
|
|
+ :rules="stockInRules"
|
|
|
+ label-position="top"
|
|
|
+ class="material-dialog-form"
|
|
|
+ scroll-to-error
|
|
|
+ :scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
|
|
|
+ >
|
|
|
+ <el-form-item label="物料名称">
|
|
|
+ <el-input :model-value="stockInForm.materialName" disabled />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="当前库存">
|
|
|
+ <el-input :model-value="stockInForm.currentStock" disabled />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="入库数量" prop="qty">
|
|
|
+ <el-input-number
|
|
|
+ v-model="stockInForm.qty"
|
|
|
+ :min="0.0001"
|
|
|
+ :precision="4"
|
|
|
+ style="width: 100%"
|
|
|
+ placeholder="请输入入库数量"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="备注">
|
|
|
+ <el-input v-model="stockInForm.remark" type="textarea" :rows="3" placeholder="选填" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button class="btn-ghost" @click="stockInVisible = false">
|
|
|
+ <el-icon><Close /></el-icon>
|
|
|
+ 取消
|
|
|
+ </el-button>
|
|
|
+ <el-button class="btn-main" type="primary" @click="handleStockInSubmit" :loading="stockInLoading">
|
|
|
+ <el-icon><Check /></el-icon>
|
|
|
+ 确定
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+ import { ref, reactive, onMounted } from 'vue'
|
|
|
+ import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
+ import { Plus, Close, Check } from '@element-plus/icons-vue'
|
|
|
+ import {
|
|
|
+ fetchQcMaterialList,
|
|
|
+ fetchCreateQcMaterial,
|
|
|
+ fetchUpdateQcMaterial,
|
|
|
+ fetchDeleteQcMaterial,
|
|
|
+ fetchStockInQcMaterial,
|
|
|
+ } from '@/api/qc-material'
|
|
|
+ import type { QcMaterial } from '@/api/qc-material'
|
|
|
+
|
|
|
+ defineOptions({ name: 'QualityInspectionQcMaterial' })
|
|
|
+
|
|
|
+ const searchForm = reactive({ keyword: '', status: '' })
|
|
|
+ const tableData = ref<QcMaterial[]>([])
|
|
|
+ const selectedRows = ref<QcMaterial[]>([])
|
|
|
+ const loading = ref(false)
|
|
|
+ const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
|
|
|
+
|
|
|
+ // 新增/编辑
|
|
|
+ const dialogVisible = ref(false)
|
|
|
+ const dialogTitle = ref('新增物料')
|
|
|
+ const isEdit = ref(false)
|
|
|
+ const submitLoading = ref(false)
|
|
|
+ const formRef = ref()
|
|
|
+ const editingId = ref<string | null>(null)
|
|
|
+
|
|
|
+ const formData = reactive({
|
|
|
+ materialCode: '',
|
|
|
+ materialName: '',
|
|
|
+ specModel: '',
|
|
|
+ unit: '',
|
|
|
+ warningQty: 0,
|
|
|
+ status: '1',
|
|
|
+ remark: ''
|
|
|
+ })
|
|
|
+
|
|
|
+ const formRules = {
|
|
|
+ materialCode: [{ required: true, message: '请输入物料编码', trigger: 'blur' }],
|
|
|
+ materialName: [{ required: true, message: '请输入物料名称', trigger: 'blur' }]
|
|
|
+ }
|
|
|
+
|
|
|
+ // 入库
|
|
|
+ const stockInVisible = ref(false)
|
|
|
+ const stockInLoading = ref(false)
|
|
|
+ const stockInFormRef = ref()
|
|
|
+ const stockInTargetId = ref<string | null>(null)
|
|
|
+
|
|
|
+ const stockInForm = reactive({
|
|
|
+ materialName: '',
|
|
|
+ currentStock: 0,
|
|
|
+ qty: undefined as number | undefined,
|
|
|
+ remark: ''
|
|
|
+ })
|
|
|
+
|
|
|
+ const stockInRules = {
|
|
|
+ qty: [{ required: true, message: '请输入入库数量', trigger: 'blur' }]
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleSelectionChange = (rows: QcMaterial[]) => {
|
|
|
+ selectedRows.value = rows
|
|
|
+ }
|
|
|
+
|
|
|
+ async function loadData() {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const res = await fetchQcMaterialList({
|
|
|
+ page: pagination.page,
|
|
|
+ pageSize: pagination.pageSize,
|
|
|
+ keyword: searchForm.keyword || undefined,
|
|
|
+ status: searchForm.status || undefined
|
|
|
+ })
|
|
|
+ tableData.value = res.list || []
|
|
|
+ pagination.total = res.total ?? 0
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleSearch() {
|
|
|
+ pagination.page = 1
|
|
|
+ loadData()
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleReset() {
|
|
|
+ searchForm.keyword = ''
|
|
|
+ searchForm.status = ''
|
|
|
+ pagination.page = 1
|
|
|
+ loadData()
|
|
|
+ }
|
|
|
+
|
|
|
+ // 新增/编辑
|
|
|
+ function resetForm() {
|
|
|
+ formData.materialCode = ''
|
|
|
+ formData.materialName = ''
|
|
|
+ formData.specModel = ''
|
|
|
+ formData.unit = ''
|
|
|
+ formData.warningQty = 0
|
|
|
+ formData.status = '1'
|
|
|
+ formData.remark = ''
|
|
|
+ editingId.value = null
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleAdd() {
|
|
|
+ isEdit.value = false
|
|
|
+ dialogTitle.value = '新增物料'
|
|
|
+ resetForm()
|
|
|
+ dialogVisible.value = true
|
|
|
+ formRef.value?.clearValidate?.()
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleEdit(row: QcMaterial) {
|
|
|
+ isEdit.value = true
|
|
|
+ dialogTitle.value = '编辑物料'
|
|
|
+ editingId.value = row.id
|
|
|
+ formData.materialCode = row.materialCode || ''
|
|
|
+ formData.materialName = row.materialName || ''
|
|
|
+ formData.specModel = row.specModel || ''
|
|
|
+ formData.unit = row.unit || ''
|
|
|
+ formData.warningQty = row.warningQty ?? 0
|
|
|
+ formData.status = row.status || '1'
|
|
|
+ formData.remark = row.remark || ''
|
|
|
+ dialogVisible.value = true
|
|
|
+ formRef.value?.clearValidate?.()
|
|
|
+ }
|
|
|
+
|
|
|
+ async function handleSubmit() {
|
|
|
+ await formRef.value?.validate?.()
|
|
|
+ submitLoading.value = true
|
|
|
+ try {
|
|
|
+ const payload = {
|
|
|
+ materialCode: formData.materialCode.trim(),
|
|
|
+ materialName: formData.materialName.trim(),
|
|
|
+ specModel: formData.specModel?.trim() || undefined,
|
|
|
+ unit: formData.unit?.trim() || undefined,
|
|
|
+ warningQty: formData.warningQty,
|
|
|
+ status: formData.status,
|
|
|
+ remark: formData.remark?.trim() || undefined
|
|
|
+ }
|
|
|
+ if (isEdit.value && editingId.value) {
|
|
|
+ await fetchUpdateQcMaterial(editingId.value, payload)
|
|
|
+ } else {
|
|
|
+ await fetchCreateQcMaterial(payload)
|
|
|
+ }
|
|
|
+ ElMessage.success(isEdit.value ? '更新成功' : '创建成功')
|
|
|
+ dialogVisible.value = false
|
|
|
+ await loadData()
|
|
|
+ } catch {
|
|
|
+ /* 校验或接口错误已由全局处理 */
|
|
|
+ } finally {
|
|
|
+ submitLoading.value = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 入库
|
|
|
+ function openStockInDialog(row: QcMaterial) {
|
|
|
+ stockInTargetId.value = row.id
|
|
|
+ stockInForm.materialName = row.materialName || ''
|
|
|
+ stockInForm.currentStock = row.stockQty ?? 0
|
|
|
+ stockInForm.qty = undefined
|
|
|
+ stockInForm.remark = ''
|
|
|
+ stockInVisible.value = true
|
|
|
+ stockInFormRef.value?.clearValidate?.()
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleStockIn(row: QcMaterial) {
|
|
|
+ openStockInDialog(row)
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleStockInFromToolbar() {
|
|
|
+ if (selectedRows.value.length === 0) return
|
|
|
+ openStockInDialog(selectedRows.value[0])
|
|
|
+ }
|
|
|
+
|
|
|
+ async function handleStockInSubmit() {
|
|
|
+ await stockInFormRef.value?.validate?.()
|
|
|
+ if (!stockInTargetId.value) return
|
|
|
+ stockInLoading.value = true
|
|
|
+ try {
|
|
|
+ await fetchStockInQcMaterial(stockInTargetId.value, {
|
|
|
+ qty: stockInForm.qty!,
|
|
|
+ remark: stockInForm.remark?.trim() || undefined
|
|
|
+ })
|
|
|
+ ElMessage.success('入库成功')
|
|
|
+ stockInVisible.value = false
|
|
|
+ await loadData()
|
|
|
+ } catch {
|
|
|
+ /* 校验或接口错误已由全局处理 */
|
|
|
+ } finally {
|
|
|
+ stockInLoading.value = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查看变动记录
|
|
|
+ function handleViewUsage(row: QcMaterial) {
|
|
|
+ window.location.hash = `/sqxp/quality-inspection/qc-material-usage?materialId=${row.id}`
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除
|
|
|
+ async function handleDelete(row: QcMaterial) {
|
|
|
+ if ((row.stockQty ?? 0) > 0) {
|
|
|
+ ElMessage.warning('当前库存大于0,无法删除')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ await ElMessageBox.confirm('确定要删除该物料吗?', '提示', { type: 'warning' })
|
|
|
+ } catch {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ await fetchDeleteQcMaterial(row.id)
|
|
|
+ ElMessage.success('删除成功')
|
|
|
+ await loadData()
|
|
|
+ }
|
|
|
+
|
|
|
+ onMounted(() => loadData())
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+ .material-dialog-form {
|
|
|
+ :deep(.el-form-item) {
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+</style>
|