Procházet zdrojové kódy

feat(web-admin): add qc material month import template UI

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu před 2 měsíci
rodič
revize
6957952b4c

+ 83 - 0
web-admin/src/api/qc-material-month-template.ts

@@ -0,0 +1,83 @@
+import request from '@/utils/http'
+
+export interface QcMonthTemplateRow {
+  id: string
+  materialId?: string
+  materialCode: string
+  materialName: string
+  specModel?: string
+  unit?: string
+  catalogNo: string
+  batchNo: string
+  manufacturer: string
+  unitPrice: number
+  amount: number
+  inQty: number
+  expireDate: string
+  orderNum?: number
+  remark?: string
+}
+
+export interface QcMonthImportPreviewLine {
+  templateId?: string
+  materialId?: string
+  materialCode: string
+  materialName: string
+  specModel?: string
+  unit?: string
+  stockInNo: string
+  stockInDate: string
+  expireDate: string
+  catalogNo: string
+  batchNo: string
+  manufacturer: string
+  unitPrice: number
+  amount: number
+  qty: number
+  remark?: string
+}
+
+export function fetchQcMonthTemplateList() {
+  return request.get<QcMonthTemplateRow[]>({
+    url: '/web/admin/qc-material-month-templates'
+  })
+}
+
+export function fetchCreateQcMonthTemplate(data: Record<string, any>) {
+  return request.post<QcMonthTemplateRow>({
+    url: '/web/admin/qc-material-month-templates',
+    data
+  })
+}
+
+export function fetchUpdateQcMonthTemplate(id: string, data: Record<string, any>) {
+  return request.put<QcMonthTemplateRow>({
+    url: `/web/admin/qc-material-month-templates/${id}`,
+    data
+  })
+}
+
+export function fetchDeleteQcMonthTemplate(id: string) {
+  return request.delete<void>({
+    url: `/web/admin/qc-material-month-templates/${id}`
+  })
+}
+
+export function fetchPreviewQcMonthImport(stockInDate: string) {
+  return request.post<QcMonthImportPreviewLine[]>({
+    url: '/web/admin/qc-material-month-templates/preview',
+    data: { stockInDate }
+  })
+}
+
+export function fetchConfirmQcMonthImport(data: {
+  stockInDate: string
+  items: QcMonthImportPreviewLine[]
+  remark?: string
+  stockInTime?: string
+}) {
+  return request.post<{ count: number }>({
+    url: '/web/admin/qc-material-month-templates/confirm',
+    data
+  })
+}

+ 261 - 0
web-admin/src/views/quality-inspection/qc-material/QcMaterialMonthImportDialog.vue

