import { getProductById } from '../../utils/productService' import { getMachineInfo, getPayTypes,getShopMachineList,addGoodsV2 } from '../../api/products' import { truncateAmountByCoin, truncateTo6 } from '../../utils/amount' import { getGoodsListV2 } from '../../api/shoppingCart' export default { name: 'ProductDetail', data() { return { product: null, loading: false, // 默认展开的行keys expandedRowKeys: [], selectedMap: {}, // 新接口:单层矿机列表 & 支付方式 machineList: [], paymentMethodList: [], // 筛选状态 selectedPayKey: null, filters: { chain: '', coin: '', minPrice: null, maxPrice: null, minPower: null, maxPower: null, minPowerDissipation: null, maxPowerDissipation: null, unit: 'GH/S' }, // 实际算力单位选项 powerUnitOptions: ['KH/S', 'MH/S', 'GH/S', 'TH/S', 'PH/S'], // 排序状态:true 升序,false 降序 sortStates: { priceSort: true, powerSort: true, powerDissipationSort: true }, // 当前激活的排序字段(仅当用户点击后才会传参) activeSortField: '', // 首次进入时是否已按价格币种设置过支付方式筛选默认值 payFilterDefaultApplied: false, params: { id: "", pageNum: 1, pageSize: 10, }, confirmAddDialog: { visible: false, items: [] }, // 购物车中已存在的当前商品机器集合:id 与 user|miner 组合键 cartMachineIdSet: new Set(), cartCompositeKeySet: new Set(), cartLoaded: false, machinesLoaded: false, /** * 可展开的产品系列数据 * 每个系列(group)包含多个可选条目(variants) */ productListData: [ // { // id: 'grp-1', // group: 'A系列', // summary: { // theoryPower: '56T', // computingPower: '54T', // powerDissipation: '3200W', // algorithm: 'power', // type: 'A-Pro', // count: 3, // price: '¥1000+' // }, // variants: [ // { id: 'A-1', model: 'A1', theoryPower: '14T', computingPower: '13.5T', powerDissipation: '780W', algorithm: 'power', stock: 50, price: 999, quantity: 1 }, // { id: 'A-2', model: 'A2', theoryPower: '18T', computingPower: '17.2T', powerDissipation: '900W', algorithm: 'power', stock: 40, price: 1299, quantity: 1 }, // { id: 'A-3', model: 'A3', theoryPower: '24T', computingPower: '23.1T', powerDissipation: '1520W', algorithm: 'power', stock: 30, price: 1699, quantity: 1 } // ] // }, // { // id: 'grp-2', // group: 'B系列', // summary: { // theoryPower: '72T', // computingPower: '70T', // powerDissipation: '4100W', // algorithm: 'power', // type: 'B-Max', // count: 2, // price: '¥2000+' // }, // variants: [ // { id: 'B-1', model: 'B1', theoryPower: '32T', computingPower: '31.2T', powerDissipation: '1800W', algorithm: 'power', stock: 28, price: 2199, quantity: 1 }, // { id: 'B-2', model: 'B2', theoryPower: '40T', computingPower: '38.8T', powerDissipation: '2300W', algorithm: 'power', stock: 18, price: 2699, quantity: 1 } // ] // } ], tableData: [ // { // theoryPower: "55656",//理论算力 // computingPower: "44545",//实际算力 // powerDissipation: "5565",//功耗 // algorithm: "power",//算法 // type: "型号1",//矿机型号 // number:2001, // cost:"1000",//价格 // }, // { // theoryPower: "55656",//理论算力 // computingPower: "44545",//实际算力 // powerDissipation: "5565",//功耗 // algorithm: "power",//算法 // type: "型号1",//矿机型号 // number:2001, // cost:"1000",//价格 // }, // { // theoryPower: "55656",//理论算力 // computingPower: "44545",//实际算力 // powerDissipation: "5565",//功耗 // algorithm: "power",//算法 // type: "型号1",//矿机型号 // number:2001, // cost:"1000",//价格 // }, // { // theoryPower: "55656",//理论算力 // computingPower: "44545",//实际算力 // powerDissipation: "5565",//功耗 // algorithm: "power",//算法 // type: "型号1",//矿机型号 // number:2001, // cost:"1000",//价格 // }, ], productDetailLoading: false, pageSizes: [10, 20, 50], currentPage: 1, total: 0, // 动态列表(模拟渲染) dynamicMeta: {}, dynamicColumns: [], dynamicRows: [], dynamicSearch: { visible: false, keyword: '' }, // 矿机种类:0-ASIC,1-GPU(默认GPU) machineType: 1, } }, mounted() { // 读取用户上次选择的矿机种类(0: ASIC, 1: GPU) try{ const savedType = Number(window && window.localStorage ? window.localStorage.getItem('pl_machineType') : NaN) if (savedType === 0 || savedType === 1) { this.machineType = savedType } }catch(e){/* noop */} // 不再使用本地模拟数据,动态表格完全依赖后端返回的 columns/rows // 仅当路由携带 shopId 时,才发起店铺商品请求 const routeShopId = (this.$route && this.$route.params && (this.$route.params.shopId || this.$route.params.id)) || (this.$route && this.$route.query && this.$route.query.shopId) if (routeShopId) { this.params.id = routeShopId this.product = true this.fetchGetMachineInfo(this.buildQueryParams()) this.fetchPayTypes() } else { this.$message.warning('缺少店铺ID(shopId),无法加载商品列表') this.product = false } }, methods: { // 动态表格单元格格式化(金额/算力/天数/文本)- 统一最多显示6位小数,hover展示完整 formatDynamicCell(row, col) { try{ let val = row[col.key] if (val === null || val === undefined || val === '') return { text: '—', full: '—', truncated: false } if (col.type === 'amount') { // 价格列:单位取 priceList 里的 coin;默认取第一条;选择支付方式后按选择展示 if (col.key === 'price') { if (Array.isArray(row.priceList) && row.priceList.length) { const pv = this.getDisplayPrice(row) const pc = this.getDisplayPriceCoin(row) if (pv !== null && pv !== undefined) val = pv const nPrice = val const t = truncateTo6(nPrice) const coinUnit = (pc || '').toString().toUpperCase() return { text: coinUnit ? `${t.text} ${coinUnit}` : t.text, full: coinUnit ? `${t.full} ${coinUnit}` : t.full, truncated: t.truncated } } // 无 priceList:仅展示数值,不附加任何单位 const t = truncateTo6(val) return { text: t.text, full: t.full, truncated: t.truncated } } // 列级优先:若列声明 currency=USDT,则固定展示为 "xx.xx USDT" const colCurrency = (col.currency || '').toString().toUpperCase() if (colCurrency === 'USDT') { const t = truncateTo6(val) return { text: `${t.text} USDT`, full: `${t.full} USDT`, truncated: t.truncated } } // 兜底:不再使用 meta 的货币符号,直接返回数值 const t = truncateTo6(val) return { text: t.text, full: t.full, truncated: t.truncated } } if (col.type === 'hashrate') { const unit = col.unit ? ` ${col.unit}` : '' const t = truncateTo6(val) return { text: `${t.text}${unit}`, full: `${t.full}${unit}`, truncated: t.truncated } } if (col.type === 'days') { const n = Number(val) if (!Number.isFinite(n)) return { text: String(val), full: String(val), truncated: false } const s = `${Math.floor(n)} 天` return { text: s, full: s, truncated: false } } const s = String(val) return { text: s, full: s, truncated: false } }catch(e){ return { text: '—', full: '—', truncated: false } } }, /** * 如果存在 priceList,则用第一条的 chain|coin 作为默认筛选值, * 使“支付方式筛选”与价格列默认展示一致 */ ensureDefaultPayFilterFromPrices() { try{ if (this.payFilterDefaultApplied) return const rows = Array.isArray(this.dynamicRows) ? this.dynamicRows : [] const firstWithPriceList = rows.find(r => Array.isArray(r && r.priceList) && r.priceList.length) if (!firstWithPriceList) return const first = firstWithPriceList.priceList[0] const chain = String(first && first.chain || '').trim() const coin = String(first && first.coin || '').trim() if (!chain && !coin) return this.selectedPayKey = `${chain}|${coin}` this.filters.chain = chain this.filters.coin = coin this.payFilterDefaultApplied = true }catch(e){ /* noop */ } }, /** * 获取行在当前支付方式下的展示价格 * 优先匹配 selectedPayKey(chain|coin),否则回退 priceList[0];再否则回退 row.price */ getDisplayPrice(row){ try{ const list = Array.isArray(row && row.priceList) ? row.priceList : [] if (!list.length) return row && row.price const key = this.selectedPayKey if (key) { const [chainRaw, coinRaw] = String(key).split('|') const chain = String(chainRaw || '').toUpperCase().trim() const coin = String(coinRaw || '').toUpperCase().trim() const hit = list.find(it => String(it && it.chain).toUpperCase().trim() === chain && String(it && it.coin).toUpperCase().trim() === coin ) if (hit && hit.price !== undefined && hit.price !== null) return hit.price } const first = list[0] if (first && first.price !== undefined && first.price !== null) return first.price return row && row.price }catch(e){ return row && row.price } }, /** * 获取行在当前支付方式下价格的币种(coin) */ getDisplayPriceCoin(row){ try{ const list = Array.isArray(row && row.priceList) ? row.priceList : [] if (!list.length) return '' const key = this.selectedPayKey if (key) { const [chainRaw, coinRaw] = String(key).split('|') const chain = String(chainRaw || '').toUpperCase().trim() const coin = String(coinRaw || '').toUpperCase().trim() const hit = list.find(it => String(it && it.chain).toUpperCase().trim() === chain && String(it && it.coin).toUpperCase().trim() === coin ) if (hit && hit.coin) return String(hit.coin) } const first = list[0] return first && first.coin ? String(first.coin) : '' }catch(e){ return '' } }, _truncate(num, decimals=2){ try{ const f = Math.pow(10, decimals) return (Math.floor(Number(num)*f)/f).toFixed(decimals) }catch(e){ return String(num) } }, // 判断是否为“框出来部分”的最后一列(最后一个 hashrate 列) isLastHashrateColumn(colIdx){ try{ const cols = this.getRenderedColumns() for (let i = cols.length - 1; i >= 0; i--) { if (String(cols[i] && cols[i].type).toLowerCase() === 'hashrate') { return i === colIdx } } return false }catch(e){ return false } }, // 仅渲染前 8 个算力列,后接其它非算力列(如收益、回收期) getRenderedColumns(){ try{ const cols = Array.isArray(this.dynamicColumns) ? this.dynamicColumns : [] const hashrate = cols.filter(c => String(c && c.type).toLowerCase() === 'hashrate').slice(0, 8) const others = cols.filter(c => String(c && c.type).toLowerCase() !== 'hashrate') return [...hashrate, ...others] }catch(e){ return [] } }, // 打开动态搜索弹窗 handleOpenDynamicSearch(){ this.dynamicSearch.visible = true this.dynamicSearch.keyword = '' }, // 确认搜索:向后端请求新的 columns/rows,替换动态表格 async handleConfirmDynamicSearch(){ const keyword = (this.dynamicSearch.keyword || '').trim() this.dynamicSearch.visible = false await this.fetchDynamicTable({ shopId: this.params.id, type: 1, keyword }) }, // 拉取动态表格数据(占位实现:如果后端已就绪,直接替换为真实接口) async fetchDynamicTable(params){ try{ // 这里预留与后端对接: // 期待返回格式:{ code, data: { meta, columns, rows } } // 示例中用本地 mock 演示:根据 keyword 过滤/调整列 // 如果没有 keyword,就还原初始 mock if (!params || !params.keyword) { return } // 简单模拟:当 keyword 命中 'ERG',只保留 ERG + 价格/型号/回收期 等少量列 const kw = String(params.keyword).toUpperCase() const baseCols = (this.dynamicColumns || []).filter(c => ['model','price','maxLeaseDays','monthIncome'].includes(c.key)) const hitCols = (this.dynamicColumns || []).filter(c => String(c.label || c.key).toUpperCase().includes(kw)) const nextCols = [...(baseCols.length?baseCols:[this.dynamicColumns[0]||[]]), ...hitCols] if (nextCols.length) { this.dynamicColumns = nextCols // 行数据无需特别处理(真实环境后端会按列同步返回),这里保留原 rows } }catch(e){ // eslint-disable-next-line no-console console.warn('fetchDynamicTable mock error', e) } }, // 切换矿机种类:0-ASIC,1-GPU handleMachineTypeChange(){ // 切换前清空所有已勾选状态与确认弹窗 try { if (Array.isArray(this.dynamicRows)) { this.dynamicRows.forEach(r => { if (r) this.$set(r, '_selected', false) }) } if (this.confirmAddDialog) { this.confirmAddDialog.items = [] this.confirmAddDialog.visible = false } } catch (e) { /* noop */ } // 变更类型后,重新请求数据与支付方式 this.fetchGetMachineInfo(this.buildQueryParams()) this.fetchPayTypes() // 本地记住用户选择 try{ if (window && window.localStorage) { window.localStorage.setItem('pl_machineType', String(this.machineType)) } }catch(e){/* noop */} }, // 行币种:优先行内 payCoin > coin,其次取全局表头币种 getRowCoin(row) { try { const c = (row && (row.payCoin || row.coin)) || this.getPriceCoinSymbol() || '' return String(c).toUpperCase() } catch (e) { return '' } }, // 金额格式化:不补0、不四舍五入;返回 {text,truncated,full} formatAmount(value, coin) { return truncateAmountByCoin(value, coin) }, // 数值格式化:最多6位小数,截断不补0 formatNum6(value) { return truncateTo6(value) }, /** * 首次加载时,将“支付方式筛选”的默认选中值设为与价格列币种一致, * 并同步 filters.chain/filters.coin;仅执行一次,不触发额外查询。 */ ensureDefaultPayFilterSelection() { try { if (this.payFilterDefaultApplied) return const payList = Array.isArray(this.paymentMethodList) ? this.paymentMethodList : [] if (!payList.length) return const coinSymbol = (this.getPriceCoinSymbol && this.getPriceCoinSymbol()) || '' if (!coinSymbol) return const hit = payList.find(it => String(it && it.payCoin).toUpperCase() === String(coinSymbol).toUpperCase()) if (!hit) return const key = `${hit.payChain || ''}|${hit.payCoin || ''}` this.selectedPayKey = key this.filters.chain = String(hit.payChain || '').trim() this.filters.coin = String(hit.payCoin || '').trim() this.payFilterDefaultApplied = true } catch (e) { /* noop */ } }, // 切换排序:field in ['priceSort','powerSort','powerDissipationSort'] handleToggleSort(field) { try { if (!this.sortStates) this.sortStates = {} if (this.activeSortField !== field) { // 切换到新的字段:默认从升序开始(true) // 先将其它字段复位为升序(▲) Object.keys(this.sortStates).forEach(k => { this.sortStates[k] = true }) this.activeSortField = field // 后端默认升序,首次点击应为降序 this.sortStates[field] = false } else { // 同一字段:升降序切换 this.sortStates[field] = !this.sortStates[field] } const params = this.buildQueryParams() this.fetchGetMachineInfo(params) } catch (e) { /* noop */ } }, // 组合查询参数:店铺入口,必须包含 shopId 与 type(0-ASIC,1-GPU) buildQueryParams() { const q = { shopId: this.params.id, type: this.machineType } // 分页参数始终透传 try { if (this.params && this.params.pageNum != null) q.pageNum = this.params.pageNum if (this.params && this.params.pageSize != null) q.pageSize = this.params.pageSize } catch (e) { /* noop */ } // 仅当用户真实填写(>0)时才传参;默认/空值不传 const addNum = (obj, key, name) => { const raw = obj[key] if (raw === null || raw === undefined || raw === '') return const n = Number(raw) if (Number.isFinite(n) && n > 0) q[name] = n } // 支付方式条件:有值才传 if (this.filters.chain && String(this.filters.chain).trim()) q.chain = String(this.filters.chain).trim() if (this.filters.coin && String(this.filters.coin).trim()) q.coin = String(this.filters.coin).trim() if (this.filters.unit && String(this.filters.unit).trim()) q.unit = String(this.filters.unit).trim() addNum(this.filters, 'minPrice', 'minPrice') addNum(this.filters, 'maxPrice', 'maxPrice') addNum(this.filters, 'minPower', 'minPower') addNum(this.filters, 'maxPower', 'maxPower') addNum(this.filters, 'minPowerDissipation', 'minPowerDissipation') addNum(this.filters, 'maxPowerDissipation', 'maxPowerDissipation') // 排序参数:仅在用户点击某一列后传当前列 try { if (this.activeSortField) { const s = this.sortStates || {} q[this.activeSortField] = !!s[this.activeSortField] } } catch (e) { /* noop */ } return q }, // 拉取支付方式(兼容 shopId) async fetchPayTypes() { try { // 现规则:商品详情由店铺入口进入,使用 shopId 查询支付方式 // 为兼容后端两种入参,优先传 shopId,后端若仍使用 productId 也能兼容处理 const res = await getPayTypes({ shopId: this.params.id, productId: this.params.id }) // 接口示例:{ code: 0, data: [ { payChain, payCoin, payCoinImage, shopId } ], msg: '' } if (res && (res.code === 0 || res.code === 200)) { const list = Array.isArray(res.data) ? res.data : [] this.paymentMethodList = list // 支付方式加载后尝试设置默认筛选 this.ensureDefaultPayFilterSelection() } } catch (e) { // 忽略错误,保持页面可用 this.paymentMethodList = [] } }, async fetchGetMachineInfo(params) { this.productDetailLoading = true // 改为使用店铺机器列表接口 const res = await getShopMachineList(params) if (res && (res.code === 200 || res.code === 0)) { const root = (res && res.data) ? res.data : res const columns = Array.isArray(root.columns) ? root.columns : (Array.isArray(res.columns) ? res.columns : []) const rows = Array.isArray(root.rows) ? root.rows : (Array.isArray(res.rows) ? res.rows : []) const total = Number(root.total != null ? root.total : (res.total != null ? res.total : 0)) this.total = Number.isFinite(total) ? total : 0 // 动态表格:列与行 this.dynamicColumns = columns this.dynamicRows = rows.map(r => ({ saleNumbers: 0, saleOutNumbers: 0, leaseTime: 1, purchaseQuantity: 1, _selected: false, ...r })) // 根据 rows 的 priceList 设置默认支付方式 this.ensureDefaultPayFilterFromPrices() // 若后端同步返回支付方式,刷新本地支付方式 try { const payList = root && root.payConfigList if (Array.isArray(payList) && payList.length) { this.paymentMethodList = payList this.ensureDefaultPayFilterSelection() } } catch (e) { /* noop */ } } this.productDetailLoading = false }, /** * 加载商品详情 */ async loadProduct() { try { this.loading = true const productId = this.$route.params.id this.product = await getProductById(productId) if (!this.product) { this.$message({ message: '商品不存在', type: 'error', showClose: true }) } } catch (error) { console.error('加载商品详情失败:', error) this.$message({ message: '加载商品详情失败,请稍后重试', type: 'error', showClose: true }) } finally { this.loading = false } }, //加入购物车 async fetchAddCart(params) { const res = await addCart(params) return res }, //查询购物车列表 async fetchGetGoodsList(params) { const res = await getGoodsListV2(params) // 统计当前商品在购物车中已有的机器ID,用于禁用和默认勾选 try { const productId = this.params && this.params.id ? Number(this.params.id) : Number(this.$route.params.id) // 兼容两种返回结构:1) 旧:直接是商品分组数组 2) 新:店铺数组 → shoppingCartInfoDtoList const rawRows = Array.isArray(res && res.rows) ? res.rows : Array.isArray(res && res.data && res.data.rows) ? res.data.rows : Array.isArray(res && res.data) ? res.data : [] // 扁平化为商品分组 const groups = rawRows.length && rawRows[0] && Array.isArray(rawRows[0].shoppingCartInfoDtoList) ? rawRows.flatMap(shop => Array.isArray(shop.shoppingCartInfoDtoList) ? shop.shoppingCartInfoDtoList : []) : rawRows const matched = groups.filter(g => Number(g.productId) === productId) const ids = new Set() const compositeKeys = new Set() matched.forEach(r => { const list = Array.isArray(r.productMachineDtoList) ? r.productMachineDtoList : [] list.forEach(m => { if (!m) return if (m.id !== undefined && m.id !== null) ids.add(String(m.id)) if (m.user && m.miner) compositeKeys.add(`${String(m.user)}|${String(m.miner)}`) }) }) this.cartMachineIdSet = ids this.cartCompositeKeySet = compositeKeys // 计算购物车总数量并通知头部,避免页面初次加载时徽标显示为0 try { const totalCount = groups.reduce((sum, g) => sum + (Array.isArray(g && g.productMachineDtoList) ? g.productMachineDtoList.length : 0), 0) if (Number.isFinite(totalCount)) { window.dispatchEvent(new CustomEvent('cart-updated', { detail: { count: totalCount } })) } } catch (e) { /* noop */ } // 展开表格渲染后,默认勾选并禁用这些行 this.$nextTick(() => { this.cartLoaded = true this.autoSelectAndDisable() }) } catch (e) { console.warn('解析购物车数据失败', e) } }, /** * 处理返回 */ handleBack() { this.$router.push('/productList') }, /** * 点击系列行:切换展开/收起 * @param {Object} row - 当前行 */ handleSeriesRowClick(row) { const key = row.id const lockedIds = Object.keys(this.selectedMap).filter(k => (this.selectedMap[k] || []).length > 0) const opened = this.expandedRowKeys.includes(key) if (opened) { // 关闭当前行,仅保留已勾选的行展开 this.expandedRowKeys = lockedIds } else { // 打开当前行,同时保留已勾选的行展开 this.expandedRowKeys = Array.from(new Set([key, ...lockedIds])) } }, /** * 外层系列行样式 */ handleGetSeriesRowClassName() { return 'series-clickable-row' }, // 子表选择变化 handleInnerSelectionChange(parentRow, selections) { const key = parentRow.id this.$set(this.selectedMap, key, selections) const lockedIds = Object.keys(this.selectedMap).filter(k => (this.selectedMap[k] || []).length > 0) // 更新展开:锁定的行始终展开 const openedSet = new Set(this.expandedRowKeys) lockedIds.forEach(id => openedSet.add(id)) // 清理不再勾选且不是当前展开的行 this.expandedRowKeys = Array.from(openedSet).filter(id => lockedIds.includes(id) || id === key || this.expandedRowKeys.includes(id)) }, // 展开行变化时:已取消自动与购物车对比,无需勾选/禁用 handleExpandChange(row, expandedRows) { // no-op }, // 已取消对比购物车的自动勾选/禁用逻辑 autoSelectAndDisable() { }, // 选择器可选控制:已在购物车中的机器不可再选 isSelectable(row, index) { // 不再通过 selectable 禁用,以便勾选可见;通过行样式和交互阻止点击 return true }, // 判断在特定父行下是否已选择(配合自定义checkbox使用) isSelectedByParent(parentRow, row) { const key = parentRow && parentRow.id const list = (key && this.selectedMap[key]) || [] return !!list.find(it => it && it.id === row.id) }, // 手动切换选择(自定义checkbox与 selectedMap 同步),并维护每行的 _selected 状态 handleManualSelect(parentRow, row, checked) { // 禁用:已售出或售出中的机器不可选择 if (row && (row.saleState === 1 || row.saleState === 2)) { this.$message.warning('该机器已售出或售出中,无法选择') this.$set(row, '_selected', false) return } // 无价格:不可选择 try { const hasPrice = (Array.isArray(row && row.priceList) && row.priceList.some(it => it && it.price !== null && it.price !== undefined)) || (row && row.price !== null && row.price !== undefined && row.price !== '') if (!hasPrice) { this.$message.warning('该机器暂无价格,无法选择') this.$set(row, '_selected', false) return } } catch (e) { /* noop */ } const key = parentRow.id const list = (this.selectedMap[key] && [...this.selectedMap[key]]) || [] const idx = list.findIndex(it => it && it.id === row.id) if (checked && idx === -1) list.push(row) if (!checked && idx > -1) list.splice(idx, 1) this.$set(this.selectedMap, key, list) this.$set(row, '_selected', !!checked) }, // 为子表中已在购物车的行添加只读样式,并阻止点击取消 handleGetInnerRowClass({ row }) { if (!row) return '' return (row.saleState === 1 || row.saleState === 2) ? 'sold-row' : '' }, /** * 子行:减少数量 * @param {number} groupIndex - 系列索引 * @param {number} variantIndex - 变体索引 */ handleDecreaseVariantQuantity(groupIndex, variantIndex) { const item = this.productListData[groupIndex].variants[variantIndex] if (item.quantity > 1) { item.quantity-- } }, /** * 子行:增加数量 * @param {number} groupIndex - 系列索引 * @param {number} variantIndex - 变体索引 */ handleIncreaseVariantQuantity(groupIndex, variantIndex) { const item = this.productListData[groupIndex].variants[variantIndex] if (item.quantity < 99) { item.quantity++ } }, /** * 子行:输入数量校验 * @param {number} groupIndex - 系列索引 * @param {number} variantIndex - 变体索引 */ handleVariantQuantityInput(groupIndex, variantIndex) { const item = this.productListData[groupIndex].variants[variantIndex] const q = Number(item.quantity) if (!q || q < 1) item.quantity = 1 if (q > 99) item.quantity = 99 }, /** * 子行:加入购物车 * @param {Object} variant - 子项行数据 */ handleAddVariantToCart(variant) { if (!variant || !variant.onlyKey) return try { addToCart({ id: variant.onlyKey, title: variant.model, price: variant.price, quantity: variant.quantity }) this.$message.success(`已添加 ${variant.quantity} 件 ${variant.model} 到购物车`) variant.quantity = 1 } catch (error) { console.error('添加到购物车失败:', error) } }, // 统一加入购物车 handleAddSelectedToCart() { const allSelected = Object.values(this.selectedMap).flat().filter(Boolean) if (!allSelected.length) { this.$message.warning('请先勾选至少一台矿机') return } try { allSelected.forEach(item => { addToCart({ id: item.onlyKey || item.id, title: item.type || item.model || '矿机', price: item.price, quantity: 1, leaseTime: Number(item.leaseTime || 1) }) }) this.$message.success(`已加入 ${allSelected.length} 台矿机到购物车`) this.selectedMap = {} } catch (e) { console.error('统一加入购物车失败', e) } }, // 打开确认弹窗(基于动态表格的勾选行) handleOpenAddToCartDialog() { const rows = Array.isArray(this.dynamicRows) ? this.dynamicRows : [] const picked = rows.filter(r => !!r && !!r._selected) if (!picked.length) { this.$message.warning('请先勾选至少一台矿机') return } this.confirmAddDialog.items = picked.map(r => ({ ...r, leaseTime: Number(r.leaseTime || 1), purchaseQuantity: Number(r.purchaseQuantity || 1) })) this.confirmAddDialog.visible = true }, // 确认加入:调用 addGoodsV2(按条提交),GPU 不传 numbers async handleConfirmAddSelectedToCart() { const items = Array.isArray(this.confirmAddDialog.items) ? this.confirmAddDialog.items.filter(Boolean) : [] if (!items.length) { this.$message.warning('请先勾选至少一台矿机') return } try { // 按接口要求:一次性传数组,每个对象代表一个勾选商品 const payload = items.map(it => { const obj = { id: it.id, leaseTime: Number(it.leaseTime || 1) } if (this.machineType === 0) { obj.numbers = Number(it.purchaseQuantity || 1) } return obj }) const res = await addGoodsV2(payload) if (res && (res.code === 0 || res.code === 200)) { this.$message({ message: `已加入 ${items.length} 台矿机到购物车`, type: 'success', duration: 3000, showClose: true }) } this.confirmAddDialog.visible = false // 清空勾选 try { (this.dynamicRows || []).forEach(r => { if (r) this.$set(r, '_selected', false) }) } catch (e) { /* noop */ } // 通知头部刷新 try { window.dispatchEvent(new CustomEvent('cart-updated')) } catch (e) { /* noop */ } } catch (e) { // eslint-disable-next-line no-console console.error('addGoodsV2 error:', e) this.$message.error('加入购物车失败,请稍后重试') } }, // 取消所有商品勾选(内层表格的自定义 checkbox) clearAllSelections() { try { // 清空选中映射(遗留字段) this.selectedMap = {} if (Array.isArray(this.machineList) && this.machineList.length) { this.machineList.forEach(m => { if (m) this.$set(m, '_selected', false) }) return } const groups = Array.isArray(this.productListData) ? this.productListData : [] groups.forEach(g => { const list = Array.isArray(g.productMachines) ? g.productMachines : [] list.forEach(m => { if (m) this.$set(m, '_selected', false) }) }) } catch (e) { /* noop */ } }, /** * 减少数量 * @param {number} rowIndex - 表格行索引 */ handleDecreaseQuantity(rowIndex) { if (this.tableData[rowIndex].quantity > 1) { this.tableData[rowIndex].quantity-- } }, /** * 增加数量 * @param {number} rowIndex - 表格行索引 */ handleIncreaseQuantity(rowIndex) { if (this.tableData[rowIndex].quantity < 99) { this.tableData[rowIndex].quantity++ } }, /** * 处理数量输入 * @param {number} rowIndex - 表格行索引 */ handleQuantityInput(rowIndex) { const quantity = this.tableData[rowIndex].quantity if (quantity < 1) { this.tableData[rowIndex].quantity = 1 } else if (quantity > 99) { this.tableData[rowIndex].quantity = 99 } }, /** * 处理数量输入框失焦 * @param {number} rowIndex - 表格行索引 */ handleQuantityBlur(rowIndex) { const quantity = this.tableData[rowIndex].quantity if (!quantity || quantity < 1) { this.tableData[rowIndex].quantity = 1 } else if (quantity > 99) { this.tableData[rowIndex].quantity = 99 } }, /** * 添加到购物车 * @param {Object} rowData - 表格行数据 */ handleAddToCart(rowData) { if (!rowData || rowData.quantity < 1) { this.$message.warning('请选择有效的数量') return } try { addToCart({ id: rowData.date, // 使用矿机名称作为ID title: rowData.date, price: rowData.price, quantity: rowData.quantity, leaseTime: Number(rowData.leaseTime || 1) }) this.$message.success(`已添加 ${rowData.quantity} 件 ${rowData.date} 到购物车`) // 重置数量 rowData.quantity = 1 } catch (error) { console.error('添加到购物车失败:', error) this.$message.error('添加到购物车失败,请稍后重试') } }, handleSizeChange(val) { console.log(`每页 ${val} 条`); this.params.pageSize = val; this.params.pageNum = 1; this.currentPage = 1; // 携带当前激活的排序字段 this.fetchGetMachineInfo(this.buildQueryParams()); }, handleCurrentChange(val) { console.log(`当前页: ${val}`); this.params.pageNum = val; // 携带当前激活的排序字段 this.fetchGetMachineInfo(this.buildQueryParams()); }, } }