cgProduct.js 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557
  1. import { getProductDataApi } from "@/api/item";
  2. import { mapGetters, mapActions } from "vuex";
  3. import { copyObject } from "@/utils/util";
  4. export default {
  5. data() {
  6. return {
  7. options:'',
  8. pageType: "bill", //选择商品页面类别 用于区分不同功能的数据
  9. suitInfo: [], //成品-商品信息
  10. suitClassIndex: 0, //成品-当前商品类别下标
  11. productInfo: [], //商品信息
  12. pxClassInfo: [], //培训班开单列表
  13. classIndex: 0, //当前商品类别下标
  14. py: "", //拼音搜索条件
  15. type: "", //库存预警时,固定为waring,其他情况不传或者传空
  16. autoLoad: true, //自动获取商品信息
  17. isLimit: true, //选择数量不能大于库存限制
  18. //成品开单
  19. suit_scroll_into_view:"",
  20. suit_top_list:'',
  21. suitTimeFun:"",
  22. suitFilterProductInfo:'',
  23. //组合开单
  24. scroll_into_view:"",
  25. top_list:'',
  26. cached_top_list: null,
  27. scrollTimer: null,
  28. timeFun:"",
  29. filterProductInfo:''
  30. };
  31. },
  32. onLoad(options) {
  33. this.options = options;
  34. const { pageType,shopId,id,name } = options;
  35. if (pageType) {
  36. this.pageType = pageType;
  37. }
  38. if(name){
  39. uni.setNavigationBarTitle({
  40. title: name
  41. });
  42. }
  43. if (this.autoLoad) {
  44. this.initData();
  45. if(this.isGhsProduct){
  46. }else{
  47. if(!this.$util.isEmpty(this.tabIndex)){
  48. this.initData();
  49. }
  50. }
  51. }
  52. },
  53. computed: {
  54. ...mapGetters(["getSelectInfo", "getLimitBuyInfo"]),
  55. //当前页面选中的商品列表
  56. selectList() {
  57. return this.getSelectInfo[this.pageType] || [];
  58. },
  59. //当前页面限购信息列表
  60. limitBuyList() {
  61. return this.getLimitBuyInfo[this.pageType] || [];
  62. },
  63. scrollClassId() {
  64. const curClass = this.productInfo[this.classIndex];
  65. if (curClass) {
  66. return `class_${curClass.classId}`;
  67. }
  68. return "";
  69. },
  70. //选择商品总价
  71. allPrice() {
  72. let price = 0;
  73. if (this.selectList) {
  74. for (const item of this.selectList) {
  75. price += this.getSelectItemTotalPrice(item);
  76. }
  77. }
  78. return Math.round(price * 100) / 100;
  79. },
  80. // 满减优惠总额
  81. allReachDiscountAmount() {
  82. let discount = 0;
  83. if (this.selectList) {
  84. for (const item of this.selectList) {
  85. discount += this.getReachDiscountAmount(item);
  86. }
  87. }
  88. return Math.round(discount * 100) / 100;
  89. },
  90. allPriceFun() {
  91. let price = 0;
  92. if (this.selectList) {
  93. // 唯一性处理,通过 id 去除重复商品 ---- 防止统计金额时因同一商品多次出现而重复计算
  94. const newSelectList = this.selectList.filter(
  95. (item, index, self) => index === self.findIndex(t => t.id === item.id)
  96. );
  97. for (const item of newSelectList) {
  98. price += this.getSelectItemTotalPrice(item);
  99. }
  100. }
  101. return Math.round(price * 100) / 100;
  102. },
  103. //选择商品总数和总重要
  104. allCount() {
  105. let bigLength = 0;
  106. let smallLength = 0;
  107. let weight = 0
  108. if (this.selectList) {
  109. for (const item of this.selectList) {
  110. bigLength += item.bigCount
  111. smallLength += item.smallCount
  112. if(item.weight){
  113. if(Number(item.bigCount)>0){
  114. let bigAdd = Number(item.weight)*Number(item.bigCount)
  115. weight = Number(weight) + Number(bigAdd)
  116. }else{
  117. let ratio = item.ratio||20
  118. let unitWeight = item.weight/ratio
  119. let smallAdd = Number(unitWeight)*Number(item.smallCount)
  120. weight = Number(weight) + Number(smallAdd)
  121. }
  122. weight = weight.toFixed(2)
  123. weight = parseFloat(weight)
  124. }
  125. }
  126. }
  127. return {bigLength:bigLength,smallLength:smallLength,weight:weight};
  128. },
  129. allCountFun() {
  130. let bigLength = 0;
  131. let smallLength = 0;
  132. let weight = 0;
  133. if (this.selectList) {
  134. for (const item of this.selectList) {
  135. bigLength += item.bigCount;
  136. smallLength += item.smallCount;
  137. if (item.weight) {
  138. if (Number(item.bigCount) > 0) {
  139. let bigAdd = Number(item.weight) * Number(item.bigCount);
  140. weight = Number(weight) + Number(bigAdd);
  141. } else {
  142. let ratio = item.ratio || 20;
  143. let unitWeight = item.weight / ratio;
  144. let smallAdd = Number(unitWeight) * Number(item.smallCount);
  145. weight = Number(weight) + Number(smallAdd);
  146. }
  147. weight = weight.toFixed(2);
  148. weight = parseFloat(weight);
  149. }
  150. }
  151. }
  152. return { bigLength, smallLength, weight };
  153. },
  154. //描点-定位分类
  155. suitScrollClassId() {
  156. const curClass = this.suitInfo[this.suitClassIndex];
  157. if (curClass) {
  158. return `class_${curClass.classId}`;
  159. }
  160. return "";
  161. },
  162. //多风格-选择商品总价
  163. allGoodsPrice() {
  164. let price = 0;
  165. if (this.selectList) {
  166. for (const item of this.selectList) {
  167. if(item.goodsStyle){
  168. if(item.goodsStyle==0){
  169. price += item.bigCount * Number(item.price);
  170. }else{
  171. price += item.bigCount * Number(item.goodsStyleList[item.goodsStyleSelectIndex].price);
  172. }
  173. }
  174. }
  175. }
  176. return Math.round(price * 100) / 100;
  177. },
  178. //多风格-选择商品总数
  179. allGoodsCount() {
  180. let length = 0;
  181. this.selectList.forEach(element => {
  182. length += Number(element.bigCount)
  183. })
  184. return length;
  185. },
  186. //当前选中商品列表中是否有没有库存的商品
  187. hasNoStock() {
  188. let has = false;
  189. if (this.selectList) {
  190. for (const item of this.selectList) {
  191. const {
  192. bigCount,
  193. smallCount,
  194. bigNum,
  195. bigUnit,
  196. smallNum,
  197. smallUnit,
  198. ratio
  199. } = item;
  200. const ratioNum = Number(ratio);
  201. if (
  202. Number(bigCount) * ratioNum + Number(smallCount) >
  203. Number(bigNum) * ratioNum + Number(smallNum)
  204. ) {
  205. has = true;
  206. break;
  207. }
  208. }
  209. }
  210. return has;
  211. }
  212. },
  213. onShow(){
  214. // 购物车/分类等页面自行从 account 维度恢复,避免无 account 的 selectListcg 覆盖
  215. if (this.autoLoad === false) {
  216. return;
  217. }
  218. if(this.pageType == 'cg' && !this.$util.isEmpty(this.option.id)){
  219. }else{
  220. if(uni.getStorageSync('selectList'+this.pageType)){
  221. this.setSelectInfoByType({ type: this.pageType, info: uni.getStorageSync('selectList'+this.pageType) })
  222. }
  223. }
  224. },
  225. methods: {
  226. ...mapActions([
  227. "setSelectInfoByType",
  228. "resetSelectInfoByType",
  229. "setLimitBuyInfoByType",
  230. "resetLimitBuyInfoByType"
  231. ]),
  232. isTrueValue(value) {
  233. return value === true || value === 1 || value === "1";
  234. },
  235. toSafeNumber(value) {
  236. const num = Number(value);
  237. return isNaN(num) ? 0 : num;
  238. },
  239. /** 供货商--采购 / 预订:列表里同一花材可能在不同分类下 classId 不同,购物车应按 id 合并为一行 */
  240. _shouldMergeSelectByProductId() {
  241. return this.pageType === "cg" || this.pageType === "bookCg";
  242. },
  243. /** property: 0=花束 goods,1=花材 item;历史花材数据无 property 时按花材处理 */
  244. getItemProperty(item) {
  245. if (!item) return 1;
  246. if (item.property === 0 || item.property === '0') return 0;
  247. if (item.property === 1 || item.property === '1') return 1;
  248. return 1;
  249. },
  250. isBouquetItem(item) {
  251. return this.getItemProperty(item) === 0;
  252. },
  253. /** 购物车行唯一键:花束用 id+property+specGoodsId+activityType,花材用 id(+classId) */
  254. getSelectRowKey(item) {
  255. if (!item || item.id === undefined || item.id === null) return '';
  256. const property = this.getItemProperty(item);
  257. if (property === 0) {
  258. // 与 _selectRowMatches 对齐:同规格秒杀与普通购买必须分行,避免不同计价规则被合并
  259. return `0_${item.id}_${item.specGoodsId || 0}_${item.activityType || ''}`;
  260. }
  261. if (this._shouldMergeSelectByProductId()) {
  262. return `1_${item.id}`;
  263. }
  264. return `1_${item.id}_${item.classId || 0}`;
  265. },
  266. _mergeDuplicateSelectRows(list) {
  267. if (!Array.isArray(list) || list.length === 0) {
  268. return Array.isArray(list) ? [...list] : [];
  269. }
  270. const map = new Map();
  271. const order = [];
  272. list.forEach(ele => {
  273. const key = this.getSelectRowKey(ele) || String(ele.id);
  274. if (!map.has(key)) {
  275. const copy = { ...ele };
  276. copy.bigCount = Number(copy.bigCount) || 0;
  277. copy.smallCount = Number(copy.smallCount) || 0;
  278. if (copy.property === undefined || copy.property === null) {
  279. copy.property = this.getItemProperty(copy);
  280. }
  281. map.set(key, copy);
  282. order.push(key);
  283. } else {
  284. const prev = map.get(key);
  285. prev.bigCount = Number(prev.bigCount) + (Number(ele.bigCount) || 0);
  286. prev.smallCount = Number(prev.smallCount) + (Number(ele.smallCount) || 0);
  287. }
  288. });
  289. return order.map(k => map.get(k));
  290. },
  291. _syncMergedSelectListIfNeeded() {
  292. if (!this._shouldMergeSelectByProductId()) return;
  293. const list = this.selectList;
  294. if (!list || !list.length) return;
  295. const merged = this._mergeDuplicateSelectRows(list);
  296. if (merged.length !== list.length) {
  297. this.setSelectInfoByType({ type: this.pageType, info: merged });
  298. }
  299. },
  300. _selectRowMatches(element, item) {
  301. if (!element || !item) return false;
  302. const eleProperty = this.getItemProperty(element);
  303. const itemProperty = this.getItemProperty(item);
  304. if (eleProperty !== itemProperty) return false;
  305. if (eleProperty === 0) {
  306. // 秒杀与普通购买同商品同规格也必须分行,避免不同计价规则的购物车行被合并
  307. if ((element.activityType || '') !== (item.activityType || '')) return false;
  308. return String(element.id) === String(item.id)
  309. && String(element.specGoodsId || 0) === String(item.specGoodsId || 0);
  310. }
  311. if (this._shouldMergeSelectByProductId()) {
  312. return String(element.id) === String(item.id);
  313. }
  314. return element.id == item.id && element.classId == item.classId;
  315. },
  316. getLimitBuyItemById(id) {
  317. if (!Array.isArray(this.limitBuyList)) {
  318. return null;
  319. }
  320. return this.limitBuyList.find(item => String(item.id) === String(id)) || null;
  321. },
  322. isLimitSpecialItem(item) {
  323. const limitItem = this.getLimitBuyItemById(item.id);
  324. return !!(limitItem && this.isTrueValue(limitItem.isLimit) && this.isTrueValue(limitItem.specialPrice));
  325. },
  326. getItemReachPrice(item) {
  327. const reachPrice = Number(item.reachPrice);
  328. if (!isNaN(reachPrice) && reachPrice > 0) {
  329. return parseFloat(reachPrice);
  330. }
  331. const price = this.toSafeNumber(item.price || item.bigPrice);
  332. const reachNumDiscount = Number(item.reachNumDiscount) || 0;
  333. if (reachNumDiscount > 0 && price > reachNumDiscount) {
  334. const computed = parseFloat((price - reachNumDiscount).toFixed(2));
  335. return computed <= 0 ? 0.01 : computed;
  336. }
  337. return 0;
  338. },
  339. isReachDiscountReached(item) {
  340. const reachNum = Number(item.reachNum) || 0;
  341. if (reachNum <= 0) {
  342. return false;
  343. }
  344. const itemInfo = this.getSelectItemById(item.id, item.classId);
  345. return Number(itemInfo.bigCount) >= reachNum;
  346. },
  347. getReachDiscountAmount(item) {
  348. if (!this.isReachDiscountReached(item)) {
  349. return 0;
  350. }
  351. const itemInfo = this.getSelectItemById(item.id, item.classId);
  352. const bigCount = Number(itemInfo.bigCount) || 0;
  353. const originalBig = this.toSafeNumber(item.price || item.bigPrice);
  354. const reachPrice = this.getItemReachPrice(item);
  355. return Math.round(Math.max(0, bigCount * (originalBig - reachPrice)) * 100) / 100;
  356. },
  357. getCartItemUnitPrice(item) {
  358. if (this.isBouquetItem(item)) {
  359. return parseFloat(this.toSafeNumber(item.price || item.bigPrice));
  360. }
  361. const itemInfo = this.getSelectItemById(item.id, item.classId);
  362. if (Number(itemInfo.bigCount) > 0 && this.isReachDiscountReached(item)) {
  363. return parseFloat(this.getItemReachPrice(item));
  364. }
  365. if (Number(item.discountPrice) > 0) {
  366. return parseFloat(this.toSafeNumber(item.price));
  367. }
  368. const parts = this.getLimitBuyPriceDisplayParts(item);
  369. return parseFloat(String(parts.main).replace('¥', '')) || 0;
  370. },
  371. getCartItemOriginalUnitPrice(item) {
  372. if (this.isBouquetItem(item)) {
  373. return item.activityType === 'seckill' ? parseFloat(this.toSafeNumber(item.originPrice)) : 0;
  374. }
  375. if (this.isReachDiscountReached(item)) {
  376. return parseFloat(this.toSafeNumber(item.price || item.bigPrice));
  377. }
  378. if (Number(item.discountPrice) > 0) {
  379. return parseFloat(this.toSafeNumber(item.prePrice));
  380. }
  381. const parts = this.getLimitBuyPriceDisplayParts(item);
  382. if (parts.showStrike) {
  383. return parseFloat(String(parts.strike).replace('¥', '')) || 0;
  384. }
  385. return 0;
  386. },
  387. cartPriceShowStrike(item) {
  388. if (this.isBouquetItem(item)) {
  389. if (item.activityType !== 'seckill') return false;
  390. const original = this.toSafeNumber(item.originPrice);
  391. const current = this.toSafeNumber(item.price || item.bigPrice);
  392. return original > current;
  393. }
  394. if (this.isReachDiscountReached(item)) {
  395. const original = this.toSafeNumber(item.price || item.bigPrice);
  396. const reachPrice = this.getItemReachPrice(item);
  397. return reachPrice > 0 && reachPrice < original;
  398. }
  399. if (Number(item.discountPrice) > 0) {
  400. return true;
  401. }
  402. return this.getLimitBuyPriceDisplayParts(item).showStrike;
  403. },
  404. getSelectItemTotalPrice(item) {
  405. if (this.isBouquetItem(item)) {
  406. const num = Number(item.bigCount) || 0;
  407. const price = this.toSafeNumber(item.price || item.bigPrice);
  408. return Math.round(num * price * 100) / 100;
  409. }
  410. return this.getLimitBuyPriceInfo(item).total;
  411. },
  412. /**
  413. * 购物车中与目标花束行匹配的已有数量(按 id + 规格 + 活动类型分行)
  414. * @param {Object} row 待匹配的购物车行结构
  415. * @returns {Number} 已有 bigCount 合计
  416. */
  417. getMatchedBouquetCartCount(row) {
  418. if (!row) return 0;
  419. const list = Array.isArray(this.selectList) ? this.selectList : [];
  420. let count = 0;
  421. for (let i = 0; i < list.length; i++) {
  422. if (this._selectRowMatches(list[i], row)) {
  423. count += Number(list[i].bigCount) || 0;
  424. }
  425. }
  426. return count;
  427. },
  428. /**
  429. * 校验花束本次数量是否超过商品库存 / 秒杀活动库存 / 秒杀限购。
  430. * existingCount 用于加购合并购物车已有件数。
  431. * activityBoughtCount 为结算失败后回写的历史已购,有值时与本次数量一并计入限购。
  432. * @param {Object} goods 带 stock / activityType / activityLimit / activityStock / activityBoughtCount 的商品或购物车行
  433. * @param {Number} buyNum 本次数量
  434. * @param {Number} existingCount 购物车已有数量,立即购买传 0
  435. * @returns {boolean} 是否通过
  436. */
  437. assertBouquetBuyQty(goods, buyNum, existingCount = 0) {
  438. if (!goods) return false;
  439. const num = Number(buyNum) || 0;
  440. const exist = Number(existingCount) || 0;
  441. const total = exist + num;
  442. const stockRaw = goods.stock;
  443. const hasStock = stockRaw !== undefined && stockRaw !== null && stockRaw !== '';
  444. const stock = Number(stockRaw);
  445. const stockSet = Number(goods.stockSet);
  446. // 开启库存限制必须校验;未开启时仅在库存是有效数字时校验(避免列表商品缺 stock 被误拦)
  447. if (stockSet === 1) {
  448. if (!isFinite(stock) || !(stock > 0) || total > stock) {
  449. this.$msg('库存不足哦');
  450. return false;
  451. }
  452. } else if (hasStock && isFinite(stock)) {
  453. if (!(stock > 0) || total > stock) {
  454. this.$msg('库存不足哦');
  455. return false;
  456. }
  457. }
  458. if (goods.activityType === 'seckill') {
  459. const actStockRaw = goods.activityStock;
  460. const hasActStock = actStockRaw !== undefined && actStockRaw !== null && actStockRaw !== '';
  461. const actStock = Number(actStockRaw) || 0;
  462. if (hasActStock && (!(actStock > 0) || total > actStock)) {
  463. this.$msg('库存不足哦');
  464. return false;
  465. }
  466. const limit = Number(goods.activityLimit) || 0;
  467. // 结算失败后回写的已购数量,避免「限购1件且已买过1件」仍能加到购物车
  468. const bought = Number(goods.activityBoughtCount) || 0;
  469. if (limit > 0 && (total + bought) > limit) {
  470. this.$msg('超过限购数量,每人限购' + limit + '件');
  471. return false;
  472. }
  473. }
  474. return true;
  475. },
  476. /**
  477. * 组装花束购物车行(property=0):仅做校验并构造行结构,不写入 Vuex/缓存。
  478. * 「立即购买」复用该行结构,保证与加购的计价、展示口径一致,同时不污染购物车。
  479. * 秒杀进行中允许无价格商品按活动价入行,priceType 记为 1,避免结算仍走询价拦截。
  480. * @param {Object} goods 主商品信息
  481. * @param {Object|null} spec 选中的规格子商品,单规格可为 null
  482. * @param {Number} num 数量,默认 1
  483. * @returns {Object|null} 构造失败返回 null(已通过 $msg 提示原因)
  484. */
  485. buildBouquetRow(goods, spec = null, num = 1) {
  486. if (!goods || !goods.id) {
  487. this.$msg('商品信息无效');
  488. return null;
  489. }
  490. const buyNum = Number(num) > 0 ? Number(num) : 1;
  491. const saleGoods = spec && spec.id ? spec : goods;
  492. const isSeckill = goods.activityType === 'seckill';
  493. let price = Number(saleGoods.price != null ? saleGoods.price : goods.price);
  494. // 秒杀进行中:无价格商品用活动价直购;规格自身无价时回退到主商品上已覆盖的秒杀价
  495. if (isSeckill && !(price > 0)) {
  496. price = Number(goods.price);
  497. }
  498. if (isSeckill) {
  499. if (!(price > 0)) {
  500. this.$msg('商品还没有价格哦');
  501. return null;
  502. }
  503. } else {
  504. // 无价格类型(priceType=0):不能加购,引导向商家询价
  505. if (Number(goods.priceType) === 0) {
  506. this.$msg('请向商家咨询价格');
  507. return null;
  508. }
  509. // 有价格类型且价格为0时,提示商品还没有价格
  510. if ((Number(goods.priceType) === 1 && !(price > 0))) {
  511. this.$msg('商品还没有价格哦');
  512. return null;
  513. }
  514. }
  515. const stockRaw = saleGoods.stock != null ? saleGoods.stock : goods.stock;
  516. const stockNum = Number(stockRaw);
  517. const stockSet = Number(saleGoods.stockSet != null ? saleGoods.stockSet : goods.stockSet) || 0;
  518. // 未开启库存限制且没有有效库存时,用 9999 占位,与后端 stockSet=0 的写法一致
  519. const stock = isFinite(stockNum) ? stockNum : (stockSet === 1 ? 0 : 9999);
  520. const row = {
  521. id: goods.id,
  522. goodsId: goods.id,
  523. specGoodsId: spec && spec.id ? spec.id : 0,
  524. property: 0,
  525. classId: 0,
  526. name: goods.name || '',
  527. itemName: goods.name || '',
  528. specName: (spec && spec.specName) || '',
  529. cover: goods.smallCover || goods.coverUrl || goods.cover || '',
  530. price: price,
  531. bigPrice: price,
  532. smallPrice: price,
  533. priceType: isSeckill ? 1 : goods.priceType,
  534. freightType: goods.freightType,
  535. weight: saleGoods.weight != null ? saleGoods.weight : (goods.weight || 0),
  536. stock: stock,
  537. stockSet: stockSet,
  538. bigNum: stock > 0 ? stock : 9999,
  539. smallNum: 0,
  540. bigCount: buyNum,
  541. smallCount: 0,
  542. ratio: 1,
  543. bigUnit: '份',
  544. smallUnit: '份'
  545. };
  546. // 秒杀商品:打上活动标记与原价,用于购物车/结算页展示划线价;
  547. // 实际下单价格由后端独立核对活动配置核价,此处价格仅用于前端展示
  548. if (goods.activityType === 'seckill') {
  549. row.activityType = 'seckill';
  550. row.activityEndTime = Number(goods.activityEndTime) || 0;
  551. row.originPrice = Number(goods.originPrice) || 0;
  552. row.activityLimit = Number(goods.activityLimit) || 0;
  553. // 仅在明确带了活动库存时写入,避免列表加购缺该字段时被当成售罄
  554. if (goods.activityStock !== undefined && goods.activityStock !== null && goods.activityStock !== '') {
  555. row.activityStock = Number(goods.activityStock) || 0;
  556. }
  557. }
  558. // 立即购买按本次数量校验;加购合并数量在 addBouquetToCart 里再验一次
  559. if (!this.assertBouquetBuyQty(row, buyNum, 0)) {
  560. return null;
  561. }
  562. return row;
  563. },
  564. /**
  565. * 花束加入购物车(property=0)
  566. * @param {Object} goods 主商品信息
  567. * @param {Object|null} spec 选中的规格子商品,单规格可为 null
  568. * @param {Number} num 数量,默认 1
  569. */
  570. addBouquetToCart(goods, spec = null, num = 1) {
  571. const row = this.buildBouquetRow(goods, spec, num);
  572. if (!row) {
  573. return false;
  574. }
  575. const buyNum = Number(row.bigCount) || 1;
  576. const existing = this.getMatchedBouquetCartCount(row);
  577. // 加购要合并购物车已有件数后再验库存/限购,避免分次加购绕过秒杀限购
  578. if (!this.assertBouquetBuyQty(row, buyNum, existing)) {
  579. return false;
  580. }
  581. const list = Array.isArray(this.selectList) ? [...this.selectList] : [];
  582. let found = false;
  583. for (let i = 0; i < list.length; i++) {
  584. if (this._selectRowMatches(list[i], row)) {
  585. list[i] = {
  586. ...list[i],
  587. ...row,
  588. bigCount: (Number(list[i].bigCount) || 0) + buyNum
  589. };
  590. found = true;
  591. break;
  592. }
  593. }
  594. if (!found) {
  595. list.push(row);
  596. }
  597. this.setSelectInfoByType({ type: this.pageType, info: list });
  598. this.rememberProduct();
  599. if (this.$util && this.$util.hitRemind) {
  600. this.$util.hitRemind();
  601. }
  602. return true;
  603. },
  604. _applyReachDiscountToPriceInfo(item, itemInfo, result) {
  605. if (!this.isReachDiscountReached(item)) {
  606. result.reachDiscountActive = false;
  607. return;
  608. }
  609. const bigCount = Number(itemInfo.bigCount) || 0;
  610. const smallCount = Number(itemInfo.smallCount) || 0;
  611. if (!result.isLimitSpecial) {
  612. const reachPrice = this.getItemReachPrice(item);
  613. const smallPrice = this.toSafeNumber(item.smallPrice);
  614. const unitPrice = this.toSafeNumber(item.price || item.bigPrice);
  615. result.total = bigCount * reachPrice + smallCount * (smallPrice || unitPrice);
  616. } else {
  617. const reachNumDiscount = Number(item.reachNumDiscount) || 0;
  618. result.total = Math.max(0.01, result.total - reachNumDiscount * bigCount);
  619. }
  620. result.total = Math.round(result.total * 100) / 100;
  621. result.reachDiscountActive = true;
  622. },
  623. getLimitBuyPriceInfo(item) {
  624. const itemInfo = this.getSelectItemById(item.id, item.classId);
  625. const specialPrice = this.toSafeNumber(item.price);
  626. const originalPrice = this.toSafeNumber(item.prePrice);
  627. const result = {
  628. total: 0,
  629. specialPrice: specialPrice,
  630. originalPrice: originalPrice,
  631. exceedNum: 0,
  632. limitBuy: 0,
  633. hasBuyNum: 0,
  634. currentBuyNum: 0,
  635. isLimitSpecial: false
  636. };
  637. result.total = itemInfo.bigCount * specialPrice + itemInfo.smallCount * originalPrice;
  638. const limitItem = this.getLimitBuyItemById(item.id);
  639. if (!limitItem || !this.isLimitSpecialItem(item)) {
  640. this._applyReachDiscountToPriceInfo(item, itemInfo, result);
  641. return result;
  642. }
  643. const exceedNum = this.toSafeNumber(limitItem.exceedNum) + this.toSafeNumber(limitItem.currentBuyNum);
  644. result.exceedNum = exceedNum;
  645. result.limitBuy = this.toSafeNumber(limitItem.limitBuy);
  646. result.hasBuyNum = this.toSafeNumber(limitItem.hasBuyNum);
  647. result.currentBuyNum = this.toSafeNumber(limitItem.currentBuyNum);
  648. result.isLimitSpecial = true;
  649. if (result.exceedNum <= 0) {
  650. result.total = result.currentBuyNum * specialPrice;
  651. } else if (result.exceedNum > 0 && result.hasBuyNum > result.limitBuy) {
  652. result.total = result.currentBuyNum * originalPrice;
  653. } else {
  654. result.total =
  655. (result.currentBuyNum - result.exceedNum) * specialPrice + result.exceedNum * originalPrice;
  656. }
  657. this._applyReachDiscountToPriceInfo(item, itemInfo, result);
  658. return result;
  659. },
  660. getLimitBuyPriceDisplayParts(item) {
  661. const priceInfo = this.getLimitBuyPriceInfo(item);
  662. const fmt = n => parseFloat(Number(n) || 0);
  663. const itemInfo = this.getSelectItemById(item.id, item.classId);
  664. if (priceInfo.reachDiscountActive && !priceInfo.isLimitSpecial && Number(itemInfo.bigCount) > 0) {
  665. const reachPrice = fmt(this.getItemReachPrice(item));
  666. const originalBig = fmt(item.price || item.bigPrice);
  667. return {
  668. showStrike: reachPrice < originalBig,
  669. main: `¥${reachPrice}`,
  670. strike: reachPrice < originalBig ? `¥${originalBig}` : ""
  671. };
  672. }
  673. if (!priceInfo.isLimitSpecial) {
  674. if (Number(item.discountPrice) > 0) {
  675. return {
  676. showStrike: true,
  677. main: `¥${fmt(item.price)}`,
  678. strike: `¥${fmt(item.prePrice)}`
  679. };
  680. }
  681. const big = Number(itemInfo.bigCount) > 0;
  682. const p = big ? this.toSafeNumber(item.price || item.bigPrice) : this.toSafeNumber(item.smallPrice);
  683. return {
  684. showStrike: false,
  685. main: `¥${fmt(p)}`,
  686. strike: ""
  687. };
  688. }
  689. if (priceInfo.exceedNum <= 0) {
  690. return {
  691. showStrike: false,
  692. main: `¥${fmt(priceInfo.specialPrice)}`,
  693. strike: ""
  694. };
  695. }
  696. if (priceInfo.exceedNum > 0 && priceInfo.hasBuyNum > priceInfo.limitBuy) {
  697. return {
  698. showStrike: false,
  699. main: `¥${fmt(priceInfo.originalPrice)}`,
  700. strike: ""
  701. };
  702. }
  703. return {
  704. showStrike: true,
  705. main: `¥${fmt(priceInfo.specialPrice)}`,
  706. strike: `¥${fmt(priceInfo.originalPrice)}`
  707. };
  708. },
  709. getLimitBuyPriceText(item) {
  710. const parts = this.getLimitBuyPriceDisplayParts(item);
  711. if (parts.showStrike) {
  712. return `${parts.main} ${parts.strike}`;
  713. }
  714. return parts.main;
  715. },
  716. getLimitBuyPriceTip(item) {
  717. const priceInfo = this.getLimitBuyPriceInfo(item);
  718. if (!priceInfo.isLimitSpecial) {
  719. return "";
  720. }
  721. if (priceInfo.exceedNum <= 0) {
  722. return `限购 ${priceInfo.limitBuy},未超限购,按特价计算`;
  723. }
  724. if (priceInfo.exceedNum > 0 && priceInfo.hasBuyNum > priceInfo.limitBuy) {
  725. return `限购 ${priceInfo.limitBuy},已超出,按原价计算`;
  726. }
  727. return `限购 ${priceInfo.limitBuy},${priceInfo.currentBuyNum - priceInfo.exceedNum} 扎按特价,超出 ${priceInfo.exceedNum} 扎按原价`;
  728. },
  729. //记忆已选的花材
  730. rememberProduct(){
  731. //采购或预订的记忆花材
  732. if(this.pageType == 'cg' || this.pageType == 'bookCg' ){
  733. const account = (this.option && this.option.account)
  734. || (this.shopAccount)
  735. || (this.account)
  736. || uni.getStorageSync('account')
  737. || 0
  738. if(!this.$util.isEmpty(account)){
  739. uni.setStorageSync('selectList'+this.pageType+'_hd_'+account, this.selectList)
  740. }
  741. }else{
  742. uni.setStorageSync('selectList'+this.pageType, this.selectList)
  743. }
  744. },
  745. //清除已记忆花材
  746. removeMemory(){
  747. uni.removeStorageSync('selectList'+this.pageType)
  748. this.resetSelectInfoByType(this.pageType)
  749. this.resetLimitBuyInfoByType(this.pageType)
  750. if(this.pageType == 'cg' && !this.$util.isEmpty(this.option.account)){
  751. uni.removeStorageSync('selectList'+this.pageType+'_hd_'+this.option.account)
  752. }
  753. },
  754. async initData(extendInfo) {
  755. try {
  756. let params = { py: this.py, type: this.type }
  757. if (extendInfo) {
  758. params = { ...params, ...extendInfo };
  759. }
  760. const { data } = await getProductDataApi(params);
  761. data.forEach(element => {
  762. element.child &&
  763. element.child.forEach(child => {
  764. child.classId = element.classId;
  765. child.className = element.className;
  766. });
  767. });
  768. this.productInfo = data;
  769. if(this.pageType == 'cg' && !this.$util.isEmpty(this.option.account)){
  770. if(uni.getStorageSync('selectList'+this.pageType+'_hd_'+this.option.account)){
  771. let currentInfo = uni.getStorageSync('selectList'+this.pageType+'_hd_'+this.option.account)
  772. if(!this.$util.isEmpty(currentInfo)){
  773. this.setSelectInfoByType({ type: this.pageType, info: currentInfo })
  774. }else{
  775. this.resetSelectInfoByType(this.pageType);
  776. }
  777. }else{
  778. this.resetSelectInfoByType(this.pageType);
  779. }
  780. }
  781. this.classIndex = 0;
  782. this.scrollPushList();
  783. // 异步获取并缓存分类节点高度,避免滚动时高频查询
  784. this.$nextTick(() => {
  785. setTimeout(() => {
  786. const query = uni.createSelectorQuery().in(this);
  787. query.selectAll(".selectAll").boundingClientRect(data => {
  788. if (data && data.length > 0) {
  789. this.cached_top_list = data.map(item => Math.ceil(item.top));
  790. }
  791. }).exec();
  792. }, 600);
  793. });
  794. } catch (error) {
  795. console.log(error)
  796. } finally {
  797. uni.hideLoading()
  798. }
  799. },
  800. //滚动或切换分类 获取更多数据
  801. scrollPushList(){
  802. let list = copyObject(this.productInfo);
  803. let classList = [];
  804. for (let i=0;i<list.length;i++){
  805. if(i<2){
  806. classList.push({...list[i],isActive:true});
  807. }else{
  808. classList.push({...list[i],isActive:false});
  809. }
  810. }
  811. this.filterProductInfo = classList
  812. return
  813. },
  814. //滚动商品监听(加入防抖与节流,使用缓存高度,避免高频跨线程查询)
  815. scroll_detail(options){
  816. if(uni.getStorageSync("resolve")){
  817. return;
  818. }
  819. if (this.scrollTimer) return;
  820. this.scrollTimer = setTimeout(() => {
  821. this.scrollTimer = null;
  822. // 如果有缓存的高度列表,直接使用缓存进行快速区间判定
  823. if (this.cached_top_list && this.cached_top_list.length > 0) {
  824. this.async_detail_msg_fast(options.detail.scrollTop);
  825. } else {
  826. // 降级容灾:若未获取到缓存,则使用原有的动态查询
  827. this.get_node_details(options);
  828. }
  829. }, 120);
  830. },
  831. // 快速区间判定(无 SelectorQuery 查询,极速响应)
  832. async_detail_msg_fast(scrollTop) {
  833. let top_page = scrollTop + this.cached_top_list[0];
  834. for(let i = 0; i < this.cached_top_list.length; i++){
  835. let node1 = this.cached_top_list[i];
  836. let node2 = this.cached_top_list[i+1];
  837. if(node2 && top_page >= node1 && top_page < node2){
  838. this.updateActiveClasses(i);
  839. break;
  840. }else if(node2 && top_page === node2){
  841. this.updateActiveClasses(i + 1);
  842. break;
  843. }
  844. }
  845. },
  846. // 动态 DOM 回收与激活核心逻辑
  847. updateActiveClasses(activeIndex) {
  848. this.classIndex = activeIndex;
  849. // 动态 DOM 回收:只保留当前分类、上一个分类和下两个分类处于激活状态,其余全部释放
  850. if (this.filterProductInfo && this.filterProductInfo.length > 0) {
  851. this.filterProductInfo.forEach((item, idx) => {
  852. if (idx >= activeIndex - 1 && idx <= activeIndex + 2) {
  853. item.isActive = true;
  854. } else {
  855. item.isActive = false; // 滚出可视区域的分类,销毁其内部所有花材组件,释放内存
  856. }
  857. });
  858. this.$forceUpdate();
  859. }
  860. },
  861. //获取商品节点信息
  862. get_node_details(options){
  863. const query = uni.createSelectorQuery().in(this); //获得实力
  864. //获取多个节点方式
  865. query.selectAll(".selectAll").boundingClientRect(data=>{
  866. this.top_list = data.map(item=>{
  867. return Math.ceil(item.top);
  868. });
  869. this.async_detail_msg(options);
  870. }).exec();
  871. },
  872. async_detail_msg(options){
  873. //options 为滚动信息。 options.detail.scrollTop 值为相对于scroll-view。
  874. let top_page = options.detail.scrollTop + this.top_list[0];
  875. for(let i = 0;i < this.top_list.length; i++){
  876. let node1 = this.top_list[i];
  877. let node2 = this.top_list[i+1];
  878. if(node2 && top_page >= node1 && top_page < node2){
  879. this.updateActiveClasses(i);
  880. break;
  881. }else if(node2 && top_page === node2){
  882. this.updateActiveClasses(i + 1);
  883. break;
  884. }
  885. }
  886. },
  887. scroll_bottom(){
  888. this.classIndex = this.filterProductInfo.length-1
  889. this.filterProductInfo[this.classIndex].isActive = true;
  890. this.filterProductInfo[this.classIndex-1].isActive = true;
  891. },
  892. //获取当前选中商品总价
  893. getSelectItemPrice(item) {
  894. let price = 0;
  895. const { bigCount, smallCount, bigPrice, smallPrice } = item;
  896. let sum =
  897. Number(bigCount) * Number(bigPrice) +
  898. Number(smallCount) * Number(smallPrice);
  899. price = Math.round(sum * 100) / 100;
  900. return price;
  901. },
  902. //请求成品数据
  903. async initGoodsData(extendInfo) {
  904. //extendInfo调用时的扩展参数 如 shopId
  905. try {
  906. let params = {
  907. py: this.py,
  908. type: this.type
  909. };
  910. if (extendInfo) {
  911. params = { ...params, ...extendInfo };
  912. }
  913. const { data } = await getGoodsDataApi(params);
  914. data.forEach(element => {
  915. element.child &&
  916. element.child.forEach(child => {
  917. child.classId = element.classId;
  918. child.className = element.className;
  919. });
  920. });
  921. this.suitInfo = data;
  922. this.suitClassIndex = 0;
  923. this.suitScrollPushList();
  924. } catch (error) {
  925. } finally {
  926. uni.hideLoading();
  927. }
  928. },
  929. //成品-滚动或切换分类 获取更多数据
  930. suitScrollPushList(){
  931. let list = copyObject(this.suitInfo);
  932. let classList = [];
  933. for (let i=0;i<list.length;i++){
  934. if(i<2){
  935. classList.push({...list[i],isActive:true});
  936. }else{
  937. classList.push({...list[i],isActive:false});
  938. }
  939. }
  940. this.suitFilterProductInfo = classList
  941. },
  942. //获取当前选中商品总价
  943. getSelectGoodsPrice(item) {
  944. let price = 0;
  945. if(item.goodsStyle==0){
  946. price = item.bigCount * Number(item.price);
  947. }else{
  948. price = item.bigCount * Number(item.goodsStyleList[item.goodsStyleSelectIndex].price);
  949. }
  950. return price;
  951. },
  952. //通过id获取当前选中的商品信息
  953. getSelectItemById(id, classId) {
  954. let info = {
  955. bigCount: 0, //扎数
  956. smallCount: 0, //支数
  957. autoPrice: 0, //调价价格
  958. itemPrice: 0 //调价价格
  959. };
  960. if (!this.selectList || !this.selectList.length) {
  961. return info;
  962. }
  963. const matches = this.selectList.filter(ele => this._selectRowMatches(ele, { id, classId }));
  964. if (!matches.length) {
  965. return info;
  966. }
  967. const base = { ...matches[0] };
  968. let bigCount = 0;
  969. let smallCount = 0;
  970. matches.forEach(m => {
  971. bigCount += Number(m.bigCount) || 0;
  972. smallCount += Number(m.smallCount) || 0;
  973. });
  974. return {
  975. ...base,
  976. bigCount,
  977. smallCount,
  978. autoPrice: Number(base.autoPrice),
  979. itemPrice: Number(base.itemPrice)
  980. };
  981. },
  982. //通过id获取当前选中的商品类别信息 左边菜单角标 当前选中该类别商品多少件
  983. getSelectClassById(classId) {
  984. let info = {
  985. bigCount: 0, //扎数
  986. smallCount: 0 //支数
  987. };
  988. const productItem =
  989. this.productInfo &&
  990. this.productInfo.find(ele => {
  991. return ele.classId == classId;
  992. });
  993. if (productItem && productItem.child) {
  994. for (const item of productItem.child) {
  995. for (const selectItem of this.selectList) {
  996. if (item.id == selectItem.id && item.classId == selectItem.classId) {
  997. info.bigCount += Number(selectItem.bigCount);
  998. info.smallCount += Number(selectItem.smallCount);
  999. info.itemPrice += selectItem.itemPrice?Number(selectItem.itemPrice):'';
  1000. }
  1001. }
  1002. }
  1003. }
  1004. return info;
  1005. },
  1006. //添加商品
  1007. addEvent(item) {
  1008. const list = this.selectList;
  1009. let hasItem = false;
  1010. for (let index = 0; index < list.length; index++) {
  1011. const element = list[index];
  1012. // 花束行不与花材合并
  1013. if (this.isBouquetItem(element)) {
  1014. continue;
  1015. }
  1016. if (element.id == item.id && element.classId == item.classId) {
  1017. hasItem = true;
  1018. //盘点等页面不需要验证库存
  1019. if(this.offVerifyRepertory){
  1020. list[index] = { ...element, property: 1, bigCount: ++element.bigCount };
  1021. }else {
  1022. list[index] = { ...element, property: 1, bigCount: ++element.bigCount };
  1023. if (this.isLimit) {
  1024. const {bigCount, smallCount} = list[index];
  1025. const ratio = Number(item.ratio);
  1026. if ( Number(bigCount) * ratio + Number(smallCount) > Number(item.bigNum) * ratio + Number(item.smallNum) ) {
  1027. list[index].bigCount = Number(item.bigNum);
  1028. list[index].smallCount = Number(item.smallNum);
  1029. this.$msg("库存还剩"+item.bigNum);
  1030. return;
  1031. }
  1032. }
  1033. }
  1034. break;
  1035. }
  1036. }
  1037. if (hasItem == false) {
  1038. let info = { classId: item.classId, id: item.id };
  1039. const classInfo =
  1040. this.productInfo &&
  1041. this.productInfo.find(element => {
  1042. return element.classId == item.classId && element.child;
  1043. });
  1044. if (classInfo) {
  1045. info = classInfo.child.find(ele => {
  1046. return ele.id == item.id;
  1047. });
  1048. }
  1049. let params = { ...info, property: 1, bigCount: this.isSmallCount?0:1, smallCount: this.isSmallCount?1:0, autoPrice: 0 }
  1050. if (info.bigNum && info.bigNum > 0) {
  1051. list.push(params);
  1052. } else {
  1053. //盘点等页面不需要验证库存
  1054. if(this.offVerifyRepertory){
  1055. list.push(params);
  1056. }else{
  1057. this.$msg("库存不足哦");
  1058. return;
  1059. }
  1060. }
  1061. }
  1062. this.$util.hitRemind();
  1063. this.setSelectInfoByType({ type: this.pageType, info: list });
  1064. //记忆已经选择的花材
  1065. this.rememberProduct()
  1066. },
  1067. addCustomNumEvent(item,params1,type) {
  1068. const list = this.selectList;
  1069. let info = { classId: item.classId, id: item.id };
  1070. const classInfo =
  1071. this.productInfo &&
  1072. this.productInfo.find(element => {
  1073. return element.classId == item.classId && element.child;
  1074. });
  1075. if (classInfo) {
  1076. info = classInfo.child.find(ele => {
  1077. return ele.id == item.id;
  1078. });
  1079. }
  1080. let params = {
  1081. ...info,
  1082. bigCount: 0, //扎数
  1083. smallCount: 0, //支数
  1084. autoPrice: 0 //调价价格
  1085. };
  1086. list.push(params);
  1087. if(type!='noAdd'){
  1088. this.setSelectInfoByType({ type: this.pageType, info: list });
  1089. }
  1090. this.$nextTick(()=>{
  1091. this.customNum(params1)
  1092. })
  1093. //记忆已经选择的花材
  1094. this.rememberProduct()
  1095. },
  1096. //减去商品
  1097. delEvent(item) {
  1098. const list = copyObject(this.selectList)
  1099. for (let index = 0; index < list.length; index++) {
  1100. let element = list[index];
  1101. if (element.id == item.id && element.classId == item.classId) {
  1102. if (element.bigCount > 0) {
  1103. if (!this.isSmallCount) {
  1104. list[index] = {...element, bigCount: --element.bigCount};
  1105. }
  1106. }
  1107. if (element.smallCount > 0) {
  1108. if (this.isSmallCount) {
  1109. list[index] = {...element, smallCount: --element.smallCount};
  1110. }
  1111. }
  1112. if (element.bigCount == 0 && element.smallCount == 0) {
  1113. list.splice(index, 1);
  1114. }
  1115. }
  1116. }
  1117. this.setSelectInfoByType({type: this.pageType, info: list});
  1118. this.$util.hitRemind()
  1119. //记忆已经选择的花材
  1120. this.rememberProduct()
  1121. },
  1122. delAllEvent(item) {
  1123. const list = this.selectList;
  1124. for (let index = 0; index < list.length; index++) {
  1125. const element = list[index];
  1126. if (this._selectRowMatches(element, item)) {
  1127. list.splice(index, 1);
  1128. break;
  1129. }
  1130. }
  1131. this.setSelectInfoByType({ type: this.pageType, info: list });
  1132. //记忆已经选择的花材
  1133. this.rememberProduct()
  1134. },
  1135. //通过id获取当前选中的商品类别信息 左边菜单角标 当前选中该类别商品多少件
  1136. getSuitSelectClassById(classId) {
  1137. let info = {
  1138. bigCount: 0, //扎数
  1139. smallCount: 0 //支数
  1140. };
  1141. const productItem =
  1142. this.suitInfo &&
  1143. this.suitInfo.find(ele => {
  1144. return ele.classId == classId;
  1145. });
  1146. if (productItem && productItem.child) {
  1147. for (const item of productItem.child) {
  1148. for (const selectItem of this.selectList) {
  1149. if (item.id == selectItem.id && item.classId == selectItem.classId) {
  1150. info.bigCount += Number(selectItem.bigCount);
  1151. info.smallCount += Number(selectItem.smallCount);
  1152. info.itemPrice += selectItem.itemPrice?Number(selectItem.itemPrice):'';
  1153. }
  1154. }
  1155. }
  1156. }
  1157. return info;
  1158. },
  1159. //多风格-添加商品
  1160. addGoodsEvent(item) {
  1161. const list = this.selectList;
  1162. //过滤空数据
  1163. let has = false;
  1164. for (let index = 0; index < list.length; index++) {
  1165. const element = list[index];
  1166. if (element.id == item.id && element.classId == item.classId) {
  1167. has = true;
  1168. list[index] = { ...element, bigCount: item.bigCount,goodsStyleSelectIndex: item.goodsStyleSelectIndex };
  1169. break;
  1170. }
  1171. }
  1172. if (!has) {
  1173. let info = { classId: item.classId, id: item.id };
  1174. const classInfo =
  1175. this.suitInfo &&
  1176. this.suitInfo.find(element => {
  1177. return element.classId == item.classId && element.child;
  1178. });
  1179. if (classInfo) {
  1180. info = classInfo.child.find(ele => {
  1181. return ele.id == item.id;
  1182. });
  1183. }
  1184. let params = {
  1185. ...info,
  1186. bigCount: item.bigCount, //扎数
  1187. goodsStyleSelectIndex: item.goodsStyleSelectIndex, //选中的风格
  1188. };
  1189. list.push(params);
  1190. }
  1191. this.setSelectInfoByType({ type: this.pageType, info: list });
  1192. // this.$forceUpdate();
  1193. },
  1194. //多风格-添加商品
  1195. addGoods(item) {
  1196. const list = this.selectList;
  1197. let has = false;
  1198. for (let index = 0; index < list.length; index++) {
  1199. const element = list[index];
  1200. if (element.id == item.id && element.classId == item.classId) {
  1201. has = true;
  1202. list[index] = { ...element, bigCount: ++element.bigCount };
  1203. break;
  1204. }
  1205. }
  1206. if (!has) {
  1207. let info = { classId: item.classId, id: item.id };
  1208. const classInfo =
  1209. this.suitInfo &&
  1210. this.suitInfo.find(element => {
  1211. return element.classId == item.classId && element.child;
  1212. });
  1213. if (classInfo) {
  1214. info = classInfo.child.find(ele => {
  1215. return ele.id == item.id;
  1216. });
  1217. }
  1218. let params = {
  1219. ...info,
  1220. bigCount: 1, //扎数
  1221. smallCount: 0, //支数
  1222. autoPrice: 0 //调价价格
  1223. };
  1224. list.push(params);
  1225. }
  1226. this.setSelectInfoByType({ type: this.pageType, info: list });
  1227. // this.$forceUpdate();
  1228. },
  1229. //多风格-减去商品
  1230. delGoodsEvent(item) {
  1231. const list = this.selectList;
  1232. for (let index = 0; index < list.length; index++) {
  1233. const element = list[index];
  1234. if (element.id == item.id && element.classId == item.classId) {
  1235. if (element.bigCount > 0) {
  1236. list[index] = { ...element, bigCount: --element.bigCount };
  1237. }
  1238. if (element.bigCount == 0 || !element.bigCount) {
  1239. list.splice(index, 1);
  1240. }
  1241. break;
  1242. }
  1243. }
  1244. this.setSelectInfoByType({ type: this.pageType, info: list });
  1245. },
  1246. //添加商品
  1247. addItemEvent(item) {
  1248. const list = this.selectList;
  1249. if(!this.$util.isEmpty(list)){
  1250. let hasItem = false
  1251. for (let index = 0; index < list.length; index++) {
  1252. const element = list[index];
  1253. if (element.id == item.id && element.classId == item.classId) {
  1254. hasItem = true
  1255. const { bigCount, smallCount } = list[index];
  1256. const ratio = Number(item.ratio);
  1257. if (this.isLimit) {
  1258. if ( Number(bigCount) * ratio + Number(smallCount) > Number(item.bigNum) * ratio + Number(item.smallNum) ) {
  1259. list[index].bigCount = Number(item.bigNum);
  1260. list[index].smallCount = Number(item.smallNum);
  1261. list[index].itemPrice = item.itemPrice?Number(item.itemPrice):'';
  1262. }
  1263. }
  1264. list[index] = {
  1265. ...element,
  1266. bigCount: Number(item.bigCount),
  1267. smallCount: Number(item.smallCount),
  1268. itemPrice: item.itemPrice?Number(item.itemPrice):''
  1269. };
  1270. break;
  1271. }
  1272. }
  1273. if(hasItem == false){
  1274. list.push(item)
  1275. }
  1276. }else{
  1277. list.push(item)
  1278. }
  1279. this.setSelectInfoByType({ type: this.pageType, info: list });
  1280. //记忆已经选择的花材
  1281. this.rememberProduct()
  1282. },
  1283. //更新商品
  1284. updateItemEvent(item) {
  1285. const list = this.selectList;
  1286. for (let index = 0; index < list.length; index++) {
  1287. const element = list[index];
  1288. if (element.id == item.id && element.classId == item.classId) {
  1289. const { bigCount, smallCount } = list[index];
  1290. const ratio = Number(item.ratio);
  1291. if (this.isLimit) {
  1292. if ( Number(bigCount) * ratio + Number(smallCount) > Number(item.bigNum) * ratio + Number(item.smallNum) ) {
  1293. list[index].bigCount = Number(item.bigNum);
  1294. list[index].smallCount = Number(item.smallNum);
  1295. list[index].itemPrice = item.itemPrice?Number(item.itemPrice):'';
  1296. }
  1297. }
  1298. list[index] = {
  1299. ...element,
  1300. bigCount: Number(item.bigCount),
  1301. smallCount: Number(item.smallCount),
  1302. itemPrice: item.itemPrice?Number(item.itemPrice):''
  1303. };
  1304. break;
  1305. }
  1306. }
  1307. this.setSelectInfoByType({ type: this.pageType, info: list });
  1308. //记忆已经选择的花材
  1309. this.rememberProduct()
  1310. },
  1311. //切换左侧商品类别
  1312. swichClass(cur) {
  1313. // clearInterval(this.timeFun)
  1314. this.classIndex = cur;
  1315. this.filterProductInfo[cur].isActive = true;
  1316. if(cur==this.filterProductInfo.length-1){
  1317. this.filterProductInfo[cur-1].isActive = true;
  1318. }
  1319. if(cur!=0&&cur!=this.filterProductInfo.length-2){
  1320. this.filterProductInfo[cur-1].isActive = true;
  1321. this.filterProductInfo[cur+1].isActive = true;
  1322. if(this.filterProductInfo[cur+1].child.length<5&&cur<this.filterProductInfo.length-3){
  1323. this.filterProductInfo[cur+2].isActive = true;
  1324. }
  1325. }
  1326. this.scroll_into_view = '';
  1327. this.$nextTick(()=>{this.scroll_into_view = 'class_'+cur;})
  1328. // this.scrollPushList();
  1329. //解决最后一个 ***来回*** 问题 (由于点击左侧导航,右侧锚点位置信息变化,此时滚动事件也随之滚动。)
  1330. uni.setStorageSync("resolve","last");
  1331. setTimeout(()=>{
  1332. uni.removeStorageSync("resolve")
  1333. },400);
  1334. },
  1335. //成品(多风格)-切换左侧商品类别
  1336. suitSwichClass(cur) {
  1337. // clearInterval(this.suitTimeFun)
  1338. this.suitClassIndex = cur;
  1339. this.suitFilterProductInfo[cur].isActive = true;
  1340. if(cur==this.suitFilterProductInfo.length-1){
  1341. this.suitFilterProductInfo[cur-1].isActive = true;
  1342. }
  1343. if(cur!=0&&cur!=this.suitFilterProductInfo.length-1){
  1344. this.suitFilterProductInfo[cur-1].isActive = true;
  1345. this.suitFilterProductInfo[cur+1].isActive = true;
  1346. }
  1347. this.suit_scroll_into_view = '';
  1348. this.$nextTick(()=>{this.suit_scroll_into_view = 'class_'+cur;})
  1349. // this.suitScrollPushList();
  1350. // this.scrollPushList();
  1351. //解决最后一个 ***来回*** 问题 (由于点击左侧导航,右侧锚点位置信息变化,此时滚动事件也随之滚动。)
  1352. uni.setStorageSync("resolve","last");
  1353. setTimeout(()=>{
  1354. uni.removeStorageSync("resolve")
  1355. },400);
  1356. },
  1357. //滚动商品监听
  1358. suit_scroll_detail(options){
  1359. //options 为滚动信息。 options.detail.scrollTop 值为相对于scroll-view。
  1360. if(!uni.getStorageSync("resolve")){
  1361. this.suit_get_node_details(options);
  1362. };
  1363. },
  1364. //获取商品节点信息
  1365. suit_get_node_details(options){
  1366. const query = uni.createSelectorQuery().in(this); //获得实力
  1367. //获取多个节点方式
  1368. query.selectAll(".suitSelectAll").boundingClientRect(data=>{
  1369. this.suit_top_list = data.map(item=>{
  1370. return Math.ceil(item.top);
  1371. });
  1372. this.suit_async_detail_msg(options);
  1373. }).exec();
  1374. },
  1375. suit_async_detail_msg(options){
  1376. //options 为滚动信息。 options.detail.scrollTop 值为相对于scroll-view。
  1377. let top_page = options.detail.scrollTop + this.suit_top_list[0];
  1378. for(let i = 0;i < this.suit_top_list.length; i++){
  1379. let node1 = this.suit_top_list[i];
  1380. let node2 = this.suit_top_list[i+1];
  1381. if(node2 && top_page >= node1 && top_page < node2){
  1382. this.suitClassIndex = i;
  1383. this.suitFilterProductInfo[this.suitClassIndex].isActive = true;
  1384. this.suitFilterProductInfo[this.suitClassIndex+1].isActive = true;
  1385. // this.suitScrollPushList();
  1386. break;
  1387. }else if(node2 && top_page === node2){
  1388. this.suitClassIndex = i + 1;
  1389. this.suitFilterProductInfo[this.suitClassIndex].isActive = true;
  1390. this.suitFilterProductInfo[this.suitClassIndex+1].isActive = true;
  1391. // this.suitScrollPushList();
  1392. break;
  1393. }
  1394. }
  1395. },
  1396. suit_scroll_bottom(){
  1397. this.suitClassIndex = this.suitFilterProductInfo.length-1
  1398. this.suitFilterProductInfo[this.suitClassIndex].isActive = true;
  1399. // this.suitScrollPushList();
  1400. },
  1401. //搜索对应花材
  1402. suitSearchFn() {
  1403. let list = copyObject(this.suitFilterProductInfo);
  1404. if(!this.$util.isEmpty(this.py)){
  1405. clearInterval(this.suitTimeFun)
  1406. let commonChildList = [];
  1407. list.forEach(ele => {
  1408. //过滤常用
  1409. if(ele.classId != -2){
  1410. if (!this.$util.isEmpty(ele.child)) {
  1411. ele.child.forEach(obj => {
  1412. let hasName = obj.name && obj.name.indexOf(this.py) >= 0;
  1413. let elePy = (obj.py && obj.py.toLocaleUpperCase()) || "";
  1414. let hasPy = elePy.indexOf(this.py.toLocaleUpperCase()) >= 0;
  1415. if (hasName || hasPy) {
  1416. if(this.$util.isEmpty(commonChildList)){
  1417. commonChildList = [...commonChildList,{classId: ele.classId, className: ele.className,isActive:true,child:[obj]}];
  1418. }else{
  1419. commonChildList.forEach(res => {
  1420. if(res.classId==ele.classId){
  1421. res.child.push(obj)
  1422. }else{
  1423. commonChildList.push({classId: ele.classId, className: ele.className,isActive:true,child:[obj]});
  1424. }
  1425. })
  1426. }
  1427. }
  1428. });
  1429. }
  1430. }
  1431. });
  1432. list = this.$util.unique(commonChildList)
  1433. this.suitFilterProductInfo = list;
  1434. this.suitClassIndex = 0;
  1435. this.suit_scroll_into_view = 'class_'+0;
  1436. }else{
  1437. this.suitFilterProductInfo = '';
  1438. this.suitClassIndex = 0;
  1439. this.suit_scroll_into_view = 'class_'+0;
  1440. this.suitScrollPushList();
  1441. }
  1442. },
  1443. searchFn() {
  1444. let list = copyObject(this.productInfo);
  1445. if(!this.$util.isEmpty(this.py)){
  1446. //大于等于2个字符才开始搜索 shish 20210622
  1447. if(this.py.length <=1){
  1448. return
  1449. }
  1450. //红色粉色紫色绿色白色花材太多,搜索时会卡住,所以设置输入第三个字母时才搜索
  1451. // if(this.py.toLocaleLowerCase().indexOf('fs') == 0 && this.py.length <= 2){
  1452. // return
  1453. // }
  1454. // if(this.py.toLocaleLowerCase().indexOf('hs') == 0 && this.py.length <= 2){
  1455. // return
  1456. // }
  1457. // if(this.py.toLocaleLowerCase().indexOf('zs') == 0 && this.py.length <= 2){
  1458. // return
  1459. // }
  1460. // if(this.py.toLocaleLowerCase().indexOf('ls') == 0 && this.py.length <= 2){
  1461. // return
  1462. // }
  1463. // if(this.py.toLocaleLowerCase().indexOf('bs') == 0 && this.py.length <= 2){
  1464. // return
  1465. // }
  1466. clearInterval(this.timeFun)
  1467. let commonChildList = [];
  1468. list.forEach(ele => {
  1469. //过滤常用
  1470. if(ele.classId != -2){
  1471. if (!this.$util.isEmpty(ele.child)) {
  1472. ele.child.forEach(obj => {
  1473. //模糊搜索 shish 20210622
  1474. let hasName = obj.itemName && obj.itemName.indexOf(this.py) >= 0;
  1475. let elePy = (obj.py && obj.py.toLocaleUpperCase()) || "";
  1476. let hasPy = elePy.indexOf(this.py.toLocaleUpperCase()) >= 0;
  1477. if (hasName || hasPy) {
  1478. if(this.$util.isEmpty(commonChildList)){
  1479. commonChildList = [...commonChildList,{classId: ele.classId, className: ele.className,isActive:true,child:[obj]}];
  1480. }else{
  1481. commonChildList.forEach(res => {
  1482. if(res.classId==ele.classId){
  1483. res.child.push(obj)
  1484. }else{
  1485. commonChildList.push({classId: ele.classId, className: ele.className,isActive:true,child:[obj]});
  1486. }
  1487. })
  1488. }
  1489. }
  1490. });
  1491. }
  1492. }
  1493. });
  1494. list = this.$util.unique(commonChildList)
  1495. this.filterProductInfo = list;
  1496. this.classIndex = 0;
  1497. this.scroll_into_view = 'class_'+0;
  1498. }else{
  1499. this.filterProductInfo = '';
  1500. this.classIndex = 0;
  1501. this.scroll_into_view = 'class_'+0;
  1502. this.scrollPushList();
  1503. }
  1504. },
  1505. //请求课程数据
  1506. async initPxClassData() {
  1507. try {
  1508. const { data } = await getPxClassDataApi({pageSize:50});
  1509. this.pxClassInfo = data.list;
  1510. } catch (error) {
  1511. } finally {
  1512. uni.hideLoading();
  1513. }
  1514. },
  1515. }
  1516. };