@@ -0,0 +1,261 @@
+<!-- 质检物料库 · 月初导入预览确认 -->
+<!-- eslint-disable vue/no-v-model-argument -->
+<template>
+  <el-dialog
+    :model-value="modelValue"
+    title="月初导入"
+    width="1080px"
+    class="dialog-form-layout material-batch-stock-dialog"
+    align-center
+    append-to-body
+    modal-class="protocol-dialog-mask"
+    destroy-on-close
+    :close-on-click-modal="false"
+    @update:model-value="emit('update:modelValue', $event)"
+    @open="handleOpen"
+  >
+    <el-form label-position="top">
+      <div class="form-grid-2 batch-stock-move-head">
+        <el-form-item label="本次入库日期">
+          <el-date-picker
+            v-model="stockInDate"
+            type="date"
+            value-format="YYYY-MM-DD"
+            placeholder="选择入库日期"
+            style="width: 100%"
+            @change="reloadPreview"
+          />
+        </el-form-item>
+        <el-form-item label="备注">
+          <el-input
+            v-model="remark"
+            type="textarea"
+            :rows="2"
+            placeholder="可选,未填写行备注时将作为默认备注"
+          />
+        </el-form-item>
+      </div>
+    </el-form>
+
+    <div class="batch-stock-move-lines-head" style="margin-bottom: 12px">
+      <div class="batch-stock-move-lines-head-left">
+        <span class="batch-stock-move-lines-title">入库明细</span>
+        <span class="batch-stock-move-lines-hint">
+          共 {{ lines.length }} 项,可改字段后确认;删除行仅影响本次
+        </span>
+      </div>
+    </div>
+
+    <div v-loading="loading">
+      <div v-if="lines.length" class="batch-in-line-list">
+        <div v-for="(row, index) in lines" :key="index" class="batch-in-line-card">
+          <div class="batch-in-line-card-head">
+            <span class="batch-in-line-card-index">{{ index + 1 }}</span>
+            <el-button link type="danger" size="small" @click="lines.splice(index, 1)">
+              <el-icon><Delete /></el-icon>
+            </el-button>
+          </div>
+          <div class="batch-in-line-grid batch-in-line-card-body">
+            <el-form-item label="物料" class="batch-in-col-span-2">
+              <el-input
+                :model-value="`${row.materialName}(${row.materialCode})`"
+                size="small"
+                disabled
+              />
+            </el-form-item>
+            <el-form-item label="入库编号">
+              <el-input v-model="row.stockInNo" size="small" placeholder="必填" />
+            </el-form-item>
+            <el-form-item label="过期日期">
+              <el-date-picker
+                v-model="row.expireDate"
+                type="date"
+                size="small"
+                value-format="YYYY-MM-DD"
+                style="width: 100%"
+              />
+            </el-form-item>
+            <el-form-item label="货号">
+              <el-input v-model="row.catalogNo" size="small" />
+            </el-form-item>
+            <el-form-item label="批号">
+              <el-input v-model="row.batchNo" size="small" />
+            </el-form-item>
+            <el-form-item label="厂家">
+              <el-input v-model="row.manufacturer" size="small" />
+            </el-form-item>
+            <el-form-item label="数量">
+              <el-input-number
+                v-model="row.qty"
+                size="small"
+                :min="0.0001"
+                :precision="4"
+                controls-position="right"
+                style="width: 100%"
+                @change="() => recalcAmount(row)"
+              />
+            </el-form-item>
+            <el-form-item label="单价">
+              <el-input-number
+                v-model="row.unitPrice"
+                size="small"
+                :min="0"
+                :precision="2"
+                controls-position="right"
+                style="width: 100%"
+                @change="() => recalcAmount(row)"
+              />
+            </el-form-item>
+            <el-form-item label="金额">
+              <el-input-number
+                v-model="row.amount"
+                size="small"
+                :min="0"
+                :precision="2"
+                controls-position="right"
+                style="width: 100%"
+                @change="() => markAmountTouched(row)"
+              />
+            </el-form-item>
+            <el-form-item label="备注" class="batch-in-col-span-2">
+              <el-input v-model="row.remark" size="small" placeholder="可选" />
+            </el-form-item>
+          </div>
+        </div>
+      </div>
+      <el-empty v-else description="暂无预览明细" :image-size="56" />
+    </div>
+
+    <template #footer>
+      <el-button class="btn-ghost" @click="emit('update:modelValue', false)">
+        <el-icon><Close /></el-icon>
+        取消
+      </el-button>
+      <el-button class="btn-main" type="primary" :loading="submitting" @click="handleConfirm">
+        <el-icon><Check /></el-icon>
+        确认入库
+      </el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+  import { ElMessage } from 'element-plus'
+  import { Check, Close, Delete } from '@element-plus/icons-vue'
+  import {
+    fetchConfirmQcMonthImport,
+    fetchPreviewQcMonthImport,
+    type QcMonthImportPreviewLine
+  } from '@/api/qc-material-month-template'
+
+  defineOptions({ name: 'QcMaterialMonthImportDialog' })
+
+  defineProps<{ modelValue: boolean }>()
+  const emit = defineEmits<{
+    (e: 'update:modelValue', val: boolean): void
+    (e: 'success'): void
+  }>()
+
+  const loading = ref(false)
+  const submitting = ref(false)
+  const stockInDate = ref('')
+  const remark = ref('')
+  const lines = ref<QcMonthImportPreviewLine[]>([])
+  const amountTouchedMap = ref<Record<number, boolean>>({})
+
+  function todayStr() {
+    const d = new Date()
+    const m = `${d.getMonth() + 1}`.padStart(2, '0')
+    const day = `${d.getDate()}`.padStart(2, '0')
+    return `${d.getFullYear()}-${m}-${day}`
+  }
+
+  async function handleOpen() {
+    stockInDate.value = todayStr()
+    remark.value = ''
+    amountTouchedMap.value = {}
+    await reloadPreview()
+  }
+
+  async function reloadPreview() {
+    if (!stockInDate.value) {
+      ElMessage.warning('请选择入库日期')
+      return
+    }
+    loading.value = true
+    try {
+      lines.value = (await fetchPreviewQcMonthImport(stockInDate.value)) || []
+      amountTouchedMap.value = {}
+    } catch {
+      lines.value = []
+    } finally {
+      loading.value = false
+    }
+  }
+
+  function markAmountTouched(row: QcMonthImportPreviewLine) {
+    const idx = lines.value.indexOf(row)
+    if (idx >= 0) amountTouchedMap.value[idx] = true
+  }
+
+  function recalcAmount(row: QcMonthImportPreviewLine) {
+    const idx = lines.value.indexOf(row)
+    if (idx >= 0 && amountTouchedMap.value[idx]) return
+    const price = Number(row.unitPrice) || 0
+    const qty = Number(row.qty) || 0
+    row.amount = Number((price * qty).toFixed(2))
+  }
+
+  async function handleConfirm() {
+    if (!stockInDate.value) {
+      ElMessage.warning('请选择入库日期')
+      return
+    }
+    if (!lines.value.length) {
+      ElMessage.warning('请至少保留一条入库明细')
+      return
+    }
+    for (let i = 0; i < lines.value.length; i++) {
+      const line = lines.value[i]
+      if (!line.stockInNo?.trim()) {
+        ElMessage.warning(`第 ${i + 1} 行请填写入库编号`)
+        return
+      }
+      if (!line.expireDate) {
+        ElMessage.warning(`第 ${i + 1} 行请选择过期日期`)
+        return
+      }
+      if (!line.catalogNo?.trim() || !line.batchNo?.trim() || !line.manufacturer?.trim()) {
+        ElMessage.warning(`第 ${i + 1} 行货号/批号/厂家不能为空`)
+        return
+      }
+      if (!(Number(line.qty) > 0)) {
+        ElMessage.warning(`第 ${i + 1} 行数量必须大于0`)
+        return
+      }
+    }
+    submitting.value = true
+    try {
+      const res = await fetchConfirmQcMonthImport({
+        stockInDate: stockInDate.value,
+        remark: remark.value?.trim() || undefined,
+        items: lines.value.map((l) => ({
+          ...l,
+          materialId: l.materialId || undefined,
+          stockInNo: l.stockInNo.trim(),
+          catalogNo: l.catalogNo.trim(),
+          batchNo: l.batchNo.trim(),
+          manufacturer: l.manufacturer.trim(),
+          qty: Number(l.qty),
+          unitPrice: Number(l.unitPrice),
+          amount: Number(l.amount)
+        }))
+      })
+      ElMessage.success(`月初导入成功,共 ${res?.count ?? lines.value.length} 项`)
+      emit('update:modelValue', false)
+      emit('success')
+    } finally {
+      submitting.value = false
+    }
+  }
+</script>

