option.js 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970
  1. define({
  2. template: `
  3. <div>
  4. <div class="vue-main-title">
  5. <div class="vue-main-title-left"></div>
  6. <div class="vue-main-title-content">商品规格</div>
  7. </div>
  8. <div class="option-box">是否启用商品规格:<el-switch v-model="has_option"></el-switch> <el-button type="text" @click="validate"></el-button></div>
  9. <div class="tips-box">
  10. 1、启用商品规格后,商品的价格及库存以商品规格为准 <br/>
  11. 2、拼团活动创建后的商品,请勿对该商品进行修改规格、下架、删除等操作,否则影响下单购买,活动结束可正常编辑该商品!<br/>
  12. 3、每一种规格代表不同型号,例如颜色为一种规格,尺寸为一种规格,如果设置多规格,手机用户必须每一种规格都选择一个规格项,才能添加购物车或购买。<br/>
  13. </div>
  14. <el-form label-width="100px" v-if="has_option">
  15. <el-form-item label="商品规格">
  16. <div class="goods-spec_info-forms">
  17. <div class="goods-spec_info-form" v-for="(specItem,specItemIndex) in tableColumnList.goodsSpecs" :key="specItemIndex">
  18. <div class="goods-spec_info_form-item">
  19. <div class="goods-sped_info_form-name">
  20. 规格名:
  21. </div>
  22. <div class="goods-spec_info_form-content">
  23. <el-input v-model.trim="specItem.attr" ref="specValueInput" placeholder="(比如颜色)" @focus="attrFocus(specItem.attr)" @blur="attrBlur(specItem.attr)"></el-input>
  24. <!-- <div>
  25. <el-checkbox>添加规格图片</el-checkbox>
  26. <el-checkbox>规格图片展示在商详页和规格选择页</el-checkbox>
  27. </div> -->
  28. </div>
  29. </div>
  30. <div class="goods-spec_info_form-item">
  31. <div class="goods-sped_info_form-name">
  32. 规格值:
  33. </div>
  34. <div class="goods-spec_info_form-content">
  35. <div class="goods-spec_info-values">
  36. <div class="goods-spec_info_value-item" v-for="(valueItem,valueItemIndex) in specItem.valueList" :key="valueItemIndex">
  37. <el-input v-model.trim="specItem.valueList[valueItemIndex].title" ref="attrValueInput" @focus="attrValueFocus(specItem.valueList[valueItemIndex].title)" @blur="newAttrValueBlur(specItem.attr, specItem.valueList[valueItemIndex].title,specItemIndex,valueItemIndex)"></el-input>
  38. <!-- <div
  39. class="upload-boxed"
  40. @click="selectedingImageSpecIndex=specItemIndex;selectedingImageSpecValueIndex=valueItemIndex;showSelectSpecImageDialog=!showSelectSpecImageDialog"
  41. >
  42. <img :src="valueItem.thumb" v-show="valueItem.thumb" />
  43. <div class="el-icon-plus" v-show="!valueItem.thumb"></div>
  44. </div> -->
  45. <div class="el-icon-close goods-spec_info_value-remove" v-if="specItem.valueList.length > 1" @click="removeSpecValue(specItemIndex,valueItemIndex, specItem.attr, specItem.valueList[valueItemIndex].title)"></div>
  46. </div>
  47. <div class="goods-spec_info_value-item">
  48. <el-button @click="addSpecValue(specItemIndex)" :disabled="specItem.valueList.filter(value => value.title === '').length > 0">添加规格值</el-button>
  49. </div>
  50. </div>
  51. <!-- <div style="line-height:20px;color:#ccc;">
  52. 仅支持为第一组规格设置规格图片(最多40张图),买家选择不同规格会看到对应规格图片,建议尺寸:800 x 800像素
  53. </div> -->
  54. </div>
  55. </div>
  56. <div class="el-icon-close goods-spec_info-remove" @click="removeSpec(specItemIndex)"></div>
  57. </div>
  58. <div class="goods-spec_info-form" style="margin-bottom: 0;">
  59. <div class="goods-spec_info_form-item">
  60. <el-popover placement="top" width="240" v-model="add_popover_bool" @after-enter="$refs.addValueInput.focus()">
  61. <div style="display: flex; grid-gap: 10px;">
  62. <el-input ref="addValueInput" v-model.trim="add_value" @keyup.enter.native="addSpec()" />
  63. <el-button type="primary" @click="addSpec()">确定</el-button>
  64. </div>
  65. <el-button slot="reference" type="text" style="margin-right: 75px;">添加规格项</el-button>
  66. </el-popover>
  67. <el-button type="text" @click="clearALL">清空全部规格项</el-button>
  68. </div>
  69. </div>
  70. </div>
  71. </el-form-item>
  72. <el-form-item label="批量设置">
  73. <div class="goods-spec_info-forms">
  74. <el-popover placement="top" width="240" trigger="click">
  75. <div style="display: flex; grid-gap: 10px;">
  76. <el-input placeholder="请输入库存" v-model.trim="batch_input" @keyup.enter.native="batchSet('stock')" />
  77. <el-button type="primary" @click="batchSet('stock')">确定</el-button>
  78. </div>
  79. <el-button slot="reference" type="text" style="margin-right: 25px;">设置库存</el-button>
  80. </el-popover>
  81. <el-popover placement="top" width="240" trigger="click">
  82. <div style="display: flex; grid-gap: 10px;">
  83. <el-input placeholder="请输入市场价格" v-model.trim="batch_input" @keyup.enter.native="batchSet('market_price')" />
  84. <el-button type="primary" @click="batchSet('market_price')">确定</el-button>
  85. </div>
  86. <el-button slot="reference" type="text" style="margin-right: 25px;">设置市场价格</el-button>
  87. </el-popover>
  88. <el-popover placement="top" width="240" trigger="click">
  89. <div style="display: flex; grid-gap: 10px;">
  90. <el-input placeholder="请输入现价" v-model.trim="batch_input" @keyup.enter.native="batchSet('product_price')" />
  91. <el-button type="primary" @click="batchSet('product_price')">确定</el-button>
  92. </div>
  93. <el-button slot="reference" type="text" style="margin-right: 25px;">设置现价</el-button>
  94. </el-popover>
  95. <el-popover placement="top" width="240" trigger="click">
  96. <div style="display: flex; grid-gap: 10px;">
  97. <el-input placeholder="请输入成本价格" v-model.trim="batch_input" @keyup.enter.native="batchSet('cost_price')" />
  98. <el-button type="primary" @click="batchSet('cost_price')">确定</el-button>
  99. </div>
  100. <el-button slot="reference" type="text" style="margin-right: 25px;">设置成本价格</el-button>
  101. </el-popover>
  102. <el-popover placement="top" width="240" trigger="click">
  103. <div style="display: flex; grid-gap: 10px;">
  104. <el-input placeholder="请输入商品编码" v-model.trim="batch_input" @keyup.enter.native="batchSet('goods_sn')" />
  105. <el-button type="primary" @click="batchSet('goods_sn')">确定</el-button>
  106. </div>
  107. <el-button slot="reference" type="text" style="margin-right: 25px;">设置商品编码</el-button>
  108. </el-popover>
  109. <el-popover placement="top" width="240" trigger="click">
  110. <div style="display: flex; grid-gap: 10px;">
  111. <el-input placeholder="请输入商品条码" v-model.trim="batch_input" @keyup.enter.native="batchSet('product_sn')" />
  112. <el-button type="primary" @click="batchSet('product_sn')">确定</el-button>
  113. </div>
  114. <el-button slot="reference" type="text" style="margin-right: 25px;">设置商品条码</el-button>
  115. </el-popover>
  116. <el-popover placement="top" width="240" trigger="click">
  117. <div style="display: flex; grid-gap: 10px;">
  118. <el-input placeholder="请输入重量" v-model.trim="batch_input" @keyup.enter.native="batchSet('weight')" />
  119. <el-button type="primary" @click="batchSet('weight')">确定</el-button>
  120. </div>
  121. <el-button slot="reference" type="text" style="margin-right: 25px;">设置重量</el-button>
  122. </el-popover>
  123. <el-popover placement="top" width="240" trigger="click">
  124. <div style="display: flex; grid-gap: 10px;">
  125. <el-input placeholder="请输入体积" v-model.trim="batch_input" @keyup.enter.native="batchSet('volume')" />
  126. <el-button type="primary" @click="batchSet('volume')">确定</el-button>
  127. </div>
  128. <el-button slot="reference" type="text" style="margin-right: 25px;">设置体积</el-button>
  129. </el-popover>
  130. <el-button type="text" @click="openSortDialog" >自定义规格排序</el-button>
  131. </div>
  132. </el-form-item>
  133. <el-form-item label="规格明细">
  134. <el-table :data="tableColumnList.table_data" border :span-method="mergeRowComputed">
  135. <el-table-column :label="specItem.attr" v-for="(specItem,itemIndex) in tableColumnList.goodsSpecs" :key="itemIndex" :prop="specItem.attr">
  136. <template slot-scope="scope">
  137. <div>{{scope.row[specItem.attr]}}</div>
  138. </template>
  139. </el-table-column>
  140. <el-table-column label="库存" prop="stock">
  141. <template slot-scope="scope">
  142. <el-input v-model="scope.row.stock" :ref="'stockValueInput' + [[scope.$index]]" placeholder="0"></el-input>
  143. </template>
  144. </el-table-column>
  145. <el-table-column label="预扣库存" prop="withhold_stock">
  146. <template slot-scope="scope">
  147. <el-input v-model="scope.row.withhold_stock" disabled placeholder="0"></el-input>
  148. </template>
  149. </el-table-column>
  150. <el-table-column label="市场价格" prop="market_price">
  151. <template slot-scope="scope">
  152. <el-input v-model="scope.row.market_price" :disabled="readonly" placeholder="0.00"></el-input>
  153. </template>
  154. </el-table-column>
  155. <el-table-column label="现价" prop="product_price">
  156. <template slot-scope="scope">
  157. <el-input v-model="scope.row.product_price" :disabled="readonly" :ref="'priceValueInput' + [[scope.$index]]" placeholder="0.00"></el-input>
  158. </template>
  159. </el-table-column>
  160. <el-table-column label="成本价" prop="cost_price">
  161. <template slot-scope="scope">
  162. <el-input v-model="scope.row.cost_price" :disabled="readonly" placeholder="0.00"></el-input>
  163. </template>
  164. </el-table-column>
  165. <el-table-column label="商品编码" prop="goods_sn">
  166. <template slot-scope="scope">
  167. <el-input v-model="scope.row.goods_sn" ></el-input>
  168. </template>
  169. </el-table-column>
  170. <el-table-column label="商品条码" prop="product_sn">
  171. <template slot-scope="scope">
  172. <el-input v-model="scope.row.product_sn" ></el-input>
  173. </template>
  174. </el-table-column>
  175. <el-table-column label="重量(克)" prop="weight">
  176. <template slot-scope="scope">
  177. <el-input v-model="scope.row.weight" placeholder="0.000"></el-input>
  178. </template>
  179. </el-table-column>
  180. <el-table-column label="体积(m³)" prop="volume">
  181. <template slot-scope="scope">
  182. <el-input v-model="scope.row.volume" placeholder="0.000" ></el-input>
  183. </template>
  184. </el-table-column>
  185. <el-table-column label="图片(300*300)" prop="thumb">
  186. <template slot-scope="scope">
  187. <i class="el-icon-circle-close" v-show="scope.row.thumb" style="top: -3px;left: 70px;position: relative;z-index: 10;" @click="delUploadImg(scope.$index)"></i>
  188. <div class="upload-boxed spec-image" @click="selectedingImageSpecIndex=scope.$index;showSelectSpecImageDialog=!showSelectSpecImageDialog;materialType = '1'">
  189. <img
  190. :src="scope.row.thumb"
  191. style="width: 60px; height: 60px; border-radius: 5px; cursor: pointer;object-fit:cover;"
  192. v-show="scope.row.thumb"
  193. />
  194. <div class="el-icon-plus" v-show="!scope.row.thumb"></div>
  195. </div>
  196. </template>
  197. </el-table-column>
  198. </el-table>
  199. <p>数量:{{ specCounts }}条(建议不超过100条)</p>
  200. </el-form-item>
  201. </el-form>
  202. <el-dialog :visible.sync="sortDialogVisible" width="40%">
  203. <template slot="title">
  204. <div style="font-size: 14px;">
  205. 拖动规格项或规格值进行排序
  206. </div>
  207. </template>
  208. <div>
  209. <draggable v-model="sortGoodsSpecs">
  210. <ul class="attribute-item" v-for="(specItem,itemIndex) in sortGoodsSpecs" :key="itemIndex">
  211. <li class="attribute-title">
  212. <div>{{specItem.attr}}</div>
  213. </li>
  214. <li class="attribute-spec">
  215. <draggable v-model="specItem.valueList">
  216. <span class="specItem-span" v-for="(valueItem,valueItemIndex) in specItem.valueList" :key="valueItemIndex">{{valueItem.title}}</span>
  217. </draggable>
  218. </li>
  219. </ul>
  220. </draggable>
  221. </div>
  222. <span slot="footer" class="dialog-footer">
  223. <el-button @click="sortDialogVisible = false">取 消</el-button>
  224. <el-button type="primary" @click="sureSortDialog">确 定</el-button>
  225. </span>
  226. </el-dialog>
  227. <upload-multimedia-img
  228. :upload-show="showSelectSpecImageDialog"
  229. :type="materialType"
  230. name="spec"
  231. selNum="one"
  232. @replace="showSelectSpecImageDialog = !showSelectSpecImageDialog"
  233. @sure="selectSpecImage"
  234. ></upload-multimedia-img>
  235. </div>
  236. `,
  237. style: `
  238. .attribute-item {
  239. display: flex;
  240. flex-direction: column;
  241. padding: 0 0 18px 18px;
  242. font-size: 14px;
  243. color: #101010;
  244. border-bottom: 1px solid #e8e8e8;
  245. }
  246. .attribute-title {
  247. font-weight: bold;
  248. }
  249. .specItem-span {
  250. display: inline-block;
  251. color: #FFF;
  252. background-color: #29BA9C;
  253. padding: 4px 10px;
  254. cursor: move;
  255. margin: 10px 0 0 10px;
  256. }
  257. .option-box {
  258. font-weight: bold;
  259. margin: 20px 0 20px 28px;
  260. }
  261. .tips-box {
  262. font-size: 12px;
  263. color: #737373;
  264. margin: 20px 0 25px 28px;
  265. }
  266. .goods-spec_info-forms {
  267. padding:15px 10px;
  268. border:1px solid #eee;
  269. border-radius:2px;
  270. }
  271. .goods-spec_info-form {
  272. position:relative;
  273. }
  274. .goods-spec_info-remove {
  275. visibility:hidden;
  276. position:absolute;
  277. top:0;
  278. right:0;
  279. margin:3px;
  280. padding:5px;
  281. color:white;
  282. border-radius:50%;
  283. background:rgba(0,0,0,0.5);
  284. cursor:pointer;
  285. }
  286. .goods-spec_info-form:hover .goods-spec_info-remove {
  287. visibility:visible;
  288. }
  289. .goods-spec_info_form-item:first-child {
  290. background-color:#fafafa;
  291. }
  292. .goods-spec_info_form-item {
  293. display:flex;
  294. column-gap:15px;
  295. margin-bottom:15px;
  296. padding:10px;
  297. }
  298. .goods-sped_info_form-name {
  299. flex-shrink:0;
  300. }
  301. .goods-spec_info_form-content {
  302. flex-shrink:0;
  303. width:80%;
  304. }
  305. .goods-spec_info_form-content .upload-boxed {
  306. margin:10px 0;
  307. width:100%;
  308. height:unset;
  309. text-align:center;
  310. }
  311. .goods-spec_info_form-content .upload-boxed .el-icon-plus {
  312. width:60px;
  313. height:60px;
  314. line-height:60px;
  315. font-size:14px;
  316. border-bottom:1px dashed #ccc;
  317. }
  318. .goods-spec_info_form-content .upload-boxed img {
  319. width:60px;
  320. height:60px;
  321. }
  322. .goods-spec_info_value-item {
  323. display:inline-block;
  324. position:relative;
  325. vertical-align:top;
  326. margin:0 10px 10px 0;
  327. width:150px;
  328. }
  329. .goods-spec_info_value-remove {
  330. position:absolute;
  331. right:-5px;
  332. top:-5px;
  333. font-size:16px;
  334. background-color:white;
  335. border-radius:50%;
  336. cursor:pointer;
  337. visibility: hidden;
  338. }
  339. .goods-spec_info_value-item:hover .goods-spec_info_value-remove {
  340. visibility:visible;
  341. }
  342. .spec-image.upload-boxed {
  343. width:60px;
  344. height:62px;
  345. }
  346. .spec-image.upload-boxed .el-icon-plus {
  347. width:60px;
  348. line-height:60px;
  349. border-bottom:1px dashed #ccc;
  350. box-sizing:border-box;
  351. background-color:white;
  352. }
  353. `,
  354. props: ["form"],
  355. mounted() {
  356. // console.log(this.form)
  357. let test_data = {
  358. "has_option": 1,
  359. "specs": [
  360. {
  361. "id": 31,
  362. "title": "电池",
  363. "goods_id": 222,
  364. "spec_item": [
  365. {
  366. "id": 61,
  367. "specid": 31,
  368. "title": "一号"
  369. },
  370. {
  371. "id": 62,
  372. "specid": 31,
  373. "title": "二号"
  374. }
  375. ]
  376. },
  377. {
  378. "id": 32,
  379. "title": "类型",
  380. "goods_id": 222,
  381. "spec_item": [
  382. {
  383. "id": 63,
  384. "specid": 32,
  385. "title": "大功率"
  386. },
  387. {
  388. "id": 64,
  389. "specid": 32,
  390. "title": "小功率"
  391. }
  392. ]
  393. }
  394. ],
  395. "option": [
  396. {
  397. "id": 84,
  398. "uniacid": 6,
  399. "goods_id": 222,
  400. "title": "一号+大功率",
  401. "thumb": "",
  402. "product_price": "750.00",
  403. "market_price": "300.00",
  404. "cost_price": "100.00",
  405. "stock": 10,
  406. "weight": "0.00",
  407. "display_order": 0,
  408. "specs": "61_63",
  409. "skuId": "",
  410. "goods_sn": "",
  411. "product_sn": "",
  412. "virtual": 0,
  413. "red_price": "",
  414. "volume": "0.000",
  415. "withhold_stock": 0,
  416. "电池": "一号",
  417. "类型": "大功率"
  418. },
  419. {
  420. "id": 85,
  421. "uniacid": 6,
  422. "goods_id": 222,
  423. "title": "一号+小功率",
  424. "thumb": "",
  425. "product_price": "750.00",
  426. "market_price": "300.00",
  427. "cost_price": "100.00",
  428. "stock": 10,
  429. "weight": "0.00",
  430. "display_order": 0,
  431. "specs": "61_64",
  432. "skuId": "",
  433. "goods_sn": "",
  434. "product_sn": "",
  435. "virtual": 0,
  436. "red_price": "",
  437. "volume": "0.000",
  438. "withhold_stock": 0,
  439. "电池": "一号",
  440. "类型": "小功率"
  441. },
  442. {
  443. "id": 86,
  444. "uniacid": 6,
  445. "goods_id": 222,
  446. "title": "二号+大功率",
  447. "thumb": "",
  448. "product_price": "750.00",
  449. "market_price": "300.00",
  450. "cost_price": "100.00",
  451. "stock": 10,
  452. "weight": "0.00",
  453. "display_order": 0,
  454. "specs": "62_63",
  455. "skuId": "",
  456. "goods_sn": "",
  457. "product_sn": "",
  458. "virtual": 0,
  459. "red_price": "",
  460. "volume": "0.000",
  461. "withhold_stock": 0,
  462. "电池": "二号",
  463. "类型": "大功率"
  464. },
  465. {
  466. "id": 87,
  467. "uniacid": 6,
  468. "goods_id": 222,
  469. "title": "二号+小功率",
  470. "thumb": "",
  471. "product_price": "750.00",
  472. "market_price": "300.00",
  473. "cost_price": "100.00",
  474. "stock": 10,
  475. "weight": "0.00",
  476. "display_order": 0,
  477. "specs": "62_64",
  478. "skuId": "",
  479. "goods_sn": "",
  480. "product_sn": "",
  481. "virtual": 0,
  482. "red_price": "",
  483. "volume": "0.000",
  484. "withhold_stock": 0,
  485. "电池": "二号",
  486. "类型": "小功率"
  487. }
  488. ]
  489. }
  490. this.editHandleData(this.form); // test_data测试数据
  491. },
  492. data() {
  493. return {
  494. readonly:readonly,
  495. materialType:"",
  496. has_option: false,
  497. // 表单
  498. tableHeaderList: ['stock', 'withhold_stock', 'market_price', 'product_price', 'cost_price', 'goods_sn', 'product_sn', 'weight', 'volume', 'thumb', ], // 表格列
  499. tableColumnList: {
  500. goodsSpecs: [],
  501. table_data: [], // 表格中的数据
  502. },
  503. add_value: "", // 添加属性的input
  504. add_popover_bool: false, // 添加属性的小弹窗
  505. showSelectSpecImageDialog: false,
  506. selectedingImageSpecIndex: null, // 获取上传图片的表格索引
  507. // selectedingImageSpecValueIndex: null,
  508. sp_id: -1, // 新增table_data的id
  509. sc_id: -1, // 新增规格名的id
  510. specCounts: 0,
  511. batch_input: "",
  512. sortDialogVisible: false,
  513. sortGoodsSpecs: []
  514. };
  515. },
  516. methods: {
  517. editHandleData(data) {
  518. // 处理回显的数据
  519. this.has_option = Number(data.has_option) == 0 ? false : true;
  520. for (let i = 0; i < data.specs.length; i++) {
  521. this.tableColumnList.goodsSpecs.push({
  522. id: data.specs[i].id,
  523. attr: data.specs[i].title,
  524. valueList: data.specs[i].spec_item
  525. })
  526. // 循环旧数据 规格项都对应的数据应该赋给新数据
  527. this.changeOption(data.option)
  528. }
  529. },
  530. addSpec() {
  531. if (!this.add_value) return
  532. if (this.selectedAttr.includes(this.add_value)){
  533. this.$message({
  534. message: '不能添加相同规格名',
  535. type: 'warning'
  536. });
  537. return
  538. }
  539. this.sc_id++;
  540. this.tableColumnList.goodsSpecs.push({
  541. id: 'SC'+this.sc_id,
  542. attr: this.add_value,
  543. valueList: [{
  544. "id": 'SV0&' + 'SC'+this.sc_id,
  545. "specid": 'SC'+this.sc_id,
  546. "title": ""
  547. }],
  548. });
  549. // 生成处理表头数据和表格数据
  550. this.generateTableColumn()
  551. this.traverseSku()
  552. this.add_popover_bool = false
  553. this.add_value = ''
  554. },
  555. // 属性获得焦点时 得到旧的值 存一下
  556. attrFocus(oldVal) {
  557. old_attr = oldVal
  558. },
  559. // 属性失去焦点时
  560. attrBlur(newVal) {
  561. console.log('attrBlur')
  562. // 如果 '新值等于旧值' 或者 '空' 什么也不做
  563. if (newVal === old_attr || newVal === '') return
  564. // 生成处理表头数据和表格数据
  565. this.generateTableColumn()
  566. this.traverseSku('old_attr')
  567. },
  568. // 删除属性
  569. removeSpec(specItemIndex) {
  570. this.tableColumnList.goodsSpecs.splice(specItemIndex, 1);
  571. // 生成处理表头数据和表格数据
  572. this.generateTableColumn()
  573. this.traverseSku()
  574. },
  575. async addSpecValue(specItemIndex) {
  576. this.tableColumnList.goodsSpecs[specItemIndex].valueList.push({
  577. "id": 'SV'+ this.tableColumnList.goodsSpecs[specItemIndex].valueList.length + '&' + this.tableColumnList.goodsSpecs[specItemIndex].id,
  578. "specid": this.tableColumnList.goodsSpecs[specItemIndex].id,
  579. "title": ""
  580. });
  581. // 让新增的输入框获得焦点
  582. await this.$nextTick()
  583. this.$refs.attrValueInput[this.$refs.attrValueInput.length - 1].focus()
  584. },
  585. // 属性值获得焦点时 得到旧的值 在输入框失去焦点的时候, 如果值没有变化, 则什么也不做
  586. attrValueFocus(oldVal) {
  587. old_attr_value = oldVal
  588. },
  589. // 属性值失去焦点时, 操作表格数据 (新版本 可以实现无限个规格)
  590. newAttrValueBlur(curr_attr, newVal, specItemIndex, valueItemIndex) {
  591. if (curr_attr === '') return
  592. if (newVal === old_attr_value) return
  593. let value_num = this.tableColumnList.goodsSpecs[specItemIndex].valueList.filter(item => item.title == newVal)
  594. if ( value_num.length > 1 ) {
  595. this.$message({
  596. message: `规格值不能重复`,
  597. type: 'warning'
  598. });
  599. this.tableColumnList.goodsSpecs[specItemIndex].valueList[valueItemIndex].title = "";
  600. return
  601. }
  602. // 这里根据规格生成的笛卡尔积计算出table中需要改变的行索引 ( 包含新增和修改 )
  603. let cartesian_arr = this.generateBaseData(this.tableColumnList.goodsSpecs)
  604. // console.log(cartesian_arr)
  605. let change_idx_arr = [] // 需要被改变的行的索引
  606. for (let i in cartesian_arr) {
  607. if (cartesian_arr[i][curr_attr] === newVal) change_idx_arr.push(Number(i))
  608. }
  609. console.log('change_idx_arr', change_idx_arr)
  610. // 新的表格应该有的长度与现有的表格长度比较, 区分新增还是修改
  611. let length_arr = this.tableColumnList.goodsSpecs.map(x => x.valueList.length)
  612. let new_table_length = length_arr.reduce((acc, curr_item) => acc * curr_item) // 新的表格数据长度 求乘积
  613. let old_table_length = this.tableColumnList.table_data.length // 旧的表格数据长度
  614. this.specCounts = new_table_length;
  615. // 如果是修改
  616. if (new_table_length === old_table_length) {
  617. this.tableColumnList.table_data.forEach((item, index) => {
  618. if (change_idx_arr.includes(index)) this.tableColumnList.table_data[index][curr_attr] = newVal
  619. })
  620. return
  621. }
  622. // 如果是新增
  623. if (new_table_length > old_table_length) {
  624. // 得到当前属性的当前值和其他规格的 goodsSpecs, 构造新的表格数据
  625. let other_sku_arr = this.tableColumnList.goodsSpecs.map(item => {
  626. if (item.attr !== curr_attr) return item
  627. else {
  628. return { id: this.tableColumnList.goodsSpecs[specItemIndex].id, attr: item.attr, valueList: [{
  629. "id": 'SV'+ this.tableColumnList.goodsSpecs[specItemIndex].valueList.length + '&' + this.tableColumnList.goodsSpecs[specItemIndex].id,
  630. "specid": this.tableColumnList.goodsSpecs[specItemIndex].id,
  631. "title": newVal
  632. }] }
  633. }
  634. })
  635. // 得到新增的表格数据
  636. let ready_map = this.generateBaseData(other_sku_arr)
  637. let new_table_data = this.mergeTableData(ready_map)
  638. change_idx_arr.forEach((item_idx, index) => {
  639. this.tableColumnList.table_data.splice(item_idx, 0, new_table_data[index])
  640. })
  641. this.tableColumnList.table_data.reverse().reverse()
  642. }
  643. },
  644. removeSpecValue(specItemIndex, valueItemIndex, attr_name, attr_val) {
  645. this.tableColumnList.goodsSpecs[specItemIndex]["valueList"].splice(valueItemIndex, 1);
  646. // 删除table行
  647. let data_length = this.tableColumnList.table_data.length
  648. for (let i = 0; i < data_length; i++) {
  649. if (this.tableColumnList.table_data[i][attr_name] == attr_val) {
  650. this.tableColumnList.table_data.splice(i, 1)
  651. i = i - 1
  652. data_length = data_length - 1
  653. }
  654. }
  655. this.specCounts = this.tableColumnList.table_data.length;
  656. },
  657. // 根据 `this.tableColumnList.goodsSpecs` 生成表格列, `tableHeaderList` 用于 el-table-column 的 v-for
  658. generateTableColumn() {
  659. this.tableHeaderList = this.tableColumnList.goodsSpecs.map(x => x.attr).concat(['stock', 'withhold_stock', 'market_price', 'product_price', 'cost_price', 'goods_sn', 'product_sn', 'weight', 'volume', 'thumb', ])
  660. },
  661. // 合并 goodsSpecs 与 '现价', '库存', '市场价格' , 返回整个表格数据数组
  662. mergeTableData(arr) {
  663. return arr.map((item) => {
  664. this.sp_id++;
  665. return { ...item, id: 'SP'+ this.sp_id, 'stock': '', 'withhold_stock': '', 'market_price': '', 'product_price': '', 'cost_price': '', 'goods_sn': '', 'product_sn': '', 'weight': '', 'volume': '', 'thumb': '', }
  666. })
  667. },
  668. // 已有数据的修改后,合并 goodsSpecs 与 '现价', '库存', '市场价格' , 返回整个表格数据数组,(已有数据的字段值不置为空 '')
  669. mergeSaveTableData(arr,list) {
  670. return arr.map((item,index) => {
  671. this.sp_id++;
  672. return { ...item, id: 'SP'+ this.sp_id, 'stock': list[index].stock, 'withhold_stock': list[index].withhold_stock, 'market_price': list[index].market_price, 'product_price': list[index].product_price, 'cost_price': list[index].cost_price, 'goods_sn': list[index].goods_sn, 'product_sn': list[index].product_sn, 'weight': list[index].weight, 'volume': list[index].volume, 'thumb': list[index].thumb, }
  673. })
  674. },
  675. // 遍历 `goodsSpecs` 生成表格数据
  676. traverseSku(type) {
  677. let ready_map = this.generateBaseData(this.tableColumnList.goodsSpecs)
  678. this.tableColumnList.table_data = type == 'old_attr' ? this.mergeSaveTableData(ready_map,this.tableColumnList.table_data) : this.mergeTableData(ready_map)
  679. this.specCounts = this.tableColumnList.table_data.length;
  680. },
  681. // 重新实现笛卡尔积 入参是: this.tableColumnList.goodsSpecs 传入的数组 '为空', '长度为1', '长度大于1' 三种情况 分别处理
  682. generateBaseData(arr) {
  683. if (arr.length === 0) return []
  684. if (arr.length === 1) {
  685. let [item_spec] = arr
  686. return item_spec.valueList.map(x => {
  687. return {
  688. [item_spec.attr]: x.title
  689. }
  690. })
  691. }
  692. if (arr.length >= 1) {
  693. return arr.reduce((accumulator, spec_item) => {
  694. // accumulator判断是之前整合的规格数组还是单个规格项
  695. let acc_value_list = Array.isArray(accumulator.valueList) ? accumulator.valueList : accumulator
  696. let item_value_list = spec_item.valueList
  697. let result = []
  698. for (let i in acc_value_list) {
  699. for (let j in item_value_list) {
  700. let temp_data = {}
  701. if (!acc_value_list[i].title) {
  702. // accumulator不是Array的情况
  703. temp_data = {
  704. ...acc_value_list[i],
  705. [spec_item.attr]: item_value_list[j].title
  706. }
  707. // 否则如果是单个规格项
  708. } else {
  709. temp_data[accumulator.attr] = acc_value_list[i].title
  710. temp_data[spec_item.attr] = item_value_list[j].title
  711. }
  712. result.push(temp_data)
  713. }
  714. }
  715. return result
  716. })
  717. }
  718. },
  719. // 合并单元格
  720. mergeRowComputed({ row, column, rowIndex, columnIndex }) {
  721. if (columnIndex == 0) {
  722. let key_0 = column.label
  723. let first_idx = this.tableColumnList.table_data.findIndex(x => x[key_0] == row[key_0])
  724. const calcSameLength = () => this.tableColumnList.table_data.filter(x => x[key_0] == row[key_0]).length
  725. first_column_rule = rowIndex == first_idx ? [calcSameLength(), 1] : [0, 0]
  726. return first_column_rule
  727. }else {
  728. // 表格数据的每一项,
  729. const callBacks = (table_item, start_idx = 0) => {
  730. if (columnIndex < start_idx) return true
  731. let curr_key = this.tableHeaderList[start_idx]
  732. return table_item[curr_key] === row[curr_key] && callBacks(table_item, ++start_idx)
  733. }
  734. let first_idx = this.tableColumnList.table_data.findIndex(x => callBacks(x))
  735. const calcSameLength = () => this.tableColumnList.table_data.filter(x => callBacks(x)).length
  736. return rowIndex == first_idx ? [calcSameLength(), 1] : [0, 0]
  737. }
  738. },
  739. // 拖拽排序
  740. openSortDialog() {
  741. this.sortDialogVisible = true;
  742. this.sortGoodsSpecs = JSON.parse(JSON.stringify(this.tableColumnList.goodsSpecs));
  743. },
  744. sureSortDialog() {
  745. this.tableColumnList.goodsSpecs = this.sortGoodsSpecs;
  746. //循环旧数据 规格项都对应的数据应该赋给新数据
  747. this.changeOption(this.tableColumnList.table_data);
  748. this.sortDialogVisible = false;
  749. },
  750. changeOption(data) {
  751. let oldData = [];
  752. // 先保存旧的表格数据
  753. let old_table_data = JSON.parse(JSON.stringify(data));
  754. let old_data_length = old_table_data.length
  755. for (let i = 0; i < old_data_length; i++) {
  756. let title = "";
  757. this.tableColumnList.goodsSpecs.forEach((item)=> {
  758. title = title + old_table_data[i][item.attr] + '+'
  759. })
  760. oldData.push({
  761. title: title.slice(0,title.length-1),
  762. index: i
  763. })
  764. }
  765. // 重新生成处理表头数据和表格数据
  766. this.generateTableColumn();
  767. this.traverseSku();
  768. let data_length = this.tableColumnList.table_data.length
  769. for (let i = 0; i < data_length; i++) {
  770. // 循环新的表格数据进行替换
  771. let title = "";
  772. this.tableColumnList.goodsSpecs.forEach((item)=> {
  773. title = title + this.tableColumnList.table_data[i][item.attr] + '+'
  774. })
  775. oldData.forEach((item)=> {
  776. if(item.title == title.slice(0,title.length-1)) {
  777. // this.tableColumnList.table_data[i] = old_table_data[item.index] 这样赋值会导致输入框有问题 只能像下面这样
  778. this.tableColumnList.table_data[i].stock = old_table_data[item.index].stock;
  779. this.tableColumnList.table_data[i].withhold_stock = old_table_data[item.index].withhold_stock;
  780. this.tableColumnList.table_data[i].market_price = old_table_data[item.index].market_price;
  781. this.tableColumnList.table_data[i].product_price = old_table_data[item.index].product_price;
  782. this.tableColumnList.table_data[i].cost_price = old_table_data[item.index].cost_price;
  783. this.tableColumnList.table_data[i].goods_sn = old_table_data[item.index].goods_sn;
  784. this.tableColumnList.table_data[i].product_sn = old_table_data[item.index].product_sn;
  785. this.tableColumnList.table_data[i].weight = old_table_data[item.index].weight;
  786. this.tableColumnList.table_data[i].volume = old_table_data[item.index].volume;
  787. this.tableColumnList.table_data[i].thumb = old_table_data[item.index].thumb;
  788. this.tableColumnList.table_data[i].id = old_table_data[item.index].id;
  789. }
  790. })
  791. }
  792. },
  793. clearALL() {
  794. this.tableColumnList.goodsSpecs = [];
  795. // 生成处理表头数据和表格数据
  796. this.generateTableColumn()
  797. this.traverseSku()
  798. },
  799. batchSet(value) {
  800. // 批量设置
  801. let data_length = this.tableColumnList.table_data.length
  802. for (let i = 0; i < data_length; i++) {
  803. this.tableColumnList.table_data[i][value] = this.batch_input;
  804. }
  805. this.batch_input = "";
  806. },
  807. selectSpecImage(attr, show, images) {
  808. this.tableColumnList.table_data[this.selectedingImageSpecIndex].thumb = images[0]["url"];
  809. this.$forceUpdate()
  810. },
  811. handleSpecs() {
  812. let specsList = [];
  813. let specs_length = this.tableColumnList.goodsSpecs.length;
  814. if(this.has_option) {
  815. // 启用商品规格再校验
  816. for (let i = 0; i < specs_length; i++) {
  817. // 第一步校验是否有空的规格名
  818. if(!this.tableColumnList.goodsSpecs[i].attr) {
  819. this.$message({
  820. message: '规格名不能为空',
  821. type: 'warning'
  822. });
  823. this.$refs.specValueInput[i].focus()
  824. return [];
  825. }
  826. }
  827. }
  828. for (let i = 0; i < specs_length; i++) {
  829. if(this.has_option) {
  830. // 启用商品规格再校验
  831. let valueList = this.tableColumnList.goodsSpecs[i].valueList.map(value => value.title);
  832. if(valueList.includes('')) {
  833. // 第二步校验是否有空的规格值
  834. this.$message({
  835. message: `${this.tableColumnList.goodsSpecs[i].attr}规格值不能为空`,
  836. type: 'warning'
  837. });
  838. this.$refs.specValueInput[i].focus();
  839. return [];
  840. }
  841. }
  842. specsList.push({
  843. id: this.tableColumnList.goodsSpecs[i].id,
  844. title: this.tableColumnList.goodsSpecs[i].attr,
  845. spec_item: this.tableColumnList.goodsSpecs[i].valueList
  846. })
  847. }
  848. return specsList;
  849. },
  850. handleOption(specs) {
  851. let option = [];
  852. let data_length = this.tableColumnList.table_data.length
  853. for (let i = 0; i < data_length; i++) {
  854. let title = "";
  855. let specs_id = "";
  856. if(this.has_option) {
  857. // 启用商品规格再校验
  858. if(this.tableColumnList.table_data[i].stock === '') {
  859. this.$message({
  860. message: `库存不能为空`,
  861. type: 'warning'
  862. });
  863. this.$refs[`stockValueInput${i}`].focus()
  864. return [];
  865. }
  866. if(this.tableColumnList.table_data[i].product_price === '') {
  867. this.$message({
  868. message: `现价不能为空`,
  869. type: 'warning'
  870. });
  871. this.$refs[`priceValueInput${i}`].focus()
  872. return [];
  873. }
  874. }
  875. specs.forEach((item)=> {
  876. title = title + this.tableColumnList.table_data[i][item.title] + '+'
  877. item.spec_item.forEach((spec)=> {
  878. if(spec.title === this.tableColumnList.table_data[i][item.title]) {
  879. specs_id = specs_id + spec.id + '_'
  880. }
  881. })
  882. })
  883. option.push({
  884. ...this.tableColumnList.table_data[i],
  885. title: title.slice(0,title.length-1),
  886. specs: specs_id.slice(0,specs_id.length-1),
  887. })
  888. }
  889. return option;
  890. },
  891. validate() {
  892. console.log(this.tableColumnList.goodsSpecs);
  893. console.log(this.tableColumnList.table_data)
  894. let data = {};
  895. // 注意:option是SP 规格名SC 和规格值SV新增 前缀分开来 保证id唯一
  896. if(this.has_option) {
  897. let specs = this.handleSpecs();
  898. if(specs.length == 0) {
  899. // 校验规格是否有空值
  900. return false;
  901. }
  902. let option = this.handleOption(specs);
  903. if(option.length == 0) {
  904. // 校验库存和价格是否有空值
  905. return false;
  906. }
  907. data = {
  908. has_option: 1,
  909. specs: specs,
  910. option: option
  911. };
  912. }else {
  913. // 不启用商品规格
  914. let specs = this.handleSpecs();
  915. let option = this.handleOption(specs);
  916. data = {
  917. has_option: 0,
  918. specs: specs,
  919. option: option
  920. };
  921. }
  922. console.log(data)
  923. return data;
  924. },
  925. // 删除图片
  926. delUploadImg(index){
  927. this.tableColumnList.table_data[index].thumb = ""
  928. }
  929. },
  930. computed: {
  931. // 已添加的属性(字符串数组)
  932. selectedAttr() {
  933. return this.tableColumnList.goodsSpecs.map(x => x.attr)
  934. },
  935. },
  936. });