option.js 37 KB

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