|
|
@@ -188,6 +188,18 @@
|
|
|
<div class="form-item-tip">A10产品名称由系统自动拼接:品牌名 + 规格型号 + 产品分类(三级分类)+ 发票规格,无需手动填写</div>
|
|
|
</el-form-item>
|
|
|
|
|
|
+ <!-- 商品描述 -->
|
|
|
+ <el-form-item label="商品描述:">
|
|
|
+ <el-input
|
|
|
+ v-model="productForm.productDescription"
|
|
|
+ type="textarea"
|
|
|
+ :rows="3"
|
|
|
+ placeholder="请输入商品描述"
|
|
|
+ maxlength="500"
|
|
|
+ show-word-limit
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
<!-- 规格型号 和 UPC(69)条码 -->
|
|
|
<el-row :gutter="20">
|
|
|
<el-col :span="12">
|
|
|
@@ -235,7 +247,7 @@
|
|
|
</el-col>
|
|
|
|
|
|
<el-col :span="12">
|
|
|
- <el-form-item label="单位:">
|
|
|
+ <el-form-item label="单位:" required>
|
|
|
<el-select
|
|
|
v-model="productForm.unitId"
|
|
|
placeholder="请选择"
|
|
|
@@ -252,7 +264,7 @@
|
|
|
<!-- 税率编码 、税率 和 币种 -->
|
|
|
<el-row :gutter="20">
|
|
|
<el-col :span="12">
|
|
|
- <el-form-item label="税率编码:">
|
|
|
+ <el-form-item label="税率编码:" required>
|
|
|
<el-input
|
|
|
v-model="taxCodeNo"
|
|
|
placeholder="点击选择税率编码"
|
|
|
@@ -269,9 +281,7 @@
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="税率:" required>
|
|
|
- <el-select v-model="productForm.taxRate" placeholder="请选择税率" clearable class="w-full">
|
|
|
- <el-option v-for="option in taxRateOptions" :key="option.id" :label="`${option.taxrateNo},${option.taxrateName}`" :value="option.taxrate" />
|
|
|
- </el-select>
|
|
|
+ <el-input :model-value="formatTaxRateDisplay(productForm.taxRate)" placeholder="由税率编码带出" readonly disabled class="w-full" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
@@ -308,19 +318,32 @@
|
|
|
|
|
|
<!-- 促销标题 -->
|
|
|
<el-form-item label="促销标题:">
|
|
|
- <el-input v-model="productForm.promotionTitle" type="textarea" :rows="3" placeholder="请输入促销标题" maxlength="300" show-word-limit />
|
|
|
+ <el-input v-model="productForm.packagingSpec" type="textarea" :rows="3" placeholder="请输入促销标题" maxlength="300" show-word-limit />
|
|
|
</el-form-item>
|
|
|
|
|
|
- <!-- 商品简介 -->
|
|
|
- <el-form-item label="商品简介:">
|
|
|
- <el-input v-model="productForm.productDescription" type="textarea" :rows="3" placeholder="请输入商品简介" maxlength="500" show-word-limit />
|
|
|
+ <!-- 商品说明 -->
|
|
|
+ <el-form-item label="商品说明:">
|
|
|
+ <el-input
|
|
|
+ v-model="productForm.productExplain"
|
|
|
+ type="textarea"
|
|
|
+ :rows="3"
|
|
|
+ placeholder="请输入商品说明"
|
|
|
+ maxlength="500"
|
|
|
+ show-word-limit
|
|
|
+ />
|
|
|
</el-form-item>
|
|
|
|
|
|
<!-- 重量 和 体积 -->
|
|
|
<el-row :gutter="20">
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="商品重量:">
|
|
|
- <el-input v-model="productForm.productWeight" placeholder="0" maxlength="10" show-word-limit>
|
|
|
+ <el-input
|
|
|
+ v-model="productForm.productWeight"
|
|
|
+ placeholder="0"
|
|
|
+ maxlength="10"
|
|
|
+ show-word-limit
|
|
|
+ @input="handleWeightInput"
|
|
|
+ >
|
|
|
<template #append>
|
|
|
<el-select v-model="productForm.weightUnit" placeholder="请选择" style="width: 100px">
|
|
|
<el-option label="kg" value="kg" />
|
|
|
@@ -333,7 +356,13 @@
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="商品体积:">
|
|
|
- <el-input v-model="productForm.productVolume" placeholder="0" maxlength="10" show-word-limit>
|
|
|
+ <el-input
|
|
|
+ v-model="productForm.productVolume"
|
|
|
+ placeholder="0"
|
|
|
+ maxlength="10"
|
|
|
+ show-word-limit
|
|
|
+ @input="handleVolumeInput"
|
|
|
+ >
|
|
|
<template #append>
|
|
|
<el-select v-model="productForm.volumeUnit" placeholder="请选择" style="width: 80px">
|
|
|
<el-option label="m³" value="m3" />
|
|
|
@@ -347,12 +376,14 @@
|
|
|
</el-row>
|
|
|
|
|
|
<!-- 参考链接 -->
|
|
|
- <el-form-item label="参考链接">
|
|
|
+ <el-form-item label="参考链接" prop="referenceLink" required>
|
|
|
<el-input
|
|
|
+ ref="referenceLinkRef"
|
|
|
v-model="productForm.referenceLink"
|
|
|
type="textarea"
|
|
|
:rows="3"
|
|
|
placeholder="请输入参考链接"
|
|
|
+ @input="handleReferenceLinkInput"
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
|
|
|
@@ -389,17 +420,25 @@
|
|
|
<el-row :gutter="20">
|
|
|
<el-col :span="8">
|
|
|
<el-form-item label="市场价:" prop="marketPrice" required>
|
|
|
- <el-input v-model="productForm.marketPrice" type="number" placeholder="请输入市场价" :min="0" @blur="formatPrice('marketPrice')" />
|
|
|
+ <el-input
|
|
|
+ ref="marketPriceRef"
|
|
|
+ v-model="productForm.marketPrice"
|
|
|
+ type="number"
|
|
|
+ placeholder="请输入市场价"
|
|
|
+ :min="0"
|
|
|
+ @blur="handlePriceBlur('marketPrice')"
|
|
|
+ />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="8">
|
|
|
<el-form-item label="官网价:" prop="memberPrice" required>
|
|
|
<el-input
|
|
|
+ ref="memberPriceRef"
|
|
|
v-model="productForm.memberPrice"
|
|
|
type="number"
|
|
|
placeholder="请输入平台售价"
|
|
|
:min="0"
|
|
|
- @blur="formatPrice('memberPrice')"
|
|
|
+ @blur="handlePriceBlur('memberPrice')"
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
@@ -412,7 +451,7 @@
|
|
|
</el-col>
|
|
|
<el-col :span="8">
|
|
|
<el-form-item label="备注:">
|
|
|
- <span class="currency-text">市场价>官网价</span>
|
|
|
+ <span class="currency-text">供应价 < 官网价 < 市场价</span>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
@@ -434,26 +473,33 @@
|
|
|
type="number"
|
|
|
placeholder="请输入供应价"
|
|
|
:min="0"
|
|
|
- @blur="formatPrice('supplyPrice')"
|
|
|
+ @blur="handlePriceBlur('supplyPrice')"
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="8">
|
|
|
- <el-form-item label="供应有效时间:">
|
|
|
- <el-date-picker
|
|
|
- v-model="productForm.supplyValidityPeriod"
|
|
|
- type="date"
|
|
|
- placeholder="请选择供应有效时间"
|
|
|
- value-format="YYYY-MM-DD"
|
|
|
- class="w-full"
|
|
|
+ <el-form-item label="供应时间(天):">
|
|
|
+ <el-input
|
|
|
+ v-model="productForm.supplyDays"
|
|
|
+ type="number"
|
|
|
+ placeholder="请输入供应天数"
|
|
|
+ :min="1"
|
|
|
+ @blur="calcSupplyExpireTime"
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="8">
|
|
|
- <el-form-item label="是否包邮:">
|
|
|
+ <el-form-item label="到期时间:">
|
|
|
+ <el-input v-model="productForm.supplyValidityPeriod" disabled placeholder="自动计算" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-form-item label="一件代发:">
|
|
|
<el-radio-group v-model="productForm.supplyPostStatus">
|
|
|
- <el-radio :value="0">不包邮</el-radio>
|
|
|
- <el-radio :value="1">包邮</el-radio>
|
|
|
+ <el-radio :value="0">不支持</el-radio>
|
|
|
+ <el-radio :value="1">支持</el-radio>
|
|
|
</el-radio-group>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
@@ -559,14 +605,14 @@
|
|
|
<el-form ref="detailFormRef" :model="productForm" label-width="120px" class="product-info-form">
|
|
|
<!-- 商品主图 -->
|
|
|
<el-form-item label="商品主图:" required>
|
|
|
- <upload-image v-model="productForm.productImage" :limit="1" width="178px" height="178px" imageText="选择图片" />
|
|
|
- <div class="form-item-tip">从图片库选择,建议尺寸300*300px</div>
|
|
|
+ <image-upload v-model="productForm.productImage" :limit="1" />
|
|
|
+ <div class="form-item-tip">建议尺寸300*300px</div>
|
|
|
</el-form-item>
|
|
|
|
|
|
<!-- 商品轮播图 -->
|
|
|
<el-form-item label="商品轮播图:" required>
|
|
|
- <upload-image v-model="carouselImages" :limit="20" width="120px" height="120px" imageText="添加图片" />
|
|
|
- <div class="form-item-tip">从图片库选择,支持多选,建议尺寸300*300px</div>
|
|
|
+ <image-upload v-model="carouselImages" :limit="20" />
|
|
|
+ <div class="form-item-tip">支持多张上传,建议尺寸300*300px</div>
|
|
|
</el-form-item>
|
|
|
|
|
|
<!-- 商品详情 -->
|
|
|
@@ -714,9 +760,9 @@ import { useRoute, useRouter } from 'vue-router';
|
|
|
import { ElMessage } from 'element-plus';
|
|
|
import { Warning, ArrowRight, Check, Plus, CircleCheck, Search } from '@element-plus/icons-vue';
|
|
|
import Editor from '@/components/Editor/index.vue';
|
|
|
-import UploadImage from '@/components/upload-image/index.vue';
|
|
|
import TaxCodeSelect from '@/components/TaxCodeSelect/index.vue';
|
|
|
import { categoryTreeVO, CategoryVO } from '@/api/product/category/types';
|
|
|
+import { getCategory } from '@/api/product/category';
|
|
|
import { BrandVO } from '@/api/product/brand/types';
|
|
|
import { BaseForm } from '@/api/product/base/types';
|
|
|
import { ClassificationDiyForm } from '@/api/product/classificationDiy/types';
|
|
|
@@ -730,8 +776,7 @@ import {
|
|
|
categoryAttributeList,
|
|
|
getAfterSaleList,
|
|
|
getServiceList,
|
|
|
- getUnitList,
|
|
|
- getTaxRateList
|
|
|
+ getUnitList
|
|
|
} from '@/api/product/base';
|
|
|
import {
|
|
|
addBaseAudit,
|
|
|
@@ -754,6 +799,11 @@ const loading = ref(false);
|
|
|
const submitLoading = ref(false);
|
|
|
const productFormRef = ref();
|
|
|
|
|
|
+// 关键输入框 refs,用于校验失败时聚焦
|
|
|
+const referenceLinkRef = ref();
|
|
|
+const marketPriceRef = ref();
|
|
|
+const memberPriceRef = ref();
|
|
|
+
|
|
|
// 服务保障和安装服务的多选框
|
|
|
const serviceGuarantees = ref<(string | number)[]>([]);
|
|
|
const installationServices = ref<string[]>([]);
|
|
|
@@ -761,24 +811,37 @@ const installationServices = ref<string[]>([]);
|
|
|
// 商品详情选项卡
|
|
|
const activeDetailTab = ref('pc');
|
|
|
|
|
|
-// 轮播图URL数组(UI管理用)
|
|
|
-const carouselImages = ref<string[]>([]);
|
|
|
-
|
|
|
-// 税率选项
|
|
|
-const taxRateOptions = ref<any[]>([]);
|
|
|
+// 轮播图(逗号分隔的 ossId 字符串,由 ImageUpload 管理)
|
|
|
+const carouselImages = ref<string>('');
|
|
|
|
|
|
// 税率编码选择组件
|
|
|
const taxCodeSelectRef = ref();
|
|
|
// 已选的税率编码(显示用)
|
|
|
const taxCodeNo = ref('');
|
|
|
|
|
|
+// 格式化税率显示(小数转百分比)
|
|
|
+const formatTaxRateDisplay = (val: any): string => {
|
|
|
+ if (val === null || val === undefined || val === '') return '';
|
|
|
+ const num = Number(val);
|
|
|
+ if (isNaN(num)) return String(val);
|
|
|
+ return `${Math.round(num * 100)}%`;
|
|
|
+};
|
|
|
+
|
|
|
// 处理税率编码选择
|
|
|
const handleTaxCodeSelect = async (row: any) => {
|
|
|
(productForm as any).taxationId = row.id;
|
|
|
+ // 选择税率编码时自动带出税率
|
|
|
+ if (row.taxrate !== undefined && row.taxrate !== null) {
|
|
|
+ productForm.taxRate = Number(row.taxrate);
|
|
|
+ }
|
|
|
try {
|
|
|
const taxRes = await getTaxCode(row.id);
|
|
|
if (taxRes.data) {
|
|
|
taxCodeNo.value = `${taxRes.data.taxationNo},${taxRes.data.name}`;
|
|
|
+ // 使用详情接口返回的税率值(更准确)
|
|
|
+ if ((taxRes.data as any).taxrate !== undefined && (taxRes.data as any).taxrate !== null) {
|
|
|
+ productForm.taxRate = Number((taxRes.data as any).taxrate);
|
|
|
+ }
|
|
|
} else {
|
|
|
taxCodeNo.value = row.taxationNo ? `${row.taxationNo},${row.name}` : (row.name || '');
|
|
|
}
|
|
|
@@ -966,10 +1029,11 @@ const productForm = reactive<BaseForm>({
|
|
|
purchasingPrice: undefined,
|
|
|
maxPurchasePrice: undefined,
|
|
|
supplyPrice: undefined,
|
|
|
+ supplyDays: undefined,
|
|
|
supplyValidityPeriod: undefined,
|
|
|
supplyPostStatus: undefined,
|
|
|
- productNature: '1',
|
|
|
- purchasingPersonnel: '1',
|
|
|
+ productNature: '',
|
|
|
+ purchasingPersonnel: '',
|
|
|
pcDetail: undefined,
|
|
|
mobileDetail: undefined,
|
|
|
taxRate: undefined,
|
|
|
@@ -984,9 +1048,23 @@ const productRules = {
|
|
|
itemName: [{ required: true, message: '商品名称不能为空', trigger: 'blur' }],
|
|
|
brandId: [{ required: true, message: '商品品牌不能为空', trigger: 'change' }],
|
|
|
marketPrice: [{ required: true, message: '市场价不能为空', trigger: 'blur' }],
|
|
|
- memberPrice: [{ required: true, message: '平台售价不能为空', trigger: 'blur' }],
|
|
|
+ memberPrice: [{ required: true, message: '官网价不能为空', trigger: 'blur' }],
|
|
|
minSellingPrice: [{ required: true, message: '最低售价不能为空', trigger: 'blur' }],
|
|
|
- // purchasingPrice: [{ required: true, message: '采购价不能为空', trigger: 'blur' }],
|
|
|
+ purchasingPrice: [{ required: true, message: '采购价不能为空', trigger: 'blur' }],
|
|
|
+ maxPurchasePrice: [{ required: true, message: '最高采购价不能为空', trigger: 'blur' }],
|
|
|
+ referenceLink: [
|
|
|
+ { required: true, message: '参考链接不能为空', trigger: 'blur' },
|
|
|
+ {
|
|
|
+ validator: (_rule: any, value: any, callback: any) => {
|
|
|
+ if (value && /\u3000/.test(String(value))) {
|
|
|
+ callback(new Error('参考链接不能包含中文空格'));
|
|
|
+ } else {
|
|
|
+ callback();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ trigger: 'blur'
|
|
|
+ }
|
|
|
+ ],
|
|
|
productNature: [{ required: true, message: '产品经理不能为空', trigger: 'change' }],
|
|
|
purchasingPersonnel: [{ required: true, message: '采购人员不能为空', trigger: 'change' }],
|
|
|
taxRate: [{ required: true, message: '税率不能为空', trigger: 'change' }],
|
|
|
@@ -1091,6 +1169,39 @@ const handleLevel3Search = async (keyword: string) => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+// 从三级分类联动填充采购信息
|
|
|
+const fillPurchaseFromCategory = async (categoryId: string | number, category?: CategoryVO) => {
|
|
|
+ try {
|
|
|
+ let detail: CategoryVO | undefined = category;
|
|
|
+ if (!detail) {
|
|
|
+ const res = await getCategory(categoryId);
|
|
|
+ detail = res.data;
|
|
|
+ }
|
|
|
+ if (!detail) return;
|
|
|
+ (productForm as any).purchaseNo = (detail as any).purchaseNo || undefined;
|
|
|
+ (productForm as any).purchaseName = (detail as any).purchaseName || undefined;
|
|
|
+ (productForm as any).purchaseManagerNo = (detail as any).purchaseManagerNo || undefined;
|
|
|
+ (productForm as any).purchaseManagerName = (detail as any).purchaseManagerName || undefined;
|
|
|
+
|
|
|
+ // 联动产品经理下拉:通过 purchaseManagerNo 匹配 staffCode
|
|
|
+ if ((detail as any).purchaseManagerNo) {
|
|
|
+ const matchedManager = staffOptions.value.find((s) => s.staffCode === (detail as any).purchaseManagerNo);
|
|
|
+ if (matchedManager) {
|
|
|
+ productForm.productNature = String(matchedManager.staffId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 联动采购人员下拉:通过 purchaseNo 匹配 staffCode
|
|
|
+ if ((detail as any).purchaseNo) {
|
|
|
+ const matchedPurchase = staffOptions.value.find((s) => s.staffCode === (detail as any).purchaseNo);
|
|
|
+ if (matchedPurchase) {
|
|
|
+ productForm.purchasingPersonnel = String(matchedPurchase.staffId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('获取三级分类采购信息失败:', e);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
// 选择三级分类搜索结果后,自动在树中定位
|
|
|
const handleLevel3SearchSelect = async (categoryId: string | number) => {
|
|
|
if (!categoryId) return;
|
|
|
@@ -1123,10 +1234,12 @@ const handleLevel3SearchSelect = async (categoryId: string | number) => {
|
|
|
if (level3Node) {
|
|
|
categoryForm.bottomCategoryId = level3Node.id;
|
|
|
selectedLevel3Name.value = level3Node.label;
|
|
|
+ await fillPurchaseFromCategory(level3Node.id, selectedCategory);
|
|
|
await loadCategoryAttributes(level3Node.id);
|
|
|
} else {
|
|
|
categoryForm.bottomCategoryId = selectedCategory.id;
|
|
|
selectedLevel3Name.value = selectedCategory.categoryName;
|
|
|
+ await fillPurchaseFromCategory(selectedCategory.id, selectedCategory);
|
|
|
await loadCategoryAttributes(selectedCategory.id);
|
|
|
}
|
|
|
|
|
|
@@ -1176,6 +1289,9 @@ const selectLevel3 = async (item: categoryTreeVO) => {
|
|
|
categoryForm.bottomCategoryId = item.id;
|
|
|
selectedLevel3Name.value = item.label;
|
|
|
|
|
|
+ // 联动填充产品经理与采购人员(从三级分类详情获取)
|
|
|
+ await fillPurchaseFromCategory(item.id);
|
|
|
+
|
|
|
// 加载该分类下的属性列表
|
|
|
await loadCategoryAttributes(item.id);
|
|
|
};
|
|
|
@@ -1257,7 +1373,7 @@ const handleSubmit = async () => {
|
|
|
submitLoading.value = false;
|
|
|
return;
|
|
|
}
|
|
|
- if (!carouselImages.value || carouselImages.value.length === 0) {
|
|
|
+ if (!carouselImages.value) {
|
|
|
ElMessage.warning('请上传商品轮播图');
|
|
|
submitLoading.value = false;
|
|
|
return;
|
|
|
@@ -1268,18 +1384,30 @@ const handleSubmit = async () => {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // 校验价格关系:市场价 > 官网价 > 最低售价
|
|
|
- const midRange = parseFloat(String(productForm.marketPrice));
|
|
|
- const standard = parseFloat(String(productForm.memberPrice));
|
|
|
- const certificate = parseFloat(String(productForm.minSellingPrice));
|
|
|
- if (!isNaN(midRange) && !isNaN(standard) && !isNaN(certificate)) {
|
|
|
- if (!(midRange > standard)) {
|
|
|
- ElMessage.warning('市场价必须大于官网价');
|
|
|
+ // 校验价格关系:供应价 < 官网价 < 市场价(任一字段为空时跳过对应校验)
|
|
|
+ const marketPriceNum = parseFloat(String(productForm.marketPrice));
|
|
|
+ const memberPriceNum = parseFloat(String(productForm.memberPrice));
|
|
|
+ const supplyPriceNum = parseFloat(String(productForm.supplyPrice));
|
|
|
+ // 官网价 < 市场价
|
|
|
+ if (!isNaN(memberPriceNum) && !isNaN(marketPriceNum)) {
|
|
|
+ if (!(memberPriceNum < marketPriceNum)) {
|
|
|
+ ElMessage.warning('官网价必须小于市场价');
|
|
|
submitLoading.value = false;
|
|
|
return;
|
|
|
}
|
|
|
- if (!(standard > certificate)) {
|
|
|
- ElMessage.warning('官网价必须大于最低售价');
|
|
|
+ }
|
|
|
+ // 供应价 < 官网价
|
|
|
+ if (!isNaN(supplyPriceNum) && !isNaN(memberPriceNum)) {
|
|
|
+ if (!(supplyPriceNum < memberPriceNum)) {
|
|
|
+ ElMessage.warning('供应价必须小于官网价');
|
|
|
+ submitLoading.value = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 供应价 < 市场价(当官网价为空但供应价与市场价都存在时也校验)
|
|
|
+ if (!isNaN(supplyPriceNum) && !isNaN(marketPriceNum)) {
|
|
|
+ if (!(supplyPriceNum < marketPriceNum)) {
|
|
|
+ ElMessage.warning('供应价必须小于市场价');
|
|
|
submitLoading.value = false;
|
|
|
return;
|
|
|
}
|
|
|
@@ -1290,8 +1418,8 @@ const handleSubmit = async () => {
|
|
|
...productForm,
|
|
|
// 将服务保障ID数组转换为逗号分隔字符串
|
|
|
serviceGuarantee: serviceGuarantees.value.map((id) => String(id)).join(','),
|
|
|
- // 轮播图URL逗号分隔
|
|
|
- imageUrl: carouselImages.value.join(','),
|
|
|
+ // 轮播图(ImageUpload 已是逗号分隔字符串)
|
|
|
+ imageUrl: carouselImages.value,
|
|
|
// 将商品属性值转换为JSON字符串
|
|
|
attributesList: JSON.stringify(productAttributesValues.value),
|
|
|
isCustomize: customForm.isCustomize ? 1 : 0,
|
|
|
@@ -1343,6 +1471,41 @@ const handleUpcInput = () => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+// 商品重量只允许数字和小数点(过滤中文及其他非法字符)
|
|
|
+const handleWeightInput = (val: string) => {
|
|
|
+ if (val !== undefined && val !== null) {
|
|
|
+ let v = String(val).replace(/[^\d.]/g, '');
|
|
|
+ // 只保留第一个小数点
|
|
|
+ const firstDot = v.indexOf('.');
|
|
|
+ if (firstDot !== -1) {
|
|
|
+ v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, '');
|
|
|
+ }
|
|
|
+ productForm.productWeight = v;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 商品体积只允许数字和小数点
|
|
|
+const handleVolumeInput = (val: string) => {
|
|
|
+ if (val !== undefined && val !== null) {
|
|
|
+ let v = String(val).replace(/[^\d.]/g, '');
|
|
|
+ const firstDot = v.indexOf('.');
|
|
|
+ if (firstDot !== -1) {
|
|
|
+ v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, '');
|
|
|
+ }
|
|
|
+ productForm.productVolume = v;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 参考链接过滤中文空格(全角空格 U+3000)
|
|
|
+const handleReferenceLinkInput = (val: string) => {
|
|
|
+ if (val !== undefined && val !== null) {
|
|
|
+ const filtered = String(val).replace(/\u3000/g, '');
|
|
|
+ if (filtered !== val) {
|
|
|
+ productForm.referenceLink = filtered;
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
// 销量人气只允许输入整数
|
|
|
const handleSalesVolumeInput = (val: string) => {
|
|
|
if (val !== undefined && val !== null && val !== '') {
|
|
|
@@ -1376,6 +1539,44 @@ const formatPrice = (field: string) => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+// 价格失焦校验:供应价 < 官网价 < 市场价
|
|
|
+const handlePriceBlur = (field: string) => {
|
|
|
+ formatPrice(field);
|
|
|
+ const supplyPrice = parseFloat(String(productForm.supplyPrice));
|
|
|
+ const memberPrice = parseFloat(String(productForm.memberPrice));
|
|
|
+ const marketPrice = parseFloat(String(productForm.marketPrice));
|
|
|
+
|
|
|
+ if (field === 'supplyPrice' && !isNaN(supplyPrice) && !isNaN(memberPrice) && supplyPrice >= memberPrice) {
|
|
|
+ ElMessage.warning('供应价必须小于官网价');
|
|
|
+ }
|
|
|
+ if (field === 'memberPrice') {
|
|
|
+ if (!isNaN(memberPrice) && !isNaN(supplyPrice) && memberPrice <= supplyPrice) {
|
|
|
+ ElMessage.warning('官网价必须大于供应价');
|
|
|
+ }
|
|
|
+ if (!isNaN(memberPrice) && !isNaN(marketPrice) && memberPrice >= marketPrice) {
|
|
|
+ ElMessage.warning('官网价必须小于市场价');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (field === 'marketPrice' && !isNaN(marketPrice) && !isNaN(memberPrice) && marketPrice <= memberPrice) {
|
|
|
+ ElMessage.warning('市场价必须大于官网价');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 根据供应天数自动计算到期时间
|
|
|
+const calcSupplyExpireTime = () => {
|
|
|
+ const days = parseInt(String(productForm.supplyDays));
|
|
|
+ if (!isNaN(days) && days > 0) {
|
|
|
+ const now = new Date();
|
|
|
+ now.setDate(now.getDate() + days);
|
|
|
+ const y = now.getFullYear();
|
|
|
+ const m = String(now.getMonth() + 1).padStart(2, '0');
|
|
|
+ const d = String(now.getDate()).padStart(2, '0');
|
|
|
+ productForm.supplyValidityPeriod = `${y}-${m}-${d}`;
|
|
|
+ } else {
|
|
|
+ productForm.supplyValidityPeriod = undefined;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
// 格式化表格行中的价格为两位小数(不允许负数)
|
|
|
const formatRowPrice = (row: any, field: string) => {
|
|
|
const val = row[field];
|
|
|
@@ -1512,16 +1713,6 @@ const getStaffOptions = async () => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-// 获取税率列表
|
|
|
-const getTaxRateOptions = async () => {
|
|
|
- try {
|
|
|
- const res = await getTaxRateList();
|
|
|
- taxRateOptions.value = res.rows || [];
|
|
|
- } catch (error) {
|
|
|
- console.error('获取税率列表失败:', error);
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
// 加载分类属性列表
|
|
|
const loadCategoryAttributes = async (categoryId: string | number) => {
|
|
|
try {
|
|
|
@@ -1608,16 +1799,9 @@ const loadProductDetail = async () => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 回显税率 - 在税率选项中查找匹配的值(处理浮点数精度问题)
|
|
|
+ // 回显税率(直接使用接口返回值,由税率编码带出,无需下拉匹配)
|
|
|
if (res.data.taxRate !== undefined && res.data.taxRate !== null) {
|
|
|
- const apiTaxRate = Number(res.data.taxRate);
|
|
|
- // 使用精度容差比较浮点数
|
|
|
- const matchedOption = taxRateOptions.value.find((opt) => Math.abs(Number(opt.taxrate) - apiTaxRate) < 0.0001);
|
|
|
- if (matchedOption) {
|
|
|
- productForm.taxRate = matchedOption.taxrate;
|
|
|
- } else {
|
|
|
- productForm.taxRate = apiTaxRate;
|
|
|
- }
|
|
|
+ productForm.taxRate = Number(res.data.taxRate);
|
|
|
}
|
|
|
|
|
|
// 回显单位 - 确保类型与下拉选项的id一致(数字类型)
|
|
|
@@ -1644,15 +1828,11 @@ const loadProductDetail = async () => {
|
|
|
|
|
|
// 回显售后服务 - 确保类型与下拉选项的id一致(数字类型)
|
|
|
if (res.data.afterSalesService !== undefined && res.data.afterSalesService !== null) {
|
|
|
- productForm.afterSalesService = String(res.data.afterSalesService);
|
|
|
+ productForm.afterSalesService = Number(res.data.afterSalesService);
|
|
|
}
|
|
|
|
|
|
// 回显轮播图
|
|
|
- if (res.data.imageUrl) {
|
|
|
- carouselImages.value = res.data.imageUrl.split(',').filter((url: string) => url.trim());
|
|
|
- } else {
|
|
|
- carouselImages.value = [];
|
|
|
- }
|
|
|
+ carouselImages.value = res.data.imageUrl || '';
|
|
|
|
|
|
// 回显分类选择
|
|
|
categoryForm.topCategoryId = res.data.topCategoryId;
|
|
|
@@ -1797,7 +1977,6 @@ onMounted(async () => {
|
|
|
await getUnitOptions();
|
|
|
await getAfterSalesOptions();
|
|
|
await getServiceGuaranteeOptions();
|
|
|
- await getTaxRateOptions();
|
|
|
// 先加载商品详情(如果是编辑模式)
|
|
|
await loadProductDetail();
|
|
|
// 再加载下拉选项,这样如果详情中没有值,会自动设置第一个
|