添加报表

This commit is contained in:
2025-12-25 17:18:12 +08:00
parent d46d50018e
commit 6ec846472d
15 changed files with 1078 additions and 15 deletions

View File

@@ -252,6 +252,36 @@
"navigationBarTitleText": "客户详情"
}
},
{
"path": "customer/transaction-list",
"style": {
"navigationBarTitleText": "交易记录"
}
},
{
"path": "customer/withdraw-list",
"style": {
"navigationBarTitleText": "提现记录"
}
},
{
"path": "me/index",
"style": {
"navigationBarTitleText": "银行中心"
}
},
{
"path": "report/list",
"style": {
"navigationBarTitleText": "报表列表"
}
},
{
"path": "report/download",
"style": {
"navigationBarTitleText": "报表下载"
}
},
{
"path": "visit/list",
"style": {
@@ -261,19 +291,13 @@
{
"path": "visit/create",
"style": {
"navigationBarTitleText": "创建拜访计划"
"navigationBarTitleText": "创建拜访"
}
},
{
"path": "visit/detail",
"style": {
"navigationBarTitleText": "拜访计划详情"
}
},
{
"path": "me/index",
"style": {
"navigationBarTitleText": "银行中心"
"navigationBarTitleText": "拜访详情"
}
}
]

View File

@@ -205,11 +205,6 @@ function handleLogout() {
<!-- 常用功能 -->
<view class="section-card">
<view class="cell-group">
<view class="cell" @click="navigateTo('/pages/me/loan-application')">
<text class="i-carbon-money icon"></text>
<text class="label">我要借钱</text>
<text class="i-carbon-chevron-right arrow"></text>
</view>
<view class="cell" @click="navigateTo('/pages/me/loan-application-records')">
<text class="i-carbon-document-attachment icon"></text>
<text class="label">助贷申请进度</text>

View File

@@ -17,6 +17,7 @@ import {
mockVisitPlans,
mockMarketingProducts
} from '../mock'
export { getReportList, downloadReport } from './report'
/** 获取银行端首页统计 */
export function getBankStats(): Promise<BankStats> {

View File

@@ -0,0 +1,22 @@
import type { ReportType, ReportDownloadParams } from '@/typings/bank'
import { mockReportList } from '../mock'
/** 获取报表列表 */
export function getReportList(): Promise<ReportType[]> {
return new Promise((resolve) => {
setTimeout(() => {
resolve(mockReportList)
}, 300)
})
}
/** 下载报表 */
export function downloadReport(params: ReportDownloadParams): Promise<boolean> {
return new Promise((resolve) => {
setTimeout(() => {
// 模拟文件生成和下载过程
console.log('下载报表:', params)
resolve(true)
}, 500)
})
}

View File

@@ -17,6 +17,7 @@ const quickActions = [
{ icon: 'i-carbon-group', label: '客户管理', path: '/pagesBank/customer/list' },
{ icon: 'i-carbon-calendar', label: '拜访计划', path: '/pagesBank/visit/list' },
{ icon: 'i-carbon-add', label: '创建拜访', path: '/pagesBank/visit/create' },
{ icon: 'i-carbon-document-download', label: '报表', path: '/pagesBank/report/list' },
{ icon: 'i-carbon-settings', label: '设置', path: '/pagesBank/me/index' },
]

View File

@@ -12,12 +12,20 @@ const userStore = useUserStore()
const config = CLIENT_TYPE_CONFIG[ClientType.BANK]
const menuList = [
{ icon: 'i-carbon-report', label: '数据报表' },
{ icon: 'i-carbon-report', label: '数据报表', path: '/pagesBank/report/list' },
{ icon: 'i-carbon-settings', label: '系统设置' },
{ icon: 'i-carbon-help', label: '帮助中心' },
{ icon: 'i-carbon-information', label: '关于我们' },
]
function handleMenuClick(item: { label: string; path?: string }) {
if (item.path) {
uni.navigateTo({ url: item.path })
} else {
uni.showToast({ title: '功能开发中', icon: 'none' })
}
}
function handleLogout() {
uni.showModal({
title: '提示',
@@ -48,7 +56,7 @@ function handleLogout() {
<!-- 菜单列表 -->
<view class="menu-list">
<view v-for="item in menuList" :key="item.label" class="menu-item">
<view v-for="item in menuList" :key="item.label" class="menu-item" @click="handleMenuClick(item)">
<view class="menu-left">
<text :class="item.icon" class="menu-icon"></text>
<text class="menu-label">{{ item.label }}</text>

View File

@@ -7,6 +7,7 @@ import type {
MarketingProduct
} from '@/typings/bank'
import { AuditStatus, AuditType, VisitStatus } from '@/typings/bank'
export { mockReportList, reportCategoryInfo } from './report'
// 统计数据 Mock
export const mockBankStats: BankStats = {

View File

@@ -0,0 +1,119 @@
import type { ReportType } from '@/typings/bank'
import { ReportCategory } from '@/typings/bank'
/** 报表列表 Mock 数据 */
export const mockReportList: ReportType[] = [
// 多维统计
{
id: 'R001',
name: '按支行统计',
category: ReportCategory.MULTI_DIMENSION
},
{
id: 'R002',
name: '按网点统计',
category: ReportCategory.MULTI_DIMENSION
},
{
id: 'R003',
name: '按部门统计',
category: ReportCategory.MULTI_DIMENSION
},
{
id: 'R004',
name: '按人员统计',
category: ReportCategory.MULTI_DIMENSION
},
{
id: 'R005',
name: '按客户统计',
category: ReportCategory.MULTI_DIMENSION
},
// 访客报表
{
id: 'R006',
name: '支行访客报表',
category: ReportCategory.VISITOR
},
{
id: 'R007',
name: '网点访客报表',
category: ReportCategory.VISITOR
},
{
id: 'R008',
name: '人员访客报表',
category: ReportCategory.VISITOR
},
// 营销报表
{
id: 'R009',
name: '小额贷业绩报表',
category: ReportCategory.MARKETING
},
{
id: 'R010',
name: '小额贷营销汇总',
category: ReportCategory.MARKETING
},
{
id: 'R011',
name: '消费贷营销报表',
category: ReportCategory.MARKETING
},
{
id: 'R012',
name: '三农部支行汇总',
category: ReportCategory.MARKETING
},
{
id: 'R013',
name: '三农部营销报表',
category: ReportCategory.MARKETING
},
{
id: 'R014',
name: '公司部营销汇总表',
category: ReportCategory.MARKETING
},
// 权益管理
{
id: 'R015',
name: '我赠送的记录',
category: ReportCategory.BENEFIT
},
{
id: 'R016',
name: '全员赠送记录',
category: ReportCategory.BENEFIT
},
{
id: 'R017',
name: '权益二维码',
category: ReportCategory.BENEFIT
}
]
/** 报表分类信息 */
export const reportCategoryInfo = {
[ReportCategory.MULTI_DIMENSION]: {
name: '多维统计',
icon: 'i-carbon-chart-cluster-bar',
color: '#00c05a'
},
[ReportCategory.VISITOR]: {
name: '访客报表',
icon: 'i-carbon-user-multiple',
color: '#4d80f0'
},
[ReportCategory.MARKETING]: {
name: '营销报表',
icon: 'i-carbon-ibm-watson-natural-language-understanding',
color: '#ff8f0d'
},
[ReportCategory.BENEFIT]: {
name: '权益管理',
icon: 'i-carbon-gift',
color: '#fa4350'
}
}

View File

@@ -0,0 +1,461 @@
<script lang="ts" setup>
import { downloadReport } from '@/pagesBank/api'
import { DateDimension } from '@/typings/bank'
definePage({
style: {
navigationBarTitleText: '报表下载',
},
})
// 从路由参数获取报表信息
const reportId = ref('')
const reportName = ref('')
// 日期维度选项
const dimensionOptions = [
{ label: '按日', value: DateDimension.DAY },
{ label: '按月', value: DateDimension.MONTH },
{ label: '按季', value: DateDimension.QUARTER },
{ label: '自定义', value: DateDimension.CUSTOM },
]
// 当前选中的日期维度
const activeDimension = ref(DateDimension.DAY)
// 当前选中的日期
const selectedDate = ref('2025-12-25')
// 日期选择器显示状态
const showDatePicker = ref(false)
// 下载中状态
const downloading = ref(false)
// 获取日期维度标签
function getDimensionLabel(dimension: DateDimension) {
const option = dimensionOptions.find(opt => opt.value === dimension)
return option?.label || ''
}
// 切换日期维度
function handleDimensionChange(dimension: DateDimension) {
activeDimension.value = dimension
// 根据维度更新默认日期
updateDefaultDate(dimension)
}
// 根据维度更新默认日期
function updateDefaultDate(dimension: DateDimension) {
const now = new Date()
const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, '0')
const day = String(now.getDate()).padStart(2, '0')
switch (dimension) {
case DateDimension.DAY:
selectedDate.value = `${year}-${month}-${day}`
break
case DateDimension.MONTH:
selectedDate.value = `${year}-${month}`
break
case DateDimension.QUARTER:
const quarter = Math.floor(now.getMonth() / 3) + 1
selectedDate.value = `${year}-Q${quarter}`
break
case DateDimension.CUSTOM:
selectedDate.value = `${year}-${month}-${day}`
break
}
}
// 显示日期选择器
function showDateSelector() {
showDatePicker.value = true
}
// 日期选择确认
function handleDateConfirm(e: any) {
const { value } = e
const date = new Date(value)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
if (activeDimension.value === DateDimension.MONTH) {
selectedDate.value = `${year}-${month}`
} else {
selectedDate.value = `${year}-${month}-${day}`
}
showDatePicker.value = false
}
// 下载报表
async function handleDownload() {
if (downloading.value) return
downloading.value = true
try {
await downloadReport({
reportId: reportId.value,
dimension: activeDimension.value,
date: selectedDate.value
})
uni.showToast({
title: '下载成功',
icon: 'success'
})
} catch (error) {
uni.showToast({
title: '下载失败',
icon: 'none'
})
} finally {
downloading.value = false
}
}
onLoad((options: any) => {
if (options.reportId) {
reportId.value = options.reportId
}
if (options.reportName) {
reportName.value = decodeURIComponent(options.reportName)
}
})
</script>
<template>
<view class="download-page">
<!-- 报表信息卡片 -->
<view class="report-info-card">
<view class="report-icon">
<text class="i-carbon-document"></text>
</view>
<view class="report-details">
<text class="report-name">{{ reportName }}</text>
<text class="report-id">报表编号: {{ reportId }}</text>
</view>
</view>
<!-- 日期维度筛选 -->
<view class="dimension-section">
<view class="section-title">选择日期维度</view>
<view class="dimension-tabs">
<view
v-for="option in dimensionOptions"
:key="option.value"
class="dimension-tab"
:class="{ active: activeDimension === option.value }"
@click="handleDimensionChange(option.value)"
>
{{ option.label }}
</view>
</view>
</view>
<!-- 日期选择 -->
<view class="date-section">
<view class="section-title">选择日期</view>
<view class="date-picker" @click="showDateSelector">
<view class="date-display">
<text class="i-carbon-calendar"></text>
<text class="date-value">{{ selectedDate }}</text>
</view>
<text class="i-carbon-chevron-right"></text>
</view>
</view>
<!-- 下载区域 -->
<view class="download-section">
<view class="format-tag">EXCEL</view>
<button
class="download-btn"
:class="{ loading: downloading }"
:disabled="downloading"
@click="handleDownload"
>
<text v-if="downloading" class="i-carbon-circle-dash loading-icon"></text>
<text>{{ downloading ? '下载中...' : '点击下载' }}</text>
</button>
</view>
<!-- 使用帮助 -->
<view class="help-section">
<view class="help-title">
<text class="i-carbon-help"></text>
<text>Excel格式: 使用帮助</text>
</view>
<view class="help-content">
<view class="help-item">
<text class="help-number">1</text>
<text class="help-text">选择要下载报表的日期</text>
</view>
<view class="help-item">
<text class="help-number">2</text>
<text class="help-text">点击下载按钮进行下载</text>
</view>
<view class="help-item">
<text class="help-number">3</text>
<text class="help-text">下载后自动打开文件可以点击手机右上角的...进行转发或保存文件</text>
</view>
</view>
</view>
<!-- 日期选择器弹窗 -->
<wd-datetime-picker
v-model="selectedDate"
v-model:show="showDatePicker"
type="date"
@confirm="handleDateConfirm"
@cancel="showDatePicker = false"
/>
</view>
</template>
<style lang="scss" scoped>
.download-page {
min-height: 100vh;
background: #f8f9fa;
padding: 30rpx;
}
.report-info-card {
background: #fff;
border-radius: 24rpx;
padding: 30rpx;
display: flex;
align-items: center;
gap: 24rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.02);
.report-icon {
width: 96rpx;
height: 96rpx;
background: linear-gradient(135deg, #00c05a 0%, #34d19d 100%);
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
text {
font-size: 48rpx;
color: #fff;
}
}
.report-details {
flex: 1;
.report-name {
font-size: 32rpx;
font-weight: 700;
color: #333;
display: block;
margin-bottom: 8rpx;
}
.report-id {
font-size: 24rpx;
color: #999;
}
}
}
.dimension-section,
.date-section {
background: #fff;
border-radius: 24rpx;
padding: 30rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.02);
}
.section-title {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 24rpx;
}
.dimension-tabs {
display: flex;
gap: 16rpx;
.dimension-tab {
flex: 1;
height: 72rpx;
background: #f8f9fa;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 26rpx;
color: #666;
transition: all 0.2s;
&.active {
background: #00c05a;
color: #fff;
font-weight: 600;
}
}
}
.date-picker {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx;
background: #f8f9fa;
border-radius: 16rpx;
.date-display {
display: flex;
align-items: center;
gap: 16rpx;
text:first-child {
font-size: 36rpx;
color: #00c05a;
}
.date-value {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
}
text:last-child {
font-size: 28rpx;
color: #adb5bd;
}
}
.download-section {
background: #fff;
border-radius: 24rpx;
padding: 40rpx 30rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.02);
display: flex;
flex-direction: column;
align-items: center;
gap: 30rpx;
.format-tag {
font-size: 24rpx;
color: #00c05a;
background: rgba(0, 192, 90, 0.1);
padding: 8rpx 24rpx;
border-radius: 8rpx;
font-weight: 600;
}
.download-btn {
width: 100%;
height: 96rpx;
background: linear-gradient(135deg, #00c05a 0%, #34d19d 100%);
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
font-size: 32rpx;
font-weight: 600;
color: #fff;
border: none;
transition: all 0.2s;
&:active:not(:disabled) {
opacity: 0.8;
transform: scale(0.98);
}
&:disabled {
opacity: 0.6;
}
&.loading {
opacity: 0.8;
}
.loading-icon {
font-size: 32rpx;
animation: spin 1s linear infinite;
}
}
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.help-section {
background: #fff;
border-radius: 24rpx;
padding: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.02);
.help-title {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 24rpx;
text:first-child {
font-size: 32rpx;
color: #4d80f0;
}
text:last-child {
font-size: 28rpx;
font-weight: 600;
color: #333;
}
}
.help-content {
.help-item {
display: flex;
align-items: flex-start;
gap: 16rpx;
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
.help-number {
width: 40rpx;
height: 40rpx;
background: #4d80f0;
color: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 22rpx;
font-weight: 600;
flex-shrink: 0;
}
.help-text {
flex: 1;
font-size: 26rpx;
color: #666;
line-height: 1.6;
}
}
}
}
</style>

View File

@@ -0,0 +1,217 @@
<script lang="ts" setup>
import { getReportList } from '@/pagesBank/api'
import type { ReportType } from '@/typings/bank'
import { ReportCategory } from '@/typings/bank'
import { reportCategoryInfo } from '@/pagesBank/mock'
// i-carbon-chart-cluster-bar
// i-carbon-user-multiple
// i-carbon-ibm-watson-natural-language-understanding
// i-carbon-gift
definePage({
style: {
navigationBarTitleText: '报表下载',
},
})
const reports = ref<ReportType[]>([])
const loading = ref(false)
// 按分类分组报表
const groupedReports = computed(() => {
const groups: Record<string, ReportType[]> = {}
reports.value.forEach(report => {
if (!groups[report.category]) {
groups[report.category] = []
}
groups[report.category].push(report)
})
return groups
})
// 获取分类信息
function getCategoryInfo(category: ReportCategory) {
return reportCategoryInfo[category] || { name: '未知', icon: '', color: '#999' }
}
// 点击报表跳转到下载页
function handleReportClick(report: ReportType) {
uni.navigateTo({
url: `/pagesBank/report/download?reportId=${report.id}&reportName=${encodeURIComponent(report.name)}`
})
}
async function loadData() {
loading.value = true
try {
const res = await getReportList()
reports.value = res
} finally {
loading.value = false
}
}
onMounted(() => {
loadData()
})
</script>
<template>
<view class="report-list-page">
<view v-if="loading && reports.length === 0" class="loading-state">
<text>加载中...</text>
</view>
<view v-else class="report-container">
<view
v-for="(groupReports, category) in groupedReports"
:key="category"
class="report-category"
>
<view class="category-header">
<view class="category-info">
<text :class="['category-icon', getCategoryInfo(category as ReportCategory).icon]"></text>
<text class="category-name">{{ getCategoryInfo(category as ReportCategory).name }}</text>
</view>
<view class="category-count">{{ groupReports.length }} 个报表</view>
</view>
<view class="report-grid">
<view
v-for="report in groupReports"
:key="report.id"
class="report-item"
@click="handleReportClick(report)"
>
<view class="report-icon">
<text :class="getCategoryInfo(report.category).icon"></text>
</view>
<text class="report-name">{{ report.name }}</text>
<view class="report-arrow">
<text class="i-carbon-chevron-right"></text>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<style lang="scss" scoped>
.report-list-page {
min-height: 100vh;
background: #f8f9fa;
padding-bottom: 30rpx;
}
.loading-state {
display: flex;
align-items: center;
justify-content: center;
padding: 100rpx 0;
color: #adb5bd;
text {
font-size: 28rpx;
}
}
.report-container {
padding: 24rpx 30rpx;
}
.report-category {
background: #fff;
border-radius: 24rpx;
padding: 30rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.02);
}
.category-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
padding-bottom: 20rpx;
border-bottom: 1rpx solid #f1f3f5;
.category-info {
display: flex;
align-items: center;
gap: 16rpx;
.category-icon {
font-size: 36rpx;
color: #00c05a;
}
.category-name {
font-size: 32rpx;
font-weight: 700;
color: #333;
}
}
.category-count {
font-size: 24rpx;
color: #999;
}
}
.report-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
}
.report-item {
background: #f8f9fa;
border-radius: 16rpx;
padding: 30rpx 24rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 16rpx;
position: relative;
transition: all 0.2s;
&:active {
background: #e9ecef;
transform: scale(0.98);
}
.report-icon {
width: 80rpx;
height: 80rpx;
background: #fff;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
text {
font-size: 40rpx;
color: #00c05a;
}
}
.report-name {
font-size: 26rpx;
color: #333;
text-align: center;
font-weight: 500;
}
.report-arrow {
position: absolute;
top: 20rpx;
right: 20rpx;
text {
font-size: 24rpx;
color: #adb5bd;
}
}
}
</style>

View File

@@ -100,6 +100,36 @@ export interface CompleteVisitPlanParams {
photos: string[]
}
/** 报表分类 */
export enum ReportCategory {
MULTI_DIMENSION = 'multi_dimension', // 多维统计
VISITOR = 'visitor', // 访客报表
MARKETING = 'marketing', // 营销报表
BENEFIT = 'benefit', // 权益管理
}
/** 日期维度 */
export enum DateDimension {
DAY = 'day', // 按日
MONTH = 'month', // 按月
QUARTER = 'quarter', // 按季
CUSTOM = 'custom', // 自定义
}
/** 报表类型 */
export interface ReportType {
id: string
name: string
category: ReportCategory
}
/** 报表下载参数 */
export interface ReportDownloadParams {
reportId: string
dimension: DateDimension
date: string
}
/** 银行统计指标 */
export interface BankStats {
pendingAuditStore: number // 待审核商户