新增库存生成功能,支持明细模式和批次模式两种存储模式,在SKU管理中实现动态表单字段的库存操作,同时在资产编辑中为租户ID为1的用户新增存储模式选择功能
This commit is contained in:
@@ -163,3 +163,31 @@ export function getSpecsUnitOptions(assetType: string) {
|
|||||||
params: { assetType },
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -70,6 +70,14 @@
|
|||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</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>
|
</el-row>
|
||||||
|
|
||||||
<!-- 分类属性值选择 -->
|
<!-- 分类属性值选择 -->
|
||||||
@@ -454,6 +462,7 @@ import { Plus, Delete } from '@element-plus/icons-vue';
|
|||||||
import { getAsset, createAsset, updateAsset, uploadAssetImage } from '/@/api/assets/asset';
|
import { getAsset, createAsset, updateAsset, uploadAssetImage } from '/@/api/assets/asset';
|
||||||
import { getCategoryTree, getCategory } from '/@/api/assets/category';
|
import { getCategoryTree, getCategory } from '/@/api/assets/category';
|
||||||
import { createFormDiff } from '/@/utils/diffUtils';
|
import { createFormDiff } from '/@/utils/diffUtils';
|
||||||
|
import { Session } from '/@/utils/storage';
|
||||||
import Editor from '/@/components/editor/index.vue';
|
import Editor from '/@/components/editor/index.vue';
|
||||||
import type { UploadFile, UploadUserFile, UploadRequestOptions } from 'element-plus';
|
import type { UploadFile, UploadUserFile, UploadRequestOptions } from 'element-plus';
|
||||||
|
|
||||||
@@ -495,6 +504,7 @@ interface RuleForm {
|
|||||||
onlineTime: string;
|
onlineTime: string;
|
||||||
offlineTime: string;
|
offlineTime: string;
|
||||||
unlimitedStock: boolean;
|
unlimitedStock: boolean;
|
||||||
|
stockMode: number;
|
||||||
physicalAssetConfig: {
|
physicalAssetConfig: {
|
||||||
shipping: {
|
shipping: {
|
||||||
deliveryMethod: string;
|
deliveryMethod: string;
|
||||||
@@ -568,6 +578,9 @@ const fileAddressPrefix = ref('');
|
|||||||
// 使用通用工具函数保存原始数据,用于最小化传参
|
// 使用通用工具函数保存原始数据,用于最小化传参
|
||||||
const assetFormDiff = createFormDiff<Record<string, any>>();
|
const assetFormDiff = createFormDiff<Record<string, any>>();
|
||||||
|
|
||||||
|
// 获取租户ID
|
||||||
|
const tenantId = ref(Session.get('userInfo')?.tenantId || '');
|
||||||
|
|
||||||
const formatImageUrl = (url?: string) => {
|
const formatImageUrl = (url?: string) => {
|
||||||
if (!url) return '';
|
if (!url) return '';
|
||||||
if (/^https?:\/\//i.test(url)) return url;
|
if (/^https?:\/\//i.test(url)) return url;
|
||||||
@@ -598,6 +611,7 @@ const getInitialForm = (): RuleForm => ({
|
|||||||
onlineTime: '',
|
onlineTime: '',
|
||||||
offlineTime: '',
|
offlineTime: '',
|
||||||
unlimitedStock: false,
|
unlimitedStock: false,
|
||||||
|
stockMode: 1,
|
||||||
physicalAssetConfig: {
|
physicalAssetConfig: {
|
||||||
shipping: {
|
shipping: {
|
||||||
deliveryMethod: 'express',
|
deliveryMethod: 'express',
|
||||||
@@ -903,6 +917,7 @@ const openDialog = (row?: any, edit?: boolean) => {
|
|||||||
ruleForm.onlineTime = data.onlineTime || '';
|
ruleForm.onlineTime = data.onlineTime || '';
|
||||||
ruleForm.offlineTime = data.offlineTime || '';
|
ruleForm.offlineTime = data.offlineTime || '';
|
||||||
ruleForm.unlimitedStock = data.unlimitedStock || false;
|
ruleForm.unlimitedStock = data.unlimitedStock || false;
|
||||||
|
ruleForm.stockMode = data.stockMode || 1;
|
||||||
|
|
||||||
// 主图预览 (支持 imageUrl 和 fileURL)
|
// 主图预览 (支持 imageUrl 和 fileURL)
|
||||||
const mainImg = data.imageUrl || data.fileURL;
|
const mainImg = data.imageUrl || data.fileURL;
|
||||||
@@ -1110,6 +1125,11 @@ const buildRequestBody = async (): Promise<any> => {
|
|||||||
// 库存类型
|
// 库存类型
|
||||||
body.unlimitedStock = ruleForm.unlimitedStock;
|
body.unlimitedStock = ruleForm.unlimitedStock;
|
||||||
|
|
||||||
|
// 库存存储模式(仅租户ID为1时提交)
|
||||||
|
if (tenantId.value === '1') {
|
||||||
|
body.stockMode = ruleForm.stockMode;
|
||||||
|
}
|
||||||
|
|
||||||
// 主图 (已在上传时直接赋值给 ruleForm.mainImage)
|
// 主图 (已在上传时直接赋值给 ruleForm.mainImage)
|
||||||
if (ruleForm.mainImage) {
|
if (ruleForm.mainImage) {
|
||||||
body.imageURL = ruleForm.mainImage;
|
body.imageURL = ruleForm.mainImage;
|
||||||
|
|||||||
@@ -81,7 +81,7 @@
|
|||||||
<el-table-column label="操作" width="180" align="center">
|
<el-table-column label="操作" width="180" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button size="small" text type="primary" @click="onEditSku(scope.row)">编辑</el-button>
|
<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>
|
<el-button size="small" text type="danger" @click="onDeleteSku(scope.row)">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -163,6 +163,50 @@
|
|||||||
<el-button type="primary" :loading="submitLoading" @click="onSubmitSku">确认</el-button>
|
<el-button type="primary" :loading="submitLoading" @click="onSubmitSku">确认</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</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>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -170,7 +214,7 @@
|
|||||||
import { ref, reactive } from 'vue';
|
import { ref, reactive } from 'vue';
|
||||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
|
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
|
||||||
import { ElMessageBox } 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 { createFormDiff } from '/@/utils/diffUtils';
|
||||||
import type { UploadRequestOptions, UploadUserFile } from 'element-plus';
|
import type { UploadRequestOptions, UploadUserFile } from 'element-plus';
|
||||||
|
|
||||||
@@ -185,6 +229,18 @@ interface AssetSpecAttr {
|
|||||||
dictType?: string;
|
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 dialogVisible = ref(false);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const submitLoading = ref(false);
|
const submitLoading = ref(false);
|
||||||
@@ -193,6 +249,16 @@ const skuFormVisible = ref(false);
|
|||||||
const isEditSku = ref(false);
|
const isEditSku = ref(false);
|
||||||
const editSkuId = ref('');
|
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 assetId = ref('');
|
||||||
const assetName = ref('');
|
const assetName = ref('');
|
||||||
const assetType = ref('');
|
const assetType = ref('');
|
||||||
@@ -479,9 +545,82 @@ const onDeleteSku = (row: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 生成库存
|
// 生成库存
|
||||||
const onGenerateStock = (row: any) => {
|
const onGenerateStock = async (row: any) => {
|
||||||
// TODO: 实现生成库存功能
|
currentSkuId.value = row.id;
|
||||||
ElMessage.info(`生成库存功能待实现,SKU: ${row.skuName}`);
|
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 表单
|
// 重置 SKU 表单
|
||||||
|
|||||||
Reference in New Issue
Block a user