zhouhui.jiang

update 订单、出库、发票

Showing 25 changed files with 509 additions and 50 deletions
......@@ -141,3 +141,4 @@ public class DeliveryMainController {
}
}
......
......@@ -87,7 +87,7 @@ public class InvoiceMainController {
@Operation(summary = "删除发票", description = "根据发票ID逻辑删除发票")
@DeleteMapping("/{invoiceId}")
@PreAuthorize("hasAuthority('invoice:delete')")
@PreAuthorize("hasAuthority('invoice:remove')")
public ApiRes<Void> deleteInvoice(
@Parameter(description = "发票ID", required = true) @PathVariable Long invoiceId) {
try {
......@@ -141,3 +141,4 @@ public class InvoiceMainController {
}
}
......
......@@ -61,3 +61,4 @@ public class DeliveryAddReq {
private List<DeliveryItemAddReq> deliveryItems;
}
......
......@@ -37,3 +37,4 @@ public class DeliveryItemAddReq {
private BigDecimal deliveryAmount;
}
......
......@@ -59,3 +59,4 @@ public class DeliveryItemRes {
private LocalDateTime updateTime;
}
......
......@@ -40,3 +40,4 @@ public class DeliveryItemUpdateReq {
private BigDecimal deliveryAmount;
}
......
......@@ -67,3 +67,4 @@ public class DeliveryRes {
private List<DeliveryItemRes> deliveryItems;
}
......
......@@ -65,3 +65,4 @@ public class DeliveryUpdateReq {
private List<DeliveryItemUpdateReq> deliveryItems;
}
......
......@@ -42,3 +42,4 @@ public class InvoiceItemAddReq {
private BigDecimal taxAmount;
}
......
......@@ -62,3 +62,4 @@ public class InvoiceItemRes {
private LocalDateTime updateTime;
}
......
......@@ -45,3 +45,4 @@ public class InvoiceItemUpdateReq {
private BigDecimal taxAmount;
}
......
......@@ -55,3 +55,4 @@ public class InvoiceQueryReq {
private Integer pageSize = 10;
}
......
......@@ -66,3 +66,4 @@ public class DeliveryItem {
private String delFlag;
}
......
......@@ -72,3 +72,4 @@ public class InvoiceItem {
private String delFlag;
}
......
......@@ -81,3 +81,4 @@ public class InvoiceMain {
private String delFlag;
}
......
......@@ -14,3 +14,4 @@ import org.apache.ibatis.annotations.Mapper;
public interface DeliveryItemMapper extends BaseMapper<DeliveryItem> {
}
......
......@@ -14,3 +14,4 @@ import org.apache.ibatis.annotations.Mapper;
public interface DeliveryMainMapper extends BaseMapper<DeliveryMain> {
}
......
......@@ -14,3 +14,4 @@ import org.apache.ibatis.annotations.Mapper;
public interface InvoiceItemMapper extends BaseMapper<InvoiceItem> {
}
......
......@@ -14,3 +14,4 @@ import org.apache.ibatis.annotations.Mapper;
public interface InvoiceMainMapper extends BaseMapper<InvoiceMain> {
}
......
......@@ -69,3 +69,4 @@ public interface DeliveryMainService extends IService<DeliveryMain> {
boolean updateDeliveryStatus(Long deliveryId, Integer deliveryStatus);
}
......
......@@ -69,3 +69,4 @@ public interface InvoiceMainService extends IService<InvoiceMain> {
boolean updateInvoiceStatus(Long invoiceId, Integer invoiceStatus);
}
......
......@@ -229,3 +229,4 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del
}
}
......
......@@ -112,6 +112,7 @@ public class InvoiceMainServiceImpl extends ServiceImpl<InvoiceMainMapper, Invoi
InvoiceMain invoiceMain = new InvoiceMain();
BeanUtils.copyProperties(addReq, invoiceMain);
invoiceMain.setUpdateTime(LocalDateTime.now());
invoiceMain.setCreateTime(LocalDateTime.now());
invoiceMain.setUpdateTime(LocalDateTime.now());
invoiceMain.setDelFlag("0");
......
......@@ -63,7 +63,7 @@
<!-- 操作按钮区域 -->
<div class="action-section">
<div class="action-buttons">
<button @click="handleAdd" class="action-btn primary">➕ 新增出库</button>
<button @click="handleAdd" class="action-btn primary">✨ 新增</button>
<button @click="handleExport" class="action-btn secondary">📋 导出</button>
</div>
</div>
......@@ -175,10 +175,18 @@
<div class="dialog-body">
<div v-if="orderDetail" class="detail-content">
<div class="detail-section">
<h4>订单基本信息</h4>
<h4>出库基本信息</h4>
<div class="detail-grid">
<div class="detail-item">
<label>订单编号:</label>
<label>出库单ID:</label>
<span>{{ orderDetail.deliveryId }}</span>
</div>
<div class="detail-item">
<label>出库单编号:</label>
<span>{{ orderDetail.deliveryNo }}</span>
</div>
<div class="detail-item">
<label>关联订单编号:</label>
<span>{{ orderDetail.orderNo }}</span>
</div>
<div class="detail-item">
......@@ -190,12 +198,8 @@
<span>{{ orderDetail.dealerName }}</span>
</div>
<div class="detail-item">
<label>订单日期:</label>
<span>{{ formatDate(orderDetail.orderDate) }}</span>
</div>
<div class="detail-item">
<label>订单总金额:</label>
<span>{{ formatCurrency(orderDetail.totalAmount) }}</span>
<label>出库日期:</label>
<span>{{ formatDate(orderDetail.deliveryDate) }}</span>
</div>
<div class="detail-item">
<label>出库状态:</label>
......@@ -204,33 +208,47 @@
</span>
</div>
<div class="detail-item">
<label>出库时间:</label>
<label>仓库编码:</label>
<span>{{ orderDetail.warehouseCode }}</span>
</div>
<div class="detail-item">
<label>数据来源:</label>
<span>{{ orderDetail.dataSource || '-' }}</span>
</div>
<div class="detail-item">
<label>上传时间:</label>
<span>{{ formatTime(orderDetail.uploadTime || '') }}</span>
</div>
<div class="detail-item">
<label>创建时间:</label>
<span>{{ formatTime(orderDetail.createTime || '') }}</span>
</div>
<div class="detail-item">
<label>更新时间:</label>
<span>{{ formatTime(orderDetail.updateTime || '') }}</span>
</div>
</div>
</div>
<div class="detail-section" v-if="orderDetail.orderItems && orderDetail.orderItems.length > 0">
<div class="detail-section" v-if="orderDetail.deliveryItems && orderDetail.deliveryItems.length > 0">
<h4>出库明细</h4>
<table class="detail-table">
<thead>
<tr>
<th>产品编码</th>
<th>产品名称</th>
<th>产品型号</th>
<th>数量</th>
<th>单价</th>
<th>总价</th>
<th>出库数量</th>
<th>出库单价</th>
<th>出库金额</th>
</tr>
</thead>
<tbody>
<tr v-for="item in orderDetail.orderItems" :key="item.itemId">
<tr v-for="item in orderDetail.deliveryItems" :key="item.deliveryItemId">
<td>{{ item.productCode }}</td>
<td>{{ item.productName }}</td>
<td>{{ item.productModel }}</td>
<td>{{ item.quantity }}</td>
<td>{{ formatCurrency(item.unitPrice) }}</td>
<td>{{ formatCurrency(item.totalPrice) }}</td>
<td>{{ item.deliveryQty }}</td>
<td>{{ formatCurrency(item.deliveryPrice) }}</td>
<td>{{ formatCurrency(item.deliveryAmount) }}</td>
</tr>
</tbody>
</table>
......
......@@ -71,7 +71,7 @@
<!-- 操作按钮区域 -->
<div class="action-section">
<div class="action-buttons">
<button @click="handleAdd" class="action-btn primary">✨ 新增发票</button>
<button @click="handleAdd" class="action-btn primary">✨ 新增</button>
<button @click="handleExport" class="action-btn secondary">📋 导出</button>
</div>
</div>
......@@ -101,7 +101,7 @@
<th>开票日期</th>
<th>发票金额</th>
<th>开票状态</th>
<th>开票时间</th>
<th>上传时间</th>
<th>关联订单编号</th>
<th>操作</th>
</tr>
......@@ -124,6 +124,7 @@
<td class="action-col">
<button @click="handleView(order)" class="view-btn">👁️ 查看</button>
<button @click="handleEdit(order)" class="edit-btn">✏️ 编辑</button>
<button @click="handleDelete(order)" class="delete-btn">🗑️ 删除</button>
</td>
</tr>
</tbody>
......@@ -177,7 +178,7 @@
<!-- 新增/编辑发票对话框 -->
<div v-if="showDialog" class="dialog-overlay" @click="closeDialog">
<div class="dialog-content" @click.stop>
<div class="dialog-content large-dialog" @click.stop>
<div class="dialog-header">
<h3>{{ isEdit ? '编辑发票' : '新增发票' }}</h3>
<button @click="closeDialog" class="close-btn">×</button>
......@@ -206,11 +207,41 @@
</div>
<div class="form-row">
<div class="form-item">
<label>出库单编号:</label>
<input
v-model="formData.deliveryNo"
class="form-input"
placeholder="请输入出库单编号"
/>
</div>
<div class="form-item">
<label class="required">经销商编码:</label>
<input
v-model="formData.dealerCode"
class="form-input"
placeholder="请输入经销商编码"
required
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">经销商名称:</label>
<input
v-model="formData.dealerName"
class="form-input"
placeholder="请输入经销商名称"
required
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">开票日期:</label>
<input
v-model="formData.invoiceDate"
class="form-input"
type="datetime-local"
type="date"
required
/>
</div>
......@@ -221,7 +252,8 @@
class="form-input"
type="number"
step="0.01"
placeholder="请输入开票金额"
placeholder="开票金额(自动计算)"
readonly
required
/>
</div>
......@@ -235,6 +267,7 @@
type="number"
step="0.01"
placeholder="请输入税率"
@input="calculateTaxAmount"
/>
</div>
<div class="form-item">
......@@ -244,10 +277,84 @@
class="form-input"
type="number"
step="0.01"
placeholder="请输入税额"
placeholder="税额(自动计算)"
readonly
/>
</div>
</div>
<!-- 发票明细信息 -->
<div class="form-section">
<div class="section-header">
<h4>发票明细</h4>
<button type="button" @click="addInvoiceItem" class="add-item-btn">+ 添加明细</button>
</div>
<div class="invoice-items-container">
<div v-for="(item, index) in formData.invoiceItems" :key="index" class="invoice-item">
<div class="item-row-single">
<div class="form-item" style="flex: 0 0 120px;">
<label>产品编码:</label>
<input
v-model="item.productCode"
class="form-input"
placeholder="产品编码"
/>
</div>
<div class="form-item" style="flex: 1 1 200px; min-width: 150px;">
<label>产品名称:</label>
<input
v-model="item.productName"
class="form-input"
placeholder="产品名称"
/>
</div>
<div class="form-item" style="flex: 0 0 80px;">
<label>数量:</label>
<input
v-model.number="item.productQty"
class="form-input"
type="number"
@input="calculateInvoiceItemTotal(item)"
/>
</div>
<div class="form-item" style="flex: 0 0 100px;">
<label>单价:</label>
<input
v-model.number="item.unitPrice"
class="form-input"
type="number"
step="0.01"
@input="calculateInvoiceItemTotal(item)"
/>
</div>
<div class="form-item" style="flex: 0 0 100px;">
<label>金额:</label>
<input
v-model.number="item.itemAmount"
class="form-input"
type="number"
step="0.01"
readonly
/>
</div>
<div class="form-item" style="flex: 0 0 100px;">
<label>税额:</label>
<input
v-model.number="item.taxAmount"
class="form-input"
type="number"
step="0.01"
readonly
/>
</div>
<div class="form-item" style="flex: 0 0 60px;">
<button type="button" @click="removeInvoiceItem(index)" class="remove-item-btn">删除</button>
</div>
</div>
</div>
</div>
</div>
<div class="form-row">
<div class="form-item full-width">
<label>备注:</label>
......@@ -321,11 +428,11 @@
</div>
<div class="detail-item">
<label>税率:</label>
<span>{{ invoiceDetail.taxRate ? (invoiceDetail.taxRate * 100).toFixed(2) + '%' : '-' }}</span>
<span>{{ invoiceDetail.taxRate ? invoiceDetail.taxRate + '%' : '-' }}</span>
</div>
<div class="detail-item">
<label>税额:</label>
<span class="amount-text">{{ formatCurrency((invoiceDetail.totalAmount || 0) * (invoiceDetail.taxRate || 0)) }}</span>
<span class="amount-text">{{ formatCurrency((invoiceDetail.totalAmount || 0) * (invoiceDetail.taxRate || 0) / 100) }}</span>
</div>
<div class="detail-item">
<label>数据来源:</label>
......@@ -363,22 +470,22 @@
</tr>
</thead>
<tbody>
<tr v-for="item in invoiceDetail.invoiceItems" :key="item.itemId">
<tr v-for="item in invoiceDetail.invoiceItems" :key="item.invoiceItemId">
<td>{{ item.productCode }}</td>
<td>{{ item.productName }}</td>
<td>{{ item.productQty }}</td>
<td>{{ formatCurrency(item.unitPrice) }}</td>
<td class="amount-text">{{ formatCurrency(item.itemAmount) }}</td>
<td>{{ item.taxRate ? (item.taxRate * 100).toFixed(2) + '%' : '-' }}</td>
<td>{{ item.invoiceQty }}</td>
<td>{{ formatCurrency(item.unitPriceNoTax) }}</td>
<td class="amount-text">{{ formatCurrency(item.amountNoTax) }}</td>
<td>{{ invoiceDetail.taxRate ? (invoiceDetail.taxRate / 100).toFixed(2) + '%' : '-' }}</td>
<td class="amount-text">{{ formatCurrency(item.taxAmount || 0) }}</td>
</tr>
</tbody>
<tfoot>
<tr class="total-row">
<td colspan="4" class="total-label">合计:</td>
<td class="amount-text total-amount">{{ formatCurrency(invoiceDetail.totalAmount) }}</td>
<td class="amount-text total-amount">{{ formatCurrency(invoiceDetail.invoiceItems.reduce((sum, item) => sum + (item.amountNoTax || 0), 0)) }}</td>
<td>-</td>
<td class="amount-text total-amount">{{ formatCurrency((invoiceDetail.totalAmount || 0) * (invoiceDetail.taxRate || 0)) }}</td>
<td class="amount-text total-amount">{{ formatCurrency(invoiceDetail.invoiceItems.reduce((sum, item) => sum + (item.taxAmount || 0), 0)) }}</td>
</tr>
</tfoot>
</table>
......@@ -445,13 +552,18 @@ const totalPages = computed(() => Math.ceil(pagination.total / pagination.pageSi
// 表单数据
const formData = reactive({
invoiceId: 0,
orderNo: '',
invoiceNo: '',
deliveryNo: '',
dealerCode: '',
dealerName: '',
invoiceDate: '',
invoiceAmount: 0,
taxRate: 0,
taxAmount: 0,
remark: ''
remark: '',
invoiceItems: [] as any[]
})
// 状态相关方法
......@@ -550,29 +662,148 @@ const handleReset = () => {
const handleAdd = () => {
isEdit.value = false
Object.assign(formData, {
invoiceId: 0,
orderNo: '',
invoiceNo: '',
deliveryNo: '',
dealerCode: '',
dealerName: '',
invoiceDate: '',
invoiceAmount: 0,
taxRate: 0,
taxAmount: 0,
remark: ''
remark: '',
invoiceItems: []
})
showDialog.value = true
}
const handleEdit = (order: InvoiceInfo) => {
isEdit.value = true
Object.assign(formData, {
orderNo: order.orderNo,
invoiceNo: order.invoiceNo,
invoiceDate: order.invoiceDate,
invoiceAmount: order.totalAmount,
taxRate: 0,
taxAmount: 0,
remark: ''
// 发票明细相关方法
const addInvoiceItem = () => {
const newItem = {
productCode: '',
productName: '',
productQty: 1,
unitPrice: 0,
itemAmount: 0,
taxAmount: 0
}
formData.invoiceItems.push(newItem)
// 计算新明细项目的金额
calculateInvoiceItemTotal(newItem)
}
const removeInvoiceItem = (index: number) => {
formData.invoiceItems.splice(index, 1)
// 删除明细项目后,重新计算开票金额和税额
calculateTaxAmount()
}
const calculateInvoiceItemTotal = (item: any) => {
if (item.productQty && item.unitPrice) {
item.itemAmount = parseFloat((item.productQty * item.unitPrice).toFixed(2))
}
// 明细项目变化时,重新计算开票金额和税额
calculateTaxAmount()
}
const calculateTaxAmount = () => {
// 计算开票金额(基于明细项目的金额总和)
const totalAmount = formData.invoiceItems.reduce((sum, item) => {
return sum + (item.itemAmount || 0)
}, 0)
console.log('计算开票金额:', {
invoiceItems: formData.invoiceItems,
totalAmount: totalAmount,
taxRate: formData.taxRate
})
// 更新开票金额(保留两位小数)
formData.invoiceAmount = parseFloat(totalAmount.toFixed(2))
// 计算税额(基于主表税率)
if (totalAmount && formData.taxRate) {
formData.taxAmount = parseFloat((totalAmount * formData.taxRate / 100).toFixed(2))
// 更新明细项目的税额(基于主表税率)
formData.invoiceItems.forEach(item => {
if (item.itemAmount && formData.taxRate) {
item.taxAmount = parseFloat((item.itemAmount * formData.taxRate / 100).toFixed(2))
} else {
item.taxAmount = 0
}
})
} else {
formData.taxAmount = 0
// 清空明细项目的税额
formData.invoiceItems.forEach(item => {
item.taxAmount = 0
})
}
console.log('计算结果:', {
invoiceAmount: formData.invoiceAmount,
taxAmount: formData.taxAmount
})
showDialog.value = true
}
const handleEdit = async (order: InvoiceInfo) => {
try {
// 调用详情API获取最新的发票信息
const response = await invoiceApi.getInvoiceDetail(order.invoiceId) as any
console.log('发票编辑详情API响应:', response)
if (response && response.invoiceId) {
isEdit.value = true
Object.assign(formData, {
invoiceId: response.invoiceId,
orderNo: response.orderNo,
invoiceNo: response.invoiceNo,
deliveryNo: response.deliveryNo || '',
dealerCode: response.dealerCode,
dealerName: response.dealerName,
invoiceDate: response.invoiceDate,
invoiceAmount: response.totalAmount,
taxRate: response.taxRate || 0,
taxAmount: (response.totalAmount || 0) * (response.taxRate || 0) / 100,
remark: '',
invoiceItems: (response.invoiceItems || []).map((item: any) => ({
invoiceItemId: item.invoiceItemId,
invoiceId: item.invoiceId,
invoiceNo: item.invoiceNo,
orderNo: item.orderNo,
productCode: item.productCode,
productName: item.productName,
productQty: item.invoiceQty, // 映射字段名
unitPrice: item.unitPriceNoTax, // 映射字段名
itemAmount: item.amountNoTax, // 映射字段名
taxAmount: item.taxAmount
}))
})
// 确保税额计算正确
calculateTaxAmount()
showDialog.value = true
} else {
showMessage('获取发票详情失败', 'error')
}
} catch (error) {
console.error('获取发票详情失败:', error)
showMessage('获取发票详情失败, 请重试', 'error')
}
}
const handleDelete = async (order: InvoiceInfo) => {
if (confirm(`确定要删除发票"${order.invoiceNo}"吗?`)) {
try {
await invoiceApi.deleteInvoice(order.invoiceId)
showMessage('删除发票成功', 'success')
fetchOrders() // 刷新列表
} catch (error) {
console.error('删除发票失败:', error)
showMessage('删除发票失败, 请重试', 'error')
}
}
}
const handleView = async (order: InvoiceInfo) => {
......@@ -606,8 +837,85 @@ const handlePrintInvoice = (invoice: InvoiceInfo) => {
const handleSubmit = async () => {
try {
showMessage('发票保存功能开发中...', 'warning')
// 验证必填字段
if (!formData.orderNo || !formData.invoiceNo || !formData.dealerCode || !formData.dealerName || !formData.invoiceDate || !formData.invoiceAmount) {
showMessage('请填写所有必填字段', 'error')
return
}
// 验证明细
if (!formData.invoiceItems || formData.invoiceItems.length === 0) {
showMessage('请至少添加一个发票明细', 'error')
return
}
// 验证明细字段
for (let i = 0; i < formData.invoiceItems.length; i++) {
const item = formData.invoiceItems[i]
if (!item.productCode || !item.productName || !item.productQty || !item.unitPrice) {
showMessage(`请填写第${i + 1}个明细的所有必填字段`, 'error')
return
}
}
if (isEdit.value) {
// 编辑发票
const updateData = {
invoiceId: formData.invoiceId,
invoiceNo: formData.invoiceNo,
orderNo: formData.orderNo,
deliveryNo: formData.deliveryNo,
dealerCode: formData.dealerCode,
dealerName: formData.dealerName,
totalAmount: formData.invoiceAmount,
invoiceDate: formData.invoiceDate,
invoiceStatus: 1,
taxRate: formData.taxRate,
dataSource: 'ERP系统',
invoiceItems: formData.invoiceItems.map((item: any) => ({
itemId: item.invoiceItemId,
productCode: item.productCode,
productName: item.productName,
invoiceQty: item.productQty, // 映射字段名
unitPriceNoTax: item.unitPrice, // 映射字段名
amountNoTax: item.itemAmount, // 映射字段名
taxRate: item.taxRate,
taxAmount: item.taxAmount
}))
}
await invoiceApi.updateInvoice(updateData)
showMessage('发票更新成功', 'success')
} else {
// 新增发票
const addData = {
invoiceNo: formData.invoiceNo,
orderNo: formData.orderNo,
deliveryNo: formData.deliveryNo,
dealerCode: formData.dealerCode,
dealerName: formData.dealerName,
totalAmount: formData.invoiceAmount,
invoiceDate: formData.invoiceDate,
invoiceStatus: 1,
taxRate: formData.taxRate,
dataSource: 'ERP系统',
invoiceItems: formData.invoiceItems.map((item: any) => ({
productCode: item.productCode,
productName: item.productName,
invoiceQty: item.productQty, // 映射字段名
unitPriceNoTax: item.unitPrice, // 映射字段名
amountNoTax: item.itemAmount, // 映射字段名
taxRate: item.taxRate,
taxAmount: item.taxAmount
}))
}
await invoiceApi.addInvoice(addData)
showMessage('发票新增成功', 'success')
}
closeDialog()
fetchOrders() // 刷新列表
} catch (error) {
console.error('保存发票失败:', error)
showMessage('保存发票失败, 请重试', 'error')
......@@ -883,6 +1191,19 @@ onMounted(() => {
color: #333;
}
.delete-btn {
background: #dc3545;
color: white;
}
/* 只读字段样式 */
.form-input[readonly] {
background-color: #f8f9fa;
color: #6c757d;
cursor: not-allowed;
border-color: #dee2e6;
}
.status-btn {
background: #28a745;
color: white;
......@@ -1310,4 +1631,101 @@ onMounted(() => {
.detail-actions .close-btn:hover {
background: #5a6268;
}
/* 发票明细样式 */
.form-section {
margin: 20px 0;
border: 1px solid #e0e0e0;
border-radius: 6px;
padding: 15px;
background: #f9f9f9;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #ddd;
}
.section-header h4 {
margin: 0;
color: #333;
font-size: 16px;
font-weight: 600;
}
.add-item-btn {
background: #28a745;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: background 0.2s;
}
.add-item-btn:hover {
background: #218838;
}
.invoice-items-container {
max-height: 300px;
overflow-y: auto;
}
.invoice-item {
background: white;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
margin-bottom: 10px;
}
.item-row-single {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: nowrap;
}
.item-row-single .form-item {
flex: 0 0 auto;
margin-bottom: 0;
min-width: 0;
}
.item-row-single .form-item label {
font-size: 11px;
color: #666;
margin-bottom: 2px;
display: block;
}
.item-row-single .form-input {
width: 100%;
padding: 4px 6px;
font-size: 11px;
border: 1px solid #ddd;
border-radius: 3px;
}
.remove-item-btn {
background: #dc3545;
color: white;
border: none;
padding: 4px 8px;
border-radius: 3px;
cursor: pointer;
font-size: 11px;
height: 28px;
transition: background 0.2s;
}
.remove-item-btn:hover {
background: #c82333;
}
</style>
......