Compare commits
2 Commits
e31ef59df0
...
cd55a6aee1
| Author | SHA1 | Date | |
|---|---|---|---|
| cd55a6aee1 | |||
| 6f4917f57e |
@ -1,23 +1,17 @@
|
||||
# app/services/inbound/buy_service.py
|
||||
from app.extensions import db
|
||||
from app.models.material import MaterialBase
|
||||
from app.models.stock import StockBuy
|
||||
from datetime import datetime
|
||||
from sqlalchemy import or_
|
||||
from sqlalchemy import or_, func # 引入 func 用于聚合计算
|
||||
import traceback
|
||||
|
||||
|
||||
class BuyInboundService:
|
||||
@staticmethod
|
||||
def search_base_material(keyword):
|
||||
"""
|
||||
搜索基础物料库
|
||||
"""
|
||||
try:
|
||||
if not keyword:
|
||||
return []
|
||||
|
||||
# 过滤条件:名称或规格包含关键词,且 is_enabled 为 True
|
||||
query = MaterialBase.query.filter(
|
||||
MaterialBase.is_enabled == True,
|
||||
or_(
|
||||
@ -44,11 +38,7 @@ class BuyInboundService:
|
||||
|
||||
@staticmethod
|
||||
def handle_inbound(data):
|
||||
"""
|
||||
新增入库逻辑
|
||||
"""
|
||||
try:
|
||||
# 1. 核心校验
|
||||
base_id = data.get('base_id')
|
||||
if not base_id:
|
||||
raise ValueError("必须选择基础物料进行入库 (缺少 base_id)")
|
||||
@ -57,24 +47,19 @@ class BuyInboundService:
|
||||
if not material:
|
||||
raise ValueError(f"ID为 {base_id} 的基础物料不存在")
|
||||
|
||||
# 2. 处理日期 (防止空字符串报错)
|
||||
in_date_val = datetime.utcnow().date()
|
||||
if data.get('in_date'):
|
||||
try:
|
||||
date_str = str(data['in_date'])
|
||||
# 截取前10位 YYYY-MM-DD
|
||||
if len(date_str) > 10:
|
||||
in_date_val = datetime.strptime(date_str[:10], '%Y-%m-%d').date()
|
||||
if len(str(data['in_date'])) > 10:
|
||||
in_date_val = datetime.strptime(str(data['in_date'])[:10], '%Y-%m-%d').date()
|
||||
else:
|
||||
in_date_val = datetime.strptime(date_str, '%Y-%m-%d').date()
|
||||
in_date_val = datetime.strptime(str(data['in_date']), '%Y-%m-%d').date()
|
||||
except ValueError:
|
||||
pass # 格式错误则使用当前日期
|
||||
pass
|
||||
|
||||
# 3. 数据转换
|
||||
in_qty = float(data.get('in_quantity') or 0)
|
||||
u_price = float(data.get('unit_price') or 0)
|
||||
|
||||
# 4. 创建 StockBuy
|
||||
new_stock = StockBuy(
|
||||
base_id=material.id,
|
||||
sku=data.get('sku'),
|
||||
@ -82,27 +67,20 @@ class BuyInboundService:
|
||||
serial_number=data.get('serial_number'),
|
||||
batch_number=data.get('batch_number'),
|
||||
barcode=data.get('barcode'),
|
||||
|
||||
# --- 状态与数量强制逻辑 ---
|
||||
status='在库',
|
||||
in_quantity=in_qty,
|
||||
stock_quantity=in_qty, # 初始库存 = 入库量
|
||||
available_quantity=in_qty, # 初始可用 = 入库量
|
||||
stock_quantity=in_qty,
|
||||
available_quantity=in_qty,
|
||||
inspection_status=data.get('inspection_status', '未检'),
|
||||
# ----------------
|
||||
|
||||
warehouse_location=data.get('warehouse_location'),
|
||||
unit_price=u_price,
|
||||
total_price=in_qty * u_price,
|
||||
currency=data.get('currency', 'CNY'),
|
||||
exchange_rate=data.get('exchange_rate', 1.0),
|
||||
supplier_name=data.get('supplier_name'),
|
||||
|
||||
# [字段映射] 前端 -> DB
|
||||
buyer_name=data.get('purchaser'),
|
||||
buyer_email=data.get('purchaser_email'),
|
||||
original_link=data.get('source_link'),
|
||||
|
||||
detail_link=data.get('detail_link'),
|
||||
arrival_photo=data.get('arrival_photo'),
|
||||
remark=data.get('remark')
|
||||
@ -114,23 +92,18 @@ class BuyInboundService:
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"Insert Error: {str(e)}")
|
||||
raise e
|
||||
|
||||
@staticmethod
|
||||
def update_inbound(stock_id, data):
|
||||
"""
|
||||
更新入库逻辑
|
||||
"""
|
||||
try:
|
||||
print(f"----- UPDATE DEBUG: ID={stock_id} -----")
|
||||
print(f"Payload: {data}")
|
||||
|
||||
stock = StockBuy.query.get(stock_id)
|
||||
if not stock:
|
||||
raise ValueError("记录不存在")
|
||||
|
||||
# 1. 字段映射字典:前端Key -> Model属性名
|
||||
# 使用映射可以避免写大量 if...else,且逻辑更清晰
|
||||
field_mapping = {
|
||||
'sku': 'sku',
|
||||
'barcode': 'barcode',
|
||||
@ -145,50 +118,37 @@ class BuyInboundService:
|
||||
'remark': 'remark',
|
||||
'currency': 'currency',
|
||||
'exchange_rate': 'exchange_rate',
|
||||
|
||||
# 关键映射
|
||||
'purchaser': 'buyer_name',
|
||||
'purchaser_email': 'buyer_email',
|
||||
'source_link': 'original_link'
|
||||
}
|
||||
|
||||
# 遍历更新 (排除日期、数量、单价,下面单独处理)
|
||||
for frontend_key, db_attr in field_mapping.items():
|
||||
if frontend_key in data:
|
||||
setattr(stock, db_attr, data[frontend_key])
|
||||
|
||||
# 2. 核心数值逻辑 (数量 & 单价 & 总价)
|
||||
qty_changed = False
|
||||
price_changed = False
|
||||
|
||||
# (A) 处理入库数量变更
|
||||
if 'in_quantity' in data:
|
||||
new_qty = float(data['in_quantity'])
|
||||
old_qty = float(stock.in_quantity)
|
||||
|
||||
if new_qty != old_qty:
|
||||
print(f"Quantity Changed: {old_qty} -> {new_qty}")
|
||||
diff = new_qty - old_qty
|
||||
stock.in_quantity = new_qty
|
||||
# 联动更新库存和可用量
|
||||
stock.stock_quantity = float(stock.stock_quantity) + diff
|
||||
stock.available_quantity = float(stock.available_quantity) + diff
|
||||
qty_changed = True
|
||||
|
||||
# (B) 处理单价变更
|
||||
if 'unit_price' in data:
|
||||
new_price = float(data['unit_price'])
|
||||
old_price = float(stock.unit_price)
|
||||
|
||||
if new_price != old_price:
|
||||
print(f"Price Changed: {old_price} -> {new_price}")
|
||||
stock.unit_price = new_price
|
||||
price_changed = True
|
||||
|
||||
# (C) 强制重算总价
|
||||
if qty_changed or price_changed:
|
||||
stock.total_price = float(stock.in_quantity) * float(stock.unit_price)
|
||||
print(f"New Total Price: {stock.total_price}")
|
||||
|
||||
db.session.commit()
|
||||
print("----- UPDATE SUCCESS -----")
|
||||
@ -206,7 +166,6 @@ class BuyInboundService:
|
||||
stock = StockBuy.query.get(stock_id)
|
||||
if not stock:
|
||||
raise ValueError("记录不存在")
|
||||
|
||||
db.session.delete(stock)
|
||||
db.session.commit()
|
||||
return True
|
||||
@ -216,14 +175,10 @@ class BuyInboundService:
|
||||
|
||||
@staticmethod
|
||||
def get_list(page, limit, keyword=None):
|
||||
"""
|
||||
列表查询
|
||||
"""
|
||||
try:
|
||||
# 使用 Outer Join 确保即使 MaterialBase 物理删除,库存记录也不会报错或消失
|
||||
# 1. 查询分页数据
|
||||
query = db.session.query(StockBuy).outerjoin(MaterialBase, StockBuy.base_id == MaterialBase.id)
|
||||
|
||||
# 搜索逻辑
|
||||
if keyword:
|
||||
query = query.filter(
|
||||
or_(
|
||||
@ -237,16 +192,41 @@ class BuyInboundService:
|
||||
|
||||
pagination = query.order_by(StockBuy.id.desc()).paginate(page=page, per_page=limit, error_out=False)
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# 新增逻辑:计算总库存
|
||||
# ---------------------------------------------------------------------
|
||||
# 2. 提取当前页所有涉及的 base_id
|
||||
current_items = pagination.items
|
||||
base_ids = list(set([item.base_id for item in current_items if item.base_id]))
|
||||
|
||||
# 3. 聚合查询:一次性查出这些 base_id 对应的 stock_quantity 和 available_quantity 总和
|
||||
stock_map = {}
|
||||
if base_ids:
|
||||
# SELECT base_id, SUM(stock_quantity), SUM(available_quantity) FROM stock_buy WHERE base_id IN (...) GROUP BY base_id
|
||||
aggregates = db.session.query(
|
||||
StockBuy.base_id,
|
||||
func.sum(StockBuy.stock_quantity).label('total_stock'),
|
||||
func.sum(StockBuy.available_quantity).label('total_avail')
|
||||
).filter(StockBuy.base_id.in_(base_ids)).group_by(StockBuy.base_id).all()
|
||||
|
||||
for agg in aggregates:
|
||||
stock_map[agg.base_id] = {
|
||||
'total_stock': float(agg.total_stock or 0),
|
||||
'total_avail': float(agg.total_avail or 0)
|
||||
}
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
items = []
|
||||
for item in pagination.items:
|
||||
# 获取关联的基础信息,如果关联不存在则给默认值
|
||||
for item in current_items:
|
||||
mat_name = item.material.name if item.material else '未知物料'
|
||||
mat_spec = item.material.spec_model if item.material else ''
|
||||
mat_cat = item.material.category if item.material else ''
|
||||
mat_unit = item.material.unit if item.material else ''
|
||||
mat_type = item.material.material_type if item.material else ''
|
||||
|
||||
# 构建返回字典
|
||||
# 获取该物料的统计数据
|
||||
stats = stock_map.get(item.base_id, {'total_stock': 0, 'total_avail': 0})
|
||||
|
||||
d = {
|
||||
'id': item.id,
|
||||
'base_id': item.base_id,
|
||||
@ -264,23 +244,24 @@ class BuyInboundService:
|
||||
'status': item.status,
|
||||
'inspection_status': item.inspection_status,
|
||||
|
||||
# --- 原始批次数据 (用于编辑) ---
|
||||
'qty_inbound': float(item.in_quantity or 0),
|
||||
'qty_stock': float(item.stock_quantity or 0),
|
||||
'qty_available': float(item.available_quantity or 0),
|
||||
|
||||
# --- [新增] 聚合统计数据 (用于列表显示) ---
|
||||
'sum_stock': stats['total_stock'],
|
||||
'sum_available': stats['total_avail'],
|
||||
|
||||
'warehouse_loc': item.warehouse_location,
|
||||
'unit_price': float(item.unit_price or 0),
|
||||
'total_price': float(item.total_price or 0),
|
||||
|
||||
'currency': item.currency,
|
||||
'exchange_rate': float(item.exchange_rate or 1),
|
||||
'supplier_name': item.supplier_name,
|
||||
|
||||
# [关键映射] DB -> Frontend
|
||||
'purchaser': item.buyer_name,
|
||||
'purchaser_email': item.buyer_email,
|
||||
'source_link': item.original_link,
|
||||
|
||||
'detail_link': item.detail_link,
|
||||
'arrival_photo': item.arrival_photo,
|
||||
'remark': item.remark
|
||||
@ -289,7 +270,6 @@ class BuyInboundService:
|
||||
|
||||
return {"total": pagination.total, "items": items}
|
||||
except Exception as e:
|
||||
# 打印错误到 Docker 日志
|
||||
print(f"List Error: {e}")
|
||||
traceback.print_exc()
|
||||
return {"total": 0, "items": []}
|
||||
@ -4,7 +4,7 @@
|
||||
<div class="left-tools">
|
||||
<el-input
|
||||
v-model="queryParams.keyword"
|
||||
placeholder="搜索物料名称/规格/单号..."
|
||||
placeholder="🔍 搜索物料名称 / 规格 / 批号 / SN / 供应商..."
|
||||
class="search-input"
|
||||
clearable
|
||||
@clear="fetchData"
|
||||
@ -17,19 +17,21 @@
|
||||
</div>
|
||||
|
||||
<div class="right-tools">
|
||||
<el-button type="primary" :icon="Plus" @click="handleCreate" class="big-font-btn">采购入库登记</el-button>
|
||||
<el-button :icon="Refresh" @click="fetchData" class="big-font-btn">刷新数据</el-button>
|
||||
<el-button type="primary" :icon="Plus" @click="handleCreate" class="action-btn">采购入库登记</el-button>
|
||||
<el-button :icon="Refresh" @click="fetchData" class="action-btn">刷新</el-button>
|
||||
|
||||
<el-popover placement="bottom-end" title="显示列配置" :width="600" trigger="click">
|
||||
<el-popover placement="bottom-end" title="列配置" :width="500" trigger="click">
|
||||
<template #reference>
|
||||
<el-button :icon="Setting" class="big-font-btn">自定义表头</el-button>
|
||||
<el-button :icon="Setting" class="action-btn">表头</el-button>
|
||||
</template>
|
||||
<el-checkbox-group v-model="visibleColumnProps" class="column-selector">
|
||||
<el-row>
|
||||
<el-col :span="24"><el-divider content-position="left">基础信息 (只读)</el-divider></el-col>
|
||||
<el-col :span="8" v-for="c in baseColumns" :key="c.prop"><el-checkbox :label="c.prop">{{ c.label }}</el-checkbox></el-col>
|
||||
<el-col :span="24"><el-divider content-position="left">库存与商务</el-divider></el-col>
|
||||
<el-col :span="8" v-for="c in stockColumns" :key="c.prop"><el-checkbox :label="c.prop">{{ c.label }}</el-checkbox></el-col>
|
||||
<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-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-row>
|
||||
</el-checkbox-group>
|
||||
</el-popover>
|
||||
@ -42,8 +44,9 @@
|
||||
border
|
||||
stripe
|
||||
style="width: 100%"
|
||||
class="custom-big-table"
|
||||
class="modern-table"
|
||||
highlight-current-row
|
||||
header-cell-class-name="table-header-gray"
|
||||
>
|
||||
<template v-for="col in allColumns" :key="col.prop">
|
||||
<el-table-column
|
||||
@ -54,34 +57,46 @@
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="scope" v-if="['serial_number', 'batch_number'].includes(col.prop)">
|
||||
<span v-if="scope.row[col.prop]" :class="col.prop === 'serial_number' ? 'text-sn' : 'text-bn'">
|
||||
<span v-if="scope.row[col.prop]" :class="col.prop === 'serial_number' ? 'tag-sn' : 'tag-bn'">
|
||||
{{ scope.row[col.prop] }}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
<span v-else class="text-placeholder">-</span>
|
||||
</template>
|
||||
|
||||
<template #default="scope" v-else-if="col.prop === 'qty_stock'">
|
||||
<span class="stock-num">{{ scope.row.sum_stock }}</span>
|
||||
<el-tag size="small" type="info" effect="plain" class="sum-tag">总</el-tag>
|
||||
</template>
|
||||
|
||||
<template #default="scope" v-else-if="col.prop === 'qty_available'">
|
||||
<span class="avail-num">{{ scope.row.sum_available }}</span>
|
||||
<el-tag size="small" type="info" effect="plain" class="sum-tag">总</el-tag>
|
||||
</template>
|
||||
|
||||
<template #default="scope" v-else-if="col.prop === 'status'">
|
||||
<el-tag :type="getStatusType(scope.row.status)" effect="dark" size="large">
|
||||
<el-tag :type="getStatusType(scope.row.status)" effect="light" round>
|
||||
{{ scope.row.status }}
|
||||
</el-tag>
|
||||
</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">查看</el-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>
|
||||
</template>
|
||||
|
||||
<template #default="scope" v-else-if="['unit_price', 'total_price'].includes(col.prop)">
|
||||
{{ formatMoney(scope.row[col.prop], scope.row.currency) }}
|
||||
<span class="money-text">{{ formatMoney(scope.row[col.prop], scope.row.currency) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
|
||||
<el-table-column label="操作" width="180" fixed="right" align="center">
|
||||
<el-table-column label="操作" width="160" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" size="large" @click="handleUpdate(row)">编辑</el-button>
|
||||
<el-popconfirm title="确定删除该条入库记录吗?" @confirm="handleDelete(row)">
|
||||
<el-button link type="primary" size="default" @click="handleUpdate(row)">编辑</el-button>
|
||||
<el-popconfirm title="确定删除该条记录吗?不可恢复。" @confirm="handleDelete(row)" width="220">
|
||||
<template #reference>
|
||||
<el-button link type="danger" size="large">删除</el-button>
|
||||
<el-button link type="danger" size="default">删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
@ -89,12 +104,13 @@
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
class="pagination-container"
|
||||
class="pagination-bar"
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[15, 30, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
background
|
||||
@size-change="fetchData"
|
||||
@current-change="fetchData"
|
||||
/>
|
||||
@ -102,198 +118,207 @@
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="dialogStatus === 'create' ? '新增采购入库' : '编辑入库信息'"
|
||||
width="1100px"
|
||||
top="3vh"
|
||||
width="1050px"
|
||||
top="5vh"
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
class="stylish-dialog"
|
||||
>
|
||||
<el-form :model="form" label-width="120px" ref="formRef" :rules="rules" size="large" class="custom-form">
|
||||
<el-form :model="form" label-width="110px" ref="formRef" :rules="rules" size="large" class="stylish-form">
|
||||
|
||||
<div class="section-block">
|
||||
<el-divider content-position="left"><el-icon><Box /></el-icon> <b>1. 基础物料信息 (必须已存在)</b></el-divider>
|
||||
<div class="form-card basic-card">
|
||||
<div class="card-title">
|
||||
<el-icon class="icon"><Box /></el-icon>
|
||||
<span>1. 基础信息</span>
|
||||
<span class="sub-title" v-if="dialogStatus === 'create'"> (请先搜索选择物料)</span>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12" v-if="dialogStatus === 'create'">
|
||||
<el-form-item label="搜索关联" prop="base_id">
|
||||
<el-select
|
||||
v-model="form.base_id"
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
placeholder="输入名称或规格型号搜索..."
|
||||
:remote-method="handleSearchMaterial"
|
||||
:loading="searchLoading"
|
||||
style="width: 100%"
|
||||
@change="onMaterialSelected"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in materialOptions"
|
||||
:key="item.id"
|
||||
:label="item.name + ' - ' + item.spec"
|
||||
:value="item.id"
|
||||
<div class="card-content">
|
||||
<el-row :gutter="24" v-if="dialogStatus === 'create'" style="margin-bottom: 20px;">
|
||||
<el-col :span="14">
|
||||
<el-form-item label="物料搜索" prop="base_id" class="highlight-label">
|
||||
<el-select
|
||||
v-model="form.base_id"
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
placeholder="输入名称 / 规格型号进行模糊搜索..."
|
||||
:remote-method="handleSearchMaterial"
|
||||
:loading="searchLoading"
|
||||
style="width: 100%"
|
||||
@change="onMaterialSelected"
|
||||
size="large"
|
||||
>
|
||||
<span style="float: left; font-weight: bold;">{{ item.name }}</span>
|
||||
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.spec }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" v-if="dialogStatus === 'create'">
|
||||
<el-alert title="注:只能入库状态为'启用'的基础物料。" type="warning" :closable="false" show-icon />
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" class="read-only-area">
|
||||
<el-col :span="8"><el-form-item label="名称"><el-input v-model="form.material_name" disabled /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="规格型号"><el-input v-model="form.spec_model" disabled /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="单位"><el-input v-model="form.unit" disabled /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="类别"><el-input v-model="form.category" disabled /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="类型"><el-input v-model="form.material_type" disabled placeholder="自动关联" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<div class="section-block">
|
||||
<el-divider content-position="left"><el-icon><House /></el-icon> <b>2. 库存实体信息</b></el-divider>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6"><el-form-item label="编码/SKU" prop="sku"><el-input v-model="form.sku" /></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
|
||||
placeholder="自动生成"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="6"><el-form-item label="条码" prop="barcode"><el-input v-model="form.barcode" /></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="库位" prop="warehouse_location"><el-input v-model="form.warehouse_location" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="sn-bn-row">
|
||||
<el-row style="margin-bottom: 15px; padding-left: 20px;">
|
||||
<el-col :span="24">
|
||||
<el-radio-group v-model="entryMode" @change="handleEntryModeChange" :disabled="modeLocked">
|
||||
<el-radio label="batch" size="large" border>按批号入库 (Batch)</el-radio>
|
||||
<el-radio label="serial" size="large" border>按序列号入库 (SN)</el-radio>
|
||||
</el-radio-group>
|
||||
<div v-if="modeLocked" class="locked-tip">
|
||||
<el-icon style="vertical-align: middle"><Lock /></el-icon>
|
||||
该物料已有入库记录,系统已自动匹配历史入库方式(同物料同策略),不可手动更改。
|
||||
<el-option
|
||||
v-for="item in materialOptions"
|
||||
:key="item.id"
|
||||
:label="item.name + ' [' + item.spec + ']'"
|
||||
:value="item.id"
|
||||
>
|
||||
<div class="option-item">
|
||||
<span class="opt-name">{{ item.name }}</span>
|
||||
<span class="opt-spec">{{ item.spec }}</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<div class="info-alert">
|
||||
<el-icon><InfoFilled /></el-icon> 仅展示状态为“启用”的基础物料
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="10">
|
||||
<el-form-item label="批号" prop="batch_number">
|
||||
<el-input
|
||||
v-model="form.batch_number"
|
||||
placeholder="系统自动生成"
|
||||
:disabled="entryMode === 'serial'"
|
||||
clearable
|
||||
>
|
||||
<template #prefix><span style="color:#67C23A; font-weight:bold">BN</span></template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item label="序列号" prop="serial_number">
|
||||
<el-input
|
||||
v-model="form.serial_number"
|
||||
placeholder="请扫描或输入设备SN"
|
||||
:disabled="entryMode === 'batch'"
|
||||
clearable
|
||||
>
|
||||
<template #prefix><span style="color:#409EFF; font-weight:bold">SN</span></template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="tip-col">
|
||||
<span class="small-tip" v-if="entryMode === 'batch'">
|
||||
* <b>批号模式</b>:{{ modeLocked ? '根据历史记录' : '首次入库' }},系统已自动生成新批号。
|
||||
</span>
|
||||
<span class="small-tip" v-else>
|
||||
* <b>序列号模式</b>:{{ modeLocked ? '根据历史记录' : '首次入库' }},请手动录入唯一SN码。
|
||||
</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="read-only-grid">
|
||||
<el-row :gutter="24">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="form-card inbound-card">
|
||||
<div class="card-title">
|
||||
<el-icon class="icon"><House /></el-icon>
|
||||
<span>2. 入库详情</span>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="20" style="margin-top: 15px;">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="入库量" prop="in_quantity">
|
||||
<el-input-number v-model="form.in_quantity" :min="1" style="width:100%" controls-position="right" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<div class="card-content">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="6"><el-form-item label="编码/SKU" prop="sku"><el-input v-model="form.sku" placeholder="选填" /></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-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>
|
||||
|
||||
<template v-if="dialogStatus === 'update'">
|
||||
<div class="identity-panel">
|
||||
<el-row>
|
||||
<el-col :span="24" style="margin-bottom: 12px;">
|
||||
<el-radio-group v-model="entryMode" @change="handleEntryModeChange" :disabled="modeLocked" 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>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="批号" prop="batch_number">
|
||||
<el-input
|
||||
v-model="form.batch_number"
|
||||
:placeholder="entryMode === 'batch' ? '系统生成...' : '不可用'"
|
||||
:disabled="entryMode === 'serial'"
|
||||
clearable
|
||||
>
|
||||
<template #prefix><span class="prefix-tag bn">BN</span></template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="序列号" prop="serial_number">
|
||||
<el-input
|
||||
v-model="form.serial_number"
|
||||
:placeholder="entryMode === 'serial' ? '请扫描 SN...' : '不可用'"
|
||||
:disabled="entryMode === 'batch'"
|
||||
clearable
|
||||
>
|
||||
<template #prefix><span class="prefix-tag sn">SN</span></template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="24" style="margin-top: 15px;">
|
||||
<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-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-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-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-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-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<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-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-form-item label="到检状态" prop="inspection_status">
|
||||
<el-select v-model="form.inspection_status" style="width:100%">
|
||||
<el-option label="未检" value="未检"><span style="color:#909399">⚪ 未检</span></el-option>
|
||||
<el-option label="合格" value="合格"><span style="color:#67C23A">🟢 合格</span></el-option>
|
||||
<el-option label="不合格" value="不合格"><span style="color:#F56C6C">🔴 不合格</span></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
<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>
|
||||
|
||||
<el-col :span="6">
|
||||
<el-form-item label="到检状态" prop="inspection_status">
|
||||
<el-select v-model="form.inspection_status" style="width:100%">
|
||||
<el-option label="未检" value="未检" />
|
||||
<el-option label="合格" value="合格" />
|
||||
<el-option label="不合格" value="不合格" />
|
||||
</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" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="divider-text">商务与采购信息</div>
|
||||
|
||||
<div class="section-block">
|
||||
<el-divider content-position="left"><el-icon><Money /></el-icon> <b>3. 商务信息</b></el-divider>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6"><el-form-item label="币种"><el-input v-model="form.currency" /></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-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="总价"><el-input-number v-model="form.total_price" :precision="4" disabled :controls="false" style="width:100%"/></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8"><el-form-item label="供应商"><el-input v-model="form.supplier_name" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="采购人"><el-input v-model="form.purchaser" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="邮箱"><el-input v-model="form.purchaser_email" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12"><el-form-item label="原始链接"><el-input v-model="form.source_link" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="详情链接"><el-input v-model="form.detail_link" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="6"><el-form-item label="币种"><el-input v-model="form.currency" /></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-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-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8"><el-form-item label="供应商"><el-input v-model="form.supplier_name" placeholder="供应商全称" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="采购人"><el-input v-model="form.purchaser" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="采购邮箱"><el-input v-model="form.purchaser_email" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="24">
|
||||
<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>
|
||||
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="visible = false" class="big-font-btn">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="submitForm" class="big-font-btn">
|
||||
{{ dialogStatus === 'create' ? '确认入库' : '保存修改' }}
|
||||
</el-button>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="visible = false" size="large">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="submitForm" size="large" class="confirm-btn">
|
||||
{{ dialogStatus === 'create' ? '确认入库' : '保存修改' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@ -301,7 +326,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, watch } from 'vue'
|
||||
import { Plus, Setting, Refresh, Search, Lock, Box, House, Money } from '@element-plus/icons-vue'
|
||||
import { Plus, Setting, Refresh, Search, Lock, Box, House, InfoFilled, Link } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import dayjs from 'dayjs'
|
||||
import {
|
||||
@ -326,11 +351,10 @@ const formRef = ref()
|
||||
const queryParams = reactive({ page: 1, pageSize: 15, keyword: '' })
|
||||
const materialOptions = ref<any[]>([])
|
||||
|
||||
// 新增:入库模式控制
|
||||
const entryMode = ref('batch')
|
||||
const modeLocked = ref(false)
|
||||
|
||||
// 列定义 (保持不变)
|
||||
// 列定义
|
||||
const baseColumns = [
|
||||
{ prop: 'material_name', label: '名称' },
|
||||
{ prop: 'category', label: '类别' },
|
||||
@ -367,11 +391,29 @@ const stockColumns = [
|
||||
|
||||
const allColumns = [...baseColumns, ...stockColumns]
|
||||
|
||||
const visibleColumnProps = ref([
|
||||
// 表头持久化
|
||||
const STORAGE_KEY = 'stock_buy_visible_columns'
|
||||
const defaultColumns = [
|
||||
'material_name', 'category', 'material_type', 'spec_model', 'unit',
|
||||
'inbound_date', 'serial_number', 'batch_number', 'status', 'inspection_status',
|
||||
'unit_price', 'total_price', 'supplier_name', 'purchaser'
|
||||
])
|
||||
'unit_price', 'total_price', 'supplier_name', 'purchaser', 'qty_stock', 'qty_available'
|
||||
]
|
||||
|
||||
const getSavedColumns = () => {
|
||||
try {
|
||||
const saved = localStorage.getItem(STORAGE_KEY)
|
||||
return saved ? JSON.parse(saved) : defaultColumns
|
||||
} catch (e) {
|
||||
return defaultColumns
|
||||
}
|
||||
}
|
||||
|
||||
const visibleColumnProps = ref(getSavedColumns())
|
||||
|
||||
watch(visibleColumnProps, (newVal) => {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(newVal))
|
||||
}, { deep: true })
|
||||
|
||||
|
||||
const form = reactive({
|
||||
id: undefined,
|
||||
@ -399,7 +441,7 @@ const validateUnique = (rule: any, value: string, callback: any) => {
|
||||
return false
|
||||
})
|
||||
if (isDuplicate) {
|
||||
callback(new Error('该编号在当前页已存在'))
|
||||
callback(new Error('编号重复'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
@ -407,18 +449,17 @@ const validateUnique = (rule: any, value: string, callback: any) => {
|
||||
|
||||
const validateIdentity = (rule: any, value: any, callback: any) => {
|
||||
if (entryMode.value === 'serial' && !form.serial_number && rule.field === 'serial_number') {
|
||||
callback(new Error('序列号模式下必填'))
|
||||
callback(new Error('SN必填'))
|
||||
} else if (entryMode.value === 'batch' && !form.batch_number && rule.field === 'batch_number') {
|
||||
callback(new Error('批号模式下必填'))
|
||||
callback(new Error('批号必填'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const rules = {
|
||||
base_id: [{ required: true, message: '请搜索并选择基础物料', trigger: 'change' }],
|
||||
in_quantity: [{ required: true, message: '请输入入库数量', trigger: 'blur' }],
|
||||
in_date: [{ required: true, message: '请选择日期', trigger: 'change' }],
|
||||
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' }]
|
||||
}
|
||||
@ -426,7 +467,6 @@ const rules = {
|
||||
// ------------------------------------
|
||||
// 核心逻辑函数
|
||||
// ------------------------------------
|
||||
|
||||
const checkHistoryAndSetMode = async (baseId: number) => {
|
||||
try {
|
||||
const res: any = await getBuyList({ page: 1, pageSize: 1000 })
|
||||
@ -436,7 +476,6 @@ const checkHistoryAndSetMode = async (baseId: number) => {
|
||||
if (historyItems.length > 0) {
|
||||
modeLocked.value = true
|
||||
const latest = historyItems.sort((a: any, b: any) => b.id - a.id)[0]
|
||||
|
||||
if (latest.serial_number) {
|
||||
entryMode.value = 'serial'
|
||||
form.serial_number = ''
|
||||
@ -512,7 +551,6 @@ watch(() => [form.in_quantity, form.unit_price], () => {
|
||||
form.total_price = Number((form.in_quantity * form.unit_price).toFixed(4))
|
||||
})
|
||||
|
||||
// CRUD 操作
|
||||
const fetchData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
@ -525,20 +563,16 @@ const fetchData = async () => {
|
||||
const handleCreate = () => {
|
||||
dialogStatus.value = 'create'
|
||||
resetForm()
|
||||
// 新增时自动设置当前时间,不可修改
|
||||
form.in_date = dayjs().format('YYYY-MM-DD')
|
||||
|
||||
modeLocked.value = false
|
||||
entryMode.value = 'batch'
|
||||
form.batch_number = ''
|
||||
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
const handleUpdate = (row: any) => {
|
||||
dialogStatus.value = 'update'
|
||||
resetForm()
|
||||
|
||||
modeLocked.value = true
|
||||
|
||||
form.id = row.id
|
||||
@ -548,10 +582,8 @@ const handleUpdate = (row: any) => {
|
||||
form.category = row.category
|
||||
form.unit = row.unit
|
||||
form.material_type = row.material_type
|
||||
|
||||
form.sku = row.sku
|
||||
form.barcode = row.barcode
|
||||
// 编辑时回显原有时间,disabled 属性确保不可修改
|
||||
form.in_date = row.inbound_date
|
||||
form.warehouse_location = row.warehouse_loc
|
||||
|
||||
@ -585,7 +617,6 @@ const handleUpdate = (row: any) => {
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
// 核心修复:修复编辑不生效的问题
|
||||
const submitForm = async () => {
|
||||
if (!formRef.value) return
|
||||
await formRef.value.validate(async (valid: boolean) => {
|
||||
@ -596,23 +627,16 @@ const submitForm = async () => {
|
||||
await createBuyInbound(form)
|
||||
ElMessage.success('入库成功')
|
||||
} else {
|
||||
// 确保 ID 和数值类型正确传递
|
||||
const payload = {
|
||||
...form,
|
||||
in_quantity: Number(form.in_quantity),
|
||||
unit_price: Number(form.unit_price)
|
||||
}
|
||||
// 1. 等待更新完成
|
||||
await updateBuyInbound(form.id!, payload)
|
||||
ElMessage.success('更新成功')
|
||||
}
|
||||
|
||||
// 2. 更新成功后,先刷新数据
|
||||
await fetchData()
|
||||
|
||||
// 3. 数据刷新完毕,再关闭弹窗
|
||||
visible.value = false
|
||||
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e.msg || '操作失败')
|
||||
} finally {
|
||||
@ -662,37 +686,122 @@ onMounted(() => fetchData())
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.buy-module { background: #fff; padding: 15px; }
|
||||
/* 全局布局 */
|
||||
.buy-module {
|
||||
background: #f5f7fa; /* 整体背景微灰,突出内容 */
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 头部布局优化:左搜右操作 */
|
||||
.header-tools { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
|
||||
.left-tools { flex: 0 0 300px; /* 左侧固定宽度或自适应 */ }
|
||||
/* 顶部工具栏 */
|
||||
.header-tools {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
background: #fff;
|
||||
padding: 15px 20px;
|
||||
border-radius: 8px;
|
||||
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; }
|
||||
|
||||
.search-input { width: 100%; }
|
||||
/* 表格美化 */
|
||||
.modern-table {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
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); }
|
||||
|
||||
.custom-big-table { font-size: 14px; }
|
||||
.section-block { margin-bottom: 20px; border: 1px solid #ebeef5; padding: 15px; border-radius: 4px; }
|
||||
.read-only-area { background-color: #f5f7fa; padding: 10px; border-radius: 4px; }
|
||||
/* 弹窗与表单美化 */
|
||||
.stylish-form .form-card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e4e7ed;
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sn-bn-row {
|
||||
background-color: #fffbf0;
|
||||
.card-title {
|
||||
background: #fcfcfc;
|
||||
padding: 12px 20px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
color: #303133;
|
||||
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: 20px; }
|
||||
|
||||
/* 基础信息卡片 (蓝色调,示读) */
|
||||
.basic-card { border-left: 4px solid #409EFF; }
|
||||
.is-text-view :deep(.el-input__wrapper) {
|
||||
box-shadow: none !important;
|
||||
background-color: #f5f7fa;
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
border-radius: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
.is-text-view :deep(.el-input__inner) { color: #606266; font-weight: 500; }
|
||||
|
||||
/* 入库信息卡片 */
|
||||
.inbound-card { border-left: 4px solid #67C23A; }
|
||||
|
||||
/* 身份区域 (SN/BN) */
|
||||
.identity-panel {
|
||||
background: #fffbf0;
|
||||
border: 1px dashed #e6a23c;
|
||||
padding: 15px 0 5px 0;
|
||||
border-radius: 4px;
|
||||
margin-top: 10px;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.text-sn { color: #409EFF; font-weight: bold; }
|
||||
.text-bn { color: #67C23A; font-weight: bold; }
|
||||
.small-tip { font-size: 12px; color: #909399; margin-left: 10px; }
|
||||
.tip-col { padding-left: 20px; margin-bottom: 5px; margin-top: -10px;}
|
||||
.locked-tip {
|
||||
font-size: 12px;
|
||||
color: #E6A23C;
|
||||
margin-top: 5px;
|
||||
background-color: #fdf6ec;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
.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 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
margin: 30px 0 20px;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
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; }
|
||||
|
||||
/* 底部按钮 */
|
||||
.dialog-footer { display: flex; justify-content: flex-end; gap: 15px; margin-top: 20px; }
|
||||
.info-alert { font-size: 12px; color: #909399; margin-top: 10px; display: flex; align-items: center; gap: 5px; }
|
||||
.option-item { display: flex; justify-content: space-between; width: 100%; }
|
||||
.opt-name { font-weight: bold; }
|
||||
.opt-spec { color: #8492a6; font-size: 13px; }
|
||||
.total-price-input :deep(.el-input__inner) { color: #F56C6C; font-weight: bold; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user