This commit is contained in:
aaron 2025-03-09 13:41:40 +08:00
parent 0f7e55b139
commit b66cd95bda
5 changed files with 863 additions and 3 deletions

View File

@ -16,6 +16,14 @@
<template #icon><dashboard-outlined /></template>
<span>仪表盘</span>
</a-menu-item>
<a-menu-item key="finance" @click="() => $router.push('/finance')">
<template #icon><dollar-outlined /></template>
<span>财务管理</span>
</a-menu-item>
<a-menu-item key="bank-card" @click="() => $router.push('/bank-card')">
<template #icon><credit-card-outlined /></template>
<span>银行卡管理</span>
</a-menu-item>
</a-menu>
</a-layout-sider>
@ -71,14 +79,18 @@ import { useStore } from 'vuex';
import { useRoute, useRouter } from 'vue-router';
import {
DashboardOutlined,
HomeOutlined
HomeOutlined,
DollarOutlined,
CreditCardOutlined
} from '@ant-design/icons-vue';
export default {
name: 'AdminLayout',
components: {
DashboardOutlined,
HomeOutlined
HomeOutlined,
DollarOutlined,
CreditCardOutlined
},
setup() {
const store = useStore();

View File

@ -12,6 +12,18 @@ const routes = [
name: 'Dashboard',
component: () => import('../views/Dashboard.vue'),
meta: { title: '仪表盘', icon: 'dashboard' }
},
{
path: 'finance',
name: 'Finance',
component: () => import('../views/Finance.vue'),
meta: { title: '财务管理', icon: 'money' }
},
{
path: 'bank-card',
name: 'BankCard',
component: () => import('../views/BankCard.vue'),
meta: { title: '银行卡管理', icon: 'credit-card' }
}
]
},

View File

