Files
webs/power_leasing/src/views/productDetail/index.js
2025-12-05 16:24:20 +08:00

948 lines
42 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

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

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-ASIC1-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('缺少店铺IDshopId无法加载商品列表')
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 */ }
},
/**
* 获取行在当前支付方式下的展示价格
* 优先匹配 selectedPayKeychain|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-ASIC1-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 与 type0-ASIC1-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());
},
}
}