代码修改

This commit is contained in:
qiuyuan 2026-01-14 16:25:27 +08:00
parent 10a9268bbb
commit b2ebdca17d
3 changed files with 160 additions and 378 deletions

View File

@ -53,3 +53,9 @@ export const invoiceDetail = (id:any) => request.delete(`/v1/invoice/invoice_det
// 获取订单列表
export const getOrderList = (params:any) => request.get('/v1/order/order_list',{params})
// 算力点兑换界面
export const getExchangeList = (params:any) => request.get('/v1/balance/exchange_list', { params })
// 兑换算力点
export const exchangePoint = (params:any) => request.put('/v1/balance/exchange_point',params)

View File

@ -34,3 +34,5 @@ export const getUpList=(params:any)=>request.get("/v1/balance/top_up_list",{para
//提现记录
export const getWithdrawList=(params:any)=>request.get("/v1/balance/withdraw_list",{params})

View File

@ -8,13 +8,11 @@
<a-breadcrumb-item>算力点兑换</a-breadcrumb-item>
</a-breadcrumb>
<!-- 余额卡片 -->
<a-card class="balance-card" :bordered="false">
<div class="balance-info">
<div class="balance-item">
<div class="balance-label">
<span class="label-text">我的余额</span>
</div>
<div class="balance-value">
@ -24,19 +22,15 @@
</div>
<div class="balance-item">
<div class="balance-label">
<span class="label-text">算力点</span>
</div>
<div class="balance-value">
<span class="points">{{ formatPoints(points) }}</span>
</div>
</div>
</div>
</a-card>
<!-- 兑换卡片 -->
<a-card class="exchange-card" title="兑换算力点">
<div class="exchange-content">
@ -56,12 +50,8 @@
<!-- 自定义输入 -->
<div class="custom-amount">
<a-input-group compact>
<a-input v-model:value="customAmount" placeholder="输入其他金额" size="large" style="width: calc(100% - 120px)"
@change="handleCustomAmountChange" :disabled="loading" />
<!-- <a-button type="primary" size="large" style="width: 120px" :disabled="!isCustomAmountValid || loading"
@click="handleCustomAmountConfirm">
确认
</a-button> -->
<a-input v-model:value="customAmount" placeholder="输入其他金额" size="large" @change="handleCustomAmountChange"
:disabled="loading" />
</a-input-group>
<p class="input-name">用户自定义输入算力点数量</p>
<div v-if="customAmountError" class="error-text">
@ -69,60 +59,7 @@
{{ customAmountError }}
</div>
</div>
</div>
<!-- 兑换规则说明 -->
<div class="exchange-section">
<h3 class="section-title">兑换规则</h3>
<div class="exchange-rules">
<div class="rule-item">
<CheckCircleOutlined class="rule-icon" />
<span>算力点兑换比例为人民币 <strong>1 = 1算力点</strong></span>
</div>
<div class="rule-item">
<ExclamationCircleOutlined class="rule-icon" />
<span><strong>算力点不可退</strong>兑换前请确认需求</span>
</div>
<div class="rule-item">
<InfoCircleOutlined class="rule-icon" />
<span>兑换后算力点立即生效可用于平台所有计算服务</span>
</div>
<div class="rule-item">
<SafetyOutlined class="rule-icon" />
<span>兑换过程安全加密保障您的资金安全</span>
</div>
</div>
</div>
<!-- 兑换信息展示 -->
<div v-if="selectedExchangeAmount > 0" class="exchange-preview">
<div class="preview-card">
<div class="preview-header">
<div class="preview-title">兑换明细</div>
<a-button type="text" @click="clearSelection">
<CloseOutlined />
</a-button>
</div>
<div class="preview-content">
<div class="preview-item">
<span class="preview-label">兑换金额</span>
<span class="preview-value">¥{{ selectedExchangeAmount }}</span>
</div>
<div class="preview-item">
<span class="preview-label">获得算力点</span>
<span class="preview-value points-value">{{ selectedExchangeAmount }} 算力点</span>
</div>
<div class="preview-item">
<span class="preview-label">兑换后余额</span>
<span class="preview-value">¥{{ formatCurrency(balance - selectedExchangeAmount) }}</span>
</div>
<div class="preview-item">
<span class="preview-label">兑换后算力点</span>
<span class="preview-value points-value">{{ formatPoints(points + selectedExchangeAmount) }}
算力点</span>
</div>
</div>
</div>
<div class="title">兑换说明算力点换算比例为人民币1 = 1 算力点算力点兑换后不可退</div>
</div>
<!-- 用户协议确认 -->
@ -138,16 +75,9 @@
<!-- 操作按钮 -->
<div class="action-buttons">
<a-button type="primary" size="large" :loading="loading" :disabled="!canExchange" @click="handleExchange"
class="exchange-button">
<template #icon>
<ThunderboltOutlined />
</template>
<a-button type="primary" size="large" @click="handleExchange" :loading="loading" :disabled="!canExchange" class="exchange-button">
确认兑换
</a-button>
<a-button size="large" @click="handleCancel" :disabled="loading" class="cancel-button">
取消
</a-button>
</div>
</div>
</a-card>
@ -155,8 +85,11 @@
<!-- 主要内容区域 -->
<div class="main-content">
<a-card class="history-card" title="算力点兑换历史">
<a-table :columns="historyColumns" :data-source="exchangeHistory" row-key="key" :pagination="false">
<!-- 自定义状态列的渲染 -->
<a-table :columns="historyColumns"
:data-source="listData"
row-key="key"
:pagination="paginationState"
@change="onTableChange">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<span :style="{ color: getStatusColor(record.status) }">{{ getStatusText(record.status) }}</span>
@ -170,12 +103,6 @@
</template>
</a-table>
</a-card>
</div>
<!-- 用户协议模态框 -->
@ -186,7 +113,7 @@
<h4>兑换规则</h4>
<p>1. 算力点兑换比例为1元人民币兑换1算力点</p>
<p>2. 小兑换金额为100元大单次兑换金额为10000元</p>
<p>2. 大单次兑换金额为10000元</p>
<p>3. 兑换操作一经确认不可撤销或退款</p>
<h4>使用规则</h4>
@ -211,22 +138,20 @@
</div>
</template>
<script setup>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import {
DollarCircleOutlined,
ThunderboltOutlined,
InfoCircleOutlined,
CheckCircleOutlined,
ExclamationCircleOutlined,
SafetyOutlined,
CloseOutlined
ExclamationCircleOutlined
} from '@ant-design/icons-vue'
import { message, Modal, Divider } from 'ant-design-vue'
import { message, Modal } from 'ant-design-vue'
import { getExchangeList, exchangePoint } from '@/apis/admin'
import { usePagination } from '@/hooks'
const { listData, paginationState, resetPagination, searchFormData } = usePagination()
//
const balance = ref(3568.5)
const points = ref(1250)
const balance = ref(0)
const points = ref(0)
//
const amountOptions = ref([
@ -236,31 +161,25 @@ const amountOptions = ref([
{ value: 5000, label: '5000' }
])
// columns customRender
//
const historyColumns = [
{
title: '兑换金额',
dataIndex: 'amount',
key: 'amount',
align: 'right'
dataIndex: 'exchange_amount',
key: 'exchange_amount',
align: 'center'
},
{
title: '获得算力点',
dataIndex: 'points',
key: 'points',
align: 'right'
dataIndex: 'exchange_point',
key: 'exchange_point',
align: 'center'
},
{
title: '兑换时间',
dataIndex: 'time',
key: 'time'
dataIndex: 'created_at',
key: 'created_at'
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
align: 'center'
}
]
//
@ -279,15 +198,6 @@ const loading = ref(false)
const agreementModalVisible = ref(false)
const modalAgreementChecked = ref(false)
//
const exchangeHistory = ref([
{ key: 1, amount: 1000, points: 1000, time: '2024-03-15 14:30:22', status: 'success' },
{ key: 2, amount: 500, points: 500, time: '2024-03-10 09:15:45', status: 'success' },
{ key: 3, amount: 2000, points: 2000, time: '2024-03-05 16:20:33', status: 'success' },
{ key: 4, amount: 100, points: 100, time: '2024-02-28 11:45:12', status: 'success' },
{ key: 5, amount: 5000, points: 5000, time: '2024-02-20 13:05:27', status: 'success' }
])
//
const selectedExchangeAmount = computed(() => {
if (selectedAmount.value > 0) {
@ -311,10 +221,7 @@ const isCustomAmountValid = computed(() => {
if (isNaN(amount) || amount <= 0) {
return false
}
if (amount < 100) {
customAmountError.value = '最小兑换金额为100元'
return false
}
if (amount > 10000) {
customAmountError.value = '单次最大兑换金额为10000元'
return false
@ -328,17 +235,20 @@ const isCustomAmountValid = computed(() => {
})
//
const formatCurrency = (value) => {
return value.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',')
const formatCurrency = (value: any): string => {
const num = typeof value === 'number' ? value : parseFloat(value);
if (isNaN(num)) return '0.00';
return num.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
//
const formatPoints = (value) => {
return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
const formatPoints = (value: any): string => {
const num = typeof value === 'number' ? value : parseFloat(value);
if (isNaN(num)) return '0';
return Math.floor(num).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
//
const selectAmount = (amount) => {
const selectAmount = (amount: number) => {
selectedAmount.value = amount
customAmount.value = ''
customAmountError.value = ''
@ -350,9 +260,7 @@ const handleCustomAmountChange = () => {
if (customAmount.value) {
const amount = parseFloat(customAmount.value)
if (!isNaN(amount)) {
if (amount < 100) {
customAmountError.value = '最小兑换金额为100元'
} else if (amount > 10000) {
if (amount > 10000) {
customAmountError.value = '单次最大兑换金额为10000元'
} else if (amount > balance.value) {
customAmountError.value = '兑换金额不能超过余额'
@ -365,21 +273,6 @@ const handleCustomAmountChange = () => {
}
}
//
const handleCustomAmountConfirm = () => {
if (isCustomAmountValid.value) {
selectedAmount.value = 0
message.success(`已选择兑换¥${customAmount.value}`)
}
}
//
const clearSelection = () => {
selectedAmount.value = 0
customAmount.value = ''
customAmountError.value = ''
}
//
const showAgreementModal = () => {
agreementModalVisible.value = true
@ -394,7 +287,7 @@ const handleAgreementConfirm = () => {
}
//
const handleExchange = () => {
const handleExchange = async () => {
showAgreementError.value = false
if (!agreementChecked.value) {
@ -413,48 +306,63 @@ const handleExchange = () => {
content: `您确定要兑换 ¥${selectedExchangeAmount.value} 获得 ${selectedExchangeAmount.value} 算力点吗?此操作不可撤销。`,
okText: '确认兑换',
cancelText: '取消',
onOk: performExchange
async onOk() {
await performExchange()
}
})
}
//
const performExchange = () => {
const performExchange = async () => {
loading.value = true
// API
setTimeout(() => {
balance.value -= selectedExchangeAmount.value
points.value += selectedExchangeAmount.value
const now = new Date()
const timeStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`
exchangeHistory.value.unshift({
key: exchangeHistory.value.length + 1,
amount: selectedExchangeAmount.value,
points: selectedExchangeAmount.value,
time: timeStr,
status: 'success'
try {
//
const response = await exchangePoint({
exchange_value: selectedExchangeAmount.value
})
console.log('兑换结果:', response)
clearSelection()
agreementChecked.value = false
loading.value = false
if (response.code === 1) {
//
const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}');
userInfo.balance -= selectedExchangeAmount.value;
userInfo.computingPowerPoint += selectedExchangeAmount.value;
localStorage.setItem('userInfo', JSON.stringify(userInfo));
message.success(`兑换成功!获得 ${selectedExchangeAmount.value} 算力点`)
}, 1500)
//
balance.value = userInfo.balance;
points.value = userInfo.computingPowerPoint;
//
await getPageList();
//
clearSelection();
agreementChecked.value = false;
message.success(`兑换成功`);
} else {
message.error(response.msg || '兑换失败');
}
} catch (error: any) {
console.error('兑换失败:', error);
message.error(error.msg || '兑换失败,请稍后重试');
} finally {
loading.value = false;
}
}
//
const handleCancel = () => {
clearSelection()
agreementChecked.value = false
showAgreementError.value = false
//
const clearSelection = () => {
selectedAmount.value = 0
customAmount.value = ''
customAmountError.value = ''
}
//
const getStatusColor = (status) => {
const colors = {
const getStatusColor = (status: string) => {
const colors: Record<string, string> = {
success: 'green',
pending: 'orange',
failed: 'red'
@ -463,8 +371,8 @@ const getStatusColor = (status) => {
}
//
const getStatusText = (status) => {
const texts = {
const getStatusText = (status: string) => {
const texts: Record<string, string> = {
success: '成功',
pending: '处理中',
failed: '失败'
@ -472,13 +380,50 @@ const getStatusText = (status) => {
return texts[status] || status
}
//
const getPageList = async () => {
try {
const { pageSize, current } = paginationState
const res: any = await getExchangeList({
page_size: pageSize,
page_num: current,
})
if (res.data && Array.isArray(res.data)) {
listData.value = res.data
paginationState.total = res.total || res.data.length
} else {
listData.value = []
paginationState.total = 0
}
} catch (error: any) {
console.error('兑换历史请求失败:', error)
listData.value = []
paginationState.total = 0
}
}
//
function onTableChange({ current, pageSize }: { current: number, pageSize: number }) {
paginationState.current = current
paginationState.pageSize = pageSize
getPageList()
}
//
onMounted(() => {
console.log('页面加载完成')
const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}');
if (userInfo.balance && userInfo.computingPowerPoint) {
balance.value = userInfo.balance;
points.value = userInfo.computingPowerPoint;
}
getPageList();
})
</script>
<style scoped>
/* 你的样式保持不变 */
/* 样式保持不变,同上 */
.points-exchange-page {
padding: 20px;
background-color: #f0f2f5;
@ -489,25 +434,6 @@ onMounted(() => {
margin-bottom: 16px;
}
.page-header {
margin-bottom: 24px;
}
.page-title {
font-size: 24px;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
margin-bottom: 8px;
}
.page-description {
color: rgba(0, 0, 0, 0.45);
font-size: 14px;
margin: 0;
}
.main-content {}
.balance-card {
margin-bottom: 20px;
border-radius: 12px;
@ -530,19 +456,6 @@ onMounted(() => {
margin-bottom: 8px;
}
.icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
background: #f0f5ff;
border-radius: 8px;
margin-right: 12px;
color: #1890ff;
font-size: 18px;
}
.label-text {
font-size: 20px;
font-weight: 500;
@ -565,24 +478,6 @@ onMounted(() => {
color: rgba(56, 56, 56, 1);
}
.balance-value .points-unit {
font-size: 16px;
color: rgba(0, 0, 0, 0.45);
margin-left: 8px;
}
.balance-tips {
padding: 12px 16px;
background: #f6ffed;
border-radius: 8px;
color: #52c41a;
font-size: 14px;
}
.balance-tips :deep(.anticon) {
margin-right: 8px;
}
.exchange-card {
margin-bottom: 20px;
border-radius: 12px;
@ -604,12 +499,6 @@ onMounted(() => {
margin-bottom: 8px;
}
.section-subtitle {
color: rgba(0, 0, 0, 0.45);
font-size: 14px;
margin-bottom: 20px;
}
.amount-options {
display: grid;
grid-template-columns: repeat(8, 1fr);
@ -625,7 +514,6 @@ onMounted(() => {
.amount-option {
height: auto !important;
/* padding: 6px !important; */
border-radius: 8px !important;
border: 2px solid #f0f0f0 !important;
background: #fff !important;
@ -654,30 +542,32 @@ onMounted(() => {
margin-bottom: 4px;
}
.amount-points {
font-size: 12px;
color: rgba(0, 0, 0, 0.45);
.custom-amount {
width: 345px;
margin-bottom: 24px;
margin-left: 180px;
}
.custom-amount {
width: 460px;
margin-bottom: 24px;
margin-left: 170px;
.custom-amount input {
height: 45px;
}
input {
height: 45px;
}
.input-name {
width: 350px;
text-align: center;
font-size: 14px;
font-weight: 400;
color: rgba(166, 166, 166, 1);
height: 35px;
line-height: 35px;
}
.input-name {
width: 350px;
text-align: center;
font-size: 14px;
font-weight: 400;
color: rgba(166, 166, 166, 1);
height: 35px;
line-height: 35px;
}
.title {
font-size: 14px;
font-weight: 400;
letter-spacing: 0px;
line-height: 32.93px;
color: rgba(166, 166, 166, 1);
}
.error-text {
@ -689,99 +579,6 @@ onMounted(() => {
gap: 4px;
}
.exchange-rules {
background: #fafafa;
padding: 20px;
border-radius: 8px;
}
.rule-item {
display: flex;
align-items: flex-start;
margin-bottom: 12px;
}
.rule-item:last-child {
margin-bottom: 0;
}
.rule-icon {
margin-right: 12px;
margin-top: 2px;
font-size: 16px;
}
.rule-item:nth-child(1) .rule-icon {
color: #52c41a;
}
.rule-item:nth-child(2) .rule-icon {
color: #faad14;
}
.rule-item:nth-child(3) .rule-icon {
color: #1890ff;
}
.rule-item:nth-child(4) .rule-icon {
color: #722ed1;
}
.exchange-preview {
margin: 24px 0;
}
.preview-card {
background: #f6ffed;
border: 1px solid #b7eb8f;
border-radius: 8px;
padding: 20px;
}
.preview-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.preview-title {
font-size: 16px;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
}
.preview-content {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
@media (max-width: 576px) {
.preview-content {
grid-template-columns: 1fr;
}
}
.preview-item {
display: flex;
justify-content: space-between;
align-items: center;
}
.preview-label {
color: rgba(0, 0, 0, 0.45);
}
.preview-value {
font-weight: 500;
color: rgba(0, 0, 0, 0.85);
}
.preview-value.points-value {
color: #722ed1;
}
.agreement-section {
margin: 24px 0;
padding: 16px;
@ -800,18 +597,12 @@ onMounted(() => {
.action-buttons {
display: flex;
justify-content: flex-end;
gap: 16px;
margin-top: 32px;
}
.exchange-button {
flex: 1;
height: 48px;
font-size: 16px;
font-weight: 500;
}
.cancel-button {
width: 120px;
height: 48px;
}
@ -821,23 +612,6 @@ onMounted(() => {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.exchange-amount {
color: #1890ff;
font-weight: 500;
}
.exchange-points {
color: #722ed1;
font-weight: 500;
}
.view-all-link {
text-align: center;
padding-top: 16px;
border-top: 1px solid #f0f0f0;
margin-top: 16px;
}
.agreement-content {
max-height: 60vh;
overflow-y: auto;