每周更新
This commit is contained in:
5
power_leasing/package-lock.json
generated
5
power_leasing/package-lock.json
generated
@@ -5171,6 +5171,11 @@
|
||||
"esprima": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"jsencrypt": {
|
||||
"version": "3.5.4",
|
||||
"resolved": "https://registry.npmjs.org/jsencrypt/-/jsencrypt-3.5.4.tgz",
|
||||
"integrity": "sha512-kNjfYEMNASxrDGsmcSQh/rUTmcoRfSUkxnAz+MMywM8jtGu+fFEZ3nJjHM58zscVnwR0fYmG9sGkTDjqUdpiwA=="
|
||||
},
|
||||
"jsesc": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"axios": "^1.11.0",
|
||||
"core-js": "^3.8.3",
|
||||
"element-ui": "^2.15.14",
|
||||
"jsencrypt": "^3.5.4",
|
||||
"vue": "^2.6.14",
|
||||
"vue-router": "^3.5.1",
|
||||
"vuex": "^3.6.2"
|
||||
|
||||
@@ -108,5 +108,17 @@ export function getPurchasedItems(data) {
|
||||
})
|
||||
}
|
||||
|
||||
//已购商品详情
|
||||
export function getPurchasedInfoV2(data) {
|
||||
return request({
|
||||
url: `/lease/v2/order/info/getPurchasedInfo`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -154,6 +154,15 @@ export function balanceWithdrawListV2(data) {
|
||||
})
|
||||
}
|
||||
|
||||
// 修改店铺钱包配置 V2
|
||||
export function updateShopConfigV2(data) {
|
||||
return request({
|
||||
url: `/lease/v2/shop/updateShopConfigV2`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import './utils/loginInfo.js';
|
||||
// 全局输入防表情守卫(极简、无侵入)
|
||||
import { initNoEmojiGuard } from './utils/noEmojiGuard.js';
|
||||
|
||||
// console.log = ()=>{} //全局关闭打印
|
||||
console.log = ()=>{} //全局关闭打印
|
||||
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@@ -100,9 +100,13 @@ export const accountRoutes = [
|
||||
}
|
||||
},
|
||||
{
|
||||
// 兼容旧入口:收款记录 -> 卖家资金流水(收款tab)
|
||||
path: 'receipt-record',
|
||||
name: 'accountReceiptRecord',
|
||||
component: () => import('../views/account/receiptRecord.vue'),
|
||||
redirect: (to) => ({
|
||||
path: '/account/seller-funds-flow',
|
||||
query: { ...(to && to.query ? to.query : {}), tab: 'receipt' }
|
||||
}),
|
||||
meta: {
|
||||
title: '收款记录',
|
||||
description: '卖家收款流水记录',
|
||||
@@ -110,15 +114,29 @@ export const accountRoutes = [
|
||||
}
|
||||
},
|
||||
{
|
||||
// 兼容旧入口:提现记录 -> 卖家资金流水(提现tab)
|
||||
path: 'withdraw-record',
|
||||
name: 'accountWithdrawRecord',
|
||||
component: () => import('../views/account/withdrawRecord.vue'),
|
||||
redirect: (to) => ({
|
||||
path: '/account/seller-funds-flow',
|
||||
query: { ...(to && to.query ? to.query : {}), tab: 'withdraw' }
|
||||
}),
|
||||
meta: {
|
||||
title: '提现记录',
|
||||
description: '卖家提现流水记录',
|
||||
allAuthority: ['all']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'seller-funds-flow',
|
||||
name: 'accountSellerFundsFlow',
|
||||
component: () => import('../views/account/sellerFundsFlow.vue'),
|
||||
meta: {
|
||||
title: '资金流水',
|
||||
description: '卖家收款/提现记录切换查看',
|
||||
allAuthority: ['all']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'shop-new',
|
||||
name: 'accountShopNew',
|
||||
@@ -169,23 +187,23 @@ export const accountRoutes = [
|
||||
allAuthority: ['all']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'purchased',
|
||||
name: 'accountPurchased',
|
||||
component: () => import('../views/account/purchased.vue'),
|
||||
meta: {
|
||||
title: '已购商品',
|
||||
description: '查看已购买的商品列表',
|
||||
allAuthority: ['all']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'purchased-machine-config',
|
||||
name: 'accountPurchasedMachineConfig',
|
||||
component: () => import('../views/account/purchasedMachineConfig.vue'),
|
||||
meta: {
|
||||
title: '已购矿机配置',
|
||||
description: '查看已购买矿机的配置信息',
|
||||
title: '已购商品',
|
||||
description: '查看已购买商品的配置信息',
|
||||
allAuthority: ['all']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'purchased-machine-detail/:id',
|
||||
name: 'purchasedMachineDetail',
|
||||
component: () => import('../views/account/purchasedMachineDetail.vue'),
|
||||
meta: {
|
||||
title: '已购商品详情',
|
||||
description: '查看已购买商品的详细信息',
|
||||
allAuthority: ['all']
|
||||
}
|
||||
},
|
||||
@@ -199,16 +217,6 @@ export const accountRoutes = [
|
||||
allAuthority: ['all']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'purchased-detail/:id',
|
||||
name: 'PurchasedDetail',
|
||||
component: () => import('../views/account/purchasedDetail.vue'),
|
||||
meta: {
|
||||
title: '已购商品详情',
|
||||
description: '查看已购商品详细信息',
|
||||
allAuthority: ['all']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'orders',
|
||||
name: 'accountOrders',
|
||||
|
||||
101
power_leasing/src/utils/rsaEncrypt.js
Normal file
101
power_leasing/src/utils/rsaEncrypt.js
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* RSA 加密工具
|
||||
* 使用 jsencrypt 库进行 RSA 公钥加密
|
||||
*/
|
||||
|
||||
// 导入 jsencrypt
|
||||
import JSEncrypt from 'jsencrypt'
|
||||
|
||||
// RSA 公钥(Base64 格式,不带 BEGIN/END 标记)
|
||||
const RSA_PUBLIC_KEY_BASE64 = `MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsQVIKYozXCfnXUw8+omYLdcdL1pTzmQh35YPsvn22wM4SQJKvMmXmcS6bI5Bu+5zCjL0F56DzfKz0BNZEwb46UshUOO+KFBUr8CxjYE8NOgIsoe5FUn57O6er9/KySaWlkpGZX49K+l3e90R+dFUEfRE/ijYpeZWkLRwcgWZ+2u6HGpl9h/eF6XD0aW9asDjdAbxUQ48TlaWgfP+OHC+Zy2GKGQG16EcDMczrN6a2HbFnwRIUKrFP67UqyRq11BTUziOhXLY8J0MFuwXUk2OY4VpqjrJjHHjlHYADjIL/5K4Io2AhRU9+QSsKFR2wGxi4e8vw2IXDzscrDuah/7YSwIDAQAB`
|
||||
|
||||
// RSA 公钥(PEM 格式)
|
||||
const RSA_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
||||
${RSA_PUBLIC_KEY_BASE64}
|
||||
-----END PUBLIC KEY-----`
|
||||
|
||||
/**
|
||||
* 获取 JSEncrypt 构造函数
|
||||
* @returns {Function} JSEncrypt 构造函数
|
||||
*/
|
||||
function getJSEncrypt() {
|
||||
// 优先使用已导入的 JSEncrypt
|
||||
if (JSEncrypt) {
|
||||
return JSEncrypt
|
||||
}
|
||||
|
||||
// 尝试从全局获取(可能通过 CDN 引入)
|
||||
if (typeof window !== 'undefined' && window.JSEncrypt) {
|
||||
return window.JSEncrypt
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA 加密函数(同步版本)
|
||||
* @param {string} plainText - 要加密的明文
|
||||
* @returns {string|null} 加密后的密文(Base64 编码),失败返回 null
|
||||
*/
|
||||
export function rsaEncryptSync(plainText) {
|
||||
if (!plainText || typeof plainText !== 'string') {
|
||||
console.error('RSA 加密:输入必须是非空字符串')
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const Encrypt = getJSEncrypt()
|
||||
if (!Encrypt) {
|
||||
console.error('JSEncrypt 未加载')
|
||||
return null
|
||||
}
|
||||
|
||||
const encrypt = new Encrypt()
|
||||
encrypt.setPublicKey(RSA_PUBLIC_KEY)
|
||||
const encrypted = encrypt.encrypt(plainText)
|
||||
|
||||
if (!encrypted) {
|
||||
console.error('RSA 加密失败:返回值为空')
|
||||
return null
|
||||
}
|
||||
|
||||
return encrypted
|
||||
} catch (error) {
|
||||
console.error('RSA 加密异常:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA 加密函数(异步版本,兼容同步调用)
|
||||
* @param {string} plainText - 要加密的明文
|
||||
* @returns {Promise<string|null>} 加密后的密文(Base64 编码),失败返回 null
|
||||
*/
|
||||
export async function rsaEncrypt(plainText) {
|
||||
if (!plainText || typeof plainText !== 'string') {
|
||||
console.error('RSA 加密:输入必须是非空字符串')
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const Encrypt = getJSEncrypt()
|
||||
if (!Encrypt) {
|
||||
console.error('JSEncrypt 未加载')
|
||||
return null
|
||||
}
|
||||
|
||||
const encrypt = new Encrypt()
|
||||
encrypt.setPublicKey(RSA_PUBLIC_KEY)
|
||||
const encrypted = encrypt.encrypt(plainText)
|
||||
|
||||
if (!encrypted) {
|
||||
console.error('RSA 加密失败:返回值为空')
|
||||
return null
|
||||
}
|
||||
|
||||
return encrypted
|
||||
} catch (error) {
|
||||
console.error('RSA 加密异常:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -63,8 +63,7 @@ export default {
|
||||
// 买家侧导航
|
||||
buyerLinks: [
|
||||
{ label: '我的钱包', to: '/account/wallet' },
|
||||
{ label: '已购商品', to: '/account/purchased' },
|
||||
{ label: '已购矿机配置', to: '/account/purchased-machine-config' },
|
||||
{ label: '已购商品', to: '/account/purchased-machine-config' },
|
||||
{ label: '订单列表', to: '/account/orders' },
|
||||
// { label: '充值记录', to: '/account/rechargeRecord' },
|
||||
// { label: '提现记录', to: '/account/withdrawalHistory' },
|
||||
@@ -76,8 +75,7 @@ export default {
|
||||
{ label: '我的店铺', to: '/account/shops' },
|
||||
{ label: '商品列表', to: '/account/products' },
|
||||
{ label: '已售出订单', to: '/account/seller-orders' },
|
||||
{ label: '收款记录', to: '/account/receipt-record' },
|
||||
{ label: '提现记录', to: '/account/withdraw-record' },
|
||||
{ label: '资金流水', to: '/account/seller-funds-flow' },
|
||||
|
||||
],
|
||||
}
|
||||
@@ -158,9 +156,8 @@ export default {
|
||||
// 买家前缀优先匹配,确保"已购详情"等页面归属买家侧
|
||||
const buyerPrefixes = [
|
||||
'/account/wallet',
|
||||
'/account/purchased',
|
||||
'/account/purchased-detail',
|
||||
'/account/purchased-machine-config',
|
||||
'/account/purchased-machine-detail',
|
||||
'/account/orders',
|
||||
'/account/funds-flow'
|
||||
]
|
||||
@@ -172,6 +169,8 @@ export default {
|
||||
'/account/product-detail',
|
||||
'/account/product-machine-add',
|
||||
'/account/seller-orders',
|
||||
'/account/seller-funds-flow',
|
||||
// 兼容旧路径(会被路由重定向到 seller-funds-flow)
|
||||
'/account/receipt-record',
|
||||
'/account/withdraw-record',
|
||||
'/account/shop-config'
|
||||
@@ -208,16 +207,18 @@ export default {
|
||||
const map = {
|
||||
'/account/seller-orders': ['/account/seller-orders'],
|
||||
'/account/products': ['/account/products', '/account/product-detail'],
|
||||
'/account/purchased': ['/account/purchased', '/account/purchased-detail'],
|
||||
'/account/purchased-machine-config': ['/account/purchased-machine-config']
|
||||
'/account/purchased-machine-config': ['/account/purchased-machine-config', '/account/purchased-machine-detail']
|
||||
}
|
||||
const prefixes = map[pathLike]
|
||||
if (Array.isArray(prefixes)) {
|
||||
return prefixes.some(p => {
|
||||
// 精确匹配
|
||||
if (current === p) return true
|
||||
// 对于详情页路径,使用前缀匹配(支持 /account/purchased-detail/:id)
|
||||
if (p === '/account/purchased-detail' || p === '/account/product-detail') {
|
||||
// 对于详情页路径,使用前缀匹配(支持 /account/product-detail/:id)
|
||||
if (p === '/account/product-detail') {
|
||||
return current.indexOf(p) === 0
|
||||
}
|
||||
if (p === '/account/purchased-machine-detail') {
|
||||
return current.indexOf(p) === 0
|
||||
}
|
||||
// 列表页已经在上面精确匹配了,这里不需要额外处理
|
||||
@@ -225,8 +226,6 @@ export default {
|
||||
})
|
||||
}
|
||||
// 不在 map 中的路径,精确匹配
|
||||
// 特别注意:避免 /account/purchased 匹配到 /account/purchased-machine-config
|
||||
// 使用精确匹配而不是前缀匹配
|
||||
return current === pathLike
|
||||
}
|
||||
},
|
||||
|
||||
@@ -13,13 +13,17 @@
|
||||
请在本页点击 <b>钱包绑定</b>,配置自己的收款地址(支持不同链与币种)。
|
||||
</li>
|
||||
<li>
|
||||
<b>商品</b>:完成钱包绑定后,即可在“我的店铺”页面 <b>创建商品</b>。
|
||||
商品可按 <b>币种</b> 进行分类管理,创建的商品会在商城对买家展示。
|
||||
商品可理解为“不同算法、币种的机器集合分类”。
|
||||
</li>
|
||||
<li>
|
||||
<b>出售机器</b>:创建商品后,请进入 <b>商品列表</b> 为该商品 <b>添加出售机器明细</b>。
|
||||
必须添加出售机器,否则买家无法下单。买家点击某个商品后,会看到该商品下的机器明细并进行选购。
|
||||
<b>创建商品</b>:完成钱包绑定后,即可在“我的店铺”页面 点击<b>新增商品</b>按钮。
|
||||
<ul class="guide-substeps">
|
||||
<li>
|
||||
<b>ASIC 商品创建</b>:选择矿机种类为 ASIC,填写页面商品信息后创建,商品可按 <b>币种</b> 进行分类管理,创建的商品会在商城对买家展示;
|
||||
商品可理解为“不同算法、币种的机器集合分类”。
|
||||
</li>
|
||||
<li>
|
||||
<b>GPU 商品创建</b>:选择矿机种类为 GPU,查看页面注意事项并下载对应客户端,启动后读取自动创建。创建完成请进入 <b>商品列表</b> 为该商品手动配置售价等相关信息并上架。
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
<div class="guide-note">提示:建议先创建店铺 → 完成钱包绑定 → 创建商品的顺序,避免漏配导致无法收款或无法下单。</div>
|
||||
@@ -245,6 +249,7 @@ import { getMyShop, updateShop, deleteShop, queryShop, closeShop ,updateShopConf
|
||||
|
||||
import { coinList } from '@/utils/coinList'
|
||||
import { getShopConfig,getShopConfigV2 ,withdrawBalanceForSeller,updateShopConfigV2} from '@/api/wallet'
|
||||
import { rsaEncrypt, rsaEncryptSync } from '@/utils/rsaEncrypt'
|
||||
|
||||
|
||||
export default {
|
||||
@@ -434,12 +439,55 @@ export default {
|
||||
this.withdrawLoading = true
|
||||
try {
|
||||
const row = this.currentWithdrawRow || {}
|
||||
|
||||
/**
|
||||
* 提现地址 RSA 加密(与“钱包绑定”页面保持一致:同步优先,异步兜底)
|
||||
* - toAddress: 用户输入/默认地址
|
||||
* - fromAddress: 当前绑定的钱包地址(后端可能用于校验来源)
|
||||
*/
|
||||
const toAddressPlain = String(this.withdrawForm.toAddress || '').trim()
|
||||
const fromAddressPlain = String(row.payAddress || this.withdrawForm.toAddress || '').trim()
|
||||
|
||||
/** @type {string} */
|
||||
let encryptedToAddress = toAddressPlain
|
||||
if (encryptedToAddress) {
|
||||
const syncEncrypted = rsaEncryptSync(encryptedToAddress)
|
||||
if (syncEncrypted) {
|
||||
encryptedToAddress = syncEncrypted
|
||||
} else {
|
||||
const asyncEncrypted = await rsaEncrypt(encryptedToAddress)
|
||||
if (asyncEncrypted) {
|
||||
encryptedToAddress = asyncEncrypted
|
||||
} else {
|
||||
this.$message.error('钱包地址加密失败,请重试')
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {string} */
|
||||
let encryptedFromAddress = fromAddressPlain
|
||||
if (encryptedFromAddress) {
|
||||
const syncEncrypted = rsaEncryptSync(encryptedFromAddress)
|
||||
if (syncEncrypted) {
|
||||
encryptedFromAddress = syncEncrypted
|
||||
} else {
|
||||
const asyncEncrypted = await rsaEncrypt(encryptedFromAddress)
|
||||
if (asyncEncrypted) {
|
||||
encryptedFromAddress = asyncEncrypted
|
||||
} else {
|
||||
this.$message.error('钱包地址加密失败,请重试')
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const payload = {
|
||||
toChain: row.chain,
|
||||
toSymbol: row.payCoin,
|
||||
amount: Number(this.withdrawForm.amount),
|
||||
toAddress: this.withdrawForm.toAddress,
|
||||
fromAddress: row.payAddress || this.withdrawForm.toAddress || '',
|
||||
toAddress: encryptedToAddress,
|
||||
fromAddress: encryptedFromAddress,
|
||||
code: this.withdrawForm.googleCode,
|
||||
serviceCharge: Number(this.withdrawForm.fee) || 0
|
||||
}
|
||||
@@ -679,26 +727,53 @@ export default {
|
||||
this.deleteShopConfig({id:row.id})
|
||||
},
|
||||
|
||||
submitConfigEdit() {
|
||||
/**
|
||||
* 提交配置修改
|
||||
*/
|
||||
async submitConfigEdit() {
|
||||
// 仅校验钱包地址
|
||||
const addr = (this.configForm.payAddress || '').trim()
|
||||
if (!addr) {
|
||||
this.$message.warning('请输入钱包地址')
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 RSA 加密钱包地址(与“钱包绑定”页面保持一致:同步优先,异步兜底)
|
||||
* @type {string}
|
||||
*/
|
||||
let encryptedPayAddress = addr
|
||||
if (encryptedPayAddress) {
|
||||
const syncEncrypted = rsaEncryptSync(encryptedPayAddress)
|
||||
if (syncEncrypted) {
|
||||
encryptedPayAddress = syncEncrypted
|
||||
} else {
|
||||
const asyncEncrypted = await rsaEncrypt(encryptedPayAddress)
|
||||
if (asyncEncrypted) {
|
||||
encryptedPayAddress = asyncEncrypted
|
||||
} else {
|
||||
this.$message.error('钱包地址加密失败,请重试')
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const payload = {
|
||||
id: this.configForm.id,
|
||||
chain: this.configForm.chainValue || this.configForm.chainLabel || '',
|
||||
payAddress: this.configForm.payAddress
|
||||
payAddress: encryptedPayAddress
|
||||
}
|
||||
;(async () => {
|
||||
try {
|
||||
const res = await updateShopConfigV2(payload)
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
this.$message.success('保存成功')
|
||||
this.visibleConfigEdit = false
|
||||
this.fetchShopConfigs(this.shop.id)
|
||||
}
|
||||
})()
|
||||
} catch (e) {
|
||||
console.error('修改配置失败', e)
|
||||
this.$message.error('修改配置失败,请重试')
|
||||
}
|
||||
},
|
||||
removeSelectedCoin(labelUpper) {
|
||||
const label = String(labelUpper || '').toLowerCase()
|
||||
@@ -935,6 +1010,8 @@ export default {
|
||||
.guide-steps { margin: 0; padding-left: 18px; color: #374151; }
|
||||
.guide-steps li { line-height: 1.9; margin: 6px 0; }
|
||||
.guide-steps b { color: #111827; }
|
||||
.guide-substeps { margin: 6px 0 0 0; padding-left: 18px; list-style: disc; }
|
||||
.guide-substeps li { line-height: 1.8; margin: 4px 0; }
|
||||
.guide-note { margin-top: 10px; color: #6b7280; font-size: 13px; background: #f9fafb; border: 1px dashed #e5e7eb; padding: 8px 10px; border-radius: 8px; }
|
||||
.coin-list { display: flex; align-items: center; gap: 8px; }
|
||||
.coin-img { width: 20px; height: 20px; border-radius: 4px; display: inline-block; }
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="product-machine-add">
|
||||
<div class="header">
|
||||
<el-button type="text" @click="handleBack">返回</el-button>
|
||||
<h2 class="title">添加出售机器</h2>
|
||||
<h2 class="title">创建商品</h2>
|
||||
</div>
|
||||
|
||||
<!-- <el-alert
|
||||
@@ -10,8 +10,8 @@
|
||||
type="warning"
|
||||
show-icon
|
||||
:closable="false"
|
||||
title="新增出售机器必须在 M2pool 有挖矿算力记录才能添加出租"
|
||||
description="建议稳定在 M2pool 矿池挖矿 24 小时之后,再添加出售该机器"
|
||||
title="新增商品必须在 M2pool 有挖矿算力记录才能添加出租"
|
||||
description="建议稳定在 M2pool 矿池挖矿 24 小时之后,再创建该商品"
|
||||
/> -->
|
||||
|
||||
<el-card shadow="never" class="form-card">
|
||||
@@ -236,7 +236,7 @@
|
||||
<div v-if="form.machineCategory === 'ASIC'" class="actions">
|
||||
<el-button @click="handleBack">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="handleSave"
|
||||
>确认添加</el-button
|
||||
>确认创建</el-button
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -1531,7 +1531,7 @@ export default {
|
||||
const res = await addAsicMachine(payload);
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
this.$message({
|
||||
message: "添加成功",
|
||||
message: "创建成功",
|
||||
duration: 3000,
|
||||
showClose: true,
|
||||
type: "success",
|
||||
@@ -1540,8 +1540,8 @@ export default {
|
||||
this.$router.push("/account/products");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("添加出售机器失败", e);
|
||||
console.log("添加失败");
|
||||
console.error("创建商品失败", e);
|
||||
console.log("创建失败");
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@
|
||||
<el-switch
|
||||
:active-value="0"
|
||||
:inactive-value="1"
|
||||
:value="(updateMap[getRowId(scope.row)] && updateMap[getRowId(scope.row)].state) || 0"
|
||||
:value="(updateMap[getRowId(scope.row)] && updateMap[getRowId(scope.row)].state) != null ? updateMap[getRowId(scope.row)].state : 1"
|
||||
@change="handleToggleState(scope.row, $event)"
|
||||
/>
|
||||
<span
|
||||
@@ -896,15 +896,31 @@ export default {
|
||||
const merged = { ...exist }
|
||||
// 覆盖为后端最新状态/天数(若存在)
|
||||
if (m && typeof m.maxLeaseDays !== 'undefined') merged.maxLeaseDays = m.maxLeaseDays
|
||||
if (m && (m.state === 0 || m.state === 1)) merged.state = m.state
|
||||
// 如果接口有返回 state,使用接口返回的值;否则保持现有值
|
||||
if (m && (m.state === 0 || m.state === 1)) {
|
||||
merged.state = m.state
|
||||
} else {
|
||||
// 如果接口没有返回 state,默认下架(state = 1)
|
||||
merged.state = 1
|
||||
}
|
||||
// 保存 effect 字段(从原始数据获取)
|
||||
if (m && (m.effect === 0 || m.effect === 1)) {
|
||||
merged.effect = m.effect
|
||||
} else if (merged.effect === undefined) {
|
||||
// 如果原始数据没有 effect,默认设为 1(能上架)
|
||||
merged.effect = 1
|
||||
}
|
||||
merged.priceList = normalizePriceList(m && m.priceList, exist.priceList)
|
||||
nextMap[key] = merged
|
||||
} else {
|
||||
// 如果接口没有返回 state,默认下架(state = 1)
|
||||
const defaultState = (m && (m.state === 0 || m.state === 1)) ? m.state : 1
|
||||
nextMap[key] = {
|
||||
id: key,
|
||||
maxLeaseDays: (m && typeof m.maxLeaseDays !== 'undefined') ? m.maxLeaseDays : '',
|
||||
priceList: normalizePriceList(m && m.priceList, []),
|
||||
state: (m && (m.state === 0 || m.state === 1)) ? m.state : 0
|
||||
state: defaultState,
|
||||
effect: (m && (m.effect === 0 || m.effect === 1)) ? m.effect : 1 // 保存 effect 字段,默认1(能上架)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1015,7 +1031,35 @@ export default {
|
||||
/** 切换状态(0 上架 1 下架) */
|
||||
handleToggleState(row, val) {
|
||||
const rowId = this.getRowId(row) || row.__key
|
||||
if (this.updateMap[rowId]) this.updateMap[rowId].state = val
|
||||
// 如果要上架(val === 0),需要检查 effect 字段
|
||||
if (val === 0) {
|
||||
// 从原始数据或 updateMap 中获取 effect 字段
|
||||
let effect = 1 // 默认能上架
|
||||
if (row && (row.effect === 0 || row.effect === 1)) {
|
||||
effect = row.effect
|
||||
} else if (this.updateMap[rowId] && (this.updateMap[rowId].effect === 0 || this.updateMap[rowId].effect === 1)) {
|
||||
effect = this.updateMap[rowId].effect
|
||||
}
|
||||
|
||||
if (effect === 0) {
|
||||
// effect = 0 表示不能上架,提示用户
|
||||
this.$message.warning('本网站暂时不支持该矿机币种及算法,暂时不能上架!')
|
||||
// 恢复为下架状态
|
||||
if (this.updateMap[rowId]) {
|
||||
this.updateMap[rowId].state = 1
|
||||
}
|
||||
this.updateArr = Object.values(this.updateMap)
|
||||
return
|
||||
}
|
||||
}
|
||||
// 允许切换状态
|
||||
if (this.updateMap[rowId]) {
|
||||
this.updateMap[rowId].state = val
|
||||
// 确保 effect 字段被保存(从原始数据获取)
|
||||
if (row && (row.effect === 0 || row.effect === 1)) {
|
||||
this.updateMap[rowId].effect = row.effect
|
||||
}
|
||||
}
|
||||
this.updateArr = Object.values(this.updateMap)
|
||||
},
|
||||
/** 提交 GPU 更新(触发按钮) */
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="account-purchased-machine-config">
|
||||
<div class="toolbar">
|
||||
<div class="left-area">
|
||||
<h2 class="page-title">已购矿机配置</h2>
|
||||
<h2 class="page-title">已购商品</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,63 +15,6 @@
|
||||
:header-cell-style="{ textAlign: 'left' }"
|
||||
:cell-style="{ textAlign: 'left' }"
|
||||
>
|
||||
<el-table-column type="expand" width="46">
|
||||
<template #default="scope">
|
||||
<div class="expand-content">
|
||||
<div class="expand-row" v-if="scope.row.walletAddress">
|
||||
<div class="expand-label">钱包地址:</div>
|
||||
<div class="expand-value">
|
||||
<span class="mono-ellipsis" style="font-family: monospace;">{{ scope.row.walletAddress }}</span>
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
icon="el-icon-document-copy"
|
||||
@click="handleCopy(scope.row.walletAddress, '钱包地址')"
|
||||
class="copy-btn"
|
||||
>
|
||||
复制
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="expand-row" v-if="scope.row.poolUrl">
|
||||
<div class="expand-label">矿池地址:</div>
|
||||
<div class="expand-value">
|
||||
<span class="mono-ellipsis">{{ scope.row.poolUrl }}</span>
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
icon="el-icon-document-copy"
|
||||
@click="handleCopy(scope.row.poolUrl, '矿池地址')"
|
||||
class="copy-btn"
|
||||
>
|
||||
复制
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="expand-row" v-if="scope.row.watchUrl">
|
||||
<div class="expand-label">挖矿信息页面地址:</div>
|
||||
<div class="expand-value">
|
||||
<span class="mono-ellipsis">{{ scope.row.watchUrl }}</span>
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
icon="el-icon-document-copy"
|
||||
@click="handleCopy(scope.row.watchUrl, '挖矿信息页面地址')"
|
||||
class="copy-btn"
|
||||
>
|
||||
复制
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!scope.row.walletAddress && !scope.row.poolUrl && !scope.row.watchUrl" class="expand-empty">
|
||||
暂无地址信息
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<!-- <el-table-column prop="id" label="ID" width="80" /> -->
|
||||
|
||||
<el-table-column prop="coin" label="币种" width="100">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.coin || '—' }}</span>
|
||||
@@ -90,31 +33,53 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="poolUser" label="矿池用户" min-width="140">
|
||||
<el-table-column prop="walletAddress" label="钱包地址" min-width="200">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.poolUser || '—' }}</span>
|
||||
<div class="address-cell">
|
||||
<span v-if="scope.row.walletAddress" class="mono-ellipsis" style="font-family: monospace;">{{ scope.row.walletAddress }}</span>
|
||||
<span v-else>—</span>
|
||||
<el-button
|
||||
v-if="scope.row.walletAddress"
|
||||
type="text"
|
||||
size="mini"
|
||||
icon="el-icon-document-copy"
|
||||
@click="handleCopy(scope.row.walletAddress, '钱包地址')"
|
||||
class="copy-btn"
|
||||
>
|
||||
复制
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
|
||||
|
||||
<el-table-column prop="startTime" label="开始时间" min-width="160">
|
||||
<el-table-column prop="poolUrl" label="矿池地址" min-width="200">
|
||||
<template #default="scope">
|
||||
<span>{{ formatDateTime(scope.row.startTime) }}</span>
|
||||
<div class="address-cell">
|
||||
<span v-if="scope.row.poolUrl" class="mono-ellipsis">{{ scope.row.poolUrl }}</span>
|
||||
<span v-else>—</span>
|
||||
<el-button
|
||||
v-if="scope.row.poolUrl"
|
||||
type="text"
|
||||
size="mini"
|
||||
icon="el-icon-document-copy"
|
||||
@click="handleCopy(scope.row.poolUrl, '矿池地址')"
|
||||
class="copy-btn"
|
||||
>
|
||||
复制
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="endTime" label="结束时间" min-width="160">
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="scope">
|
||||
<span>{{ formatDateTime(scope.row.endTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="status" label="状态" width="140">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(scope.row.status)">
|
||||
{{ getStatusText(scope.row.status) }}
|
||||
</el-tag>
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
@click="handleViewDetail(scope.row)"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -182,11 +147,7 @@ export default {
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取已购矿机配置失败', e)
|
||||
this.$message({
|
||||
message: '获取已购矿机配置失败,请重试',
|
||||
type: 'error',
|
||||
showClose: true
|
||||
})
|
||||
|
||||
this.tableData = []
|
||||
this.total = 0
|
||||
this.totalPage = 0
|
||||
@@ -283,6 +244,22 @@ export default {
|
||||
})
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 查看详情
|
||||
* @param {Object} row - 行数据
|
||||
*/
|
||||
handleViewDetail(row) {
|
||||
// 跳转到详情页面,传递行数据的ID
|
||||
const id = row.id || row.productMachineId || row.machineId
|
||||
if (id) {
|
||||
this.$router.push({
|
||||
name: 'purchasedMachineDetail',
|
||||
params: { id: id }
|
||||
})
|
||||
} else {
|
||||
this.$message.warning('无法获取详情,缺少ID信息')
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 获取状态文本
|
||||
* @param {number|boolean} status - 状态值
|
||||
@@ -345,49 +322,21 @@ export default {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
/* 展开内容样式 */
|
||||
.expand-content {
|
||||
padding: 16px;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.expand-row {
|
||||
/* 地址单元格样式 */
|
||||
.address-cell {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 16px;
|
||||
padding: 12px;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.expand-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.expand-label {
|
||||
min-width: 100px;
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
margin-right: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.expand-value {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.expand-value .mono-ellipsis {
|
||||
.address-cell .mono-ellipsis {
|
||||
font-family: monospace;
|
||||
color: #303133;
|
||||
line-height: 1.5;
|
||||
word-break: break-all;
|
||||
flex: 0 1 auto;
|
||||
margin-right: 8px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
@@ -400,13 +349,6 @@ export default {
|
||||
color: #66b1ff;
|
||||
}
|
||||
|
||||
.expand-empty {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 钱包地址和URL格式化显示样式 */
|
||||
.mono-ellipsis {
|
||||
font-family: monospace;
|
||||
|
||||
410
power_leasing/src/views/account/purchasedMachineDetail.vue
Normal file
410
power_leasing/src/views/account/purchasedMachineDetail.vue
Normal file
@@ -0,0 +1,410 @@
|
||||
<template>
|
||||
<div class="purchased-machine-detail">
|
||||
<div class="toolbar">
|
||||
<div class="left-area">
|
||||
<el-button
|
||||
type="text"
|
||||
icon="el-icon-arrow-left"
|
||||
@click="handleBack"
|
||||
class="back-btn"
|
||||
>
|
||||
返回
|
||||
</el-button>
|
||||
<h2 class="page-title">已购商品详情</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-content" v-loading="loading">
|
||||
<!-- 基本信息卡片 -->
|
||||
<el-card v-if="detailData" class="detail-card">
|
||||
<div slot="header" class="card-header">
|
||||
<span>基本信息</span>
|
||||
</div>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="挖矿账户">
|
||||
{{ detailData.poolUser || '—' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="矿池名称">
|
||||
{{ detailData.pool || '—' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="矿池挖矿地址">
|
||||
<div class="address-item" v-if="detailData.poolUrl">
|
||||
<span class="mono-ellipsis">{{ detailData.poolUrl }}</span>
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
icon="el-icon-document-copy"
|
||||
@click="handleCopy(detailData.poolUrl, '矿池挖矿地址')"
|
||||
class="copy-btn"
|
||||
>
|
||||
复制
|
||||
</el-button>
|
||||
</div>
|
||||
<span v-else>—</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="矿池所挖币种">
|
||||
{{ detailData.coin || '—' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="币种对应算法">
|
||||
{{ detailData.algorithm || '—' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="收款钱包">
|
||||
<div class="address-item" v-if="detailData.walletAddress">
|
||||
<span class="mono-ellipsis" style="font-family: monospace;">{{ detailData.walletAddress }}</span>
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
icon="el-icon-document-copy"
|
||||
@click="handleCopy(detailData.walletAddress, '收款钱包')"
|
||||
class="copy-btn"
|
||||
>
|
||||
复制
|
||||
</el-button>
|
||||
</div>
|
||||
<span v-else>—</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="挖矿信息页面地址" v-if="detailData.watchUrl">
|
||||
<div class="address-item">
|
||||
<span class="mono-ellipsis">{{ detailData.watchUrl }}</span>
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
icon="el-icon-document-copy"
|
||||
@click="handleCopy(detailData.watchUrl, '挖矿信息页面地址')"
|
||||
class="copy-btn"
|
||||
>
|
||||
复制
|
||||
</el-button>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
|
||||
<!-- 已购矿机信息列表 -->
|
||||
<el-card v-if="detailData" class="detail-card">
|
||||
<div slot="header" class="card-header">
|
||||
<span>已购矿机信息</span>
|
||||
</div>
|
||||
<el-table
|
||||
:data="purchasedMachinesList"
|
||||
border
|
||||
stripe
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ textAlign: 'left' }"
|
||||
:cell-style="{ textAlign: 'left' }"
|
||||
v-if="purchasedMachinesList && purchasedMachinesList.length > 0"
|
||||
>
|
||||
<el-table-column prop="workerId" label="矿工号" min-width="120">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.workerId || '—' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="power" label="实时算力" min-width="140">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.power || '—' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="recordTime" label="最近实时算力记录时间" min-width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ formatDateTime(scope.row.recordTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="startTime" label="挖矿开始时间" min-width="160">
|
||||
<template #default="scope">
|
||||
<span>{{ formatDateTime(scope.row.startTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="endTime" label="挖矿结束时间" min-width="160">
|
||||
<template #default="scope">
|
||||
<span>{{ formatDateTime(scope.row.endTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="140">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(scope.row.status)">
|
||||
{{ getStatusText(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div v-else class="empty-table">
|
||||
<p>暂无矿机信息</p>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<div v-if="!loading && !detailData" class="empty-state">
|
||||
<p>未找到详情信息</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getPurchasedInfoV2 } from '../../api/order'
|
||||
|
||||
export default {
|
||||
name: 'PurchasedMachineDetail',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
detailData: null
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchDetail()
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* 已购矿机列表(从 detailData.purchasedMachines 中提取)
|
||||
*/
|
||||
purchasedMachinesList() {
|
||||
if (!this.detailData) return []
|
||||
return Array.isArray(this.detailData.purchasedMachines) ? this.detailData.purchasedMachines : []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 获取详情数据
|
||||
*/
|
||||
async fetchDetail() {
|
||||
const id = this.$route.params.id
|
||||
if (!id) {
|
||||
this.$message.error('缺少ID参数')
|
||||
this.handleBack()
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
try {
|
||||
const params = {
|
||||
id: id
|
||||
}
|
||||
const res = await getPurchasedInfoV2(params)
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
// 接口返回的数据可能在 res.data 中
|
||||
this.detailData = res.data || res
|
||||
|
||||
if (!this.detailData) {
|
||||
this.$message.warning('未找到对应的详情信息')
|
||||
}
|
||||
} else {
|
||||
const errorMsg = (res && res.msg) || '获取详情失败'
|
||||
this.$message.error(errorMsg)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取详情失败', e)
|
||||
this.$message.error('获取详情失败,请稍后重试')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 返回上一页
|
||||
*/
|
||||
handleBack() {
|
||||
this.$router.go(-1)
|
||||
},
|
||||
/**
|
||||
* 格式化日期时间
|
||||
* @param {string|number} value - 日期时间值
|
||||
* @returns {string} 格式化后的日期时间字符串
|
||||
*/
|
||||
formatDateTime(value) {
|
||||
if (!value) return '—'
|
||||
try {
|
||||
const str = String(value)
|
||||
return str.includes('T') ? str.replace('T', ' ') : str
|
||||
} catch (e) {
|
||||
return String(value)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 复制文本到剪贴板
|
||||
* @param {string} text - 需要复制的内容
|
||||
* @param {string} label - 语义标签,用于提示文案
|
||||
*/
|
||||
async handleCopy(text, label = '内容') {
|
||||
if (!text) {
|
||||
this.$message({
|
||||
message: `${label}为空,无法复制`,
|
||||
type: 'warning',
|
||||
showClose: true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const value = String(text).trim()
|
||||
if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
|
||||
await navigator.clipboard.writeText(value)
|
||||
this.$message({
|
||||
message: `${label}已复制到剪贴板`,
|
||||
type: 'success',
|
||||
showClose: true
|
||||
})
|
||||
} else {
|
||||
// 备用复制方法
|
||||
const textArea = document.createElement('textarea')
|
||||
textArea.value = value
|
||||
textArea.style.position = 'fixed'
|
||||
textArea.style.left = '-9999px'
|
||||
document.body.appendChild(textArea)
|
||||
textArea.focus()
|
||||
textArea.select()
|
||||
try {
|
||||
document.execCommand('copy')
|
||||
this.$message({
|
||||
message: `${label}已复制到剪贴板`,
|
||||
type: 'success',
|
||||
showClose: true
|
||||
})
|
||||
} catch (err) {
|
||||
this.$message({
|
||||
message: '复制失败,请手动复制',
|
||||
type: 'error',
|
||||
showClose: true
|
||||
})
|
||||
}
|
||||
document.body.removeChild(textArea)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('复制失败', e)
|
||||
this.$message({
|
||||
message: '复制失败,请手动复制',
|
||||
type: 'error',
|
||||
showClose: true
|
||||
})
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 获取状态文本
|
||||
* @param {number|boolean} status - 状态值
|
||||
* @returns {string} 状态文本
|
||||
*/
|
||||
getStatusText(status) {
|
||||
const statusNum = Number(status)
|
||||
if (statusNum === 0) return '租约已到期'
|
||||
if (statusNum === 1) return '挖矿中'
|
||||
if (statusNum === 2) return '卖家矿机启动中'
|
||||
// 兼容旧数据(boolean类型)
|
||||
if (status === true) return '挖矿中'
|
||||
return '未知状态'
|
||||
},
|
||||
/**
|
||||
* 获取状态标签类型
|
||||
* @param {number|boolean} status - 状态值
|
||||
* @returns {string} el-tag 的类型
|
||||
*/
|
||||
getStatusType(status) {
|
||||
const statusNum = Number(status)
|
||||
if (statusNum === 0) return 'info' // 租约已到期 - 灰色
|
||||
if (statusNum === 1) return 'success' // 挖矿中 - 绿色
|
||||
if (statusNum === 2) return 'warning' // 卖家矿机启动中 - 黄色
|
||||
// 兼容旧数据(boolean类型)
|
||||
if (status === true) return 'success'
|
||||
return 'info'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.purchased-machine-detail {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.left-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
padding: 0;
|
||||
font-size: 14px;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
color: #66b1ff;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.address-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.address-item .mono-ellipsis {
|
||||
font-family: monospace;
|
||||
color: #303133;
|
||||
line-height: 1.5;
|
||||
word-break: break-all;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
flex-shrink: 0;
|
||||
padding: 0 8px;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
color: #66b1ff;
|
||||
}
|
||||
|
||||
.mono-ellipsis {
|
||||
font-family: monospace;
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.empty-table {
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
border
|
||||
stripe
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
style="width: 100%; table-layout: auto;"
|
||||
:row-key="getRowKey"
|
||||
:expand-row-keys="expandedRowKeys"
|
||||
:row-class-name="getRowClassName"
|
||||
@@ -47,7 +47,7 @@
|
||||
:header-cell-style="{ textAlign: 'left' }"
|
||||
:cell-style="{ textAlign: 'left' }"
|
||||
>
|
||||
<el-table-column type="expand" width="46">
|
||||
<el-table-column v-if="rows.length > 0" type="expand" width="46">
|
||||
<template #default="scope">
|
||||
<div class="detail-panel">
|
||||
<div class="detail-grid">
|
||||
@@ -74,10 +74,10 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="支付时间" min-width="160">
|
||||
<el-table-column label="支付时间" width="160">
|
||||
<template #default="scope">{{ formatFullTime(scope.row.createTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="收款金额(USDT)" min-width="160" align="right">
|
||||
<el-table-column label="收款金额(USDT)" width="140" align="right">
|
||||
<template #default="scope">
|
||||
<span class="amount-green">
|
||||
<el-tooltip
|
||||
@@ -96,30 +96,30 @@
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="收款链" min-width="120">
|
||||
<el-table-column label="收款链" width="140">
|
||||
<template #default="scope">{{ formatChain(scope.row.toChain) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="收款币种" min-width="100">
|
||||
<el-table-column label="收款币种" width="100">
|
||||
<template #default="scope">{{ String(scope.row.coin || '').toUpperCase() }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="收款地址" min-width="260">
|
||||
<el-table-column label="收款地址" min-width="200">
|
||||
<template #default="scope">
|
||||
<span class="mono-ellipsis" :title="scope.row.toAddress">{{ scope.row.toAddress }}</span>
|
||||
<el-button type="text" size="mini" @click.stop="copy(scope.row.toAddress)">复制</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="交易HASH" min-width="260">
|
||||
<el-table-column label="交易HASH" min-width="200">
|
||||
<template #default="scope">
|
||||
<span class="mono-ellipsis" :title="scope.row.txHash">{{ scope.row.txHash }}</span>
|
||||
<el-button type="text" size="mini" @click.stop="copy(scope.row.txHash)" v-if="scope.row.txHash">复制</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="支付状态" min-width="120">
|
||||
<el-table-column label="支付状态" width="120">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(scope.row.status)" size="small">{{ getStatusText(scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态更新时间" min-width="160">
|
||||
<el-table-column label="状态更新时间" width="160">
|
||||
<template #default="scope">{{ formatFullTime(scope.row.updateTime) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -306,8 +306,8 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
|
||||
.receipt-page { margin: 0; box-sizing: border-box; overflow-x: hidden; }
|
||||
.card { background: #fff; border: 1px solid #eee; border-radius: 10px; padding: 12px; box-shadow: 0 4px 18px rgba(0,0,0,0.04); overflow-x: auto; }
|
||||
.receipt-page { margin: 0; box-sizing: border-box; overflow-x: hidden; width: 100%; }
|
||||
.card { background: #fff; border: 1px solid #eee; border-radius: 10px; padding: 12px; box-shadow: 0 4px 18px rgba(0,0,0,0.04); width: 100%; box-sizing: border-box; }
|
||||
.card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
|
||||
.card-title { margin: 0; font-size: 18px; font-weight: 700; color: #2c3e50; }
|
||||
.card-actions { display: flex; align-items: center; gap: 8px; }
|
||||
@@ -322,6 +322,15 @@ export default {
|
||||
.type-red { color: #ef4444; }
|
||||
.pagination { display: flex; justify-content: flex-end; margin-top: 8px; }
|
||||
|
||||
/* 表格容器自适应 */
|
||||
.receipt-page :deep(.el-table) {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.receipt-page :deep(.el-table__body-wrapper) {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* 展开详情样式 */
|
||||
.detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 24px; padding: 8px 4px; }
|
||||
.detail-item { display: grid; grid-template-columns: 90px 1fr; align-items: center; gap: 8px; }
|
||||
@@ -331,7 +340,7 @@ export default {
|
||||
.detail-value.address { font-family: "Monaco", "Menlo", monospace; word-break: break-all; }
|
||||
|
||||
/* 单行等宽省略 */
|
||||
.mono-ellipsis { font-family: "Monaco", "Menlo", monospace; max-width: 360px; display: inline-block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; }
|
||||
.mono-ellipsis { font-family: "Monaco", "Menlo", monospace; display: inline-block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; max-width: 100%; }
|
||||
|
||||
/* 可点击行的轻交互提示 */
|
||||
.clickable-row:hover > td { background: #f8fafc !important; cursor: pointer; }
|
||||
|
||||
118
power_leasing/src/views/account/sellerFundsFlow.vue
Normal file
118
power_leasing/src/views/account/sellerFundsFlow.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div class="funds-page">
|
||||
<!-- 页面标题:卖家资金流水(与买家 fundsFlow.vue 视觉对齐) -->
|
||||
<h3 class="title">资金流水</h3>
|
||||
|
||||
<!-- Tab 容器:仅卖家维度(收款记录/提现记录) -->
|
||||
<div class="tabs-card" aria-label="资金流水tab" tabindex="0">
|
||||
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||||
<el-tab-pane label="收款记录" name="receipt" />
|
||||
<el-tab-pane label="提现记录" name="withdraw" />
|
||||
</el-tabs>
|
||||
|
||||
<!--
|
||||
使用 keep-alive + 动态组件:
|
||||
- 避免一次性挂载两个页面导致双请求
|
||||
- 切换 Tab 时缓存组件状态(如分页页码/展开行)
|
||||
-->
|
||||
<keep-alive>
|
||||
<component :is="activeComponentName" />
|
||||
</keep-alive>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AccountReceiptRecord from './receiptRecord.vue'
|
||||
import AccountWithdrawRecord from './withdrawRecord.vue'
|
||||
|
||||
/**
|
||||
* 卖家资金流水聚合页
|
||||
* - 仅整合“收款记录/提现记录”两个页面
|
||||
* - 复用原有页面组件,保证请求接口与内容展示完全不变
|
||||
*/
|
||||
export default {
|
||||
name: 'AccountSellerFundsFlow',
|
||||
components: {
|
||||
AccountReceiptRecord,
|
||||
AccountWithdrawRecord
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
/** @type {'receipt'|'withdraw'} */
|
||||
activeTab: 'receipt'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* 根据当前 tab 返回动态组件名
|
||||
* @returns {'AccountReceiptRecord'|'AccountWithdrawRecord'}
|
||||
*/
|
||||
activeComponentName() {
|
||||
return this.activeTab === 'withdraw' ? 'AccountWithdrawRecord' : 'AccountReceiptRecord'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.syncTabFromRoute()
|
||||
},
|
||||
watch: {
|
||||
/**
|
||||
* 监听 query.tab 变化(兼容外部链接/旧路由重定向)
|
||||
*/
|
||||
'$route.query.tab': {
|
||||
immediate: false,
|
||||
handler() {
|
||||
this.syncTabFromRoute()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 从路由 query.tab 同步当前 tab
|
||||
* 允许值:receipt / withdraw;其他值回退为 receipt
|
||||
*/
|
||||
syncTabFromRoute() {
|
||||
const tab = String((this.$route && this.$route.query && this.$route.query.tab) || '')
|
||||
this.activeTab = tab === 'withdraw' ? 'withdraw' : 'receipt'
|
||||
},
|
||||
/**
|
||||
* Tab 点击:同步写入 URL(replace,避免历史栈过长)
|
||||
*/
|
||||
handleTabClick() {
|
||||
const next = this.activeTab === 'withdraw' ? 'withdraw' : 'receipt'
|
||||
const current = String((this.$route && this.$route.query && this.$route.query.tab) || '')
|
||||
if (current === next) return
|
||||
this.$router.replace({ query: { ...(this.$route.query || {}), tab: next } })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 外层容器:与买家 fundsFlow.vue 的布局对齐 */
|
||||
.funds-page {
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0 0 18px 0;
|
||||
text-align: left;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.tabs-card {
|
||||
background: #fff;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.04);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@
|
||||
<script>
|
||||
import { getMyShop } from "@/api/shops";
|
||||
import { getChainAndList, addWalletShopConfig,getProductListForShopWalletConfig,updateProductListForShopWalletConfig } from "../../api/wallet";
|
||||
import { rsaEncrypt, rsaEncryptSync } from "../../utils/rsaEncrypt";
|
||||
|
||||
export default {
|
||||
name: "AccountShopConfig",
|
||||
@@ -280,7 +281,33 @@ export default {
|
||||
},
|
||||
async FetchAddWalletShopConfig(params) {
|
||||
this.loading = true;
|
||||
const res = await addWalletShopConfig(params);
|
||||
|
||||
// 使用 RSA 加密钱包地址
|
||||
let encryptedPayAddress = params.payAddress
|
||||
if (params.payAddress) {
|
||||
// 优先使用同步版本(如果 JSEncrypt 已加载)
|
||||
const syncEncrypted = rsaEncryptSync(params.payAddress)
|
||||
if (syncEncrypted) {
|
||||
encryptedPayAddress = syncEncrypted
|
||||
} else {
|
||||
// 如果同步版本失败,使用异步版本
|
||||
const asyncEncrypted = await rsaEncrypt(params.payAddress)
|
||||
if (asyncEncrypted) {
|
||||
encryptedPayAddress = asyncEncrypted
|
||||
} else {
|
||||
this.$message.error('钱包地址加密失败,请重试')
|
||||
this.loading = false
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const encryptedParams = {
|
||||
...params,
|
||||
payAddress: encryptedPayAddress
|
||||
}
|
||||
|
||||
const res = await addWalletShopConfig(encryptedParams);
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
this.$message.success("绑定成功");
|
||||
this.$router.push("/account/shops");
|
||||
@@ -341,10 +368,25 @@ export default {
|
||||
async preCheckBeforeBind() {
|
||||
try {
|
||||
this.loading = true
|
||||
// 先关闭弹窗,确保不会显示旧数据
|
||||
this.preCheck.visible = false
|
||||
this.preCheck.rows = []
|
||||
|
||||
const params = { chain: this.form.chain, payCoin: this.form.payCoin }
|
||||
const res = await getProductListForShopWalletConfig(params)
|
||||
|
||||
// 检查接口是否成功(code === 0 或 code === 200)
|
||||
const isSuccess = res && (res.code === 0 || res.code === 200)
|
||||
if (!isSuccess) {
|
||||
// 接口失败,直接走绑定流程,不显示弹窗
|
||||
await this.submitBindWithPrice([])
|
||||
return
|
||||
}
|
||||
|
||||
// 接口成功,提取数据
|
||||
const rows = Array.isArray(res && res.data) ? res.data : (Array.isArray(res && res.rows) ? res.rows : [])
|
||||
if (rows && rows.length) {
|
||||
// 确保 rows 是有效数组且有数据
|
||||
if (Array.isArray(rows) && rows.length > 0) {
|
||||
this.preCheck.rows = rows
|
||||
// 初始化各币种价格输入
|
||||
const coins = (this.form.payCoin || '').split(',').map(s => s.trim().toUpperCase()).filter(Boolean)
|
||||
@@ -360,11 +402,15 @@ export default {
|
||||
})
|
||||
this.preCheck.visible = true
|
||||
} else {
|
||||
// 无关联商品,直接绑定并设置(机器列表为空)
|
||||
// 无关联商品或数据为空,直接绑定并设置(机器列表为空),不显示弹窗
|
||||
this.preCheck.visible = false
|
||||
this.preCheck.rows = []
|
||||
await this.submitBindWithPrice([])
|
||||
}
|
||||
} catch (e) {
|
||||
// 接口异常不阻塞绑定流程
|
||||
// 接口异常不阻塞绑定流程,直接走绑定,不显示弹窗
|
||||
this.preCheck.visible = false
|
||||
this.preCheck.rows = []
|
||||
await this.submitBindWithPrice([])
|
||||
} finally {
|
||||
this.loading = false
|
||||
@@ -427,21 +473,45 @@ export default {
|
||||
(g.machineIds || []).forEach(id => { list.push({ productMachineId: id, price: priceStr }) })
|
||||
})
|
||||
}
|
||||
|
||||
// 使用 RSA 加密钱包地址
|
||||
let encryptedPayAddress = this.form.payAddress
|
||||
if (this.form.payAddress) {
|
||||
// 优先使用同步版本(如果 JSEncrypt 已加载)
|
||||
const syncEncrypted = rsaEncryptSync(this.form.payAddress)
|
||||
if (syncEncrypted) {
|
||||
encryptedPayAddress = syncEncrypted
|
||||
} else {
|
||||
// 如果同步版本失败,使用异步版本
|
||||
const asyncEncrypted = await rsaEncrypt(this.form.payAddress)
|
||||
if (asyncEncrypted) {
|
||||
encryptedPayAddress = asyncEncrypted
|
||||
} else {
|
||||
this.$message.error('钱包地址加密失败,请重试')
|
||||
this.loading = false
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const payload = {
|
||||
chain: this.form.chain,
|
||||
symbol: this.form.payCoin,
|
||||
payAddress: this.form.payAddress,
|
||||
// payAddress: this.form.payAddress,//使用未加密地址
|
||||
payAddress: encryptedPayAddress, // 使用加密后的地址
|
||||
productMachineForWalletConfigVoList: list
|
||||
}
|
||||
const res = await updateProductListForShopWalletConfig(payload)
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
|
||||
this.preCheck.visible = false
|
||||
this.resetPreCheckPrices()
|
||||
this.$message.success('绑定成功')
|
||||
this.$router.push('/account/shops')
|
||||
}else{
|
||||
this.preCheck.visible = true
|
||||
} else {
|
||||
// 绑定失败,关闭弹窗并显示错误消息
|
||||
this.preCheck.visible = false
|
||||
this.resetPreCheckPrices()
|
||||
|
||||
}
|
||||
} catch (e) {
|
||||
// 错误交由全局拦截或简单提示
|
||||
|
||||
@@ -306,6 +306,7 @@
|
||||
import { getWalletInfo, withdrawBalance ,balanceRechargeList,balanceWithdrawList,getRecentlyTransaction} from '@/api/wallet'
|
||||
|
||||
import { getChainAndList,bindWallet } from "../../api/wallet";
|
||||
import { rsaEncrypt, rsaEncryptSync } from '@/utils/rsaEncrypt'
|
||||
export default {
|
||||
name: 'WalletPage',
|
||||
data() {
|
||||
@@ -893,13 +894,55 @@ export default {
|
||||
this.withdrawLoading = true
|
||||
|
||||
try {
|
||||
/**
|
||||
* 提现地址 RSA 加密(与“钱包绑定”页面保持一致:同步优先,异步兜底)
|
||||
* - toAddress: 用户输入的收款地址
|
||||
* - fromAddress: 当前钱包来源地址(后端可能用于校验来源)
|
||||
*/
|
||||
const toAddressPlain = String(this.withdrawForm.toAddress || '').trim()
|
||||
const fromAddressPlain = String((this.WalletData && this.WalletData.fromAddress) || '').trim()
|
||||
|
||||
/** @type {string} */
|
||||
let encryptedToAddress = toAddressPlain
|
||||
if (encryptedToAddress) {
|
||||
const syncEncrypted = rsaEncryptSync(encryptedToAddress)
|
||||
if (syncEncrypted) {
|
||||
encryptedToAddress = syncEncrypted
|
||||
} else {
|
||||
const asyncEncrypted = await rsaEncrypt(encryptedToAddress)
|
||||
if (asyncEncrypted) {
|
||||
encryptedToAddress = asyncEncrypted
|
||||
} else {
|
||||
this.$message.error('钱包地址加密失败,请重试')
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {string} */
|
||||
let encryptedFromAddress = fromAddressPlain
|
||||
if (encryptedFromAddress) {
|
||||
const syncEncrypted = rsaEncryptSync(encryptedFromAddress)
|
||||
if (syncEncrypted) {
|
||||
encryptedFromAddress = syncEncrypted
|
||||
} else {
|
||||
const asyncEncrypted = await rsaEncrypt(encryptedFromAddress)
|
||||
if (asyncEncrypted) {
|
||||
encryptedFromAddress = asyncEncrypted
|
||||
} else {
|
||||
this.$message.error('钱包地址加密失败,请重试')
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 调用后台提现API,添加谷歌验证码参数
|
||||
const res = await withdrawBalance({
|
||||
toChain: (this.WalletData && (this.WalletData.fromChain || this.WalletData.chain)) || this.withdrawForm.toChain,
|
||||
toSymbol: (this.WalletData && (this.WalletData.fromSymbol || this.WalletData.coin)) || this.withdrawForm.toSymbol,
|
||||
amount: parseFloat(this.withdrawForm.amount),
|
||||
toAddress: this.withdrawForm.toAddress,
|
||||
fromAddress: (this.WalletData && this.WalletData.fromAddress) || '',
|
||||
toAddress: encryptedToAddress,
|
||||
fromAddress: encryptedFromAddress,
|
||||
code: this.withdrawForm.googleCode // 添加谷歌验证码
|
||||
})
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
:row-key="'id'" reserve-selection
|
||||
:ref="'innerTable-' + shopScope.row.id"
|
||||
@selection-change="sels => handleShopInnerSelectionChange(shopScope.row, sels)"
|
||||
:row-class-name="getMachineRowClassName"
|
||||
:header-cell-style="{ textAlign: 'left' }" :cell-style="{ textAlign: 'left' }">
|
||||
<el-table-column type="selection" width="46" :selectable="row => isRowSelectableByShop(shopScope.row, row)" />
|
||||
<el-table-column prop="name" label="商品名称" />
|
||||
@@ -711,6 +712,7 @@
|
||||
import { getGoodsList, deleteBatchGoods as apiDeleteBatchGoods ,deleteBatchGoodsForIsDeleteV2,deleteBatchGoodsV2,deleteBatchGoodsForIsDelete,getGoodsListV2} from '../../api/shoppingCart'
|
||||
import { addOrders,cancelOrder,getOrdersByIds,getOrdersByStatus , getMachineSupportPool,getChainAndListForSeller,getCoinPrice,getMachineSupportCoinAndAlgorithm, addOrdersV2} from '../../api/order'
|
||||
import { truncateAmountByCoin, truncateTo6 } from '../../utils/amount'
|
||||
import { rsaEncrypt, rsaEncryptSync } from '../../utils/rsaEncrypt'
|
||||
|
||||
export default {
|
||||
name: 'Cart',
|
||||
@@ -1081,10 +1083,12 @@ export default {
|
||||
: (Array.isArray(sp && sp.productMachineDtoList) ? sp.productMachineDtoList : [])
|
||||
// 记录每台机器原始租期,便于后续判断是否修改
|
||||
try { machines.forEach(m => { if (m && m._origLeaseTime == null) m._origLeaseTime = Number(m.leaseTime || 1) }) } catch (e) { /* noop */ }
|
||||
// 对机器列表进行排序:下架的排到最下面
|
||||
const sortedMachines = this.sortMachinesByShelfStatus(machines)
|
||||
return {
|
||||
...sp,
|
||||
id: sp && sp.id != null ? String(sp.id) : `shop-${idx}`,
|
||||
productMachineDtoList: machines
|
||||
productMachineDtoList: sortedMachines
|
||||
}
|
||||
})
|
||||
this.shops = normalized
|
||||
@@ -1153,10 +1157,39 @@ export default {
|
||||
isRowSelectable(row, index) {
|
||||
return !(Number(row && row.del) === 1 || Number(row && row.state) === 1)
|
||||
},
|
||||
// 判断机器是否“上架”(用于未展开时的自动筛选)
|
||||
// 判断机器是否"上架"(用于未展开时的自动筛选)
|
||||
isOnShelf(row) {
|
||||
return !(Number(row && row.del) === 1 || Number(row && row.state) === 1)
|
||||
},
|
||||
/**
|
||||
* 对机器列表进行排序:下架的排到最下面
|
||||
* @param {Array} machines - 机器列表
|
||||
* @returns {Array} 排序后的机器列表
|
||||
*/
|
||||
sortMachinesByShelfStatus(machines) {
|
||||
if (!Array.isArray(machines)) return []
|
||||
// 上架的机器排前面,下架的排后面
|
||||
return [...machines].sort((a, b) => {
|
||||
const aOnShelf = this.isOnShelf(a)
|
||||
const bOnShelf = this.isOnShelf(b)
|
||||
// 如果都是上架或都是下架,保持原有顺序
|
||||
if (aOnShelf === bOnShelf) return 0
|
||||
// 上架的排前面(返回-1),下架的排后面(返回1)
|
||||
return aOnShelf ? -1 : 1
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 获取机器表格行的类名(用于样式)
|
||||
* @param {Object} param - 包含 row, rowIndex 的对象
|
||||
* @returns {string} 行类名
|
||||
*/
|
||||
getMachineRowClassName({ row }) {
|
||||
// 如果机器已下架,返回下架样式类
|
||||
if (!this.isOnShelf(row)) {
|
||||
return 'off-shelf-row'
|
||||
}
|
||||
return ''
|
||||
},
|
||||
// 获取本地最大可租赁天数,默认 365
|
||||
getRowMaxLeaseDaysLocal(row) {
|
||||
const raw = row && row.maxLeaseDays
|
||||
@@ -1510,11 +1543,13 @@ export default {
|
||||
try {
|
||||
withShopKeys.forEach(sp => this.ensureDefaultPaySelection(sp))
|
||||
} catch (e) { /* noop */ }
|
||||
// 记录每台机器的原始租期,便于判断是否被修改
|
||||
// 记录每台机器的原始租期,便于判断是否被修改,并对机器列表进行排序
|
||||
try {
|
||||
withShopKeys.forEach(sp => {
|
||||
const list = Array.isArray(sp.productMachineDtoList) ? sp.productMachineDtoList : []
|
||||
list.forEach(m => { if (m && m._origLeaseTime == null) m._origLeaseTime = Number(m.leaseTime || 1) })
|
||||
// 对机器列表进行排序:下架的排到最下面
|
||||
sp.productMachineDtoList = this.sortMachinesByShelfStatus(list)
|
||||
})
|
||||
} catch (e) { /* noop */ }
|
||||
this.shops = withShopKeys
|
||||
@@ -1765,6 +1800,7 @@ export default {
|
||||
// 为每台机器创建独立的配置项
|
||||
orderMiningInfoDtoList.push({
|
||||
coinConfigId: coinConfigId,
|
||||
coin: config.coin || '', // 用户选择的币种/算法框里的币种
|
||||
poolName: config.poolName || '',
|
||||
poolUser: poolUser,
|
||||
walletAddress: walletAddress,
|
||||
@@ -1774,6 +1810,29 @@ export default {
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* 结算提交前:对配置中的钱包地址进行 RSA 加密(字段名不变,仍为 walletAddress)
|
||||
* 规则:同步优先(rsaEncryptSync),失败再异步兜底(rsaEncrypt);仍失败则提示并中断结算
|
||||
*/
|
||||
for (const item of (orderMiningInfoDtoList || [])) {
|
||||
const plainWalletAddress = String(item && item.walletAddress ? item.walletAddress : '').trim()
|
||||
// 空地址无需加密(例如 walletMining=false 时可能仅提交 poolUser)
|
||||
if (!plainWalletAddress) continue
|
||||
|
||||
/** @type {string|null} */
|
||||
let encryptedWalletAddress = rsaEncryptSync(plainWalletAddress)
|
||||
if (!encryptedWalletAddress) {
|
||||
encryptedWalletAddress = await rsaEncrypt(plainWalletAddress)
|
||||
}
|
||||
if (!encryptedWalletAddress) {
|
||||
this.$message.error('钱包地址加密失败,请重试')
|
||||
// 中断前恢复勾选 UI(与其它失败分支保持一致)
|
||||
this.reapplySelectionsForPendingShop()
|
||||
return false
|
||||
}
|
||||
item.walletAddress = encryptedWalletAddress
|
||||
}
|
||||
|
||||
this.creatingOrder = true
|
||||
try {
|
||||
const res = await this.fetchAddOrdersV2(orderInfoVoList, orderMiningInfoDtoList, googleCode)
|
||||
@@ -3317,4 +3376,13 @@ export default {
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* 下架商品行样式 */
|
||||
:deep(.off-shelf-row) {
|
||||
background-color: #f5f5f5 !important;
|
||||
}
|
||||
|
||||
:deep(.off-shelf-row:hover > td) {
|
||||
background-color: #e8e8e8 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -215,9 +215,9 @@ export default {
|
||||
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 }
|
||||
// 接口已返回带单位的内容,直接返回字符串值,不拼接单位
|
||||
const s = String(val || '')
|
||||
return { text: s, full: s, truncated: false }
|
||||
}
|
||||
if (col.type === 'days') {
|
||||
const n = Number(val)
|
||||
|
||||
Binary file not shown.
1
power_leasing/test/css/app.262a57c7.css
Normal file
1
power_leasing/test/css/app.262a57c7.css
Normal file
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>power_leasing</title><script defer="defer" src="/js/chunk-vendors.f4da7ffe.js"></script><script defer="defer" src="/js/app.551d07c7.js"></script><link href="/css/chunk-vendors.10dd4e95.css" rel="stylesheet"><link href="/css/app.5ed3e526.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but power_leasing doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
||||
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>power_leasing</title><script defer="defer" src="/js/chunk-vendors.d698a8ca.js"></script><script defer="defer" src="/js/app.58e678d6.js"></script><link href="/css/chunk-vendors.10dd4e95.css" rel="stylesheet"><link href="/css/app.262a57c7.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but power_leasing doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
||||
2
power_leasing/test/js/app.58e678d6.js
Normal file
2
power_leasing/test/js/app.58e678d6.js
Normal file
File diff suppressed because one or more lines are too long
1
power_leasing/test/js/app.58e678d6.js.map
Normal file
1
power_leasing/test/js/app.58e678d6.js.map
Normal file
File diff suppressed because one or more lines are too long
43
power_leasing/test/js/chunk-vendors.d698a8ca.js
Normal file
43
power_leasing/test/js/chunk-vendors.d698a8ca.js
Normal file
File diff suppressed because one or more lines are too long
1
power_leasing/test/js/chunk-vendors.d698a8ca.js.map
Normal file
1
power_leasing/test/js/chunk-vendors.d698a8ca.js.map
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user