init
This commit is contained in:
@@ -0,0 +1,75 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
数据库迁移脚本:为采购订单表添加状态变更时间字段
|
||||||
|
- received_date: 实际到货时间
|
||||||
|
- paid_date: 实际付款时间
|
||||||
|
"""
|
||||||
|
import asyncio
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
project_root = Path(__file__).parent
|
||||||
|
sys.path.insert(0, str(project_root))
|
||||||
|
sys.path.insert(0, str(project_root / "src"))
|
||||||
|
|
||||||
|
from sqlalchemy.ext.asyncio import create_async_engine
|
||||||
|
from sqlalchemy import text
|
||||||
|
from config.settings import settings
|
||||||
|
|
||||||
|
|
||||||
|
async def migrate():
|
||||||
|
"""执行数据库迁移"""
|
||||||
|
if not settings.DATABASE_URL:
|
||||||
|
print("错误:未配置数据库连接")
|
||||||
|
return
|
||||||
|
|
||||||
|
engine = create_async_engine(
|
||||||
|
settings.DATABASE_URL,
|
||||||
|
echo=True
|
||||||
|
)
|
||||||
|
|
||||||
|
async with engine.begin() as conn:
|
||||||
|
# 检查字段是否已存在
|
||||||
|
check_sql = """
|
||||||
|
SELECT column_name
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'purchase_orders'
|
||||||
|
AND column_name = 'received_date'
|
||||||
|
"""
|
||||||
|
result = await conn.execute(text(check_sql))
|
||||||
|
if result.fetchone():
|
||||||
|
print("字段 received_date 已存在,跳过")
|
||||||
|
else:
|
||||||
|
# 添加实际到货时间字段
|
||||||
|
alter_sql = """
|
||||||
|
ALTER TABLE purchase_orders
|
||||||
|
ADD COLUMN received_date TIMESTAMP WITHOUT TIME ZONE
|
||||||
|
"""
|
||||||
|
await conn.execute(text(alter_sql))
|
||||||
|
print("成功添加字段 received_date")
|
||||||
|
|
||||||
|
# 检查 paid_date 字段是否已存在
|
||||||
|
check_sql2 = """
|
||||||
|
SELECT column_name
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'purchase_orders'
|
||||||
|
AND column_name = 'paid_date'
|
||||||
|
"""
|
||||||
|
result2 = await conn.execute(text(check_sql2))
|
||||||
|
if result2.fetchone():
|
||||||
|
print("字段 paid_date 已存在,跳过")
|
||||||
|
else:
|
||||||
|
# 添加实际付款时间字段
|
||||||
|
alter_sql2 = """
|
||||||
|
ALTER TABLE purchase_orders
|
||||||
|
ADD COLUMN paid_date TIMESTAMP WITHOUT TIME ZONE
|
||||||
|
"""
|
||||||
|
await conn.execute(text(alter_sql2))
|
||||||
|
print("成功添加字段 paid_date")
|
||||||
|
|
||||||
|
await engine.dispose()
|
||||||
|
print("迁移完成")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(migrate())
|
||||||
@@ -36,7 +36,7 @@ async def _calculate_material_cost_map(db_session: AsyncSession, product_ids: Li
|
|||||||
ProductMaterial.finished_product_id,
|
ProductMaterial.finished_product_id,
|
||||||
func.coalesce(
|
func.coalesce(
|
||||||
func.sum(
|
func.sum(
|
||||||
Product.cost_price * ProductMaterial.quantity * (1 + ProductMaterial.loss_rate)
|
Product.cost_price * ProductMaterial.quantity
|
||||||
),
|
),
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
@@ -186,7 +186,7 @@ async def get_product_bom(
|
|||||||
items: List[ProductMaterialItemResponse] = []
|
items: List[ProductMaterialItemResponse] = []
|
||||||
total_material_cost = 0.0
|
total_material_cost = 0.0
|
||||||
for bom, material in bom_result.all():
|
for bom, material in bom_result.all():
|
||||||
line_cost = float(material.cost_price or 0) * float(bom.quantity) * (1 + float(bom.loss_rate or 0))
|
line_cost = float(material.cost_price or 0) * float(bom.quantity)
|
||||||
total_material_cost += line_cost
|
total_material_cost += line_cost
|
||||||
items.append(
|
items.append(
|
||||||
ProductMaterialItemResponse(
|
ProductMaterialItemResponse(
|
||||||
@@ -194,7 +194,6 @@ async def get_product_bom(
|
|||||||
material_sku=material.sku,
|
material_sku=material.sku,
|
||||||
material_name=material.name,
|
material_name=material.name,
|
||||||
quantity=round(float(bom.quantity), 4),
|
quantity=round(float(bom.quantity), 4),
|
||||||
loss_rate=round(float(bom.loss_rate or 0), 4),
|
|
||||||
unit_cost=round(float(material.cost_price or 0), 4),
|
unit_cost=round(float(material.cost_price or 0), 4),
|
||||||
line_cost=round(float(line_cost), 4),
|
line_cost=round(float(line_cost), 4),
|
||||||
)
|
)
|
||||||
@@ -246,15 +245,12 @@ async def replace_product_bom(
|
|||||||
|
|
||||||
for item in payload.items:
|
for item in payload.items:
|
||||||
if item.quantity <= 0:
|
if item.quantity <= 0:
|
||||||
raise HTTPException(status_code=400, detail="物料数量必须大于0")
|
raise HTTPException(status_code=400, detail="物料数量必须大于 0")
|
||||||
if item.loss_rate < 0:
|
|
||||||
raise HTTPException(status_code=400, detail="损耗率不能为负数")
|
|
||||||
db_session.add(
|
db_session.add(
|
||||||
ProductMaterial(
|
ProductMaterial(
|
||||||
finished_product_id=product_id,
|
finished_product_id=product_id,
|
||||||
material_product_id=item.material_id,
|
material_product_id=item.material_id,
|
||||||
quantity=item.quantity,
|
quantity=item.quantity,
|
||||||
loss_rate=item.loss_rate,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ router = APIRouter(prefix="/purchase-orders", tags=["采购订单"])
|
|||||||
|
|
||||||
|
|
||||||
def _build_purchase_order_response(order: PurchaseOrder, supplier_name: str) -> PurchaseOrderResponse:
|
def _build_purchase_order_response(order: PurchaseOrder, supplier_name: str) -> PurchaseOrderResponse:
|
||||||
|
# 检查字段是否存在,避免数据库中没有这些字段时的错误
|
||||||
|
received_date = getattr(order, 'received_date', None)
|
||||||
|
paid_date = getattr(order, 'paid_date', None)
|
||||||
|
|
||||||
return PurchaseOrderResponse(
|
return PurchaseOrderResponse(
|
||||||
id=order.id,
|
id=order.id,
|
||||||
order_no=order.order_no,
|
order_no=order.order_no,
|
||||||
@@ -49,8 +53,8 @@ def _build_purchase_order_response(order: PurchaseOrder, supplier_name: str) ->
|
|||||||
paid_amount=order.paid_amount,
|
paid_amount=order.paid_amount,
|
||||||
remark=order.remark,
|
remark=order.remark,
|
||||||
created_at=order.created_at,
|
created_at=order.created_at,
|
||||||
received_date=order.received_date,
|
received_date=received_date,
|
||||||
paid_date=order.paid_date
|
paid_date=paid_date
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -81,6 +85,10 @@ async def _build_purchase_order_detail(
|
|||||||
.order_by(PurchaseOrderItem.id.asc())
|
.order_by(PurchaseOrderItem.id.asc())
|
||||||
)
|
)
|
||||||
item_rows = item_result.all()
|
item_rows = item_result.all()
|
||||||
|
# 检查字段是否存在,避免数据库中没有这些字段时的错误
|
||||||
|
received_date = getattr(order, 'received_date', None)
|
||||||
|
paid_date = getattr(order, 'paid_date', None)
|
||||||
|
|
||||||
return PurchaseOrderDetailResponse(
|
return PurchaseOrderDetailResponse(
|
||||||
id=order.id,
|
id=order.id,
|
||||||
order_no=order.order_no,
|
order_no=order.order_no,
|
||||||
@@ -93,8 +101,8 @@ async def _build_purchase_order_detail(
|
|||||||
paid_amount=order.paid_amount,
|
paid_amount=order.paid_amount,
|
||||||
remark=order.remark,
|
remark=order.remark,
|
||||||
created_at=order.created_at,
|
created_at=order.created_at,
|
||||||
received_date=order.received_date,
|
received_date=received_date,
|
||||||
paid_date=order.paid_date,
|
paid_date=paid_date,
|
||||||
items=[
|
items=[
|
||||||
PurchaseOrderItemResponse(
|
PurchaseOrderItemResponse(
|
||||||
id=item.id,
|
id=item.id,
|
||||||
@@ -218,8 +226,10 @@ async def create_purchase_order(
|
|||||||
await db_session.commit()
|
await db_session.commit()
|
||||||
await db_session.refresh(order)
|
await db_session.refresh(order)
|
||||||
|
|
||||||
supplier = await db_session.execute(select(Supplier).where(Supplier.id == order.supplier_id))
|
supplier_result = await db_session.execute(select(Supplier).where(Supplier.id == order.supplier_id))
|
||||||
supplier = supplier.scalar_one()
|
supplier = supplier_result.scalar_one_or_none()
|
||||||
|
if not supplier:
|
||||||
|
raise HTTPException(status_code=404, detail="供应商不存在")
|
||||||
|
|
||||||
return _build_purchase_order_response(order, supplier.name)
|
return _build_purchase_order_response(order, supplier.name)
|
||||||
|
|
||||||
@@ -313,9 +323,10 @@ async def update_purchase_order_status(
|
|||||||
|
|
||||||
# 更新状态和对应时间
|
# 更新状态和对应时间
|
||||||
order.status = new_status
|
order.status = new_status
|
||||||
if new_status == "received":
|
# 检查字段是否存在,避免数据库中没有这些字段时的错误
|
||||||
|
if hasattr(order, 'received_date') and new_status == "received":
|
||||||
order.received_date = func.now()
|
order.received_date = func.now()
|
||||||
elif new_status == "paid":
|
elif hasattr(order, 'paid_date') and new_status == "paid":
|
||||||
order.paid_date = func.now()
|
order.paid_date = func.now()
|
||||||
|
|
||||||
await db_session.commit()
|
await db_session.commit()
|
||||||
@@ -407,7 +418,9 @@ async def receive_purchase_order(
|
|||||||
any_received = any((item.received_quantity or 0) > 0 for item in item_map.values())
|
any_received = any((item.received_quantity or 0) > 0 for item in item_map.values())
|
||||||
if all_received:
|
if all_received:
|
||||||
order.status = "received"
|
order.status = "received"
|
||||||
order.received_date = func.now()
|
# 检查字段是否存在,避免数据库中没有这些字段时的错误
|
||||||
|
if hasattr(order, 'received_date'):
|
||||||
|
order.received_date = func.now()
|
||||||
elif any_received:
|
elif any_received:
|
||||||
order.status = "partial_received"
|
order.status = "partial_received"
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ class ProductResponse(BaseModel):
|
|||||||
class ProductMaterialItemUpdate(BaseModel):
|
class ProductMaterialItemUpdate(BaseModel):
|
||||||
material_id: int
|
material_id: int
|
||||||
quantity: float
|
quantity: float
|
||||||
loss_rate: float = 0
|
|
||||||
|
|
||||||
|
|
||||||
class ProductBOMUpdate(BaseModel):
|
class ProductBOMUpdate(BaseModel):
|
||||||
@@ -51,7 +50,6 @@ class ProductMaterialItemResponse(BaseModel):
|
|||||||
material_sku: str
|
material_sku: str
|
||||||
material_name: str
|
material_name: str
|
||||||
quantity: float
|
quantity: float
|
||||||
loss_rate: float
|
|
||||||
unit_cost: float
|
unit_cost: float
|
||||||
line_cost: float
|
line_cost: float
|
||||||
|
|
||||||
|
|||||||
@@ -167,6 +167,8 @@ async def ensure_schema_updates():
|
|||||||
await conn.execute(text("ALTER TABLE sales_orders ADD COLUMN IF NOT EXISTS production_no VARCHAR(50)"))
|
await conn.execute(text("ALTER TABLE sales_orders ADD COLUMN IF NOT EXISTS production_no VARCHAR(50)"))
|
||||||
await conn.execute(text("ALTER TABLE sales_orders ADD COLUMN IF NOT EXISTS planned_material_cost DOUBLE PRECISION DEFAULT 0"))
|
await conn.execute(text("ALTER TABLE sales_orders ADD COLUMN IF NOT EXISTS planned_material_cost DOUBLE PRECISION DEFAULT 0"))
|
||||||
await conn.execute(text("ALTER TABLE sales_orders ADD COLUMN IF NOT EXISTS actual_material_cost DOUBLE PRECISION DEFAULT 0"))
|
await conn.execute(text("ALTER TABLE sales_orders ADD COLUMN IF NOT EXISTS actual_material_cost DOUBLE PRECISION DEFAULT 0"))
|
||||||
|
await conn.execute(text("ALTER TABLE purchase_orders ADD COLUMN IF NOT EXISTS received_date TIMESTAMP WITHOUT TIME ZONE"))
|
||||||
|
await conn.execute(text("ALTER TABLE purchase_orders ADD COLUMN IF NOT EXISTS paid_date TIMESTAMP WITHOUT TIME ZONE"))
|
||||||
await conn.execute(text("""
|
await conn.execute(text("""
|
||||||
CREATE TABLE IF NOT EXISTS product_materials (
|
CREATE TABLE IF NOT EXISTS product_materials (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
|
|||||||
+1
-1
@@ -77,6 +77,6 @@
|
|||||||
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
||||||
<script src="https://unpkg.com/vue-router@4/dist/vue-router.global.prod.js"></script>
|
<script src="https://unpkg.com/vue-router@4/dist/vue-router.global.prod.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/air-datepicker@3.5.3/air-datepicker.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/air-datepicker@3.5.3/air-datepicker.js"></script>
|
||||||
<script src="/static/vue-app.js?v=20260324-01"></script>
|
<script src="/static/vue-app.js?v=20260409-01"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+50
-9
@@ -2194,6 +2194,16 @@ const InventoryView = {
|
|||||||
warehouse_id: state.purchaseWarehouseId || state.warehouses.find(w => w.is_default)?.id || state.warehouses[0]?.id || null,
|
warehouse_id: state.purchaseWarehouseId || state.warehouses.find(w => w.is_default)?.id || state.warehouses[0]?.id || null,
|
||||||
remark: ''
|
remark: ''
|
||||||
};
|
};
|
||||||
|
} else if (type === 'product') {
|
||||||
|
state.form = { ...item };
|
||||||
|
if (item.item_type === 'finished') {
|
||||||
|
await loadMaterials();
|
||||||
|
const bom = await apiRequest(`/api/products/${item.id}/materials`);
|
||||||
|
state.productBomItems = (bom.items || []).map(bomItem => ({
|
||||||
|
material_id: bomItem.material_id,
|
||||||
|
quantity: bomItem.quantity
|
||||||
|
}));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
state.form = { ...item };
|
state.form = { ...item };
|
||||||
}
|
}
|
||||||
@@ -2459,6 +2469,12 @@ const InventoryView = {
|
|||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: JSON.stringify(state.form)
|
body: JSON.stringify(state.form)
|
||||||
});
|
});
|
||||||
|
if (state.form.item_type === 'finished' && state.productBomItems.length > 0) {
|
||||||
|
await apiRequest(`/api/products/${state.editingItem.id}/materials`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify({ items: state.productBomItems })
|
||||||
|
});
|
||||||
|
}
|
||||||
addNotification('产品更新成功', 'success');
|
addNotification('产品更新成功', 'success');
|
||||||
} else {
|
} else {
|
||||||
await apiRequest('/api/products', {
|
await apiRequest('/api/products', {
|
||||||
@@ -2495,20 +2511,18 @@ const InventoryView = {
|
|||||||
state.editingItem = product;
|
state.editingItem = product;
|
||||||
state.productBomItems = (bom.items || []).map(item => ({
|
state.productBomItems = (bom.items || []).map(item => ({
|
||||||
material_id: item.material_id,
|
material_id: item.material_id,
|
||||||
quantity: item.quantity,
|
quantity: item.quantity
|
||||||
loss_rate: item.loss_rate
|
|
||||||
}));
|
}));
|
||||||
state.showModal = true;
|
state.showModal = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleApiError(e, '加载产品BOM');
|
handleApiError(e, '加载产品 BOM');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const addBomItem = () => {
|
const addBomItem = () => {
|
||||||
state.productBomItems.push({
|
state.productBomItems.push({
|
||||||
material_id: state.materials[0]?.id || null,
|
material_id: state.materials[0]?.id || null,
|
||||||
quantity: 1,
|
quantity: 1
|
||||||
loss_rate: 0
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -3116,7 +3130,6 @@ const InventoryView = {
|
|||||||
<td>{{ formatCurrency(product.material_cost || 0) }}</td>
|
<td>{{ formatCurrency(product.material_cost || 0) }}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="action-btns">
|
<div class="action-btns">
|
||||||
<button class="btn btn-sm btn-secondary" @click="editProductBom(product)">BOM</button>
|
|
||||||
<button class="btn btn-sm btn-secondary" @click="openModal('product', product)">编辑</button>
|
<button class="btn btn-sm btn-secondary" @click="openModal('product', product)">编辑</button>
|
||||||
<button class="btn btn-sm btn-danger" @click="deleteProduct(product.id)">删除</button>
|
<button class="btn btn-sm btn-danger" @click="deleteProduct(product.id)">删除</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -3618,7 +3631,7 @@ const InventoryView = {
|
|||||||
<div v-if="state.showModal" class="modal-overlay" @click.self="closeModal">
|
<div v-if="state.showModal" class="modal-overlay" @click.self="closeModal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h3>{{ state.editingItem ? '编辑' : '新增' }}{{ state.modalType === 'product' ? '产品/物料' : state.modalType === 'productBom' ? '产品BOM' : state.modalType === 'inventoryItem' ? '物料库存' : state.modalType === 'salesOrder' ? '销售订单' : state.modalType === 'purchaseOrder' ? '采购订单' : state.modalType === 'purchaseReceive' ? '采购到货入库' : state.modalType === 'supplier' ? '供应商' : '客户' }}</h3>
|
<h3>{{ state.editingItem ? '编辑' : '新增' }}{{ state.modalType === 'product' ? (state.form.item_type === 'finished' ? '成品' : '物料') : state.modalType === 'productBom' ? '产品物料' : state.modalType === 'inventoryItem' ? '物料库存' : state.modalType === 'salesOrder' ? '销售订单' : state.modalType === 'purchaseOrder' ? '采购订单' : state.modalType === 'purchaseReceive' ? '采购到货入库' : state.modalType === 'supplier' ? '供应商' : '客户' }}</h3>
|
||||||
<button class="modal-close" @click="closeModal">×</button>
|
<button class="modal-close" @click="closeModal">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
@@ -3661,6 +3674,36 @@ const InventoryView = {
|
|||||||
<label class="form-label">说明</label>
|
<label class="form-label">说明</label>
|
||||||
<input disabled value="成品不做库存,成本由下方BOM定义物料构成后自动计算" class="form-input" />
|
<input disabled value="成品不做库存,成本由下方BOM定义物料构成后自动计算" class="form-input" />
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="state.form.item_type === 'finished' && state.editingItem" class="bom-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">BOM物料配置</label>
|
||||||
|
<button type="button" class="btn btn-secondary" @click="addBomItem" style="margin-bottom: 8px;">+ 添加物料</button>
|
||||||
|
<div class="table-container">
|
||||||
|
<table class="data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>物料</th>
|
||||||
|
<th>数量</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(item, idx) in state.productBomItems" :key="'edit-bom-item-' + idx">
|
||||||
|
<td>
|
||||||
|
<select v-model.number="item.material_id" class="form-input" required>
|
||||||
|
<option v-for="material in state.materials" :key="'edit-bom-material-' + material.id" :value="material.id">
|
||||||
|
{{ material.sku }} - {{ material.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td><input v-model.number="item.quantity" type="number" min="0.0001" step="0.0001" class="form-input" required /></td>
|
||||||
|
<td><button type="button" class="btn btn-sm btn-danger" @click="removeBomItem(idx)">删除</button></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" @click="closeModal">取消</button>
|
<button type="button" class="btn btn-secondary" @click="closeModal">取消</button>
|
||||||
<button type="submit" class="btn btn-primary">保存</button>
|
<button type="submit" class="btn btn-primary">保存</button>
|
||||||
@@ -3923,7 +3966,6 @@ const InventoryView = {
|
|||||||
<tr>
|
<tr>
|
||||||
<th>物料</th>
|
<th>物料</th>
|
||||||
<th>数量</th>
|
<th>数量</th>
|
||||||
<th>损耗率</th>
|
|
||||||
<th>操作</th>
|
<th>操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -3937,7 +3979,6 @@ const InventoryView = {
|
|||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td><input v-model.number="item.quantity" type="number" min="0.0001" step="0.0001" class="form-input" required /></td>
|
<td><input v-model.number="item.quantity" type="number" min="0.0001" step="0.0001" class="form-input" required /></td>
|
||||||
<td><input v-model.number="item.loss_rate" type="number" min="0" step="0.0001" class="form-input" required /></td>
|
|
||||||
<td><button type="button" class="btn btn-sm btn-danger" @click="removeBomItem(idx)">删除</button></td>
|
<td><button type="button" class="btn btn-sm btn-danger" @click="removeBomItem(idx)">删除</button></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
Reference in New Issue
Block a user