Compare commits
10 Commits
80ab64e428
...
1ddd3f8c90
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ddd3f8c90 | |||
| aa50fdc9de | |||
| 24344e380a | |||
| 19d4dd64d4 | |||
| ef82203ed7 | |||
| 24d9b07c97 | |||
| ede3d6b330 | |||
| 9af7e0d005 | |||
| 6507b3ee14 | |||
| 2a38cf5bc0 |
@@ -3387,6 +3387,12 @@
|
||||
],
|
||||
"summary": "获取当前库存列表",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "只查询有库存的原料",
|
||||
"name": "has_stock",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "排序字段, 例如 \"stock DESC\"",
|
||||
@@ -3710,6 +3716,7 @@
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
7,
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
@@ -3719,12 +3726,12 @@
|
||||
5,
|
||||
-1,
|
||||
5,
|
||||
6,
|
||||
7
|
||||
6
|
||||
],
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"x-enum-varnames": [
|
||||
"_numLevels",
|
||||
"DebugLevel",
|
||||
"InfoLevel",
|
||||
"WarnLevel",
|
||||
@@ -3734,8 +3741,7 @@
|
||||
"FatalLevel",
|
||||
"_minLevel",
|
||||
"_maxLevel",
|
||||
"InvalidLevel",
|
||||
"_numLevels"
|
||||
"InvalidLevel"
|
||||
],
|
||||
"name": "level",
|
||||
"in": "query"
|
||||
@@ -7262,6 +7268,10 @@
|
||||
"type": "string",
|
||||
"maxLength": 255
|
||||
},
|
||||
"max_addition_ratio": {
|
||||
"description": "最大添加比例",
|
||||
"type": "number"
|
||||
},
|
||||
"name": {
|
||||
"description": "原料名称",
|
||||
"type": "string",
|
||||
@@ -7331,6 +7341,14 @@
|
||||
"dto.CurrentStockResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"last_operation_source_type": {
|
||||
"description": "上次库存变动的来源类型",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.StockLogSourceType"
|
||||
}
|
||||
]
|
||||
},
|
||||
"last_updated": {
|
||||
"description": "最后更新时间",
|
||||
"type": "string"
|
||||
@@ -8819,6 +8837,10 @@
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"max_addition_ratio": {
|
||||
"description": "最大添加比例",
|
||||
"type": "number"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -9192,7 +9214,8 @@
|
||||
"type": "object",
|
||||
"required": [
|
||||
"change_amount",
|
||||
"raw_material_id"
|
||||
"raw_material_id",
|
||||
"source_type"
|
||||
],
|
||||
"properties": {
|
||||
"change_amount": {
|
||||
@@ -9207,6 +9230,18 @@
|
||||
"description": "备注",
|
||||
"type": "string",
|
||||
"maxLength": 255
|
||||
},
|
||||
"source_id": {
|
||||
"description": "来源ID, 例如: 配方ID, 采购单ID等",
|
||||
"type": "integer"
|
||||
},
|
||||
"source_type": {
|
||||
"description": "库存变动来源类型",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.StockLogSourceType"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -9859,6 +9894,10 @@
|
||||
"type": "string",
|
||||
"maxLength": 255
|
||||
},
|
||||
"max_addition_ratio": {
|
||||
"description": "最大添加比例",
|
||||
"type": "number"
|
||||
},
|
||||
"name": {
|
||||
"description": "原料名称",
|
||||
"type": "string",
|
||||
@@ -10581,6 +10620,7 @@
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"enum": [
|
||||
7,
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
@@ -10590,10 +10630,10 @@
|
||||
5,
|
||||
-1,
|
||||
5,
|
||||
6,
|
||||
7
|
||||
6
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"_numLevels",
|
||||
"DebugLevel",
|
||||
"InfoLevel",
|
||||
"WarnLevel",
|
||||
@@ -10603,8 +10643,7 @@
|
||||
"FatalLevel",
|
||||
"_minLevel",
|
||||
"_maxLevel",
|
||||
"InvalidLevel",
|
||||
"_numLevels"
|
||||
"InvalidLevel"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -7,6 +7,8 @@ import { PaginationDTO, Response, StockLogSourceType } from '../enums';
|
||||
* @typedef {object} StockAdjustmentRequest
|
||||
* @property {number} change_amount - 变动数量, 正数为入库, 负数为出库, 单位: g
|
||||
* @property {number} raw_material_id - 要调整的原料ID
|
||||
* @property {StockLogSourceType} source_type - 库存变动来源类型
|
||||
* @property {number} [source_id] - 来源ID, 例如: 配方ID, 采购单ID等
|
||||
* @property {string} [remarks] - 备注
|
||||
*/
|
||||
|
||||
@@ -36,6 +38,7 @@ import { PaginationDTO, Response, StockLogSourceType } from '../enums';
|
||||
* @property {number} raw_material_id - 原料ID
|
||||
* @property {string} raw_material_name - 原料名称
|
||||
* @property {number} stock - 当前库存量, 单位: g
|
||||
* @property {StockLogSourceType} [last_operation_source_type] - 上次库存变动的来源类型
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -51,6 +54,7 @@ import { PaginationDTO, Response, StockLogSourceType } from '../enums';
|
||||
|
||||
/**
|
||||
* @typedef {object} GetCurrentStockListParams
|
||||
* @property {boolean} [has_stock] - 只查询有库存的原料
|
||||
* @property {string} [order_by] - 排序字段, 例如 "stock DESC"
|
||||
* @property {number} [page] - 页码
|
||||
* @property {number} [page_size] - 每页数量
|
||||
|
||||
258
src/components/inventory/StockAdjustmentDialog.vue
Normal file
258
src/components/inventory/StockAdjustmentDialog.vue
Normal file
@@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
width="400px"
|
||||
:close-on-click-modal="false"
|
||||
@close="handleClose"
|
||||
>
|
||||
<el-form :model="form" :rules="rules" ref="formRef" label-width="80px">
|
||||
<el-form-item label="原料名称">
|
||||
<el-input v-model="rawMaterial.raw_material_name" disabled></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作类型">
|
||||
<el-radio-group v-model="form.operationType">
|
||||
<el-radio label="in">存入</el-radio>
|
||||
<el-radio label="out">取出</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="变更类型" prop="source_type">
|
||||
<el-select v-model="form.source_type" placeholder="请选择变更类型" style="width: 100%;">
|
||||
<el-option
|
||||
v-for="item in availableSourceTypes"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="变动数量" prop="change_amount">
|
||||
<div class="amount-unit-wrapper">
|
||||
<el-input-number
|
||||
v-model="displayAmount"
|
||||
:min="computedMin"
|
||||
:precision="computedPrecision"
|
||||
:step="computedStep"
|
||||
controls-position="right"
|
||||
class="amount-input"
|
||||
></el-input-number>
|
||||
<el-select v-model="unit" placeholder="单位" class="unit-select">
|
||||
<el-option label="g" value="g"></el-option>
|
||||
<el-option label="kg" value="kg"></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remarks">
|
||||
<el-input v-model="form.remarks" type="textarea" :rows="2"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">确认</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, computed, watch } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { InventoryApi } from '../../api/inventory';
|
||||
import { StockLogSourceType } from '../../enums'; // 导入 StockLogSourceType
|
||||
|
||||
export default {
|
||||
name: 'StockAdjustmentDialog',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
rawMaterial: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
emits: ['update:visible', 'success'],
|
||||
setup(props, { emit }) {
|
||||
const formRef = ref(null);
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (val) => emit('update:visible', val),
|
||||
});
|
||||
|
||||
const unit = ref('g'); // 默认单位为克
|
||||
const form = reactive({
|
||||
operationType: 'in', // 默认存入
|
||||
source_type: StockLogSourceType.MANUAL, // 默认变更类型
|
||||
change_amount: 1000, // 内部始终以克为单位
|
||||
remarks: '',
|
||||
});
|
||||
|
||||
const dialogTitle = computed(() => {
|
||||
return form.operationType === 'in' ? '存入库存' : '取出库存';
|
||||
});
|
||||
|
||||
// 定义存入和取出对应的变更类型
|
||||
const inSourceTypes = [
|
||||
{ value: StockLogSourceType.PURCHASE, label: '采购入库' },
|
||||
{ value: StockLogSourceType.FERMENT_END, label: '发酵入库' },
|
||||
{ value: StockLogSourceType.MANUAL, label: '手动盘点' },
|
||||
];
|
||||
|
||||
const outSourceTypes = [
|
||||
{ value: StockLogSourceType.FEEDING, label: '饲喂出库' },
|
||||
{ value: StockLogSourceType.DETERIORATE, label: '变质出库' },
|
||||
{ value: StockLogSourceType.SALE, label: '售卖出库' },
|
||||
{ value: StockLogSourceType.MISCELLANEOUS, label: '杂用领取' },
|
||||
{ value: StockLogSourceType.FERMENT_START, label: '发酵出库' },
|
||||
{ value: StockLogSourceType.MANUAL, label: '手动盘点' },
|
||||
];
|
||||
|
||||
// 根据操作类型动态计算可用的变更类型
|
||||
const availableSourceTypes = computed(() => {
|
||||
const types = form.operationType === 'in' ? inSourceTypes : outSourceTypes;
|
||||
// 如果当前选中的 source_type 不在新的可用列表中,则重置为第一个可用类型或 MANUAL
|
||||
if (!types.some(item => item.value === form.source_type)) {
|
||||
form.source_type = types.length > 0 ? types[0].value : StockLogSourceType.MANUAL;
|
||||
}
|
||||
return types;
|
||||
});
|
||||
|
||||
// 计算属性:用于 el-input-number 的显示值和输入值转换
|
||||
const displayAmount = computed({
|
||||
get() {
|
||||
if (unit.value === 'kg') {
|
||||
return form.change_amount / 1000;
|
||||
}
|
||||
return form.change_amount;
|
||||
},
|
||||
set(val) {
|
||||
if (unit.value === 'kg') {
|
||||
form.change_amount = val * 1000;
|
||||
} else {
|
||||
form.change_amount = val;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// 计算属性:el-input-number 的步长
|
||||
const computedStep = computed(() => {
|
||||
return unit.value === 'kg' ? 0.1 : 100;
|
||||
});
|
||||
|
||||
// 计算属性:el-input-number 的精度
|
||||
const computedPrecision = computed(() => {
|
||||
return unit.value === 'kg' ? 2 : 0;
|
||||
});
|
||||
|
||||
// 计算属性:el-input-number 的最小值
|
||||
const computedMin = computed(() => {
|
||||
return unit.value === 'kg' ? 0.001 : 1;
|
||||
});
|
||||
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal) {
|
||||
// 弹窗打开时重置表单和单位
|
||||
form.change_amount = 1000; // 默认1000克
|
||||
form.remarks = '';
|
||||
form.operationType = 'in'; // 默认存入
|
||||
form.source_type = StockLogSourceType.MANUAL; // 默认变更类型
|
||||
unit.value = 'g'; // 默认单位为克
|
||||
formRef.value?.clearValidate();
|
||||
}
|
||||
});
|
||||
|
||||
const rules = {
|
||||
source_type: [
|
||||
{ required: true, message: '请选择变更类型', trigger: 'change' },
|
||||
],
|
||||
change_amount: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入变动数量',
|
||||
trigger: 'blur',
|
||||
// 验证器直接作用于内部的 form.change_amount (克)
|
||||
validator: (rule, value, callback) => {
|
||||
if (form.change_amount === null || form.change_amount === undefined || form.change_amount <= 0) {
|
||||
callback(new Error('数量必须大于0'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await formRef.value.validate();
|
||||
|
||||
let finalChangeAmount = form.change_amount; // form.change_amount 已经是克
|
||||
if (form.operationType === 'out') {
|
||||
finalChangeAmount = -finalChangeAmount;
|
||||
// 检查是否超出库存 (props.rawMaterial.stock 也是克)
|
||||
if (props.rawMaterial.stock + finalChangeAmount < 0) {
|
||||
ElMessage.error('取出数量不能大于当前库存!');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const data = {
|
||||
raw_material_id: props.rawMaterial.raw_material_id,
|
||||
change_amount: finalChangeAmount,
|
||||
source_type: form.source_type, // 添加 source_type
|
||||
remarks: form.remarks,
|
||||
};
|
||||
|
||||
await InventoryApi.adjustStock(data);
|
||||
ElMessage.success(`${form.operationType === 'in' ? '存入' : '取出'}成功!`);
|
||||
emit('success');
|
||||
dialogVisible.value = false;
|
||||
} catch (error) {
|
||||
if (error !== false) { // 阻止表单验证失败时的错误提示
|
||||
ElMessage.error('操作失败: ' + (error.message || '未知错误'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false;
|
||||
formRef.value?.resetFields();
|
||||
};
|
||||
|
||||
return {
|
||||
formRef,
|
||||
dialogVisible,
|
||||
dialogTitle,
|
||||
form,
|
||||
rules,
|
||||
handleSubmit,
|
||||
handleClose,
|
||||
unit,
|
||||
displayAmount,
|
||||
computedStep,
|
||||
computedPrecision,
|
||||
computedMin,
|
||||
availableSourceTypes, // 暴露给模板
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.amount-unit-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.amount-input {
|
||||
flex: 1; /* 让输入框占据剩余空间 */
|
||||
margin-right: 10px; /* 输入框和选择器之间的间距 */
|
||||
}
|
||||
|
||||
.unit-select {
|
||||
width: 80px; /* 固定单位选择器的宽度 */
|
||||
}
|
||||
</style>
|
||||
137
src/components/inventory/StockListTable.vue
Normal file
137
src/components/inventory/StockListTable.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-table :data="stockList" style="width: 100%" v-loading="loading">
|
||||
<el-table-column prop="raw_material_name" label="原料名称"></el-table-column>
|
||||
<el-table-column prop="stock" label="当前库存">
|
||||
<template #default="scope">
|
||||
{{ formatWeight(scope.row.stock) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="last_updated" label="最后更新时间">
|
||||
<template #default="scope">
|
||||
{{ formatRFC3339(scope.row.last_updated) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="last_operation_source_type" label="上次操作类型">
|
||||
<template #default="scope">
|
||||
{{ getStockLogSourceTypeLabel(scope.row.last_operation_source_type) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120">
|
||||
<template #default="scope">
|
||||
<el-button size="small" type="primary" @click="handleAdjust(scope.row)">调整库存</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="pagination.page"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:page-size="pagination.page_size"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="pagination.total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import { InventoryApi } from '../../api/inventory';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { formatRFC3339, formatWeight } from '../../utils/format';
|
||||
import { getStockLogSourceTypeLabel } from '../../enums'; // 导入 getStockLogSourceTypeLabel
|
||||
|
||||
export default {
|
||||
name: 'StockListTable',
|
||||
props: {
|
||||
stockFilter: {
|
||||
type: String,
|
||||
default: 'all', // 默认值与父组件保持一致
|
||||
},
|
||||
searchRawMaterialName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
emits: ['adjust-stock'], // 声明组件将发出的事件
|
||||
setup(props, { expose, emit }) { // 接收 emit 函数
|
||||
const stockList = ref([]);
|
||||
const loading = ref(false);
|
||||
const pagination = ref({
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const fetchStockList = async (filter = props.stockFilter, rawMaterialName = props.searchRawMaterialName) => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const params = {
|
||||
page: pagination.value.page,
|
||||
page_size: pagination.value.page_size,
|
||||
};
|
||||
if (filter === 'in_stock') {
|
||||
params.has_stock = true;
|
||||
}
|
||||
if (rawMaterialName) {
|
||||
params.raw_material_name = rawMaterialName;
|
||||
}
|
||||
const res = await InventoryApi.getCurrentStockList(params);
|
||||
stockList.value = res.data.list;
|
||||
pagination.value.total = res.data.pagination.total;
|
||||
} catch (error) {
|
||||
ElMessage.error('获取库存列表失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSizeChange = (val) => {
|
||||
pagination.value.page_size = val;
|
||||
fetchStockList();
|
||||
};
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
pagination.value.page = val;
|
||||
fetchStockList();
|
||||
};
|
||||
|
||||
// 处理库存调整操作
|
||||
const handleAdjust = (row) => {
|
||||
emit('adjust-stock', row); // 只传递行数据
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchStockList();
|
||||
});
|
||||
|
||||
watch(() => props.stockFilter, (newFilter) => {
|
||||
pagination.value.page = 1; // 筛选条件变化时重置页码
|
||||
fetchStockList(newFilter, props.searchRawMaterialName);
|
||||
});
|
||||
|
||||
watch(() => props.searchRawMaterialName, (newRawMaterialName) => {
|
||||
pagination.value.page = 1; // 搜索条件变化时重置页码
|
||||
fetchStockList(props.stockFilter, newRawMaterialName);
|
||||
});
|
||||
|
||||
// 暴露 fetchStockList 方法给父组件
|
||||
expose({
|
||||
fetchStockList,
|
||||
});
|
||||
|
||||
return {
|
||||
stockList,
|
||||
loading,
|
||||
pagination,
|
||||
handleSizeChange,
|
||||
handleCurrentChange,
|
||||
formatRFC3339,
|
||||
formatWeight,
|
||||
handleAdjust,
|
||||
getStockLogSourceTypeLabel, // 暴露辅助函数给模板
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
25
src/enums.js
25
src/enums.js
@@ -79,6 +79,31 @@ export const StockLogSourceType = {
|
||||
FERMENT_END: '发酵入库', // 发酵料产出,作为新原料计入库存
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据库存变动来源的键或值获取其对应的中文标签。
|
||||
* @param {string} sourceTypeKeyOrValue - 库存变动来源的键 (如 "PURCHASE") 或值 (如 "采购入库")。
|
||||
* @returns {string} 对应的中文标签,如果未找到则返回 '--'。
|
||||
*/
|
||||
export function getStockLogSourceTypeLabel(sourceTypeKeyOrValue) {
|
||||
if (!sourceTypeKeyOrValue) {
|
||||
return '--';
|
||||
}
|
||||
|
||||
// 尝试直接通过键查找
|
||||
if (StockLogSourceType[sourceTypeKeyOrValue]) {
|
||||
return StockLogSourceType[sourceTypeKeyOrValue];
|
||||
}
|
||||
|
||||
// 尝试通过值反向查找
|
||||
for (const key in StockLogSourceType) {
|
||||
if (StockLogSourceType[key] === sourceTypeKeyOrValue) {
|
||||
return StockLogSourceType[key];
|
||||
}
|
||||
}
|
||||
|
||||
return '--';
|
||||
}
|
||||
|
||||
/**
|
||||
* 用药原因
|
||||
* @enum {string}
|
||||
|
||||
@@ -115,6 +115,12 @@
|
||||
</el-icon>
|
||||
<template #title>配方管理</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/inventory/stock">
|
||||
<el-icon>
|
||||
<Tickets/>
|
||||
</el-icon>
|
||||
<template #title>库存管理</template>
|
||||
</el-menu-item>
|
||||
|
||||
</el-sub-menu>
|
||||
|
||||
@@ -444,7 +450,7 @@ export default {
|
||||
|
||||
const activeMenu = computed(() => {
|
||||
const path = route.path;
|
||||
if (path.startsWith('/monitor') || path.startsWith('/pms') || path.startsWith('/devices') || path.startsWith('/device-templates') || path.startsWith('/alarms') || path.startsWith('/feed')) {
|
||||
if (path.startsWith('/monitor') || path.startsWith('/pms') || path.startsWith('/devices') || path.startsWith('/device-templates') || path.startsWith('/alarms') || path.startsWith('/feed') || path.startsWith('/inventory')) {
|
||||
return path;
|
||||
}
|
||||
return route.path;
|
||||
|
||||
@@ -28,6 +28,7 @@ import NutrientList from '../views/feed/NutrientList.vue'; // 修正拼写错误
|
||||
import PigAgeStageList from '../views/feed/PigAgeStageList.vue'; // 导入 PigAgeStageList 组件
|
||||
import PigBreedList from '../views/feed/PigBreedList.vue'; // 导入 PigBreedList 组件
|
||||
import RecipeList from '../views/feed/RecipeList.vue';
|
||||
import StockManagement from '../views/inventory/StockManagement.vue';
|
||||
|
||||
|
||||
const routes = [
|
||||
@@ -45,6 +46,7 @@ const routes = [
|
||||
{path: '/feed/pig-age-stages', component: PigAgeStageList, meta: {requiresAuth: true, title: '年龄阶段管理'}}, // 添加年龄阶段管理路由
|
||||
{path: '/feed/pig-breeds', component: PigBreedList, meta: {requiresAuth: true, title: '品种管理'}}, // 添加品种管理路由
|
||||
{path: '/feed/recipes', component: RecipeList, meta: {requiresAuth: true, title: '配方管理'}},
|
||||
{path: '/inventory/stock', component: StockManagement, meta: {requiresAuth: true, title: '库存管理'}},
|
||||
|
||||
{path: '/monitor/device-command-logs', component: DeviceCommandLogView, meta: {requiresAuth: true, title: '设备命令日志'}},
|
||||
{path: '/monitor/medication-logs', component: MedicationLogsView, meta: {requiresAuth: true, title: '用药记录'}},
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
* @returns {string} - 格式化后的字符串,如果输入无效则返回空字符串或提示
|
||||
*/
|
||||
export function formatRFC3339(rfc3339String) {
|
||||
if (!rfc3339String) {
|
||||
return '--'; // 或者返回空字符串 ''
|
||||
if (!rfc3339String || rfc3339String.startsWith('0001-01-01')) {
|
||||
return '--';
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -18,5 +18,21 @@ export function formatRFC3339(rfc3339String) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将以克为单位的重量转换为更合适的单位(克或千克)并格式化
|
||||
* @param {number} grams - 以克为单位的重量
|
||||
* @returns {string} - 格式化后的重量字符串 (例如 "1.5 kg" 或 "500 g")
|
||||
*/
|
||||
export function formatWeight(grams) {
|
||||
if (typeof grams !== 'number' || isNaN(grams)) {
|
||||
return '--';
|
||||
}
|
||||
if (grams >= 1000) {
|
||||
return (grams / 1000).toFixed(2) + ' kg';
|
||||
} else {
|
||||
return grams + ' g';
|
||||
}
|
||||
}
|
||||
|
||||
// 你未来还可以添加其他全局格式化函数
|
||||
// export function formatCurrency(number) { ... }
|
||||
|
||||
139
src/views/inventory/StockManagement.vue
Normal file
139
src/views/inventory/StockManagement.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<div class="stock-management-container">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<div class="title-container">
|
||||
<h2 class="page-title">库存管理</h2>
|
||||
<el-button type="text" @click="refreshList" class="refresh-btn" title="刷新库存列表">
|
||||
<el-icon :size="20"><Refresh /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="filter-controls">
|
||||
<el-input
|
||||
v-model="searchRawMaterialName"
|
||||
placeholder="按原料名称搜索"
|
||||
clearable
|
||||
@keyup.enter="refreshList"
|
||||
style="width: 200px; margin-right: 10px;"
|
||||
></el-input>
|
||||
<el-select v-model="stockFilter" placeholder="筛选库存" @change="refreshList" style="width: 150px;">
|
||||
<el-option label="所有原料" value="all"></el-option>
|
||||
<el-option label="有库存的原料" value="in_stock"></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<stock-list-table
|
||||
ref="stockListTableRef"
|
||||
:stockFilter="stockFilter"
|
||||
:searchRawMaterialName="searchRawMaterialName"
|
||||
@adjust-stock="handleAdjustStock"
|
||||
></stock-list-table>
|
||||
</el-card>
|
||||
|
||||
<!-- 库存调整弹窗 -->
|
||||
<stock-adjustment-dialog
|
||||
v-model:visible="showAdjustmentDialog"
|
||||
:rawMaterial="currentRawMaterialForAdjustment"
|
||||
@success="refreshList"
|
||||
></stock-adjustment-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
import { Refresh } from '@element-plus/icons-vue';
|
||||
import StockListTable from '../../components/inventory/StockListTable.vue';
|
||||
import StockAdjustmentDialog from '../../components/inventory/StockAdjustmentDialog.vue'; // 导入库存调整弹窗组件
|
||||
|
||||
export default {
|
||||
name: 'StockManagement',
|
||||
components: {
|
||||
StockListTable,
|
||||
Refresh,
|
||||
StockAdjustmentDialog, // 注册组件
|
||||
},
|
||||
setup() {
|
||||
const stockListTableRef = ref(null);
|
||||
const stockFilter = ref('all'); // 默认显示所有原料
|
||||
const searchRawMaterialName = ref(''); // 搜索原料名称
|
||||
|
||||
// 库存调整弹窗相关
|
||||
const showAdjustmentDialog = ref(false);
|
||||
const currentRawMaterialForAdjustment = ref(null);
|
||||
// currentOperationType 不再需要在这里管理,由 StockAdjustmentDialog 内部管理
|
||||
|
||||
const refreshList = () => {
|
||||
if (stockListTableRef.value) {
|
||||
stockListTableRef.value.fetchStockList(stockFilter.value, searchRawMaterialName.value);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理库存调整操作
|
||||
* @param {object} rawMaterial - 当前操作的原料数据
|
||||
*/
|
||||
const handleAdjustStock = (rawMaterial) => {
|
||||
currentRawMaterialForAdjustment.value = { ...rawMaterial };
|
||||
showAdjustmentDialog.value = true;
|
||||
};
|
||||
|
||||
return {
|
||||
stockListTableRef,
|
||||
stockFilter,
|
||||
searchRawMaterialName,
|
||||
refreshList,
|
||||
// 库存调整弹窗相关
|
||||
showAdjustmentDialog,
|
||||
currentRawMaterialForAdjustment,
|
||||
handleAdjustStock,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stock-management-container {
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
.title-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
color: black;
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.filter-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user