在SKU管理中新增规格单位和规格数量字段,优化时间显示格式并调整列宽布局,同时修改分类属性类型接口路径以匹配后端枚举接口规范
This commit is contained in:
@@ -154,3 +154,12 @@ export function deleteAssetSku(id: string) {
|
|||||||
params: { id },
|
params: { id },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取规格单位选项
|
||||||
|
export function getSpecsUnitOptions(assetType: string) {
|
||||||
|
return newService({
|
||||||
|
url: '/assets/enum/getSpecsUnit',
|
||||||
|
method: 'get',
|
||||||
|
params: { assetType },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export function getCategory(id: string) {
|
|||||||
// 获取属性类型选项
|
// 获取属性类型选项
|
||||||
export function getCategoryAttrTypeOptions() {
|
export function getCategoryAttrTypeOptions() {
|
||||||
return newService({
|
return newService({
|
||||||
url: '/assets/category/getCategoryAttrTypeOptions',
|
url: '/assets/enum/getCategoryAttrType',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog v-model="dialogVisible" :title="`SKU管理 - ${assetName}`" width="1000px" :close-on-click-modal="false" @close="onClose">
|
<el-dialog v-model="dialogVisible" :title="`SKU管理 - ${assetName}`" width="1200px" :close-on-click-modal="false" @close="onClose">
|
||||||
<!-- 搜索区域 -->
|
<!-- 搜索区域 -->
|
||||||
<div class="sku-search mb15">
|
<div class="sku-search mb15">
|
||||||
<el-button type="primary" @click="onOpenAddSku">
|
<el-button type="primary" @click="onOpenAddSku">
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<!-- SKU 列表 -->
|
<!-- SKU 列表 -->
|
||||||
<el-table :data="tableData" v-loading="loading" border style="width: 100%">
|
<el-table :data="tableData" v-loading="loading" border style="width: 100%">
|
||||||
<el-table-column prop="skuName" label="SKU名称" min-width="120" show-overflow-tooltip />
|
<el-table-column prop="skuName" label="SKU名称" min-width="120" show-overflow-tooltip />
|
||||||
<el-table-column prop="specValues" label="规格属性" min-width="150">
|
<el-table-column prop="specValues" label="规格属性" min-width="140">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span v-if="scope.row.specValues && Array.isArray(scope.row.specValues) && scope.row.specValues.length > 0">
|
<span v-if="scope.row.specValues && Array.isArray(scope.row.specValues) && scope.row.specValues.length > 0">
|
||||||
<el-tag v-for="(item, idx) in scope.row.specValues" :key="idx" size="small" style="margin-right: 4px">
|
<el-tag v-for="(item, idx) in scope.row.specValues" :key="idx" size="small" style="margin-right: 4px">
|
||||||
@@ -36,7 +36,17 @@
|
|||||||
<span v-else>-</span>
|
<span v-else>-</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="price" label="价格" width="100" align="center">
|
<el-table-column prop="specsUnit" label="规格单位" width="80" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ scope.row.specsUnit?.value || getSpecsUnitLabel(scope.row.specsUnit) || '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="specsCount" label="规格数量" width="80" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ scope.row.specsCount || '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="price" label="价格" width="90" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
¥{{ (scope.row.price / 100).toFixed(2) }}
|
¥{{ (scope.row.price / 100).toFixed(2) }}
|
||||||
</template>
|
</template>
|
||||||
@@ -53,9 +63,21 @@
|
|||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="sort" label="排序" width="80" align="center" />
|
<el-table-column prop="sort" label="排序" width="60" align="center" />
|
||||||
<el-table-column prop="createdAt" label="创建时间" width="160" align="center" />
|
<el-table-column prop="createdAt" label="创建时间" width="100" align="center">
|
||||||
<el-table-column prop="updatedAt" label="修改时间" width="160" align="center" />
|
<template #default="scope">
|
||||||
|
<el-tooltip :content="scope.row.createdAt" placement="top">
|
||||||
|
<span>{{ formatShortTime(scope.row.createdAt) }}</span>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="updatedAt" label="修改时间" width="100" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tooltip :content="scope.row.updatedAt" placement="top">
|
||||||
|
<span>{{ formatShortTime(scope.row.updatedAt) }}</span>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="120" align="center">
|
<el-table-column label="操作" width="120" 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>
|
||||||
@@ -86,7 +108,7 @@
|
|||||||
<el-form-item label="SKU名称" prop="skuName">
|
<el-form-item label="SKU名称" prop="skuName">
|
||||||
<el-input v-model="skuForm.skuName" placeholder="请输入SKU名称" />
|
<el-input v-model="skuForm.skuName" placeholder="请输入SKU名称" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="规格属性" v-if="assetSpecAttrs.length > 0">
|
<el-form-item v-if="assetSpecAttrs.length > 0">
|
||||||
<div class="spec-values-container">
|
<div class="spec-values-container">
|
||||||
<div v-for="attr in assetSpecAttrs" :key="attr.name" class="spec-item">
|
<div v-for="attr in assetSpecAttrs" :key="attr.name" class="spec-item">
|
||||||
<span class="spec-label">{{ attr.name }}:</span>
|
<span class="spec-label">{{ attr.name }}:</span>
|
||||||
@@ -97,6 +119,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="规格单位" prop="specsUnit">
|
||||||
|
<el-select v-model="skuForm.specsUnit" placeholder="请选择规格单位" style="width: 150px" clearable>
|
||||||
|
<el-option v-for="opt in specsUnitOptions" :key="opt.key" :label="opt.value" :value="opt.key" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="规格数量" prop="specsCount">
|
||||||
|
<el-input-number v-model="skuForm.specsCount" :min="1" controls-position="right" />
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="价格" prop="price">
|
<el-form-item label="价格" prop="price">
|
||||||
<el-input-number v-model="skuForm.price" :min="0" :precision="2" :step="0.01" controls-position="right" />
|
<el-input-number v-model="skuForm.price" :min="0" :precision="2" :step="0.01" controls-position="right" />
|
||||||
<span style="margin-left: 8px">元</span>
|
<span style="margin-left: 8px">元</span>
|
||||||
@@ -143,7 +173,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 } from '/@/api/assets/asset';
|
import { listAssetSkus, createAssetSku, updateAssetSku, deleteAssetSku, getAssetSku, getAsset, uploadAssetImage, getSpecsUnitOptions } from '/@/api/assets/asset';
|
||||||
import type { UploadRequestOptions, UploadUserFile } from 'element-plus';
|
import type { UploadRequestOptions, UploadUserFile } from 'element-plus';
|
||||||
|
|
||||||
interface SpecValueItem {
|
interface SpecValueItem {
|
||||||
@@ -167,9 +197,11 @@ const editSkuId = ref('');
|
|||||||
|
|
||||||
const assetId = ref('');
|
const assetId = ref('');
|
||||||
const assetName = ref('');
|
const assetName = ref('');
|
||||||
|
const assetType = ref('');
|
||||||
const assetSpecAttrs = ref<AssetSpecAttr[]>([]);
|
const assetSpecAttrs = ref<AssetSpecAttr[]>([]);
|
||||||
const fileAddressPrefix = ref('');
|
const fileAddressPrefix = ref('');
|
||||||
const skuImagePreview = ref('');
|
const skuImagePreview = ref('');
|
||||||
|
const specsUnitOptions = ref<{ key: string; value: string }[]>([]);
|
||||||
const tableData = ref<any[]>([]);
|
const tableData = ref<any[]>([]);
|
||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
|
|
||||||
@@ -190,6 +222,8 @@ const skuForm = reactive({
|
|||||||
sort: 0,
|
sort: 0,
|
||||||
imageUrl: '',
|
imageUrl: '',
|
||||||
description: '',
|
description: '',
|
||||||
|
specsUnit: '',
|
||||||
|
specsCount: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
const specValuesList = ref<SpecValueItem[]>([{ key: '', value: '' }]);
|
const specValuesList = ref<SpecValueItem[]>([{ key: '', value: '' }]);
|
||||||
@@ -197,6 +231,11 @@ const specValuesMap = reactive<Record<string, string>>({});
|
|||||||
|
|
||||||
const skuRules: FormRules = {
|
const skuRules: FormRules = {
|
||||||
skuName: [{ required: true, message: '请输入SKU名称', trigger: 'blur' }],
|
skuName: [{ required: true, message: '请输入SKU名称', trigger: 'blur' }],
|
||||||
|
specsUnit: [{ required: true, message: '请选择规格单位', trigger: 'change' }],
|
||||||
|
specsCount: [
|
||||||
|
{ required: true, message: '请输入规格数量', trigger: 'blur' },
|
||||||
|
{ type: 'number', min: 1, message: '规格数量必须大于0', trigger: 'blur' }
|
||||||
|
],
|
||||||
price: [
|
price: [
|
||||||
{ required: true, message: '请输入价格', trigger: 'blur' },
|
{ required: true, message: '请输入价格', trigger: 'blur' },
|
||||||
{ type: 'number', min: 0.01, message: '价格必须大于0', trigger: 'blur' }
|
{ type: 'number', min: 0.01, message: '价格必须大于0', trigger: 'blur' }
|
||||||
@@ -221,13 +260,41 @@ const skuRules: FormRules = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 打开弹窗
|
// 打开弹窗
|
||||||
const openDialog = (row: { id: string; name: string }) => {
|
const openDialog = (row: { id: string; name: string; type?: string }) => {
|
||||||
assetId.value = row.id;
|
assetId.value = row.id;
|
||||||
assetName.value = row.name;
|
assetName.value = row.name;
|
||||||
|
assetType.value = row.type || '';
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
resetQuery();
|
resetQuery();
|
||||||
getSkuList();
|
getSkuList();
|
||||||
fetchAssetSpecAttrs();
|
fetchAssetSpecAttrs();
|
||||||
|
fetchSpecsUnitOptions();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取规格单位选项
|
||||||
|
const fetchSpecsUnitOptions = () => {
|
||||||
|
if (!assetType.value) return;
|
||||||
|
getSpecsUnitOptions(assetType.value)
|
||||||
|
.then((res: any) => {
|
||||||
|
specsUnitOptions.value = res.data?.options || [];
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
specsUnitOptions.value = [];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据规格单位 key 获取对应的 label
|
||||||
|
const getSpecsUnitLabel = (key: string) => {
|
||||||
|
if (!key) return '';
|
||||||
|
const option = specsUnitOptions.value.find((opt) => opt.key === key);
|
||||||
|
return option?.value || key;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化时间为短格式(只显示日期)
|
||||||
|
const formatShortTime = (time: string) => {
|
||||||
|
if (!time) return '-';
|
||||||
|
// 只取日期部分 YYYY-MM-DD
|
||||||
|
return time.substring(0, 10);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取资产规格属性(只获取多选类型)
|
// 获取资产规格属性(只获取多选类型)
|
||||||
@@ -328,6 +395,13 @@ const onEditSku = async (row: any) => {
|
|||||||
skuForm.sort = data.sort || 0;
|
skuForm.sort = data.sort || 0;
|
||||||
skuForm.imageUrl = data.imageUrl || '';
|
skuForm.imageUrl = data.imageUrl || '';
|
||||||
skuForm.description = data.description || '';
|
skuForm.description = data.description || '';
|
||||||
|
// specsUnit 可能是对象格式 { key, value } 或字符串
|
||||||
|
if (data.specsUnit && typeof data.specsUnit === 'object') {
|
||||||
|
skuForm.specsUnit = data.specsUnit.key || '';
|
||||||
|
} else {
|
||||||
|
skuForm.specsUnit = data.specsUnit || '';
|
||||||
|
}
|
||||||
|
skuForm.specsCount = data.specsCount || 1;
|
||||||
// 图片预览回显
|
// 图片预览回显
|
||||||
if (data.imageUrl) {
|
if (data.imageUrl) {
|
||||||
skuImagePreview.value = formatImageUrl(data.imageUrl);
|
skuImagePreview.value = formatImageUrl(data.imageUrl);
|
||||||
@@ -396,6 +470,8 @@ const resetSkuForm = () => {
|
|||||||
skuForm.sort = 0;
|
skuForm.sort = 0;
|
||||||
skuForm.imageUrl = '';
|
skuForm.imageUrl = '';
|
||||||
skuForm.description = '';
|
skuForm.description = '';
|
||||||
|
skuForm.specsUnit = '';
|
||||||
|
skuForm.specsCount = 1;
|
||||||
specValuesList.value = [{ key: '', value: '' }];
|
specValuesList.value = [{ key: '', value: '' }];
|
||||||
// 清空 specValuesMap
|
// 清空 specValuesMap
|
||||||
Object.keys(specValuesMap).forEach((key) => delete specValuesMap[key]);
|
Object.keys(specValuesMap).forEach((key) => delete specValuesMap[key]);
|
||||||
@@ -522,6 +598,16 @@ const onSubmitSku = async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 构建 specsUnit 对象格式
|
||||||
|
let specsUnitObj = undefined;
|
||||||
|
if (skuForm.specsUnit) {
|
||||||
|
const unitOption = specsUnitOptions.value.find((opt) => opt.key === skuForm.specsUnit);
|
||||||
|
specsUnitObj = {
|
||||||
|
key: skuForm.specsUnit,
|
||||||
|
value: unitOption?.value || skuForm.specsUnit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
assetId: assetId.value,
|
assetId: assetId.value,
|
||||||
assetName: assetName.value,
|
assetName: assetName.value,
|
||||||
@@ -533,7 +619,9 @@ const onSubmitSku = async () => {
|
|||||||
stock: skuForm.stock,
|
stock: skuForm.stock,
|
||||||
sort: skuForm.sort,
|
sort: skuForm.sort,
|
||||||
status: skuForm.status,
|
status: skuForm.status,
|
||||||
description:skuForm.description,
|
description: skuForm.description,
|
||||||
|
specsUnit: specsUnitObj,
|
||||||
|
specsCount: skuForm.specsCount || undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user