Files
lilishop-ui/seller/src/views/goods/goods-seller/goodsOperationSec.vue
田香琪 3ca9aad9be feat(editor): 增强 TinyMCE 编辑器功能与内容同步
- 提交前同步 PC 与移动端编辑器内容
- 增加编辑器未初始化时的待写入内容处理
- 优化内容读写方法,保证数据绑定准确
- 重构事件处理,避免初始化时覆盖 v-model 值
2026-08-24 12:46:39 +08:00

2764 lines
94 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div>
<el-dialog v-model="visible" title="预览图片">
<img v-if="visible" :src="previewPicture" style="width: 100%">
</el-dialog>
<div class="content-goods-publish">
<el-form ref="baseInfoForm" label-width="120px" :model="baseInfoForm" :rules="baseInfoFormRule">
<div class="base-info-item">
<h4>基本信息</h4>
<div class="form-item-view">
<el-form-item label="商品类型" prop="goodsType">
<el-select
v-model="baseInfoForm.goodsType"
placeholder="请选择商品类型"
style="width: 260px"
popper-class="goods-type-select-popper"
@change="handleGoodsTypeChange"
>
<el-option
v-for="item in goodsTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
>
<div class="goods-type-option">
<span class="goods-type-option-label">{{ item.label }}</span>
<span class="goods-type-option-desc">{{ item.desc }}</span>
</div>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="商品分类" prop="categoryPath">
<el-cascader
v-model="selectedCategory"
:options="categoryList"
placeholder="请选择商品分类"
clearable
style="width: 260px"
@change="handleCategoryChange"
/>
</el-form-item>
<el-form-item label="商品名称" prop="goodsName">
<el-input v-model="baseInfoForm.goodsName" clearable placeholder="商品名称" style="width: 260px" type="text" />
</el-form-item>
<el-form-item label="商品价格" prop="price">
<el-input v-model="baseInfoForm.price" clearable placeholder="商品价格" style="width: 260px" type="text" />
</el-form-item>
<el-form-item label="商品卖点" prop="sellingPoint">
<el-input v-model="baseInfoForm.sellingPoint" :rows="4" style="width: 260px" type="textarea" />
</el-form-item>
<el-form-item label="商品品牌" prop="brandId">
<el-select
v-model="baseInfoForm.brandId"
filterable
clearable
placeholder="请选择品牌"
style="width: 200px"
>
<el-option v-for="item in brandList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-button class="refresh-icon" circle link @click="refresh('brand')">
<el-icon><Refresh /></el-icon>
</el-button>
</el-form-item>
<el-form-item class="form-item-view-el" label="计量单位" prop="goodsUnit">
<el-select v-model="baseInfoForm.goodsUnit" style="width: 100px">
<el-option v-for="(item, index) in goodsUnitList" :key="index" :label="item" :value="item" />
</el-select>
<el-button class="refresh-icon" circle link @click="refresh('goodsUnit')">
<el-icon><Refresh /></el-icon>
</el-button>
</el-form-item>
<el-form-item class="form-item-view-el" label="销售模式" prop="salesModel">
<el-radio-group
v-if="!isVirtualLikeGoods"
v-model="baseInfoForm.salesModel"
@change="handleSalesModeChange"
>
<el-radio-button value="RETAIL">零售型</el-radio-button>
<el-radio-button value="WHOLESALE">批发型</el-radio-button>
</el-radio-group>
<el-radio-group v-else v-model="baseInfoForm.salesModel">
<el-radio-button value="RETAIL">{{ isECouponGoods ? "电子卡券" : "虚拟型" }}</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="baseInfoForm.salesModel == 'WHOLESALE'"
class="form-item-view-el wholesale-rule-form-item"
label="销售规则"
prop="wholesaleRule"
>
<div class="form-item-view-wholesale">
<div>
<el-table
:key="'wholesale-edit-' + (baseInfoForm.goodsUnit || '')"
:data="wholesaleData"
border
style="width: 100%"
>
<el-table-column label="购买数量" align="center" width="220">
<template #default="{ $index }">
<el-input
class="wholesale-num-input"
v-model="wholesaleData[$index].num"
type="number"
@blur="checkWholesaleNum($index)"
>
<template #append>{{ baseInfoForm.goodsUnit || "" }}</template>
</el-input>
</template>
</el-table-column>
<el-table-column label="商品单价" align="center" width="320">
<template #default="{ $index }">
<div class="wholesale-price-cell">
<el-input
class="wholesale-price-input"
v-model="wholesaleData[$index].price"
type="number"
@blur="checkWholesalePrice($index)"
>
<template #append></template>
</el-input>
<el-button
v-if="$index > 0"
class="wholesale-price-del"
size="small"
type="danger"
@click="handleDeleteWholesaleData($index)"
>
删除
</el-button>
</div>
</template>
</el-table-column>
</el-table>
<el-button v-if="wholesaleData.length < 3" style="margin-top: 10px" @click="handleAddWholesaleData()">
<el-icon><Plus /></el-icon>
添加价格区间
</el-button>
</div>
<div class="form-item-view-wholesale-preview">
<el-table
:key="'wholesale-preview-' + (baseInfoForm.goodsUnit || '')"
:data="wholesaleData"
border
class="wholesale-preview-table"
>
<el-table-column label="销售规则" width="480">
<template #default="{ row }">
{{ wholesaleRulePreview(row) }}
</template>
</el-table-column>
</el-table>
</div>
</div>
</el-form-item>
<el-form-item class="form-item-view-el" label="商品发布" prop="release">
<el-radio-group v-model="baseInfoForm.release">
<el-radio-button :value="1">上架</el-radio-button>
<el-radio-button :value="0">下架</el-radio-button>
</el-radio-group>
</el-form-item>
</div>
<h4>商品规格及图片</h4>
<div class="form-item-view">
<el-form-item class="form-item-view-el required" label="主图" prop="goodsGalleryFiles">
<div style="display: flex; flex-wrap: wrap;">
<vuedraggable
v-model="baseInfoForm.goodsGalleryFiles"
:animation="200"
:item-key="draggableItemKey"
style="display: flex; flex-wrap: wrap"
>
<template #item="{ element }">
<div class="demo-upload-list">
<img :src="element" @error="handleGoodsPictureError($event, element)" />
<div class="demo-upload-list-cover">
<div>
<el-button link type="primary" @click="handleViewGoodsPicture(element)">预览</el-button>
<el-button link type="danger" @click="handleRemoveGoodsPicture(element)">删除</el-button>
</div>
</div>
</div>
</template>
</vuedraggable>
<!--<Upload ref="upload"-->
<!--:action="uploadFileUrl" :before-upload="handleBeforeUploadGoodsPicture"-->
<!--:format="['jpg', 'jpeg', 'png', 'webp']"-->
<!--:headers="{ ...accessToken }"-->
<!--:max-size="2048" :on-error="() => { $Spin.hide(); }" :on-exceeded-size="handleMaxSize"-->
<!--:on-format-error="handleFormatError" :on-progress="() => { $Spin.show(); }"-->
<!--:on-success="handleSuccessGoodsPicture" :show-upload-list="false" multiple-->
<!--style="margin-left: 10px"-->
<!--type="drag">-->
<!--<div style="width: 148px; height: 148px; line-height: 148px">-->
<!--<Icon size="20" type="md-add"></Icon>-->
<!--</div>-->
<!--</Upload>-->
</div>
<div style="width: 100%;display: flex;justify-content: start;margin-top: 10px;">
<el-button @click="handleCLickImg('goodsGalleryFiles')" type="primary">上传图片</el-button>
</div>
<el-dialog v-model="goodsPictureVisible" title="View Image">
<img v-if="goodsPictureVisible" :src="previewGoodsPicture" style="width: 100%" />
</el-dialog>
</el-form-item>
<el-form-item>
<div style="color: grey">主图仅支持pngjpgjpeg格式宽高至少600*600px大小2M内可拖拽调整主图顺序</div>
</el-form-item>
<el-form-item
class="form-item-view-el goods-video-form-item"
:class="{ 'goods-video-form-item--multi': baseInfoForm.goodsVideo }"
label="主图视频"
prop="goodsVideo"
>
<div class="goods-video">
<div v-if="baseInfoForm.goodsVideo" class="goods-video-preview">
<video :src="baseInfoForm.goodsVideo" class="video" controls style="max-width: 300px;" />
</div>
<div
class="goods-video-actions"
:class="{ 'goods-video-actions--with-preview': baseInfoForm.goodsVideo }"
>
<el-upload
:action="uploadFileUrl"
:headers="{ ...accessToken }"
accept=".avi,.wmv,.mpeg,.mp4,.mov"
:show-file-list="false"
:before-upload="beforeVideoUpload"
:on-success="handleSuccessGoodsVideo"
:on-error="() => { loadingVideo = false }"
>
<el-button :loading="loadingVideo" type="primary" link>
{{ loadingVideo ? "正在上传..." : `${baseInfoForm.goodsVideo ? "" : ""}上传视频` }}
</el-button>
</el-upload>
<el-button v-if="baseInfoForm.goodsVideo" link type="danger" @click="handleRemoveGoodsVideo">
删除
</el-button>
</div>
</div>
</el-form-item>
<div class="layout" style="width: 100%">
<el-collapse v-model="open_panel">
<el-collapse-item title="自定义规格项" name="1">
<div>
<div v-for="(item, $index) in skuInfo" :key="$index" class="sku-item-content">
<el-card>
<template #header>
<el-button link type="danger" @click="handleCloseSkuItem($index, item)">删除规格项</el-button>
</template>
<div class="sku-item-content-name">
<div class="sku-item-label">规格项</div>
<div class="sku-item-input-row">
<el-autocomplete
v-model="item.name"
:fetch-suggestions="(q, cb) => cb([])"
maxlength="30"
placeholder="请输入规格项名称"
style="width: 150px"
@focus="changeSkuItem(item.name)"
@change="editSkuItem(item.name, $index, item)"
/>
<el-switch
v-if="$index === 0"
v-model="openImage"
style="margin-left: 10px"
@change="changeSkuOpenImage"
/>
<span v-if="$index === 0" style="margin-left: 5px">添加规格图片</span>
</div>
</div>
<div class="sku-val">
<div class="sku-val-label">规格值 (输入完成后,鼠标点击其他地方后生效)</div>
<el-form :model="item" class="sku-val-form flex">
<!--规格值文本列表-->
<el-form-item v-for="(val, index) in item.spec_values" :key="index"
class="sku-item-content-val" label="">
<div class="sku-val-item">
<div class="sku-val-input-row">
<el-autocomplete
v-model="val.value"
:disabled="containsSameSkuItem"
:fetch-suggestions="(q, cb) => cb([])"
maxlength="30"
placeholder="请输入规格值"
style="width: 180px"
@focus="changeSkuVals(val, item.name)"
@blur="checkSkuVal(val, $index, item)"
@change="skuValueChange(val, index, item)"
/>
<el-button link type="danger" style="margin-left: 6px" @click="handleCloseSkuValue(val, index, item)">
<el-icon><Delete /></el-icon>
</el-button>
</div>
<!-- 内联错误提示 -->
<div v-if="val._error" class="sku-inline-error">{{ val._error }}</div>
<div v-if="$index === 0 && openImage" class="sku-val-image">
<vuedraggable
v-model="val.images"
:animation="200"
:item-key="draggableItemKey"
style="display: flex; flex-wrap: wrap"
>
<template #item="{ element, index: imgIndex }">
<div
class="sku-upload-list"
style="width: 180px;height: 140px"
>
<img :src="element" style="width: 180px;height: 140px" />
<div class="sku-upload-list-cover">
<div style="margin-top: 50px">
<el-icon :size="25" style="cursor:pointer;margin-right:8px" @click="handleView(element)"><ZoomIn /></el-icon>
<el-icon :size="25" style="cursor:pointer" @click="handleRemove(val.images, imgIndex)"><Delete /></el-icon>
</div>
</div>
</div>
</template>
</vuedraggable>
<el-upload
v-if="val.images.length < 1"
:action="uploadFileUrl"
:before-upload="handleBeforeUpload"
:headers="{ ...accessToken }"
:on-error="() => {}"
:on-exceeded-size="handleMaxSize"
:on-success="(res, file) => handleSuccess(res, file, val.images)"
:show-file-list="false"
accept=".jpg,.jpeg,.png,.webp"
drag
style="width: 180px;height: 140px;margin-right: 10px"
>
<el-icon :size="40"><Camera /></el-icon>
</el-upload>
</div>
</div>
</el-form-item>
<el-form-item
v-if="item.spec_values.length < 10 && item.spec_values.length >= 1 && item.spec_values[0].value !== ''"
class="sku-item-content-val" label="">
<el-autocomplete
v-model="newSkuValues[$index]"
:disabled="containsSameSkuItem"
:fetch-suggestions="(q, cb) => cb([])"
maxlength="30"
placeholder="自定义规格值"
style="width: 180px"
@blur="addSpec($index, item)"
@keyup.enter="addSpec($index, item)"
/>
</el-form-item>
</el-form>
</div>
</el-card>
</div>
<div style="display: flex">
<el-button class="add-sku-btn" type="primary" @click="addSkuItem">添加规格项
</el-button>
</div>
</div>
</el-collapse-item>
<el-collapse-item title="规格详细" name="2">
<div v-if="needToloadSku" class="topinfo" @click="handleLoadingSkuData">点击加载sku数据</div>
<div :class="needToloadSku ? 'mask' : ''">
<el-table :data="skuTableData" border class="mt_10" style="width: 100%">
<el-table-column
v-for="(col, colIndex) in skuTableColumn"
:key="colIndex"
:label="col.title"
:prop="col.key"
min-width="120"
>
<template #default="{ row, $index }">
<span v-if="col.key && !col.slot">{{ row[col.key] }}</span>
<el-input
v-else-if="col.slot === 'sn'"
v-model="row.sn"
clearable
placeholder="请输入货号"
@change="updateSkuTable(row, 'sn', $index)"
/>
<el-input
v-else-if="col.slot === 'weight' && needsLogistics && baseInfoForm.salesModel !== 'WHOLESALE'"
v-model="row.weight"
clearable
placeholder="请输入重量"
@change="updateSkuTable(row, 'weight', $index)"
>
<template #append>kg</template>
</el-input>
<el-input
v-else-if="col.slot === 'quantity' && isECouponGoods"
:model-value="row.quantity ?? 0"
disabled
placeholder="库存"
>
<template #append>{{ baseInfoForm.goodsUnit || "" }}</template>
</el-input>
<el-input
v-else-if="col.slot === 'quantity'"
v-model="row.quantity"
clearable
placeholder="请输入库存"
@change="updateSkuTable(row, 'quantity', $index)"
>
<template #append>{{ baseInfoForm.goodsUnit || "" }}</template>
</el-input>
<el-input
v-else-if="col.slot === 'cost'"
v-model="row.cost"
clearable
placeholder="请输入成本价"
@change="updateSkuTable(row, 'cost', $index)"
>
<template #append>元</template>
</el-input>
<el-input
v-else-if="col.slot === 'price'"
v-model="row.price"
clearable
placeholder="请输入价格"
@change="updateSkuTable(row, 'price', $index)"
>
<template #append>元</template>
</el-input>
<el-input
v-else-if="col.slot === 'wholePrice0' && wholesaleData[0]"
v-model="wholesaleData[0].price"
disabled
>
<template #append>元</template>
</el-input>
<el-input
v-else-if="col.slot === 'wholePrice1' && wholesaleData[1]"
v-model="wholesaleData[1].price"
disabled
>
<template #append>元</template>
</el-input>
<el-input
v-else-if="col.slot === 'wholePrice2' && wholesaleData[2]"
v-model="wholesaleData[2].price"
disabled
>
<template #append>元</template>
</el-input>
</template>
</el-table-column>
</el-table>
</div>
</el-collapse-item>
</el-collapse>
</div>
</div>
<h4 v-if="showContent">规格描述内容</h4>
<div v-if="showContent" class="form-item-view">
<div>
<el-form-item :label="contentImage" class="form-item-view-el">
<!-- {{item.url}} -->
<div v-for="(item, index) in listImages.images" :key="index" style="width:100%;display:flex;">
<img :src="item.url" style="width:100px;flex:1;margin-top:10px;cursor:pointer;"
@click="handleView(item.url)" />
</div>
</el-form-item>
</div>
</div>
<h4>商品详情描述</h4>
<div class="form-item-view">
<div class="tree-bar" :class="{ 'is-empty': !shopCategory.length }">
<el-form-item class="form-item-view-el" label="店内分类" prop="shopCategory">
<el-tree
ref="tree"
:data="shopCategory"
show-checkbox
node-key="id"
:props="{ label: 'title', children: 'children' }"
style="text-align: left"
@node-click="(data) => selectTree([data])"
@check="(_, ctx) => changeSelect(ctx.checkedNodes)"
>
<template #empty>
<span class="shop-category-empty">暂无分类</span>
</template>
</el-tree>
</el-form-item>
</div>
<el-form-item class="form-item-view-el" label="PC商品描述" prop="intro" style="width: 100%">
<div class="intro-editor-field">
<editor ref="editor" v-model="baseInfoForm.intro" height="800px" openXss></editor>
<div class="promise-intro-btn">
<el-button type="primary" @click="promiseIntroEditor">将PC商品描述同步到移动端描述
</el-button>
</div>
</div>
</el-form-item>
<el-form-item class="form-item-view-el" label="移动端描述" prop="skuList" style="width: 100%">
<div class="intro-editor-field">
<editor ref="introEditor" v-model="baseInfoForm.mobileIntro" height="800px" openXss></editor>
</div>
</el-form-item>
</div>
<div v-if="needsLogistics">
<h4>商品物流信息</h4>
<div class="form-item-view">
<el-form-item class="form-item-view-el" label="运费模板" prop="templateId">
<el-select v-model="baseInfoForm.templateId" style="width: 200px">
<el-option v-for="item in logisticsTemplate" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-button class="refresh-icon" circle link @click="refresh('template')">
<el-icon><Refresh /></el-icon>
</el-button>
</el-form-item>
<el-form-item v-if="baseInfoForm.salesModel == 'WHOLESALE'" class="form-item-view-el" label="商品重量"
prop="weight">
<el-input v-model="baseInfoForm.weight" placeholder="请输入商品重量">
<template #append>kg</template>
</el-input>
</el-form-item>
</div>
</div>
<h4>参数信息</h4>
<div class="form-item-view">
<el-form-item v-for="(paramsItem, paramsIndex) in goodsParams" :key="paramsItem.id || paramsIndex"
:label="`${paramsItem.paramName}`"
:prop="`goodsParams.${paramsIndex}.paramValue`"
:rules="paramsItem.required ? { required: true, message: `${paramsItem.paramName}不能为空`, trigger: 'change' } : {}">
<el-select v-model="baseInfoForm.goodsParams[paramsIndex].paramValue" clearable placeholder="请选择" style="width: 200px"
@change="(val) => selectParams(paramsItem, val, paramsIndex)">
<el-option
v-for="option in getParamOptions(paramsItem.options)"
:key="option"
:label="option"
:value="option"
/>
</el-select>
</el-form-item>
</div>
</div>
</el-form>
</div>
<!-- 底部按钮 -->
<div class="footer">
<div class="footer-btns">
<el-button :loading="submitLoading" type="primary" @click="save">
{{ $route.query.id ? "保存" : "保存商品" }}
</el-button>
</div>
</div>
<el-dialog v-model="showGoodsVideo" title="查看视频">
<div id="dplayer">
</div>
</el-dialog>
<!--<el-dialog width="1200px" v-model="picModelFlag">-->
<!--<ossManage @callback="callbackSelected" ref="ossManage" />-->
<!--</el-dialog>-->
<el-dialog v-model="picModelFlag" width="1200px" title="选择图片" append-to-body destroy-on-close>
<ossManage
ref="ossManage"
:isComponent="true"
:initialize="picModelFlag"
:max-select="selectedFormBtnName === 'goodsGalleryFiles' ? 1 : 0"
:hide-select-footer="true"
@callback="callbackSelected"
@selected="(list) => { selectedImage = list }"
/>
<template #footer>
<el-button @click="picModelFlag = false; selectedImage = []">取消</el-button>
<el-button type="primary" @click="confirmUrls">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import * as API_GOODS from "@/api/goods";
import * as API_Shop from "@/api/shops";
import cloneObj from "@/utils/index";
import { Camera, Delete, Plus, Refresh, ZoomIn } from "@element-plus/icons-vue";
import vuedraggable from "vuedraggable";
import tinymec from "@/views/lili-components/editor/index.vue";
import { uploadFile } from "@/libs/axios";
import { regular } from "@/utils";
import DPlayer from 'dplayer';
import ossManage from "@/views/sys/oss-manage/ossManage";
export default {
name: "goodsOperationSec",
components: {
Camera,
Delete,
Plus,
Refresh,
ZoomIn,
editor: tinymec,
vuedraggable,
ossManage,
},
props: {
firstData: {
default: {},
type: Object,
},
},
computed: {
/** 卡密商品:库存只读展示 quantity提交时 quantity 传 0FR-S-01 / P-02 */
isECouponGoods() {
return this.baseInfoForm.goodsType === "E_COUPON";
},
isVirtualGoods() {
return this.baseInfoForm.goodsType === "VIRTUAL_GOODS";
},
isVirtualLikeGoods() {
return this.isVirtualGoods || this.isECouponGoods;
},
/** 仅实物需要运费模板E_COUPON 强制 templateId=0§6.1 */
needsLogistics() {
return this.baseInfoForm.goodsType === "PHYSICAL_GOODS";
},
},
data() {
// 表单验证项,商品价格
const checkPrice = (rule, value, callback) => {
if (!value && value !== 0) {
return callback(new Error("商品价格不能为空"));
}
setTimeout(() => {
if (!regular.money.test(value)) {
callback(new Error("请输入正整数或者两位小数"));
} else if (parseFloat(value) > 99999999) {
callback(new Error("商品价格设置超过上限值"));
} else {
callback();
}
}, 1000);
};
return {
regular,
openImage: false,
needToloadSku: false,
total: 0,
goodsVideo: "",
showContent: false,
loadingVideo: false,
listImages: [],
newSkuValues: [],
contentImage: "",
previewImage: '', // 预览图片地址
global: 0,
accessToken: "", //令牌token
goodsParams: [],
categoryId: "", // 商品分类第三级id
/** 商品类型选项 */
goodsTypeOptions: [
{ label: "实物商品", value: "PHYSICAL_GOODS", desc: "零售批发物流配送" },
{ label: "虚拟商品", value: "VIRTUAL_GOODS", desc: "虚拟核验无需物流" },
{ label: "电子卡券", value: "E_COUPON", desc: "卡密自动发卡无需物流" },
],
/** 商城分类级联数据 */
categoryList: [],
/** 商城分类选中值 [一级id, 二级id, 三级id] */
selectedCategory: [],
//提交状态
submitLoading: false,
//上传图片路径
uploadFileUrl: uploadFile,
// 预览图片路径
previewPicture: "",
//商品图片
previewGoodsPicture: "",
//展示图片层
visible: false,
//展示商品图片
goodsPictureVisible: false,
//展示sku图片
showSkuPicture: false,
//选择的sku
selectedSku: {},
wholesalePreviewColumns: [
{
title: "销售规则",
width: 480,
render: (h, params) => {
let guide =
"当商品购买数量 " +
params.row.num +
" 售价为 " +
params.row.price +
" /" +
this.baseInfoForm.goodsUnit;
return h("div", guide);
},
},
],
wholesaleColumns: [
{
title: "购买数量",
key: "num",
align: "center",
slot: "wholesaleNum",
},
{
title: "商品单价",
key: "price",
align: "center",
width: "280px",
slot: "wholesalePrice",
},
],
wholesaleData: [
{
num: 0,
price: 0,
goodsId: this.goodsId,
},
],
/** 发布商品基本参数 */
baseInfoForm: {
salesModel: "RETAIL",
/** 商品相册列表 */
goodsGalleryFiles: [],
/** 是否立即发布 true 立即发布 false 放入仓库 */
release: 1,
/** 是否为推荐商品 */
recommend: 1,
/** 店铺分类 */
storeCategoryPath: "",
brandId: "",
/** 计量单位 **/
goodsUnit: "",
/** 商品类型 **/
goodsType: "",
/** 分类路径 **/
categoryPath: "",
/** 商品卖点 **/
sellingPoint: "",
/** 商品详情 **/
intro: "",
mobileIntro: "",
updateSku: true,
/** 是否重新生成sku */
regeneratorSkuFlag: false,
/** 物流模板id **/
templateId: "",
/** 参数组*/
goodsParamsDTOList: [],
/** 商品分类中文名 */
categoryName: [],
goodsVideo: "",
/** 商品参数用于验证 */
goodsParams: [],
},
invalidGoodsGalleryFiles: [],
goodsPictureFallback:
"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%22148%22%20height%3D%22148%22%20viewBox%3D%220%200%20148%20148%22%3E%3Crect%20width%3D%22148%22%20height%3D%22148%22%20fill%3D%22%23f5f7fa%22/%3E%3Ctext%20x%3D%2274%22%20y%3D%2274%22%20text-anchor%3D%22middle%22%20dominant-baseline%3D%22middle%22%20font-size%3D%2212%22%20fill%3D%22%23999%22%3EImage%20unavailable%3C/text%3E%3C/svg%3E",
/** 表格头 */
skuTableColumn: [],
/** 表格数据 */
skuTableData: [],
// 持久化的sku数据
skuTableDataCopy: [],
// 持久化的sku数据
skuInfoCopy: [],
/** 默认的规格参数 */
skuData: [],
/** 默认的规格值 */
skuVals: [],
// 某一规格名下的规格值
skuVal: [],
// 规格展开的项
open_panel: ["1", "2"],
/** 要提交的规格数据*/
skuInfo: [],
/** 物流模板 **/
logisticsTemplate: [],
/** 固定列校验提示内容 */
validatatxt: "请输入0~99999999之间的数字值",
//参数panel展示
params_panel: [],
/** 存储未通过校验的单元格位置 */
validateError: [],
baseInfoFormRule: {
goodsType: [{ required: true, message: "请选择商品类型", trigger: "change" }],
categoryPath: [{ required: true, message: "请选择商品分类", trigger: "change" }],
goodsName: [regular.REQUIRED, regular.WHITE_SPACE, regular.VARCHAR60],
price: [regular.REQUIRED, { validator: checkPrice }],
sellingPoint: [regular.REQUIRED, regular.VARCHAR60],
goodsUnit: [{ required: true, message: "请选择计量单位" }],
name: [regular.REQUIRED, regular.VARCHAR5],
value: [regular.REQUIRED, regular.VARCHAR60],
templateId: [regular.REQUIRED],
weight: [regular.REQUIRED],
},
params: {
pageNumber: 1,
pageSize: 1000,
},
currentSkuVal: "",
skuInfoRules: {},
/** 品牌列表 */
brandList: [],
/** 店铺分类列表 */
shopCategory: [],
/** 商品单位列表 */
goodsUnitList: [],
containsSameSkuItem: false,
containsSameSkuValue: false,
containsSameSkuNewValue: false,
// 展示商品视频
showGoodsVideo: false,
ignoreColumn: [
// 添加规格时需要忽略的参数
"_index",
"_rowKey",
"sn",
// "cost",
"price",
"weight",
"quantity",
// "alertQuantity",
"specId",
"specValueId",
],
picModelFlag: false, // 图片选择器
selectedFormBtnName: "", // 点击图片绑定form
selectedImage: [],
lastEditSkuValue: '',
};
},
watch: {
// 如果点击了展示商品视频,则初始化商品视频
showGoodsVideo(val) {
if (val) {
this.initVideo();
}
}
},
methods: {
/** E_COUPON 提交固定传 0真实库存由卡池 syncSkuStock 同步至 quantity */
resolveSubmitQuantity(sku) {
if (this.isECouponGoods) {
return 0;
}
return sku.quantity;
},
defaultSkuQuantity() {
return this.isECouponGoods ? 0 : "";
},
draggableItemKey(item) {
return item;
},
// 选择图片modal
handleCLickImg(val, index) {
this.selectedImage = [];
this.picModelFlag = true;
this.selectedFormBtnName = val;
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
});
},
handleLoadingSkuData() {
this.needToloadSku = false
this.renderTableData(this.skuTableData)
},
changeSkuOpenImage() {
this.skuTableData.forEach(item => {
item.images = []
})
if (this.skuInfo.length > 0 && this.skuInfo[0].spec_values.length > 0) {
this.skuInfo[0].spec_values.forEach(item => {
item.images = []
})
}
},
// ship大小不正确
handleVideoMaxSize(file) {
this.$Notice.warning({
title: "超过文件大小限制",
desc: "视频大小不能超过10MB",
});
},
parseOssSelectionUrl(item) {
if (!item) return "";
if (typeof item === "string") {
const index = item.indexOf(",");
return index >= 0 ? item.slice(index + 1) : item;
}
return item.url || "";
},
// 图片选择后回调
callbackSelected(val) {
this.picModelFlag = false;
if (!val?.url) {
return;
}
if (this.selectedFormBtnName === "selectedSkuImages") {
if (!this.selectedSku.images) {
this.selectedSku.images = [];
}
this.selectedSku.images.push(val.url);
} else {
this.baseInfoForm[this.selectedFormBtnName].push(val.url);
}
},
confirmUrls() {
const urls = (this.selectedImage || [])
.map((item) => this.parseOssSelectionUrl(item))
.filter(Boolean);
if (!urls.length) {
this.$Message.warning("请选择图片");
return;
}
if (this.selectedFormBtnName === "selectedSkuImages") {
if (!this.selectedSku.images) {
this.selectedSku.images = [];
}
urls.forEach((url) => {
if (this.selectedSku.images.length < 5) {
this.selectedSku.images.push(url);
}
});
} else if (this.selectedFormBtnName === "goodsGalleryFiles") {
urls.forEach((url) => {
if (this.baseInfoForm.goodsGalleryFiles.length < 5) {
this.baseInfoForm.goodsGalleryFiles.push(url);
}
});
} else if (this.selectedFormBtnName) {
const target = this.baseInfoForm[this.selectedFormBtnName];
if (Array.isArray(target)) {
urls.forEach((url) => target.push(url));
}
}
this.selectedImage = [];
this.picModelFlag = false;
},
// 局部刷新
refresh(v) {
if (v == 'template') {
this.GET_ShipTemplate('localRefresh');
} else if (v == 'goodsUnit') {
this.goodsUnitList = []
this.GET_GoodsUnit('localRefresh');
} else {
this.getGoodsBrandList('localRefresh');
}
},
getImages(v) {
this.previewImage = v;
this.visible = true;
},
mouseOver(v) {
this.showContent = true
this.listImages = v
if (this.listImages.images.length <= 0) {
this.contentImage = '规格专属图片暂无'
} else {
this.contentImage = '当前规格专属图片'
}
},
mouseLeave() {
// this.showContent = false
},
getParamOptions(options) {
if (!options) return [];
return String(options)
.split(",")
.map((i) => i.trim())
.filter((i) => i);
},
selectParams(params, value, paramsIndex) {
if (!Array.isArray(this.baseInfoForm.goodsParamsDTOList)) {
this.baseInfoForm.goodsParamsDTOList = [];
}
// 确保baseInfoForm.goodsParams存在
if (!Array.isArray(this.baseInfoForm.goodsParams)) {
this.baseInfoForm.goodsParams = [];
}
// 确保对应索引的参数项存在
if (!this.baseInfoForm.goodsParams[paramsIndex]) {
this.baseInfoForm.goodsParams[paramsIndex] = {};
}
// 更新baseInfoForm中的值用于验证
this.baseInfoForm.goodsParams[paramsIndex].paramValue = value || '';
const list = this.baseInfoForm.goodsParamsDTOList;
const paramId = params && params.id ? String(params.id) : "";
const index = list.findIndex((i) => String(i.paramId) === paramId);
if (!value && value !== 0) {
if (index >= 0) {
list.splice(index, 1);
}
// 清空表单项的值
params.paramValue = '';
return;
}
const newItem = {
paramId,
paramName: params.paramName,
paramValue: value,
isIndex: params.isIndex || 0,
required: params.required || 0,
sort: params.sort || 0,
};
if (index >= 0) {
list[index] = newItem;
} else {
list.push(newItem);
}
// 同步更新表单项的值
params.paramValue = value;
},
// 编辑sku图片
editSkuPicture(row) {
this.showContent = false
if (row.images && row.images.length > 0) {
this.previewPicture = row.images[0];
}
this.selectedSku = row;
this.showSkuPicture = true;
},
// 初始化视频操作
initVideo() {
if (this.baseInfoForm.goodsVideo) {
this.goodsVideo = new DPlayer({
container: document.getElementById('dplayer'),
video: {
url: this.baseInfoForm.goodsVideo,
pic: ''
},
});
}
},
/** 加载商城商品分类树 */
loadCategoryList() {
return API_GOODS.getGoodsCategoryAll().then((res) => {
if (res.success && res.result) {
this.categoryList = this.formatCategoryTree(res.result);
this.syncSelectedCategoryFromPath();
}
});
},
formatCategoryTree(list) {
return (list || []).map((item) => ({
value: String(item.id),
label: item.name,
children: (item.children || []).map((child) => ({
value: String(child.id),
label: child.name,
children: (child.children || []).map((grandson) => ({
value: String(grandson.id),
label: grandson.name,
})),
})),
}));
},
syncSelectedCategoryFromPath() {
const path = this.baseInfoForm.categoryPath;
if (!path) {
return;
}
const ids = path.split(",").filter(Boolean).map(String);
if (ids.length >= 3) {
this.selectedCategory = ids;
this.categoryId = ids[2];
}
},
getCategoryNames(ids) {
const names = [];
let options = this.categoryList;
ids.forEach((id) => {
const found = (options || []).find((item) => item.value === id);
if (found) {
names.push(found.label);
options = found.children || [];
}
});
return names;
},
handleGoodsTypeChange() {
if (this.isVirtualLikeGoods) {
this.baseInfoForm.salesModel = "RETAIL";
}
if (this.isECouponGoods) {
this.baseInfoForm.templateId = 0;
}
this.renderTableData(this.skuTableData);
},
handleCategoryChange(val) {
if (!val || val.length < 3) {
this.selectedCategory = val || [];
this.categoryId = "";
this.baseInfoForm.categoryPath = "";
this.baseInfoForm.categoryName = [];
this.baseInfoForm.brandId = "";
this.brandList = [];
this.goodsParams = [];
this.params_panel = [];
return;
}
this.categoryId = val[2];
this.baseInfoForm.categoryPath = val.join(",");
this.baseInfoForm.categoryName = this.getCategoryNames(val);
this.baseInfoForm.brandId = "";
this.getGoodsBrandList();
this.GET_GoodsParams();
},
// 预览图片
handleView(url) {
this.previewPicture = url;
this.visible = true;
},
// 移除已选图片
handleRemove(item, index) {
item.splice(index, 1)
this.previewPicture = "";
},
// 查看商品大图
handleViewGoodsPicture(url) {
if (this.invalidGoodsGalleryFiles.includes(url)) {
this.$Message.warning("图片链接已失效,请重新上传主图");
return;
}
this.previewGoodsPicture = url;
this.goodsPictureVisible = true;
},
handleGoodsPictureError(event, url) {
if (!this.invalidGoodsGalleryFiles.includes(url)) {
this.invalidGoodsGalleryFiles.push(url);
}
const img = event && event.target;
if (!img || img.dataset.fallbackApplied) return;
img.dataset.fallbackApplied = "true";
img.src = this.goodsPictureFallback;
},
// 移除商品图片
handleRemoveGoodsPicture(file) {
this.baseInfoForm.goodsGalleryFiles =
this.baseInfoForm.goodsGalleryFiles.filter((i) => i !== file);
this.invalidGoodsGalleryFiles =
this.invalidGoodsGalleryFiles.filter((i) => i !== file);
},
assignSkuTableIndex() {
this.skuTableData.forEach((row, index) => {
row._index = index;
});
},
// 更新sku图片
updateSkuPicture() {
this.baseInfoForm.regeneratorSkuFlag = true;
let _index = this.selectedSku._index;
if (_index === undefined || _index === null) {
_index = this.skuTableData.indexOf(this.selectedSku);
}
if (_index >= 0) {
this.skuTableData[_index] = this.selectedSku;
}
},
// sku图片上传成功
handleSuccess(res, file, images) {
if (file.response) {
file.url = file.response.result;
if (images) {
images.push(file.url);
} else {
images = [file.url];
}
this.previewPicture = file.url;
}
},
handleAddWholesaleData() {
if (
this.wholesaleData.length === 1 &&
(this.wholesaleData[0].price <= 0 || this.wholesaleData[0].num <= 0)
) {
this.$Message.error("请输入正确的销售规则");
return;
}
if (this.wholesaleData.length < 3) {
this.wholesaleData.push({
price:
Number(this.wholesaleData[this.wholesaleData.length - 1].price) -
0.01,
num:
Number(this.wholesaleData[this.wholesaleData.length - 1].num) + 1,
goodsId: this.goodsId,
});
}
this.renderTableData(this.skuTableData);
this.syncWholesalePriceToSku();
},
wholesaleRulePreview(row) {
const unit = this.baseInfoForm.goodsUnit || "";
const unitText = unit ? `/${unit}` : "";
return `当商品购买数量 ≥${row.num} 时,售价为 ¥${row.price}${unitText}`;
},
handleDeleteWholesaleData(index) {
this.wholesaleData.splice(index, 1);
this.renderTableData(this.skuTableData);
this.syncWholesalePriceToSku();
},
// 同步批发价格到SKU
syncWholesalePriceToSku() {
if (this.baseInfoForm.salesModel === 'WHOLESALE' && this.wholesaleData.length > 0) {
// 使用第一个批发价格作为SKU的基础价格
const basePrice = this.wholesaleData[0].price || 0;
this.skuTableData.forEach(sku => {
sku.price = basePrice;
});
}
},
// 处理销售模式切换
handleSalesModeChange() {
this.renderTableData(this.skuTableData);
// 如果切换到批发模式,同步价格
if (this.baseInfoForm.salesModel === 'WHOLESALE') {
this.syncWholesalePriceToSku();
}
},
checkWholesaleNum(index) {
if (this.wholesaleData[index].num < 0) {
this.$Message.error("购买数量必须大于0");
this.wholesaleData[index].num = 0;
return;
}
if (
index > 0 &&
this.wholesaleData[index - 1].num >= this.wholesaleData[index].num
) {
this.$Notice.error({
title: "在批发模式的销售规则中",
desc: "下一个购买数量必须大于上一个购买数量",
duration: 5,
});
this.wholesaleData[index].num = this.wholesaleData[index - 1].num + 1;
}
this.renderTableData(this.skuTableData);
this.syncWholesalePriceToSku();
},
checkWholesalePrice(index) {
if (this.wholesaleData[index].price < 0) {
this.$Message.error("商品单价必须大于0");
this.wholesaleData[index].price = 0;
return;
}
if (
index > 0 &&
this.wholesaleData[index - 1].price <= this.wholesaleData[index].price
) {
this.$Notice.error({
title: "在批发模式的销售规则中",
desc: "下一个商品单价必须小于上一个商品单价",
duration: 5,
});
this.wholesaleData[index].price =
this.wholesaleData[index - 1].price - 0.01;
}
this.renderTableData(this.skuTableData);
this.syncWholesalePriceToSku();
},
// 商品图片上传成功
handleSuccessGoodsPicture(res, file) {
if (file.response) {
file.url = file.response.result;
this.baseInfoForm.goodsGalleryFiles.push(file.url);
}
},
// 图片格式不正确
handleFormatError(file) {
this.$Notice.warning({
title: "文件格式不正确",
desc: "文件 " + file.name + " 的格式不正确",
});
},
handleSuccessGoodsVideo(res, file) {
if (file.response) {
file.url = file.response.result;
this.baseInfoForm.goodsVideo = file.url;
}
this.loadingVideo = false;
},
handleRemoveGoodsVideo() {
this.baseInfoForm.goodsVideo = "";
this.showGoodsVideo = false;
if (this.goodsVideo && typeof this.goodsVideo.destroy === "function") {
this.goodsVideo.destroy();
}
this.goodsVideo = "";
},
// 图片大小不正确
handleMaxSize(size = 2) {
this.$Notice.warning({
title: "超过文件大小限制",
desc: `图片大小不能超过${size}MB`,
});
},
// 图片上传前钩子
handleBeforeUploadGoodsPicture(file) {
const check = this.baseInfoForm.goodsGalleryFiles.length < 5;
if (!check) {
this.$Notice.warning({
title: "图片数量不能大于五张",
});
return false;
}
},
// sku图片上传前钩子
handleBeforeUpload(file) {
const check =
this.selectedSku.images !== undefined &&
this.selectedSku.images.length > 5;
if (check) {
this.$Notice.warning({ title: "图片数量不能大于五张" });
return false;
}
},
/** 无品牌时后端用 0选择器空值用空字符串避免 el-select 直接显示 0 */
normalizeBrandId(id) {
if (id === 0 || id === "0" || id == null || id === "") return "";
return id;
},
/** 查询商品品牌列表 */
getGoodsBrandList(type) {
API_GOODS.getCategoryBrandListDataSeller(this.categoryId).then(
(response) => {
this.brandList = response;
if (type === 'localRefresh') {
this.$Message.success("刷新成功");
}
}
).catch(() => {
if (type === 'localRefresh') {
this.$Message.error("刷新失败,请重试");
}
});
},
// 获取商品单位
GET_GoodsUnit(type) {
API_GOODS.getGoodsUnitList(this.params).then((res) => {
if (res.success) {
this.goodsUnitList.push(...res.result.records.map((i) => i.name));
this.total = res.result.total;
}
if (type === 'localRefresh' && res.success) {
this.$Message.success("刷新成功");
} else if (type === 'localRefresh') {
this.$Message.error("刷新失败,请重试");
}
});
},
// 获取当前店铺分类
GET_ShopGoodsLabel() {
API_GOODS.getShopGoodsLabelListSeller().then((res) => {
if (res.success) {
const shopCategories = !this.baseInfoForm.storeCategoryPath
? []
: this.baseInfoForm.storeCategoryPath.split(",").filter(Boolean);
this.shopCategory = res.result.map((i) => {
i.title = i.labelName;
i.expand = false;
i.children = i.children.map((j) => {
j.title = j.labelName;
j.expand = false;
return j;
});
return i;
});
this.$nextTick(() => {
this.$refs.tree?.setCheckedKeys(shopCategories);
});
}
});
},
// 编辑时获取商品信息
async GET_GoodData(id, draftId) {
let response = {};
if (draftId) {
response = await API_GOODS.getDraftGoodsDetail(draftId);
} else {
response = await API_GOODS.getGoods(id);
this.goodsId = response.result.id;
}
response.result.recommend
? (response.result.recommend = 1)
: (response.result.recommend = 0);
this.invalidGoodsGalleryFiles = [];
this.baseInfoForm = { ...this.baseInfoForm, ...response.result };
this.baseInfoForm.release = 1; //即使是被放入仓库,修改的时候也会显示会立即发布
this.baseInfoForm.brandId = this.normalizeBrandId(this.baseInfoForm.brandId);
this.categoryId = response.result.categoryPath.split(",")[2];
this.syncSelectedCategoryFromPath();
// 如果是复制商品需要清除ID确保提交时作为新商品
if (this.$route.query.copyId) {
this.baseInfoForm.id = "";
}
if (
response.result.goodsGalleryList &&
response.result.goodsGalleryList.length > 0
) {
this.baseInfoForm.goodsGalleryFiles =
response.result.goodsGalleryList.map((i) => {
return i;
});
}
if (
response.result.wholesaleList &&
response.result.wholesaleList.length > 0
) {
this.wholesaleData = response.result.wholesaleList;
}
if (response.result.salesModel === "WHOLESALE") {
this.baseInfoForm.weight = response.result.skuList[0].weight;
}
this.renderGoodsDetailSku(response.result.skuList);
/** 查询品牌列表 */
this.getGoodsBrandList();
/** 查询店铺商品分类 */
this.GET_ShopGoodsLabel();
this.GET_GoodsUnit();
/** 查询商品参数 */
this.GET_GoodsParams();
this.$nextTick(() => {
this.$refs.editor?.setContent?.(this.baseInfoForm.intro);
this.$refs.introEditor?.setContent?.(this.baseInfoForm.mobileIntro);
});
},
/** TinyMCE 的 v-model 可能滞后,提交前从编辑器实例取最新 HTML */
syncIntroFromEditors() {
const pcEditor = this.$refs.editor;
const mobileEditor = this.$refs.introEditor;
if (pcEditor?.getContent) {
this.baseInfoForm.intro = pcEditor.getContent() ?? "";
}
if (mobileEditor?.getContent) {
this.baseInfoForm.mobileIntro = mobileEditor.getContent() ?? "";
}
},
// 渲染sku数据
renderGoodsDetailSku(skuList) {
let skus = [];
let skusInfo = [];
skuList.map((e) => {
let sku = {
id: e.id,
sn: e.sn,
price: e.price,
// cost: e.cost,
quantity: e.quantity,
// alertQuantity: e.alertQuantity,
weight: e.weight,
};
if (e.goodsGalleryList && e.goodsGalleryList.length >= 1) {
this.openImage = true
} else {
this.openImage = false
}
e.specList.forEach((u) => {
if (u.specName === "images") {
sku.images = u.specImage;
} else {
sku[u.specName] = u.specValue;
if (
!skusInfo.some((s) => s.name === u.specName) &&
!this.ignoreColumn.includes(u.specName)
) {
skusInfo.push({
name: u.specName,
spec_id: u.specNameId,
spec_values: [
{
id: u.specValueId,
name: u.specName,
value: u.specValue || "",
images: e.goodsGalleryList || []
},
],
});
} else {
skusInfo = skusInfo.map((sk) => {
if (
!sk.spec_values.some((s) => s.value === u.specValue) &&
sk.name === u.specName
) {
sk.spec_values.push({
id: u.specValueId,
name: u.specName,
value: u.specValue || "",
images: e.goodsGalleryList || []
});
}
if (!sk.spec_id && u.specName === "specId") {
sk.spec_id = u.specValue;
}
return sk;
});
}
}
});
skus.push(sku);
});
this.skuInfo = skusInfo;
this.skuTableData = skus;
this.renderTableData(skus);
},
// 将pc商品描述同步给移动端
promiseIntroEditor() {
this.syncIntroFromEditors();
const pcContent = this.baseInfoForm.intro ?? "";
this.baseInfoForm.mobileIntro = pcContent;
this.$nextTick(() => {
this.$refs.introEditor?.setContent?.(pcContent);
});
},
/** 根据当前分类id查询商品应包含的参数 */
GET_GoodsParams() {
this.goodsParams = [];
this.params_panel = [];
API_GOODS.getCategoryParamsListDataSeller(this.categoryId).then(
(response) => {
const list = Array.isArray(response)
? response
: Array.isArray(response?.result)
? response.result
: [];
if (!list.length) {
return;
}
if (!Array.isArray(this.baseInfoForm.goodsParamsDTOList)) {
this.baseInfoForm.goodsParamsDTOList = [];
}
const mergedSelected = new Map();
const selectedList = [];
this.baseInfoForm.goodsParamsDTOList.forEach((item) => {
if (!item) return;
if (Array.isArray(item.goodsParamsItemDTOList)) {
selectedList.push(...item.goodsParamsItemDTOList);
} else if (item.paramId || item.paramName) {
selectedList.push(item);
}
});
selectedList.forEach((param) => {
if (!param) return;
const key = param.paramId ? String(param.paramId) : param.paramName;
if (!key) return;
mergedSelected.set(key, param);
});
this.baseInfoForm.goodsParamsDTOList = Array.from(mergedSelected.values()).map((p) => {
return {
paramId: p.paramId ? String(p.paramId) : "",
paramName: p.paramName,
paramValue: p.paramValue,
isIndex: p.isIndex || 0,
required: p.required || 0,
sort: p.sort || 0,
};
});
const findSelectedValue = (param) => {
if (!param) return undefined;
const byId = mergedSelected.get(String(param.id));
if (byId) return byId.paramValue;
const byName = mergedSelected.get(param.paramName);
if (byName) return byName.paramValue;
return undefined;
};
const isGrouped = list[0] && Array.isArray(list[0].params);
const flatParams = isGrouped
? list.reduce((acc, g) => {
if (g && Array.isArray(g.params)) {
acc.push(...g.params);
}
return acc;
}, [])
: list;
this.goodsParams = flatParams
.map((p) => {
const selectedValue = findSelectedValue(p);
return {
...p,
paramValue:
selectedValue !== undefined ? selectedValue : (p.paramValue || ""),
};
})
.sort((a, b) => Number(a.sort || 0) - Number(b.sort || 0));
// 初始化baseInfoForm.goodsParams用于表单验证
this.baseInfoForm.goodsParams = [];
this.goodsParams.forEach((param, index) => {
this.baseInfoForm.goodsParams[index] = {
paramValue: param.paramValue || ''
};
});
// 确保表单验证能正确初始化
this.$nextTick(() => {
if (this.$refs.baseInfoForm) {
this.$refs.baseInfoForm.clearValidate();
}
});
}
);
},
/** 添加规格项 */
addSkuItem() {
if (this.containsSameSkuItem) {
this.$Message.error("存在重复规格项!");
return;
}
if (this.containsSameSkuValue) {
this.$Message.error("存在重复规格值!");
return;
}
if (this.skuInfo.length >= 5) {
this.$Message.error("规格项不能大于5个");
return;
}
if (this.skuInfo.find((i) => i.name === "")) {
this.$Message.error("规格项不能为空!");
return;
}
this.skuInfo.push({
spec_values: [{ name: "", value: "", images: [] }],
name: "",
});
this.renderTableData(this.skuTableData);
},
changeSkuItem(val) {
this.currentSkuItem = val;
},
// 编辑规格名
editSkuItem(val, index, item) {
if (this.skuTableData.find((i) => i[val])) {
this.$Message.error("已存在相同规格项!");
this.containsSameSkuItem = true;
return;
}
this.containsSameSkuItem = false;
if (this.zz(0, val) > 20) {
this.$Message.error("规格值最多十个字符长度!");
// val = val.toString().slice(0, 4);
this.skuInfo[index].name = this.countCharacters(val, 10);
this.$forceUpdate();// 调用该方法会触发组件的重新渲染
// return;
}
this.skuTableData = this.skuTableData.map((e) => {
e[val] = e[this.currentSkuItem];
delete e[this.currentSkuItem];
return e;
});
this.skuInfo[index].name = val;
this.skuInfo[index].spec_values.forEach((e) => {
e.name = val;
});
this.currentSkuItem = val;
this.renderTableData(this.skuTableData);
},
// 正则验证中文超过10个英文数字超过20个
zz(len, value) {
for (let i = 0; i < value.length; i++) {
//正则表达式判断中文
if (/[\u4e00-\u9fa5]/.test(value[i])) {
len += 2;
} else {
len++;
}
}
return len;
},
countCharacters(defaultStr, defaultNum) {
let str = '' + defaultStr || '',
num = +defaultNum || 0,
res = '',
length = 0;
if (!str || !num) {
return str;
}
// 循环字符串,判断长度 最少也会返回一个字
for (const i in str) {
res += str[i];
// 测试长度
length += /[\u4e00-\u9fa5]/.test(str[i]) ? 2 : 1;
// 如果长度大于设置长度 或者 循环到最后则终止循环
if (length >= num || +i == str.length - 1) {
break;
}
}
return res;
},
// 编辑规格值
skuValueChange(val, index, item) {
if (val.value === '') {
return;
}
if (this.skuTableData.find((i) => i[val.name] === val.value)) {
this.$Message.error("已存在相同规格值!");
this.skuInfo = cloneObj(this.skuInfoCopy);
this.skuTableData = cloneObj(this.skuTableDataCopy);
return;
}
this.containsSameSkuValue = false;
if (this.zz(0, val.value) > 20) {
this.$Message.error("规格值最多十个字符长度!");
// val.value = val.value.toString().slice(0, 4);
// 使用传入的 item 引用,避免对未定义的 $index 访问
item.spec_values[index].value = this.countCharacters(val.value, 10);
this.$forceUpdate();// 调用该方法会触发组件的重新渲染
// return;
}
this.lastEditSkuValue = val.value;
let curVal = this.currentSkuVal;
this.skuTableData = this.skuTableData.map((e) => {
if (e[val.name] === curVal) {
e[val.name] = val.value;
}
return e;
});
this.currentSkuVal = val.value;
this.skuTableDataCopy = cloneObj(this.skuTableData);
this.skuInfoCopy = cloneObj(this.skuInfo);
this.renderTableData(this.skuTableData);
},
// 获取焦点时,取得规格名对应的规格值
changeSkuVals(val, name) {
this.currentSkuVal = val.value;
if (name) {
this.skuData.forEach((e, index) => {
if (e === name) {
if (this.skuVal.length !== this.skuVals[index].length) {
this.skuVal = this.skuVals[index];
}
}
});
}
},
checkSkuVal(val, groupIndex, spec) {
if (val.value === "") {
// 内联错误提示,不使用弹窗
val._error = '规格值不能为空!';
// 如果规格项和规格名称存在,从表格数据中移除包含该空规格值的行
if (spec && spec.name && this.skuInfo[groupIndex]) {
// 从表格数据中过滤掉包含该空规格值的行
this.skuTableData = this.skuTableData.filter(item => {
return item[spec.name] !== val.value && item[spec.name] !== "";
});
// 重新渲染表格数据
this.renderTableData(this.skuTableData);
}
} else if (val._error) {
// 清除错误
delete val._error;
if (this.skuInfo[groupIndex]) {
this.skuInfo[groupIndex].spec_values = this.skuInfo[groupIndex].spec_values.filter((i) => i.value !== "");
}
// this.skuTableData = this.skuTableData.filter(
// (e) => e[spec && spec.name] !== this.lastEditSkuValue
// );
}
// 判断是否存在重复规格值
if (!this.skuTableData.find((i) => i[val.name] === val.value)) {
this.skuTableDataCopy = cloneObj(this.skuTableData);
this.skuInfoCopy = cloneObj(this.skuInfo);
}
},
/** 移除当前规格项 进行数据变化*/
handleCloseSkuItem($index, item) {
if ($index === 0 && this.skuInfo.length === 1) {
this.skuInfo = [];
this.skuTableData = [];
} else {
// 获取当前操作的规格项,在规格项数组中的位置(下标)
let itemIndex = 0;
this.skuInfo.forEach((i, _index) => {
if (i.name === item.name) {
itemIndex = _index;
}
});
if (itemIndex === this.skuInfo.length - 1) {
// 如果当前为最后一个规格项,则按照下标按照最后一个规格项生成规则删除
// 最后一个规格项生成规格数据规则: 如为最后一个规格项则在规格列表每隔1个中删除n(n为最后一个规格项的规格值列表数量 - 1)个规格数据,生成一个规格数据
// 除了当前操作的规格项的规格项列表,用于获取所有规格项的规格值列表总数
let filterSkuInfo = this.skuInfo.filter((i) => i.name !== item.name);
let index = 1;
let totalLength = 1;
filterSkuInfo.forEach((skuInfo) => {
totalLength *= skuInfo.spec_values.length;
});
// 去除第一个,因为第一个不需要生成新的规格数据
item.spec_values.splice(0, 1);
for (let i = 0; i < totalLength; i++) {
// 移除对应的规格数据
this.skuTableData.splice(index, item.spec_values.length);
index++;
}
} else {
// 当前规格项生成规格数据的时候,每次应该生成的条数
let currentNum =
this.skuInfo[this.skuInfo.length - 1].spec_values.length;
for (let i = this.skuInfo.length - 2; i > itemIndex; i--) {
// 计算每次删除规格数据后移动的位置(计算规则为,以最后的规格项的规格值数量为基础,乘以其他规格项的规格值总数)
currentNum *= this.skuInfo[i].spec_values.length;
}
// 移除对应规格数据的起始索引,起始位置为每次生成条数的下一位
let beginIndex = currentNum + 1;
let filterSkuInfo = this.skuInfo.filter((i) => i.name !== item.name);
let totalLength = 1;
filterSkuInfo.forEach((skuInfo) => {
totalLength *= skuInfo.spec_values.length;
});
for (let i = 0; i < totalLength; i++) {
// 移除对应的规格数据,删除数量为 每次生成条数 * (当前规格项的规格值总数 - 1
this.skuTableData.splice(
beginIndex,
currentNum * (item.spec_values.length - 1)
);
beginIndex += currentNum;
}
}
this.skuInfo.splice($index, 1);
this.skuTableData = this.skuTableData.map((e) => {
delete e[item.name];
return e;
});
}
/**
* 渲染规格详细表格
*/
this.renderTableData(this.skuTableData);
},
// 添加规格值的验证
validateEmpty(params) {
let flag = true;
params.forEach((item) => {
for (const key in item) {
if (item[key] !== "0" && !item.value) {
this.$Message.error("请必填规格项");
flag = false;
return false; // 终止程序
}
}
});
return flag;
},
/** 添加当前规格项的规格值*/
addSpec($index, item) {
if (!this.newSkuValues[$index]) {
this.$Message.error("请输入规格值");
return;
}
if (this.containsSameSkuItem) {
this.$Message.error("存在重复规格项!");
return;
}
if (item.spec_values.find((i) => i.value === this.newSkuValues[$index])) {
this.newSkuValues[$index] = "";
this.skuInfo = cloneObj(this.skuInfoCopy);
this.skuTableData = cloneObj(this.skuTableDataCopy);
this.$Message.error("存在重复规格值!");
this.containsSameSkuNewValue = true;
return;
}
if (this.validateEmpty(item.spec_values)) {
if (item.spec_values.length >= 10) {
this.$Message.error("规格值不能大于10个");
return;
}
let beforeLength = item.spec_values.length;
let itemValue = {
name: item.name,
value: this.newSkuValues[$index],
};
if (this.openImage) {
itemValue.images = []
} else {
itemValue.images = this.baseInfoForm.goodsGalleryFiles
}
item.spec_values.push(itemValue);
// 生成新的规格组合
const newCombinations = this.generateSkuCombinations(this.skuInfo);
// 保留原有组合的属性值
this.skuTableData = newCombinations.map(combination => {
// 查找匹配的原有组合
const existingCombination = this.skuTableData.find(item => {
// 检查所有规格项是否匹配
return this.skuInfo.every(info => {
return item[info.name] === combination[info.name];
});
});
if (existingCombination) {
// 保留原有组合的属性值
return {
...combination,
id: existingCombination.id || "",
sn: existingCombination.sn || "",
quantity: this.isECouponGoods
? (existingCombination.quantity ?? 0)
: (existingCombination.quantity || ""),
cost: existingCombination.cost || "",
price: existingCombination.price || "",
weight: existingCombination.weight || ""
};
} else {
// 新组合使用默认值
return {
...combination,
quantity: this.defaultSkuQuantity(),
};
}
});
this.baseInfoForm.regeneratorSkuFlag = true;
this.newSkuValues[$index] = "";
this.skuTableDataCopy = cloneObj(this.skuTableData);
this.skuInfoCopy = cloneObj(this.skuInfo);
}
},
handleClearSku() {
this.skuInfo = [];
this.skuTableData = [];
this.renderTableData(this.skuTableData);
},
/** 移除当前规格值 */
handleCloseSkuValue(item, index, spec) {
if (spec.spec_values.length <= 1) {
this.$Message.error("至少保留一个规格值!");
return;
}
// 从规格项中移除规格值
this.skuInfo.forEach((i) => {
if (i.name === spec.name) {
i.spec_values.splice(index, 1);
}
});
// 生成新的规格组合
const newCombinations = this.generateSkuCombinations(this.skuInfo);
// 保留原有组合的属性值
this.skuTableData = newCombinations.map(combination => {
// 查找匹配的原有组合
const existingCombination = this.skuTableData.find(item => {
// 检查所有规格项是否匹配
return this.skuInfo.every(info => {
return item[info.name] === combination[info.name];
});
});
if (existingCombination) {
// 保留原有组合的属性值
return {
...combination,
id: existingCombination.id || "",
sn: existingCombination.sn || "",
quantity: this.isECouponGoods
? (existingCombination.quantity ?? 0)
: (existingCombination.quantity || ""),
cost: existingCombination.cost || "",
price: existingCombination.price || "",
weight: existingCombination.weight || ""
};
} else {
// 新组合使用默认值
return {
...combination,
quantity: this.defaultSkuQuantity(),
};
}
});
this.baseInfoForm.regeneratorSkuFlag = true;
},
/**
* 渲染table所需要的column 和 data
*/
renderTableData(skus) {
this.skuTableColumn = [];
let pushData = [];
// 渲染头部
this.skuInfo.forEach((sku) => {
// 列名称
let columnName = sku.name;
pushData.push({
title: columnName,
key: columnName,
});
});
// 有成本价和价格的情况
if (this.baseInfoForm.salesModel !== "WHOLESALE") {
pushData.push(
// {
// title: "成本价",
// slot: "cost",
// },
{
title: "价格",
slot: "price",
}
);
}
if (this.baseInfoForm.salesModel === "WHOLESALE" && this.wholesaleData) {
this.wholesaleData.forEach((item, index) => {
pushData.push({
title: "购买量 ≥ " + item.num,
slot: "wholePrice" + index,
});
});
}
// 有重量的情况
if (
this.needsLogistics &&
this.baseInfoForm.salesModel !== "WHOLESALE"
) {
pushData.push({
title: "重量",
slot: "weight",
});
}
pushData.push(
{
title: "库存",
slot: "quantity",
},
// {
// title: "库存预警",
// slot: "alertQuantity",
// },
{
title: "货号",
slot: "sn",
},
);
this.skuTableColumn = pushData;
// 生成规格组合
if (this.skuInfo.length > 0) {
const newCombinations = this.generateSkuCombinations(this.skuInfo);
// 保留原有组合的属性值
this.skuTableData = newCombinations.map(combination => {
// 查找匹配的原有组合
const existingCombination = skus.find(item => {
// 检查所有规格项是否匹配
return this.skuInfo.every(info => {
return item[info.name] === combination[info.name];
});
});
if (existingCombination) {
// 保留原有组合的属性值
return {
...combination,
id: existingCombination.id || "",
sn: existingCombination.sn || "",
quantity: this.isECouponGoods
? (existingCombination.quantity ?? 0)
: (existingCombination.quantity || ""),
cost: existingCombination.cost || "",
price: existingCombination.price || (this.baseInfoForm.salesModel === 'WHOLESALE' && this.wholesaleData.length > 0 ? this.wholesaleData[0].price : ""),
weight: existingCombination.weight || ""
};
} else {
// 新组合使用默认值,批发模式下设置默认价格
return {
...combination,
id: "",
sn: "",
quantity: this.defaultSkuQuantity(),
cost: "",
price: this.baseInfoForm.salesModel === 'WHOLESALE' && this.wholesaleData.length > 0 ? this.wholesaleData[0].price : "",
weight: ""
};
}
});
this.assignSkuTableIndex();
}
},
/**
* 迭代属性,形成表格
* result 渲染的数据
* array spec数据
*/
specIterator(result, spec, skus) {
let table = result;
if (spec.length > 0) {
//清除当前循环的分组
let cloneTemp = cloneObj(spec);
cloneTemp.shift();
spec[0].spec_values.forEach((specItem) => {
let index = this.skuIndex;
if (table[index]) {
table[index][spec[0].name] = specItem.value;
} else if (skus && skus[index] && specItem.value !== "") {
let obj = {
...skus[index],
id: skus[index].id,
sn: skus[index].sn,
quantity: skus[index].quantity,
cost: skus[index].cost,
price: skus[index].price,
// [spec[0].name]: skus[index][spec[0].name] ? skus[index][spec[0].name] : specItem.value,
[spec[0].name]: specItem.value,
images:
skus[index].images || this.baseInfoForm.goodsGalleryFiles || [],
};
if (specItem.value !== "") {
obj.id = skus[index].id;
}
if (skus[index].weight !== "") {
obj.weight = skus[index].weight;
}
table.push(obj);
} else if (specItem.value !== "") {
table.push({
[spec[0].name]: specItem.value,
images: this.baseInfoForm.goodsGalleryFiles || [],
});
}
table = this.specIterator(table, cloneTemp, skus, index);
});
} else {
this.skuIndex++;
}
return table;
},
/**
* 生成所有可能的规格组合
* @param {Array} skuInfo 规格信息
* @returns {Array} 所有可能的规格组合
*/
generateSkuCombinations(skuInfo) {
if (!skuInfo || skuInfo.length === 0) {
return [];
}
// 提取每个规格项的规格值
const specValues = skuInfo.map(item => {
return item.spec_values
.filter(val => val.value !== "") // 过滤掉空值
.map(val => ({
name: item.name,
value: val.value,
images: val.images || this.baseInfoForm.goodsGalleryFiles || []
}));
});
// 如果有规格项没有有效的规格值,返回空数组
if (specValues.some(values => values.length === 0)) {
return [];
}
// 递归生成所有组合
const generateCombinations = (index, current) => {
if (index === specValues.length) {
// 基本属性
const combination = {
images: this.baseInfoForm.goodsGalleryFiles || []
};
// 添加规格属性
current.forEach(spec => {
combination[spec.name] = spec.value;
// 如果是第一个规格项且有图片,使用其图片
if (spec.name === skuInfo[0].name) {
combination.images = spec.images;
}
});
return [combination];
}
const results = [];
for (const value of specValues[index]) {
results.push(...generateCombinations(index + 1, [...current, value]));
}
return results;
};
return generateCombinations(0, []);
},
// 判断相同数组的值
scalarArrayEquals(array1, array2) {
return (
array1.length === array2.length &&
array1.every(function (v, i) {
return v === array2[i];
})
);
},
/** 自动完成表单所需方法*/
filterMethod(value, option) {
return option.toUpperCase().indexOf(value.toUpperCase()) !== -1;
},
/** 数据改变之后 抛出数据 */
updateSkuTable(row, item, rowIndex) {
let index = rowIndex;
if (index === undefined || index === null) {
index = row._index;
}
if (index === undefined || index === null) {
index = this.skuTableData.indexOf(row);
}
if (index < 0 || !this.skuTableData[index]) {
return;
}
row._index = index;
this.baseInfoForm.regeneratorSkuFlag = true;
/** 进行自定义校验 判断是否是数字(小数也能通过)重量 */
if (item === "weight") {
if (
!/^[+]{0,1}(\d+)$|^[+]{0,1}(\d+\.\d+)$/.test(row[item]) ||
parseInt(row[item]) < 0 ||
parseInt(row[item]) > 99999999
) {
// 校验未通过 加入错误存储列表中
this.validateError.push([index, item]);
this.validatatxt = "请输入0~99999999之间的数字值";
return;
}
} else if (item === "quantity") {
if (
!/^[0-9]\d*$/.test(row[item]) ||
parseInt(row[item]) < 0 ||
parseInt(row[item]) > 99999999
) {
// 库存
this.validateError.push([index, item]);
this.validatatxt = "请输入0~99999999之间的整数";
return;
}
}
this.$nextTick(() => {
if (this.skuTableData[index]) {
this.skuTableData[index][item] = row[item];
}
});
},
// 店内分类选择
selectTree(v) {
if (v.length > 0) {
// 转换null为""
for (let attr in v[0]) {
if (v[0][attr] == null) {
v[0][attr] = "";
}
}
let str = JSON.stringify(v[0]);
let menu = JSON.parse(str);
this.form = menu;
this.editTitle = menu.title;
}
},
// 店内分类选中
changeSelect(v) {
this.selectCount = v.length;
let ids = "";
v.forEach(function (e) {
ids += e.id + ",";
});
ids = ids.substring(0, ids.length - 1);
if (ids.length > 100) {
this.$Message.error("选择了过多的店铺分类,请谨慎选择");
}
this.baseInfoForm.storeCategoryPath = ids;
},
/** 添加商品 **/
save() {
if (this.containsSameSkuItem) {
this.$Message.error("存在重复规格项!");
return;
}
if (this.containsSameSkuValue) {
this.$Message.error("存在重复规格值!");
return;
}
let checkFlag = false;
let missingParams = [];
this.goodsParams.forEach((param) => {
if (!param || !param.required) return;
const check = this.baseInfoForm.goodsParamsDTOList.some((paramsItem) => {
if (String(paramsItem.paramId) !== String(param.id)) return false;
return !!paramsItem.paramValue;
});
if (!check) {
checkFlag = true;
missingParams.push(param.paramName);
}
});
if (checkFlag) {
this.$Message.error(`以下参数为必填项:${missingParams.join('、')}`);
return;
}
if (!this.selectedCategory || this.selectedCategory.length < 3) {
this.$Message.error("必须选择到三级分类");
return;
}
this.submitLoading = true;
this.$refs["baseInfoForm"].validate((valid) => {
if (valid) {
if (this.baseInfoForm.salesModel === "WHOLESALE") {
for (let i = 0; i < this.wholesaleData.length; i++) {
this.checkWholesaleNum(i);
this.checkWholesalePrice(i);
this.wholesaleData[i].goodsId = this.goodsId;
}
this.baseInfoForm.wholesaleList = this.wholesaleData;
}
// 判断是否是复制商品
if (!this.$route.query.copyId) {
this.baseInfoForm.goodsId = this.goodsId;
}
this.syncIntroFromEditors();
let submit = JSON.parse(JSON.stringify(this.baseInfoForm));
if (
submit.goodsGalleryFiles &&
submit.goodsGalleryFiles.length <= 0
) {
this.submitLoading = false;
this.$Message.error("请上传商品图片");
return;
}
const invalidGalleryFiles = submit.goodsGalleryFiles.filter((url) =>
this.invalidGoodsGalleryFiles.includes(url)
);
if (invalidGalleryFiles.length > 0) {
this.submitLoading = false;
this.$Message.error("存在已失效的商品主图,请删除后重新上传");
return;
}
if (submit.templateId === "") submit.templateId = 0;
if (submit.brandId === "" || submit.brandId == null) submit.brandId = 0;
if (this.isECouponGoods) {
submit.templateId = 0;
submit.salesModel = "RETAIL";
}
let flag = false;
let paramValue = "";
if (flag) {
this.$Message.error(paramValue + " 参数值不能为空");
this.submitLoading = false;
return;
}
if (this.goodsUnitList && !this.goodsUnitList.find(i => i === this.baseInfoForm.goodsUnit)) {
submit.goodsUnit = ""
this.$Message.error("商品单位不存在");
this.submitLoading = false;
return;
}
let skuInfoNames = this.skuInfo.map((n) => n.name);
submit.skuList = [];
let containEmptyImage = false;
this.skuTableData.map((sku) => {
let skuCopy = {
cost: 1,
price: sku.price,
quantity: this.resolveSubmitQuantity(sku),
// alertQuantity: sku.alertQuantity,
sn: sku.sn,
images: [],
};
if (this.openImage) {
this.skuInfo[0].spec_values.forEach(item => {
if (!item.images || item.images.length === 0) {
containEmptyImage = true;
return;
}
if (item.value === sku[this.skuInfo[0].name]) {
skuCopy.images = item.images
}
})
}
if (sku.weight) {
skuCopy.weight = sku.weight;
}
if (this.baseInfoForm.weight) {
skuCopy.weight = this.baseInfoForm.weight;
}
if (sku.id) {
skuCopy.id = sku.id;
}
for (let skuInfoName of skuInfoNames) {
skuCopy[skuInfoName] = sku[skuInfoName];
}
submit.skuList.push(skuCopy);
});
if (containEmptyImage) {
this.$Message.error("开启规格图片,所有规格图片不能为空!");
this.submitLoading = false;
return;
}
if (submit.goodsGalleryFiles.length > 0) {
submit.goodsGalleryList = submit.goodsGalleryFiles.map(
(i) => i
);
}
/** 参数校验 **/
/* Object.keys(submit.goodsParamsList).forEach((item) => {
});*/
submit.release ? (submit.release = true) : (submit.release = false);
submit.recommend
? (submit.recommend = true)
: (submit.recommend = false);
// 判断是否是复制商品
if (this.goodsId && !this.$route.query.copyId) {
API_GOODS.editGoods(this.goodsId, submit).then((res) => {
if (res.success) {
this.submitLoading = false;
this.$router.go(-1);
} else {
this.submitLoading = false;
}
}).catch(() => {
this.submitLoading = false;
this.$Message.error("保存失败,请重试");
});
} else {
API_GOODS.createGoods(submit).then((res) => {
if (res.success) {
this.submitLoading = false;
this.$parent.activestep = 2;
window.scrollTo(0, 0);
} else {
this.submitLoading = false;
}
});
}
} else {
this.submitLoading = false;
this.$Message.error("还有必填项未做处理,请检查表单");
}
});
},
GET_ShipTemplate(type) {
// 获取物流模板
API_Shop.getShipTemplate().then((res) => {
if (res.success) {
this.logisticsTemplate = res.result;
}
if (type === 'localRefresh' && res.success) {
this.$Message.success("刷新成功");
} else if (type === 'localRefresh') {
this.$Message.error("刷新失败,请重试");
}
});
}
},
mounted() {
this.accessToken = {
accessToken: this.getStore("accessToken"),
};
this.GET_ShipTemplate()
this.loadCategoryList();
if (this.$route.query.id || this.$route.query.draftId) {
// 编辑商品、模板
this.GET_GoodData(this.$route.query.id, this.$route.query.draftId);
} else if (this.$route.query.copyId) {
// 复制商品
this.GET_GoodData(this.$route.query.copyId);
} else if (this.firstData.tempId) {
// 选择模板
this.GET_GoodData("", this.firstData.tempId);
} else {
// 新增商品
this.GET_GoodsUnit();
this.GET_ShopGoodsLabel();
}
},
};
</script>
<style lang="scss" scoped>
/* 规格值内联错误提示样式 */
.sku-inline-error {
font-size: 12px;
line-height: 16px;
color: #ed4014; /* 与 iview 错误色系一致 */
margin-left: 4px;
margin-top: 4px;
white-space: nowrap;
}
@import "./addGoods.scss";
.tree-bar.is-empty {
min-height: auto !important;
}
.tree-bar :deep(.el-tree__empty-block) {
display: flex;
align-items: center;
justify-content: flex-start;
min-height: 32px;
height: auto;
width: auto;
}
.tree-bar :deep(.el-tree__empty-text),
.shop-category-empty {
position: static;
left: auto;
top: auto;
transform: none;
white-space: nowrap;
writing-mode: horizontal-tb;
}
</style>
<style scoped>
.el-select .el-select-dropdown {
overflow: hidden !important;
}
.editor-alert {
text-align: left;
}
.intro-editor-field {
width: calc(100% - 400px);
}
.promise-intro-btn {
margin-top: 12px;
text-align: left;
}
.footer {
display: flex;
justify-content: center;
}
.footer-btns {
display: flex;
gap: 12px;
justify-content: center;
}
/* .tox-notifications-container{
display: none !important;
} */
.goods-video-form-item {
align-items: center;
}
.goods-video-form-item--multi {
align-items: flex-start;
}
.goods-video-form-item--multi :deep(.el-form-item__label) {
padding-top: 8px;
}
.goods-video-form-item :deep(.el-form-item__content) {
display: flex;
align-items: center;
}
.goods-video-form-item--multi :deep(.el-form-item__content) {
align-items: flex-start;
}
.goods-video {
align-items: flex-start;
display: flex;
flex-direction: column;
width: 100%;
}
.goods-video-preview {
display: flex;
align-items: center;
}
.goods-video-actions {
display: flex;
align-items: center;
gap: 10px;
min-height: 32px;
}
.goods-video-actions--with-preview {
margin-top: 8px;
}
.goods-video-actions :deep(.el-upload) {
display: inline-flex;
align-items: center;
vertical-align: middle;
}
.mb-10 {
margin-bottom: 10px;
}
.view-video {
margin: 0 10px;
}
.refresh-icon {
margin-left: 10px;
}
.wholesale-rule-form-item {
width: 100% !important;
min-width: 900px;
}
.wholesale-preview-table {
width: 480px;
}
.wholesale-preview-table :deep(.el-table__cell .cell) {
overflow: visible;
white-space: nowrap;
}
.wholesale-rule-form-item :deep(.el-form-item__content) {
width: 100%;
}
.wholesale-num-input {
width: 190px;
}
.wholesale-num-input :deep(.el-input__wrapper) {
min-width: 140px;
}
.wholesale-price-cell {
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
.wholesale-price-input {
width: 190px;
}
.wholesale-price-del {
position: absolute;
top: 50%;
left: 50%;
margin-left: 100px;
transform: translateY(-50%);
}
.goods-type-option {
display: flex;
flex-direction: column;
line-height: 1.4;
padding: 2px 0;
}
.goods-type-option-label {
color: #303133;
}
.goods-type-option-desc {
color: #909399;
font-size: 12px;
}
</style>
<style lang="scss">
.goods-type-select-popper {
.el-select-dropdown__item {
height: auto;
min-height: 58px;
padding: 10px 12px;
line-height: 1.5;
white-space: normal;
}
.goods-type-option {
display: flex;
flex-direction: column;
gap: 4px;
}
.goods-type-option-label {
color: #303133;
font-size: 14px;
line-height: 1.4;
}
.goods-type-option-desc {
color: #909399;
font-size: 12px;
line-height: 1.5;
white-space: normal;
}
}
</style>