+ 379 - 0
web-admin/src/views/quality-inspection/qc-material/QcMaterialMonthTemplateDialog.vue

@@ -0,0 +1,379 @@
+<!-- 质检物料库 · 维护月初入库模板 -->
+<!-- eslint-disable vue/no-v-model-argument -->
+<template>
+  <div class="qc-month-template-dialogs">
+  <el-dialog
+    :model-value="modelValue"
+    title="维护月初模板"
+    width="1080px"
+    class="dialog-form-layout material-batch-stock-dialog"
+    align-center
+    append-to-body
+    modal-class="protocol-dialog-mask"
+    destroy-on-close
+    :close-on-click-modal="false"
+    @update:model-value="emit('update:modelValue', $event)"
+    @open="handleOpen"
+  >
+    <div class="batch-stock-move-lines-head" style="margin-bottom: 12px">
+      <div class="batch-stock-move-lines-head-left">
+        <span class="batch-stock-move-lines-title">模板明细</span>
+        <span class="batch-stock-move-lines-hint">共 {{ rows.length }} 项,保存后供「月初导入」使用</span>
+      </div>
+      <el-button class="btn-ghost" @click="openEdit()">
+        <el-icon><Plus /></el-icon>
+        新增一行
+      </el-button>
+    </div>
+
+    <el-table v-loading="loading" :data="rows" class="list-table" stripe max-height="420">
+      <el-table-column label="物料编码" prop="materialCode" min-width="120" show-overflow-tooltip />
+      <el-table-column label="物料名称" prop="materialName" min-width="140" show-overflow-tooltip />
+      <el-table-column label="货号" prop="catalogNo" min-width="100" show-overflow-tooltip />
+      <el-table-column label="批号" prop="batchNo" min-width="100" show-overflow-tooltip />
+      <el-table-column label="厂家" prop="manufacturer" min-width="120" show-overflow-tooltip />
+      <el-table-column label="数量" prop="inQty" min-width="80" align="right" />
+      <el-table-column label="过期日期" prop="expireDate" min-width="110" />
+      <el-table-column label="操作" fixed="right" min-width="160">
+        <template #default="{ row }">
+          <el-button link type="primary" @click="openEdit(row)">
+            <el-icon><Edit /></el-icon>
+            编辑
+          </el-button>
+          <el-button link type="danger" @click="handleDelete(row)">
+            <el-icon><Delete /></el-icon>
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+      <template #empty>
+        <el-empty description="暂无模板,请点击「新增一行」" :image-size="64" />
+      </template>
+    </el-table>
+
+    <template #footer>
+      <el-button class="btn-ghost" @click="emit('update:modelValue', false)">
+        <el-icon><Close /></el-icon>
+        关闭
+      </el-button>
+    </template>
+  </el-dialog>
+
+  <el-dialog
+    v-model="editVisible"
+    :title="editForm.id ? '编辑模板行' : '新增模板行'"
+    width="720px"
+    class="dialog-form-layout"
+    align-center
+    append-to-body
+    modal-class="protocol-dialog-mask"
+    destroy-on-close
+    :close-on-click-modal="false"
+  >
+    <el-form
+      ref="editFormRef"
+      :model="editForm"
+      :rules="editRules"
+      label-position="top"
+      scroll-to-error
+      :scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
+    >
+      <div class="form-grid-2">
+        <el-form-item label="关联物资" class="form-col-span-2">
+          <el-select
+            v-model="editForm.materialId"
+            filterable
+            clearable
+            placeholder="可选;不选则可手填编码名称,导入时自动创建"
+            style="width: 100%"
+            :loading="materialOptionsLoading"
+            @change="onMaterialChange"
+          >
+            <el-option
+              v-for="opt in materialOptions"
+              :key="opt.id"
+              :label="`${opt.materialName}(${opt.materialCode})`"
+              :value="opt.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="物料编码" prop="materialCode">
+          <el-input v-model="editForm.materialCode" placeholder="必填" />
+        </el-form-item>
+        <el-form-item label="物料名称" prop="materialName">
+          <el-input v-model="editForm.materialName" placeholder="必填" />
+        </el-form-item>
+        <el-form-item label="规格型号">
+          <el-input v-model="editForm.specModel" placeholder="可选" />
+        </el-form-item>
+        <el-form-item label="单位">
+          <el-input v-model="editForm.unit" placeholder="可选" />
+        </el-form-item>
+        <el-form-item label="货号" prop="catalogNo">
+          <el-input v-model="editForm.catalogNo" placeholder="必填" />
+        </el-form-item>
+        <el-form-item label="批号" prop="batchNo">
+          <el-input v-model="editForm.batchNo" placeholder="必填" />
+        </el-form-item>
+        <el-form-item label="厂家" prop="manufacturer">
+          <el-input v-model="editForm.manufacturer" placeholder="必填" />
+        </el-form-item>
+        <el-form-item label="过期日期" prop="expireDate">
+          <el-date-picker
+            v-model="editForm.expireDate"
+            type="date"
+            value-format="YYYY-MM-DD"
+            placeholder="选择过期日期"
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="单价(元)" prop="unitPrice">
+          <el-input-number
+            v-model="editForm.unitPrice"
+            :min="0"
+            :precision="2"
+            controls-position="right"
+            style="width: 100%"
+            @change="onPriceOrQtyChange"
+          />
+        </el-form-item>
+        <el-form-item label="数量" prop="inQty">
+          <el-input-number
+            v-model="editForm.inQty"
+            :min="0.0001"
+            :precision="4"
+            controls-position="right"
+            style="width: 100%"
+            @change="onPriceOrQtyChange"
+          />
+        </el-form-item>
+        <el-form-item label="金额(元)" prop="amount">
+          <el-input-number
+            v-model="editForm.amount"
+            :min="0"
+            :precision="2"
+            controls-position="right"
+            style="width: 100%"
+            @change="amountTouched = true"
+          />
+        </el-form-item>
+        <el-form-item label="备注" class="form-col-span-2">
+          <el-input v-model="editForm.remark" type="textarea" :rows="2" placeholder="可选" />
+        </el-form-item>
+      </div>
+    </el-form>
+    <template #footer>
+      <el-button class="btn-ghost" @click="editVisible = false">
+        <el-icon><Close /></el-icon>
+        取消
+      </el-button>
+      <el-button class="btn-main" type="primary" :loading="saving" @click="handleSave">
+        <el-icon><Check /></el-icon>
+        保存
+      </el-button>
+    </template>
+  </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
+  import { Check, Close, Delete, Edit, Plus } from '@element-plus/icons-vue'
+  import { fetchQcMaterialOptions, type QcMaterialOption } from '@/api/qc-material'
+  import {
+    fetchCreateQcMonthTemplate,
+    fetchDeleteQcMonthTemplate,
+    fetchQcMonthTemplateList,
+    fetchUpdateQcMonthTemplate,
+    type QcMonthTemplateRow
+  } from '@/api/qc-material-month-template'
+
+  defineOptions({ name: 'QcMaterialMonthTemplateDialog' })
+
+  defineProps<{ modelValue: boolean }>()
+  const emit = defineEmits<{
+    (e: 'update:modelValue', val: boolean): void
+    (e: 'saved'): void
+  }>()
+
+  const loading = ref(false)
+  const saving = ref(false)
+  const rows = ref<QcMonthTemplateRow[]>([])
+  const materialOptions = ref<QcMaterialOption[]>([])
+  const materialOptionsLoading = ref(false)
+
+  const editVisible = ref(false)
+  const editFormRef = ref<FormInstance>()
+  const amountTouched = ref(false)
+  const editForm = reactive({
+    id: '',
+    materialId: '',
+    materialCode: '',
+    materialName: '',
+    specModel: '',
+    unit: '',
+    catalogNo: '',
+    batchNo: '',
+    manufacturer: '',
+    unitPrice: 0,
+    amount: 0,
+    inQty: 1,
+    expireDate: '',
+    remark: ''
+  })
+
+  const editRules: FormRules = {
+    materialCode: [{ required: true, message: '请输入物料编码', trigger: 'blur' }],
+    materialName: [{ required: true, message: '请输入物料名称', trigger: 'blur' }],
+    catalogNo: [{ required: true, message: '请输入货号', trigger: 'blur' }],
+    batchNo: [{ required: true, message: '请输入批号', trigger: 'blur' }],
+    manufacturer: [{ required: true, message: '请输入厂家', trigger: 'blur' }],
+    expireDate: [{ required: true, message: '请选择过期日期', trigger: 'change' }],
+    unitPrice: [{ required: true, message: '请输入单价', trigger: 'blur' }],
+    amount: [{ required: true, message: '请输入金额', trigger: 'blur' }],
+    inQty: [{ required: true, message: '请输入数量', trigger: 'blur' }]
+  }
+
+  async function loadRows() {
+    loading.value = true
+    try {
+      rows.value = (await fetchQcMonthTemplateList()) || []
+    } catch {
+      rows.value = []
+    } finally {
+      loading.value = false
+    }
+  }
+
+  async function loadMaterialOptions() {
+    materialOptionsLoading.value = true
+    try {
+      materialOptions.value = (await fetchQcMaterialOptions()) || []
+    } catch {
+      materialOptions.value = []
+    } finally {
+      materialOptionsLoading.value = false
+    }
+  }
+
+  async function handleOpen() {
+    await Promise.all([loadRows(), loadMaterialOptions()])
+  }
+
+  function resetEditForm() {
+    editForm.id = ''
+    editForm.materialId = ''
+    editForm.materialCode = ''
+    editForm.materialName = ''
+    editForm.specModel = ''
+    editForm.unit = ''
+    editForm.catalogNo = ''
+    editForm.batchNo = ''
+    editForm.manufacturer = ''
+    editForm.unitPrice = 0
+    editForm.amount = 0
+    editForm.inQty = 1
+    editForm.expireDate = ''
+    editForm.remark = ''
+    amountTouched.value = false
+  }
+
+  function openEdit(row?: QcMonthTemplateRow) {
+    resetEditForm()
+    if (row) {
+      editForm.id = row.id
+      editForm.materialId = row.materialId || ''
+      editForm.materialCode = row.materialCode || ''
+      editForm.materialName = row.materialName || ''
+      editForm.specModel = row.specModel || ''
+      editForm.unit = row.unit || ''
+      editForm.catalogNo = row.catalogNo || ''
+      editForm.batchNo = row.batchNo || ''
+      editForm.manufacturer = row.manufacturer || ''
+      editForm.unitPrice = Number(row.unitPrice) || 0
+      editForm.amount = Number(row.amount) || 0
+      editForm.inQty = Number(row.inQty) || 1
+      editForm.expireDate = row.expireDate || ''
+      editForm.remark = row.remark || ''
+      amountTouched.value = true
+    }
+    editVisible.value = true
+  }
+
+  function onMaterialChange(id: string) {
+    const opt = materialOptions.value.find((o) => o.id === id)
+    if (!opt) return
+    editForm.materialId = opt.id
+    editForm.materialCode = opt.materialCode || ''
+    editForm.materialName = opt.materialName || ''
+    editForm.specModel = opt.specModel || ''
+    editForm.unit = opt.unit || ''
+  }
+
+  function onPriceOrQtyChange() {
+    if (amountTouched.value) return
+    const price = Number(editForm.unitPrice) || 0
+    const qty = Number(editForm.inQty) || 0
+    editForm.amount = Number((price * qty).toFixed(2))
+  }
+
+  async function handleSave() {
+    const form = editFormRef.value
+    if (!form) return
+    try {
+      await form.validate()
+    } catch {
+      return
+    }
+    if (!(Number(editForm.inQty) > 0)) {
+      ElMessage.warning('数量必须大于0')
+      return
+    }
+    saving.value = true
+    try {
+      const payload = {
+        materialId: editForm.materialId || undefined,
+        materialCode: editForm.materialCode.trim(),
+        materialName: editForm.materialName.trim(),
+        specModel: editForm.specModel?.trim() || undefined,
+        unit: editForm.unit?.trim() || undefined,
+        catalogNo: editForm.catalogNo.trim(),
+        batchNo: editForm.batchNo.trim(),
+        manufacturer: editForm.manufacturer.trim(),
+        unitPrice: Number(editForm.unitPrice),
+        amount: Number(editForm.amount),
+        inQty: Number(editForm.inQty),
+        expireDate: editForm.expireDate,
+        remark: editForm.remark?.trim() || undefined
+      }
+      if (editForm.id) {
+        await fetchUpdateQcMonthTemplate(editForm.id, payload)
+        ElMessage.success('更新成功')
+      } else {
+        await fetchCreateQcMonthTemplate(payload)
+        ElMessage.success('新增成功')
+      }
+      editVisible.value = false
+      await loadRows()
+      emit('saved')
+    } finally {
+      saving.value = false
+    }
+  }
+
+  async function handleDelete(row: QcMonthTemplateRow) {
+    try {
+      await ElMessageBox.confirm(`确定删除模板「${row.materialName}」吗?`, '删除确认', {
+        type: 'warning',
+        confirmButtonText: '删除',
+        cancelButtonText: '取消'
+      })
+    } catch {
+      return
+    }
+    await fetchDeleteQcMonthTemplate(row.id)
+    ElMessage.success('删除成功')
+    await loadRows()
+    emit('saved')
+  }
+</script>

