添加银行后端接口,前端代码
This commit is contained in:
@@ -9,70 +9,67 @@
|
||||
</div>
|
||||
|
||||
<!-- 桌面端布局 -->
|
||||
<a-layout v-else class="desktop-layout">
|
||||
<a-layout-header class="header">
|
||||
<div class="logo">
|
||||
<a-button
|
||||
type="text"
|
||||
@click="settingsStore.toggleSidebar"
|
||||
style="color: white; margin-right: 8px;"
|
||||
>
|
||||
<menu-unfold-outlined v-if="sidebarCollapsed" />
|
||||
<menu-fold-outlined v-else />
|
||||
</a-button>
|
||||
银行管理后台系统
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<a-dropdown>
|
||||
<a-button type="text" style="color: white;">
|
||||
<user-outlined />
|
||||
{{ userData?.real_name || userData?.username }}
|
||||
<down-outlined />
|
||||
<div v-else class="desktop-layout">
|
||||
<!-- 固定头部 -->
|
||||
<header class="header">
|
||||
<div class="header-content">
|
||||
<div class="logo">
|
||||
<a-button
|
||||
type="text"
|
||||
@click="settingsStore.toggleSidebar"
|
||||
class="menu-toggle"
|
||||
>
|
||||
<menu-unfold-outlined v-if="sidebarCollapsed" />
|
||||
<menu-fold-outlined v-else />
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="profile" @click="goToProfile">
|
||||
<user-outlined />
|
||||
个人中心
|
||||
</a-menu-item>
|
||||
<a-menu-item key="settings" @click="goToSettings">
|
||||
<setting-outlined />
|
||||
系统设置
|
||||
</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item key="logout" @click="handleLogout">
|
||||
<logout-outlined />
|
||||
退出登录
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<span class="logo-text">银行管理后台系统</span>
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<a-dropdown>
|
||||
<a-button type="text" class="user-button">
|
||||
<user-outlined />
|
||||
{{ userData?.real_name || userData?.username }}
|
||||
<down-outlined />
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="profile" @click="goToProfile">
|
||||
<user-outlined />
|
||||
个人中心
|
||||
</a-menu-item>
|
||||
<a-menu-item key="settings" @click="goToSettings">
|
||||
<setting-outlined />
|
||||
系统设置
|
||||
</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item key="logout" @click="handleLogout">
|
||||
<logout-outlined />
|
||||
退出登录
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</a-layout-header>
|
||||
</header>
|
||||
|
||||
<a-layout class="main-layout">
|
||||
<a-layout-sider
|
||||
class="sidebar"
|
||||
width="200"
|
||||
:collapsed="sidebarCollapsed"
|
||||
collapsible
|
||||
>
|
||||
<DynamicMenu :collapsed="sidebarCollapsed" />
|
||||
</a-layout-sider>
|
||||
|
||||
<a-layout class="content-layout">
|
||||
<a-layout-content class="main-content">
|
||||
<div class="content-wrapper">
|
||||
<router-view />
|
||||
</div>
|
||||
</a-layout-content>
|
||||
|
||||
<a-layout-footer class="footer">
|
||||
银行管理后台系统 ©2025
|
||||
</a-layout-footer>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
<!-- 侧边栏 -->
|
||||
<aside class="sidebar" :class="{ collapsed: sidebarCollapsed }">
|
||||
<DynamicMenu :collapsed="sidebarCollapsed" />
|
||||
</aside>
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<main class="main-content">
|
||||
<div class="content-wrapper">
|
||||
<router-view />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 页脚 -->
|
||||
<footer class="footer">
|
||||
银行管理后台系统 ©2025
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<router-view />
|
||||
@@ -156,57 +153,139 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 全局重置 */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#app {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 桌面端布局样式 */
|
||||
.desktop-layout {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"header header"
|
||||
"sidebar main";
|
||||
grid-template-columns: 200px 1fr;
|
||||
grid-template-rows: 64px 1fr;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main-layout {
|
||||
height: calc(100vh - 64px);
|
||||
overflow: hidden;
|
||||
.desktop-layout.collapsed {
|
||||
grid-template-columns: 80px 1fr;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
/* 头部样式 - 完全修复 */
|
||||
.header {
|
||||
grid-area: header;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
z-index: 1001;
|
||||
background: #001529;
|
||||
color: white;
|
||||
height: 64px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
width: 100vw;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #001529;
|
||||
color: white;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 24px;
|
||||
height: 64px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
color: white !important;
|
||||
margin-right: 8px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
line-height: 64px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 侧边栏样式 */
|
||||
.user-button {
|
||||
color: white !important;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
/* 侧边栏样式 - 完全修复 */
|
||||
.sidebar {
|
||||
grid-area: sidebar;
|
||||
position: fixed;
|
||||
top: 64px;
|
||||
left: 0;
|
||||
width: 200px;
|
||||
height: calc(100vh - 64px);
|
||||
background: #001529 !important;
|
||||
z-index: 999;
|
||||
background: #001529;
|
||||
z-index: 1000;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
transition: width 0.2s;
|
||||
}
|
||||
|
||||
.sidebar.collapsed {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.sidebar::-webkit-scrollbar {
|
||||
@@ -226,35 +305,48 @@ onUnmounted(() => {
|
||||
background: #40a9ff;
|
||||
}
|
||||
|
||||
/* 内容区域样式 */
|
||||
.content-layout {
|
||||
/* 主内容区域样式 - 完全修复 */
|
||||
.main-content {
|
||||
grid-area: main;
|
||||
margin-left: 200px;
|
||||
height: calc(100vh - 64px);
|
||||
transition: margin-left 0.2s;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
height: calc(100vh - 64px - 70px);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
background: #f0f2f5;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
transition: margin-left 0.2s;
|
||||
}
|
||||
|
||||
.desktop-layout.collapsed .main-content {
|
||||
margin-left: 80px;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 24px;
|
||||
min-height: 100%;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
min-height: calc(100vh - 64px - 70px);
|
||||
background: #f0f2f5;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 页脚样式 - 完全修复 */
|
||||
.footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 200px;
|
||||
right: 0;
|
||||
height: 70px;
|
||||
line-height: 70px;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
color: #666;
|
||||
z-index: 999;
|
||||
transition: left 0.2s;
|
||||
}
|
||||
|
||||
.desktop-layout.collapsed .footer {
|
||||
left: 80px;
|
||||
}
|
||||
|
||||
/* 侧边栏折叠时的样式 */
|
||||
|
||||
@@ -128,11 +128,103 @@ watch(
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 完全重置菜单样式 - 确保从顶部开始 */
|
||||
.ant-menu {
|
||||
border-right: none;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
height: 100% !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.ant-menu-root {
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
height: 100% !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.ant-menu-vertical {
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
height: 100% !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.ant-menu-inline {
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
height: 100% !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* 强制移除所有菜单容器的空白 */
|
||||
.ant-menu ul {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.ant-menu li {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.ant-menu li:first-child {
|
||||
margin-top: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
border-top: none !important;
|
||||
}
|
||||
|
||||
/* 菜单项样式重置 */
|
||||
.ant-menu-item {
|
||||
margin: 0 !important;
|
||||
padding: 0 16px !important;
|
||||
height: 48px !important;
|
||||
line-height: 48px !important;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
.ant-menu-item:first-child {
|
||||
margin-top: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
border-top: none !important;
|
||||
}
|
||||
|
||||
.ant-menu-submenu-title {
|
||||
margin: 0 !important;
|
||||
padding: 0 16px !important;
|
||||
height: 48px !important;
|
||||
line-height: 48px !important;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
/* 确保菜单项图标和文字正确对齐 */
|
||||
.ant-menu-item .anticon,
|
||||
.ant-menu-submenu-title .anticon {
|
||||
margin-right: 8px !important;
|
||||
font-size: 16px !important;
|
||||
flex-shrink: 0 !important;
|
||||
}
|
||||
|
||||
.ant-menu-item span,
|
||||
.ant-menu-submenu-title span {
|
||||
flex: 1 !important;
|
||||
white-space: nowrap !important;
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
}
|
||||
|
||||
.ant-menu::-webkit-scrollbar {
|
||||
@@ -158,6 +250,13 @@ watch(
|
||||
align-items: center;
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
margin: 0 !important;
|
||||
padding: 0 16px !important;
|
||||
}
|
||||
|
||||
.ant-menu-item:first-child,
|
||||
.ant-menu-submenu:first-child .ant-menu-submenu-title {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.ant-menu-item .anticon,
|
||||
|
||||
@@ -17,14 +17,22 @@ import {
|
||||
CreditCardOutlined,
|
||||
DesktopOutlined,
|
||||
TeamOutlined,
|
||||
UserSwitchOutlined
|
||||
UserSwitchOutlined,
|
||||
ProjectOutlined,
|
||||
ClockCircleOutlined,
|
||||
ToolOutlined,
|
||||
CheckCircleOutlined,
|
||||
SearchOutlined,
|
||||
DollarOutlined,
|
||||
BellOutlined,
|
||||
WarningOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
// 路由配置
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/dashboard'
|
||||
redirect: '/project-list',
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
@@ -37,14 +45,13 @@ const routes = [
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'Dashboard',
|
||||
component: () => import('@/views/Dashboard.vue'),
|
||||
path: '/test-projects',
|
||||
name: 'TestProjects',
|
||||
component: () => import('@/views/TestProjects.vue'),
|
||||
meta: {
|
||||
title: '仪表盘',
|
||||
icon: DashboardOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager', 'teller', 'user']
|
||||
title: '项目接口测试',
|
||||
requiresAuth: false,
|
||||
hideInMenu: true
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -53,7 +60,40 @@ const routes = [
|
||||
component: () => import('@/views/ProjectList.vue'),
|
||||
meta: {
|
||||
title: '项目清单',
|
||||
icon: FileTextOutlined,
|
||||
icon: ProjectOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager', 'teller']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/supervision-tasks',
|
||||
name: 'SupervisionTasks',
|
||||
component: () => import('@/views/SupervisionTasks.vue'),
|
||||
meta: {
|
||||
title: '监管任务',
|
||||
icon: ClockCircleOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager', 'teller']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/pending-installation',
|
||||
name: 'PendingInstallation',
|
||||
component: () => import('@/views/PendingInstallation.vue'),
|
||||
meta: {
|
||||
title: '待安装任务',
|
||||
icon: ToolOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager', 'teller']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/completed-supervision',
|
||||
name: 'CompletedSupervision',
|
||||
component: () => import('@/views/CompletedSupervision.vue'),
|
||||
meta: {
|
||||
title: '监管任务已结项',
|
||||
icon: CheckCircleOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager', 'teller']
|
||||
}
|
||||
@@ -64,62 +104,28 @@ const routes = [
|
||||
component: () => import('@/views/SystemCheck.vue'),
|
||||
meta: {
|
||||
title: '系统日检',
|
||||
icon: SafetyOutlined,
|
||||
icon: SearchOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/market-trends',
|
||||
name: 'MarketTrends',
|
||||
component: () => import('@/views/MarketTrends.vue'),
|
||||
meta: {
|
||||
title: '市场行情',
|
||||
icon: LineChartOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager', 'teller', 'user']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/users',
|
||||
name: 'Users',
|
||||
component: () => import('@/views/Users.vue'),
|
||||
meta: {
|
||||
title: '用户管理',
|
||||
icon: UserOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/accounts',
|
||||
name: 'Accounts',
|
||||
component: () => import('@/views/Accounts.vue'),
|
||||
meta: {
|
||||
title: '账户管理',
|
||||
icon: BankOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager', 'teller', 'user']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/transactions',
|
||||
name: 'Transactions',
|
||||
component: () => import('@/views/Transactions.vue'),
|
||||
meta: {
|
||||
title: '交易管理',
|
||||
icon: TransactionOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager', 'teller', 'user']
|
||||
}
|
||||
},
|
||||
// {
|
||||
// path: '/market-trends',
|
||||
// name: 'MarketTrends',
|
||||
// component: () => import('@/views/MarketTrends.vue'),
|
||||
// meta: {
|
||||
// title: '市场行情',
|
||||
// icon: BarChartOutlined,
|
||||
// requiresAuth: true,
|
||||
// roles: ['admin', 'manager', 'teller', 'user']
|
||||
// }
|
||||
// },
|
||||
{
|
||||
path: '/loan-management',
|
||||
name: 'LoanManagement',
|
||||
component: () => import('@/views/LoanManagement.vue'),
|
||||
meta: {
|
||||
title: '贷款管理',
|
||||
icon: CreditCardOutlined,
|
||||
icon: DollarOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager', 'teller']
|
||||
},
|
||||
@@ -166,15 +172,26 @@ const routes = [
|
||||
}
|
||||
]
|
||||
},
|
||||
// {
|
||||
// path: '/message-notification',
|
||||
// name: 'MessageNotification',
|
||||
// component: () => import('@/views/MessageNotification.vue'),
|
||||
// meta: {
|
||||
// title: '消息通知',
|
||||
// icon: BellOutlined,
|
||||
// requiresAuth: true,
|
||||
// roles: ['admin', 'manager', 'teller', 'user']
|
||||
// }
|
||||
// },
|
||||
{
|
||||
path: '/hardware-management',
|
||||
name: 'HardwareManagement',
|
||||
component: () => import('@/views/HardwareManagement.vue'),
|
||||
path: '/device-warning',
|
||||
name: 'DeviceWarning',
|
||||
component: () => import('@/views/DeviceWarning.vue'),
|
||||
meta: {
|
||||
title: '硬件管理',
|
||||
icon: DesktopOutlined,
|
||||
title: '设备预警',
|
||||
icon: WarningOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager']
|
||||
roles: ['admin', 'manager', 'teller', 'user']
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -188,17 +205,6 @@ const routes = [
|
||||
roles: ['admin', 'manager']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/reports',
|
||||
name: 'Reports',
|
||||
component: () => import('@/views/Reports.vue'),
|
||||
meta: {
|
||||
title: '报表统计',
|
||||
icon: BarChartOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager', 'teller']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/settings',
|
||||
name: 'Settings',
|
||||
|
||||
@@ -9,6 +9,189 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 强制重置所有元素的默认样式 */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 确保body和html没有默认空白 */
|
||||
html {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
overflow-x: hidden !important;
|
||||
box-sizing: border-box !important;
|
||||
font-size: 14px !important;
|
||||
line-height: 1.5715 !important;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
overflow-x: hidden !important;
|
||||
box-sizing: border-box !important;
|
||||
position: relative !important;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif !important;
|
||||
color: #262626 !important;
|
||||
background-color: #f0f2f5 !important;
|
||||
}
|
||||
|
||||
/* 确保#app容器没有空白 */
|
||||
#app {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
/* 强制修复Ant Design菜单空白问题 */
|
||||
.ant-menu,
|
||||
.ant-menu-root,
|
||||
.ant-menu-vertical,
|
||||
.ant-menu-inline {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.ant-menu-item,
|
||||
.ant-menu-submenu-title {
|
||||
margin: 0 !important;
|
||||
padding: 0 16px !important;
|
||||
}
|
||||
|
||||
.ant-menu-item:first-child,
|
||||
.ant-menu-submenu:first-child .ant-menu-submenu-title {
|
||||
margin-top: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
/* 确保侧边栏菜单从顶部开始 */
|
||||
.ant-layout-sider .ant-menu,
|
||||
.ant-layout-sider .ant-menu-root,
|
||||
.ant-layout-sider .ant-menu-vertical,
|
||||
.ant-layout-sider .ant-menu-inline {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/* 强制移除菜单容器的所有空白 */
|
||||
.ant-menu > li:first-child {
|
||||
margin-top: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
.ant-menu .ant-menu-item:first-child {
|
||||
margin-top: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
/* 针对侧边栏的特殊处理 */
|
||||
.ant-layout-sider .ant-menu > li:first-child {
|
||||
margin-top: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
.ant-layout-sider .ant-menu .ant-menu-item:first-child {
|
||||
margin-top: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
/* 最终强制重置 - 确保没有任何空白 */
|
||||
.ant-layout-sider {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
border: none !important;
|
||||
background: #001529 !important;
|
||||
}
|
||||
|
||||
.ant-layout-sider .ant-menu {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.ant-layout-sider .ant-menu ul {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.ant-layout-sider .ant-menu li {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.ant-layout-sider .ant-menu li:first-child {
|
||||
margin-top: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
border-top: none !important;
|
||||
}
|
||||
|
||||
/* 强制重置菜单项样式 */
|
||||
.ant-layout-sider .ant-menu-item {
|
||||
margin: 0 !important;
|
||||
padding: 0 16px !important;
|
||||
height: 48px !important;
|
||||
line-height: 48px !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.ant-layout-sider .ant-menu-item:first-child {
|
||||
margin-top: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
border-top: none !important;
|
||||
}
|
||||
|
||||
.ant-layout-sider .ant-menu-submenu-title {
|
||||
margin: 0 !important;
|
||||
padding: 0 16px !important;
|
||||
height: 48px !important;
|
||||
line-height: 48px !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
/* 确保头部完全显示 */
|
||||
.ant-layout-header {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
line-height: 64px !important;
|
||||
height: 64px !important;
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
z-index: 1001 !important;
|
||||
width: 100vw !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
/* 强制重置所有可能的头部样式 */
|
||||
.ant-layout-header * {
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
/* 确保头部内容不被截断 */
|
||||
.ant-layout-header .logo,
|
||||
.ant-layout-header .user-info {
|
||||
line-height: 64px !important;
|
||||
height: 64px !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
@@ -654,6 +654,147 @@ export const api = {
|
||||
async getStats(params = {}) {
|
||||
return api.get('/transactions/stats', { params })
|
||||
}
|
||||
},
|
||||
|
||||
// 项目清单API
|
||||
projects: {
|
||||
/**
|
||||
* 获取项目列表
|
||||
* @param {Object} params - 查询参数
|
||||
* @returns {Promise} 项目列表
|
||||
*/
|
||||
async getList(params = {}) {
|
||||
return api.get('/projects', { params })
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取项目详情
|
||||
* @param {number} id - 项目ID
|
||||
* @returns {Promise} 项目详情
|
||||
*/
|
||||
async getById(id) {
|
||||
return api.get(`/projects/${id}`)
|
||||
},
|
||||
|
||||
/**
|
||||
* 创建项目
|
||||
* @param {Object} data - 项目数据
|
||||
* @returns {Promise} 创建结果
|
||||
*/
|
||||
async create(data) {
|
||||
return api.post('/projects', data)
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新项目
|
||||
* @param {number} id - 项目ID
|
||||
* @param {Object} data - 项目数据
|
||||
* @returns {Promise} 更新结果
|
||||
*/
|
||||
async update(id, data) {
|
||||
return api.put(`/projects/${id}`, data)
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除项目
|
||||
* @param {number} id - 项目ID
|
||||
* @returns {Promise} 删除结果
|
||||
*/
|
||||
async delete(id) {
|
||||
return api.delete(`/projects/${id}`)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取项目统计
|
||||
* @returns {Promise} 统计数据
|
||||
*/
|
||||
async getStats() {
|
||||
return api.get('/projects/stats')
|
||||
},
|
||||
|
||||
/**
|
||||
* 批量更新项目状态
|
||||
* @param {Object} data - 批量更新数据
|
||||
* @returns {Promise} 更新结果
|
||||
*/
|
||||
async batchUpdateStatus(data) {
|
||||
return api.put('/projects/batch/status', data)
|
||||
}
|
||||
},
|
||||
|
||||
// 监管任务API
|
||||
supervisionTasks: {
|
||||
/**
|
||||
* 获取监管任务列表
|
||||
* @param {Object} params - 查询参数
|
||||
* @returns {Promise} 监管任务列表
|
||||
*/
|
||||
async getList(params = {}) {
|
||||
return api.get('/supervision-tasks', { params })
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取监管任务详情
|
||||
* @param {number} id - 监管任务ID
|
||||
* @returns {Promise} 监管任务详情
|
||||
*/
|
||||
async getById(id) {
|
||||
return api.get(`/supervision-tasks/${id}`)
|
||||
},
|
||||
|
||||
/**
|
||||
* 创建监管任务
|
||||
* @param {Object} data - 监管任务数据
|
||||
* @returns {Promise} 创建结果
|
||||
*/
|
||||
async create(data) {
|
||||
return api.post('/supervision-tasks', data)
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新监管任务
|
||||
* @param {number} id - 监管任务ID
|
||||
* @param {Object} data - 监管任务数据
|
||||
* @returns {Promise} 更新结果
|
||||
*/
|
||||
async update(id, data) {
|
||||
return api.put(`/supervision-tasks/${id}`, data)
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除监管任务
|
||||
* @param {number} id - 监管任务ID
|
||||
* @returns {Promise} 删除结果
|
||||
*/
|
||||
async delete(id) {
|
||||
return api.delete(`/supervision-tasks/${id}`)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取监管任务统计
|
||||
* @returns {Promise} 统计数据
|
||||
*/
|
||||
async getStats() {
|
||||
return api.get('/supervision-tasks/stats')
|
||||
},
|
||||
|
||||
/**
|
||||
* 批量更新监管任务状态
|
||||
* @param {Object} data - 批量更新数据
|
||||
* @returns {Promise} 更新结果
|
||||
*/
|
||||
async batchUpdateStatus(data) {
|
||||
return api.put('/supervision-tasks/batch/status', data)
|
||||
},
|
||||
|
||||
/**
|
||||
* 批量删除监管任务
|
||||
* @param {Object} data - 批量删除数据
|
||||
* @returns {Promise} 删除结果
|
||||
*/
|
||||
async batchDelete(data) {
|
||||
return api.delete('/supervision-tasks/batch', { data })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
336
bank-frontend/src/views/CompletedSupervision.vue
Normal file
336
bank-frontend/src/views/CompletedSupervision.vue
Normal file
@@ -0,0 +1,336 @@
|
||||
<template>
|
||||
<div class="completed-supervision-page">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header">
|
||||
<h1>监管任务结项</h1>
|
||||
<div class="header-actions">
|
||||
<a-button type="primary" @click="handleBatchImport">
|
||||
<upload-outlined />
|
||||
批量导入
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索筛选区域 -->
|
||||
<div class="search-section">
|
||||
<a-row :gutter="16" align="middle">
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="searchForm.contractNumber"
|
||||
placeholder="合同编号"
|
||||
allow-clear
|
||||
style="width: 100%"
|
||||
>
|
||||
<a-select-option value="LOAN2024001">LOAN2024001</a-select-option>
|
||||
<a-select-option value="LOAN2024002">LOAN2024002</a-select-option>
|
||||
<a-select-option value="LOAN2024003">LOAN2024003</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-input
|
||||
v-model:value="searchForm.keyword"
|
||||
placeholder="请输入关键字"
|
||||
allow-clear
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<search-outlined />
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button @click="handleReset">重置</a-button>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<div class="table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredTasks"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
row-key="id"
|
||||
:locale="{ emptyText: '暂无数据' }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'settlementStatus'">
|
||||
<a-tag :color="getSettlementStatusColor(record.settlementStatus)">
|
||||
{{ getSettlementStatusName(record.settlementStatus) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="viewTask(record)">查看</a-button>
|
||||
<a-button type="link" size="small" @click="editTask(record)">编辑</a-button>
|
||||
<a-button type="link" size="small" @click="exportTask(record)">导出</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { UploadOutlined, SearchOutlined } from '@ant-design/icons-vue'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const loading = ref(false)
|
||||
const tasks = ref([])
|
||||
|
||||
const searchForm = reactive({
|
||||
contractNumber: undefined,
|
||||
keyword: '',
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{ title: '申请单号', dataIndex: 'applicationNumber', key: 'applicationNumber', sorter: true },
|
||||
{ title: '放款合同编号', dataIndex: 'contractNumber', key: 'contractNumber', sorter: true },
|
||||
{ title: '产品名称', dataIndex: 'productName', key: 'productName' },
|
||||
{ title: '客户姓名', dataIndex: 'customerName', key: 'customerName' },
|
||||
{ title: '证件类型', dataIndex: 'idType', key: 'idType' },
|
||||
{ title: '证件号码', dataIndex: 'idNumber', key: 'idNumber' },
|
||||
{ title: '养殖生资种类', dataIndex: 'assetType', key: 'assetType' },
|
||||
{ title: '监管生资数量', dataIndex: 'assetQuantity', key: 'assetQuantity', sorter: true },
|
||||
{ title: '总还款期数', dataIndex: 'totalRepaymentPeriods', key: 'totalRepaymentPeriods', sorter: true },
|
||||
{ title: '结清状态', dataIndex: 'settlementStatus', key: 'settlementStatus', filters: [
|
||||
{ text: '已结清', value: 'settled' },
|
||||
{ text: '未结清', value: 'unsettled' },
|
||||
{ text: '部分结清', value: 'partial' },
|
||||
]},
|
||||
{ title: '结清日期', dataIndex: 'settlementDate', key: 'settlementDate', sorter: true },
|
||||
{ title: '结清任务导入时间', dataIndex: 'importTime', key: 'importTime', sorter: true },
|
||||
{ title: '操作', key: 'action', fixed: 'right', width: 180 },
|
||||
]
|
||||
|
||||
// 模拟数据
|
||||
const mockTasks = [
|
||||
{
|
||||
id: 'S001',
|
||||
applicationNumber: 'APP2024001',
|
||||
contractNumber: 'LOAN2024001',
|
||||
productName: '生猪养殖贷',
|
||||
customerName: '张三',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4401XXXXXXXXXXXXXX',
|
||||
assetType: '生猪',
|
||||
assetQuantity: 500,
|
||||
totalRepaymentPeriods: 12,
|
||||
settlementStatus: 'settled',
|
||||
settlementDate: '2024-01-15',
|
||||
importTime: '2024-01-15 10:30:00',
|
||||
},
|
||||
{
|
||||
id: 'S002',
|
||||
applicationNumber: 'APP2024002',
|
||||
contractNumber: 'LOAN2024002',
|
||||
productName: '肉牛养殖贷',
|
||||
customerName: '李四',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4402XXXXXXXXXXXXXX',
|
||||
assetType: '肉牛',
|
||||
assetQuantity: 150,
|
||||
totalRepaymentPeriods: 24,
|
||||
settlementStatus: 'partial',
|
||||
settlementDate: '2024-01-20',
|
||||
importTime: '2024-01-20 14:20:00',
|
||||
},
|
||||
{
|
||||
id: 'S003',
|
||||
applicationNumber: 'APP2024003',
|
||||
contractNumber: 'LOAN2024003',
|
||||
productName: '蛋鸡养殖贷',
|
||||
customerName: '王五',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4403XXXXXXXXXXXXXX',
|
||||
assetType: '蛋鸡',
|
||||
assetQuantity: 10000,
|
||||
totalRepaymentPeriods: 18,
|
||||
settlementStatus: 'unsettled',
|
||||
settlementDate: null,
|
||||
importTime: '2024-01-10 09:15:00',
|
||||
},
|
||||
]
|
||||
|
||||
const fetchTasks = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 实际项目中这里会调用API获取数据
|
||||
// const response = await api.completedSupervision.getList({
|
||||
// page: pagination.current,
|
||||
// pageSize: pagination.pageSize,
|
||||
// ...searchForm,
|
||||
// })
|
||||
|
||||
// 使用模拟数据
|
||||
tasks.value = mockTasks.map(task => ({
|
||||
...task,
|
||||
settlementDate: task.settlementDate ? dayjs(task.settlementDate) : null,
|
||||
importTime: dayjs(task.importTime),
|
||||
}))
|
||||
pagination.total = mockTasks.length
|
||||
} catch (error) {
|
||||
console.error('获取结项任务失败:', error)
|
||||
message.error('获取结项任务失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const filteredTasks = computed(() => {
|
||||
let result = tasks.value
|
||||
|
||||
if (searchForm.contractNumber) {
|
||||
result = result.filter(task => task.contractNumber === searchForm.contractNumber)
|
||||
}
|
||||
|
||||
if (searchForm.keyword) {
|
||||
result = result.filter(task =>
|
||||
task.applicationNumber.toLowerCase().includes(searchForm.keyword.toLowerCase()) ||
|
||||
task.customerName.toLowerCase().includes(searchForm.keyword.toLowerCase()) ||
|
||||
task.productName.toLowerCase().includes(searchForm.keyword.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
fetchTasks()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
searchForm.contractNumber = undefined
|
||||
searchForm.keyword = ''
|
||||
pagination.current = 1
|
||||
fetchTasks()
|
||||
}
|
||||
|
||||
const handleBatchImport = () => {
|
||||
message.info('批量导入功能开发中...')
|
||||
}
|
||||
|
||||
const handleTableChange = (pag, filters, sorter) => {
|
||||
pagination.current = pag.current
|
||||
pagination.pageSize = pag.pageSize
|
||||
fetchTasks()
|
||||
}
|
||||
|
||||
const getSettlementStatusColor = (status) => {
|
||||
const colors = {
|
||||
'settled': 'green',
|
||||
'unsettled': 'red',
|
||||
'partial': 'orange',
|
||||
}
|
||||
return colors[status] || 'default'
|
||||
}
|
||||
|
||||
const getSettlementStatusName = (status) => {
|
||||
const names = {
|
||||
'settled': '已结清',
|
||||
'unsettled': '未结清',
|
||||
'partial': '部分结清',
|
||||
}
|
||||
return names[status] || status
|
||||
}
|
||||
|
||||
const viewTask = (record) => {
|
||||
message.info(`查看任务: ${record.applicationNumber}`)
|
||||
}
|
||||
|
||||
const editTask = (record) => {
|
||||
message.info(`编辑任务: ${record.applicationNumber}`)
|
||||
}
|
||||
|
||||
const exportTask = (record) => {
|
||||
message.success(`导出任务: ${record.applicationNumber}`)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchTasks()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.completed-supervision-page {
|
||||
padding: 0;
|
||||
background: #f0f2f5;
|
||||
min-height: calc(100vh - 134px);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
padding: 24px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.search-section .ant-col {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-section .ant-col:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
421
bank-frontend/src/views/DeviceWarning.vue
Normal file
421
bank-frontend/src/views/DeviceWarning.vue
Normal file
@@ -0,0 +1,421 @@
|
||||
<template>
|
||||
<div class="device-warning">
|
||||
<div class="page-header">
|
||||
<h1>设备预警</h1>
|
||||
<p>监控和管理银行设备运行状态预警</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 预警统计 -->
|
||||
<div class="overview-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="总预警数"
|
||||
:value="warningStats.total"
|
||||
:value-style="{ color: '#ff4d4f' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="严重预警"
|
||||
:value="warningStats.critical"
|
||||
:value-style="{ color: '#ff4d4f' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="一般预警"
|
||||
:value="warningStats.warning"
|
||||
:value-style="{ color: '#faad14' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="已处理"
|
||||
:value="warningStats.resolved"
|
||||
:value-style="{ color: '#52c41a' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="search-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-input-search
|
||||
v-model:value="searchText"
|
||||
placeholder="搜索设备名称"
|
||||
enter-button="搜索"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="levelFilter"
|
||||
placeholder="预警级别"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="critical">严重</a-select-option>
|
||||
<a-select-option value="warning">一般</a-select-option>
|
||||
<a-select-option value="info">信息</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="statusFilter"
|
||||
placeholder="处理状态"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="pending">待处理</a-select-option>
|
||||
<a-select-option value="processing">处理中</a-select-option>
|
||||
<a-select-option value="resolved">已解决</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="refreshWarnings">
|
||||
<reload-outlined /> 刷新
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 预警列表 -->
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredWarnings"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
row-key="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'level'">
|
||||
<a-tag :color="getLevelColor(record.level)">
|
||||
{{ getLevelName(record.level) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusName(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="viewWarning(record)">查看</a-button>
|
||||
<a-button type="link" size="small" @click="handleWarning(record.id)">处理</a-button>
|
||||
<a-button type="link" size="small" @click="resolveWarning(record.id)">解决</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 预警详情对话框 -->
|
||||
<a-modal
|
||||
v-model:open="detailModalVisible"
|
||||
title="预警详情"
|
||||
width="800px"
|
||||
:footer="null"
|
||||
>
|
||||
<div v-if="selectedWarning" class="warning-detail">
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item label="设备名称">
|
||||
{{ selectedWarning.deviceName }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="预警级别">
|
||||
<a-tag :color="getLevelColor(selectedWarning.level)">
|
||||
{{ getLevelName(selectedWarning.level) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="预警类型">
|
||||
{{ selectedWarning.type }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="处理状态">
|
||||
<a-tag :color="getStatusColor(selectedWarning.status)">
|
||||
{{ getStatusName(selectedWarning.status) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="发生时间">
|
||||
{{ selectedWarning.occurTime }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="处理人员">
|
||||
{{ selectedWarning.handler || '未分配' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="预警描述" :span="2">
|
||||
{{ selectedWarning.description }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { ReloadOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const searchText = ref('')
|
||||
const levelFilter = ref(undefined)
|
||||
const statusFilter = ref(undefined)
|
||||
const detailModalVisible = ref(false)
|
||||
const selectedWarning = ref(null)
|
||||
|
||||
// 预警统计
|
||||
const warningStats = ref({
|
||||
total: 23,
|
||||
critical: 5,
|
||||
warning: 12,
|
||||
resolved: 6
|
||||
})
|
||||
|
||||
// 分页配置
|
||||
const pagination = ref({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
|
||||
})
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '设备名称',
|
||||
dataIndex: 'deviceName',
|
||||
key: 'deviceName',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '预警级别',
|
||||
dataIndex: 'level',
|
||||
key: 'level',
|
||||
filters: [
|
||||
{ text: '严重', value: 'critical' },
|
||||
{ text: '一般', value: 'warning' },
|
||||
{ text: '信息', value: 'info' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '预警类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
},
|
||||
{
|
||||
title: '处理状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
filters: [
|
||||
{ text: '待处理', value: 'pending' },
|
||||
{ text: '处理中', value: 'processing' },
|
||||
{ text: '已解决', value: 'resolved' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '发生时间',
|
||||
dataIndex: 'occurTime',
|
||||
key: 'occurTime',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '处理人员',
|
||||
dataIndex: 'handler',
|
||||
key: 'handler',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
},
|
||||
]
|
||||
|
||||
// 模拟预警数据
|
||||
const warnings = ref([
|
||||
{
|
||||
id: 1,
|
||||
deviceName: 'ATM-001',
|
||||
level: 'critical',
|
||||
type: '硬件故障',
|
||||
status: 'pending',
|
||||
occurTime: '2024-01-22 14:30:25',
|
||||
handler: null,
|
||||
description: 'ATM机读卡器故障,无法正常读取银行卡'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
deviceName: 'POS-002',
|
||||
level: 'warning',
|
||||
type: '网络异常',
|
||||
status: 'processing',
|
||||
occurTime: '2024-01-22 13:15:10',
|
||||
handler: '张三',
|
||||
description: 'POS机网络连接不稳定,偶尔出现断线'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
deviceName: 'SERVER-003',
|
||||
level: 'critical',
|
||||
type: '系统异常',
|
||||
status: 'resolved',
|
||||
occurTime: '2024-01-22 10:20:30',
|
||||
handler: '李四',
|
||||
description: '服务器CPU使用率过高,已重启解决'
|
||||
}
|
||||
])
|
||||
|
||||
// 计算属性
|
||||
const filteredWarnings = computed(() => {
|
||||
let result = warnings.value
|
||||
|
||||
if (searchText.value) {
|
||||
result = result.filter(warning =>
|
||||
warning.deviceName.toLowerCase().includes(searchText.value.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
if (levelFilter.value) {
|
||||
result = result.filter(warning => warning.level === levelFilter.value)
|
||||
}
|
||||
|
||||
if (statusFilter.value) {
|
||||
result = result.filter(warning => warning.status === statusFilter.value)
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
// 方法
|
||||
const getLevelColor = (level) => {
|
||||
const colors = {
|
||||
'critical': 'red',
|
||||
'warning': 'orange',
|
||||
'info': 'blue'
|
||||
}
|
||||
return colors[level] || 'default'
|
||||
}
|
||||
|
||||
const getLevelName = (level) => {
|
||||
const names = {
|
||||
'critical': '严重',
|
||||
'warning': '一般',
|
||||
'info': '信息'
|
||||
}
|
||||
return names[level] || level
|
||||
}
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
'pending': 'red',
|
||||
'processing': 'orange',
|
||||
'resolved': 'green'
|
||||
}
|
||||
return colors[status] || 'default'
|
||||
}
|
||||
|
||||
const getStatusName = (status) => {
|
||||
const names = {
|
||||
'pending': '待处理',
|
||||
'processing': '处理中',
|
||||
'resolved': '已解决'
|
||||
}
|
||||
return names[status] || status
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.value.current = 1
|
||||
}
|
||||
|
||||
const handleFilter = () => {
|
||||
pagination.value.current = 1
|
||||
}
|
||||
|
||||
const handleTableChange = (pag) => {
|
||||
pagination.value = pag
|
||||
}
|
||||
|
||||
const refreshWarnings = () => {
|
||||
loading.value = true
|
||||
setTimeout(() => {
|
||||
loading.value = false
|
||||
message.success('预警信息已刷新')
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const viewWarning = (warning) => {
|
||||
selectedWarning.value = warning
|
||||
detailModalVisible.value = true
|
||||
}
|
||||
|
||||
const handleWarning = (id) => {
|
||||
const warning = warnings.value.find(w => w.id === id)
|
||||
if (warning) {
|
||||
warning.status = 'processing'
|
||||
warning.handler = '当前用户'
|
||||
message.success('预警已开始处理')
|
||||
}
|
||||
}
|
||||
|
||||
const resolveWarning = (id) => {
|
||||
const warning = warnings.value.find(w => w.id === id)
|
||||
if (warning) {
|
||||
warning.status = 'resolved'
|
||||
message.success('预警已解决')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
pagination.value.total = warnings.value.length
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.device-warning {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.overview-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
margin-bottom: 24px;
|
||||
padding: 16px;
|
||||
background: #fafafa;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.warning-detail {
|
||||
padding: 16px 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,168 +1,64 @@
|
||||
<template>
|
||||
<div class="employee-management">
|
||||
<div class="employee-management-page">
|
||||
<div class="page-header">
|
||||
<h1>员工管理</h1>
|
||||
<p>管理和维护银行员工信息</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 员工概览 -->
|
||||
<div class="overview-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="员工总数"
|
||||
:value="employeeStats.total"
|
||||
:value-style="{ color: '#1890ff' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="在职员工"
|
||||
:value="employeeStats.active"
|
||||
:value-style="{ color: '#52c41a' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="离职员工"
|
||||
:value="employeeStats.inactive"
|
||||
:value-style="{ color: '#ff4d4f' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="本月入职"
|
||||
:value="employeeStats.newHires"
|
||||
:value-style="{ color: '#faad14' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<div class="search-filter-section">
|
||||
<a-row :gutter="16" align="middle">
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="handleAddEmployee">
|
||||
<plus-outlined /> 新增人员
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-input
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入员工姓名"
|
||||
allow-clear
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<search-outlined /> 搜索
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button @click="handleReset">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="search-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-input-search
|
||||
v-model:value="searchText"
|
||||
placeholder="搜索员工姓名或工号"
|
||||
enter-button="搜索"
|
||||
@search="handleSearch"
|
||||
<div class="employees-table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredEmployees"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-switch
|
||||
v-model:checked="record.status"
|
||||
checked-children="启用"
|
||||
un-checked-children="禁用"
|
||||
@change="handleToggleStatus(record)"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="statusFilter"
|
||||
placeholder="员工状态"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="active">在职</a-select-option>
|
||||
<a-select-option value="inactive">离职</a-select-option>
|
||||
<a-select-option value="suspended">停职</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="departmentFilter"
|
||||
placeholder="部门"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="admin">行政部</a-select-option>
|
||||
<a-select-option value="finance">财务部</a-select-option>
|
||||
<a-select-option value="it">技术部</a-select-option>
|
||||
<a-select-option value="hr">人事部</a-select-option>
|
||||
<a-select-option value="sales">销售部</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="positionFilter"
|
||||
placeholder="职位"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="manager">经理</a-select-option>
|
||||
<a-select-option value="supervisor">主管</a-select-option>
|
||||
<a-select-option value="staff">员工</a-select-option>
|
||||
<a-select-option value="intern">实习生</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleAddEmployee">
|
||||
<PlusOutlined />
|
||||
添加员工
|
||||
<a-button type="link" size="small" @click="handleResetPassword(record)">
|
||||
重设密码
|
||||
</a-button>
|
||||
<a-button @click="handleExport">
|
||||
<DownloadOutlined />
|
||||
导出
|
||||
<a-button type="link" size="small" @click="handleEdit(record)">
|
||||
编辑
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 员工列表 -->
|
||||
<div class="table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredEmployees"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'department'">
|
||||
<a-tag :color="getDepartmentColor(record.department)">
|
||||
{{ getDepartmentText(record.department) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'position'">
|
||||
{{ getPositionText(record.position) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'avatar'">
|
||||
<a-avatar :src="record.avatar" :size="32">
|
||||
{{ record.name.charAt(0) }}
|
||||
</a-avatar>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleView(record)">
|
||||
查看
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="handleEdit(record)">
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleToggleStatus(record)"
|
||||
:danger="record.status === 'active'"
|
||||
>
|
||||
{{ record.status === 'active' ? '停职' : '复职' }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 员工详情模态框 -->
|
||||
@@ -266,26 +162,15 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { PlusOutlined, DownloadOutlined } from '@ant-design/icons-vue'
|
||||
import { PlusOutlined, SearchOutlined } from '@ant-design/icons-vue'
|
||||
import { api } from '@/utils/api'
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const searchText = ref('')
|
||||
const statusFilter = ref(undefined)
|
||||
const departmentFilter = ref(undefined)
|
||||
const positionFilter = ref(undefined)
|
||||
const detailModalVisible = ref(false)
|
||||
const selectedEmployee = ref(null)
|
||||
|
||||
// 员工统计
|
||||
const employeeStats = ref({
|
||||
total: 156,
|
||||
active: 142,
|
||||
inactive: 14,
|
||||
newHires: 8
|
||||
})
|
||||
|
||||
// 分页配置
|
||||
const pagination = ref({
|
||||
current: 1,
|
||||
@@ -293,58 +178,45 @@ const pagination = ref({
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
|
||||
showTotal: (total) => `共${total}条`
|
||||
})
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '头像',
|
||||
dataIndex: 'avatar',
|
||||
key: 'avatar',
|
||||
width: 80
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
key: 'index',
|
||||
width: 80,
|
||||
customRender: ({ index }) => index + 1
|
||||
},
|
||||
{
|
||||
title: '姓名',
|
||||
title: '员工姓名',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '工号',
|
||||
dataIndex: 'employeeId',
|
||||
key: 'employeeId',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '部门',
|
||||
dataIndex: 'department',
|
||||
key: 'department',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '职位',
|
||||
dataIndex: 'position',
|
||||
key: 'position',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
title: '联系电话',
|
||||
dataIndex: 'phone',
|
||||
key: 'phone',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '贷款专员',
|
||||
dataIndex: 'isLoanSpecialist',
|
||||
key: 'isLoanSpecialist',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '入职日期',
|
||||
dataIndex: 'hireDate',
|
||||
key: 'hireDate',
|
||||
width: 120
|
||||
title: '账号状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 120,
|
||||
filters: [
|
||||
{ text: '启用', value: true },
|
||||
{ text: '禁用', value: false }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
@@ -361,21 +233,22 @@ const employees = ref([])
|
||||
const mockEmployees = [
|
||||
{
|
||||
id: 1,
|
||||
name: '张三',
|
||||
name: '刘超',
|
||||
phone: '150****1368',
|
||||
isLoanSpecialist: '否',
|
||||
status: true,
|
||||
employeeId: 'EMP001',
|
||||
gender: '男',
|
||||
age: 28,
|
||||
phone: '13800138000',
|
||||
email: 'zhangsan@bank.com',
|
||||
email: 'liuchao@bank.com',
|
||||
idCard: '110101199001011234',
|
||||
hireDate: '2020-03-15',
|
||||
department: 'admin',
|
||||
position: 'manager',
|
||||
status: 'active',
|
||||
supervisor: '李总',
|
||||
salaryLevel: 'L5',
|
||||
workLocation: '总行',
|
||||
emergencyContact: '张四',
|
||||
emergencyContact: '刘四',
|
||||
emergencyPhone: '13900139000',
|
||||
bio: '具有5年银行管理经验,擅长团队管理和业务规划',
|
||||
avatar: null,
|
||||
@@ -387,76 +260,6 @@ const mockEmployees = [
|
||||
startDate: '2020-03-15',
|
||||
endDate: null,
|
||||
description: '负责行政部日常管理工作'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '行政专员',
|
||||
company: '某银行',
|
||||
startDate: '2018-06-01',
|
||||
endDate: '2020-03-14',
|
||||
description: '负责行政事务处理'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '李四',
|
||||
employeeId: 'EMP002',
|
||||
gender: '女',
|
||||
age: 25,
|
||||
phone: '13900139000',
|
||||
email: 'lisi@bank.com',
|
||||
idCard: '110101199002021234',
|
||||
hireDate: '2021-07-01',
|
||||
department: 'finance',
|
||||
position: 'staff',
|
||||
status: 'active',
|
||||
supervisor: '王经理',
|
||||
salaryLevel: 'L3',
|
||||
workLocation: '分行',
|
||||
emergencyContact: '李五',
|
||||
emergencyPhone: '13700137000',
|
||||
bio: '财务专业毕业,具有3年财务工作经验',
|
||||
avatar: null,
|
||||
experience: [
|
||||
{
|
||||
id: 1,
|
||||
title: '财务专员',
|
||||
company: '某银行',
|
||||
startDate: '2021-07-01',
|
||||
endDate: null,
|
||||
description: '负责财务核算和报表编制'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '王五',
|
||||
employeeId: 'EMP003',
|
||||
gender: '男',
|
||||
age: 32,
|
||||
phone: '13700137000',
|
||||
email: 'wangwu@bank.com',
|
||||
idCard: '110101199003031234',
|
||||
hireDate: '2019-01-10',
|
||||
department: 'it',
|
||||
position: 'supervisor',
|
||||
status: 'inactive',
|
||||
supervisor: '赵总',
|
||||
salaryLevel: 'L4',
|
||||
workLocation: '总行',
|
||||
emergencyContact: '王六',
|
||||
emergencyPhone: '13600136000',
|
||||
bio: '计算机专业,具有8年IT工作经验,擅长系统开发',
|
||||
avatar: null,
|
||||
experience: [
|
||||
{
|
||||
id: 1,
|
||||
title: '技术主管',
|
||||
company: '某银行',
|
||||
startDate: '2019-01-10',
|
||||
endDate: '2024-01-15',
|
||||
description: '负责技术团队管理和系统开发'
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -468,46 +271,24 @@ const filteredEmployees = computed(() => {
|
||||
|
||||
if (searchText.value) {
|
||||
result = result.filter(employee =>
|
||||
employee.name.toLowerCase().includes(searchText.value.toLowerCase()) ||
|
||||
employee.employeeId.toLowerCase().includes(searchText.value.toLowerCase())
|
||||
employee.name.toLowerCase().includes(searchText.value.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
if (statusFilter.value) {
|
||||
result = result.filter(employee => employee.status === statusFilter.value)
|
||||
}
|
||||
|
||||
if (departmentFilter.value) {
|
||||
result = result.filter(employee => employee.department === departmentFilter.value)
|
||||
}
|
||||
|
||||
if (positionFilter.value) {
|
||||
result = result.filter(employee => employee.position === positionFilter.value)
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
// 方法
|
||||
const handleAddEmployee = () => {
|
||||
message.info('新增人员功能开发中...')
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
// 搜索逻辑已在计算属性中处理
|
||||
}
|
||||
|
||||
const handleFilter = () => {
|
||||
// 筛选逻辑已在计算属性中处理
|
||||
}
|
||||
|
||||
const handleTableChange = (pag) => {
|
||||
pagination.value.current = pag.current
|
||||
pagination.value.pageSize = pag.pageSize
|
||||
}
|
||||
|
||||
const handleAddEmployee = () => {
|
||||
message.info('添加员工功能开发中...')
|
||||
}
|
||||
|
||||
const handleExport = () => {
|
||||
message.info('导出功能开发中...')
|
||||
const handleReset = () => {
|
||||
searchText.value = ''
|
||||
}
|
||||
|
||||
const handleView = (record) => {
|
||||
@@ -519,10 +300,13 @@ const handleEdit = (record) => {
|
||||
message.info(`编辑员工: ${record.name}`)
|
||||
}
|
||||
|
||||
const handleResetPassword = (record) => {
|
||||
message.info(`重设密码: ${record.name}`)
|
||||
}
|
||||
|
||||
const handleToggleStatus = (record) => {
|
||||
const newStatus = record.status === 'active' ? 'inactive' : 'active'
|
||||
record.status = newStatus
|
||||
message.success(`员工已${newStatus === 'active' ? '复职' : '停职'}`)
|
||||
record.status = !record.status
|
||||
message.success(`员工账号已${record.status ? '启用' : '禁用'}`)
|
||||
}
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
@@ -583,9 +367,6 @@ const fetchEmployees = async (params = {}) => {
|
||||
page: pagination.value.current,
|
||||
limit: pagination.value.pageSize,
|
||||
search: searchText.value,
|
||||
status: statusFilter.value,
|
||||
department: departmentFilter.value,
|
||||
position: positionFilter.value,
|
||||
...params
|
||||
})
|
||||
|
||||
@@ -609,32 +390,6 @@ const fetchEmployees = async (params = {}) => {
|
||||
}
|
||||
}
|
||||
|
||||
const fetchEmployeeStats = async () => {
|
||||
try {
|
||||
const response = await api.employees.getStats()
|
||||
if (response.success) {
|
||||
employeeStats.value = {
|
||||
total: response.data.total || 0,
|
||||
active: response.data.active || 0,
|
||||
inactive: response.data.inactive || 0,
|
||||
newHires: 0 // 这个需要单独计算
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取员工统计失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.value.current = 1
|
||||
fetchEmployees()
|
||||
}
|
||||
|
||||
const handleFilter = () => {
|
||||
pagination.value.current = 1
|
||||
fetchEmployees()
|
||||
}
|
||||
|
||||
const handleTableChange = (paginationInfo) => {
|
||||
pagination.value = paginationInfo
|
||||
fetchEmployees()
|
||||
@@ -643,49 +398,47 @@ const handleTableChange = (paginationInfo) => {
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
fetchEmployees()
|
||||
fetchEmployeeStats()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.employee-management {
|
||||
.employee-management-page {
|
||||
padding: 24px;
|
||||
background-color: #f0f2f5;
|
||||
min-height: calc(100vh - 134px);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
font-size: 28px;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
.search-filter-section {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.overview-section {
|
||||
border-radius: 8px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.search-section {
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
margin-top: 16px;
|
||||
.employees-table-section {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.employee-detail {
|
||||
@@ -756,4 +509,49 @@ onMounted(() => {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.ant-table-thead > tr > th) {
|
||||
background-color: #fafafa;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr > td) {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr:hover > td) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
:deep(.ant-btn-link) {
|
||||
padding: 4px 8px;
|
||||
height: auto;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 分页样式 */
|
||||
:deep(.ant-pagination) {
|
||||
margin-top: 16px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.search-filter-section .ant-col {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-filter-section .ant-col:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
397
bank-frontend/src/views/MessageNotification.vue
Normal file
397
bank-frontend/src/views/MessageNotification.vue
Normal file
@@ -0,0 +1,397 @@
|
||||
<template>
|
||||
<div class="message-notification">
|
||||
<div class="page-header">
|
||||
<h1>消息通知</h1>
|
||||
<p>管理和查看系统消息通知</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 消息统计 -->
|
||||
<div class="overview-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="总消息数"
|
||||
:value="messageStats.total"
|
||||
:value-style="{ color: '#1890ff' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="未读消息"
|
||||
:value="messageStats.unread"
|
||||
:value-style="{ color: '#ff4d4f' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="系统通知"
|
||||
:value="messageStats.system"
|
||||
:value-style="{ color: '#52c41a' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="业务通知"
|
||||
:value="messageStats.business"
|
||||
:value-style="{ color: '#faad14' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="search-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-input-search
|
||||
v-model:value="searchText"
|
||||
placeholder="搜索消息内容"
|
||||
enter-button="搜索"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="typeFilter"
|
||||
placeholder="消息类型"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="system">系统通知</a-select-option>
|
||||
<a-select-option value="business">业务通知</a-select-option>
|
||||
<a-select-option value="warning">预警通知</a-select-option>
|
||||
<a-select-option value="info">信息通知</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="statusFilter"
|
||||
placeholder="阅读状态"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="unread">未读</a-select-option>
|
||||
<a-select-option value="read">已读</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="markAllRead">
|
||||
<check-outlined /> 全部标记为已读
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 消息列表 -->
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredMessages"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
row-key="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'type'">
|
||||
<a-tag :color="getTypeColor(record.type)">
|
||||
{{ getTypeName(record.type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="record.status === 'read' ? 'green' : 'red'">
|
||||
{{ record.status === 'read' ? '已读' : '未读' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="viewMessage(record)">查看</a-button>
|
||||
<a-button type="link" size="small" @click="markAsRead(record.id)">标记已读</a-button>
|
||||
<a-button type="link" size="small" danger @click="deleteMessage(record.id)">删除</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 消息详情对话框 -->
|
||||
<a-modal
|
||||
v-model:open="detailModalVisible"
|
||||
title="消息详情"
|
||||
width="600px"
|
||||
:footer="null"
|
||||
>
|
||||
<div v-if="selectedMessage" class="message-detail">
|
||||
<a-descriptions :column="1" bordered>
|
||||
<a-descriptions-item label="消息标题">
|
||||
{{ selectedMessage.title }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="消息类型">
|
||||
<a-tag :color="getTypeColor(selectedMessage.type)">
|
||||
{{ getTypeName(selectedMessage.type) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="发送时间">
|
||||
{{ selectedMessage.sendTime }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="发送人">
|
||||
{{ selectedMessage.sender }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="消息内容">
|
||||
<div class="message-content">{{ selectedMessage.content }}</div>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { CheckOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const searchText = ref('')
|
||||
const typeFilter = ref(undefined)
|
||||
const statusFilter = ref(undefined)
|
||||
const detailModalVisible = ref(false)
|
||||
const selectedMessage = ref(null)
|
||||
|
||||
// 消息统计
|
||||
const messageStats = ref({
|
||||
total: 45,
|
||||
unread: 12,
|
||||
system: 20,
|
||||
business: 25
|
||||
})
|
||||
|
||||
// 分页配置
|
||||
const pagination = ref({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
|
||||
})
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '消息标题',
|
||||
dataIndex: 'title',
|
||||
key: 'title',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '消息类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
filters: [
|
||||
{ text: '系统通知', value: 'system' },
|
||||
{ text: '业务通知', value: 'business' },
|
||||
{ text: '预警通知', value: 'warning' },
|
||||
{ text: '信息通知', value: 'info' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '发送人',
|
||||
dataIndex: 'sender',
|
||||
key: 'sender',
|
||||
},
|
||||
{
|
||||
title: '发送时间',
|
||||
dataIndex: 'sendTime',
|
||||
key: 'sendTime',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
filters: [
|
||||
{ text: '未读', value: 'unread' },
|
||||
{ text: '已读', value: 'read' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
},
|
||||
]
|
||||
|
||||
// 模拟消息数据
|
||||
const messages = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: '系统维护通知',
|
||||
type: 'system',
|
||||
sender: '系统管理员',
|
||||
sendTime: '2024-01-22 14:30:25',
|
||||
status: 'unread',
|
||||
content: '系统将于今晚22:00-24:00进行维护,期间可能影响部分功能使用,请提前做好准备。'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '新用户注册提醒',
|
||||
type: 'business',
|
||||
sender: '业务系统',
|
||||
sendTime: '2024-01-22 13:15:10',
|
||||
status: 'read',
|
||||
content: '有新的用户注册,请及时审核用户信息。'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '设备异常预警',
|
||||
type: 'warning',
|
||||
sender: '监控系统',
|
||||
sendTime: '2024-01-22 10:20:30',
|
||||
status: 'unread',
|
||||
content: 'ATM-001设备出现异常,请及时检查处理。'
|
||||
}
|
||||
])
|
||||
|
||||
// 计算属性
|
||||
const filteredMessages = computed(() => {
|
||||
let result = messages.value
|
||||
|
||||
if (searchText.value) {
|
||||
result = result.filter(msg =>
|
||||
msg.title.toLowerCase().includes(searchText.value.toLowerCase()) ||
|
||||
msg.content.toLowerCase().includes(searchText.value.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
if (typeFilter.value) {
|
||||
result = result.filter(msg => msg.type === typeFilter.value)
|
||||
}
|
||||
|
||||
if (statusFilter.value) {
|
||||
result = result.filter(msg => msg.status === statusFilter.value)
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
// 方法
|
||||
const getTypeColor = (type) => {
|
||||
const colors = {
|
||||
'system': 'blue',
|
||||
'business': 'green',
|
||||
'warning': 'orange',
|
||||
'info': 'purple'
|
||||
}
|
||||
return colors[type] || 'default'
|
||||
}
|
||||
|
||||
const getTypeName = (type) => {
|
||||
const names = {
|
||||
'system': '系统通知',
|
||||
'business': '业务通知',
|
||||
'warning': '预警通知',
|
||||
'info': '信息通知'
|
||||
}
|
||||
return names[type] || type
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.value.current = 1
|
||||
}
|
||||
|
||||
const handleFilter = () => {
|
||||
pagination.value.current = 1
|
||||
}
|
||||
|
||||
const handleTableChange = (pag) => {
|
||||
pagination.value = pag
|
||||
}
|
||||
|
||||
const markAllRead = () => {
|
||||
messages.value.forEach(msg => {
|
||||
msg.status = 'read'
|
||||
})
|
||||
message.success('所有消息已标记为已读')
|
||||
}
|
||||
|
||||
const viewMessage = (msg) => {
|
||||
selectedMessage.value = msg
|
||||
detailModalVisible.value = true
|
||||
// 查看消息时自动标记为已读
|
||||
if (msg.status === 'unread') {
|
||||
msg.status = 'read'
|
||||
}
|
||||
}
|
||||
|
||||
const markAsRead = (id) => {
|
||||
const msg = messages.value.find(m => m.id === id)
|
||||
if (msg) {
|
||||
msg.status = 'read'
|
||||
message.success('消息已标记为已读')
|
||||
}
|
||||
}
|
||||
|
||||
const deleteMessage = (id) => {
|
||||
const index = messages.value.findIndex(m => m.id === id)
|
||||
if (index > -1) {
|
||||
messages.value.splice(index, 1)
|
||||
message.success('消息已删除')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
pagination.value.total = messages.value.length
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.message-notification {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.overview-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
margin-bottom: 24px;
|
||||
padding: 16px;
|
||||
background: #fafafa;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.message-detail {
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
@@ -1 +1,16 @@
|
||||
<template>\n <div class=page-container>404 Not Found</div>\n</template>\n\n<script setup>\n</script>\n\n<style scoped>\n.page-container { padding: 24px; text-align:center; color:#8c8c8c; }\n</style>
|
||||
<template>
|
||||
<div class="page-container">
|
||||
404 Not Found
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-container {
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
</style>
|
||||
351
bank-frontend/src/views/PendingInstallation.vue
Normal file
351
bank-frontend/src/views/PendingInstallation.vue
Normal file
@@ -0,0 +1,351 @@
|
||||
<template>
|
||||
<div class="pending-installation-page">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header">
|
||||
<h1>待安装任务</h1>
|
||||
<div class="header-actions">
|
||||
<a-button type="primary" @click="handleExport">
|
||||
<download-outlined />
|
||||
安装任务导出
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索筛选区域 -->
|
||||
<div class="search-section">
|
||||
<a-row :gutter="16" align="middle">
|
||||
<a-col :span="6">
|
||||
<a-input
|
||||
v-model:value="searchForm.contractNumber"
|
||||
placeholder="请输入合同编号"
|
||||
allow-clear
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-range-picker
|
||||
v-model:value="searchForm.dateRange"
|
||||
:placeholder="['开始日期', '结束日期']"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="searchForm.installationStatus"
|
||||
placeholder="请选择安装状态"
|
||||
allow-clear
|
||||
style="width: 100%"
|
||||
>
|
||||
<a-select-option value="pending">待安装</a-select-option>
|
||||
<a-select-option value="in-progress">安装中</a-select-option>
|
||||
<a-select-option value="completed">已完成</a-select-option>
|
||||
<a-select-option value="failed">安装失败</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<search-outlined />
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button @click="handleReset">重置</a-button>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<div class="table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredTasks"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
row-key="id"
|
||||
:locale="{ emptyText: '暂无数据' }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'installationStatus'">
|
||||
<a-tag :color="getStatusColor(record.installationStatus)">
|
||||
{{ getStatusName(record.installationStatus) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="viewTask(record)">查看</a-button>
|
||||
<a-button type="link" size="small" @click="editTask(record)">编辑</a-button>
|
||||
<a-button type="link" size="small" @click="startInstallation(record)">开始安装</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { DownloadOutlined, SearchOutlined } from '@ant-design/icons-vue'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const loading = ref(false)
|
||||
const tasks = ref([])
|
||||
|
||||
const searchForm = reactive({
|
||||
contractNumber: '',
|
||||
dateRange: [],
|
||||
installationStatus: undefined,
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{ title: '申请单号', dataIndex: 'applicationNumber', key: 'applicationNumber', sorter: true },
|
||||
{ title: '放款合同编号', dataIndex: 'contractNumber', key: 'contractNumber', sorter: true },
|
||||
{ title: '产品名称', dataIndex: 'productName', key: 'productName' },
|
||||
{ title: '客户姓名', dataIndex: 'customerName', key: 'customerName' },
|
||||
{ title: '证件类型', dataIndex: 'idType', key: 'idType' },
|
||||
{ title: '证件号码', dataIndex: 'idNumber', key: 'idNumber' },
|
||||
{ title: '养殖生资种类', dataIndex: 'assetType', key: 'assetType' },
|
||||
{ title: '待安装设备', dataIndex: 'equipmentToInstall', key: 'equipmentToInstall' },
|
||||
{ title: '安装状态', dataIndex: 'installationStatus', key: 'installationStatus', filters: [
|
||||
{ text: '待安装', value: 'pending' },
|
||||
{ text: '安装中', value: 'in-progress' },
|
||||
{ text: '已完成', value: 'completed' },
|
||||
{ text: '安装失败', value: 'failed' },
|
||||
]},
|
||||
{ title: '生成安装任务时间', dataIndex: 'taskGenerationTime', key: 'taskGenerationTime', sorter: true },
|
||||
{ title: '安装完成生效时间', dataIndex: 'completionTime', key: 'completionTime', sorter: true },
|
||||
{ title: '操作', key: 'action', fixed: 'right', width: 180 },
|
||||
]
|
||||
|
||||
// 模拟数据
|
||||
const mockTasks = [
|
||||
{
|
||||
id: 'I001',
|
||||
applicationNumber: 'APP2024001',
|
||||
contractNumber: 'LOAN2024001',
|
||||
productName: '生猪养殖贷',
|
||||
customerName: '张三',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4401XXXXXXXXXXXXXX',
|
||||
assetType: '生猪',
|
||||
equipmentToInstall: '耳标设备',
|
||||
installationStatus: 'pending',
|
||||
taskGenerationTime: '2024-01-15 10:30:00',
|
||||
completionTime: null,
|
||||
},
|
||||
{
|
||||
id: 'I002',
|
||||
applicationNumber: 'APP2024002',
|
||||
contractNumber: 'LOAN2024002',
|
||||
productName: '肉牛养殖贷',
|
||||
customerName: '李四',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4402XXXXXXXXXXXXXX',
|
||||
assetType: '肉牛',
|
||||
equipmentToInstall: '项圈设备',
|
||||
installationStatus: 'in-progress',
|
||||
taskGenerationTime: '2024-01-16 14:20:00',
|
||||
completionTime: null,
|
||||
},
|
||||
{
|
||||
id: 'I003',
|
||||
applicationNumber: 'APP2024003',
|
||||
contractNumber: 'LOAN2024003',
|
||||
productName: '蛋鸡养殖贷',
|
||||
customerName: '王五',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4403XXXXXXXXXXXXXX',
|
||||
assetType: '蛋鸡',
|
||||
equipmentToInstall: '监控设备',
|
||||
installationStatus: 'completed',
|
||||
taskGenerationTime: '2024-01-10 09:15:00',
|
||||
completionTime: '2024-01-20 16:30:00',
|
||||
},
|
||||
]
|
||||
|
||||
const fetchTasks = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 实际项目中这里会调用API获取数据
|
||||
// const response = await api.installationTasks.getList({
|
||||
// page: pagination.current,
|
||||
// pageSize: pagination.pageSize,
|
||||
// ...searchForm,
|
||||
// })
|
||||
|
||||
// 使用模拟数据
|
||||
tasks.value = mockTasks.map(task => ({
|
||||
...task,
|
||||
taskGenerationTime: dayjs(task.taskGenerationTime),
|
||||
completionTime: task.completionTime ? dayjs(task.completionTime) : null,
|
||||
}))
|
||||
pagination.total = mockTasks.length
|
||||
} catch (error) {
|
||||
console.error('获取安装任务失败:', error)
|
||||
message.error('获取安装任务失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const filteredTasks = computed(() => {
|
||||
let result = tasks.value
|
||||
|
||||
if (searchForm.contractNumber) {
|
||||
result = result.filter(task =>
|
||||
task.contractNumber.toLowerCase().includes(searchForm.contractNumber.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
if (searchForm.installationStatus) {
|
||||
result = result.filter(task => task.installationStatus === searchForm.installationStatus)
|
||||
}
|
||||
|
||||
if (searchForm.dateRange && searchForm.dateRange.length === 2) {
|
||||
const [startDate, endDate] = searchForm.dateRange
|
||||
result = result.filter(task => {
|
||||
const taskTime = dayjs(task.taskGenerationTime)
|
||||
return taskTime.isAfter(startDate.startOf('day')) && taskTime.isBefore(endDate.endOf('day'))
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
fetchTasks()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
searchForm.contractNumber = ''
|
||||
searchForm.dateRange = []
|
||||
searchForm.installationStatus = undefined
|
||||
pagination.current = 1
|
||||
fetchTasks()
|
||||
}
|
||||
|
||||
const handleExport = () => {
|
||||
message.info('安装任务导出功能开发中...')
|
||||
}
|
||||
|
||||
const handleTableChange = (pag, filters, sorter) => {
|
||||
pagination.current = pag.current
|
||||
pagination.pageSize = pag.pageSize
|
||||
fetchTasks()
|
||||
}
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
'pending': 'blue',
|
||||
'in-progress': 'orange',
|
||||
'completed': 'green',
|
||||
'failed': 'red',
|
||||
}
|
||||
return colors[status] || 'default'
|
||||
}
|
||||
|
||||
const getStatusName = (status) => {
|
||||
const names = {
|
||||
'pending': '待安装',
|
||||
'in-progress': '安装中',
|
||||
'completed': '已完成',
|
||||
'failed': '安装失败',
|
||||
}
|
||||
return names[status] || status
|
||||
}
|
||||
|
||||
const viewTask = (record) => {
|
||||
message.info(`查看任务: ${record.applicationNumber}`)
|
||||
}
|
||||
|
||||
const editTask = (record) => {
|
||||
message.info(`编辑任务: ${record.applicationNumber}`)
|
||||
}
|
||||
|
||||
const startInstallation = (record) => {
|
||||
message.success(`开始安装任务: ${record.applicationNumber}`)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchTasks()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pending-installation-page {
|
||||
padding: 0;
|
||||
background: #f0f2f5;
|
||||
min-height: calc(100vh - 134px);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
padding: 24px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.search-section .ant-col {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-section .ant-col:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
691
bank-frontend/src/views/SupervisionTasks.vue
Normal file
691
bank-frontend/src/views/SupervisionTasks.vue
Normal file
@@ -0,0 +1,691 @@
|
||||
<template>
|
||||
<div class="supervision-tasks">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header">
|
||||
<h1>监管任务导入</h1>
|
||||
<div class="header-actions">
|
||||
<a-button type="primary" @click="showAddTaskModal">
|
||||
<plus-outlined /> 新增监管任务
|
||||
</a-button>
|
||||
<a-button type="primary" @click="showBatchAddModal">
|
||||
<plus-outlined /> 批量新增
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索筛选区域 -->
|
||||
<div class="search-section">
|
||||
<div class="search-form">
|
||||
<a-input
|
||||
v-model:value="searchForm.contractNumber"
|
||||
placeholder="请输入合同编号"
|
||||
class="search-input"
|
||||
/>
|
||||
|
||||
<a-range-picker
|
||||
v-model:value="searchForm.dateRange"
|
||||
:placeholder="['开始日期', '结束日期']"
|
||||
class="date-picker"
|
||||
/>
|
||||
|
||||
<a-select
|
||||
v-model:value="searchForm.supervisionStatus"
|
||||
placeholder="请选择监管状态"
|
||||
class="status-select"
|
||||
>
|
||||
<a-select-option value="pending">待监管</a-select-option>
|
||||
<a-select-option value="supervising">监管中</a-select-option>
|
||||
<a-select-option value="completed">已完成</a-select-option>
|
||||
<a-select-option value="suspended">已暂停</a-select-option>
|
||||
</a-select>
|
||||
|
||||
<div class="action-buttons">
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<search-outlined /> 搜索
|
||||
</a-button>
|
||||
<a-button @click="handleExport">
|
||||
<download-outlined /> 任务导出
|
||||
</a-button>
|
||||
<a-button @click="handleReset">
|
||||
重置
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<div class="table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredTasks"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
row-key="id"
|
||||
:locale="{ emptyText: '暂无数据' }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'supervisionStatus'">
|
||||
<a-tag :color="getStatusColor(record.supervisionStatus)">
|
||||
{{ getStatusName(record.supervisionStatus) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="viewTask(record)">查看</a-button>
|
||||
<a-button type="link" size="small" @click="editTask(record)">编辑</a-button>
|
||||
<a-button type="link" size="small" @click="deleteTask(record.id)">删除</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 新增监管任务对话框 -->
|
||||
<a-modal
|
||||
v-model:open="addTaskModalVisible"
|
||||
title="新增监管任务"
|
||||
width="800px"
|
||||
@ok="handleAddTask"
|
||||
@cancel="handleCancelAdd"
|
||||
>
|
||||
<a-form
|
||||
ref="addTaskFormRef"
|
||||
:model="addTaskForm"
|
||||
:rules="addTaskRules"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="申请单号" name="applicationNumber">
|
||||
<a-input v-model:value="addTaskForm.applicationNumber" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="放款合同编号" name="contractNumber">
|
||||
<a-input v-model:value="addTaskForm.contractNumber" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="产品名称" name="productName">
|
||||
<a-input v-model:value="addTaskForm.productName" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="客户姓名" name="customerName">
|
||||
<a-input v-model:value="addTaskForm.customerName" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="证件类型" name="idType">
|
||||
<a-select v-model:value="addTaskForm.idType">
|
||||
<a-select-option value="id_card">身份证</a-select-option>
|
||||
<a-select-option value="passport">护照</a-select-option>
|
||||
<a-select-option value="other">其他</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="证件号码" name="idNumber">
|
||||
<a-input v-model:value="addTaskForm.idNumber" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="养殖生资种类" name="assetType">
|
||||
<a-select v-model:value="addTaskForm.assetType">
|
||||
<a-select-option value="cattle">牛</a-select-option>
|
||||
<a-select-option value="sheep">羊</a-select-option>
|
||||
<a-select-option value="pig">猪</a-select-option>
|
||||
<a-select-option value="poultry">家禽</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="监管生资数量" name="assetQuantity">
|
||||
<a-input-number v-model:value="addTaskForm.assetQuantity" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="监管起始时间" name="startTime">
|
||||
<a-date-picker v-model:value="addTaskForm.startTime" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="监管结束时间" name="endTime">
|
||||
<a-date-picker v-model:value="addTaskForm.endTime" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 任务详情对话框 -->
|
||||
<a-modal
|
||||
v-model:open="detailModalVisible"
|
||||
title="任务详情"
|
||||
width="800px"
|
||||
:footer="null"
|
||||
>
|
||||
<div v-if="selectedTask" class="task-detail">
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item label="申请单号">
|
||||
{{ selectedTask.applicationNumber }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="放款合同编号">
|
||||
{{ selectedTask.contractNumber }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="产品名称">
|
||||
{{ selectedTask.productName }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="客户姓名">
|
||||
{{ selectedTask.customerName }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="证件类型">
|
||||
{{ getIDTypeName(selectedTask.idType) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="证件号码">
|
||||
{{ selectedTask.idNumber }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="养殖生资种类">
|
||||
{{ getAssetTypeName(selectedTask.assetType) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="监管生资数量">
|
||||
{{ selectedTask.assetQuantity }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="监管状态">
|
||||
<a-tag :color="getStatusColor(selectedTask.supervisionStatus)">
|
||||
{{ getStatusName(selectedTask.supervisionStatus) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="任务导入时间">
|
||||
{{ selectedTask.importTime }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="监管起始时间">
|
||||
{{ selectedTask.startTime }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="监管结束时间">
|
||||
{{ selectedTask.endTime }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { api } from '@/utils/api'
|
||||
import {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
DownloadOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const addTaskModalVisible = ref(false)
|
||||
const detailModalVisible = ref(false)
|
||||
const selectedTask = ref(null)
|
||||
const addTaskFormRef = ref()
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = ref({
|
||||
contractNumber: '',
|
||||
dateRange: [],
|
||||
supervisionStatus: undefined
|
||||
})
|
||||
|
||||
// 新增任务表单
|
||||
const addTaskForm = ref({
|
||||
applicationNumber: '',
|
||||
contractNumber: '',
|
||||
productName: '',
|
||||
customerName: '',
|
||||
idType: '',
|
||||
idNumber: '',
|
||||
assetType: '',
|
||||
assetQuantity: 0,
|
||||
startTime: null,
|
||||
endTime: null
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const addTaskRules = {
|
||||
applicationNumber: [
|
||||
{ required: true, message: '请输入申请单号', trigger: 'blur' }
|
||||
],
|
||||
contractNumber: [
|
||||
{ required: true, message: '请输入放款合同编号', trigger: 'blur' }
|
||||
],
|
||||
productName: [
|
||||
{ required: true, message: '请输入产品名称', trigger: 'blur' }
|
||||
],
|
||||
customerName: [
|
||||
{ required: true, message: '请输入客户姓名', trigger: 'blur' }
|
||||
],
|
||||
idType: [
|
||||
{ required: true, message: '请选择证件类型', trigger: 'change' }
|
||||
],
|
||||
idNumber: [
|
||||
{ required: true, message: '请输入证件号码', trigger: 'blur' }
|
||||
],
|
||||
assetType: [
|
||||
{ required: true, message: '请选择养殖生资种类', trigger: 'change' }
|
||||
],
|
||||
assetQuantity: [
|
||||
{ required: true, message: '请输入监管生资数量', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 分页配置
|
||||
const pagination = ref({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total, range) => `共${total}条`
|
||||
})
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '申请单号',
|
||||
dataIndex: 'applicationNumber',
|
||||
key: 'applicationNumber',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '放款合同编号',
|
||||
dataIndex: 'contractNumber',
|
||||
key: 'contractNumber',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '产品名称',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '客户姓名',
|
||||
dataIndex: 'customerName',
|
||||
key: 'customerName',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '证件类型',
|
||||
dataIndex: 'idType',
|
||||
key: 'idType',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '证件号码',
|
||||
dataIndex: 'idNumber',
|
||||
key: 'idNumber',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '养殖生资种类',
|
||||
dataIndex: 'assetType',
|
||||
key: 'assetType',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '监管生资数量',
|
||||
dataIndex: 'assetQuantity',
|
||||
key: 'assetQuantity',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '监管状态',
|
||||
dataIndex: 'supervisionStatus',
|
||||
key: 'supervisionStatus',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '任务导入时间',
|
||||
dataIndex: 'importTime',
|
||||
key: 'importTime',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '监管起始时间',
|
||||
dataIndex: 'startTime',
|
||||
key: 'startTime',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '监管结束时间',
|
||||
dataIndex: 'endTime',
|
||||
key: 'endTime',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 150,
|
||||
fixed: 'right'
|
||||
},
|
||||
]
|
||||
|
||||
// 监管任务数据
|
||||
const tasks = ref([])
|
||||
|
||||
// 计算属性
|
||||
const filteredTasks = computed(() => {
|
||||
return tasks.value
|
||||
})
|
||||
|
||||
// 方法
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
'pending': 'orange',
|
||||
'supervising': 'blue',
|
||||
'completed': 'green',
|
||||
'suspended': 'red'
|
||||
}
|
||||
return colors[status] || 'default'
|
||||
}
|
||||
|
||||
const getStatusName = (status) => {
|
||||
const names = {
|
||||
'pending': '待监管',
|
||||
'supervising': '监管中',
|
||||
'completed': '已完成',
|
||||
'suspended': '已暂停'
|
||||
}
|
||||
return names[status] || status
|
||||
}
|
||||
|
||||
const getIDTypeName = (type) => {
|
||||
const names = {
|
||||
'id_card': '身份证',
|
||||
'passport': '护照',
|
||||
'other': '其他'
|
||||
}
|
||||
return names[type] || type
|
||||
}
|
||||
|
||||
const getAssetTypeName = (type) => {
|
||||
const names = {
|
||||
'cattle': '牛',
|
||||
'sheep': '羊',
|
||||
'pig': '猪',
|
||||
'poultry': '家禽'
|
||||
}
|
||||
return names[type] || type
|
||||
}
|
||||
|
||||
const showAddTaskModal = () => {
|
||||
addTaskModalVisible.value = true
|
||||
}
|
||||
|
||||
const showBatchAddModal = () => {
|
||||
message.info('批量新增功能开发中...')
|
||||
}
|
||||
|
||||
|
||||
const resetAddTaskForm = () => {
|
||||
addTaskForm.value = {
|
||||
applicationNumber: '',
|
||||
contractNumber: '',
|
||||
productName: '',
|
||||
customerName: '',
|
||||
idType: '',
|
||||
idNumber: '',
|
||||
assetType: '',
|
||||
assetQuantity: 0,
|
||||
startTime: null,
|
||||
endTime: null
|
||||
}
|
||||
}
|
||||
|
||||
const viewTask = (task) => {
|
||||
selectedTask.value = task
|
||||
detailModalVisible.value = true
|
||||
}
|
||||
|
||||
// API调用函数
|
||||
const fetchTasks = async (params = {}) => {
|
||||
try {
|
||||
loading.value = true
|
||||
console.log('开始获取监管任务列表...', {
|
||||
page: pagination.value.current,
|
||||
limit: pagination.value.pageSize,
|
||||
search: searchForm.value.contractNumber,
|
||||
supervisionStatus: searchForm.value.supervisionStatus,
|
||||
dateRange: searchForm.value.dateRange ?
|
||||
`${searchForm.value.dateRange[0].format('YYYY-MM-DD')},${searchForm.value.dateRange[1].format('YYYY-MM-DD')}` : ''
|
||||
})
|
||||
|
||||
const response = await api.supervisionTasks.getList({
|
||||
page: pagination.value.current,
|
||||
limit: pagination.value.pageSize,
|
||||
search: searchForm.value.contractNumber,
|
||||
supervisionStatus: searchForm.value.supervisionStatus,
|
||||
dateRange: searchForm.value.dateRange ?
|
||||
`${searchForm.value.dateRange[0].format('YYYY-MM-DD')},${searchForm.value.dateRange[1].format('YYYY-MM-DD')}` : '',
|
||||
...params
|
||||
})
|
||||
|
||||
console.log('监管任务列表响应:', response)
|
||||
|
||||
if (response.success) {
|
||||
tasks.value = response.data.tasks || []
|
||||
pagination.value.total = response.data.pagination?.total || 0
|
||||
console.log('监管任务数据已更新:', tasks.value.length, '个任务')
|
||||
} else {
|
||||
console.error('API返回错误:', response.message)
|
||||
message.error(response.message || '获取监管任务列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取监管任务列表失败:', error)
|
||||
message.error('获取监管任务列表失败: ' + error.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleTableChange = (paginationInfo) => {
|
||||
pagination.value = paginationInfo
|
||||
fetchTasks()
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.value.current = 1
|
||||
fetchTasks()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
searchForm.value = {
|
||||
contractNumber: '',
|
||||
dateRange: null,
|
||||
supervisionStatus: ''
|
||||
}
|
||||
pagination.value.current = 1
|
||||
fetchTasks()
|
||||
}
|
||||
|
||||
const editTask = async (task) => {
|
||||
try {
|
||||
// 这里可以实现编辑功能
|
||||
message.info(`编辑任务: ${task.applicationNumber}`)
|
||||
} catch (error) {
|
||||
console.error('编辑任务失败:', error)
|
||||
message.error('编辑任务失败')
|
||||
}
|
||||
}
|
||||
|
||||
const deleteTask = async (id) => {
|
||||
try {
|
||||
const response = await api.supervisionTasks.delete(id)
|
||||
if (response.success) {
|
||||
message.success('任务删除成功')
|
||||
await fetchTasks()
|
||||
} else {
|
||||
message.error(response.message || '删除任务失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除任务失败:', error)
|
||||
message.error('删除任务失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleAddTask = async () => {
|
||||
try {
|
||||
await addTaskFormRef.value.validate()
|
||||
|
||||
const response = await api.supervisionTasks.create(addTaskForm.value)
|
||||
|
||||
if (response.success) {
|
||||
message.success('监管任务创建成功')
|
||||
addTaskModalVisible.value = false
|
||||
addTaskFormRef.value.resetFields()
|
||||
await fetchTasks()
|
||||
} else {
|
||||
message.error(response.message || '创建监管任务失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('创建监管任务失败:', error)
|
||||
message.error('创建监管任务失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleCancelAdd = () => {
|
||||
addTaskModalVisible.value = false
|
||||
addTaskFormRef.value.resetFields()
|
||||
}
|
||||
|
||||
const handleExport = () => {
|
||||
message.info('任务导出功能开发中...')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchTasks()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.supervision-tasks {
|
||||
padding: 0;
|
||||
background: #f0f2f5;
|
||||
min-height: calc(100vh - 134px);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
padding: 24px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.date-picker {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.status-select {
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.task-detail {
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 1200px) {
|
||||
.search-form {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
margin-left: 0;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.search-input,
|
||||
.date-picker,
|
||||
.status-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2,12 +2,11 @@
|
||||
<div class="system-check">
|
||||
<div class="page-header">
|
||||
<h1>系统日检</h1>
|
||||
<p>每日系统健康检查和监控</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 检查概览 -->
|
||||
<div class="overview-section">
|
||||
<!-- <div class="overview-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
@@ -46,7 +45,7 @@
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="action-section">
|
||||
|
||||
147
bank-frontend/src/views/TestProjects.vue
Normal file
147
bank-frontend/src/views/TestProjects.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<div class="test-projects">
|
||||
<h1>项目接口测试页面</h1>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>1. 登录测试</h2>
|
||||
<a-button @click="testLogin" :loading="loginLoading">
|
||||
测试登录
|
||||
</a-button>
|
||||
<div v-if="loginResult" class="result">
|
||||
<pre>{{ loginResult }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>2. 项目接口测试</h2>
|
||||
<a-button @click="testProjects" :loading="projectsLoading" :disabled="!token">
|
||||
测试项目接口
|
||||
</a-button>
|
||||
<div v-if="projectsResult" class="result">
|
||||
<pre>{{ projectsResult }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>3. 项目数据展示</h2>
|
||||
<div v-if="projects.length > 0">
|
||||
<p>共 {{ projects.length }} 个项目</p>
|
||||
<div v-for="project in projects" :key="project.id" class="project-item">
|
||||
<h3>{{ project.name }}</h3>
|
||||
<p>状态: {{ project.status }}</p>
|
||||
<p>养殖场: {{ project.farmName }}</p>
|
||||
<p>监管对象: {{ project.supervisionObject }}</p>
|
||||
<p>监管数量: {{ project.supervisionQuantity }}</p>
|
||||
<p>监管金额: {{ project.supervisionAmount }} 元</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { api } from '@/utils/api'
|
||||
|
||||
const loginLoading = ref(false)
|
||||
const projectsLoading = ref(false)
|
||||
const loginResult = ref('')
|
||||
const projectsResult = ref('')
|
||||
const token = ref('')
|
||||
const projects = ref([])
|
||||
|
||||
const testLogin = async () => {
|
||||
try {
|
||||
loginLoading.value = true
|
||||
loginResult.value = '正在登录...'
|
||||
|
||||
const response = await api.auth.login('admin', 'Admin123456')
|
||||
|
||||
if (response.success) {
|
||||
token.value = response.data.token
|
||||
loginResult.value = `登录成功!\nToken: ${token.value.substring(0, 50)}...\n用户: ${response.data.user.username}`
|
||||
message.success('登录成功')
|
||||
} else {
|
||||
loginResult.value = `登录失败: ${response.message}`
|
||||
message.error('登录失败')
|
||||
}
|
||||
} catch (error) {
|
||||
loginResult.value = `登录错误: ${error.message}`
|
||||
message.error('登录错误')
|
||||
} finally {
|
||||
loginLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const testProjects = async () => {
|
||||
try {
|
||||
projectsLoading.value = true
|
||||
projectsResult.value = '正在获取项目列表...'
|
||||
|
||||
const response = await api.projects.getList({
|
||||
page: 1,
|
||||
limit: 12,
|
||||
search: '',
|
||||
status: ''
|
||||
})
|
||||
|
||||
if (response.success) {
|
||||
projects.value = response.data.projects || []
|
||||
projectsResult.value = `获取成功!\n项目数量: ${projects.value.length}\n总数量: ${response.data.pagination.total}`
|
||||
message.success('获取项目列表成功')
|
||||
} else {
|
||||
projectsResult.value = `获取失败: ${response.message}`
|
||||
message.error('获取项目列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
projectsResult.value = `获取错误: ${error.message}`
|
||||
message.error('获取项目列表错误')
|
||||
} finally {
|
||||
projectsLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.test-projects {
|
||||
padding: 24px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.test-section {
|
||||
margin-bottom: 32px;
|
||||
padding: 16px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.result {
|
||||
margin-top: 16px;
|
||||
padding: 12px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
white-space: pre-wrap;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.project-item {
|
||||
margin-bottom: 16px;
|
||||
padding: 12px;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 4px;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.project-item h3 {
|
||||
margin: 0 0 8px 0;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.project-item p {
|
||||
margin: 4px 0;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
@@ -1,120 +1,80 @@
|
||||
<template>
|
||||
<div class="loan-applications">
|
||||
<div class="loan-applications-page">
|
||||
<div class="page-header">
|
||||
<h1>贷款申请进度</h1>
|
||||
<p>管理和跟踪贷款申请流程</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="search-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-input-search
|
||||
v-model:value="searchText"
|
||||
placeholder="搜索申请人或申请编号"
|
||||
enter-button="搜索"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="statusFilter"
|
||||
placeholder="申请状态"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="pending">待审核</a-select-option>
|
||||
<a-select-option value="approved">已通过</a-select-option>
|
||||
<a-select-option value="rejected">已拒绝</a-select-option>
|
||||
<a-select-option value="processing">处理中</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="typeFilter"
|
||||
placeholder="贷款类型"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="personal">个人贷款</a-select-option>
|
||||
<a-select-option value="business">企业贷款</a-select-option>
|
||||
<a-select-option value="mortgage">抵押贷款</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-range-picker
|
||||
v-model:value="dateRange"
|
||||
@change="handleFilter"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="handleAddApplication">
|
||||
<PlusOutlined />
|
||||
新建申请
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<div class="search-filter-section">
|
||||
<a-row :gutter="16" align="middle">
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="searchQuery.field"
|
||||
placeholder="申请单号"
|
||||
style="width: 100%"
|
||||
>
|
||||
<a-select-option value="applicationNumber">申请单号</a-select-option>
|
||||
<a-select-option value="customerName">客户姓名</a-select-option>
|
||||
<a-select-option value="productName">贷款产品</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-input
|
||||
v-model:value="searchQuery.value"
|
||||
placeholder="请输入内容"
|
||||
allow-clear
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<search-outlined /> 搜索
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button @click="handleReset">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 申请列表 -->
|
||||
<div class="table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredApplications"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'type'">
|
||||
<a-tag :color="getTypeColor(record.type)">
|
||||
{{ getTypeText(record.type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'amount'">
|
||||
{{ formatAmount(record.amount) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'progress'">
|
||||
<a-progress
|
||||
:percent="getProgressPercent(record.status)"
|
||||
:status="getProgressStatus(record.status)"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleView(record)">
|
||||
查看
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleApprove(record)"
|
||||
v-if="record.status === 'pending'"
|
||||
>
|
||||
审核
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleReject(record)"
|
||||
v-if="record.status === 'pending'"
|
||||
danger
|
||||
>
|
||||
拒绝
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<div class="applications-table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredApplications"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
:expand-row-by-click="false"
|
||||
:expand-icon-column-index="0"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
<template v-else-if="column.key === 'policyInfo'">
|
||||
<a-button type="link" size="small" @click="viewPolicy(record)">
|
||||
查看保单
|
||||
</a-button>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'amount'">
|
||||
{{ formatAmount(record.amount) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleReject(record)">
|
||||
打回
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="handleApprove(record)">
|
||||
通过
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="handleView(record)">
|
||||
详情
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 申请详情模态框 -->
|
||||
@@ -222,14 +182,14 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { PlusOutlined } from '@ant-design/icons-vue'
|
||||
import { SearchOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const searchText = ref('')
|
||||
const statusFilter = ref(undefined)
|
||||
const typeFilter = ref(undefined)
|
||||
const dateRange = ref([])
|
||||
const searchQuery = ref({
|
||||
field: 'applicationNumber',
|
||||
value: ''
|
||||
})
|
||||
const detailModalVisible = ref(false)
|
||||
const auditModalVisible = ref(false)
|
||||
const selectedApplication = ref(null)
|
||||
@@ -245,52 +205,85 @@ const pagination = ref({
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
|
||||
showTotal: (total) => `共${total}条`
|
||||
})
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '申请编号',
|
||||
title: '',
|
||||
key: 'expand',
|
||||
width: 50,
|
||||
customRender: () => '>'
|
||||
},
|
||||
{
|
||||
title: '申请单号',
|
||||
dataIndex: 'applicationNumber',
|
||||
key: 'applicationNumber',
|
||||
width: 150
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '申请人',
|
||||
dataIndex: 'applicantName',
|
||||
key: 'applicantName',
|
||||
title: '贷款产品',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '申请养殖户',
|
||||
dataIndex: 'farmerName',
|
||||
key: 'farmerName',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
title: '贷款人姓名',
|
||||
dataIndex: 'borrowerName',
|
||||
key: 'borrowerName',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '贷款人身份证号',
|
||||
dataIndex: 'borrowerIdNumber',
|
||||
key: 'borrowerIdNumber',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '生资种类',
|
||||
dataIndex: 'assetType',
|
||||
key: 'assetType',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
title: '申请数量',
|
||||
dataIndex: 'applicationQuantity',
|
||||
key: 'applicationQuantity',
|
||||
width: 120,
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '保单信息',
|
||||
dataIndex: 'policyInfo',
|
||||
key: 'policyInfo',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '申请金额',
|
||||
title: '申请额度',
|
||||
dataIndex: 'amount',
|
||||
key: 'amount',
|
||||
width: 120
|
||||
width: 120,
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '申请时间',
|
||||
dataIndex: 'applicationTime',
|
||||
key: 'applicationTime',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '进度',
|
||||
dataIndex: 'progress',
|
||||
key: 'progress',
|
||||
width: 150
|
||||
title: '当前状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 120,
|
||||
filters: [
|
||||
{ text: '待初审', value: 'pending_review' },
|
||||
{ text: '核验待放款', value: 'verification_pending' },
|
||||
{ text: '待绑定', value: 'pending_binding' },
|
||||
{ text: '已通过', value: 'approved' },
|
||||
{ text: '已拒绝', value: 'rejected' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
@@ -304,87 +297,86 @@ const columns = [
|
||||
const applications = ref([
|
||||
{
|
||||
id: 1,
|
||||
applicationNumber: 'APP-202401180001',
|
||||
applicantName: '张三',
|
||||
type: 'personal',
|
||||
status: 'pending',
|
||||
amount: 200000,
|
||||
term: 24,
|
||||
interestRate: 6.5,
|
||||
applicationTime: '2024-01-18 09:30:00',
|
||||
applicationNumber: '20240325123703784',
|
||||
productName: '惠农贷',
|
||||
farmerName: '刘超',
|
||||
borrowerName: '11',
|
||||
borrowerIdNumber: '511***********3017',
|
||||
assetType: '牛',
|
||||
applicationQuantity: '10头',
|
||||
policyInfo: '查看保单',
|
||||
amount: 100000.00,
|
||||
status: 'pending_review',
|
||||
applicationTime: '2024-03-25 12:37:03',
|
||||
phone: '13800138000',
|
||||
idCard: '110101199001011234',
|
||||
purpose: '个人消费',
|
||||
purpose: '养殖贷款',
|
||||
remark: '',
|
||||
auditRecords: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'submit',
|
||||
auditor: '张三',
|
||||
time: '2024-01-18 09:30:00',
|
||||
auditor: '刘超',
|
||||
time: '2024-03-25 12:37:03',
|
||||
comment: '提交申请'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
applicationNumber: 'APP-202401180002',
|
||||
applicantName: '李四',
|
||||
type: 'business',
|
||||
status: 'approved',
|
||||
amount: 1000000,
|
||||
term: 36,
|
||||
interestRate: 5.8,
|
||||
applicationTime: '2024-01-17 14:20:00',
|
||||
applicationNumber: '20240229110801968',
|
||||
productName: '中国工商银行扎旗支行"畜禽活体抵押"',
|
||||
farmerName: '刘超',
|
||||
borrowerName: '1',
|
||||
borrowerIdNumber: '511***********3017',
|
||||
assetType: '牛',
|
||||
applicationQuantity: '10头',
|
||||
policyInfo: '查看保单',
|
||||
amount: 100000.00,
|
||||
status: 'verification_pending',
|
||||
applicationTime: '2024-02-29 11:08:01',
|
||||
phone: '13900139000',
|
||||
idCard: '110101199002021234',
|
||||
purpose: '企业经营',
|
||||
purpose: '养殖贷款',
|
||||
remark: '',
|
||||
auditRecords: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'submit',
|
||||
auditor: '李四',
|
||||
time: '2024-01-17 14:20:00',
|
||||
auditor: '刘超',
|
||||
time: '2024-02-29 11:08:01',
|
||||
comment: '提交申请'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
action: 'approve',
|
||||
auditor: '王经理',
|
||||
time: '2024-01-18 10:15:00',
|
||||
time: '2024-03-01 10:15:00',
|
||||
comment: '资料齐全,符合条件,同意放款'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
applicationNumber: 'APP-202401180003',
|
||||
applicantName: '王五',
|
||||
type: 'mortgage',
|
||||
status: 'rejected',
|
||||
amount: 500000,
|
||||
term: 120,
|
||||
interestRate: 4.5,
|
||||
applicationTime: '2024-01-16 16:45:00',
|
||||
applicationNumber: '20240229105806431',
|
||||
productName: '惠农贷',
|
||||
farmerName: '刘超',
|
||||
borrowerName: '1',
|
||||
borrowerIdNumber: '511***********3017',
|
||||
assetType: '牛',
|
||||
applicationQuantity: '10头',
|
||||
policyInfo: '查看保单',
|
||||
amount: 100000.00,
|
||||
status: 'pending_binding',
|
||||
applicationTime: '2024-02-29 10:58:06',
|
||||
phone: '13700137000',
|
||||
idCard: '110101199003031234',
|
||||
purpose: '购房',
|
||||
purpose: '养殖贷款',
|
||||
remark: '',
|
||||
auditRecords: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'submit',
|
||||
auditor: '王五',
|
||||
time: '2024-01-16 16:45:00',
|
||||
auditor: '刘超',
|
||||
time: '2024-02-29 10:58:06',
|
||||
comment: '提交申请'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
action: 'reject',
|
||||
auditor: '赵经理',
|
||||
time: '2024-01-17 11:30:00',
|
||||
comment: '抵押物价值不足,不符合放款条件'
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -394,19 +386,21 @@ const applications = ref([
|
||||
const filteredApplications = computed(() => {
|
||||
let result = applications.value
|
||||
|
||||
if (searchText.value) {
|
||||
result = result.filter(app =>
|
||||
app.applicantName.toLowerCase().includes(searchText.value.toLowerCase()) ||
|
||||
app.applicationNumber.toLowerCase().includes(searchText.value.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
if (statusFilter.value) {
|
||||
result = result.filter(app => app.status === statusFilter.value)
|
||||
}
|
||||
|
||||
if (typeFilter.value) {
|
||||
result = result.filter(app => app.type === typeFilter.value)
|
||||
if (searchQuery.value.value) {
|
||||
const searchValue = searchQuery.value.value.toLowerCase()
|
||||
const field = searchQuery.value.field
|
||||
|
||||
result = result.filter(app => {
|
||||
if (field === 'applicationNumber') {
|
||||
return app.applicationNumber.toLowerCase().includes(searchValue)
|
||||
} else if (field === 'customerName') {
|
||||
return app.borrowerName.toLowerCase().includes(searchValue) ||
|
||||
app.farmerName.toLowerCase().includes(searchValue)
|
||||
} else if (field === 'productName') {
|
||||
return app.productName.toLowerCase().includes(searchValue)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -417,8 +411,11 @@ const handleSearch = () => {
|
||||
// 搜索逻辑已在计算属性中处理
|
||||
}
|
||||
|
||||
const handleFilter = () => {
|
||||
// 筛选逻辑已在计算属性中处理
|
||||
const handleReset = () => {
|
||||
searchQuery.value = {
|
||||
field: 'applicationNumber',
|
||||
value: ''
|
||||
}
|
||||
}
|
||||
|
||||
const handleTableChange = (pag) => {
|
||||
@@ -426,10 +423,6 @@ const handleTableChange = (pag) => {
|
||||
pagination.value.pageSize = pag.pageSize
|
||||
}
|
||||
|
||||
const handleAddApplication = () => {
|
||||
message.info('新建申请功能开发中...')
|
||||
}
|
||||
|
||||
const handleView = (record) => {
|
||||
selectedApplication.value = record
|
||||
detailModalVisible.value = true
|
||||
@@ -449,6 +442,11 @@ const handleReject = (record) => {
|
||||
auditModalVisible.value = true
|
||||
}
|
||||
|
||||
const viewPolicy = (record) => {
|
||||
message.info(`查看保单: ${record.applicationNumber}`)
|
||||
// 实际项目中这里会打开保单详情页面
|
||||
}
|
||||
|
||||
const handleAuditSubmit = () => {
|
||||
if (!auditForm.value.comment) {
|
||||
message.error('请输入审核意见')
|
||||
@@ -478,20 +476,22 @@ const handleAuditCancel = () => {
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
pending: 'orange',
|
||||
pending_review: 'blue',
|
||||
verification_pending: 'blue',
|
||||
pending_binding: 'blue',
|
||||
approved: 'green',
|
||||
rejected: 'red',
|
||||
processing: 'blue'
|
||||
rejected: 'red'
|
||||
}
|
||||
return colors[status] || 'default'
|
||||
}
|
||||
|
||||
const getStatusText = (status) => {
|
||||
const texts = {
|
||||
pending: '待审核',
|
||||
pending_review: '待初审',
|
||||
verification_pending: '核验待放款',
|
||||
pending_binding: '待绑定',
|
||||
approved: '已通过',
|
||||
rejected: '已拒绝',
|
||||
processing: '处理中'
|
||||
rejected: '已拒绝'
|
||||
}
|
||||
return texts[status] || status
|
||||
}
|
||||
@@ -549,10 +549,7 @@ const getAuditActionText = (action) => {
|
||||
}
|
||||
|
||||
const formatAmount = (amount) => {
|
||||
if (amount >= 10000) {
|
||||
return (amount / 10000).toFixed(0) + '万'
|
||||
}
|
||||
return amount.toString()
|
||||
return `${amount.toFixed(2)}元`
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
@@ -562,40 +559,43 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.loan-applications {
|
||||
.loan-applications-page {
|
||||
padding: 24px;
|
||||
background-color: #f0f2f5;
|
||||
min-height: calc(100vh - 134px);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
font-size: 28px;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
.search-filter-section {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
border-radius: 8px;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.table-section {
|
||||
margin-top: 16px;
|
||||
.applications-table-section {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.application-detail {
|
||||
@@ -644,4 +644,56 @@ onMounted(() => {
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.ant-table-thead > tr > th) {
|
||||
background-color: #fafafa;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr > td) {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr:hover > td) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 状态标签样式 */
|
||||
:deep(.ant-tag) {
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
:deep(.ant-btn-link) {
|
||||
padding: 4px 8px;
|
||||
height: auto;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 分页样式 */
|
||||
:deep(.ant-pagination) {
|
||||
margin-top: 16px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.search-filter-section .ant-col {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-filter-section .ant-col:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,112 +1,74 @@
|
||||
<template>
|
||||
<div class="loan-contracts">
|
||||
<div class="loan-contracts-page">
|
||||
<div class="page-header">
|
||||
<h1>贷款合同</h1>
|
||||
<p>管理和跟踪贷款合同状态</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="search-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-input-search
|
||||
v-model:value="searchText"
|
||||
placeholder="搜索合同编号或客户姓名"
|
||||
enter-button="搜索"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="statusFilter"
|
||||
placeholder="合同状态"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="draft">草稿</a-select-option>
|
||||
<a-select-option value="pending">待签署</a-select-option>
|
||||
<a-select-option value="signed">已签署</a-select-option>
|
||||
<a-select-option value="active">生效中</a-select-option>
|
||||
<a-select-option value="completed">已完成</a-select-option>
|
||||
<a-select-option value="terminated">已终止</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="typeFilter"
|
||||
placeholder="合同类型"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="personal">个人贷款</a-select-option>
|
||||
<a-select-option value="business">企业贷款</a-select-option>
|
||||
<a-select-option value="mortgage">抵押贷款</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-range-picker
|
||||
v-model:value="dateRange"
|
||||
@change="handleFilter"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="handleCreateContract">
|
||||
<PlusOutlined />
|
||||
新建合同
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<div class="search-filter-section">
|
||||
<a-row :gutter="16" align="middle">
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="searchQuery.field"
|
||||
placeholder="申请单号"
|
||||
style="width: 100%"
|
||||
>
|
||||
<a-select-option value="applicationNumber">申请单号</a-select-option>
|
||||
<a-select-option value="customerName">客户姓名</a-select-option>
|
||||
<a-select-option value="productName">贷款产品</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-input
|
||||
v-model:value="searchQuery.value"
|
||||
placeholder="请输入内容"
|
||||
allow-clear
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<search-outlined /> 搜索
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button @click="handleReset">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 合同列表 -->
|
||||
<div class="table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredContracts"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'type'">
|
||||
<a-tag :color="getTypeColor(record.type)">
|
||||
{{ getTypeText(record.type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'amount'">
|
||||
{{ formatAmount(record.amount) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleView(record)">
|
||||
查看
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="handleEdit(record)">
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="handleDownload(record)">
|
||||
下载
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleSign(record)"
|
||||
v-if="record.status === 'pending'"
|
||||
>
|
||||
签署
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<div class="contracts-table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredContracts"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
:locale="{ emptyText: '暂无数据' }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
<template v-else-if="column.key === 'amount'">
|
||||
{{ formatAmount(record.amount) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleView(record)">
|
||||
查看
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="handleEdit(record)">
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="handleDownload(record)">
|
||||
下载
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 合同详情模态框 -->
|
||||
@@ -232,14 +194,14 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { PlusOutlined } from '@ant-design/icons-vue'
|
||||
import { SearchOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const searchText = ref('')
|
||||
const statusFilter = ref(undefined)
|
||||
const typeFilter = ref(undefined)
|
||||
const dateRange = ref([])
|
||||
const searchQuery = ref({
|
||||
field: 'applicationNumber',
|
||||
value: ''
|
||||
})
|
||||
const detailModalVisible = ref(false)
|
||||
const signModalVisible = ref(false)
|
||||
const selectedContract = ref(null)
|
||||
@@ -255,202 +217,97 @@ const pagination = ref({
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
|
||||
showTotal: (total) => `共${total}条`
|
||||
})
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '合同编号',
|
||||
dataIndex: 'contractNumber',
|
||||
key: 'contractNumber',
|
||||
width: 150
|
||||
title: '申请单号',
|
||||
dataIndex: 'applicationNumber',
|
||||
key: 'applicationNumber',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '客户姓名',
|
||||
dataIndex: 'customerName',
|
||||
key: 'customerName',
|
||||
title: '贷款产品',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '申请养殖户',
|
||||
dataIndex: 'farmerName',
|
||||
key: 'farmerName',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
title: '贷款人姓名',
|
||||
dataIndex: 'borrowerName',
|
||||
key: 'borrowerName',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '贷款人身份证号',
|
||||
dataIndex: 'borrowerIdNumber',
|
||||
key: 'borrowerIdNumber',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '生资种类',
|
||||
dataIndex: 'assetType',
|
||||
key: 'assetType',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 100
|
||||
title: '申请数量',
|
||||
dataIndex: 'applicationQuantity',
|
||||
key: 'applicationQuantity',
|
||||
width: 120,
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '贷款金额',
|
||||
title: '申请额度',
|
||||
dataIndex: 'amount',
|
||||
key: 'amount',
|
||||
width: 120
|
||||
width: 120,
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '期限',
|
||||
dataIndex: 'term',
|
||||
key: 'term',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '年利率',
|
||||
dataIndex: 'interestRate',
|
||||
key: 'interestRate',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '签署日期',
|
||||
dataIndex: 'signDate',
|
||||
key: 'signDate',
|
||||
title: '当前状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 250,
|
||||
width: 200,
|
||||
fixed: 'right'
|
||||
}
|
||||
]
|
||||
|
||||
// 模拟合同数据
|
||||
const contracts = ref([
|
||||
{
|
||||
id: 1,
|
||||
contractNumber: 'CON-202401180001',
|
||||
customerName: '张三',
|
||||
type: 'personal',
|
||||
status: 'signed',
|
||||
amount: 200000,
|
||||
term: 24,
|
||||
interestRate: 6.5,
|
||||
repaymentMethod: 'equal_installment',
|
||||
signDate: '2024-01-18',
|
||||
effectiveDate: '2024-01-18',
|
||||
maturityDate: '2026-01-18',
|
||||
phone: '13800138000',
|
||||
idCard: '110101199001011234',
|
||||
terms: [
|
||||
'借款人应按期还款,不得逾期',
|
||||
'借款人应按时支付利息',
|
||||
'借款人不得将贷款用于非法用途',
|
||||
'借款人应配合银行进行贷后管理'
|
||||
],
|
||||
history: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'create',
|
||||
operator: '系统',
|
||||
time: '2024-01-18 09:30:00',
|
||||
comment: '合同创建'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
action: 'sign',
|
||||
operator: '张三',
|
||||
time: '2024-01-18 10:15:00',
|
||||
comment: '客户签署合同'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
contractNumber: 'CON-202401180002',
|
||||
customerName: '李四',
|
||||
type: 'business',
|
||||
status: 'pending',
|
||||
amount: 1000000,
|
||||
term: 36,
|
||||
interestRate: 5.8,
|
||||
repaymentMethod: 'balloon',
|
||||
signDate: null,
|
||||
effectiveDate: null,
|
||||
maturityDate: '2027-01-18',
|
||||
phone: '13900139000',
|
||||
idCard: '110101199002021234',
|
||||
terms: [
|
||||
'企业应按期还款,不得逾期',
|
||||
'企业应按时支付利息',
|
||||
'企业应提供财务报表',
|
||||
'企业应配合银行进行贷后管理'
|
||||
],
|
||||
history: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'create',
|
||||
operator: '系统',
|
||||
time: '2024-01-18 14:20:00',
|
||||
comment: '合同创建'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
contractNumber: 'CON-202401180003',
|
||||
customerName: '王五',
|
||||
type: 'mortgage',
|
||||
status: 'active',
|
||||
amount: 500000,
|
||||
term: 120,
|
||||
interestRate: 4.5,
|
||||
repaymentMethod: 'equal_installment',
|
||||
signDate: '2024-01-17',
|
||||
effectiveDate: '2024-01-17',
|
||||
maturityDate: '2034-01-17',
|
||||
phone: '13700137000',
|
||||
idCard: '110101199003031234',
|
||||
terms: [
|
||||
'借款人应按期还款,不得逾期',
|
||||
'借款人应按时支付利息',
|
||||
'抵押物不得转让或处置',
|
||||
'借款人应配合银行进行贷后管理'
|
||||
],
|
||||
history: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'create',
|
||||
operator: '系统',
|
||||
time: '2024-01-17 16:45:00',
|
||||
comment: '合同创建'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
action: 'sign',
|
||||
operator: '王五',
|
||||
time: '2024-01-17 17:30:00',
|
||||
comment: '客户签署合同'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
action: 'activate',
|
||||
operator: '系统',
|
||||
time: '2024-01-17 18:00:00',
|
||||
comment: '合同生效'
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
// 模拟合同数据 - 设置为空数据以匹配图片
|
||||
const contracts = ref([])
|
||||
|
||||
// 计算属性
|
||||
const filteredContracts = computed(() => {
|
||||
let result = contracts.value
|
||||
|
||||
if (searchText.value) {
|
||||
result = result.filter(contract =>
|
||||
contract.customerName.toLowerCase().includes(searchText.value.toLowerCase()) ||
|
||||
contract.contractNumber.toLowerCase().includes(searchText.value.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
if (statusFilter.value) {
|
||||
result = result.filter(contract => contract.status === statusFilter.value)
|
||||
}
|
||||
|
||||
if (typeFilter.value) {
|
||||
result = result.filter(contract => contract.type === typeFilter.value)
|
||||
if (searchQuery.value.value) {
|
||||
const searchValue = searchQuery.value.value.toLowerCase()
|
||||
const field = searchQuery.value.field
|
||||
|
||||
result = result.filter(contract => {
|
||||
if (field === 'applicationNumber') {
|
||||
return contract.applicationNumber.toLowerCase().includes(searchValue)
|
||||
} else if (field === 'customerName') {
|
||||
return contract.borrowerName.toLowerCase().includes(searchValue) ||
|
||||
contract.farmerName.toLowerCase().includes(searchValue)
|
||||
} else if (field === 'productName') {
|
||||
return contract.productName.toLowerCase().includes(searchValue)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -461,8 +318,11 @@ const handleSearch = () => {
|
||||
// 搜索逻辑已在计算属性中处理
|
||||
}
|
||||
|
||||
const handleFilter = () => {
|
||||
// 筛选逻辑已在计算属性中处理
|
||||
const handleReset = () => {
|
||||
searchQuery.value = {
|
||||
field: 'applicationNumber',
|
||||
value: ''
|
||||
}
|
||||
}
|
||||
|
||||
const handleTableChange = (pag) => {
|
||||
@@ -470,28 +330,17 @@ const handleTableChange = (pag) => {
|
||||
pagination.value.pageSize = pag.pageSize
|
||||
}
|
||||
|
||||
const handleCreateContract = () => {
|
||||
message.info('新建合同功能开发中...')
|
||||
}
|
||||
|
||||
const handleView = (record) => {
|
||||
selectedContract.value = record
|
||||
detailModalVisible.value = true
|
||||
}
|
||||
|
||||
const handleEdit = (record) => {
|
||||
message.info(`编辑合同: ${record.contractNumber}`)
|
||||
message.info(`编辑合同: ${record.applicationNumber}`)
|
||||
}
|
||||
|
||||
const handleDownload = (record) => {
|
||||
message.info(`下载合同: ${record.contractNumber}`)
|
||||
}
|
||||
|
||||
const handleSign = (record) => {
|
||||
selectedContract.value = record
|
||||
signForm.value.password = ''
|
||||
signForm.value.comment = ''
|
||||
signModalVisible.value = true
|
||||
message.info(`下载合同: ${record.applicationNumber}`)
|
||||
}
|
||||
|
||||
const handleSignSubmit = () => {
|
||||
@@ -525,9 +374,12 @@ const handleSignCancel = () => {
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
draft: 'default',
|
||||
pending: 'orange',
|
||||
signed: 'blue',
|
||||
pending_review: 'blue',
|
||||
verification_pending: 'blue',
|
||||
pending_binding: 'blue',
|
||||
approved: 'green',
|
||||
rejected: 'red',
|
||||
signed: 'green',
|
||||
active: 'green',
|
||||
completed: 'success',
|
||||
terminated: 'red'
|
||||
@@ -537,8 +389,11 @@ const getStatusColor = (status) => {
|
||||
|
||||
const getStatusText = (status) => {
|
||||
const texts = {
|
||||
draft: '草稿',
|
||||
pending: '待签署',
|
||||
pending_review: '待初审',
|
||||
verification_pending: '核验待放款',
|
||||
pending_binding: '待绑定',
|
||||
approved: '已通过',
|
||||
rejected: '已拒绝',
|
||||
signed: '已签署',
|
||||
active: '生效中',
|
||||
completed: '已完成',
|
||||
@@ -596,10 +451,7 @@ const getHistoryActionText = (action) => {
|
||||
}
|
||||
|
||||
const formatAmount = (amount) => {
|
||||
if (amount >= 10000) {
|
||||
return (amount / 10000).toFixed(0) + '万'
|
||||
}
|
||||
return amount.toString()
|
||||
return `${amount.toFixed(2)}元`
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
@@ -609,40 +461,43 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.loan-contracts {
|
||||
.loan-contracts-page {
|
||||
padding: 24px;
|
||||
background-color: #f0f2f5;
|
||||
min-height: calc(100vh - 134px);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
font-size: 28px;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
.search-filter-section {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
border-radius: 8px;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.table-section {
|
||||
margin-top: 16px;
|
||||
.contracts-table-section {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.contract-detail {
|
||||
@@ -713,4 +568,66 @@ onMounted(() => {
|
||||
.sign-content {
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.ant-table-thead > tr > th) {
|
||||
background-color: #fafafa;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr > td) {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr:hover > td) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 状态标签样式 */
|
||||
:deep(.ant-tag) {
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
:deep(.ant-btn-link) {
|
||||
padding: 4px 8px;
|
||||
height: auto;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 分页样式 */
|
||||
:deep(.ant-pagination) {
|
||||
margin-top: 16px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 空数据样式 */
|
||||
:deep(.ant-empty) {
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
:deep(.ant-empty-description) {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.search-filter-section .ant-col {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-filter-section .ant-col:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,469 +1,387 @@
|
||||
<template>
|
||||
<div class="loan-products">
|
||||
<div class="loan-products-page">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header">
|
||||
<h1>贷款商品</h1>
|
||||
<p>管理和配置银行贷款产品</p>
|
||||
<a-button type="primary" @click="handleAddProduct">
|
||||
<plus-outlined />
|
||||
新增贷款
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="search-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-input-search
|
||||
v-model:value="searchText"
|
||||
placeholder="搜索产品名称或编号"
|
||||
enter-button="搜索"
|
||||
@search="handleSearch"
|
||||
<!-- 搜索筛选区域 -->
|
||||
<div class="search-section">
|
||||
<a-row :gutter="16" align="middle">
|
||||
<a-col :span="8">
|
||||
<a-input
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入贷款产品"
|
||||
allow-clear
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<search-outlined />
|
||||
搜索
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button @click="handleReset">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<div class="table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredProducts"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
row-key="id"
|
||||
:locale="{ emptyText: '暂无数据' }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'onSaleStatus'">
|
||||
<a-switch
|
||||
v-model:checked="record.onSaleStatus"
|
||||
@change="handleToggleStatus(record)"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-select
|
||||
v-model:value="statusFilter"
|
||||
placeholder="产品状态"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="active">启用</a-select-option>
|
||||
<a-select-option value="inactive">停用</a-select-option>
|
||||
<a-select-option value="draft">草稿</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-select
|
||||
v-model:value="typeFilter"
|
||||
placeholder="产品类型"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="personal">个人贷款</a-select-option>
|
||||
<a-select-option value="business">企业贷款</a-select-option>
|
||||
<a-select-option value="mortgage">抵押贷款</a-select-option>
|
||||
<a-select-option value="credit">信用贷款</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="handleAddProduct">
|
||||
<PlusOutlined />
|
||||
新建产品
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 产品列表 -->
|
||||
<div class="table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredProducts"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'type'">
|
||||
<a-tag :color="getTypeColor(record.type)">
|
||||
{{ getTypeText(record.type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'interestRate'">
|
||||
{{ record.interestRate }}% - {{ record.maxInterestRate }}%
|
||||
</template>
|
||||
<template v-else-if="column.key === 'amount'">
|
||||
{{ formatAmount(record.minAmount) }} - {{ formatAmount(record.maxAmount) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleView(record)">
|
||||
查看
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="handleEdit(record)">
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleToggleStatus(record)"
|
||||
:danger="record.status === 'active'"
|
||||
>
|
||||
{{ record.status === 'active' ? '停用' : '启用' }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleEdit(record)">编辑</a-button>
|
||||
<a-button type="link" size="small" @click="handleView(record)">详情</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 产品详情模态框 -->
|
||||
<a-modal
|
||||
v-model:open="detailModalVisible"
|
||||
title="产品详情"
|
||||
width="800px"
|
||||
:footer="null"
|
||||
>
|
||||
<div v-if="selectedProduct" class="product-detail">
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item label="产品名称">
|
||||
{{ selectedProduct.name }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="产品编号">
|
||||
{{ selectedProduct.code }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="产品类型">
|
||||
<a-tag :color="getTypeColor(selectedProduct.type)">
|
||||
{{ getTypeText(selectedProduct.type) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="产品状态">
|
||||
<a-tag :color="getStatusColor(selectedProduct.status)">
|
||||
{{ getStatusText(selectedProduct.status) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="贷款额度">
|
||||
{{ formatAmount(selectedProduct.minAmount) }} - {{ formatAmount(selectedProduct.maxAmount) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="贷款期限">
|
||||
{{ selectedProduct.minTerm }} - {{ selectedProduct.maxTerm }} 个月
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="利率范围">
|
||||
{{ selectedProduct.interestRate }}% - {{ selectedProduct.maxInterestRate }}%
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="申请条件">
|
||||
{{ selectedProduct.requirements }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="产品描述" :span="2">
|
||||
{{ selectedProduct.description }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { PlusOutlined } from '@ant-design/icons-vue'
|
||||
import { api } from '@/utils/api'
|
||||
import { PlusOutlined, SearchOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const searchText = ref('')
|
||||
const statusFilter = ref(undefined)
|
||||
const typeFilter = ref(undefined)
|
||||
const detailModalVisible = ref(false)
|
||||
const selectedProduct = ref(null)
|
||||
|
||||
// 分页配置
|
||||
const pagination = ref({
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
})
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '产品名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
{
|
||||
title: '贷款产品',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
sorter: true,
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '产品编号',
|
||||
dataIndex: 'code',
|
||||
key: 'code',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '贷款额度',
|
||||
dataIndex: 'amount',
|
||||
key: 'amount',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '利率范围',
|
||||
dataIndex: 'interestRate',
|
||||
key: 'interestRate',
|
||||
{
|
||||
title: '贷款额度',
|
||||
dataIndex: 'loanAmount',
|
||||
key: 'loanAmount',
|
||||
sorter: true,
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '期限',
|
||||
dataIndex: 'term',
|
||||
key: 'term',
|
||||
{
|
||||
title: '贷款周期',
|
||||
dataIndex: 'loanTerm',
|
||||
key: 'loanTerm',
|
||||
sorter: true,
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '贷款利率',
|
||||
dataIndex: 'interestRate',
|
||||
key: 'interestRate',
|
||||
sorter: true,
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '服务区域',
|
||||
dataIndex: 'serviceArea',
|
||||
key: 'serviceArea',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '服务电话',
|
||||
dataIndex: 'servicePhone',
|
||||
key: 'servicePhone',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
{
|
||||
title: '服务客户总数量',
|
||||
dataIndex: 'totalCustomers',
|
||||
key: 'totalCustomers',
|
||||
sorter: true,
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '监管中客户',
|
||||
dataIndex: 'supervisionCustomers',
|
||||
key: 'supervisionCustomers',
|
||||
sorter: true,
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '已结项客户',
|
||||
dataIndex: 'completedCustomers',
|
||||
key: 'completedCustomers',
|
||||
sorter: true,
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '添加时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
sorter: true,
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '在售状态',
|
||||
dataIndex: 'onSaleStatus',
|
||||
key: 'onSaleStatus',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
fixed: 'right'
|
||||
}
|
||||
]
|
||||
|
||||
// 产品数据
|
||||
const products = ref([])
|
||||
|
||||
// 模拟产品数据(作为备用)
|
||||
const mockProducts = [
|
||||
// 模拟数据
|
||||
const products = ref([
|
||||
{
|
||||
id: 1,
|
||||
productName: '惠农贷',
|
||||
loanAmount: '50000~5000000元',
|
||||
loanTerm: '24',
|
||||
interestRate: '3.90%',
|
||||
serviceArea: '内蒙古自治区:通辽市',
|
||||
servicePhone: '15004901368',
|
||||
totalCustomers: 16,
|
||||
supervisionCustomers: 11,
|
||||
completedCustomers: 5,
|
||||
createTime: '2023-12-18 16:23:03',
|
||||
onSaleStatus: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '个人消费贷款',
|
||||
code: 'LOAN-002',
|
||||
type: 'personal',
|
||||
status: 'active',
|
||||
minAmount: 10000,
|
||||
maxAmount: 500000,
|
||||
minTerm: 6,
|
||||
maxTerm: 60,
|
||||
interestRate: 6.8,
|
||||
maxInterestRate: 12.5,
|
||||
requirements: '年满18周岁,有稳定收入来源,信用记录良好',
|
||||
description: '用于个人消费支出的信用贷款产品',
|
||||
createTime: '2024-01-05'
|
||||
productName: '中国工商银行扎旗支行"畜禽活体抵押"',
|
||||
loanAmount: '200000~1000000元',
|
||||
loanTerm: '12',
|
||||
interestRate: '4.70%',
|
||||
serviceArea: '内蒙古自治区:通辽市',
|
||||
servicePhone: '15004901368',
|
||||
totalCustomers: 10,
|
||||
supervisionCustomers: 5,
|
||||
completedCustomers: 5,
|
||||
createTime: '2023-06-20 17:36:17',
|
||||
onSaleStatus: true,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '企业经营贷款',
|
||||
code: 'LOAN-003',
|
||||
type: 'business',
|
||||
status: 'active',
|
||||
minAmount: 500000,
|
||||
maxAmount: 50000000,
|
||||
minTerm: 12,
|
||||
maxTerm: 120,
|
||||
interestRate: 5.2,
|
||||
maxInterestRate: 8.5,
|
||||
requirements: '企业成立满2年,年营业额达到500万以上',
|
||||
description: '为企业经营发展提供的流动资金贷款',
|
||||
createTime: '2024-01-10'
|
||||
productName: '中国银行扎旗支行"畜禽活体抵押"',
|
||||
loanAmount: '200000~1000000元',
|
||||
loanTerm: '12',
|
||||
interestRate: '4.60%',
|
||||
serviceArea: '内蒙古自治区:通辽市',
|
||||
servicePhone: '15004901368',
|
||||
totalCustomers: 2,
|
||||
supervisionCustomers: 2,
|
||||
completedCustomers: 0,
|
||||
createTime: '2023-06-20 17:34:33',
|
||||
onSaleStatus: true,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '小微企业贷款',
|
||||
code: 'LOAN-004',
|
||||
type: 'business',
|
||||
status: 'draft',
|
||||
minAmount: 50000,
|
||||
maxAmount: 1000000,
|
||||
minTerm: 6,
|
||||
maxTerm: 36,
|
||||
interestRate: 7.5,
|
||||
maxInterestRate: 10.5,
|
||||
requirements: '小微企业,年营业额100万以上',
|
||||
description: '专为小微企业提供的快速贷款产品',
|
||||
createTime: '2024-01-15'
|
||||
}
|
||||
];
|
||||
productName: '中国农业银行扎旗支行"畜禽活体抵押"',
|
||||
loanAmount: '200000~1000000元',
|
||||
loanTerm: '12',
|
||||
interestRate: '4.80%',
|
||||
serviceArea: '内蒙古自治区:通辽市',
|
||||
servicePhone: '15004901368',
|
||||
totalCustomers: 26,
|
||||
supervisionCustomers: 24,
|
||||
completedCustomers: 2,
|
||||
createTime: '2023-06-20 17:09:39',
|
||||
onSaleStatus: true,
|
||||
},
|
||||
])
|
||||
|
||||
// 计算属性
|
||||
const filteredProducts = computed(() => {
|
||||
let result = products.value
|
||||
|
||||
if (searchText.value) {
|
||||
result = result.filter(product =>
|
||||
product.name.toLowerCase().includes(searchText.value.toLowerCase()) ||
|
||||
product.code.toLowerCase().includes(searchText.value.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
if (statusFilter.value) {
|
||||
result = result.filter(product => product.status === statusFilter.value)
|
||||
}
|
||||
|
||||
if (typeFilter.value) {
|
||||
result = result.filter(product => product.type === typeFilter.value)
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
// 方法
|
||||
|
||||
const handleAddProduct = () => {
|
||||
message.info('新建产品功能开发中...')
|
||||
}
|
||||
|
||||
const handleView = (record) => {
|
||||
selectedProduct.value = record
|
||||
detailModalVisible.value = true
|
||||
}
|
||||
|
||||
const handleEdit = (record) => {
|
||||
message.info(`编辑产品: ${record.name}`)
|
||||
}
|
||||
|
||||
const handleToggleStatus = (record) => {
|
||||
const newStatus = record.status === 'active' ? 'inactive' : 'active'
|
||||
record.status = newStatus
|
||||
message.success(`产品已${newStatus === 'active' ? '启用' : '停用'}`)
|
||||
}
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
active: 'green',
|
||||
inactive: 'red',
|
||||
draft: 'orange'
|
||||
}
|
||||
return colors[status] || 'default'
|
||||
}
|
||||
|
||||
const getStatusText = (status) => {
|
||||
const texts = {
|
||||
active: '启用',
|
||||
inactive: '停用',
|
||||
draft: '草稿'
|
||||
}
|
||||
return texts[status] || status
|
||||
}
|
||||
|
||||
const getTypeColor = (type) => {
|
||||
const colors = {
|
||||
personal: 'blue',
|
||||
business: 'green',
|
||||
mortgage: 'purple',
|
||||
credit: 'orange'
|
||||
}
|
||||
return colors[type] || 'default'
|
||||
}
|
||||
|
||||
const getTypeText = (type) => {
|
||||
const texts = {
|
||||
personal: '个人贷款',
|
||||
business: '企业贷款',
|
||||
mortgage: '抵押贷款',
|
||||
credit: '信用贷款'
|
||||
}
|
||||
return texts[type] || type
|
||||
}
|
||||
|
||||
const formatAmount = (amount) => {
|
||||
if (amount >= 10000) {
|
||||
return (amount / 10000).toFixed(0) + '万'
|
||||
}
|
||||
return amount.toString()
|
||||
}
|
||||
|
||||
// API调用函数
|
||||
const fetchProducts = async (params = {}) => {
|
||||
const fetchProducts = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await api.loanProducts.getList({
|
||||
page: pagination.value.current,
|
||||
limit: pagination.value.pageSize,
|
||||
search: searchText.value,
|
||||
status: statusFilter.value,
|
||||
type: typeFilter.value,
|
||||
...params
|
||||
})
|
||||
// 实际项目中这里会调用API获取数据
|
||||
// const response = await api.loanProducts.getList({
|
||||
// page: pagination.current,
|
||||
// pageSize: pagination.pageSize,
|
||||
// search: searchText.value,
|
||||
// })
|
||||
|
||||
if (response.success) {
|
||||
products.value = response.data.products || []
|
||||
pagination.value.total = response.data.pagination?.total || 0
|
||||
} else {
|
||||
message.error(response.message || '获取产品列表失败')
|
||||
// 使用模拟数据作为备用
|
||||
products.value = mockProducts
|
||||
pagination.value.total = mockProducts.length
|
||||
}
|
||||
// 使用模拟数据
|
||||
pagination.total = products.value.length
|
||||
} catch (error) {
|
||||
console.error('获取产品列表失败:', error)
|
||||
message.error('获取产品列表失败')
|
||||
// 使用模拟数据作为备用
|
||||
products.value = mockProducts
|
||||
pagination.value.total = mockProducts.length
|
||||
console.error('获取贷款商品失败:', error)
|
||||
message.error('获取贷款商品失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const filteredProducts = computed(() => {
|
||||
let result = products.value
|
||||
|
||||
if (searchText.value) {
|
||||
result = result.filter(product =>
|
||||
product.productName.toLowerCase().includes(searchText.value.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.value.current = 1
|
||||
pagination.current = 1
|
||||
fetchProducts()
|
||||
}
|
||||
|
||||
const handleFilter = () => {
|
||||
pagination.value.current = 1
|
||||
const handleReset = () => {
|
||||
searchText.value = ''
|
||||
pagination.current = 1
|
||||
fetchProducts()
|
||||
}
|
||||
|
||||
const handleTableChange = (paginationInfo) => {
|
||||
pagination.value = paginationInfo
|
||||
const handleAddProduct = () => {
|
||||
message.info('新增贷款功能开发中...')
|
||||
}
|
||||
|
||||
const handleEdit = (record) => {
|
||||
message.info(`编辑产品: ${record.productName}`)
|
||||
}
|
||||
|
||||
const handleView = (record) => {
|
||||
message.info(`查看详情: ${record.productName}`)
|
||||
}
|
||||
|
||||
const handleToggleStatus = (record) => {
|
||||
const status = record.onSaleStatus ? '启用' : '停用'
|
||||
message.success(`${record.productName} 已${status}`)
|
||||
}
|
||||
|
||||
const handleTableChange = (pag, filters, sorter) => {
|
||||
pagination.current = pag.current
|
||||
pagination.pageSize = pag.pageSize
|
||||
fetchProducts()
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
fetchProducts()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.loan-products {
|
||||
padding: 24px;
|
||||
.loan-products-page {
|
||||
padding: 0;
|
||||
background: #f0f2f5;
|
||||
min-height: calc(100vh - 134px);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
padding: 24px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
margin-top: 16px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.product-detail {
|
||||
padding: 16px 0;
|
||||
/* 表格样式优化 */
|
||||
:deep(.ant-table-thead > tr > th) {
|
||||
background: #fafafa;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
</style>
|
||||
|
||||
:deep(.ant-table-tbody > tr:hover > td) {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr:nth-child(even) > td) {
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr:nth-child(odd) > td) {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* 开关样式 */
|
||||
:deep(.ant-switch-checked) {
|
||||
background-color: #1890ff;
|
||||
}
|
||||
|
||||
/* 链接按钮样式 */
|
||||
:deep(.ant-btn-link) {
|
||||
color: #1890ff;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
:deep(.ant-btn-link:hover) {
|
||||
color: #40a9ff;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.search-section .ant-col {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-section .ant-col:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,125 +1,72 @@
|
||||
<template>
|
||||
<div class="loan-release">
|
||||
<div class="loan-release-page">
|
||||
<div class="page-header">
|
||||
<h1>贷款解押</h1>
|
||||
<p>管理和处理贷款抵押物解押业务</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="search-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-input-search
|
||||
v-model:value="searchText"
|
||||
placeholder="搜索客户姓名或合同编号"
|
||||
enter-button="搜索"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="statusFilter"
|
||||
placeholder="解押状态"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="pending">待处理</a-select-option>
|
||||
<a-select-option value="processing">处理中</a-select-option>
|
||||
<a-select-option value="completed">已完成</a-select-option>
|
||||
<a-select-option value="rejected">已拒绝</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="typeFilter"
|
||||
placeholder="抵押物类型"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="house">房产</a-select-option>
|
||||
<a-select-option value="car">车辆</a-select-option>
|
||||
<a-select-option value="land">土地</a-select-option>
|
||||
<a-select-option value="equipment">设备</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-range-picker
|
||||
v-model:value="dateRange"
|
||||
@change="handleFilter"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="handleCreateRelease">
|
||||
<PlusOutlined />
|
||||
新建解押
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<div class="search-filter-section">
|
||||
<a-row :gutter="16" align="middle">
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="searchQuery.field"
|
||||
placeholder="申请单号"
|
||||
style="width: 100%"
|
||||
>
|
||||
<a-select-option value="applicationNumber">申请单号</a-select-option>
|
||||
<a-select-option value="customerName">客户姓名</a-select-option>
|
||||
<a-select-option value="productName">贷款产品</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-input
|
||||
v-model:value="searchQuery.value"
|
||||
placeholder="请输入内容"
|
||||
allow-clear
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<search-outlined /> 搜索
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button @click="handleReset">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 解押列表 -->
|
||||
<div class="table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredReleases"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'collateralType'">
|
||||
<a-tag :color="getCollateralTypeColor(record.collateralType)">
|
||||
{{ getCollateralTypeText(record.collateralType) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'loanAmount'">
|
||||
{{ formatAmount(record.loanAmount) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'collateralValue'">
|
||||
{{ formatAmount(record.collateralValue) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleView(record)">
|
||||
查看
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleProcess(record)"
|
||||
v-if="record.status === 'pending'"
|
||||
>
|
||||
处理
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleComplete(record)"
|
||||
v-if="record.status === 'processing'"
|
||||
>
|
||||
完成
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleReject(record)"
|
||||
v-if="record.status === 'pending'"
|
||||
danger
|
||||
>
|
||||
拒绝
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<div class="releases-table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredReleases"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
:expand-row-by-click="false"
|
||||
:expand-icon-column-index="0"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
<template v-else-if="column.key === 'releaseAmount'">
|
||||
{{ formatAmount(record.releaseAmount) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleEdit(record)">
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="handleView(record)">
|
||||
详情
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 解押详情模态框 -->
|
||||
@@ -245,14 +192,14 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { PlusOutlined } from '@ant-design/icons-vue'
|
||||
import { SearchOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const searchText = ref('')
|
||||
const statusFilter = ref(undefined)
|
||||
const typeFilter = ref(undefined)
|
||||
const dateRange = ref([])
|
||||
const searchQuery = ref({
|
||||
field: 'applicationNumber',
|
||||
value: ''
|
||||
})
|
||||
const detailModalVisible = ref(false)
|
||||
const processModalVisible = ref(false)
|
||||
const selectedRelease = ref(null)
|
||||
@@ -269,58 +216,78 @@ const pagination = ref({
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
|
||||
showTotal: (total) => `共${total}条`
|
||||
})
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '解押编号',
|
||||
dataIndex: 'releaseNumber',
|
||||
key: 'releaseNumber',
|
||||
width: 150
|
||||
title: '',
|
||||
key: 'expand',
|
||||
width: 50,
|
||||
customRender: () => '>'
|
||||
},
|
||||
{
|
||||
title: '客户姓名',
|
||||
dataIndex: 'customerName',
|
||||
key: 'customerName',
|
||||
title: '申请单号',
|
||||
dataIndex: 'applicationNumber',
|
||||
key: 'applicationNumber',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '贷款产品',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '申请养殖户',
|
||||
dataIndex: 'farmerName',
|
||||
key: 'farmerName',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '合同编号',
|
||||
dataIndex: 'contractNumber',
|
||||
key: 'contractNumber',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '抵押物类型',
|
||||
dataIndex: 'collateralType',
|
||||
key: 'collateralType',
|
||||
title: '申请人姓名',
|
||||
dataIndex: 'applicantName',
|
||||
key: 'applicantName',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
title: '申请人身份证号',
|
||||
dataIndex: 'applicantIdNumber',
|
||||
key: 'applicantIdNumber',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '申请人电话',
|
||||
dataIndex: 'applicantPhone',
|
||||
key: 'applicantPhone',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '生资种类',
|
||||
dataIndex: 'assetType',
|
||||
key: 'assetType',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '贷款金额',
|
||||
dataIndex: 'loanAmount',
|
||||
key: 'loanAmount',
|
||||
width: 120
|
||||
title: '申请解押数量',
|
||||
dataIndex: 'releaseQuantity',
|
||||
key: 'releaseQuantity',
|
||||
width: 150,
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '抵押物价值',
|
||||
dataIndex: 'collateralValue',
|
||||
key: 'collateralValue',
|
||||
width: 120
|
||||
title: '申请解押额度',
|
||||
dataIndex: 'releaseAmount',
|
||||
key: 'releaseAmount',
|
||||
width: 150,
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '申请时间',
|
||||
dataIndex: 'applicationTime',
|
||||
key: 'applicationTime',
|
||||
width: 150
|
||||
title: '当前状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
@@ -334,102 +301,180 @@ const columns = [
|
||||
const releases = ref([
|
||||
{
|
||||
id: 1,
|
||||
releaseNumber: 'REL-202401180001',
|
||||
customerName: '张三',
|
||||
contractNumber: 'CON-202401180001',
|
||||
collateralType: 'house',
|
||||
status: 'pending',
|
||||
collateralDescription: '北京市朝阳区某小区3室2厅,建筑面积120平米',
|
||||
loanAmount: 200000,
|
||||
collateralValue: 500000,
|
||||
applicationTime: '2024-01-18 09:30:00',
|
||||
processTime: null,
|
||||
completeTime: null,
|
||||
phone: '13800138000',
|
||||
idCard: '110101199001011234',
|
||||
reason: '贷款已还清,申请解押房产',
|
||||
remark: '',
|
||||
applicationNumber: '20240227145555918',
|
||||
productName: '中国工商银行扎旗支行"畜禽活体抵押"',
|
||||
farmerName: '刘超',
|
||||
applicantName: '1',
|
||||
applicantIdNumber: '511***********3017',
|
||||
applicantPhone: '138****0459',
|
||||
assetType: '牛',
|
||||
releaseQuantity: '10头',
|
||||
releaseAmount: 10000.00,
|
||||
status: 'released',
|
||||
applicationTime: '2024-02-27 14:55:55',
|
||||
history: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'apply',
|
||||
operator: '张三',
|
||||
time: '2024-01-18 09:30:00',
|
||||
operator: '刘超',
|
||||
time: '2024-02-27 14:55:55',
|
||||
comment: '提交解押申请'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
action: 'complete',
|
||||
operator: '系统',
|
||||
time: '2024-02-27 15:30:00',
|
||||
comment: '解押手续办理完成'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
releaseNumber: 'REL-202401180002',
|
||||
customerName: '李四',
|
||||
contractNumber: 'CON-202401180002',
|
||||
collateralType: 'car',
|
||||
status: 'processing',
|
||||
collateralDescription: '2020年宝马X5,车牌号京A12345',
|
||||
loanAmount: 500000,
|
||||
collateralValue: 600000,
|
||||
applicationTime: '2024-01-17 14:20:00',
|
||||
processTime: '2024-01-18 10:15:00',
|
||||
completeTime: null,
|
||||
phone: '13900139000',
|
||||
idCard: '110101199002021234',
|
||||
reason: '车辆贷款已还清,申请解押车辆',
|
||||
remark: '',
|
||||
applicationNumber: '20240226113416302',
|
||||
productName: '惠农贷',
|
||||
farmerName: '刘超',
|
||||
applicantName: '1',
|
||||
applicantIdNumber: '511***********3017',
|
||||
applicantPhone: '138****0459',
|
||||
assetType: '牛',
|
||||
releaseQuantity: '10头',
|
||||
releaseAmount: 0.00,
|
||||
status: 'released',
|
||||
applicationTime: '2024-02-26 11:34:16',
|
||||
history: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'apply',
|
||||
operator: '李四',
|
||||
time: '2024-01-17 14:20:00',
|
||||
operator: '刘超',
|
||||
time: '2024-02-26 11:34:16',
|
||||
comment: '提交解押申请'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
action: 'process',
|
||||
operator: '王经理',
|
||||
time: '2024-01-18 10:15:00',
|
||||
comment: '开始处理解押申请'
|
||||
action: 'complete',
|
||||
operator: '系统',
|
||||
time: '2024-02-26 12:00:00',
|
||||
comment: '解押手续办理完成'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
releaseNumber: 'REL-202401180003',
|
||||
customerName: '王五',
|
||||
contractNumber: 'CON-202401180003',
|
||||
collateralType: 'land',
|
||||
status: 'completed',
|
||||
collateralDescription: '北京市海淀区某地块,面积500平米',
|
||||
loanAmount: 1000000,
|
||||
collateralValue: 2000000,
|
||||
applicationTime: '2024-01-16 16:45:00',
|
||||
processTime: '2024-01-17 09:30:00',
|
||||
completeTime: '2024-01-17 15:20:00',
|
||||
phone: '13700137000',
|
||||
idCard: '110101199003031234',
|
||||
reason: '土地贷款已还清,申请解押土地',
|
||||
remark: '',
|
||||
applicationNumber: '20240223140542290',
|
||||
productName: '惠农贷',
|
||||
farmerName: '刘超',
|
||||
applicantName: '张洪彬',
|
||||
applicantIdNumber: '511***********3017',
|
||||
applicantPhone: '138****0459',
|
||||
assetType: '牛',
|
||||
releaseQuantity: '10头',
|
||||
releaseAmount: 1000000.00,
|
||||
status: 'released',
|
||||
applicationTime: '2024-02-23 14:05:42',
|
||||
history: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'apply',
|
||||
operator: '王五',
|
||||
time: '2024-01-16 16:45:00',
|
||||
operator: '张洪彬',
|
||||
time: '2024-02-23 14:05:42',
|
||||
comment: '提交解押申请'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
action: 'process',
|
||||
operator: '赵经理',
|
||||
time: '2024-01-17 09:30:00',
|
||||
comment: '开始处理解押申请'
|
||||
action: 'complete',
|
||||
operator: '系统',
|
||||
time: '2024-02-23 15:00:00',
|
||||
comment: '解押手续办理完成'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
applicationNumber: '20231131890123456',
|
||||
productName: '中国工商银行扎旗支行"畜禽活体抵押"',
|
||||
farmerName: '田小平',
|
||||
applicantName: '田小平',
|
||||
applicantIdNumber: '150***********3140',
|
||||
applicantPhone: '139****5685',
|
||||
assetType: '牛',
|
||||
releaseQuantity: '30头',
|
||||
releaseAmount: 420000.00,
|
||||
status: 'released',
|
||||
applicationTime: '2023-11-31 08:90:12',
|
||||
history: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'apply',
|
||||
operator: '田小平',
|
||||
time: '2023-11-31 08:90:12',
|
||||
comment: '提交解押申请'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
id: 2,
|
||||
action: 'complete',
|
||||
operator: '赵经理',
|
||||
time: '2024-01-17 15:20:00',
|
||||
operator: '系统',
|
||||
time: '2023-11-31 10:00:00',
|
||||
comment: '解押手续办理完成'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
applicationNumber: '20231131789012345',
|
||||
productName: '中国农业银行扎旗支行"畜禽活体抵押"',
|
||||
farmerName: '杜宝民',
|
||||
applicantName: '杜宝民',
|
||||
applicantIdNumber: '150***********7238',
|
||||
applicantPhone: '159****2749',
|
||||
assetType: '牛',
|
||||
releaseQuantity: '30头',
|
||||
releaseAmount: 420000.00,
|
||||
status: 'released',
|
||||
applicationTime: '2023-11-31 07:89:01',
|
||||
history: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'apply',
|
||||
operator: '杜宝民',
|
||||
time: '2023-11-31 07:89:01',
|
||||
comment: '提交解押申请'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
action: 'complete',
|
||||
operator: '系统',
|
||||
time: '2023-11-31 09:00:00',
|
||||
comment: '解押手续办理完成'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
applicationNumber: '20231131901234567',
|
||||
productName: '中国农业银行扎旗支行"畜禽活体抵押"',
|
||||
farmerName: '满良',
|
||||
applicantName: '满良',
|
||||
applicantIdNumber: '150***********5140',
|
||||
applicantPhone: '158****9502',
|
||||
assetType: '牛',
|
||||
releaseQuantity: '38头',
|
||||
releaseAmount: 530000.00,
|
||||
status: 'released',
|
||||
applicationTime: '2023-11-31 09:01:23',
|
||||
history: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'apply',
|
||||
operator: '满良',
|
||||
time: '2023-11-31 09:01:23',
|
||||
comment: '提交解押申请'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
action: 'complete',
|
||||
operator: '系统',
|
||||
time: '2023-11-31 11:00:00',
|
||||
comment: '解押手续办理完成'
|
||||
}
|
||||
]
|
||||
@@ -440,19 +485,21 @@ const releases = ref([
|
||||
const filteredReleases = computed(() => {
|
||||
let result = releases.value
|
||||
|
||||
if (searchText.value) {
|
||||
result = result.filter(release =>
|
||||
release.customerName.toLowerCase().includes(searchText.value.toLowerCase()) ||
|
||||
release.contractNumber.toLowerCase().includes(searchText.value.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
if (statusFilter.value) {
|
||||
result = result.filter(release => release.status === statusFilter.value)
|
||||
}
|
||||
|
||||
if (typeFilter.value) {
|
||||
result = result.filter(release => release.collateralType === typeFilter.value)
|
||||
if (searchQuery.value.value) {
|
||||
const searchValue = searchQuery.value.value.toLowerCase()
|
||||
const field = searchQuery.value.field
|
||||
|
||||
result = result.filter(release => {
|
||||
if (field === 'applicationNumber') {
|
||||
return release.applicationNumber.toLowerCase().includes(searchValue)
|
||||
} else if (field === 'customerName') {
|
||||
return release.applicantName.toLowerCase().includes(searchValue) ||
|
||||
release.farmerName.toLowerCase().includes(searchValue)
|
||||
} else if (field === 'productName') {
|
||||
return release.productName.toLowerCase().includes(searchValue)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -463,8 +510,11 @@ const handleSearch = () => {
|
||||
// 搜索逻辑已在计算属性中处理
|
||||
}
|
||||
|
||||
const handleFilter = () => {
|
||||
// 筛选逻辑已在计算属性中处理
|
||||
const handleReset = () => {
|
||||
searchQuery.value = {
|
||||
field: 'applicationNumber',
|
||||
value: ''
|
||||
}
|
||||
}
|
||||
|
||||
const handleTableChange = (pag) => {
|
||||
@@ -472,51 +522,13 @@ const handleTableChange = (pag) => {
|
||||
pagination.value.pageSize = pag.pageSize
|
||||
}
|
||||
|
||||
const handleCreateRelease = () => {
|
||||
message.info('新建解押功能开发中...')
|
||||
}
|
||||
|
||||
const handleView = (record) => {
|
||||
selectedRelease.value = record
|
||||
detailModalVisible.value = true
|
||||
}
|
||||
|
||||
const handleProcess = (record) => {
|
||||
selectedRelease.value = record
|
||||
processForm.value.result = 'approve'
|
||||
processForm.value.comment = ''
|
||||
processForm.value.remark = ''
|
||||
processModalVisible.value = true
|
||||
}
|
||||
|
||||
const handleComplete = (record) => {
|
||||
record.status = 'completed'
|
||||
record.completeTime = new Date().toLocaleString()
|
||||
|
||||
record.history.push({
|
||||
id: Date.now(),
|
||||
action: 'complete',
|
||||
operator: '当前用户',
|
||||
time: new Date().toLocaleString(),
|
||||
comment: '解押手续办理完成'
|
||||
})
|
||||
|
||||
message.success('解押完成')
|
||||
}
|
||||
|
||||
const handleReject = (record) => {
|
||||
record.status = 'rejected'
|
||||
record.processTime = new Date().toLocaleString()
|
||||
|
||||
record.history.push({
|
||||
id: Date.now(),
|
||||
action: 'reject',
|
||||
operator: '当前用户',
|
||||
time: new Date().toLocaleString(),
|
||||
comment: '解押申请被拒绝'
|
||||
})
|
||||
|
||||
message.success('解押申请已拒绝')
|
||||
const handleEdit = (record) => {
|
||||
message.info(`编辑解押申请: ${record.applicationNumber}`)
|
||||
}
|
||||
|
||||
const handleProcessSubmit = () => {
|
||||
@@ -548,6 +560,7 @@ const handleProcessCancel = () => {
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
released: 'default',
|
||||
pending: 'orange',
|
||||
processing: 'blue',
|
||||
completed: 'green',
|
||||
@@ -558,6 +571,7 @@ const getStatusColor = (status) => {
|
||||
|
||||
const getStatusText = (status) => {
|
||||
const texts = {
|
||||
released: '已解押',
|
||||
pending: '待处理',
|
||||
processing: '处理中',
|
||||
completed: '已完成',
|
||||
@@ -607,10 +621,7 @@ const getHistoryActionText = (action) => {
|
||||
}
|
||||
|
||||
const formatAmount = (amount) => {
|
||||
if (amount >= 10000) {
|
||||
return (amount / 10000).toFixed(0) + '万'
|
||||
}
|
||||
return amount.toString()
|
||||
return `${amount.toFixed(2)}元`
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
@@ -620,40 +631,43 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.loan-release {
|
||||
.loan-release-page {
|
||||
padding: 24px;
|
||||
background-color: #f0f2f5;
|
||||
min-height: calc(100vh - 134px);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
font-size: 28px;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
.search-filter-section {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
border-radius: 8px;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.table-section {
|
||||
margin-top: 16px;
|
||||
.releases-table-section {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.release-detail {
|
||||
@@ -706,4 +720,56 @@ onMounted(() => {
|
||||
.process-content {
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.ant-table-thead > tr > th) {
|
||||
background-color: #fafafa;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr > td) {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr:hover > td) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 状态标签样式 */
|
||||
:deep(.ant-tag) {
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
:deep(.ant-btn-link) {
|
||||
padding: 4px 8px;
|
||||
height: auto;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 分页样式 */
|
||||
:deep(.ant-pagination) {
|
||||
margin-top: 16px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.search-filter-section .ant-col {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-filter-section .ant-col:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
184
bank-frontend/test-add-project.html
Normal file
184
bank-frontend/test-add-project.html
Normal file
@@ -0,0 +1,184 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>新增项目功能测试</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||||
.form-group { margin-bottom: 15px; }
|
||||
label { display: block; margin-bottom: 5px; font-weight: bold; }
|
||||
input, select, textarea { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
|
||||
button { background: #1890ff; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }
|
||||
button:hover { background: #40a9ff; }
|
||||
.result { margin-top: 20px; padding: 15px; background: #f5f5f5; border-radius: 4px; }
|
||||
.success { background: #f6ffed; border: 1px solid #b7eb8f; }
|
||||
.error { background: #fff2f0; border: 1px solid #ffccc7; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>新增项目功能测试</h1>
|
||||
|
||||
<div class="form-group">
|
||||
<label>项目名称:</label>
|
||||
<input type="text" id="name" value="测试项目_20241220" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>养殖场名称:</label>
|
||||
<input type="text" id="farmName" value="测试养殖场" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>监管对象:</label>
|
||||
<input type="text" id="supervisionObject" value="牛" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>监管周期:</label>
|
||||
<input type="text" id="supervisionPeriod" value="12个月" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>监管数量:</label>
|
||||
<input type="number" id="supervisionQuantity" value="100" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>监管金额:</label>
|
||||
<input type="number" id="supervisionAmount" value="500000" step="0.01" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>起始时间:</label>
|
||||
<input type="date" id="startTime" value="2024-01-01" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>结束时间:</label>
|
||||
<input type="date" id="endTime" value="2024-12-31" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>耳标数量:</label>
|
||||
<input type="number" id="earTag" value="50" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>项圈数量:</label>
|
||||
<input type="number" id="collar" value="30" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>主机数量:</label>
|
||||
<input type="number" id="host" value="20" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>贷款专员:</label>
|
||||
<input type="text" id="loanOfficer" value="张专员" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>项目描述:</label>
|
||||
<textarea id="description" rows="3">这是一个测试项目</textarea>
|
||||
</div>
|
||||
|
||||
<button onclick="testCreateProject()">测试创建项目</button>
|
||||
|
||||
<div id="result" class="result" style="display: none;"></div>
|
||||
|
||||
<script>
|
||||
let token = '';
|
||||
|
||||
async function testCreateProject() {
|
||||
try {
|
||||
// 1. 先登录
|
||||
console.log('1. 登录获取token...');
|
||||
const loginResponse = await fetch('http://localhost:5351/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: 'admin',
|
||||
password: 'Admin123456'
|
||||
})
|
||||
});
|
||||
|
||||
const loginData = await loginResponse.json();
|
||||
if (!loginData.success) {
|
||||
throw new Error('登录失败: ' + loginData.message);
|
||||
}
|
||||
|
||||
token = loginData.data.token;
|
||||
console.log('登录成功,token:', token.substring(0, 20) + '...');
|
||||
|
||||
// 2. 创建项目
|
||||
console.log('2. 创建项目...');
|
||||
const projectData = {
|
||||
name: document.getElementById('name').value,
|
||||
status: 'supervision',
|
||||
farmName: document.getElementById('farmName').value,
|
||||
supervisionObject: document.getElementById('supervisionObject').value,
|
||||
supervisionQuantity: parseInt(document.getElementById('supervisionQuantity').value),
|
||||
supervisionPeriod: document.getElementById('supervisionPeriod').value,
|
||||
supervisionAmount: parseFloat(document.getElementById('supervisionAmount').value),
|
||||
startTime: document.getElementById('startTime').value,
|
||||
endTime: document.getElementById('endTime').value,
|
||||
earTag: parseInt(document.getElementById('earTag').value),
|
||||
collar: parseInt(document.getElementById('collar').value),
|
||||
host: parseInt(document.getElementById('host').value),
|
||||
loanOfficer: document.getElementById('loanOfficer').value,
|
||||
description: document.getElementById('description').value
|
||||
};
|
||||
|
||||
console.log('项目数据:', projectData);
|
||||
|
||||
const createResponse = await fetch('http://localhost:5351/api/projects', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(projectData)
|
||||
});
|
||||
|
||||
const createData = await createResponse.json();
|
||||
console.log('创建响应:', createData);
|
||||
|
||||
const resultDiv = document.getElementById('result');
|
||||
resultDiv.style.display = 'block';
|
||||
|
||||
if (createData.success) {
|
||||
resultDiv.className = 'result success';
|
||||
resultDiv.innerHTML = `
|
||||
<h3>✅ 项目创建成功!</h3>
|
||||
<p><strong>项目ID:</strong> ${createData.data.id}</p>
|
||||
<p><strong>项目名称:</strong> ${createData.data.name}</p>
|
||||
<p><strong>养殖场:</strong> ${createData.data.farmName}</p>
|
||||
<p><strong>监管对象:</strong> ${createData.data.supervisionObject}</p>
|
||||
<p><strong>监管数量:</strong> ${createData.data.supervisionQuantity}</p>
|
||||
<p><strong>监管金额:</strong> ${createData.data.supervisionAmount}元</p>
|
||||
<p><strong>创建时间:</strong> ${new Date(createData.data.createdAt).toLocaleString()}</p>
|
||||
`;
|
||||
} else {
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.innerHTML = `
|
||||
<h3>❌ 项目创建失败</h3>
|
||||
<p><strong>错误信息:</strong> ${createData.message}</p>
|
||||
`;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error);
|
||||
const resultDiv = document.getElementById('result');
|
||||
resultDiv.style.display = 'block';
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.innerHTML = `
|
||||
<h3>❌ 测试失败</h3>
|
||||
<p><strong>错误信息:</strong> ${error.message}</p>
|
||||
`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
81
bank-frontend/test-api.html
Normal file
81
bank-frontend/test-api.html
Normal file
@@ -0,0 +1,81 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>API测试</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>API测试页面</h1>
|
||||
<button onclick="testLogin()">测试登录</button>
|
||||
<button onclick="testProjects()">测试项目接口</button>
|
||||
<div id="result"></div>
|
||||
|
||||
<script>
|
||||
let token = '';
|
||||
|
||||
async function testLogin() {
|
||||
try {
|
||||
const response = await fetch('http://localhost:5351/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: 'admin',
|
||||
password: 'Admin123456'
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log('登录响应:', data);
|
||||
|
||||
if (data.success) {
|
||||
token = data.data.token;
|
||||
document.getElementById('result').innerHTML =
|
||||
'<p style="color: green;">登录成功!Token: ' + token.substring(0, 20) + '...</p>';
|
||||
} else {
|
||||
document.getElementById('result').innerHTML =
|
||||
'<p style="color: red;">登录失败: ' + data.message + '</p>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('登录错误:', error);
|
||||
document.getElementById('result').innerHTML =
|
||||
'<p style="color: red;">登录错误: ' + error.message + '</p>';
|
||||
}
|
||||
}
|
||||
|
||||
async function testProjects() {
|
||||
if (!token) {
|
||||
document.getElementById('result').innerHTML =
|
||||
'<p style="color: red;">请先登录!</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('http://localhost:5351/api/projects?page=1&limit=12&search=&status=', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log('项目接口响应:', data);
|
||||
|
||||
if (data.success) {
|
||||
document.getElementById('result').innerHTML =
|
||||
'<p style="color: green;">项目接口成功!项目数量: ' + data.data.projects.length + '</p>' +
|
||||
'<pre>' + JSON.stringify(data.data.projects.slice(0, 2), null, 2) + '</pre>';
|
||||
} else {
|
||||
document.getElementById('result').innerHTML =
|
||||
'<p style="color: red;">项目接口失败: ' + data.message + '</p>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('项目接口错误:', error);
|
||||
document.getElementById('result').innerHTML =
|
||||
'<p style="color: red;">项目接口错误: ' + error.message + '</p>';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
206
bank-frontend/test-supervision-tasks.html
Normal file
206
bank-frontend/test-supervision-tasks.html
Normal file
@@ -0,0 +1,206 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>监管任务API测试</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||||
.test-section { margin-bottom: 20px; padding: 15px; border: 1px solid #ddd; border-radius: 4px; }
|
||||
button { background: #1890ff; color: white; padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; margin: 5px; }
|
||||
button:hover { background: #40a9ff; }
|
||||
.result { margin-top: 10px; padding: 10px; background: #f5f5f5; border-radius: 4px; white-space: pre-wrap; font-family: monospace; font-size: 12px; }
|
||||
.success { background: #f6ffed; border: 1px solid #b7eb8f; }
|
||||
.error { background: #fff2f0; border: 1px solid #ffccc7; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>监管任务API测试页面</h1>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>1. 登录测试</h2>
|
||||
<button onclick="testLogin()">测试登录</button>
|
||||
<div id="loginResult" class="result" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>2. 获取监管任务列表</h2>
|
||||
<button onclick="testGetTasks()" id="getTasksBtn" disabled>获取监管任务列表</button>
|
||||
<div id="tasksResult" class="result" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>3. 获取监管任务统计</h2>
|
||||
<button onclick="testGetStats()" id="getStatsBtn" disabled>获取监管任务统计</button>
|
||||
<div id="statsResult" class="result" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>4. 创建监管任务</h2>
|
||||
<button onclick="testCreateTask()" id="createTaskBtn" disabled>创建监管任务</button>
|
||||
<div id="createResult" class="result" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let token = '';
|
||||
|
||||
async function testLogin() {
|
||||
try {
|
||||
const response = await fetch('http://localhost:5351/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: 'admin',
|
||||
password: 'Admin123456'
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
const resultDiv = document.getElementById('loginResult');
|
||||
resultDiv.style.display = 'block';
|
||||
|
||||
if (data.success) {
|
||||
token = data.data.token;
|
||||
resultDiv.className = 'result success';
|
||||
resultDiv.textContent = `登录成功!\nToken: ${token.substring(0, 50)}...\n用户: ${data.data.user.username}`;
|
||||
|
||||
// 启用其他按钮
|
||||
document.getElementById('getTasksBtn').disabled = false;
|
||||
document.getElementById('getStatsBtn').disabled = false;
|
||||
document.getElementById('createTaskBtn').disabled = false;
|
||||
} else {
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.textContent = `登录失败: ${data.message}`;
|
||||
}
|
||||
} catch (error) {
|
||||
const resultDiv = document.getElementById('loginResult');
|
||||
resultDiv.style.display = 'block';
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.textContent = `登录错误: ${error.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
async function testGetTasks() {
|
||||
if (!token) {
|
||||
alert('请先登录!');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('http://localhost:5351/api/supervision-tasks?page=1&limit=10', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
const resultDiv = document.getElementById('tasksResult');
|
||||
resultDiv.style.display = 'block';
|
||||
|
||||
if (data.success) {
|
||||
resultDiv.className = 'result success';
|
||||
resultDiv.textContent = `获取监管任务列表成功!\n任务数量: ${data.data.tasks.length}\n总数量: ${data.data.pagination.total}\n\n前3个任务:\n${JSON.stringify(data.data.tasks.slice(0, 3), null, 2)}`;
|
||||
} else {
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.textContent = `获取失败: ${data.message}`;
|
||||
}
|
||||
} catch (error) {
|
||||
const resultDiv = document.getElementById('tasksResult');
|
||||
resultDiv.style.display = 'block';
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.textContent = `请求错误: ${error.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
async function testGetStats() {
|
||||
if (!token) {
|
||||
alert('请先登录!');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('http://localhost:5351/api/supervision-tasks/stats', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
const resultDiv = document.getElementById('statsResult');
|
||||
resultDiv.style.display = 'block';
|
||||
|
||||
if (data.success) {
|
||||
resultDiv.className = 'result success';
|
||||
resultDiv.textContent = `获取监管任务统计成功!\n总计: ${data.data.total}\n待监管: ${data.data.pending}\n监管中: ${data.data.supervising}\n已完成: ${data.data.completed}\n已暂停: ${data.data.suspended}`;
|
||||
} else {
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.textContent = `获取失败: ${data.message}`;
|
||||
}
|
||||
} catch (error) {
|
||||
const resultDiv = document.getElementById('statsResult');
|
||||
resultDiv.style.display = 'block';
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.textContent = `请求错误: ${error.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
async function testCreateTask() {
|
||||
if (!token) {
|
||||
alert('请先登录!');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const newTask = {
|
||||
applicationNumber: 'APP_TEST_' + Date.now(),
|
||||
contractNumber: 'CONTRACT_TEST_' + Date.now(),
|
||||
productName: '测试农业贷款产品',
|
||||
customerName: '测试客户',
|
||||
idType: 'id_card',
|
||||
idNumber: '110101199001011234',
|
||||
assetType: 'cattle',
|
||||
assetQuantity: 10,
|
||||
supervisionStatus: 'pending',
|
||||
startTime: '2024-12-20',
|
||||
endTime: '2024-12-31',
|
||||
loanAmount: 100000.00,
|
||||
interestRate: 0.0600,
|
||||
loanTerm: 12,
|
||||
supervisorName: '测试监管员',
|
||||
supervisorPhone: '13800138000',
|
||||
farmAddress: '测试养殖场地址',
|
||||
remarks: '这是一个测试监管任务'
|
||||
};
|
||||
|
||||
const response = await fetch('http://localhost:5351/api/supervision-tasks', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(newTask)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
const resultDiv = document.getElementById('createResult');
|
||||
resultDiv.style.display = 'block';
|
||||
|
||||
if (data.success) {
|
||||
resultDiv.className = 'result success';
|
||||
resultDiv.textContent = `创建监管任务成功!\n任务ID: ${data.data.id}\n申请单号: ${data.data.applicationNumber}\n客户姓名: ${data.data.customerName}\n监管状态: ${data.data.supervisionStatus}`;
|
||||
} else {
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.textContent = `创建失败: ${data.message}`;
|
||||
}
|
||||
} catch (error) {
|
||||
const resultDiv = document.getElementById('createResult');
|
||||
resultDiv.style.display = 'block';
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.textContent = `请求错误: ${error.message}`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user