针对于条形码生成进行修改

This commit is contained in:
dxc
2026-02-02 16:43:35 +08:00
parent cf6a4a8957
commit 11a4e5f48a
4 changed files with 364 additions and 134 deletions

View File

@ -88,5 +88,5 @@ class StockBuy(db.Model):
# [新增] 返回全局打印ID及其格式化字符串
'global_print_id': self.global_print_id,
'global_print_id_str': f"{self.global_print_id:08d}" if self.global_print_id else ""
'global_print_id_str': f"{self.global_print_id:010d}" if self.global_print_id else ""
}

View File

@ -79,7 +79,7 @@ class BuyInboundService:
# ------------------------------------------------------------------
# 2. 自动生成 SKU (格式: 00000001)
# ------------------------------------------------------------------
generated_sku = f"{next_global_id:08d}"
generated_sku = str(next_global_id).zfill(10)
# ------------------------------------------------------------------
# 3. 条码逻辑处理 (核心修改)

View File

@ -26,12 +26,13 @@ class LabelPrintService:
LABEL_HEIGHT = int(LABEL_HEIGHT_MM * DOTS_PER_MM)
# 顶部留白: 10mm (120px) - 内容从这里开始
TOP_MARGIN_MM = 5
TOP_MARGIN_MM = 2
TOP_MARGIN_PX = int(TOP_MARGIN_MM * DOTS_PER_MM)
@staticmethod
def _get_font(size):
"""获取字体"""
# 尝试加载中文字体,否则乱码
font_names = ["simhei.ttf", "msyh.ttf", "SimHei.ttf", "arial.ttf"]
base_dirs = [os.getcwd(), os.path.dirname(__file__)]
@ -48,6 +49,10 @@ class LabelPrintService:
生成真实的条形码图片
"""
try:
# 确保内容字符串有效
if not content:
content = "0000000000"
# 使用 Code128 编码(通用性最强)
code128 = barcode.get('code128', content, writer=ImageWriter())
@ -85,31 +90,31 @@ class LabelPrintService:
sku_code = data.get('sku')
if not sku_code:
# 兜底逻辑:如果没有 SKU尝试用 serial_number 或 global_id
sku_code = data.get('serial_number') or str(data.get('global_print_id', '00000000')).zfill(8)
sku_code = data.get('serial_number') or str(data.get('global_print_id', '0000000000')).zfill(10)
# ==================== 绘制布局 ====================
# X 轴偏移 (5mm)
GLOBAL_OFFSET_X = int(5 * LabelPrintService.DOTS_PER_MM)
GLOBAL_OFFSET_X = int(2 * LabelPrintService.DOTS_PER_MM)
# Y 轴起始: 10mm 处
CURRENT_Y = LabelPrintService.TOP_MARGIN_PX
# --- A. 绘制真实条形码 ---
bc_w = int(28 * LabelPrintService.DOTS_PER_MM) # 条码宽约 28mm
bc_h = int(6 * LabelPrintService.DOTS_PER_MM) # 条码高约 6mm
# --- A. 绘制真实条形码 (内容为 SKU) ---
bc_w = int(35 * LabelPrintService.DOTS_PER_MM) # 条码宽约 28mm
bc_h = int(10 * LabelPrintService.DOTS_PER_MM) # 条码高约 6mm
# 生成并粘贴条码
bc_img = LabelPrintService._generate_barcode_image(sku_code, bc_w, bc_h)
# 粘贴位置: (X, Y)
img.paste(bc_img, (GLOBAL_OFFSET_X - 5, CURRENT_Y))
img.paste(bc_img, (GLOBAL_OFFSET_X - 10, CURRENT_Y))
# 更新 Y 坐标 (条码高度 + 间距)
CURRENT_Y += bc_h + 5
# --- B. 绘制文字 (统一加粗) ---
# 准备数据
# 准备数据 (名称、规格等)
name = str(data.get('material_name', '') or '-')
if len(name) > 8: name = name[:7] + ".."
@ -123,15 +128,43 @@ class LabelPrintService:
attr = f"{cat}/{typ}"
if len(attr) > 11: attr = attr[:10] + ".."
# 底部显示的文字SKU 编号
sku_str = f"NO.{sku_code}"
# -----------------------------------------------------------
# [修改点] 底部显示的文字:不再显示 NO.SKU而是显示 SN 或 BN
# -----------------------------------------------------------
bottom_text = ""
# 1. 优先检查前端是否传了明确的 print_no (打印值) 和 print_label (序/批)
if data.get('print_no'):
val = str(data.get('print_no'))
label_type = data.get('print_label', '')
if label_type == '':
bottom_text = f"SN: {val}"
elif label_type == '':
bottom_text = f"BN: {val}"
else:
bottom_text = f"NO: {val}"
# 2. 如果没有前端打印参数,检查数据中的序列号
elif data.get('serial_number'):
bottom_text = f"SN: {data.get('serial_number')}"
# 3. 检查数据中的批号
elif data.get('batch_number'):
bottom_text = f"BN: {data.get('batch_number')}"
# 4. 兜底 (如果什么都没有,才显示 SKU)
else:
bottom_text = f"NO: {sku_code}"
# -----------------------------------------------------------
lines = [
f"名: {name}",
f"规: {spec}",
f"库: {loc}",
f"属: {attr}",
f"{sku_str}"
f"{bottom_text}" # 这里放置计算好的 SN/BN 文字
]
line_height = 36 # 行高 (字号30 + 6间距)

View File

@ -11,7 +11,7 @@
@keyup.enter="fetchData"
>
<template #append>
<el-button :icon="Search" @click="fetchData" />
<el-button :icon="Search" @click="fetchData"/>
</template>
</el-input>
</div>
@ -27,11 +27,15 @@
<el-checkbox-group v-model="visibleColumnProps" class="column-selector">
<div class="col-group-title">基础信息</div>
<el-row :gutter="10">
<el-col :span="12" v-for="c in baseColumns" :key="c.prop"><el-checkbox :label="c.prop">{{ c.label }}</el-checkbox></el-col>
<el-col :span="12" v-for="c in baseColumns" :key="c.prop">
<el-checkbox :label="c.prop">{{ c.label }}</el-checkbox>
</el-col>
</el-row>
<div class="col-group-title" style="margin-top:10px">库存与商务</div>
<el-row :gutter="10">
<el-col :span="12" v-for="c in stockColumns" :key="c.prop"><el-checkbox :label="c.prop">{{ c.label }}</el-checkbox></el-col>
<el-col :span="12" v-for="c in stockColumns" :key="c.prop">
<el-checkbox :label="c.prop">{{ c.label }}</el-checkbox>
</el-col>
</el-row>
</el-checkbox-group>
</el-popover>
@ -80,8 +84,12 @@
</template>
<template #default="scope" v-else-if="col.prop.includes('link')">
<el-link v-if="scope.row[col.prop]" type="primary" :href="scope.row[col.prop]" target="_blank" :underline="false">
<el-icon><Link /></el-icon> 查看
<el-link v-if="scope.row[col.prop]" type="primary" :href="scope.row[col.prop]" target="_blank"
:underline="false">
<el-icon>
<Link/>
</el-icon>
查看
</el-link>
</template>
@ -94,7 +102,10 @@
<el-table-column label="操作" width="220" fixed="right" align="center">
<template #default="{ row }">
<el-button link type="warning" size="default" @click="handlePrint(row)">
<el-icon><Printer /></el-icon> 打印
<el-icon>
<Printer/>
</el-icon>
打印
</el-button>
<el-button link type="primary" size="default" @click="handleUpdate(row)">编辑</el-button>
<el-popconfirm title="确定删除该条记录吗?不可恢复。" @confirm="handleDelete(row)" width="220">
@ -132,7 +143,9 @@
<div class="form-card basic-card">
<div class="card-title">
<el-icon class="icon"><Box /></el-icon>
<el-icon class="icon">
<Box/>
</el-icon>
<span>1. 基础信息</span>
<span class="sub-title" v-if="dialogStatus === 'create'"> (请先搜索锁定物料)</span>
</div>
@ -172,18 +185,38 @@
</el-col>
<el-col :span="14" style="display: flex; align-items: center;">
<span class="search-tip">
<el-icon><InfoFilled /></el-icon> 未输入时展示最新物料输入关键词进行精确搜索
<el-icon><InfoFilled/></el-icon> 未输入时展示最新物料输入关键词进行精确搜索
</span>
</el-col>
</el-row>
<div class="read-only-grid">
<el-row :gutter="20">
<el-col :span="8"><el-form-item label="名称"><el-input v-model="form.material_name" disabled class="is-text-view" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="规格型号"><el-input v-model="form.spec_model" disabled class="is-text-view" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="单位"><el-input v-model="form.unit" disabled class="is-text-view" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="类别"><el-input v-model="form.category" disabled class="is-text-view" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="类型"><el-input v-model="form.material_type" disabled class="is-text-view" /></el-form-item></el-col>
<el-col :span="8">
<el-form-item label="名称">
<el-input v-model="form.material_name" disabled class="is-text-view"/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="规格型号">
<el-input v-model="form.spec_model" disabled class="is-text-view"/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="单位">
<el-input v-model="form.unit" disabled class="is-text-view"/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="类别">
<el-input v-model="form.category" disabled class="is-text-view"/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="类型">
<el-input v-model="form.material_type" disabled class="is-text-view"/>
</el-form-item>
</el-col>
</el-row>
</div>
</div>
@ -191,7 +224,9 @@
<div class="form-card inbound-card">
<div class="card-title">
<el-icon class="icon"><House /></el-icon>
<el-icon class="icon">
<House/>
</el-icon>
<span>2. 入库详情</span>
</div>
@ -201,28 +236,38 @@
<el-form-item label="编码/SKU" prop="sku">
<el-input
v-model="form.sku"
placeholder="系统自动生成 (0000000X)"
placeholder="系统自动生成 (000000000X)"
disabled
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="入库日期" prop="in_date">
<el-date-picker v-model="form.in_date" type="date" value-format="YYYY-MM-DD" style="width:100%" disabled />
<el-date-picker v-model="form.in_date" type="date" value-format="YYYY-MM-DD" style="width:100%"
disabled/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="条码" prop="barcode">
<el-input v-model="form.barcode" placeholder="扫描条码"/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="库位" prop="warehouse_location">
<el-input v-model="form.warehouse_location" placeholder="例如: A-01-02"/>
</el-form-item>
</el-col>
<el-col :span="6"><el-form-item label="条码" prop="barcode"><el-input v-model="form.barcode" placeholder="扫描条码" /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="库位" prop="warehouse_location"><el-input v-model="form.warehouse_location" placeholder="例如: A-01-02" /></el-form-item></el-col>
</el-row>
<div class="identity-panel">
<el-row>
<el-col :span="24" style="margin-bottom: 8px;">
<el-radio-group v-model="entryMode" @change="handleEntryModeChange" :disabled="modeLocked" size="small" class="custom-radio-group">
<el-radio-group v-model="entryMode" @change="handleEntryModeChange" :disabled="modeLocked"
size="small" class="custom-radio-group">
<el-radio-button label="batch">按批号入库 (Batch)</el-radio-button>
<el-radio-button label="serial">按序列号入库 (SN)</el-radio-button>
</el-radio-group>
<span v-if="modeLocked" class="locked-msg"><el-icon><Lock /></el-icon> 历史锁定</span>
<span v-if="modeLocked" class="locked-msg"><el-icon><Lock/></el-icon> 历史锁定</span>
</el-col>
</el-row>
@ -257,27 +302,28 @@
<el-row :gutter="20" style="margin-top: 10px;">
<el-col :span="6">
<el-form-item label="入库数量" prop="in_quantity">
<el-input-number v-model="form.in_quantity" :min="1" controls-position="right" style="width:100%" class="strong-input" />
<el-input-number v-model="form.in_quantity" :min="1" controls-position="right" style="width:100%"
class="strong-input"/>
</el-form-item>
</el-col>
<template v-if="dialogStatus === 'update'">
<el-col :span="6">
<el-form-item label="当前库存" prop="stock_quantity">
<el-input-number v-model="form.stock_quantity" disabled style="width:100%" :controls="false" />
<el-input-number v-model="form.stock_quantity" disabled style="width:100%" :controls="false"/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="当前可用" prop="available_quantity">
<el-input-number v-model="form.available_quantity" disabled style="width:100%" :controls="false" />
<el-input-number v-model="form.available_quantity" disabled style="width:100%" :controls="false"/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="库存状态" prop="status">
<el-select v-model="form.status" style="width:100%">
<el-option label="在库" value="在库" />
<el-option label="出库" value="出库" />
<el-option label="损耗" value="损耗" />
<el-option label="在库" value="在库"/>
<el-option label="出库" value="出库"/>
<el-option label="损耗" value="损耗"/>
</el-select>
</el-form-item>
</el-col>
@ -292,7 +338,11 @@
</el-select>
</el-form-item>
</el-col>
<el-col :span="12"><el-form-item label="到货图片" prop="arrival_photo"><el-input v-model="form.arrival_photo" placeholder="输入图片 URL" /></el-form-item></el-col>
<el-col :span="12">
<el-form-item label="到货图片" prop="arrival_photo">
<el-input v-model="form.arrival_photo" placeholder="输入图片 URL"/>
</el-form-item>
</el-col>
</el-row>
<div class="divider-text">商务与采购信息</div>
@ -314,15 +364,22 @@
</el-autocomplete>
</el-form-item>
</el-col>
<el-col :span="6"><el-form-item label="汇率"><el-input-number v-model="form.exchange_rate" :precision="2" controls-position="right" style="width:100%"/></el-form-item></el-col>
<el-col :span="6">
<el-form-item label="汇率">
<el-input-number v-model="form.exchange_rate" :precision="2" controls-position="right"
style="width:100%"/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="含税单价" prop="unit_price">
<el-input-number v-model="form.unit_price" :precision="4" controls-position="right" style="width:100%" />
<el-input-number v-model="form.unit_price" :precision="4" controls-position="right"
style="width:100%"/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="总价">
<el-input-number v-model="form.total_price" :precision="2" disabled :controls="false" style="width:100%" class="total-price-input"/>
<el-input-number v-model="form.total_price" :precision="2" disabled :controls="false"
style="width:100%" class="total-price-input"/>
</el-form-item>
</el-col>
</el-row>
@ -370,8 +427,16 @@
</el-row>
<el-row :gutter="20">
<el-col :span="12"><el-form-item label="原始链接"><el-input v-model="form.source_link" placeholder="http://" /></el-form-item></el-col>
<el-col :span="12"><el-form-item label="详情链接"><el-input v-model="form.detail_link" placeholder="http://" /></el-form-item></el-col>
<el-col :span="12">
<el-form-item label="原始链接">
<el-input v-model="form.source_link" placeholder="http://"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="详情链接">
<el-input v-model="form.detail_link" placeholder="http://"/>
</el-form-item>
</el-col>
</el-row>
</div>
</div>
@ -398,7 +463,7 @@
>
<div style="text-align: center;">
<div v-loading="printLoading" class="preview-box">
<img v-if="previewUrl" :src="previewUrl" alt="Label Preview" style="width: 100%; border: 1px solid #ccc;" />
<img v-if="previewUrl" :src="previewUrl" alt="Label Preview" style="width: 100%; border: 1px solid #ccc;"/>
<div v-else class="empty-preview">正在生成预览...</div>
</div>
@ -411,7 +476,10 @@
<div class="dialog-footer">
<el-button @click="printVisible = false">取消</el-button>
<el-button type="primary" :loading="printing" @click="confirmPrint">
<el-icon><Printer /></el-icon> 确认打印
<el-icon>
<Printer/>
</el-icon>
确认打印
</el-button>
</div>
</template>
@ -421,9 +489,9 @@
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, watch } from 'vue'
import { Plus, Setting, Refresh, Search, Lock, Box, House, InfoFilled, Link, Printer } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import {ref, reactive, onMounted, watch} from 'vue'
import {Plus, Setting, Refresh, Search, Lock, Box, House, InfoFilled, Link, Printer} from '@element-plus/icons-vue'
import {ElMessage} from 'element-plus'
import dayjs from 'dayjs'
import {
getBuyList,
@ -432,7 +500,7 @@ import {
deleteBuyInbound,
searchMaterialBase
} from '@/api/inbound/buy'
import { getLabelPreview, executePrint } from '@/api/common/print'
import {getLabelPreview, executePrint} from '@/api/common/print'
// ------------------------------------
// 状态与变量
@ -445,7 +513,7 @@ const dialogStatus = ref<'create' | 'update'>('create')
const tableData = ref([])
const total = ref(0)
const formRef = ref()
const queryParams = reactive({ page: 1, pageSize: 15, keyword: '' })
const queryParams = reactive({page: 1, pageSize: 15, keyword: ''})
const materialOptions = ref<any[]>([])
// 打印相关变量
@ -460,37 +528,37 @@ const modeLocked = ref(false)
// 列定义
const baseColumns = [
{ prop: 'material_name', label: '名称' },
{ prop: 'category', label: '类别' },
{ prop: 'material_type', label: '类型' },
{ prop: 'spec_model', label: '规格型号' },
{ prop: 'unit', label: '单位' },
{prop: 'material_name', label: '名称'},
{prop: 'category', label: '类别'},
{prop: 'material_type', label: '类型'},
{prop: 'spec_model', label: '规格型号'},
{prop: 'unit', label: '单位'},
]
const stockColumns = [
{ prop: 'id', label: 'ID', minWidth: '60' },
{ prop: 'base_id', label: 'BaseID', minWidth: '80' },
{ prop: 'sku', label: 'SKU', minWidth: '120' },
{ prop: 'inbound_date', label: '入库日期', minWidth: '120' },
{ prop: 'barcode', label: '条码', minWidth: '120' },
{ prop: 'serial_number', label: '序列号', minWidth: '150' },
{ prop: 'batch_number', label: '批号', minWidth: '150' },
{ prop: 'status', label: '状态', minWidth: '100' },
{ prop: 'inspection_status', label: '到检', minWidth: '100' },
{ prop: 'qty_inbound', label: '入库量', minWidth: '100' },
{ prop: 'qty_stock', label: '库存数', minWidth: '100' },
{ prop: 'qty_available', label: '可用数', minWidth: '100' },
{ prop: 'warehouse_loc', label: '库位', minWidth: '120' },
{ prop: 'unit_price', label: '单价', minWidth: '120' },
{ prop: 'total_price', label: '总价', minWidth: '120' },
{ prop: 'currency', label: '币种', minWidth: '80' },
{ prop: 'exchange_rate', label: '汇率', minWidth: '80' },
{ prop: 'supplier_name', label: '供应商', minWidth: '150' },
{ prop: 'purchaser', label: '采购人', minWidth: '100' },
{ prop: 'purchaser_email', label: '邮箱', minWidth: '150' },
{ prop: 'source_link', label: '采购链接', minWidth: '100' },
{ prop: 'detail_link', label: '详情链接', minWidth: '100' },
{ prop: 'arrival_photo', label: '到货图', minWidth: '100' }
{prop: 'id', label: 'ID', minWidth: '60'},
{prop: 'base_id', label: 'BaseID', minWidth: '80'},
{prop: 'sku', label: 'SKU', minWidth: '120'},
{prop: 'inbound_date', label: '入库日期', minWidth: '120'},
{prop: 'barcode', label: '条码', minWidth: '120'},
{prop: 'serial_number', label: '序列号', minWidth: '150'},
{prop: 'batch_number', label: '批号', minWidth: '150'},
{prop: 'status', label: '状态', minWidth: '100'},
{prop: 'inspection_status', label: '到检', minWidth: '100'},
{prop: 'qty_inbound', label: '入库量', minWidth: '100'},
{prop: 'qty_stock', label: '库存数', minWidth: '100'},
{prop: 'qty_available', label: '可用数', minWidth: '100'},
{prop: 'warehouse_loc', label: '库位', minWidth: '120'},
{prop: 'unit_price', label: '单价', minWidth: '120'},
{prop: 'total_price', label: '总价', minWidth: '120'},
{prop: 'currency', label: '币种', minWidth: '80'},
{prop: 'exchange_rate', label: '汇率', minWidth: '80'},
{prop: 'supplier_name', label: '供应商', minWidth: '150'},
{prop: 'purchaser', label: '采购人', minWidth: '100'},
{prop: 'purchaser_email', label: '邮箱', minWidth: '150'},
{prop: 'source_link', label: '采购链接', minWidth: '100'},
{prop: 'detail_link', label: '详情链接', minWidth: '100'},
{prop: 'arrival_photo', label: '到货图', minWidth: '100'}
]
const allColumns = [...baseColumns, ...stockColumns]
@ -516,7 +584,7 @@ const visibleColumnProps = ref(getSavedColumns())
watch(visibleColumnProps, (newVal) => {
localStorage.setItem(STORAGE_KEY_COLS, JSON.stringify(newVal))
}, { deep: true })
}, {deep: true})
const form = reactive({
@ -554,7 +622,9 @@ const saveToHistory = (key: string, value: string) => {
list.unshift(value)
if (list.length > 20) list = list.slice(0, 20) // 最多存20条
localStorage.setItem(key, JSON.stringify(list))
} catch (e) { console.error('save history failed', e) }
} catch (e) {
console.error('save history failed', e)
}
}
// 获取历史 (String 类型)
@ -562,8 +632,10 @@ const getHistoryList = (key: string): any[] => {
try {
const existing = localStorage.getItem(key)
const list = existing ? JSON.parse(existing) : []
return list.map((v: string) => ({ value: v }))
} catch (e) { return [] }
return list.map((v: string) => ({value: v}))
} catch (e) {
return []
}
}
// 保存物料历史 (Object 类型)
@ -574,17 +646,20 @@ const saveMaterialHistory = (item: any) => {
const existing = localStorage.getItem(key)
let list = existing ? JSON.parse(existing) : []
list = list.filter((i: any) => i.id !== item.id)
list.unshift({ ...item, isHistory: true })
list.unshift({...item, isHistory: true})
if (list.length > 10) list = list.slice(0, 10)
localStorage.setItem(key, JSON.stringify(list))
} catch (e) {}
} catch (e) {
}
}
const getMaterialHistory = () => {
try {
const existing = localStorage.getItem(HISTORY_KEYS.MATERIAL)
return existing ? JSON.parse(existing) : []
} catch (e) { return [] }
} catch (e) {
return []
}
}
@ -600,7 +675,7 @@ const createFilter = (queryString: string) => {
// 辅助函数:从当前表格提取
const getTableDataUnique = (field: string) => {
const uniqueItems = Array.from(new Set(tableData.value.map((i: any) => i[field]).filter(Boolean)))
return uniqueItems.map(i => ({ value: i }))
return uniqueItems.map(i => ({value: i}))
}
// 通用查询: 历史记录 + 当前页面数据
@ -632,9 +707,9 @@ const handleEmailSelect = (item: any) => saveToHistory(HISTORY_KEYS.EMAIL, item.
// 4. 币种 (固定+过滤)
const currencyOptions = [
{ value: 'CNY', desc: '人民币' },
{ value: 'USD', desc: '美元' },
{ value: 'EUR', desc: '欧元' }
{value: 'CNY', desc: '人民币'},
{value: 'USD', desc: '美元'},
{value: 'EUR', desc: '欧元'}
]
const querySearchCurrency = (queryString: string, cb: any) => {
const results = queryString ? currencyOptions.filter(createFilter(queryString)) : currencyOptions
@ -657,7 +732,7 @@ const handleSearchMaterial = async (query: string) => {
searchLoading.value = true
try {
const res: any = await searchMaterialBase(query)
const apiResults = (res.data || []).map((i: any) => ({ ...i, isHistory: false }))
const apiResults = (res.data || []).map((i: any) => ({...i, isHistory: false}))
if (!query) {
const history = getMaterialHistory()
@ -715,10 +790,10 @@ const validateIdentity = (rule: any, value: any, callback: any) => {
}
const rules = {
base_id: [{ required: true, message: '请选择物料', trigger: 'change' }],
in_quantity: [{ required: true, message: '请输入数量', trigger: 'blur' }],
serial_number: [{ validator: validateIdentity, trigger: 'blur' }, { validator: validateUnique, trigger: 'blur' }],
batch_number: [{ validator: validateIdentity, trigger: 'blur' }, { validator: validateUnique, trigger: 'blur' }]
base_id: [{required: true, message: '请选择物料', trigger: 'change'}],
in_quantity: [{required: true, message: '请输入数量', trigger: 'blur'}],
serial_number: [{validator: validateIdentity, trigger: 'blur'}, {validator: validateUnique, trigger: 'blur'}],
batch_number: [{validator: validateIdentity, trigger: 'blur'}, {validator: validateUnique, trigger: 'blur'}]
}
// ------------------------------------
@ -726,7 +801,7 @@ const rules = {
// ------------------------------------
const checkHistoryAndSetMode = async (baseId: number) => {
try {
const res: any = await getBuyList({ page: 1, pageSize: 1000 })
const res: any = await getBuyList({page: 1, pageSize: 1000})
const allItems = res.data.items || []
const historyItems = allItems.filter((item: any) => item.base_id === baseId)
@ -749,7 +824,7 @@ const checkHistoryAndSetMode = async (baseId: number) => {
form.batch_number = '000001'
}
if(formRef.value) {
if (formRef.value) {
formRef.value.clearValidate('serial_number')
formRef.value.clearValidate('batch_number')
}
@ -771,10 +846,10 @@ const handleEntryModeChange = (val: string) => {
if (val === 'batch') {
form.serial_number = ''
form.batch_number = '000001'
if(formRef.value) formRef.value.clearValidate('serial_number')
if (formRef.value) formRef.value.clearValidate('serial_number')
} else {
form.batch_number = ''
if(formRef.value) formRef.value.clearValidate('batch_number')
if (formRef.value) formRef.value.clearValidate('batch_number')
}
}
@ -788,7 +863,9 @@ const fetchData = async () => {
const res: any = await getBuyList(queryParams)
tableData.value = res.data.items || []
total.value = res.data.total || 0
} finally { loading.value = false }
} finally {
loading.value = false
}
}
const handleCreate = () => {
@ -943,7 +1020,7 @@ const handlePrint = async (row: any) => {
try {
const res: any = await getLabelPreview(printData)
previewUrl.value = res.data
} catch(e) {
} catch (e) {
ElMessage.error('预览生成失败')
} finally {
printLoading.value = false
@ -956,7 +1033,7 @@ const confirmPrint = async () => {
await executePrint(currentPrintData.value)
ElMessage.success('指令已发送')
printVisible.value = false
} catch(e: any) {
} catch (e: any) {
ElMessage.error(e.msg || '打印失败')
} finally {
printing.value = false
@ -980,7 +1057,7 @@ const resetForm = () => {
}
const getStatusType = (status: string) => {
const map: any = { '在库': 'success', '出库': 'info', '损耗': 'danger' }
const map: any = {'在库': 'success', '出库': 'info', '损耗': 'danger'}
return map[status] || 'warning'
}
@ -1009,30 +1086,70 @@ onMounted(() => fetchData())
background: #fff;
padding: 15px 20px;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.05);
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
.left-tools {
flex: 0 0 350px;
}
.right-tools {
display: flex;
gap: 10px;
align-items: center;
}
.action-btn {
font-weight: 500;
}
.left-tools { flex: 0 0 350px; }
.right-tools { display: flex; gap: 10px; align-items: center; }
.action-btn { font-weight: 500; }
/* 表格美化 */
.modern-table {
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.05);
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
:deep(.table-header-gray th) {
background-color: #f8f9fb !important;
color: #606266;
font-weight: 600;
height: 50px;
}
.tag-sn { color: #409EFF; font-weight: bold; font-family: monospace; }
.tag-bn { color: #67C23A; font-weight: bold; font-family: monospace; }
.money-text { font-family: 'Consolas', monospace; color: #303133; }
.stock-num { font-weight: bold; color: #333; font-size: 15px; }
.avail-num { font-weight: bold; color: #67C23A; font-size: 15px; }
.sum-tag { margin-left: 4px; transform: scale(0.9); }
.tag-sn {
color: #409EFF;
font-weight: bold;
font-family: monospace;
}
.tag-bn {
color: #67C23A;
font-weight: bold;
font-family: monospace;
}
.money-text {
font-family: 'Consolas', monospace;
color: #303133;
}
.stock-num {
font-weight: bold;
color: #333;
font-size: 15px;
}
.avail-num {
font-weight: bold;
color: #67C23A;
font-size: 15px;
}
.sum-tag {
margin-left: 4px;
transform: scale(0.9);
}
/* 弹窗核心样式调整 */
:deep(.el-dialog__body) {
@ -1066,14 +1183,38 @@ onMounted(() => fetchData())
display: flex;
align-items: center;
}
.card-title .icon { margin-right: 8px; font-size: 18px; color: #409EFF; }
.card-title .sub-title { font-size: 12px; color: #909399; font-weight: normal; margin-left: 10px; }
.card-content { padding: 15px 20px; }
.card-title .icon {
margin-right: 8px;
font-size: 18px;
color: #409EFF;
}
.card-title .sub-title {
font-size: 12px;
color: #909399;
font-weight: normal;
margin-left: 10px;
}
.card-content {
padding: 15px 20px;
}
/* 基础信息卡片 */
.basic-card { border-left: 4px solid #409EFF; }
.search-tip { color: #909399; font-size: 12px; margin-left: 10px; display: flex; align-items: center; gap: 4px; }
.basic-card {
border-left: 4px solid #409EFF;
}
.search-tip {
color: #909399;
font-size: 12px;
margin-left: 10px;
display: flex;
align-items: center;
gap: 4px;
}
.is-text-view :deep(.el-input__wrapper) {
box-shadow: none !important;
background-color: #f5f7fa;
@ -1081,10 +1222,17 @@ onMounted(() => fetchData())
border-radius: 0;
padding-left: 0;
}
.is-text-view :deep(.el-input__inner) { color: #606266; font-weight: 500; font-size: 13px; }
.is-text-view :deep(.el-input__inner) {
color: #606266;
font-weight: 500;
font-size: 13px;
}
/* 入库信息卡片 */
.inbound-card { border-left: 4px solid #67C23A; }
.inbound-card {
border-left: 4px solid #67C23A;
}
/* 身份区域 (SN/BN) */
.identity-panel {
@ -1094,11 +1242,33 @@ onMounted(() => fetchData())
padding: 12px;
margin-bottom: 15px;
}
.custom-radio-group { margin-bottom: 10px; }
.locked-msg { font-size: 12px; color: #e6a23c; margin-left: 15px; }
.prefix-tag { font-weight: bold; font-size: 12px; padding: 0 5px; border-radius: 4px; }
.prefix-tag.bn { color: #67C23A; background: #f0f9eb; }
.prefix-tag.sn { color: #409EFF; background: #ecf5ff; }
.custom-radio-group {
margin-bottom: 10px;
}
.locked-msg {
font-size: 12px;
color: #e6a23c;
margin-left: 15px;
}
.prefix-tag {
font-weight: bold;
font-size: 12px;
padding: 0 5px;
border-radius: 4px;
}
.prefix-tag.bn {
color: #67C23A;
background: #f0f9eb;
}
.prefix-tag.sn {
color: #409EFF;
background: #ecf5ff;
}
/* 分割线 */
.divider-text {
@ -1110,13 +1280,20 @@ onMounted(() => fetchData())
font-size: 13px;
font-weight: 500;
}
.divider-text::before, .divider-text::after {
content: '';
flex: 1;
border-bottom: 1px solid #ebeef5;
}
.divider-text::before { margin-right: 15px; }
.divider-text::after { margin-left: 15px; }
.divider-text::before {
margin-right: 15px;
}
.divider-text::after {
margin-left: 15px;
}
/* 底部按钮 */
.dialog-footer {
@ -1128,10 +1305,27 @@ onMounted(() => fetchData())
border-top: 1px solid #ebeef5;
}
.option-item { display: flex; justify-content: space-between; width: 100%; align-items: center;}
.opt-name { font-weight: bold; }
.opt-spec { color: #8492a6; font-size: 13px; margin-right: 10px; }
.total-price-input :deep(.el-input__inner) { color: #F56C6C; font-weight: bold; }
.option-item {
display: flex;
justify-content: space-between;
width: 100%;
align-items: center;
}
.opt-name {
font-weight: bold;
}
.opt-spec {
color: #8492a6;
font-size: 13px;
margin-right: 10px;
}
.total-price-input :deep(.el-input__inner) {
color: #F56C6C;
font-weight: bold;
}
.preview-box {
min-height: 150px;
@ -1141,5 +1335,8 @@ onMounted(() => fetchData())
background: #f5f7fa;
border-radius: 4px;
}
.empty-preview { color: #909399; }
.empty-preview {
color: #909399;
}
</style>