+ 41 - 1
web-admin/src/views/quality-inspection/qc-material/index.vue

@@ -88,6 +88,14 @@
             <el-icon><CircleClose /></el-icon>
             批量废弃
           </el-button>
+          <el-button class="btn-ghost" @click="handleMonthImport">
+            <el-icon><Calendar /></el-icon>
+            月初导入
+          </el-button>
+          <el-button class="btn-ghost" @click="monthTemplateVisible = true">
+            <el-icon><Setting /></el-icon>
+            维护月初模板
+          </el-button>
           <ArtExcelExport
             type="default"
             :data="batchExportData"
@@ -1213,6 +1221,11 @@
         :batch="attachmentBatch"
         @saved="loadData"
       />
+      <QcMaterialMonthTemplateDialog v-model="monthTemplateVisible" />
+      <QcMaterialMonthImportDialog
+        v-model="monthImportVisible"
+        @success="handleMonthImportSuccess"
+      />
       </div>
 
       <div v-show="activeTab === 'usage'" class="panel-track-pane">
@@ -1242,7 +1255,9 @@
     Edit,
     Delete,
     CircleClose,
-    Paperclip
+    Paperclip,
+    Calendar,
+    Setting
   } from '@element-plus/icons-vue'
   import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
   import AmountInput from '@/components/common/amount-input/index.vue'
