管理后台

This commit is contained in:
18631081161 2026-03-19 19:33:13 +08:00
parent 3fb5192b4b
commit 53ce69a1f9
5 changed files with 18 additions and 29 deletions

View File

@ -15,6 +15,7 @@ server {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 50m;
}
# 上传文件代理

View File

@ -55,16 +55,9 @@
<el-table-column prop="id" label="ID" width="70" />
<el-table-column prop="name" label="商品名称" min-width="150" />
<el-table-column prop="style_no" label="款号" width="120" />
<el-table-column prop="stock" label="当前库存" width="100" />
<el-table-column prop="total_stock" label="总库存" width="100" />
<el-table-column label="库存比例" width="140">
<el-table-column prop="stock" label="库存" width="100">
<template #default="{ row }">
<el-progress
:percentage="row.total_stock > 0 ? Math.round((row.stock / row.total_stock) * 100) : 0"
:color="row.stock / row.total_stock < 0.2 ? '#f56c6c' : '#e6a23c'"
:stroke-width="16"
:text-inside="true"
/>
<el-tag :type="row.stock <= 5 ? 'danger' : 'warning'" size="small">{{ row.stock }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="100">

View File

@ -45,11 +45,7 @@
<el-input-number v-model="form.stock" :min="0" controls-position="right" style="width:100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="总库存" prop="totalStock">
<el-input-number v-model="form.totalStock" :min="0" controls-position="right" style="width:100%" />
</el-form-item>
</el-col>
<!-- 总库存已隐藏 -->
</el-row>
<el-row :gutter="32">
<el-col :span="12">
@ -318,7 +314,7 @@
</el-row>
<el-row :gutter="16">
<el-col :span="8"><el-form-item label="金耗"><el-input-number v-model="specForm.goldLoss" :precision="4" disabled controls-position="right" style="width:100%" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="金价"><el-input-number v-model="specForm.goldPrice" :precision="2" :min="0" controls-position="right" style="width:100%" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="金价"><el-input-number v-model="specForm.goldPrice" :precision="2" disabled controls-position="right" style="width:100%" /><div style="font-size:11px;color:#999;margin-top:2px">读取自金价配置</div></el-form-item></el-col>
<el-col :span="8"><el-form-item label="金值"><el-input-number v-model="specForm.goldValue" :precision="2" disabled controls-position="right" style="width:100%" /></el-form-item></el-col>
</el-row>
<div class="dialog-section">主石信息</div>
@ -431,7 +427,7 @@ const rules = {
basePrice: [{ required: true, message: '请输入基础价格', trigger: 'blur' }],
styleNo: [{ required: true, message: '请输入款号', trigger: 'blur' }],
stock: [{ required: true, message: '请输入库存', trigger: 'blur' }],
totalStock: [{ required: true, message: '请输入总库存', trigger: 'blur' }],
loss: [{ required: true, message: '请输入损耗', trigger: 'blur' }],
laborCost: [{ required: true, message: '请输入工费', trigger: 'blur' }],
categoryId: [{ required: true, validator: (_r: any, _v: any, cb: any) => form.categoryId.length > 0 ? cb() : cb(new Error('请选择分类')), trigger: 'change' }],
@ -681,8 +677,9 @@ async function handleSubmit() {
router.replace(`/products/${res.data.id}/edit`)
}
}
} catch {
ElMessage.error('保存失败')
} catch (e: any) {
const msg = e?.response?.data?.message || '保存失败'
ElMessage.error(msg)
} finally {
saving.value = false
}

View File

@ -3,14 +3,14 @@ import pool from '../utils/db'
import { RowDataPacket } from 'mysql2'
/**
* Filter products where stock/totalStock < 0.1, sorted by stock ascending.
* Filter products where stock < 20, sorted by stock ascending.
* Pure logic extracted for testability.
*/
export function filterStockAlerts(
products: { id: number; name: string; style_no: string; stock: number; total_stock: number }[]
products: { id: number; name: string; style_no: string; stock: number }[]
): typeof products {
return products
.filter((p) => p.total_stock > 0 && p.stock / p.total_stock < 0.1)
.filter((p) => p.stock < 20)
.sort((a, b) => a.stock - b.stock)
}
@ -18,9 +18,9 @@ export function filterStockAlerts(
export async function getStockAlerts(_req: Request, res: Response): Promise<void> {
try {
const [rows] = await pool.execute<RowDataPacket[]>(
`SELECT id, name, style_no, stock, total_stock
`SELECT id, name, style_no, stock
FROM products
WHERE total_stock > 0 AND stock / total_stock < 0.1
WHERE stock < 20
ORDER BY stock ASC`
)
res.json({ code: 0, data: rows })

View File

@ -8,28 +8,26 @@ const productArb = fc.record({
name: fc.string({ minLength: 1, maxLength: 20 }),
style_no: fc.string({ minLength: 0, maxLength: 10 }),
stock: fc.integer({ min: 0, max: 10000 }),
total_stock: fc.integer({ min: 0, max: 10000 }),
})
// Feature: jewelry-mall, Property 7: 库存预警阈值判断
// **Validates: Requirements 8.4**
describe('Property 7: 库存预警阈值判断', () => {
it('预警列表应恰好包含所有库存商品且按库存从少到多排序', () => {
it('预警列表应恰好包含所有库存<20的商品且按库存从少到多排序', () => {
fc.assert(
fc.property(
fc.array(productArb, { minLength: 0, maxLength: 20 }),
(products) => {
const alerts = filterStockAlerts(products)
// Every alert should satisfy the threshold condition
// Every alert should satisfy the threshold condition (stock < 20)
for (const a of alerts) {
expect(a.total_stock).toBeGreaterThan(0)
expect(a.stock / a.total_stock).toBeLessThan(0.1)
expect(a.stock).toBeLessThan(20)
}
// Every product meeting the condition should be in alerts
const expectedIds = products
.filter((p) => p.total_stock > 0 && p.stock / p.total_stock < 0.1)
.filter((p) => p.stock < 20)
.map((p) => p.id)
const alertIds = alerts.map((a) => a.id)
expect(alertIds.sort()).toEqual(expectedIds.sort())