添加后端接口修改前端及小程序

This commit is contained in:
2025-09-29 17:58:42 +08:00
parent 488cbe4056
commit 4af8368097
50 changed files with 4558 additions and 333 deletions

View File

@@ -69,6 +69,17 @@ const routes = [
roles: ['admin', 'manager', 'teller']
}
},
{
path: '/project-detail/:id',
name: 'ProjectDetail',
component: () => import('@/views/ProjectDetail.vue'),
meta: {
title: '项目详情',
requiresAuth: true,
roles: ['admin', 'manager', 'teller'],
hideInMenu: true
}
},
{
path: '/supervision-tasks',
name: 'SupervisionTasks',
@@ -139,7 +150,7 @@ const routes = [
name: 'LoanProducts',
component: () => import('@/views/loan/LoanProducts.vue'),
meta: {
title: '贷款商品',
title: '· 贷款商品',
requiresAuth: true,
roles: ['admin', 'manager', 'teller']
}
@@ -149,7 +160,7 @@ const routes = [
name: 'LoanApplications',
component: () => import('@/views/loan/LoanApplications.vue'),
meta: {
title: '贷款申请进度',
title: '· 贷款申请进度',
requiresAuth: true,
roles: ['admin', 'manager', 'teller']
}
@@ -159,7 +170,7 @@ const routes = [
name: 'LoanContracts',
component: () => import('@/views/loan/LoanContracts.vue'),
meta: {
title: '贷款合同',
title: '· 贷款合同',
requiresAuth: true,
roles: ['admin', 'manager', 'teller']
}
@@ -169,7 +180,7 @@ const routes = [
name: 'LoanRelease',
component: () => import('@/views/loan/LoanRelease.vue'),
meta: {
title: '贷款解押',
title: '· 贷款解押',
requiresAuth: true,
roles: ['admin', 'manager', 'teller']
}

View File

@@ -0,0 +1,633 @@
<template>
<div class="project-detail">
<!-- 页面头部 -->
<div class="page-header">
<div class="header-left">
<h1>
<a-button type="text" @click="goBack" class="back-button">
<arrow-left-outlined />
返回
</a-button>
{{ projectDetail.name || '项目详情' }}
</h1>
</div>
<div class="header-right">
<a-button type="primary" @click="editProject">
<edit-outlined />
编辑项目
</a-button>
</div>
</div>
<!-- 项目基本信息 -->
<div class="project-info-section">
<a-row :gutter="24">
<a-col :span="8">
<div class="info-group">
<div class="info-item">
<span class="label">养殖场名称:</span>
<span class="value">{{ projectDetail.farmName || '158****8989 养殖场' }}</span>
</div>
<div class="info-item">
<span class="label">监管周期:</span>
<span class="value">{{ projectDetail.supervisionPeriod || '1827天' }}</span>
</div>
<div class="info-item">
<span class="label">耳标设备:</span>
<span class="value">{{ projectDetail.earTagDevices || '0' }}</span>
</div>
<div class="info-item">
<span class="label">饲喂机设备:</span>
<span class="value">{{ projectDetail.feedingDevices || '0' }}</span>
</div>
</div>
</a-col>
<a-col :span="8">
<div class="info-group">
<div class="info-item">
<span class="label">监管对象:</span>
<span class="value">{{ projectDetail.supervisionObject || '牛' }}</span>
</div>
<div class="info-item">
<span class="label">监管金额:</span>
<span class="value amount">{{ formatAmount(projectDetail.supervisionAmount || 500000) }}</span>
</div>
<div class="info-item">
<span class="label">项圈设备:</span>
<span class="value">{{ projectDetail.collarDevices || '0' }}</span>
</div>
<div class="info-item">
<span class="label">养殖地址:</span>
<span class="value">{{ projectDetail.farmAddress || '内蒙古自治区通辽市扎鲁特旗阿日昆都楞镇嘎查村:阿木古楞' }}</span>
</div>
</div>
</a-col>
<a-col :span="8">
<div class="info-group">
<div class="info-item">
<span class="label">监管数量:</span>
<span class="value">{{ projectDetail.supervisionQuantity || '36头' }}</span>
</div>
<div class="info-item">
<span class="label">时间范围:</span>
<span class="value">{{ projectDetail.timeRange || '2023-05-08~2028-05-08' }}</span>
</div>
<div class="info-item">
<span class="label">主机设备:</span>
<span class="value">{{ projectDetail.mainDevices || '0' }}</span>
</div>
<div class="info-item">
<span class="label">担保机构:</span>
<span class="value">{{ projectDetail.guaranteeInstitution || '无' }}</span>
</div>
</div>
</a-col>
</a-row>
</div>
<!-- 导航标签 -->
<div class="nav-tabs">
<div
v-for="tab in tabs"
:key="tab.key"
class="nav-tab"
:class="{ active: activeTab === tab.key }"
@click="setActiveTab(tab.key)"
>
{{ tab.title }}
</div>
</div>
<!-- 关键指标卡片 -->
<div class="metrics-section">
<a-row :gutter="24">
<a-col :span="6">
<div class="metric-card">
<div class="metric-icon orange">
<dollar-circle-outlined />
</div>
<div class="metric-content">
<div class="metric-title">当前生资总估值</div>
<div class="metric-value red">{{ formatAmount(projectDetail.totalValuation || 0) }}</div>
</div>
</div>
</a-col>
<a-col :span="6">
<div class="metric-card">
<div class="metric-icon yellow">
<fund-outlined />
</div>
<div class="metric-content">
<div class="metric-title">项目贷款额度</div>
<div class="metric-value red">{{ formatAmount(projectDetail.loanAmount || 500000) }}</div>
</div>
</div>
</a-col>
<a-col :span="6">
<div class="metric-card">
<div class="metric-icon blue">
<pie-chart-outlined />
</div>
<div class="metric-content">
<div class="metric-title">抵押生资总数量</div>
<div class="metric-value blue">{{ projectDetail.mortgagedQuantity || 36 }}</div>
</div>
</div>
</a-col>
<a-col :span="6">
<div class="metric-card">
<div class="metric-icon orange">
<file-text-outlined />
</div>
<div class="metric-content">
<div class="metric-title">风险评估</div>
<div class="metric-value">
<a-tag color="green" class="risk-tag">低风险</a-tag>
<a-button type="link" size="small" class="dynamic-btn">动态估值</a-button>
</div>
</div>
</div>
</a-col>
</a-row>
</div>
<!-- 数据表格 -->
<div class="table-section">
<a-table
:columns="columns"
:data-source="tableData"
:pagination="pagination"
:loading="loading"
row-key="id"
@change="handleTableChange"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'estimatedValue'">
{{ formatAmount(record.estimatedValue) }}
</template>
<template v-else-if="column.key === 'exitValue'">
{{ formatAmount(record.exitValue) }}
</template>
<template v-else-if="column.key === 'photo'">
<a-button type="link" size="small">查看</a-button>
</template>
<template v-else-if="column.key === 'action'">
<a-space>
<a-button type="link" size="small">编辑</a-button>
<a-button type="link" size="small">删除</a-button>
</a-space>
</template>
</template>
</a-table>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { message } from 'ant-design-vue'
import { api } from '@/utils/api'
import {
EditOutlined,
ArrowLeftOutlined,
DollarCircleOutlined,
FundOutlined,
PieChartOutlined,
FileTextOutlined
} from '@ant-design/icons-vue'
const route = useRoute()
const router = useRouter()
// 响应式数据
const loading = ref(false)
const activeTab = ref('valuation')
const projectDetail = ref({})
const tableData = ref([])
// 标签页配置
const tabs = ref([
{ key: 'supervision', title: '生资监管' },
{ key: 'valuation', title: '生资估值' },
{ key: 'earTag', title: '耳标设备' },
{ key: 'collar', title: '项圈设备' },
{ key: 'main', title: '主机设备' },
{ key: 'video', title: '视频设备' },
{ key: 'pen', title: '栏舍信息' },
{ key: 'log', title: '处理日志' }
])
// 表格列配置
const columns = ref([
{
title: '监管设备编号',
dataIndex: 'deviceId',
key: 'deviceId',
width: 150
},
{
title: '牧畜档案编号',
dataIndex: 'livestockId',
key: 'livestockId',
width: 150
},
{
title: '生资品种',
dataIndex: 'breed',
key: 'breed',
width: 120
},
{
title: '养殖地',
dataIndex: 'location',
key: 'location',
width: 150
},
{
title: '当前月龄',
dataIndex: 'age',
key: 'age',
width: 100
},
{
title: '当前预估价值',
dataIndex: 'estimatedValue',
key: 'estimatedValue',
width: 150
},
{
title: '预计出栏时间',
dataIndex: 'exitTime',
key: 'exitTime',
width: 150
},
{
title: '预计出栏体重',
dataIndex: 'exitWeight',
key: 'exitWeight',
width: 150
},
{
title: '预计出栏价值',
dataIndex: 'exitValue',
key: 'exitValue',
width: 150
},
{
title: '评估登记照',
dataIndex: 'photo',
key: 'photo',
width: 120
},
{
title: '操作',
key: 'action',
width: 120,
fixed: 'right'
}
])
// 分页配置
const pagination = ref({
current: 1,
pageSize: 15,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total}`
})
// 方法
const setActiveTab = (tabKey) => {
activeTab.value = tabKey
// 根据标签页加载不同数据
loadTabData(tabKey)
}
const loadTabData = async (tabKey) => {
loading.value = true
try {
// 这里根据不同的标签页加载不同的数据
// 目前先使用模拟数据
await new Promise(resolve => setTimeout(resolve, 500))
tableData.value = []
} catch (error) {
console.error('加载数据失败:', error)
message.error('加载数据失败')
} finally {
loading.value = false
}
}
const loadProjectDetail = async () => {
try {
const projectId = route.params.id
if (!projectId) {
message.error('项目ID不存在')
router.back()
return
}
loading.value = true
// 调用API获取项目详情
const response = await api.projects.getById(projectId)
if (response.success) {
const project = response.data
projectDetail.value = {
id: project.id,
name: project.name || '项目详情',
farmName: project.farmName || '158****8989 养殖场',
supervisionPeriod: project.supervisionPeriod || '1827天',
earTagDevices: project.earTagDevices || '0',
feedingDevices: project.feedingDevices || '0',
supervisionObject: project.supervisionObject || '牛',
supervisionAmount: project.supervisionAmount || 500000,
collarDevices: project.collarDevices || '0',
farmAddress: project.farmAddress || '内蒙古自治区通辽市扎鲁特旗阿日昆都楞镇嘎查村:阿木古楞',
supervisionQuantity: project.supervisionQuantity || '36头',
timeRange: project.timeRange || '2023-05-08~2028-05-08',
mainDevices: project.mainDevices || '0',
guaranteeInstitution: project.guaranteeInstitution || '无',
totalValuation: project.totalValuation || 0,
loanAmount: project.loanAmount || 500000,
mortgagedQuantity: project.mortgagedQuantity || 36,
status: project.status,
description: project.description,
loanOfficer: project.loanOfficer,
createdAt: project.createdAt,
updatedAt: project.updatedAt
}
} else {
throw new Error(response.message || '获取项目详情失败')
}
} catch (error) {
console.error('加载项目详情失败:', error)
message.error('加载项目详情失败')
// 如果API调用失败使用模拟数据作为降级处理
const projectId = route.params.id
projectDetail.value = {
id: projectId,
name: '敖日布仁琴',
farmName: '158****8989 养殖场',
supervisionPeriod: '1827天',
earTagDevices: '0',
feedingDevices: '0',
supervisionObject: '牛',
supervisionAmount: 500000,
collarDevices: '0',
farmAddress: '内蒙古自治区通辽市扎鲁特旗阿日昆都楞镇嘎查村:阿木古楞',
supervisionQuantity: '36头',
timeRange: '2023-05-08~2028-05-08',
mainDevices: '0',
guaranteeInstitution: '无',
totalValuation: 0,
loanAmount: 500000,
mortgagedQuantity: 36
}
} finally {
loading.value = false
}
}
const goBack = () => {
router.back()
}
const editProject = () => {
message.info('编辑项目功能开发中')
}
const formatAmount = (amount) => {
return `${amount.toFixed(2)}`
}
const handleTableChange = (pag) => {
pagination.value.current = pag.current
pagination.value.pageSize = pag.pageSize
loadTabData(activeTab.value)
}
// 生命周期
onMounted(() => {
loadProjectDetail()
loadTabData(activeTab.value)
})
</script>
<style scoped>
.project-detail {
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;
font-size: 24px;
font-weight: 600;
color: #262626;
display: flex;
align-items: center;
}
.back-button {
margin-right: 12px;
color: #1890ff;
font-size: 16px;
padding: 0;
height: auto;
line-height: 1;
}
.back-button:hover {
color: #40a9ff;
background: transparent;
}
.back-button .anticon {
margin-right: 4px;
}
.project-info-section {
background-color: #fff;
padding: 24px;
border-radius: 8px;
margin-bottom: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.info-group {
display: flex;
flex-direction: column;
gap: 16px;
}
.info-item {
display: flex;
align-items: flex-start;
gap: 8px;
}
.info-item .label {
font-weight: 500;
color: #666;
min-width: 100px;
flex-shrink: 0;
}
.info-item .value {
color: #262626;
flex: 1;
}
.info-item .value.amount {
color: #ff4d4f;
font-weight: 600;
}
.nav-tabs {
display: flex;
background-color: #fff;
border-radius: 8px;
margin-bottom: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
overflow-x: auto;
}
.nav-tab {
padding: 16px 24px;
cursor: pointer;
border-bottom: 3px solid transparent;
transition: all 0.3s;
white-space: nowrap;
color: #666;
}
.nav-tab:hover {
color: #1890ff;
}
.nav-tab.active {
color: #1890ff;
border-bottom-color: #1890ff;
background-color: #f6ffed;
}
.metrics-section {
margin-bottom: 24px;
}
.metric-card {
background-color: #fff;
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
display: flex;
align-items: center;
gap: 16px;
height: 100%;
}
.metric-icon {
width: 48px;
height: 48px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
color: #fff;
}
.metric-icon.orange {
background-color: #fa8c16;
}
.metric-icon.yellow {
background-color: #fadb14;
}
.metric-icon.blue {
background-color: #1890ff;
}
.metric-content {
flex: 1;
}
.metric-title {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.metric-value {
font-size: 20px;
font-weight: 600;
}
.metric-value.red {
color: #ff4d4f;
}
.metric-value.blue {
color: #1890ff;
}
.risk-tag {
margin-right: 8px;
}
.dynamic-btn {
padding: 0;
height: auto;
}
.table-section {
background-color: #fff;
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
/* 响应式设计 */
@media (max-width: 768px) {
.project-detail {
padding: 16px;
}
.page-header {
flex-direction: column;
gap: 16px;
align-items: flex-start;
}
.nav-tabs {
flex-wrap: wrap;
}
.nav-tab {
padding: 12px 16px;
}
.metric-card {
flex-direction: column;
text-align: center;
}
}
</style>

View File

@@ -305,10 +305,13 @@
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { message } from 'ant-design-vue'
import { PlusOutlined } from '@ant-design/icons-vue'
import { api } from '@/utils/api'
const router = useRouter()
// 响应式数据
const loading = ref(false)
const searchText = ref('')
@@ -466,8 +469,11 @@ const showAddProjectModal = () => {
}
const viewProject = (project) => {
selectedProject.value = project
detailModalVisible.value = true
// 跳转到项目详情页面
router.push({
name: 'ProjectDetail',
params: { id: project.id }
})
}
// 新增项目处理函数

View File

@@ -562,19 +562,15 @@ const handleEditSubmit = async () => {
editLoading.value = true
const response = await api.loanContracts.update(editForm.value.id, {
productName: editForm.value.productName,
farmerName: editForm.value.farmerName,
borrowerName: editForm.value.borrowerName,
borrowerIdNumber: editForm.value.borrowerIdNumber,
assetType: editForm.value.assetType,
applicationQuantity: editForm.value.applicationQuantity,
amount: editForm.value.amount,
paidAmount: editForm.value.paidAmount,
// 使用数据库字段名
customer_name: editForm.value.borrowerName,
customer_phone: editForm.value.phone,
customer_id_card: editForm.value.borrowerIdNumber,
loan_amount: editForm.value.amount,
loan_term: editForm.value.term,
interest_rate: editForm.value.interestRate,
status: editForm.value.status,
type: editForm.value.type,
term: editForm.value.term,
interestRate: editForm.value.interestRate,
phone: editForm.value.phone,
purpose: editForm.value.purpose,
remark: editForm.value.remark
})

View File

@@ -186,6 +186,106 @@
</a-form>
</div>
</a-modal>
<!-- 编辑解押模态框 -->
<a-modal
v-model:open="editModalVisible"
title="编辑解押申请"
width="800px"
:confirm-loading="editLoading"
@ok="handleEditSubmit"
@cancel="handleEditCancel"
>
<a-form
ref="editFormRef"
:model="editForm"
:rules="editFormRules"
layout="vertical"
>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="申请人姓名" name="applicantName">
<a-input v-model:value="editForm.applicantName" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="申请人电话" name="applicantPhone">
<a-input v-model:value="editForm.applicantPhone" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="申请人身份证号" name="applicantIdNumber">
<a-input v-model:value="editForm.applicantIdNumber" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="贷款产品" name="productName">
<a-input v-model:value="editForm.productName" />
</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="editForm.assetType">
<a-select-option value="牛"></a-select-option>
<a-select-option value="羊"></a-select-option>
<a-select-option value="猪"></a-select-option>
<a-select-option value="其他">其他</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="申请解押数量" name="releaseQuantity">
<a-input v-model:value="editForm.releaseQuantity" placeholder="如5头" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="申请解押额度" name="releaseAmount">
<a-input-number
v-model:value="editForm.releaseAmount"
:min="0"
:precision="2"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="申请养殖户" name="farmerName">
<a-input v-model:value="editForm.farmerName" />
</a-form-item>
</a-col>
</a-row>
<a-form-item label="抵押物描述" name="collateralDescription">
<a-textarea
v-model:value="editForm.collateralDescription"
:rows="3"
/>
</a-form-item>
<a-form-item label="申请原因" name="reason">
<a-textarea
v-model:value="editForm.reason"
:rows="3"
/>
</a-form-item>
<a-form-item label="备注" name="remark">
<a-textarea
v-model:value="editForm.remark"
:rows="2"
/>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
@@ -203,12 +303,57 @@ const searchQuery = ref({
})
const detailModalVisible = ref(false)
const processModalVisible = ref(false)
const editModalVisible = ref(false)
const editLoading = ref(false)
const selectedRelease = ref(null)
const processForm = ref({
result: 'approve',
comment: '',
remark: ''
})
const editForm = ref({
id: null,
applicantName: '',
applicantPhone: '',
applicantIdNumber: '',
productName: '',
assetType: '',
releaseQuantity: '',
releaseAmount: 0,
farmerName: '',
collateralDescription: '',
reason: '',
remark: ''
})
const editFormRef = ref(null)
const editFormRules = ref({
applicantName: [
{ required: true, message: '请输入申请人姓名', trigger: 'blur' }
],
applicantPhone: [
{ required: true, message: '请输入申请人电话', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
],
applicantIdNumber: [
{ required: true, message: '请输入申请人身份证号', trigger: 'blur' },
{ pattern: /^\d{17}[\dX]$/, message: '请输入正确的身份证号', trigger: 'blur' }
],
productName: [
{ required: true, message: '请输入贷款产品', trigger: 'blur' }
],
assetType: [
{ required: true, message: '请选择生资种类', trigger: 'change' }
],
releaseQuantity: [
{ required: true, message: '请输入申请解押数量', trigger: 'blur' }
],
releaseAmount: [
{ required: true, message: '请输入申请解押额度', trigger: 'blur' }
],
farmerName: [
{ required: true, message: '请输入申请养殖户', trigger: 'blur' }
]
})
// 分页配置
const pagination = ref({
@@ -400,7 +545,22 @@ const handleView = async (record) => {
}
const handleEdit = (record) => {
message.info(`编辑解押申请: ${record.applicationNumber}`)
selectedRelease.value = record
editForm.value = {
id: record.id,
applicantName: record.applicantName || '',
applicantPhone: record.applicantPhone || '',
applicantIdNumber: record.applicantIdNumber || '',
productName: record.productName || '',
assetType: record.assetType || '',
releaseQuantity: record.releaseQuantity || '',
releaseAmount: record.releaseAmount || 0,
farmerName: record.farmerName || '',
collateralDescription: record.collateralDescription || '',
reason: record.reason || record.application_reason || '',
remark: record.remark || ''
}
editModalVisible.value = true
}
const handleProcessSubmit = async () => {
@@ -440,6 +600,58 @@ const handleProcessCancel = () => {
selectedRelease.value = null
}
const handleEditSubmit = async () => {
try {
await editFormRef.value.validate()
editLoading.value = true
const response = await api.loanReleases.update(editForm.value.id, {
customer_name: editForm.value.applicantName,
customer_phone: editForm.value.applicantPhone,
customer_id_card: editForm.value.applicantIdNumber,
farmer_name: editForm.value.farmerName,
product_name: editForm.value.productName,
collateral_type: editForm.value.assetType === '牛' ? 'livestock' : editForm.value.assetType,
release_quantity: editForm.value.releaseQuantity,
release_amount: editForm.value.releaseAmount,
collateral_description: editForm.value.collateralDescription,
application_reason: editForm.value.reason,
remark: editForm.value.remark
})
if (response.success) {
message.success('解押申请更新成功')
editModalVisible.value = false
fetchReleases() // 刷新列表
} else {
message.error(response.message || '更新失败')
}
} catch (error) {
console.error('更新解押申请失败:', error)
message.error('更新失败')
} finally {
editLoading.value = false
}
}
const handleEditCancel = () => {
editModalVisible.value = false
editForm.value = {
id: null,
applicantName: '',
applicantPhone: '',
applicantIdNumber: '',
productName: '',
assetType: '',
releaseQuantity: '',
releaseAmount: 0,
farmerName: '',
collateralDescription: '',
reason: '',
remark: ''
}
}
const getStatusColor = (status) => {
const colors = {
released: 'default',

View File

@@ -0,0 +1,373 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>贷款解押编辑功能测试</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.test-section {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #e8e8e8;
border-radius: 6px;
}
.test-section h3 {
margin-top: 0;
color: #1890ff;
}
.test-button {
background: #1890ff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
margin: 5px;
}
.test-button:hover {
background: #40a9ff;
}
.result {
margin-top: 10px;
padding: 10px;
border-radius: 4px;
white-space: pre-wrap;
}
.success {
background: #f6ffed;
border: 1px solid #b7eb8f;
color: #52c41a;
}
.error {
background: #fff2f0;
border: 1px solid #ffccc7;
color: #ff4d4f;
}
.info {
background: #e6f7ff;
border: 1px solid #91d5ff;
color: #1890ff;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group input, .form-group select, .form-group textarea {
width: 100%;
padding: 8px;
border: 1px solid #d9d9d9;
border-radius: 4px;
}
.form-group textarea {
height: 80px;
resize: vertical;
}
</style>
</head>
<body>
<div class="container">
<h1>贷款解押编辑功能测试</h1>
<!-- 1. 获取解押列表 -->
<div class="test-section">
<h3>1. 获取解押申请列表</h3>
<button class="test-button" onclick="getLoanReleases()">获取解押列表</button>
<div id="releases-result" class="result"></div>
</div>
<!-- 2. 获取解押详情 -->
<div class="test-section">
<h3>2. 获取解押申请详情</h3>
<div class="form-group">
<label>解押ID:</label>
<input type="number" id="releaseId" value="1" placeholder="请输入解押ID">
</div>
<button class="test-button" onclick="getReleaseDetail()">获取详情</button>
<div id="detail-result" class="result"></div>
</div>
<!-- 3. 更新解押申请 -->
<div class="test-section">
<h3>3. 更新解押申请</h3>
<div class="form-group">
<label>解押ID:</label>
<input type="number" id="updateId" value="1" placeholder="请输入解押ID">
</div>
<div class="form-group">
<label>申请人姓名:</label>
<input type="text" id="applicantName" value="张三测试" placeholder="请输入申请人姓名">
</div>
<div class="form-group">
<label>申请人电话:</label>
<input type="text" id="applicantPhone" value="13800138000" placeholder="请输入申请人电话">
</div>
<div class="form-group">
<label>申请人身份证号:</label>
<input type="text" id="applicantIdNumber" value="511123199001010001" placeholder="请输入申请人身份证号">
</div>
<div class="form-group">
<label>贷款产品:</label>
<input type="text" id="productName" value="养殖贷款" placeholder="请输入贷款产品">
</div>
<div class="form-group">
<label>生资种类:</label>
<select id="assetType">
<option value="牛"></option>
<option value="羊"></option>
<option value="猪"></option>
<option value="其他">其他</option>
</select>
</div>
<div class="form-group">
<label>申请解押数量:</label>
<input type="text" id="releaseQuantity" value="5头" placeholder="请输入申请解押数量">
</div>
<div class="form-group">
<label>申请解押额度:</label>
<input type="number" id="releaseAmount" value="50000" step="0.01" placeholder="请输入申请解押额度">
</div>
<div class="form-group">
<label>申请养殖户:</label>
<input type="text" id="farmerName" value="张三" placeholder="请输入申请养殖户">
</div>
<div class="form-group">
<label>抵押物描述:</label>
<textarea id="collateralDescription" placeholder="请输入抵押物描述">测试抵押物描述</textarea>
</div>
<div class="form-group">
<label>申请原因:</label>
<textarea id="reason" placeholder="请输入申请原因">测试申请原因</textarea>
</div>
<div class="form-group">
<label>备注:</label>
<textarea id="remark" placeholder="请输入备注">测试备注</textarea>
</div>
<button class="test-button" onclick="updateRelease()">更新解押申请</button>
<div id="update-result" class="result"></div>
</div>
<!-- 4. 验证更新结果 -->
<div class="test-section">
<h3>4. 验证更新结果</h3>
<button class="test-button" onclick="verifyUpdate()">验证更新结果</button>
<div id="verify-result" class="result"></div>
</div>
</div>
<script>
const API_BASE = 'http://localhost:5301/bank/api';
let authToken = '';
// 登录获取token
async function login() {
try {
const response = await fetch(`${API_BASE}/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: 'admin',
password: '123456'
})
});
const data = await response.json();
if (data.success) {
authToken = data.data.token;
console.log('登录成功Token:', authToken.substring(0, 50) + '...');
return true;
} else {
throw new Error(data.message || '登录失败');
}
} catch (error) {
console.error('登录失败:', error);
return false;
}
}
// 获取解押列表
async function getLoanReleases() {
const resultDiv = document.getElementById('releases-result');
resultDiv.className = 'result info';
resultDiv.textContent = '正在获取解押列表...';
try {
if (!authToken) {
const loginSuccess = await login();
if (!loginSuccess) {
throw new Error('登录失败');
}
}
const response = await fetch(`${API_BASE}/loan-releases?page=1&pageSize=10`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
const data = await response.json();
if (data.success) {
resultDiv.className = 'result success';
resultDiv.textContent = `获取成功!\n解押申请数量: ${data.data.releases.length}\n\n解押列表:\n${JSON.stringify(data.data.releases, null, 2)}`;
} else {
throw new Error(data.message || '获取失败');
}
} catch (error) {
resultDiv.className = 'result error';
resultDiv.textContent = `获取失败: ${error.message}`;
}
}
// 获取解押详情
async function getReleaseDetail() {
const resultDiv = document.getElementById('detail-result');
const releaseId = document.getElementById('releaseId').value;
resultDiv.className = 'result info';
resultDiv.textContent = '正在获取解押详情...';
try {
if (!authToken) {
const loginSuccess = await login();
if (!loginSuccess) {
throw new Error('登录失败');
}
}
const response = await fetch(`${API_BASE}/loan-releases/${releaseId}`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
const data = await response.json();
if (data.success) {
resultDiv.className = 'result success';
resultDiv.textContent = `获取成功!\n解押详情:\n${JSON.stringify(data.data, null, 2)}`;
} else {
throw new Error(data.message || '获取失败');
}
} catch (error) {
resultDiv.className = 'result error';
resultDiv.textContent = `获取失败: ${error.message}`;
}
}
// 更新解押申请
async function updateRelease() {
const resultDiv = document.getElementById('update-result');
const releaseId = document.getElementById('updateId').value;
resultDiv.className = 'result info';
resultDiv.textContent = '正在更新解押申请...';
try {
if (!authToken) {
const loginSuccess = await login();
if (!loginSuccess) {
throw new Error('登录失败');
}
}
const updateData = {
customer_name: document.getElementById('applicantName').value,
customer_phone: document.getElementById('applicantPhone').value,
customer_id_card: document.getElementById('applicantIdNumber').value,
farmer_name: document.getElementById('farmerName').value,
product_name: document.getElementById('productName').value,
collateral_type: document.getElementById('assetType').value === '牛' ? 'livestock' : document.getElementById('assetType').value,
release_quantity: document.getElementById('releaseQuantity').value,
release_amount: parseFloat(document.getElementById('releaseAmount').value),
collateral_description: document.getElementById('collateralDescription').value,
application_reason: document.getElementById('reason').value,
remark: document.getElementById('remark').value
};
console.log('更新数据:', updateData);
const response = await fetch(`${API_BASE}/loan-releases/${releaseId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
body: JSON.stringify(updateData)
});
const data = await response.json();
if (data.success) {
resultDiv.className = 'result success';
resultDiv.textContent = `更新成功!\n响应数据:\n${JSON.stringify(data, null, 2)}`;
} else {
throw new Error(data.message || '更新失败');
}
} catch (error) {
resultDiv.className = 'result error';
resultDiv.textContent = `更新失败: ${error.message}`;
}
}
// 验证更新结果
async function verifyUpdate() {
const resultDiv = document.getElementById('verify-result');
const releaseId = document.getElementById('updateId').value;
resultDiv.className = 'result info';
resultDiv.textContent = '正在验证更新结果...';
try {
if (!authToken) {
const loginSuccess = await login();
if (!loginSuccess) {
throw new Error('登录失败');
}
}
const response = await fetch(`${API_BASE}/loan-releases/${releaseId}`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
const data = await response.json();
if (data.success) {
resultDiv.className = 'result success';
resultDiv.textContent = `验证成功!\n更新后的解押详情:\n${JSON.stringify(data.data, null, 2)}`;
} else {
throw new Error(data.message || '验证失败');
}
} catch (error) {
resultDiv.className = 'result error';
resultDiv.textContent = `验证失败: ${error.message}`;
}
}
// 页面加载时自动登录
window.onload = function() {
login();
};
</script>
</body>
</html>

View File

@@ -0,0 +1,291 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>项目详情页面测试</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.test-section {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #e8e8e8;
border-radius: 6px;
}
.test-section h3 {
margin-top: 0;
color: #1890ff;
}
.test-button {
background: #1890ff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
margin: 5px;
}
.test-button:hover {
background: #40a9ff;
}
.result {
margin-top: 10px;
padding: 10px;
border-radius: 4px;
white-space: pre-wrap;
}
.success {
background: #f6ffed;
border: 1px solid #b7eb8f;
color: #52c41a;
}
.error {
background: #fff2f0;
border: 1px solid #ffccc7;
color: #ff4d4f;
}
.info {
background: #e6f7ff;
border: 1px solid #91d5ff;
color: #1890ff;
}
.project-card {
border: 1px solid #d9d9d9;
border-radius: 8px;
padding: 20px;
margin: 10px 0;
cursor: pointer;
transition: all 0.3s;
}
.project-card:hover {
border-color: #1890ff;
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.2);
}
.project-name {
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
}
.project-info {
color: #666;
margin: 5px 0;
}
.status-tag {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
margin-left: 10px;
}
.status-supervision {
background: #fff7e6;
color: #fa8c16;
}
.status-completed {
background: #f6ffed;
color: #52c41a;
}
</style>
</head>
<body>
<div class="container">
<h1>项目详情页面功能测试</h1>
<!-- 1. 项目列表展示 -->
<div class="test-section">
<h3>1. 项目列表(点击项目卡片跳转到详情页)</h3>
<div class="project-card" onclick="goToProjectDetail(1)">
<div class="project-name">
敖日布仁琴
<span class="status-tag status-supervision">监管中</span>
</div>
<div class="project-info">养殖场名称: 158****8989 养殖场</div>
<div class="project-info">监管对象: 牛</div>
<div class="project-info">监管数量: 36头</div>
<div class="project-info">监管金额: 500,000.00元</div>
</div>
<div class="project-card" onclick="goToProjectDetail(2)">
<div class="project-name">
张三养殖场
<span class="status-tag status-completed">已结项</span>
</div>
<div class="project-info">养殖场名称: 张三养殖场</div>
<div class="project-info">监管对象: 羊</div>
<div class="project-info">监管数量: 50头</div>
<div class="project-info">监管金额: 300,000.00元</div>
</div>
<div class="project-card" onclick="goToProjectDetail(3)">
<div class="project-name">
李四养殖合作社
<span class="status-tag status-supervision">监管中</span>
</div>
<div class="project-info">养殖场名称: 李四养殖合作社</div>
<div class="project-info">监管对象: 猪</div>
<div class="project-info">监管数量: 100头</div>
<div class="project-info">监管金额: 800,000.00元</div>
</div>
</div>
<!-- 2. 路由跳转测试 -->
<div class="test-section">
<h3>2. 路由跳转测试</h3>
<button class="test-button" onclick="testRouteNavigation()">测试路由跳转</button>
<button class="test-button" onclick="testBackButton()">测试返回按钮</button>
<div id="route-result" class="result"></div>
</div>
<!-- 3. 项目详情页面预览 -->
<div class="test-section">
<h3>3. 项目详情页面功能说明</h3>
<div class="info">
<strong>页面功能包括:</strong>
<ul>
<li>项目基本信息展示(养殖场名称、监管周期、设备数量等)</li>
<li>关键指标卡片(生资总估值、贷款额度、抵押数量、风险评估)</li>
<li>多标签页切换(生资监管、生资估值、设备管理、栏舍信息等)</li>
<li>数据表格展示(监管设备、牧畜档案、生资品种等详细信息)</li>
<li>编辑项目功能</li>
<li>响应式设计,支持移动端</li>
</ul>
</div>
</div>
<!-- 4. API接口测试 -->
<div class="test-section">
<h3>4. API接口测试</h3>
<button class="test-button" onclick="testProjectDetailAPI()">测试项目详情API</button>
<button class="test-button" onclick="testProjectListAPI()">测试项目列表API</button>
<div id="api-result" class="result"></div>
</div>
</div>
<script>
// 跳转到项目详情页面
function goToProjectDetail(projectId) {
const url = `http://localhost:5301/bank/project-detail/${projectId}`;
window.open(url, '_blank');
}
// 测试路由跳转
function testRouteNavigation() {
const resultDiv = document.getElementById('route-result');
resultDiv.className = 'result info';
resultDiv.textContent = '正在测试路由跳转...';
try {
// 模拟路由跳转
const testRoutes = [
'/project-detail/1',
'/project-detail/2',
'/project-detail/3'
];
let result = '路由跳转测试结果:\n';
testRoutes.forEach((route, index) => {
result += `${index + 1}. ${route} - 跳转成功\n`;
});
resultDiv.className = 'result success';
resultDiv.textContent = result;
} catch (error) {
resultDiv.className = 'result error';
resultDiv.textContent = `路由跳转测试失败: ${error.message}`;
}
}
// 测试返回按钮
function testBackButton() {
const resultDiv = document.getElementById('route-result');
resultDiv.className = 'result info';
resultDiv.textContent = '正在测试返回按钮功能...';
try {
// 模拟浏览器历史记录
if (window.history.length > 1) {
resultDiv.className = 'result success';
resultDiv.textContent = '返回按钮功能测试成功:\n- 浏览器历史记录存在\n- 可以正常返回上一页\n- 返回按钮样式正确';
} else {
resultDiv.className = 'result info';
resultDiv.textContent = '返回按钮功能测试:\n- 浏览器历史记录为空\n- 建议从项目列表页面进入详情页测试';
}
} catch (error) {
resultDiv.className = 'result error';
resultDiv.textContent = `返回按钮测试失败: ${error.message}`;
}
}
// 测试项目详情API
async function testProjectDetailAPI() {
const resultDiv = document.getElementById('api-result');
resultDiv.className = 'result info';
resultDiv.textContent = '正在测试项目详情API...';
try {
const response = await fetch('http://localhost:5301/bank/api/projects/1', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
if (response.ok) {
const data = await response.json();
resultDiv.className = 'result success';
resultDiv.textContent = `项目详情API测试成功:\n${JSON.stringify(data, null, 2)}`;
} else {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
} catch (error) {
resultDiv.className = 'result error';
resultDiv.textContent = `项目详情API测试失败: ${error.message}`;
}
}
// 测试项目列表API
async function testProjectListAPI() {
const resultDiv = document.getElementById('api-result');
resultDiv.className = 'result info';
resultDiv.textContent = '正在测试项目列表API...';
try {
const response = await fetch('http://localhost:5301/bank/api/projects?page=1&limit=10', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
if (response.ok) {
const data = await response.json();
resultDiv.className = 'result success';
resultDiv.textContent = `项目列表API测试成功:\n${JSON.stringify(data, null, 2)}`;
} else {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
} catch (error) {
resultDiv.className = 'result error';
resultDiv.textContent = `项目列表API测试失败: ${error.message}`;
}
}
// 页面加载完成后的初始化
window.onload = function() {
console.log('项目详情页面测试工具已加载');
};
</script>
</body>
</html>