@@ -1269,6 +1284,9 @@
   import QcMaterialUsageTab from './QcMaterialUsageTab.vue'
   import QcMaterialStockCheckTab from './QcMaterialStockCheckTab.vue'
   import QcMaterialBatchAttachmentDialog from './QcMaterialBatchAttachmentDialog.vue'
+  import QcMaterialMonthTemplateDialog from './QcMaterialMonthTemplateDialog.vue'
+  import QcMaterialMonthImportDialog from './QcMaterialMonthImportDialog.vue'
+  import { fetchQcMonthTemplateList } from '@/api/qc-material-month-template'
   import { useMenuPerm } from '@/hooks/core/useMenuPerm'
 
   defineOptions({ name: 'QualityInspectionQcMaterial' })
@@ -1279,6 +1297,9 @@
   /** 暂时隐藏批量入库入口,恢复时改为 true */
   const batchStockInButtonVisible = false
 
+  const monthTemplateVisible = ref(false)
+  const monthImportVisible = ref(false)
+
   const route = useRoute()
 
   const QC_MATERIAL_TABS = [
@@ -2317,6 +2338,25 @@
     batchStockInFormRef.value?.clearValidate?.()
   }
 
+  async function handleMonthImport() {
+    try {
+      const list = (await fetchQcMonthTemplateList()) || []
+      if (!list.length) {
+        ElMessage.warning('请先维护月初模板')
+        monthTemplateVisible.value = true
+        return
+      }
+      monthImportVisible.value = true
+    } catch {
+      /* request 层已提示 */
+    }
+  }
+
+  async function handleMonthImportSuccess() {
+    monthImportVisible.value = false
+    await loadData()
+  }
+
   async function handleBatchStockInSubmit() {
     await batchStockInFormRef.value?.validate?.()
     if (!batchStockInLines.value.length) {