Merge branch 'main' of https://gitlab.guxuan.icu/Leo_Ding/GPU_Web
This commit is contained in:
commit
0246b06729
BIN
src/assets/nav.png
Normal file
BIN
src/assets/nav.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
BIN
src/assets/rognqishili.png
Normal file
BIN
src/assets/rognqishili.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 334 KiB |
@ -233,6 +233,18 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () =>
|
||||
import("@/views/admin/account/cost/myOrder/index.vue"),
|
||||
},
|
||||
{
|
||||
path: "myMoney",
|
||||
name: "myMoney",
|
||||
component: () =>
|
||||
import("@/views/admin/account/cost/myMoney/index.vue"),
|
||||
},
|
||||
{
|
||||
path: "myMoneyDetail",
|
||||
name: "myMoneyDetail",
|
||||
component: () =>
|
||||
import("@/views/admin/account/cost/myMoneyDetail/index.vue"),
|
||||
},
|
||||
{
|
||||
path: "voucher",
|
||||
name: "voucher",
|
||||
@ -293,7 +305,7 @@ const router = createRouter({
|
||||
// ====== 添加全局前置守卫 ======
|
||||
// router.beforeEach((to, from, next) => {
|
||||
// console.log("Navigating to:", to.path);
|
||||
// const list = ["/layout/home", "/document/introdution", "/layout/admin/home"];
|
||||
// const list = ["/layout/home","/document/introdution","/layout/admin/home"];
|
||||
// if (list.indexOf(to.path) != -1) {
|
||||
// next();
|
||||
// return;
|
||||
|
||||
681
src/views/admin/account/cost/myMoney/index.vue
Normal file
681
src/views/admin/account/cost/myMoney/index.vue
Normal file
@ -0,0 +1,681 @@
|
||||
<template>
|
||||
<div class="home-page">
|
||||
<!-- 顶部资产卡片区域 -->
|
||||
<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>
|
||||
<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-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>
|
||||
</div>
|
||||
<div class="computing-actions">
|
||||
<a-button type="primary" size="small" @click="goToExchange">去兑换</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<!-- 算力券卡片 -->
|
||||
<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-unit">张</span>
|
||||
</div>
|
||||
<div class="coupon-actions">
|
||||
<a-button type="primary" size="small" @click="goToCoupons">去查看</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 底部账单区域 -->
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<!-- 账单表格 -->
|
||||
<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)">
|
||||
<span :class="{
|
||||
'amount-positive': record[column.key] > 0,
|
||||
'amount-negative': record[column.key] < 0
|
||||
}">
|
||||
¥ {{ formatAmount(Math.abs(record[column.key])) }}
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import {
|
||||
message,
|
||||
TableProps
|
||||
} from 'ant-design-vue'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import router from '@/router'
|
||||
|
||||
// 资产数据
|
||||
const balance = ref<number>(5.00)
|
||||
const computingPoints = ref<number>(1023)
|
||||
const availableCoupons = ref<number>(3)
|
||||
|
||||
// 日期范围选择
|
||||
const dateRange = ref<[Dayjs, Dayjs]>()
|
||||
|
||||
// 表格数据
|
||||
interface BillRecord {
|
||||
key: string
|
||||
serialNumber: string
|
||||
transactionTime: string
|
||||
transactionType: string
|
||||
productName: string
|
||||
transactionAmount: number
|
||||
originalPrice: number
|
||||
discountAmount: number
|
||||
balancePayment: number
|
||||
voucherDeduction: number
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
const billData = ref<BillRecord[]>([])
|
||||
const pagination = ref({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total: number) => `共 ${total} 条记录`
|
||||
})
|
||||
|
||||
// 表格列定义
|
||||
const columns = computed(() => [
|
||||
{
|
||||
title: '流水号',
|
||||
dataIndex: 'serialNumber',
|
||||
key: 'serialNumber',
|
||||
width: 180,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '交易时间',
|
||||
dataIndex: 'transactionTime',
|
||||
key: 'transactionTime',
|
||||
width: 170,
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '交易类型',
|
||||
dataIndex: 'transactionType',
|
||||
key: 'transactionType',
|
||||
width: 120,
|
||||
filters: [
|
||||
{ text: '充值', value: '充值' },
|
||||
{ text: '消费', value: '消费' },
|
||||
{ text: '提现', value: '提现' },
|
||||
{ text: '退款', value: '退款' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '产品名称',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
width: 150,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '交易金额',
|
||||
dataIndex: 'transactionAmount',
|
||||
key: 'transactionAmount',
|
||||
width: 120,
|
||||
align: 'right'
|
||||
},
|
||||
{
|
||||
title: '原价',
|
||||
dataIndex: 'originalPrice',
|
||||
key: 'originalPrice',
|
||||
width: 120,
|
||||
align: 'right'
|
||||
},
|
||||
{
|
||||
title: '优惠金额',
|
||||
dataIndex: 'discountAmount',
|
||||
key: 'discountAmount',
|
||||
width: 120,
|
||||
align: 'right'
|
||||
},
|
||||
{
|
||||
title: '余额支付',
|
||||
dataIndex: 'balancePayment',
|
||||
key: 'balancePayment',
|
||||
width: 120,
|
||||
align: 'right'
|
||||
},
|
||||
{
|
||||
title: '代金券抵扣',
|
||||
dataIndex: 'voucherDeduction',
|
||||
key: 'voucherDeduction',
|
||||
width: 120,
|
||||
align: 'right'
|
||||
}
|
||||
])
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
fetchBillData()
|
||||
})
|
||||
|
||||
// 格式化金额显示
|
||||
const formatAmount = (amount: number): string => {
|
||||
return amount.toFixed(2)
|
||||
}
|
||||
|
||||
// 格式化算力点显示(添加千分位)
|
||||
const formatComputingPoints = (points: number): string => {
|
||||
return points.toLocaleString()
|
||||
}
|
||||
|
||||
// 获取交易类型颜色
|
||||
const getTransactionTypeColor = (type: string): string => {
|
||||
const colorMap: Record<string, string> = {
|
||||
'充值': 'green',
|
||||
'消费': 'blue',
|
||||
'提现': 'orange',
|
||||
'退款': 'purple'
|
||||
}
|
||||
return colorMap[type] || 'default'
|
||||
}
|
||||
|
||||
// 日期范围变化处理
|
||||
const handleDateChange = (dates: [Dayjs, Dayjs] | null) => {
|
||||
if (dates) {
|
||||
console.log('选择的日期范围:', dates)
|
||||
// 重新加载数据
|
||||
fetchBillData()
|
||||
}
|
||||
}
|
||||
|
||||
// 表格变化处理
|
||||
const handleTableChange: TableProps['onChange'] = (pag, filters, sorter) => {
|
||||
pagination.value.current = pag.current!
|
||||
pagination.value.pageSize = pag.pageSize!
|
||||
|
||||
// 这里应该根据排序和筛选条件重新加载数据
|
||||
fetchBillData()
|
||||
}
|
||||
|
||||
// 模拟获取账单数据
|
||||
const fetchBillData = () => {
|
||||
loading.value = true
|
||||
|
||||
// 模拟API调用延迟
|
||||
setTimeout(() => {
|
||||
// 模拟数据
|
||||
const mockData: BillRecord[] = [
|
||||
{
|
||||
key: '1',
|
||||
serialNumber: 'TX202401010001',
|
||||
transactionTime: '2024-01-01 10:30:25',
|
||||
transactionType: '充值',
|
||||
productName: '账户充值',
|
||||
transactionAmount: 100.00,
|
||||
originalPrice: 100.00,
|
||||
discountAmount: 0.00,
|
||||
balancePayment: 0.00,
|
||||
voucherDeduction: 0.00
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
serialNumber: 'TX202401010002',
|
||||
transactionTime: '2024-01-01 14:20:15',
|
||||
transactionType: '消费',
|
||||
productName: 'GPU算力服务',
|
||||
transactionAmount: -50.00,
|
||||
originalPrice: 60.00,
|
||||
discountAmount: 10.00,
|
||||
balancePayment: 40.00,
|
||||
voucherDeduction: 10.00
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
serialNumber: 'TX202401020001',
|
||||
transactionTime: '2024-01-02 09:15:30',
|
||||
transactionType: '充值',
|
||||
productName: '账户充值',
|
||||
transactionAmount: 200.00,
|
||||
originalPrice: 200.00,
|
||||
discountAmount: 0.00,
|
||||
balancePayment: 0.00,
|
||||
voucherDeduction: 0.00
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
serialNumber: 'TX202401030001',
|
||||
transactionTime: '2024-01-03 16:45:20',
|
||||
transactionType: '消费',
|
||||
productName: '存储空间',
|
||||
transactionAmount: -30.00,
|
||||
originalPrice: 30.00,
|
||||
discountAmount: 0.00,
|
||||
balancePayment: 30.00,
|
||||
voucherDeduction: 0.00
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
serialNumber: 'TX202401040001',
|
||||
transactionTime: '2024-01-04 11:10:05',
|
||||
transactionType: '提现',
|
||||
productName: '余额提现',
|
||||
transactionAmount: -100.00,
|
||||
originalPrice: 100.00,
|
||||
discountAmount: 0.00,
|
||||
balancePayment: 100.00,
|
||||
voucherDeduction: 0.00
|
||||
},
|
||||
{
|
||||
key: '6',
|
||||
serialNumber: 'TX202401050001',
|
||||
transactionTime: '2024-01-05 15:30:40',
|
||||
transactionType: '退款',
|
||||
productName: 'GPU算力服务',
|
||||
transactionAmount: 25.00,
|
||||
originalPrice: 25.00,
|
||||
discountAmount: 0.00,
|
||||
balancePayment: 0.00,
|
||||
voucherDeduction: 0.00
|
||||
}
|
||||
]
|
||||
|
||||
billData.value = mockData
|
||||
pagination.value.total = mockData.length
|
||||
loading.value = false
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// 路由跳转方法
|
||||
const goToRecharge = () => {
|
||||
router.push('/recharge')
|
||||
}
|
||||
|
||||
const goToBills = () => {
|
||||
router.push('/bills')
|
||||
}
|
||||
|
||||
const goToCoupons = () => {
|
||||
router.push('/rights')
|
||||
}
|
||||
|
||||
const goToExchange = () => {
|
||||
message.info('跳转到兑换页面')
|
||||
// 这里应该实现跳转到兑换页面的逻辑
|
||||
// router.push('/exchange')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.home-page {
|
||||
padding: 24px;
|
||||
background: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.asset-cards {
|
||||
margin-bottom: 24px;
|
||||
|
||||
.ant-col-8, .ant-col-16 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||||
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;
|
||||
padding: 12px 0;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-card-body) {
|
||||
padding: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
// 左侧余额卡片
|
||||
.balance-card {
|
||||
.fee-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.fee-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.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;
|
||||
margin: 20px 0 30px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.money-btn {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 右侧算力点卡片
|
||||
.computing-card, .coupon-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.computing-content, .coupon-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px 0;
|
||||
|
||||
.amount-value {
|
||||
font-size: 36px;
|
||||
font-weight: 600;
|
||||
color: #1890ff; // 统一改为蓝色
|
||||
line-height: 1;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.amount-unit {
|
||||
font-size: 18px;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 账单区域
|
||||
.bill-section {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.bill-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bill-table {
|
||||
:deep(.ant-table) {
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #f0f0f0;
|
||||
|
||||
.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 {
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式调整
|
||||
@media (max-width: 768px) {
|
||||
.home-page {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.asset-cards {
|
||||
.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 {
|
||||
.amount-value {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@ -27,16 +27,16 @@ import { h, reactive, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import {
|
||||
HomeOutlined,
|
||||
FolderOpenOutlined,
|
||||
ConsoleSqlOutlined,
|
||||
GlobalOutlined,
|
||||
LaptopOutlined,
|
||||
MoneyCollectOutlined,
|
||||
TeamOutlined,
|
||||
AppstoreAddOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import type { MenuMode, MenuTheme } from 'ant-design-vue';
|
||||
import { ItemType } from 'ant-design-vue';
|
||||
|
||||
// 移除 ItemType 的导入,直接使用 Ant Design Vue 的 MenuItem 类型
|
||||
import type { MenuProps } from 'ant-design-vue';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
@ -45,38 +45,40 @@ interface MenuItem {
|
||||
path: string;
|
||||
name: string;
|
||||
icon?: any;
|
||||
disabled?: boolean; // 新增字段
|
||||
children?: Omit<MenuItem, 'icon'>[]; // 子项不需要 icon
|
||||
disabled?: boolean;
|
||||
visible?: boolean;
|
||||
children?: Omit<MenuItem, 'icon'>[];
|
||||
}
|
||||
|
||||
const menuItems: MenuItem[] = [
|
||||
// { path: '/layout/admin/home', name: '总览', icon: HomeOutlined },
|
||||
{ path: '/layout/admin/instance', name: '容器实例', icon: ConsoleSqlOutlined },
|
||||
// { path: '/layout/admin/fileStore', name: '文件存储', icon: FolderOpenOutlined },
|
||||
{ path: '/layout/admin/image', name: '镜像', icon: GlobalOutlined },
|
||||
// { path: '/layout/publicData', name: '公开数据', icon: LaptopOutlined },
|
||||
{ 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: '',
|
||||
name: '费用',
|
||||
icon: MoneyCollectOutlined,
|
||||
visible: true,
|
||||
children: [
|
||||
// { path: '/layout/admin/costDetail', name: '收支明细' },
|
||||
{ path: '/layout/admin/myOrder', name: '我的订单' },
|
||||
{ path: '/layout/admin/flow', name: '账单明细' },
|
||||
{ path: '/layout/admin/coupon', name: '优惠券(待开发)', disabled: true },
|
||||
{ path: '/layout/admin/invoice', name: '发票(待开发)', disabled: true },
|
||||
{ path: '/layout/admin/voucher', name: '代金券(待开发)', disabled: true },
|
||||
{ path: '/layout/admin/contract', name: '合同(待开发)', disabled: true },
|
||||
{ path: '/layout/admin/myMoney', name: '费用总览', visible: true, disabled: false },
|
||||
// 消费明细设置为不可见
|
||||
{ path: '/layout/admin/myMoneyDetail', name: '消费明细', visible: false, disabled: false },
|
||||
{ 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/invoice', name: '发票(待开发)', disabled: true, visible: true },
|
||||
{ path: '/layout/admin/voucher', name: '代金券(待开发)', disabled: true, visible: true },
|
||||
{ path: '/layout/admin/contract', name: '合同(待开发)', disabled: true, visible: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
name: '账号',
|
||||
icon: TeamOutlined,
|
||||
visible: true,
|
||||
children: [
|
||||
{ path: '/layout/admin/security', name: '账号安全' },
|
||||
{ path: '/layout/admin/history', name: '访问记录' },
|
||||
// { path: '/controlPanel/security', name: '安全设置' },
|
||||
{ path: '/layout/admin/security', name: '账号安全', visible: true },
|
||||
{ path: '/layout/admin/history', name: '访问记录', visible: true },
|
||||
],
|
||||
},
|
||||
];
|
||||
@ -89,60 +91,111 @@ const state = reactive({
|
||||
openKeys: ['/controlPanel/fee', '/controlPanel/account'],
|
||||
});
|
||||
|
||||
// 方法一:直接使用 MenuProps['items'] 类型
|
||||
// 或者自己定义 MenuItemType 类型
|
||||
type MenuItemType = NonNullable<MenuProps['items']>[number];
|
||||
|
||||
// 工具函数:生成菜单项
|
||||
function getItem(
|
||||
label: string,
|
||||
key: string,
|
||||
icon?: any,
|
||||
children?: ItemType[],
|
||||
children?: MenuItemType[],
|
||||
type?: 'group',
|
||||
disabled?: boolean // 新增 disabled 参数
|
||||
): ItemType {
|
||||
disabled?: boolean
|
||||
): MenuItemType | null {
|
||||
return {
|
||||
key,
|
||||
icon,
|
||||
children,
|
||||
label,
|
||||
type,
|
||||
disabled, // 应用 disabled
|
||||
} as ItemType;
|
||||
disabled,
|
||||
} as MenuItemType;
|
||||
}
|
||||
|
||||
// 将 menuItems 转为 a-menu 所需的 items
|
||||
const items = computed(() => {
|
||||
return menuItems.map((item) => {
|
||||
return menuItems
|
||||
.filter(item => item.visible !== false)
|
||||
.map((item) => {
|
||||
if (item.children && item.children.length > 0) {
|
||||
const childItems = item.children.map((child) =>
|
||||
getItem(child.name, child.path, undefined, undefined, undefined, child.disabled)
|
||||
// 过滤掉 visible 为 false 的子项
|
||||
const childItems = item.children
|
||||
.filter(child => child.visible !== false)
|
||||
.map((child) =>
|
||||
getItem(
|
||||
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),
|
||||
undefined, // 没有子项
|
||||
undefined,
|
||||
item.disabled
|
||||
);
|
||||
return getItem(item.name, item.path, h(item.icon), childItems, undefined, item.disabled);
|
||||
} else {
|
||||
return getItem(item.name, item.path, h(item.icon), undefined, undefined, item.disabled);
|
||||
}
|
||||
});
|
||||
|
||||
return getItem(
|
||||
item.name,
|
||||
item.path,
|
||||
h(item.icon),
|
||||
childItems,
|
||||
undefined,
|
||||
item.disabled
|
||||
);
|
||||
} else {
|
||||
return getItem(
|
||||
item.name,
|
||||
item.path,
|
||||
h(item.icon),
|
||||
undefined,
|
||||
undefined,
|
||||
item.disabled
|
||||
);
|
||||
}
|
||||
})
|
||||
.filter(Boolean) as MenuItemType[];
|
||||
});
|
||||
|
||||
const handleMenuSelect = ({ key }: { key: string }) => {
|
||||
// 防止跳转到被禁用的菜单(虽然 a-menu 不会触发 select,但加个判断更安全)
|
||||
const targetItem = findMenuItemByKey(menuItems, key);
|
||||
if (targetItem?.disabled) {
|
||||
// 递归查找菜单项(需要包含所有项,包括隐藏的)
|
||||
const allItems = flattenMenuItems(menuItems);
|
||||
const targetItem = allItems.find(item => item.path === key);
|
||||
|
||||
// 检查是否禁用或不可见
|
||||
if (targetItem?.disabled || targetItem?.visible === false) {
|
||||
return;
|
||||
}
|
||||
router.push(key);
|
||||
};
|
||||
|
||||
// 辅助函数:递归查找菜单项
|
||||
function findMenuItemByKey(items: MenuItem[], key: string): MenuItem | undefined {
|
||||
for (const item of items) {
|
||||
if (item.path === key) return item;
|
||||
// 辅助函数:扁平化所有菜单项
|
||||
function flattenMenuItems(items: MenuItem[]): MenuItem[] {
|
||||
let result: MenuItem[] = [];
|
||||
|
||||
items.forEach(item => {
|
||||
result.push(item);
|
||||
if (item.children) {
|
||||
const found = findMenuItemByKey(item.children, key);
|
||||
if (found) return found;
|
||||
result = result.concat(flattenMenuItems(item.children));
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 其他代码保持不变
|
||||
const changeMode = (checked: boolean) => {
|
||||
state.mode = checked ? 'vertical' : 'inline';
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user