Files
webs/power_leasing/src/views/productDetail/index.js
2025-10-20 10:15:13 +08:00

592 lines
25 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 { addToCart } from '../../utils/cartManager'
import { getMachineInfo } from '../../api/products'
import { addCart, getGoodsList } from '../../api/shoppingCart'
export default {
name: 'ProductDetail',
data() {
return {
product: null,
loading: false,
// 默认展开的行keys
expandedRowKeys: [],
selectedMap: {},
params: {
id: "",
},
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
}
},
mounted() {
console.log(this.$route.params.id, "i叫哦附加费")
if (this.$route.params.id) {
this.params.id = this.$route.params.id
this.product = true
// 默认展开第一行
if (this.productListData && this.productListData.length) {
this.expandedRowKeys = [this.productListData[0].id]
}
this.fetchGetMachineInfo(this.params)
} else {
this.$message.error('商品不存在')
this.product = false
}
this.fetchGetGoodsList()
},
methods: {
async fetchGetMachineInfo(params) {
this.productDetailLoading = true
const res = await getMachineInfo(params)
console.log(res)
if (res && res.code === 200) {
console.log(res.data, 'res.rows');
this.paymentMethodList = res.data.payConfigList || []
const list =res.data.machineRangeInfoList || []
const withKeys = list.map((group, idx) => {
const fallbackId = `grp-${idx}`
const groupId = group.id || group.onlyKey || (group.productMachineRangeGroupDto && group.productMachineRangeGroupDto.id)
const firstMachineId = Array.isArray(group.productMachines) && group.productMachines.length > 0 ? group.productMachines[0].id : undefined
// 为机器行设置默认租赁天数为1并确保未选中状态
const normalizedMachines = Array.isArray(group.productMachines)
? group.productMachines.map(m => ({
...m,
leaseTime: (m && m.leaseTime && Number(m.leaseTime) > 0) ? Number(m.leaseTime) : 1,
_selected: false // 确保所有机器行初始状态为未选中
}))
: []
return { ...group, id: groupId || (firstMachineId ? `m-${firstMachineId}` : fallbackId), productMachines: normalizedMachines }
})
this.productListData = withKeys
if (this.productListData.length && (!this.expandedRowKeys || !this.expandedRowKeys.length)) {
this.expandedRowKeys = [this.productListData[0].id]
}
// 产品机器加载完成后,依据购物车集合执行一次本地禁用与勾选
this.$nextTick(() => {
this.machinesLoaded = true
// 已取消与购物车对比:不再自动禁用或勾选
})
}
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 getGoodsList(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
}
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)
}
},
// 打开确认弹窗:以当前界面勾选(_selected)为准,并在打开后清空左侧勾选状态
handleOpenAddToCartDialog() {
// 扫描当前所有系列下被勾选的机器
const groups = Array.isArray(this.productListData) ? this.productListData : []
const pickedAll = groups.flatMap(g => Array.isArray(g.productMachines) ? g.productMachines.filter(m => !!m && !!m._selected) : [])
const picked = pickedAll.filter(m => m && (m.saleState === 0 || m.saleState === undefined || m.saleState === null))
if (!picked.length) {
this.$message.warning('请先勾选至少一台矿机')
return
}
if (picked.length < pickedAll.length) {
this.$message.warning('部分机器已售出或售出中,已自动为您排除')
}
// 使用弹窗中的固定快照,避免后续清空勾选影响弹窗显示
this.confirmAddDialog.items = picked.slice()
this.confirmAddDialog.visible = true
// 打开后立即把左侧复选框清空,避免“勾选了两个但弹窗只有一条”的不一致问题
this.$nextTick(() => {
try { this.clearAllSelections() } catch (e) { /* noop */ }
})
},
// 确认加入:调用后端购物车接口,传入裸数组 [{ productId, productMachineId }]
async handleConfirmAddSelectedToCart() {
// 以弹窗中的列表为准,避免与左侧勾选状态不一致
const allSelected = Array.isArray(this.confirmAddDialog.items) ? this.confirmAddDialog.items.filter(Boolean) : []
if (!allSelected.length) {
this.$message.warning('请先勾选至少一台矿机')
return
}
const productId = this.params && this.params.id ? this.params.id : (this.$route && this.$route.params && this.$route.params.id)
if (!productId) {
this.$message.error('商品ID缺失无法加入购物车')
return
}
// 裸数组,仅包含后端要求的两个字段
const payload = allSelected.map(item => ({
productId: productId,
productMachineId: item.id,
leaseTime: Number(item.leaseTime || 1)
}))
try {
const res = await this.fetchAddCart(payload)
// 若后端返回码存在,这里做一下兜底提示
if (!res || (res.code && Number(res.code) !== 200)) {
this.$message.error(res && res.msg ? res.msg : '加入购物车失败,请稍后重试')
return
}
// 立即本地更新禁用状态把刚加入的机器ID合并进本地集合
try {
allSelected.forEach(item => {
if (item && item.id) this.cartMachineIdSet.add(item.id)
this.$set(item, '_selected', false)
this.$set(item, '_inCart', true)
if (!item.leaseTime || Number(item.leaseTime) <= 0) this.$set(item, 'leaseTime', 1)
})
this.$nextTick(() => this.autoSelectAndDisable())
} catch (e) { /* noop */ }
this.$message({
message: `已加入 ${allSelected.length} 台矿机到购物车`,
type: 'success',
duration: 3000,
showClose: true,
});
this.confirmAddDialog.visible = false
// 清空选中映射,然后重新加载数据(数据加载时会自动设置 _selected: false
this.selectedMap = {}
// 重新加载机器信息和购物车数据
this.fetchGetMachineInfo(this.params)
this.fetchGetGoodsList()
// 通知头部刷新服务端购物车数量
try {
// 如果没有传数量header 会主动拉取服务端数量
window.dispatchEvent(new CustomEvent('cart-updated'))
} catch (e) { /* noop */ }
} catch (e) {
console.error('加入购物车失败: ', e)
this.$message.error('加入购物车失败,请稍后重试')
}
},
// 取消所有商品勾选(内层表格的自定义 checkbox
clearAllSelections() {
try {
// 清空选中映射
this.selectedMap = {}
// 遍历所有系列与机器,复位 _selected
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('添加到购物车失败,请稍后重试')
}
}
}
}