This commit is contained in:
Leo_Ding 2026-01-12 18:29:32 +08:00
parent bc094f968c
commit e50e03dcc6
9 changed files with 364 additions and 382 deletions

View File

@ -2,5 +2,5 @@
NODE_ENV=development
# api
VITE_API_BASIC="http://10.10.1.35:8888"
VITE_API_BASIC="http://10.10.1.32:8888"

View File

@ -21,4 +21,10 @@ export const getMyCouponTotal = () => request.get('/v1/voucher/voucher_value')
export const getRecommendList = () => request.get('/v1/voucher/voucher_list')
//获取消息列表
export const getMessageList = (params:any) => request.get('/v1/message/message_list', { params })
export const getMessageList = (params:any) => request.get('/v1/message/message_list', { params })
//验证码合法性
export const CodeAuth=(params:any)=>request.put("/v1/auth/update_password_auth",params)
//提现
export const tixian=(params:any)=>request.put("/v1/balance/withdraw",params)

View File

@ -57,12 +57,7 @@ interface MenuItem {
}
const menuItems: MenuItem[] = [
{ path: '/controlPanel/overview', name: '总览', icon: HomeOutlined },
{ path: '/controlPanel/container', name: '容器实例', icon: ConsoleSqlOutlined },
// { path: '/controlPanel/fileStore', name: '', icon: FolderOpenOutlined },
{ path: '/controlPanel/image', name: '镜像', icon: GlobalOutlined },
{ path: '/controlPanel/publicData', name: '公开数据', icon: LaptopOutlined },
{
{
path: '/contract',
name: '费用中心',
icon: MoneyCollectOutlined,
@ -80,7 +75,12 @@ const menuItems: MenuItem[] = [
{ path: '/accountHistory', name: '访问记录' },
// { path: '/controlPanel/account/security', name: '' }
]
}
},
{ path: '/controlPanel/container', name: '容器实例', icon: ConsoleSqlOutlined },
// { path: '/controlPanel/fileStore', name: '', icon: FolderOpenOutlined },
{ path: '/controlPanel/image', name: '镜像', icon: GlobalOutlined },
{ path: '/controlPanel/publicData', name: '公开数据', icon: LaptopOutlined },
]
const selectedKeys = computed(() => [route.path])

View File