@ -28,7 +28,11 @@ export default {
BASE_URL,
BUILD_TIMESTAMP,
API: {
LOGIN: '/api/user/password-login'
LOGIN: '/api/user/password-login',
ACCOUNT_SUMMARY: '/api/account/summary',
ACCOUNT_WITHDRAWALS: '/api/withdraw/user',
BANK_CARDS: '/api/bank-cards',
WITHDRAW: '/api/withdraw'
},
// 添加时间戳参数到 URL用于破除缓存
addTimestamp: (url) => {

288
src/views/BankCard.vue Normal file
View File

@ -0,0 +1,288 @@
<template>
<div class="bank-card-container">
<a-card title="银行卡管理" :bordered="false">
<div class="section-header">
<h3>我的银行卡</h3>
<div class="section-actions">
<a-button type="primary" @click="showAddBankCardModal">
<plus-outlined style="color: #fff;" />
添加银行卡
</a-button>
</div>
</div>
<a-spin :spinning="loading">
<div v-if="bankCards.length === 0" class="empty-data">
<a-empty description="暂无银行卡" />
<div class="empty-action">
<a-button type="primary" @click="showAddBankCardModal">
<plus-outlined style="color: #fff;" />
添加银行卡
</a-button>
</div>
</div>
<a-row :gutter="16" v-else>
<a-col :span="8" v-for="card in bankCards" :key="card.id">
<a-card class="bank-card">
<div class="bank-card-header">
<div class="bank-name">{{ card.bank_name }}</div>
<div class="card-type">储蓄卡</div>
</div>
<div class="card-number">{{ card.card_number }}</div>
<div class="card-holder">{{ card.name }}</div>
</a-card>
</a-col>
</a-row>
</a-spin>
</a-card>
<!-- 添加银行卡弹窗 -->
<a-modal
v-model:visible="addBankCardModalVisible"
title="添加银行卡"
@ok="handleAddBankCard"
@cancel="addBankCardModalVisible = false"
>
<a-form :model="bankCardForm" layout="vertical">
<a-form-item name="name" label="持卡人姓名" required>
<a-input
v-model:value="bankCardForm.name"
placeholder="请输入持卡人姓名"
/>
</a-form-item>
<a-form-item name="card_number" label="银行卡号" required>
<a-input
v-model:value="bankCardForm.card_number"
placeholder="请输入银行卡号"
/>
</a-form-item>
<a-form-item name="bank_name" label="银行名称" required>
<a-select
v-model:value="bankCardForm.bank_name"
placeholder="请选择银行"
>
<a-select-option value="工商银行">工商银行</a-select-option>
<a-select-option value="建设银行">建设银行</a-select-option>
<a-select-option value="农业银行">农业银行</a-select-option>
<a-select-option value="招商银行">招商银行</a-select-option>
<a-select-option value="中国银行">中国银行</a-select-option>
<a-select-option value="交通银行">交通银行</a-select-option>
<a-select-option value="邮储银行">邮储银行</a-select-option>
</a-select>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script>
import { ref, reactive, onMounted } from 'vue';
import { message } from 'ant-design-vue';
import { getBankCards, addBankCard } from '../api/finance';
import {
PlusOutlined
} from '@ant-design/icons-vue';
export default {
name: 'BankCard',
components: {
PlusOutlined
},
setup() {
//
const bankCards = ref([]);
const loading = ref(false);
const addBankCardModalVisible = ref(false);
const bankCardForm = reactive({
name: '',
card_number: '',
bank_name: ''
});
//
const fetchBankCards = async () => {
loading.value = true;
try {
const data = await getBankCards();
bankCards.value = data || [];
} catch (error) {
console.error('获取银行卡列表失败:', error);
message.error('获取银行卡列表失败');
} finally {
loading.value = false;
}
};
//
const showAddBankCardModal = () => {
bankCardForm.name = '';
bankCardForm.card_number = '';
bankCardForm.bank_name = '';
addBankCardModalVisible.value = true;
};
//
const handleAddBankCard = async () => {
//
if (!bankCardForm.name) {
message.error('请输入持卡人姓名');
return;
}
if (!bankCardForm.card_number) {
message.error('请输入银行卡号');
return;
}
if (!bankCardForm.bank_name) {
message.error('请选择银行');
return;
}
try {
await addBankCard(bankCardForm);
message.success('银行卡添加成功');
addBankCardModalVisible.value = false;
//
fetchBankCards();
} catch (error) {
console.error('添加银行卡失败:', error);
message.error('添加银行卡失败');
}
};
onMounted(() => {
fetchBankCards();
});
return {
bankCards,
loading,
addBankCardModalVisible,
bankCardForm,
showAddBankCardModal,
handleAddBankCard
};
}
};
</script>
<style scoped>
.bank-card-container {
padding: 0;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.section-header h3 {
margin: 0;
font-size: 18px;
font-weight: 500;
}
.section-actions {
display: flex;
gap: 8px;
}
.empty-data {
padding: 32px 0;
text-align: center;
}
.empty-action {
margin-top: 16px;
}
.bank-card {
height: 200px;
border-radius: 12px;
background: linear-gradient(135deg, #1a1a1a 0%, #333333 100%);
color: white;
margin-bottom: 16px;
position: relative;
overflow: hidden;
}
.bank-card::before {
content: '';
position: absolute;
top: -50px;
right: -50px;
width: 100px;
height: 100px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
}
.bank-card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.bank-name {
font-size: 18px;
font-weight: 500;
}
.card-type {
font-size: 12px;
opacity: 0.8;
}
.card-number {
font-size: 20px;
letter-spacing: 2px;
margin-bottom: 24px;
}
.card-holder {
font-size: 14px;
opacity: 0.8;
margin-bottom: 16px;
}
.action-button {
display: flex;
align-items: center;
justify-content: center;
line-height: 1;
}
.action-button :deep(.ant-btn-icon) {
display: flex !important;
align-items: center !important;
justify-content: center !important;
margin-right: 4px !important;
}
/* 确保所有按钮中的图标和文字都能正确对齐 */
.ant-btn {
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
}
.ant-btn :deep(.anticon) {
display: flex !important;
align-items: center !important;
line-height: 0 !important;
}
/* 确保主要按钮中的图标颜色为白色 */
.ant-btn-primary :deep(.anticon) {
color: #fff !important;
}
/* 确保默认按钮中的图标颜色可见 */
.ant-btn-default :deep(.anticon) {
color: rgba(0, 0, 0, 0.85) !important;
}
</style>

544
src/views/Finance.vue Normal file
View File

@ -0,0 +1,544 @@
<template>
<div class="finance-container">
<a-card title="账户管理" :bordered="false">
<!-- 账户信息卡片 -->
<a-row :gutter="16" class="account-summary">
<a-col :span="8">
<a-card class="summary-card">
<template #title>
<div class="card-title">
<wallet-outlined />
<span>账户余额</span>
</div>
</template>
<div class="amount">
<span class="currency">¥</span>
<span class="value">{{ accountInfo.total_balance || '0.00' }}</span>
</div>
</a-card>
</a-col>
<a-col :span="8">
<a-card class="summary-card">
<template #title>
<div class="card-title">
<dollar-outlined />
<span>可提现金额</span>
</div>
</template>
<div class="amount">
<span class="currency">¥</span>
<span class="value">{{ accountInfo.balance || '0.00' }}</span>
</div>
<div class="action-buttons">
<a-button type="primary" @click="showWithdrawalModal">申请提现</a-button>
<a-button @click="goToBankCardPage" style="margin-left: 8px">管理银行卡</a-button>
</div>
</a-card>
</a-col>
<a-col :span="8">
<a-card class="summary-card">
<template #title>
<div class="card-title">
<lock-outlined />
<span>锁定金额</span>
</div>
</template>
<div class="amount">
<span class="currency">¥</span>
<span class="value">{{ accountInfo.lock_balance || '0.00' }}</span>
</div>
</a-card>
</a-col>
</a-row>
<!-- 提现记录表格 -->
<div class="withdrawal-list">
<div class="section-header">
<h3>提现记录</h3>
<div class="section-actions">
<a-input-search
v-model:value="searchText"
placeholder="搜索提现记录"
style="width: 250px"
@search="handleSearch"
/>
<a-button type="primary" @click="refreshData">
<reload-outlined style="color: #fff;" />
刷新
</a-button>
</div>
</div>
<a-table
:dataSource="withdrawalList"
:loading="loading"
:pagination="{
total: total,
current: currentPage,
pageSize: pageSize,
showTotal: total => `${total} 条记录`,
showSizeChanger: true,
}"
@change="handleTableChange"
>
<a-table-column title="提现单号" dataIndex="id" key="id" />
<a-table-column title="申请时间" dataIndex="create_time" key="create_time">
<template #default="{ text }">
{{ formatDate(text) }}
</template>
</a-table-column>
<a-table-column title="提现金额" dataIndex="amount" key="amount">
<template #default="{ text }">
¥ {{ text }}
</template>
</a-table-column>
<a-table-column title="状态" dataIndex="status" key="status">
<template #default="{ text }">
<a-tag :color="getStatusColor(text)">
{{ getStatusText(text) }}
</a-tag>
</template>
</a-table-column>
<a-table-column title="银行卡" key="bank_card">
<template #default="{ record }">
{{ record.bank_name }} ({{ record.bank_card_number }})
</template>
</a-table-column>
<a-table-column title="持卡人" dataIndex="name" key="name" />
<a-table-column title="更新时间" dataIndex="update_time" key="update_time">
<template #default="{ text }">
{{ text ? formatDate(text) : '-' }}
</template>
</a-table-column>
<a-table-column title="操作" key="action" width="120px">
<template #default="{ record }">
<a-popconfirm
v-if="record.status === 'PENDING'"
title="确定要取消此提现申请吗?"
ok-text="确定"
cancel-text="取消"
@confirm="handleCancelWithdraw(record.id)"
>
<a-button type="link" danger>
取消申请
</a-button>
</a-popconfirm>
<span v-else-if="record.status === 'CANCELLED'" class="status-text">已取消</span>
<span v-else-if="record.status === 'REJECTED'" class="status-text">已驳回</span>
<span v-else-if="record.status === 'APPROVED'" class="status-text">处理中</span>
<span v-else-if="record.status === 'COMPLETED'" class="status-text">已完成</span>
</template>
</a-table-column>
</a-table>
</div>
</a-card>
<!-- 提现申请弹窗 -->
<a-modal
v-model:visible="withdrawalModalVisible"
title="申请提现"
@ok="handleWithdrawalSubmit"
@cancel="withdrawalModalVisible = false"
>
<a-form :model="withdrawalForm" layout="vertical">
<a-form-item name="amount" label="提现金额" required>
<a-input-number
v-model:value="withdrawalForm.amount"
placeholder="请输入提现金额"
style="width: 100%"
:min="1"
:max="accountInfo.balance"
:precision="2"
/>
<div class="form-tip">
可提现金额: ¥{{ accountInfo.balance || '0.00' }}最低提现金额: ¥1.00
</div>
</a-form-item>
<a-form-item name="bank_card_id" label="收款银行账户" required>
<a-select
v-model:value="withdrawalForm.bank_card_id"
placeholder="请选择收款银行账户"
>
<a-select-option v-for="card in bankCards" :key="card.id" :value="card.id">
{{ card.bank_name }} ({{ card.card_number }})
</a-select-option>
</a-select>
<div v-if="bankCards.length === 0" class="form-tip">
<a-alert type="warning" message="您还没有添加银行卡,请先添加银行卡" banner />
<div style="margin-top: 8px;">
<a @click="goToBankCardPage">添加银行卡</a>
</div>
</div>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script>
import { ref, reactive, onMounted } from 'vue';
import { message } from 'ant-design-vue';
import { useRouter } from 'vue-router';
import {
getAccountSummary,
getWithdrawalList,
getBankCards,
applyWithdraw,
cancelWithdraw
} from '../api/finance';
import {
WalletOutlined,
LockOutlined,
DollarOutlined,
ReloadOutlined
} from '@ant-design/icons-vue';
export default {
name: 'Finance',
components: {
WalletOutlined,
LockOutlined,
DollarOutlined,
ReloadOutlined
},
setup() {
const router = useRouter();
//
const accountInfo = ref({
total_balance: '0.00',
lock_balance: '0.00',
balance: '0.00'
});
//
const withdrawalList = ref([]);
const loading = ref(false);
const total = ref(0);
const currentPage = ref(1);
const pageSize = ref(10);
const searchText = ref('');
//
const bankCards = ref([]);
const bankCardsLoading = ref(false);
//
const withdrawalModalVisible = ref(false);
const withdrawalForm = reactive({
amount: null,
bank_card_id: null
});
//
const fetchAccountInfo = async () => {
try {
const data = await getAccountSummary();
accountInfo.value = data;
} catch (error) {
console.error('获取账户信息失败:', error);
message.error('获取账户信息失败');
}
};
//
const fetchWithdrawalList = async () => {
loading.value = true;
try {
const params = {
page: currentPage.value,
pageSize: pageSize.value,
keyword: searchText.value
};
const response = await getWithdrawalList(params);
withdrawalList.value = response.items || [];
total.value = response.total || 0;
} catch (error) {
console.error('获取提现列表失败:', error);
message.error('获取提现列表失败');
} finally {
loading.value = false;
}
};
//
const fetchBankCards = async () => {
bankCardsLoading.value = true;
try {
const data = await getBankCards();
bankCards.value = data || [];
} catch (error) {
console.error('获取银行卡列表失败:', error);
message.error('获取银行卡列表失败');
} finally {
bankCardsLoading.value = false;
}
};
//
const refreshData = () => {
fetchAccountInfo();
fetchWithdrawalList();
fetchBankCards();
};
//
const handleTableChange = (pagination, filters, sorter) => {
currentPage.value = pagination.current;
pageSize.value = pagination.pageSize;
fetchWithdrawalList();
};
//
const handleSearch = () => {
currentPage.value = 1;
fetchWithdrawalList();
};
//
const goToBankCardPage = () => {
router.push('/bank-card');
if (withdrawalModalVisible.value) {
withdrawalModalVisible.value = false;
}
};
//
const showWithdrawalModal = () => {
withdrawalForm.amount = null;
withdrawalForm.bank_card_id = null;
withdrawalModalVisible.value = true;
};
//
const handleWithdrawalSubmit = async () => {
if (!withdrawalForm.amount) {
message.error('请输入提现金额');
return;
}
if (!withdrawalForm.bank_card_id) {
message.error('请选择收款银行账户');
return;
}
try {
await applyWithdraw({
bank_card_id: withdrawalForm.bank_card_id,
amount: withdrawalForm.amount
});
message.success('提现申请已提交,请等待审核');
withdrawalModalVisible.value = false;
//
setTimeout(() => {
refreshData();
}, 1000);
} catch (error) {
console.error('提现申请失败:', error);
message.error('提现申请失败');
}
};
//
const handleCancelWithdraw = async (id) => {
try {
await cancelWithdraw(id);
message.success('提现申请已取消');
//
refreshData();
} catch (error) {
console.error('取消提现申请失败:', error);
message.error('取消提现申请失败');
}
};
//
const formatDate = (dateString) => {
if (!dateString) return '-';
const date = new Date(dateString);
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
};
//
const getStatusColor = (status) => {
const statusMap = {
PENDING: 'orange',
APPROVED: 'blue',
COMPLETED: 'green',
REJECTED: 'red',
CANCELLED: 'gray'
};
return statusMap[status] || 'default';
};
//
const getStatusText = (status) => {
const statusMap = {
PENDING: '已申请',
APPROVED: '已通过',
COMPLETED: '已完成',
REJECTED: '已驳回',
CANCELLED: '已取消'
};
return statusMap[status] || '未知状态';
};
onMounted(() => {
refreshData();
});
return {
accountInfo,
withdrawalList,
loading,
total,
currentPage,
pageSize,
searchText,
bankCards,
withdrawalModalVisible,
withdrawalForm,
refreshData,
handleTableChange,
handleSearch,
goToBankCardPage,
showWithdrawalModal,
handleWithdrawalSubmit,
handleCancelWithdraw,
formatDate,
getStatusColor,
getStatusText
};
}
};
</script>
<style scoped>
.finance-container {
padding: 0;
}
.account-summary {
margin-bottom: 24px;
}
.summary-card {
height: 100%;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
transition: all 0.3s;
}
.summary-card:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
transform: translateY(-2px);
}
.card-title {
display: flex;
align-items: center;
font-size: 16px;
font-weight: 500;
}
.card-title :deep(.anticon) {
margin-right: 8px;
font-size: 18px;
}
.amount {
margin-top: 16px;
display: flex;
align-items: baseline;
}
.currency {
font-size: 16px;
margin-right: 4px;
color: rgba(0, 0, 0, 0.65);
}
.value {
font-size: 28px;
font-weight: 500;
color: #1a1a1a;
}
.action-buttons {
margin-top: 16px;
display: flex;
justify-content: flex-end;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.section-header h3 {
margin: 0;
font-size: 18px;
font-weight: 500;
}
.section-actions {
display: flex;
gap: 8px;
}
.withdrawal-list {
margin-top: 24px;
}
.form-tip {
font-size: 12px;
color: rgba(0, 0, 0, 0.45);
margin-top: 4px;
}
.status-text {
color: rgba(0, 0, 0, 0.45);
font-size: 14px;
}
.action-button {
display: flex;
align-items: center;
justify-content: center;
line-height: 1;
}
.action-button :deep(.ant-btn-icon) {
display: flex !important;
align-items: center !important;
justify-content: center !important;
margin-right: 4px !important;
}
/* 确保所有按钮中的图标和文字都能正确对齐 */
.ant-btn {
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
}
.ant-btn :deep(.anticon) {
display: flex !important;
align-items: center !important;
line-height: 0 !important;
}
/* 确保主要按钮中的图标颜色为白色 */
.ant-btn-primary :deep(.anticon) {
color: #fff !important;
}
/* 确保默认按钮中的图标颜色可见 */
.ant-btn-default :deep(.anticon) {
color: rgba(0, 0, 0, 0.85) !important;
}
</style>