新增库存生成功能,支持明细模式和批次模式两种存储模式,在SKU管理中实现动态表单字段的库存操作,同时在资产编辑中为租户ID为1的用户新增存储模式选择功能

This commit is contained in:
WUSIJIAN
2026-01-14 16:53:02 +08:00
parent 70a956febe
commit 31faa5d08b
3 changed files with 192 additions and 5 deletions

View File

@@ -163,3 +163,31 @@ export function getSpecsUnitOptions(assetType: string) {
params: { assetType },
});
}
// 获取库存表单字段
export function getStockFormFields(assetSkuId: string) {
return newService({
url: '/assets/stock/manage/getStockFormFields',
method: 'get',
params: { assetSkuId },
});
}
// 库存操作
export interface StockOperationParams {
assetSkuId: string;
stock?: number;
batchNo?: string;
productionDate?: string;
expiryDate?: string;
expiryWarningDate?: string;
[key: string]: any; // 支持动态字段
}
export function stockOperation(data: StockOperationParams) {
return newService({
url: '/assets/stock/manage/stockOperation',
method: 'post',
data,
});
}

View File

@@ -70,6 +70,14 @@
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="8" v-if="tenantId === '1'">
<el-form-item label="存储模式">
<el-radio-group v-model="ruleForm.stockMode" :disabled="isEdit">
<el-radio :value="1">明细模式</el-radio>
<el-radio :value="2">批次模式</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<!-- 分类属性值选择 -->
@@ -454,6 +462,7 @@ import { Plus, Delete } from '@element-plus/icons-vue';
import { getAsset, createAsset, updateAsset, uploadAssetImage } from '/@/api/assets/asset';
import { getCategoryTree, getCategory } from '/@/api/assets/category';
import { createFormDiff } from '/@/utils/diffUtils';
import { Session } from '/@/utils/storage';
import Editor from '/@/components/editor/index.vue';
import type { UploadFile, UploadUserFile, UploadRequestOptions } from 'element-plus';
@@ -495,6 +504,7 @@ interface RuleForm {
onlineTime: string;
offlineTime: string;
unlimitedStock: boolean;
stockMode: number;
physicalAssetConfig: {
shipping: {
deliveryMethod: string;
@@ -568,6 +578,9 @@ const fileAddressPrefix = ref('');
// 使用通用工具函数保存原始数据,用于最小化传参
const assetFormDiff = createFormDiff<Record<string, any>>();
// 获取租户ID
const tenantId = ref(Session.get('userInfo')?.tenantId || '');
const formatImageUrl = (url?: string) => {
if (!url) return '';
if (/^https?:\/\//i.test(url)) return url;
@@ -598,6 +611,7 @@ const getInitialForm = (): RuleForm => ({
onlineTime: '',
offlineTime: '',
unlimitedStock: false,
stockMode: 1,
physicalAssetConfig: {
shipping: {
deliveryMethod: 'express',
@@ -903,6 +917,7 @@ const openDialog = (row?: any, edit?: boolean) => {
ruleForm.onlineTime = data.onlineTime || '';
ruleForm.offlineTime = data.offlineTime || '';
ruleForm.unlimitedStock = data.unlimitedStock || false;
ruleForm.stockMode = data.stockMode || 1;
// 主图预览 (支持 imageUrl 和 fileURL)
const mainImg = data.imageUrl || data.fileURL;
@@ -1110,6 +1125,11 @@ const buildRequestBody = async (): Promise<any> => {
// 库存类型
body.unlimitedStock = ruleForm.unlimitedStock;
// 库存存储模式仅租户ID为1时提交
if (tenantId.value === '1') {
body.stockMode = ruleForm.stockMode;
}
// 主图 (已在上传时直接赋值给 ruleForm.mainImage)
if (ruleForm.mainImage) {
body.imageURL = ruleForm.mainImage;

View File

@@ -81,7 +81,7 @@
<el-table-column label="操作" width="180" align="center">
<template #default="scope">
<el-button size="small" text type="primary" @click="onEditSku(scope.row)">编辑</el-button>
<el-button v-if="scope.row.unlimitedStock" size="small" text type="success" @click="onGenerateStock(scope.row)">生成库存</el-button>
<el-button v-if="!scope.row.unlimitedStock" size="small" text type="success" @click="onGenerateStock(scope.row)">生成库存</el-button>
<el-button size="small" text type="danger" @click="onDeleteSku(scope.row)">删除</el-button>
</template>
</el-table-column>
@@ -163,6 +163,50 @@
<el-button type="primary" :loading="submitLoading" @click="onSubmitSku">确认</el-button>
</template>
</el-dialog>
<!-- 生成库存弹窗 -->
<el-dialog v-model="stockFormVisible" title="生成库存" width="450px" :close-on-click-modal="false" append-to-body>
<el-form ref="stockFormRef" :model="stockForm" :rules="getStockFormRules()" label-width="100px" v-loading="stockFormLoading">
<el-form-item label="SKU名称">
<el-input :model-value="currentSkuName" disabled />
</el-form-item>
<template v-for="field in stockFormFields" :key="field.name">
<el-form-item :label="field.label" :prop="field.name">
<!-- 数字类型 -->
<el-input-number
v-if="field.type === 'number'"
v-model="stockForm[field.name]"
:min="field.min"
:max="field.max"
controls-position="right"
style="width: 200px"
/>
<!-- 日期类型 -->
<el-date-picker
v-else-if="field.type === 'date'"
v-model="stockForm[field.name]"
type="date"
placeholder="请选择日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 200px"
/>
<!-- 文本类型 -->
<el-input
v-else
v-model="stockForm[field.name]"
:maxlength="field.maxLength"
placeholder="请输入"
style="width: 200px"
/>
</el-form-item>
</template>
</el-form>
<template #footer>
<el-button @click="stockFormVisible = false">取消</el-button>
<el-button type="primary" :loading="stockSubmitLoading" @click="onSubmitStock">确认</el-button>
</template>
</el-dialog>
</el-dialog>
</template>
@@ -170,7 +214,7 @@
import { ref, reactive } from 'vue';
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
import { ElMessageBox } from 'element-plus';
import { listAssetSkus, createAssetSku, updateAssetSku, deleteAssetSku, getAssetSku, getAsset, uploadAssetImage, getSpecsUnitOptions } from '/@/api/assets/asset';
import { listAssetSkus, createAssetSku, updateAssetSku, deleteAssetSku, getAssetSku, getAsset, uploadAssetImage, getSpecsUnitOptions, getStockFormFields, stockOperation } from '/@/api/assets/asset';
import { createFormDiff } from '/@/utils/diffUtils';
import type { UploadRequestOptions, UploadUserFile } from 'element-plus';
@@ -185,6 +229,18 @@ interface AssetSpecAttr {
dictType?: string;
}
// 库存表单字段接口
interface StockFormField {
name: string;
label: string;
type: string;
required?: boolean;
min?: number;
max?: number;
maxLength?: number;
default?: string | number;
}
const dialogVisible = ref(false);
const loading = ref(false);
const submitLoading = ref(false);
@@ -193,6 +249,16 @@ const skuFormVisible = ref(false);
const isEditSku = ref(false);
const editSkuId = ref('');
// 库存弹窗相关
const stockFormVisible = ref(false);
const stockFormLoading = ref(false);
const stockSubmitLoading = ref(false);
const stockFormFields = ref<StockFormField[]>([]);
const stockFormRef = ref<FormInstance>();
const stockForm = reactive<Record<string, any>>({});
const currentSkuId = ref('');
const currentSkuName = ref('');
const assetId = ref('');
const assetName = ref('');
const assetType = ref('');
@@ -479,9 +545,82 @@ const onDeleteSku = (row: any) => {
};
// 生成库存
const onGenerateStock = (row: any) => {
// TODO: 实现生成库存功能
ElMessage.info(`生成库存功能待实现SKU: ${row.skuName}`);
const onGenerateStock = async (row: any) => {
currentSkuId.value = row.id;
currentSkuName.value = row.skuName;
stockFormVisible.value = true;
stockFormLoading.value = true;
// 重置表单
Object.keys(stockForm).forEach((key) => delete stockForm[key]);
try {
const res = await getStockFormFields(row.id);
stockFormFields.value = res.data.fields || [];
// 设置默认值
stockFormFields.value.forEach((field) => {
if (field.default !== undefined) {
stockForm[field.name] = field.default;
} else if (field.type === 'number') {
stockForm[field.name] = field.min || 0;
} else {
stockForm[field.name] = '';
}
});
} catch (error) {
console.error('获取库存表单字段失败:', error);
ElMessage.error('获取库存表单字段失败');
stockFormVisible.value = false;
} finally {
stockFormLoading.value = false;
}
};
// 提交库存操作
const onSubmitStock = async () => {
const form = stockFormRef.value;
if (!form) return;
form.validate(async (valid: boolean) => {
if (valid) {
stockSubmitLoading.value = true;
try {
await stockOperation({
assetSkuId: currentSkuId.value,
...stockForm,
});
ElMessage.success('库存生成成功');
stockFormVisible.value = false;
getSkuList();
} catch (error) {
console.error('库存操作失败:', error);
} finally {
stockSubmitLoading.value = false;
}
}
});
};
// 生成库存表单验证规则
const getStockFormRules = () => {
const rules: Record<string, any[]> = {};
stockFormFields.value.forEach((field) => {
const fieldRules: any[] = [];
if (field.required) {
fieldRules.push({ required: true, message: `${field.label}不能为空`, trigger: 'blur' });
}
if (field.type === 'number' && field.min !== undefined) {
fieldRules.push({ type: 'number', min: field.min, message: `${field.label}最小值为${field.min}`, trigger: 'blur' });
}
if (field.maxLength) {
fieldRules.push({ max: field.maxLength, message: `${field.label}最大长度为${field.maxLength}`, trigger: 'blur' });
}
if (fieldRules.length > 0) {
rules[field.name] = fieldRules;
}
});
return rules;
};
// 重置 SKU 表单