@ -1,17 +1,19 @@
// src/utils/request.ts
import axios, { AxiosInstance, InternalAxiosRequestConfig } from 'axios';
import axios, { AxiosInstance, InternalAxiosRequestConfig } from "axios";
import { message } from "ant-design-vue";
import { useRouter, useRoute } from "vue-router";
// 从环境变量读取基础 URL
const BASE_URL = import.meta.env.VITE_API_BASIC
console.log('API Basic URL:',import.meta.env.VITE_API_BASIC);
console.log('All env:', import.meta.env);
const BASE_URL = import.meta.env.VITE_API_BASIC;
console.log("API Basic URL:", import.meta.env.VITE_API_BASIC);
console.log("All env:", import.meta.env);
const router = useRouter();
// 创建 axios 实例
const request: AxiosInstance = axios.create({
baseURL: BASE_URL,
timeout: 10000, // 10 秒超时
withCredentials: false, // 跨域请求时发送 cookies
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
},
});
@ -19,7 +21,7 @@ const request: AxiosInstance = axios.create({
request.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
// 例如:从 localStorage 获取 token
const token = localStorage.getItem('token');
const token = localStorage.getItem("token");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
@ -33,22 +35,31 @@ request.interceptors.request.use(
// 响应拦截器统一处理错误、code 等)
request.interceptors.response.use(
(response) => {
console.log("response", response);
// 假设后端返回格式为 { code: 200, data: ..., message: '' }
const { code, data, message } = response.data;
console.log('Response Data:', response.data);
console.log("Response Data:", response.data);
if (code === 1) {
return data;
return data??code;
} else {
// 可抛出业务错误
return Promise.reject(new Error(message || '请求失败'));
return Promise.reject(new Error(message || "请求失败"));
}
},
(error) => {
// 网络错误 or 超时等
return Promise.reject(error.response.data);
console.log(error);
const res = error.response;
if (res.status === 401) {
message.error("登录已过期,请重新登录");
localStorage.clear();
setTimeout(() => {
window.location.href = "/login";
}, 3000);
} else {
return Promise.reject(error.response.data);
}
}
);
export default request;
export default request;

View File

@ -16,13 +16,18 @@
</div>
</a-upload>
</div>
<div class="info"><span>账户</span><span>17044054908</span></div>
<div class="info"><span>账户ID</span><span>123456789123456789</span></div>
<div class="info"><span>账户</span><span>{{ userInfo.phone }}</span></div>
<div class="info"><span>账户ID</span><span>{{ userInfo.id }}</span></div>
<div class="info" style="display: flex;justify-content: space-between;">
<div>
<div v-if="userInfo.certificationStatus !== 'CERTIFICATION_PASSED'">
<span style="margin-right: 25px;">认证类型</span><span style="color: red;">未实名认证</span>
</div>
<div style="color:#1677ff;">前往认证</div>
<div style="color:#1677ff;" v-if="userInfo.certificationStatus !== 'CERTIFICATION_PASSED'">前往认证
</div>
<div v-if="userInfo.certificationStatus == 'CERTIFICATION_PASSED'">
<span style="margin-right: 25px;">认证类型</span><span>{{ certificationInfo.certificationType
}}</span>
</div>
</div>
</a-card>
</a-col>
@ -30,38 +35,30 @@
<a-card title="安全设置">
<div class="info" style="display: flex;justify-content: space-between;">
<div>
<span style="margin-right: 25px;">手机号</span><span>{{ currentPhone }}</span>
<span style="margin-right: 38px;width: 80px;">手机号</span><span>{{ userInfo.phone }}</span>
</div>
<div style="color:#1677ff;" @click="handleModifyPhone">修改</div>
</div>
<div class="info" style="display: flex;justify-content: space-between;">
<div>
<span style="margin-right: 25px;">绑定微信</span><span style="color: #666;">未绑定微信</span>
</div>
<div style="color:#1677ff;">绑定</div>
<div style="color:#1677ff;cursor: pointer;">绑定</div>
</div>
<div class="info" style="display: flex;justify-content: space-between;">
<div>
<span style="margin-right: 25px;">登录密码</span><span>********</span>
<span style="margin-right: 25px;cursor: pointer;">登录密码</span><span>********</span>
</div>
<div style="color:#1677ff;" @click="handleModifyPassword">修改</div>
<div style="color:#1677ff;cursor: pointer;" @click="handleModifyPassword">修改</div>
</div>
</a-card>
</a-col>
</a-row>
<!-- 第一步身份验证对话框共用 -->
<a-modal
v-model:visible="verifyVisible"
title="身份验证"
@ok="handleVerify"
@cancel="handleCancelVerify"
:maskClosable="false"
:width="480"
:ok-button-props="{ disabled: !isVerificationValid }"
:confirmLoading="verifying"
okText="下一步"
>
<a-modal v-model:visible="verifyVisible" title="身份验证" @ok="handleVerify" @cancel="handleCancelVerify"
:maskClosable="false" :width="480" :ok-button-props="{ disabled: !isVerificationValid }"
:confirmLoading="verifying" okText="下一步">
<div class="verify-content">
<!-- 安全提示 -->
<div class="security-tip">
@ -77,7 +74,7 @@
<div class="phone-label">验证手机号</div>
<div class="phone-number">
<!-- <span class="phone-icon">📱</span> -->
<span class="phone-text">{{ maskedPhone }}</span>
<span class="phone-text">{{ userInfo.phone }}</span>
</div>
</div>
@ -85,19 +82,10 @@
<div class="verification-code">
<div class="code-label">验证码</div>
<div class="code-input-group">
<a-input
v-model:value="verificationCode"
placeholder="请输入6位验证码"
:maxlength="6"
@input="handleCodeInput"
class="code-input"
/>
<a-button
type="link"
@click="sendVerificationCode"
:disabled="countdown > 0 || sendingCode"
class="send-code-btn"
>
<a-input v-model:value="verificationCode" placeholder="请输入6位验证码" :maxlength="6"
@input="handleCodeInput" class="code-input" />
<a-button type="link" @click="sendVerificationCode" :disabled="countdown > 0 || sendingCode"
class="send-code-btn">
{{ countdownText }}
</a-button>
</div>
@ -112,16 +100,9 @@
</a-modal>
<!-- 第二步修改手机号对话框 -->
<a-modal
v-model:visible="changePhoneVisible"
title="修改手机号"
@ok="handleChangePhone"
@cancel="handleCancelChangePhone"
:maskClosable="false"
:width="480"
:ok-button-props="{ disabled: !isChangeValid }"
:confirmLoading="changing"
>
<a-modal v-model:visible="changePhoneVisible" title="修改手机号" @ok="handleChangePhone"
@cancel="handleCancelChangePhone" :maskClosable="false" :width="480"
:ok-button-props="{ disabled: !isChangeValid }" :confirmLoading="changing">
<div class="change-phone-content">
<!-- 安全提示 -->
<div class="security-tip">
@ -134,13 +115,8 @@
<!-- 新手机号输入 -->
<div class="new-phone-section">
<div class="input-label">新手机号</div>
<a-input
v-model:value="newPhoneNumber"
placeholder="请输入新手机号"
:maxlength="11"
@input="handlePhoneInput"
class="phone-input"
/>
<a-input v-model:value="newPhoneNumber" placeholder="请输入新手机号" :maxlength="11"
@input="handlePhoneInput" class="phone-input" />
<div v-if="phoneError" class="input-error">{{ phoneError }}</div>
</div>
@ -148,19 +124,10 @@
<div class="verification-code">
<div class="code-label">验证码</div>
<div class="code-input-group">
<a-input
v-model:value="newVerificationCode"
placeholder="请输入6位验证码"
:maxlength="6"
@input="handleNewCodeInput"
class="code-input"
/>
<a-button
type="link"
@click="sendNewVerificationCode"
:disabled="newCountdown > 0 || sendingNewCode"
class="send-code-btn"
>
<a-input v-model:value="newVerificationCode" placeholder="请输入6位验证码" :maxlength="6"
@input="handleNewCodeInput" class="code-input" />
<a-button type="link" @click="sendNewVerificationCode"
:disabled="newCountdown > 0 || sendingNewCode" class="send-code-btn">
{{ newCountdownText }}
</a-button>
</div>
@ -170,16 +137,9 @@
</a-modal>
<!-- 修改密码对话框 -->
<a-modal
v-model:visible="changePasswordVisible"
title="修改密码"
@ok="handleChangePassword"
@cancel="handleCancelChangePassword"
:maskClosable="false"
:width="480"
:ok-button-props="{ disabled: !isPasswordValid }"
:confirmLoading="changingPassword"
>
<a-modal v-model:visible="changePasswordVisible" title="修改密码" @ok="handleChangePassword"
@cancel="handleCancelChangePassword" :maskClosable="false" :width="480"
:ok-button-props="{ disabled: !isPasswordValid }" :confirmLoading="changingPassword">
<div class="change-password-content">
<!-- 安全提示 -->
<div class="security-tip">
@ -192,26 +152,16 @@
<!-- 原密码输入 -->
<div class="password-section">
<div class="input-label">原密码</div>
<a-input-password
v-model:value="oldPassword"
placeholder="请输入原密码"
@input="handleOldPasswordInput"
class="password-input"
:visibilityToggle="true"
/>
<a-input-password v-model:value="oldPassword" placeholder="请输入原密码" @input="handleOldPasswordInput"
class="password-input" :visibilityToggle="true" />
<div v-if="oldPasswordError" class="input-error">{{ oldPasswordError }}</div>
</div>
<!-- 新密码输入 -->
<div class="password-section">
<div class="input-label">新密码</div>
<a-input-password
v-model:value="newPassword"
placeholder="请输入新密码6-20位字符"
@input="handleNewPasswordInput"
class="password-input"
:visibilityToggle="true"
/>
<a-input-password v-model:value="newPassword" placeholder="请输入新密码6-20位字符"
@input="handleNewPasswordInput" class="password-input" :visibilityToggle="true" />
<div v-if="newPasswordError" class="input-error">{{ newPasswordError }}</div>
<div class="password-tip">
<div class="tip-item" :class="{ 'valid': passwordStrength.lengthValid }">
@ -232,13 +182,8 @@
<!-- 确认密码输入 -->
<div class="password-section">
<div class="input-label">确认密码</div>
<a-input-password
v-model:value="confirmPassword"
placeholder="请再次输入新密码"
@input="handleConfirmPasswordInput"
class="password-input"
:visibilityToggle="true"
/>
<a-input-password v-model:value="confirmPassword" placeholder="请再次输入新密码"
@input="handleConfirmPasswordInput" class="password-input" :visibilityToggle="true" />
<div v-if="confirmPasswordError" class="input-error">{{ confirmPasswordError }}</div>
</div>
</div>
@ -247,19 +192,20 @@
</template>
<script setup lang="ts">
import { ref, computed, onUnmounted, watch,onBeforeMount } from 'vue';
import { ref, computed, onUnmounted, watch, onBeforeMount } from 'vue';
import { LoadingOutlined, PlusOutlined } from '@ant-design/icons-vue';
import { message, Modal as AModal, Input as AInput, InputPassword as AInputPassword, Button as AButton } from 'ant-design-vue';
import{certificationInfoApi} from '@/apis/certification';
import { certificationInfoApi } from '@/apis/certification';
import { CodeAuth } from '@/apis/home'
import { updatePassword } from '@/apis/modules/login'
import router from '@/router';
const loading = ref(false);
const imageUrl = ref<string | null>("https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png");
const fileList = ref<any[]>([{ url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' }]);
const userInfo=ref<any>({})
const certificationInfo=ref<any>({})
const userInfo = ref<any>({})
const certificationInfo = ref<any>({})
//
const operationType = ref<'phone' | 'password'>('phone');
//
const verifyVisible = ref(false);
const verificationCode = ref('');
@ -288,7 +234,7 @@ const oldPasswordError = ref('');
const newPasswordError = ref('');
const confirmPasswordError = ref('');
const changingPassword = ref(false);
const successCode = ref('')
//
const currentPhone = ref('17044054908');
@ -320,10 +266,10 @@ const isVerificationValid = computed(() => {
});
const isChangeValid = computed(() => {
return newPhoneNumber.value.length === 11 &&
/^1[3-9]\d{9}$/.test(newPhoneNumber.value) &&
newVerificationCode.value.length === 6 &&
/^\d+$/.test(newVerificationCode.value);
return newPhoneNumber.value.length === 11 &&
/^1[3-9]\d{9}$/.test(newPhoneNumber.value) &&
newVerificationCode.value.length === 6 &&
/^\d+$/.test(newVerificationCode.value);
});
//
@ -331,7 +277,7 @@ const passwordStrength = computed(() => {
const lengthValid = newPassword.value.length >= 6 && newPassword.value.length <= 20;
const hasLetter = /[a-zA-Z]/.test(newPassword.value);
const hasNumber = /\d/.test(newPassword.value);
return {
lengthValid,
hasLetter,
@ -341,11 +287,11 @@ const passwordStrength = computed(() => {
const isPasswordValid = computed(() => {
const isOldPasswordValid = oldPassword.value.length >= 6;
const isNewPasswordValid = passwordStrength.value.lengthValid &&
passwordStrength.value.hasLetter &&
passwordStrength.value.hasNumber;
const isNewPasswordValid = passwordStrength.value.lengthValid &&
passwordStrength.value.hasLetter &&
passwordStrength.value.hasNumber;
const isConfirmValid = confirmPassword.value === newPassword.value && confirmPassword.value.length > 0;
return isOldPasswordValid && isNewPasswordValid && isConfirmValid;
});
@ -358,7 +304,7 @@ watch(newPassword, (newVal) => {
} else {
newPasswordError.value = '';
}
//
if (confirmPassword.value && confirmPassword.value !== newVal) {
confirmPasswordError.value = '两次输入的密码不一致';
@ -460,7 +406,7 @@ const handleCodeInput = (e: Event) => {
//
const numbersOnly = value.replace(/\D/g, '');
verificationCode.value = numbersOnly.slice(0, 6);
//
if (codeError.value) {
codeError.value = '';
@ -470,19 +416,19 @@ const handleCodeInput = (e: Event) => {
//
const sendVerificationCode = async () => {
if (countdown.value > 0 || sendingCode.value) return;
sendingCode.value = true;
sendStatus.value = '发送中...';
codeError.value = '';
try {
// API
await new Promise(resolve => setTimeout(resolve, 1000));
//
sendStatus.value = '发送成功';
countdown.value = 60;
//
timer = setInterval(() => {
countdown.value--;
@ -493,7 +439,7 @@ const sendVerificationCode = async () => {
}
}
}, 1000);
message.success('验证码已发送');
} catch (error) {
codeError.value = '发送失败,请稍后重试';
@ -509,26 +455,24 @@ const handleVerify = async () => {
codeError.value = '请输入有效的6位验证码';
return;
}
verifying.value = true;
try {
// API
await new Promise(resolve => setTimeout(resolve, 1500));
//
message.success('身份验证成功');
//
verifyVisible.value = false;
//
if (operationType.value === 'phone') {
showChangePhoneDialog();
} else if (operationType.value === 'password') {
showChangePasswordDialog();
const res: any = await CodeAuth({ code: verificationCode.value, phone: userInfo.value.phone })
console.log(res)
if (res) {
successCode.value = res.success_code
verifyVisible.value = false;
//
if (operationType.value === 'phone') {
showChangePhoneDialog();
} else if (operationType.value === 'password') {
showChangePasswordDialog();
}
} else {
message.error("验证码无效")
}
} catch (error) {
codeError.value = '验证码错误,请重新输入';
verificationCode.value = '';
@ -565,7 +509,7 @@ const handlePhoneInput = (e: Event) => {
//
const numbersOnly = value.replace(/\D/g, '');
newPhoneNumber.value = numbersOnly.slice(0, 11);
//
if (phoneError.value) {
phoneError.value = '';
@ -582,7 +526,7 @@ const handleNewCodeInput = (e: Event) => {
//
const numbersOnly = value.replace(/\D/g, '');
newVerificationCode.value = numbersOnly.slice(0, 6);
//
if (newCodeError.value) {
newCodeError.value = '';
@ -596,29 +540,29 @@ const sendNewVerificationCode = async () => {
phoneError.value = '请输入11位手机号';
return;
}
if (!/^1[3-9]\d{9}$/.test(newPhoneNumber.value)) {
phoneError.value = '请输入正确的手机号格式';
return;
}
if (newPhoneNumber.value === currentPhone.value) {
phoneError.value = '新手机号不能与当前手机号相同';
return;
}
if (newCountdown.value > 0 || sendingNewCode.value) return;
sendingNewCode.value = true;
newCodeError.value = '';
try {
// API
await new Promise(resolve => setTimeout(resolve, 1000));
//
newCountdown.value = 60;
//
newTimer = setInterval(() => {
newCountdown.value--;
@ -629,7 +573,7 @@ const sendNewVerificationCode = async () => {
}
}
}, 1000);
message.success('验证码已发送到新手机号');
} catch (error) {
newCodeError.value = '发送失败,请稍后重试';
@ -660,24 +604,24 @@ const handleChangePhone = async () => {
}
return;
}
changing.value = true;
try {
// API
await new Promise(resolve => setTimeout(resolve, 1500));
//
currentPhone.value = newPhoneNumber.value;
//
changePhoneVisible.value = false;
//
resetAllStates();
message.success('手机号修改成功');
} catch (error) {
newCodeError.value = '修改失败,请稍后重试';
} finally {
@ -687,6 +631,7 @@ const handleChangePhone = async () => {
//
const showChangePasswordDialog = () => {
console.log(111)
//
oldPassword.value = '';
newPassword.value = '';
@ -707,7 +652,7 @@ const handleOldPasswordInput = (e: Event) => {
const target = e.target as HTMLInputElement;
const value = target.value;
oldPassword.value = value;
//
if (oldPasswordError.value) {
oldPasswordError.value = '';
@ -719,7 +664,7 @@ const handleNewPasswordInput = (e: Event) => {
const target = e.target as HTMLInputElement;
const value = target.value;
newPassword.value = value;
//
if (newPasswordError.value) {
newPasswordError.value = '';
@ -731,7 +676,7 @@ const handleConfirmPasswordInput = (e: Event) => {
const target = e.target as HTMLInputElement;
const value = target.value;
confirmPassword.value = value;
//
if (confirmPasswordError.value) {
confirmPasswordError.value = '';
@ -764,21 +709,22 @@ const handleChangePassword = async () => {
}
return;
}
changingPassword.value = true;
try {
// API
await new Promise(resolve => setTimeout(resolve, 1500));
//
changePasswordVisible.value = false;
//
resetPasswordStates();
message.success('密码修改成功');
const res = await updatePassword({ confirm_password: confirmPassword.value, new_password: newPassword.value, old_password: oldPassword.value, success_code: successCode.value })
if (res) {
//
changePasswordVisible.value = false;
//
resetPasswordStates();
message.success('密码修改成功');
localStorage.clear()
router.replace('/login')
}
} catch (error) {
oldPasswordError.value = '原密码错误或修改失败';
} finally {
@ -797,7 +743,7 @@ const resetAllStates = () => {
timer = null;
}
countdown.value = 0;
//
newPhoneNumber.value = '';
newVerificationCode.value = '';
@ -820,21 +766,21 @@ const resetPasswordStates = () => {
confirmPasswordError.value = '';
};
const getCertificationInfo = async () => {
try {
const res=await certificationInfoApi();
certificationInfo.value=res;
} catch (error) {
message.error('获取认证信息失败');
}
};
onBeforeMount(()=>{
const userInfoStr = localStorage.getItem('userInfo');
if (userInfoStr) {
userInfo.value=JSON.parse(userInfoStr);
if(userInfo.value.certificationStatus==='CERTIFICATION_PASSED'){
getCertificationInfo();
try {
const res = await certificationInfoApi();
certificationInfo.value = res;
} catch (error) {
message.error('获取认证信息失败');
}
};
onBeforeMount(() => {
const userInfoStr = localStorage.getItem('userInfo');
if (userInfoStr) {
userInfo.value = JSON.parse(userInfoStr);
if (userInfo.value.certificationStatus === 'CERTIFICATION_PASSED') {
getCertificationInfo();
}
}
}
})
</script>
@ -874,11 +820,11 @@ onBeforeMount(()=>{
span:first-child {
width: 80px;
}
div[style*="color:#1677ff"] {
cursor: pointer;
transition: color 0.3s;
&:hover {
color: #0958d9;
}
@ -1074,11 +1020,11 @@ onBeforeMount(()=>{
font-size: 12px;
color: #8c8c8c;
margin-bottom: 4px;
&:last-child {
margin-bottom: 0;
}
&.valid {
color: #52c41a;
}
@ -1099,7 +1045,7 @@ onBeforeMount(()=>{
flex-direction: column;
gap: 8px;
}
.send-code-btn {
width: 100%;
}

View File

@ -4,28 +4,25 @@
<a-row :gutter="24" class="asset-cards">
<!-- 左侧可用余额卡片 -->
<a-col :span="8">
<a-card :bordered="false" class="card balance-card">
<div class="fee-header">
<div class="fee-title">可用余额</div>
<a-button type="link" size="small" class="fee-titleb" @click="goToBills">查看消费明细</a-button>
</div>
<a-divider />
<div class="money">¥ {{ formatAmount(balance) }}</div>
<a-card :bordered="false" class="card balance-card" title="可用余额">
<template #extra><a-button type="link" size="small" class="fee-titleb"
@click="goToBills">查看消费明细</a-button></template>
<div class="money">¥ {{ formatAmount(userInfo.balance || 0) }}</div>
<div class="money-btn">
<div><a-button type="primary" size="small" @click="goToRecharge">充值</a-button></div>
<div><a-button size="small">提现</a-button></div>
<div><a-button size="small" @click="dialogWithDrawal = true">提现</a-button></div>
</div>
</a-card>
</a-col>
<!-- 右侧算力点和算力券卡片 -->
<a-col :span="16">
<!-- 算力点卡片 -->
<a-card title="算力点" :bordered="false" class="card computing-card">
<div class="computing-content">
<div class="computing-amount">
<span class="amount-value">{{ formatComputingPoints(computingPoints) }}</span>
<span class="amount-unit"></span>
<span class="amount-value">{{ '¥' + formatAmount(userInfo.computingPowerPoint || 0) }}</span>
<!-- <span class="amount-unit"></span> -->
</div>
<div class="computing-actions">
<a-button type="primary" size="small" @click="goToExchange">去兑换</a-button>
@ -37,7 +34,7 @@
<a-card title="算力券" :bordered="false" class="card coupon-card">
<div class="coupon-content">
<div class="coupon-amount">
<span class="amount-value">{{ availableCoupons }}</span>
<span class="amount-value">{{ userInfo.voucherNum }}</span>
<span class="amount-unit"></span>
</div>
<div class="coupon-actions">
@ -51,44 +48,27 @@
<!-- 底部账单区域 -->
<div class="bill-section">
<div class="bill-header">
<h3 class="bill-title">账单明细</h3>
<div class="date-range">
<span class="date-label">日期范围</span>
<a-range-picker
v-model:value="dateRange"
:placeholder="['开始时间', '结束时间']"
@change="handleDateChange"
style="width: 250px;"
/>
</div>
<h3 class="bill-title">消费明细</h3>
</div>
<!-- 账单表格 -->
<a-table
:columns="columns"
:data-source="billData"
:pagination="pagination"
@change="handleTableChange"
:loading="loading"
class="bill-table"
:scroll="{ x: 1200 }"
>
<a-table :columns="columns" :data-source="billData" :pagination="pagination" @change="handleTableChange"
:loading="loading" class="bill-table" :scroll="{ x: 1200 }">
<!-- 流水号列 -->
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'serialNumber'">
<span class="serial-number">{{ record.serialNumber }}</span>
</template>
<!-- 交易类型列 -->
<template v-else-if="column.key === 'transactionType'">
<a-tag :color="getTransactionTypeColor(record.transactionType)">
{{ record.transactionType }}
</a-tag>
</template>
<!-- 金额相关列 -->
<template v-else-if="['transactionAmount', 'originalPrice', 'discountAmount', 'balancePayment', 'voucherDeduction'].includes(column.key)">
<template
v-else-if="['transactionAmount', 'originalPrice', 'discountAmount', 'balancePayment', 'voucherDeduction'].includes(column.key)">
<span :class="{
'amount-positive': record[column.key] > 0,
'amount-negative': record[column.key] < 0
@ -100,25 +80,29 @@
</a-table>
</div>
</div>
<a-modal v-model:open="dialogWithDrawal" title="提现" @ok="handleOk">
<a-card>
<div style="display: flex;justify-content: flex-start;align-items: center;">
<span style="width: 120px;">提现金额</span>
<a-input-number id="inputNumber" v-model:value="withDrawalCount" placeholder="请输入提现金额" style="width: 100%;"/>
<a-button type="link" @click="withDrawalCount=userInfo.balance" :loading="btnLoading">全部提现</a-button>
</div>
</a-card>
</a-modal>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import {
message,
TableProps
} from 'ant-design-vue'
import { ref, onMounted, computed, } from 'vue'
import { useRouter } from 'vue-router'
import {message,TableProps} from 'ant-design-vue'
import type { Dayjs } from 'dayjs'
import router from '@/router'
import{tixian} from "@/apis/home"
import { fetchUserInfo } from '@/apis/modules/login';
const router = useRouter()
//
const balance = ref<number>(5.00)
const computingPoints = ref<number>(1023)
const availableCoupons = ref<number>(3)
//
const dateRange = ref<[Dayjs, Dayjs]>()
const btnLoading=ref(false)
const dialogWithDrawal = ref(false)
const withDrawalCount = ref(0)
//
interface BillRecord {
key: string
@ -134,6 +118,7 @@ interface BillRecord {
}
const loading = ref(false)
const userInfo = ref<any>({})
const billData = ref<BillRecord[]>([])
const pagination = ref({
current: 1,
@ -173,12 +158,19 @@ const columns = computed(() => [
]
},
{
title: '产品名称',
title: '收支类型',
dataIndex: 'productName',
key: 'productName',
width: 150,
ellipsis: true
},
{
title: '交易渠道',
dataIndex: 'transactionAmount',
key: 'transactionAmount',
width: 120,
align: 'right'
},
{
title: '交易金额',
dataIndex: 'transactionAmount',
@ -187,28 +179,14 @@ const columns = computed(() => [
align: 'right'
},
{
title: '原价',
dataIndex: 'originalPrice',
key: 'originalPrice',
width: 120,
align: 'right'
},
{
title: '优惠金额',
dataIndex: 'discountAmount',
key: 'discountAmount',
width: 120,
align: 'right'
},
{
title: '余额支付',
title: '账户余额',
dataIndex: 'balancePayment',
key: 'balancePayment',
width: 120,
align: 'right'
},
{
title: '代金券抵扣',
title: '备注',
dataIndex: 'voucherDeduction',
key: 'voucherDeduction',
width: 120,
@ -218,6 +196,10 @@ const columns = computed(() => [
//
onMounted(() => {
const userInfoStr = localStorage.getItem('userInfo');
if (userInfoStr) {
userInfo.value = JSON.parse(userInfoStr);
}
fetchBillData()
})
@ -255,7 +237,7 @@ const handleDateChange = (dates: [Dayjs, Dayjs] | null) => {
const handleTableChange: TableProps['onChange'] = (pag, filters, sorter) => {
pagination.value.current = pag.current!
pagination.value.pageSize = pag.pageSize!
//
fetchBillData()
}
@ -263,7 +245,7 @@ const handleTableChange: TableProps['onChange'] = (pag, filters, sorter) => {
//
const fetchBillData = () => {
loading.value = true
// API
setTimeout(() => {
//
@ -341,7 +323,7 @@ const fetchBillData = () => {
voucherDeduction: 0.00
}
]
billData.value = mockData
pagination.value.total = mockData.length
loading.value = false
@ -358,7 +340,7 @@ const goToBills = () => {
}
const goToCoupons = () => {
router.push('/rights')
router.push('/layout/admin/myCertificate')
}
const goToExchange = () => {
@ -366,6 +348,21 @@ const goToExchange = () => {
//
router.push('/layout/admin/exchange')
}
const handleOk=async ()=>{
try {
btnLoading.value=true
const res=await tixian({amount:withDrawalCount.value})
if(res){
message.success("提现成功")
const userRes = await fetchUserInfo();
localStorage.setItem('userInfo', JSON.stringify(userRes));
}
btnLoading.value=false
} catch (error:any) {
message.error(error)
btnLoading.value=false
}
}
</script>
<style lang="scss" scoped>
@ -377,8 +374,9 @@ const goToExchange = () => {
.asset-cards {
margin-bottom: 24px;
.ant-col-8, .ant-col-16 {
.ant-col-8,
.ant-col-16 {
display: flex;
flex-direction: column;
}
@ -390,12 +388,12 @@ const goToExchange = () => {
background: #fff;
border: 1px solid #e8e8e8;
height: 100%;
:deep(.ant-card-head) {
border-bottom: 1px solid #f0f0f0;
min-height: 48px;
background: #fafafa;
.ant-card-head-title {
font-size: 16px;
font-weight: 600;
@ -403,7 +401,7 @@ const goToExchange = () => {
color: #333;
}
}
:deep(.ant-card-body) {
padding: 24px;
}
@ -417,62 +415,62 @@ const goToExchange = () => {
align-items: center;
margin-bottom: 8px;
}
.fee-title {
font-size: 16px;
font-weight: 600;
color: #333;
background: linear-gradient(90deg, rgba(240, 245, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
}
.fee-titleb {
font-size: 12px;
color: #1890ff;
padding: 0;
height: auto;
}
:deep(.ant-divider) {
margin: 16px 0;
}
.money {
font-size: 32px;
font-weight: 700;
color: #1890ff;
text-align: center;
color: #000000;
text-align: left;
margin: 20px 0 30px;
line-height: 1;
}
.money-btn {
display: flex;
justify-content: center;
justify-content: flex-end;
gap: 16px;
div {
flex: 1;
text-align: center;
:deep(.ant-btn) {
width: 100px;
height: 36px;
border-radius: 4px;
font-weight: 500;
&.ant-btn-primary {
background: #1890ff;
border-color: #1890ff;
&:hover {
background: #40a9ff;
border-color: #40a9ff;
}
}
&:not(.ant-btn-primary) {
color: #666;
border-color: #d9d9d9;
&:hover {
color: #1890ff;
border-color: #1890ff;
@ -484,48 +482,50 @@ const goToExchange = () => {
}
//
.computing-card, .coupon-card {
.computing-card,
.coupon-card {
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
}
.computing-content, .coupon-content {
.computing-content,
.coupon-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px 0;
justify-content: space-between;
// padding: 20px 0;
.amount-value {
font-size: 36px;
font-weight: 600;
color: #1890ff; //
color: #000000; //
line-height: 1;
margin-right: 8px;
}
.amount-unit {
font-size: 18px;
color: #666;
font-weight: 500;
}
.computing-actions, .coupon-actions {
margin-top: 24px;
.computing-actions,
.coupon-actions {
// margin-top: 24px;
:deep(.ant-btn) {
width: 100px;
height: 32px;
border-radius: 4px;
font-weight: 500;
&.ant-btn-primary {
background: #1890ff;
border-color: #1890ff;
&:hover {
background: #40a9ff;
border-color: #40a9ff;
@ -551,32 +551,32 @@ const goToExchange = () => {
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
.bill-title {
font-size: 18px;
font-weight: 600;
color: #333;
margin: 0;
}
.date-range {
display: flex;
align-items: center;
.date-label {
font-size: 14px;
color: #666;
margin-right: 12px;
}
:deep(.ant-picker) {
border-radius: 4px;
border-color: #d9d9d9;
&:hover {
border-color: #40a9ff;
}
&.ant-picker-focused {
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
@ -590,48 +590,48 @@ const goToExchange = () => {
border-radius: 6px;
overflow: hidden;
border: 1px solid #f0f0f0;
.ant-table-thead > tr > th {
.ant-table-thead>tr>th {
background: #fafafa;
font-weight: 600;
color: #333;
padding: 12px 16px;
border-bottom: 1px solid #f0f0f0;
&:not(:last-child) {
border-right: 1px solid #f0f0f0;
}
}
.ant-table-tbody > tr > td {
.ant-table-tbody>tr>td {
padding: 12px 16px;
border-bottom: 1px solid #f0f0f0;
&:not(:last-child) {
border-right: 1px solid #f0f0f0;
}
&:hover {
background: #fafafa;
}
}
.ant-table-tbody > tr:last-child > td {
.ant-table-tbody>tr:last-child>td {
border-bottom: none;
}
}
.serial-number {
font-family: 'Courier New', monospace;
color: #1890ff;
font-weight: 500;
}
.amount-positive {
color: #52c41a;
font-weight: 500;
}
.amount-negative {
color: #ff4d4f;
font-weight: 500;
@ -643,36 +643,39 @@ const goToExchange = () => {
.home-page {
padding: 12px;
}
.asset-cards {
.ant-col-8, .ant-col-16 {
.ant-col-8,
.ant-col-16 {
width: 100%;
}
.ant-col-8 {
margin-bottom: 16px;
}
}
.bill-header {
flex-direction: column;
align-items: stretch;
gap: 16px;
.date-range {
justify-content: space-between;
:deep(.ant-picker) {
width: 100%;
}
}
}
.money {
font-size: 28px;
}
.computing-content, .coupon-content {
.computing-content,
.coupon-content {
.amount-value {
font-size: 32px;
}

View File

@ -3,7 +3,8 @@
<div class="real-name-auth-page">
<div class="header">
<h1>实名认证</h1>
<p>请选择您的认证类型</p>
<p v-if="userInfo.certificationStatus==='PENDING_CERTIFICATION'">请选择您的认证类型</p>
<p v-if="userInfo.certificationStatus==='CERTIFICATION_PASSED'">Fast亼算云严格遵守国家相关个人信息隐私保护规定并不存储使用您的个人信息查看<span style="color:#1677ff;cursor: pointer;">实名认证协议</span> </p>
</div>
<div v-if="userInfo.certificationStatus==='CERTIFICATION_DFFILED'">认证审核中...</div>
<div v-if="userInfo.certificationStatus==='CERTIFICATION_FAILED'">认证失败</div>
@ -16,15 +17,15 @@
<div class="auth-explanation" style="display: flex;justify-content: flex-start;gap: 100px;">
<div>
<p><span>认证类型</span>&nbsp;&nbsp;&nbsp;&nbsp;<span>{{ certificationInfo.certificationType }}</span></p>
<p><span>证类型</span>&nbsp;&nbsp;&nbsp;&nbsp;<span></span><span>{{ certificationInfo.idCard }}</span></p>
<p><span>类型</span>&nbsp;&nbsp;&nbsp;&nbsp;<span></span><span>{{ certificationInfo.documentType }}</span></p>
<p><span>认证人</span>&nbsp;&nbsp;&nbsp;&nbsp;<span></span><span>{{ certificationInfo.name }}</span></p>
<p>
<a-button type="primary">变更为企业认证</a-button>
<a-button type="primary" v-if="certificationInfo.certificationType==='个人认证'" @click="transOpen=true">变更为企业认证</a-button>
</p>
</div>
<div>
<p><span>认证时间</span><span>2025-11-18</span></p>
<p><span>身份证号</span><span>放假就佛法家</span></p>
<p><span>认证时间</span><span>{{ certificationInfo.certificationTime }}</span></p>
<p><span>身份证号</span><span>{{ certificationInfo.idCard }}</span></p>
<!-- <p><span>认证人</span><span>用户名</span></p> -->
</div>
<div>
@ -86,6 +87,14 @@
</a-card>
</div>
</div>
<a-modal v-model:open="transOpen" title="变更须知">
<a-card>
<p>1.您正在进行个人实名认证变更为企业实名认证操作变更后账号以及账号下资产将属于新的认证企业</p>
<p>2. 请知悉升级企业实名认证后该账号将无法再变更回个人实名认证请您谨慎操作</p>
<p><a-checkbox v-model:checked="checked">我已仔细阅读并代表同意 <span style="color: #1677ff;cursor: pointer;">申请账号主体变更协议</span></a-checkbox></p>
<a-button type="primary" @click="goEnterRealAuth" :disabled="!checked">下一步</a-button>
</a-card>
</a-modal>
</template>
<script setup lang="ts">
@ -104,6 +113,8 @@ import{certificationInfoApi} from '@/apis/certification';
const router = useRouter();
const certificationInfo = ref<any>({
});
const checked=ref(false)
const transOpen=ref(false)
const userInfo=ref<any>({
certificationStatus:'PENDING_CERTIFICATION'
});

View File

@ -1,10 +1,12 @@
<template>
<div class="admin-layout">
<div style="background-color: #ffffff;display: flex;flex-direction: column;align-items: center;border-right: 1px solid #e8e8e8;">
<div style="height: 70px;width: 90%;display: flex;justify-content: center;border-bottom: 1px solid #e8e8e8;align-items: center;">
<div
style="background-color: #ffffff;display: flex;flex-direction: column;align-items: center;border-right: 1px solid #e8e8e8;">
<div
style="height: 70px;width: 90%;display: flex;justify-content: center;border-bottom: 1px solid #e8e8e8;align-items: center;">
<a-button type="primary" style="width: 200px;" @click="router.push('/layout/admin/home')">
<template #icon>
<AppstoreAddOutlined />
<AppstoreAddOutlined />
</template>
总览
</a-button>
@ -23,8 +25,8 @@
</template>
<script lang="ts" setup>
import { h, reactive, computed,watch } from 'vue';
import { useRouter,useRoute } from 'vue-router';
import { h, reactive, computed, watch } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import {
HomeOutlined,
ConsoleSqlOutlined,
@ -53,9 +55,8 @@ interface MenuItem {
}
const menuItems: MenuItem[] = [
{ path: '/layout/admin/home', name: '总览', icon: HomeOutlined, visible: true },
{ path: '/layout/admin/instance', name: '容器实例', icon: ConsoleSqlOutlined, visible: true },
{ path: '/layout/admin/image', name: '镜像', icon: GlobalOutlined, visible: true },
// { path: '/layout/admin/home', name: '', icon: HomeOutlined, visible: true },
{
path: '',
name: '费用中心',
@ -72,7 +73,7 @@ const menuItems: MenuItem[] = [
{ path: '/layout/admin/myOrder', name: '订单明细', visible: true, disabled: false },
// { path: '/layout/admin/flow', name: '', visible: true, disabled: false },
// { path: '/layout/admin/coupon', name: '()', disabled: true, visible: true },
// { path: '/layout/admin/voucher', name: '()', disabled: true, visible: true },
// { path: '/layout/admin/contract', name: '()', disabled: true, visible: true },
],
@ -94,6 +95,10 @@ const menuItems: MenuItem[] = [
{ path: '/layout/admin/invoices', name: '发票抬头管理', visible: true },
],
},
{ path: '/layout/admin/image', name: '镜像管理', icon: GlobalOutlined, visible: true },
{ path: '/layout/admin/instance', name: '实例管理', icon: ConsoleSqlOutlined, visible: true },
{ path: '/layout/admin/instance', name: '存储管理', icon: ConsoleSqlOutlined, visible: true },
];
//
@ -138,43 +143,43 @@ const items = computed(() => {
.filter(child => child.visible !== false)
.map((child) =>
getItem(
child.name,
child.path,
undefined,
undefined,
undefined,
child.name,
child.path,
undefined,
undefined,
undefined,
child.disabled
)
)
.filter(Boolean) as MenuItemType[];
//
if (childItems.length === 0) {
return getItem(
item.name,
item.path,
h(item.icon),
item.name,
item.path,
h(item.icon),
undefined, //
undefined,
undefined,
item.disabled
);
}
return getItem(
item.name,
item.path,
h(item.icon),
childItems,
undefined,
item.name,
item.path,
h(item.icon),
childItems,
undefined,
item.disabled
);
} else {
return getItem(
item.name,
item.path,
h(item.icon),
undefined,
undefined,
item.name,
item.path,
h(item.icon),
undefined,
undefined,
item.disabled
);
}
@ -186,7 +191,7 @@ const handleMenuSelect = ({ key }: { key: string }) => {
//
const allItems = flattenMenuItems(menuItems);
const targetItem = allItems.find(item => item.path === key);
//
if (targetItem?.disabled || targetItem?.visible === false) {
return;
@ -197,14 +202,14 @@ const handleMenuSelect = ({ key }: { key: string }) => {
//
function flattenMenuItems(items: MenuItem[]): MenuItem[] {
let result: MenuItem[] = [];
items.forEach(item => {
result.push(item);
if (item.children) {
result = result.concat(flattenMenuItems(item.children));
}
});
return result;
}

View File

@ -238,7 +238,7 @@ const getDataList = async () => {
mockData.value = res.data
loading.value = false
} catch (error) {
console.error('获取实例列表失败:', error)
console.log('获取实例列表失败:', error)
message.error('数据加载失败')
loading.value = false
}