Browse Source

feat:优化完善功能

叶静 1 month ago
parent
commit
3539705c5c

+ 38 - 0
src/api/admin/google.ts

@@ -0,0 +1,38 @@
+import request from '/@/utils/request';
+
+/**
+ * 获取Google验证器二维码
+ * @returns 返回base64编码的二维码图片
+ */
+export function getGoogleCode() {
+	return request({
+		url: '/admin/google/getCode',
+		method: 'post',
+	});
+}
+
+/**
+ * 绑定Google验证器
+ * @param code 6位验证码
+ * @returns 绑定结果
+ */
+export function bindingGoogle(code: string) {
+	return request({
+		url: '/admin/google/binding',
+		method: 'get',
+		params: { code },
+	});
+}
+
+/**
+ * 解绑Google验证器
+ * @param pwd 登录密码
+ * @returns 解绑结果
+ */
+export function unbindingGoogle(pwd: string) {
+	return request({
+		url: '/admin/google/unbinding',
+		method: 'post',
+		data: { pwd },
+	});
+}

+ 80 - 0
src/api/admin/setting.ts

@@ -0,0 +1,80 @@
+import request from '/@/utils/request';
+
+/**
+ * 获取Google验证器二维码
+ * @returns 返回base64编码的二维码图片
+ */
+export function getGoogleCode() {
+	return request({
+		url: '/admin/google/getCode',
+		method: 'post',
+	});
+}
+
+/**
+ * 绑定Google验证器
+ * @param code 6位验证码
+ * @returns 绑定结果
+ */
+export function bindingGoogle(code: string) {
+	return request({
+		url: '/admin/google/binding',
+		method: 'get',
+		params: { code },
+	});
+}
+
+/**
+ * 解绑Google验证器
+ * @param pwd 登录密码(明文)
+ * @returns 解绑结果
+ */
+export function unbindingGoogle(pwd: string) {
+	// 密码加密,与登录时使用相同的加密方式
+	return request({
+		url: '/admin/google/unbinding',
+		method: 'post',
+		data: { pwd },
+	});
+}
+
+/**
+ * 查看白名单
+ * @returns 返回白名单字符串
+ */
+export function getWhitelist() {
+	return request({
+		url: '/admin/merchant/getWhitelist',
+		method: 'get',
+	});
+}
+
+/**
+ * 设置白名单
+ * @param whitelist 白名单字符串
+ * @returns 设置结果
+ */
+export function editWhitelist(whitelist: string) {
+	return request({
+		url: '/admin/merchant/editWhitelist',
+		method: 'patch',
+		data: { whitelist },
+	});
+}
+
+/**
+ * 设置支付密码(二级密码)
+ * @param secondaryPwd 新的支付密码
+ * @param oldSecondaryPwd 旧的支付密码(可选,首次设置时为空)
+ * @returns 设置结果
+ */
+export function editSecondaryPwd(secondaryPwd: string, oldSecondaryPwd?: string) {
+	return request({
+		url: '/admin/agent/editSecondaryPwd',
+		method: 'patch',
+		data: {
+			secondaryPwd,
+			oldSecondaryPwd: oldSecondaryPwd || '',
+		},
+	});
+}

+ 2 - 2
src/api/login/index.ts

@@ -50,11 +50,11 @@ export const login = (data: any) => {
 	Session.set('basicAuth', basicAuth);
 	// 密码加密
 	const encPassword = other.encryption(data.password, import.meta.env.VITE_PWD_ENC_KEY);
-	const { username, randomStr, code, grant_type, scope } = data;
+	const { username, randomStr, code, grant_type, scope, googleCaptcha } = data;
 	return request({
 		url: '/auth/oauth2/token',
 		method: 'post',
-		params: { username, randomStr, code, grant_type, scope },
+		params: { username, randomStr, code, grant_type, scope, googleCaptcha },
 		data: { password: encPassword },
 		headers: {
 			skipToken: true,

+ 42 - 0
src/api/order/index.ts

@@ -0,0 +1,42 @@
+import request from '/@/utils/request';
+
+// ==================== 代收订单相关 API ====================
+
+export function fetchPayOrderList(query?: Object) {
+	return request({
+		url: 'open/admin/payOrder/page',
+		method: 'post',
+		data: query,
+	});
+}
+
+export function fetchPayOrderStatistics(query?: Object) {
+	return request({
+		url: 'open/admin/payOrder/statistics',
+		method: 'post',
+		data: query,
+	});
+}
+
+// ==================== 代付订单相关 API ====================
+
+export function fetchWithdrawOrderList(query?: Object) {
+	return request({
+		url: 'open/admin/withdrawOrder/page',
+		method: 'post',
+		data: query,
+	});
+}
+
+// ==================== 回调日志相关 API ====================
+
+export function fetchPayNotifyLog(appId: string, orderId: string) {
+	return request({
+		url: 'open/admin/payNotify/page',
+		method: 'get',
+		params: {
+			appId,
+			orderId,
+		},
+	});
+}

+ 11 - 0
src/api/payment/channel.ts

@@ -0,0 +1,11 @@
+import request from '/@/utils/request';
+
+/**
+ * 查看开通的费率
+ */
+export function getMerchantRate() {
+	return request({
+		url: 'admin/agent/getRate',
+		method: 'get',
+	});
+}

+ 44 - 0
src/api/settlement/index.ts

@@ -0,0 +1,44 @@
+import request from '/@/utils/request';
+
+/**
+ * 申请提现
+ */
+export function applyWithdraw(data: any) {
+	return request({
+		url: 'admin/agentWithdraw/apply',
+		method: 'post',
+		data,
+	});
+}
+
+/**
+ * 分页查询提现记录
+ */
+export function fetchWithdrawList(query?: Object) {
+	return request({
+		url: 'admin/agentWithdraw/page',
+		method: 'post',
+		data: query,
+	});
+}
+
+/**
+ * 获取商户余额
+ */
+export function getAgentBalance() {
+	return request({
+		url: 'admin/agent/getBalance',
+		method: 'get',
+	});
+}
+
+/**
+ * 查看资金流水
+ */
+export function fetchFundFlowList(query?: Object) {
+	return request({
+		url: 'admin/fundFlow/flowsPage',
+		method: 'get',
+		params: query,
+	});
+}

+ 12 - 0
src/api/statistics/index.ts

@@ -0,0 +1,12 @@
+import request from '/@/utils/request';
+
+/**
+ * 首页数据统计
+ * @returns 返回统计数据
+ */
+export function getIndexStatistics() {
+	return request({
+		url: '/open/statistisc/index',
+		method: 'get',
+	});
+}

+ 242 - 0
src/config/menuConfig.ts

@@ -0,0 +1,242 @@
+/**
+ * 固定导航菜单配置
+ * @description 系统的导航菜单固定配置,不再从后端获取
+ */
+
+export function getStaticMenuData() {
+	return {
+		code: 0,
+		msg: null,
+		data: [
+			{
+				id: '1000',
+				parentId: '-1',
+				weight: 1,
+				name: '订单管理',
+				path: '/order',
+				componentPath: null,
+				meta: {
+					isLink: '',
+					isIframe: false,
+					isKeepAlive: false,
+					icon: 'iconfont icon-document-record',
+					isAffix: false,
+					title: '订单管理',
+					isHide: false,
+				},
+				sortOrder: 1,
+				menuType: '0',
+				permission: null,
+				children: [
+					{
+						id: '1001',
+						parentId: '1000',
+						weight: 0,
+						name: '代收订单',
+						path: '/order/payOrder/index',
+						componentPath: null,
+						meta: {
+							isLink: '',
+							isIframe: false,
+							isKeepAlive: true,
+							icon: 'iconfont icon-document-record',
+							isAffix: false,
+							title: '代收订单',
+							isHide: false,
+						},
+						sortOrder: 1,
+						menuType: '0',
+						permission: null,
+					},
+					{
+						id: '1002',
+						parentId: '1000',
+						weight: 0,
+						name: '代付订单',
+						path: '/order/withdrawOrder/index',
+						componentPath: null,
+						meta: {
+							isLink: '',
+							isIframe: false,
+							isKeepAlive: true,
+							icon: 'iconfont icon-document-record',
+							isAffix: false,
+							title: '代付订单',
+							isHide: false,
+						},
+						sortOrder: 2,
+						menuType: '0',
+						permission: null,
+					},
+				],
+			},
+			{
+				id: '2000',
+				parentId: '-1',
+				weight: 1,
+				name: '支付配置',
+				path: '/payment',
+				componentPath: null,
+				meta: {
+					isLink: '',
+					isIframe: false,
+					isKeepAlive: false,
+					icon: 'iconfont icon-shujuyuanguanli',
+					isAffix: false,
+					title: '支付配置',
+					isHide: false,
+				},
+				sortOrder: 2,
+				menuType: '0',
+				permission: null,
+				children: [
+					{
+						id: '2001',
+						parentId: '2000',
+						weight: 0,
+						name: '支付通道',
+						path: '/payment/channel/index',
+						componentPath: null,
+						meta: {
+							isLink: '',
+							isIframe: false,
+							isKeepAlive: true,
+							icon: 'iconfont icon-shujuyuanguanli',
+							isAffix: false,
+							title: '支付通道',
+							isHide: false,
+						},
+						sortOrder: 1,
+						menuType: '0',
+						permission: null,
+					},
+				],
+			},
+			{
+				id: '3000',
+				parentId: '-1',
+				weight: 1,
+				name: '结算管理',
+				path: '/settlement',
+				componentPath: null,
+				meta: {
+					isLink: '',
+					isIframe: false,
+					isKeepAlive: false,
+					icon: 'iconfont icon-lingpai',
+					isAffix: false,
+					title: '结算管理',
+					isHide: false,
+				},
+				sortOrder: 3,
+				menuType: '0',
+				permission: null,
+				children: [
+					{
+						id: '3001',
+						parentId: '3000',
+						weight: 0,
+						name: '申请结算',
+						path: '/settlement/apply/index',
+						componentPath: null,
+						meta: {
+							isLink: '',
+							isIframe: false,
+							isKeepAlive: false,
+							icon: 'iconfont icon-lingpai',
+							isAffix: false,
+							title: '申请结算',
+							isHide: false,
+						},
+						sortOrder: 1,
+						menuType: '0',
+						permission: null,
+					},
+					{
+						id: '3002',
+						parentId: '3000',
+						weight: 0,
+						name: '结算记录',
+						path: '/settlement/record/index',
+						componentPath: null,
+						meta: {
+							isLink: '',
+							isIframe: false,
+							isKeepAlive: false,
+							icon: 'iconfont icon-lingpai',
+							isAffix: false,
+							title: '结算记录',
+							isHide: false,
+						},
+						sortOrder: 2,
+						menuType: '0',
+						permission: null,
+					},
+					{
+						id: '3003',
+						parentId: '3000',
+						weight: 0,
+						name: '资金明细',
+						path: '/settlement/fundFlow/index',
+						componentPath: null,
+						meta: {
+							isLink: '',
+							isIframe: false,
+							isKeepAlive: false,
+							icon: 'iconfont icon-lingpai',
+							isAffix: false,
+							title: '资金明细',
+							isHide: false,
+						},
+						sortOrder: 3,
+						menuType: '0',
+						permission: null,
+					},
+				],
+			},
+			{
+				id: '4000',
+				parentId: '-1',
+				weight: 1,
+				name: '系统设置',
+				path: '/settings',
+				componentPath: null,
+				meta: {
+					isLink: '',
+					isIframe: false,
+					isKeepAlive: false,
+					icon: 'iconfont icon-xitongshezhi',
+					isAffix: false,
+					title: '系统设置',
+					isHide: false,
+				},
+				sortOrder: 4,
+				menuType: '0',
+				permission: null,
+				children: [
+					{
+						id: '4001',
+						parentId: '4000',
+						weight: 0,
+						name: '安全中心',
+						path: '/settings/security/index',
+						componentPath: null,
+						meta: {
+							isLink: '',
+							isIframe: false,
+							isKeepAlive: false,
+							icon: 'iconfont icon-xitongshezhi',
+							isAffix: false,
+							title: '安全中心',
+							isHide: false,
+						},
+						sortOrder: 0,
+						menuType: '0',
+						permission: null,
+					},
+				],
+			},
+		],
+		ok: true,
+	};
+}

+ 9 - 10
src/router/backEnd.ts

@@ -8,12 +8,9 @@ import { baseRoutes, notFoundAndNoPower, dynamicRoutes } from '/@/router/route';
 import { formatTwoStageRoutes, formatFlatteningRoutes, router } from '/@/router/index';
 import { useRoutesList } from '/@/stores/routesList';
 import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
-import { useMenuApi } from '/@/api/admin/menu';
+import { getStaticMenuData } from '/@/config/menuConfig';
 
-// 后端控制路由
-
-// 引入 api 请求接口
-const menuApi = useMenuApi();
+// 后端控制路由(已改为从配置文件读取固定菜单)
 
 /**
  * 获取目录下的 .vue、.tsx 全部文件
@@ -100,12 +97,13 @@ export async function setAddRoute() {
 }
 
 /**
- * 请求后端路由菜单接口
- * @description isRequestRoutes 为 true,则开启后端控制路由
- * @returns 返回后端路由菜单数据
+ * 获取路由菜单数据(从配置文件读取)
+ * @description 不再从后端API获取,改为从配置文件读取固定菜单
+ * @returns 返回路由菜单数据
  */
 export function getBackEndControlRoutes() {
-	return menuApi.getAdminMenu();
+	// 从配置文件获取固定菜单数据
+	return Promise.resolve(getStaticMenuData());
 }
 
 /**
@@ -132,7 +130,8 @@ export function backEndComponent(routes: any) {
 				item.component = () => import('/@/layout/routerView/link.vue');
 			}
 			item.path = '/iframes/' + window.btoa(item.path);
-		} else if (item.componentPath) { // 支持动态路径  /a/1 ==> /a  ; /b/1 ==> /b
+		} else if (item.componentPath) {
+			// 支持动态路径  /a/1 ==> /a  ; /b/1 ==> /b
 			item.component = dynamicImport(dynamicViewsModules, item.componentPath);
 		} else {
 			item.component = dynamicImport(dynamicViewsModules, item.path);

+ 6 - 4
src/stores/userInfo.ts

@@ -18,6 +18,7 @@ export const useUserInfo = defineStore('userInfo', {
 			authBtnList: [],
 			tenantId: '',
 			tenantName: '',
+			appId: '', // 商户号
 		},
 	}),
 
@@ -128,10 +129,11 @@ export const useUserInfo = defineStore('userInfo', {
 				const userInfo: any = {
 					user: res.data,
 					time: new Date().getTime(),
-					roles: res.data.roleList,
-					authBtnList: res.data.permissions,
-					tenantId: res.data.tenantId,
-					tenantName: res.data.tenantName || ''
+					roles: res.data.roleList || [],
+					authBtnList: res.data.permissions || [],
+					tenantId: res.data.tenantId || '',
+					tenantName: res.data.tenantName || '',
+					appId: res.data.appId || '' // 商户号
 				};
 				this.userInfos = userInfo;
 

+ 1 - 0
src/types/pinia.d.ts

@@ -12,6 +12,7 @@ declare interface UserInfosState<T = any> {
 		userName: string;
 		tenantId: string;
 		tenantName: string;
+		appId: string; // 商户号
 		[key: string]: T;
 	};
 }

+ 8 - 6
src/utils/request.ts

@@ -1,6 +1,6 @@
 import axios, { AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
 import { Local, Session, STORAGE_KEYS } from '/@/utils/storage';
-import { useMessageBox } from '/@/hooks/message';
+import { useMessageBox, useMessage } from '/@/hooks/message';
 import qs from 'qs';
 import other from './other';
 import { wrapEncryption, encryptRequestParams, decrypt } from './apiCrypto';
@@ -82,14 +82,16 @@ service.interceptors.request.use(
  * @returns 如果响应成功,则返回响应的data属性;否则,抛出错误或者执行其他操作
  */
 const handleResponse = (response: AxiosResponse<any>) => {
-	if (response.data.code === 1) {
-		throw response.data;
-	}
-
 	// 针对密文返回解密
 	if (response.data.encryption) {
 		response.data = decrypt(response.data.encryption);
-		return response.data;
+	}
+
+	// 当 code 不等于 0 时,显示错误消息并抛出错误
+	if (response.data.code !== undefined && response.data.code !== 0) {
+		const errorMsg = response.data.msg || '操作失败';
+		useMessage().error(errorMsg);
+		throw response.data;
 	}
 
 	return response.data;

+ 80 - 107
src/views/admin/system/user/personal.vue

@@ -2,79 +2,76 @@
 	<el-drawer v-model="visible" :title="$t('personal.name')" size="40%">
 		<el-tabs style="height: 200px" class="demo-tabs">
 			<el-tab-pane label="基本信息" v-loading="loading">
-        <template #label>
-          <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
-            <path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
-          </svg>
-          基本信息
-        </template>
+				<template #label>
+					<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
+						<path
+							stroke-linecap="round"
+							stroke-linejoin="round"
+							d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
+						/>
+					</svg>
+					基本信息
+				</template>
 				<el-form :model="formData" :rules="ruleForm" label-width="100px" class="mt30" ref="formdataRef">
 					<el-row :gutter="20">
-						<el-col :span="24" class="mb20">
-							<el-form-item prop="avatar">
-								<ImageUpload v-model:imageUrl="formData.avatar" borderRadius="50%">
-									<template #empty>
-										<el-icon><Avatar /></el-icon>
-										<span>请上传头像</span>
-									</template>
-								</ImageUpload>
-							</el-form-item>
-						</el-col>
-						<el-col :span="24" class="mb20">
-							<el-form-item label="用户名" prop="username">
-								<el-input v-model="formData.username" clearable disabled></el-input>
+						<el-col :span="24" class="mb20" v-if="formData?.appId">
+							<el-form-item label="商户号">
+								<el-input v-model="formData.appId" readonly></el-input>
 							</el-form-item>
 						</el-col>
-						<el-col :span="24" class="mb20">
-							<el-form-item label="手机" prop="phone">
-								<el-input v-model="formData.phone" placeholder="请输入手机" clearable></el-input>
+						<el-col :span="24" class="mb20" v-if="formData?.nickname">
+							<el-form-item label="昵称" prop="nickname">
+								<el-input v-model="formData.nickname" placeholder="请输入昵称" clearable readonly></el-input>
 							</el-form-item>
 						</el-col>
-
-						<el-col :span="24" class="mb20">
-							<el-form-item label="邮箱" prop="email">
-								<el-input v-model="formData.email" placeholder="请输入邮箱" clearable></el-input>
+						<el-col :span="24" class="mb20" v-if="formData?.name">
+							<el-form-item label="姓名" prop="name">
+								<el-input v-model="formData.name" placeholder="请输入姓名" clearable readonly></el-input>
 							</el-form-item>
 						</el-col>
-						<el-col :span="24" class="mb20">
-							<el-form-item label="昵称" prop="nickname">
-								<el-input v-model="formData.nickname" placeholder="请输入昵称" clearable></el-input>
+						<el-col :span="24" class="mb20" v-if="formData?.phone">
+							<el-form-item label="手机" prop="phone">
+								<el-input v-model="formData.phone" placeholder="请输入手机" clearable readonly></el-input>
 							</el-form-item>
 						</el-col>
-						<el-col :span="24" class="mb20">
-							<el-form-item label="姓名" prop="name">
-								<el-input v-model="formData.name" placeholder="请输入姓名" clearable></el-input>
+						<el-col :span="24" class="mb20" v-if="formData?.email">
+							<el-form-item label="邮箱" prop="email">
+								<el-input v-model="formData.email" placeholder="请输入邮箱" clearable readonly></el-input>
 							</el-form-item>
 						</el-col>
-						<el-col :span="24" class="mb20">
+						<!-- <el-col :span="24" class="mb20">
 							<el-form-item>
 								<el-button type="primary" @click="handleSaveUser"> 更新个人信息 </el-button>
 							</el-form-item>
-						</el-col>
+						</el-col> -->
 					</el-row>
 				</el-form>
 			</el-tab-pane>
 			<el-tab-pane label="安全信息">
-        <template #label>
-          <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
-            <path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75m-3-7.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285Z" />
-          </svg>
-          安全信息
-        </template>
+				<template #label>
+					<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
+						<path
+							stroke-linecap="round"
+							stroke-linejoin="round"
+							d="M9 12.75 11.25 15 15 9.75m-3-7.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285Z"
+						/>
+					</svg>
+					安全信息
+				</template>
 				<el-form :model="passwordFormData" :rules="passwordRuleForm" label-width="100px" class="mt30" ref="passwordFormdataRef">
 					<el-row :gutter="20">
 						<el-col :span="24" class="mb20">
 							<el-form-item label="原密码" prop="password">
-								<el-input v-model="passwordFormData.password"  :type="showPassword ? 'text' : 'password'" placeholder="请输入密码" clearable type="password">
-                  <template #suffix>
-                    <i
-                        class="iconfont el-input__icon login-content-password"
-                        :class="showPassword ? 'icon-yincangmima' : 'icon-xianshimima'"
-                        @click="showPassword = !showPassword"
-                    >
-                    </i>
-                  </template>
-                </el-input>
+								<el-input v-model="passwordFormData.password" :type="showPassword ? 'text' : 'password'" placeholder="请输入密码" clearable>
+									<template #suffix>
+										<i
+											class="iconfont el-input__icon login-content-password"
+											:class="showPassword ? 'icon-yincangmima' : 'icon-xianshimima'"
+											@click="showPassword = !showPassword"
+										>
+										</i>
+									</template>
+								</el-input>
 							</el-form-item>
 						</el-col>
 						<el-col :span="24" class="mb20">
@@ -102,49 +99,23 @@
 					</el-row>
 				</el-form>
 			</el-tab-pane>
-			<el-tab-pane label="第三方账号">
-        <template #label>
-          <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
-            <path stroke-linecap="round" stroke-linejoin="round" d="M7.864 4.243A7.5 7.5 0 0 1 19.5 10.5c0 2.92-.556 5.709-1.568 8.268M5.742 6.364A7.465 7.465 0 0 0 4.5 10.5a7.464 7.464 0 0 1-1.15 3.993m1.989 3.559A11.209 11.209 0 0 0 8.25 10.5a3.75 3.75 0 1 1 7.5 0c0 .527-.021 1.049-.064 1.565M12 10.5a14.94 14.94 0 0 1-3.6 9.75m6.633-4.596a18.666 18.666 0 0 1-2.485 5.33" />
-          </svg>
-
-          社交登录
-        </template>
-				<el-table :data="socialList" class="mt10">
-					<el-table-column type="index" label="序号" width="80"></el-table-column>
-					<el-table-column prop="name" label="平台"></el-table-column>
-					<el-table-column label="状态">
-						<template #default="scope">
-							<el-tag v-if="scope.row.openId"> 已绑定 </el-tag>
-							<el-tag v-else> 未绑定 </el-tag>
-						</template>
-					</el-table-column>
-					<el-table-column prop="action" label="操作">
-						<template #default="scope">
-							<el-button @click="unbinding(scope.row.type)" text type="primary" v-if="scope.row.openId"> 解绑 </el-button>
-							<el-button @click="handleClick(scope.row.type)" text type="primary" v-else> 绑定 </el-button>
-						</template>
-					</el-table-column>
-				</el-table>
-			</el-tab-pane>
 		</el-tabs>
 	</el-drawer>
 </template>
 
 <script setup lang="ts" name="personal">
-import {useUserInfo} from '/@/stores/userInfo';
-import {editInfo, getObj, password, unbindingUser} from '/@/api/admin/user';
-import {useMessage} from '/@/hooks/message';
-import {rule, validateNull} from '/@/utils/validate';
+import { useUserInfo } from '/@/stores/userInfo';
+import { editInfo, password, unbindingUser } from '/@/api/admin/user';
+import { useMessage } from '/@/hooks/message';
+import { rule, validateNull } from '/@/utils/validate';
 import other from '/@/utils/other';
-import {Session} from '/@/utils/storage';
-import {useI18n} from 'vue-i18n';
-import {getLoginAppList} from "/@/api/admin/social";
+import { Session } from '/@/utils/storage';
+import { useI18n } from 'vue-i18n';
+import { getLoginAppList } from '/@/api/admin/social';
 import { SocialLoginEnum } from '/@/api/login';
 
 const { t } = useI18n();
 
-const ImageUpload = defineAsyncComponent(() => import('/@/components/Upload/Image.vue'));
 const StrengthMeter = defineAsyncComponent(() => import('/@/components/StrengthMeter/index.vue'));
 
 const visible = ref(false);
@@ -153,13 +124,14 @@ const visible = ref(false);
 const formData = ref({
 	userId: '',
 	username: '',
+	appId: '',
 	name: '',
 	email: '',
 	avatar: '',
 	nickname: '',
 	wxDingUserid: '',
 	wxCpUserid: '',
-	phone: ('' as string) || undefined,
+	phone: '',
 });
 
 const showPassword = ref(false);
@@ -173,13 +145,10 @@ const formdataRef = ref();
 const passwordFormdataRef = ref();
 
 const ruleForm = reactive({
-	phone: [
-		{ required: true, message: '手机号不能为空', trigger: 'blur' },
-		{ validator: rule.validatePhone, trigger: 'blur' },
-	],
-	nickname: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '昵称不能为空', trigger: 'blur' }],
-	email: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '邮箱不能为空', trigger: 'blur' }],
-	name: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '姓名不能为空', trigger: 'blur' }],
+	phone: [{ validator: rule.validatePhone, trigger: 'blur' }],
+	nickname: [{ validator: rule.overLength, trigger: 'blur' }],
+	email: [{ validator: rule.overLength, trigger: 'blur' }],
+	name: [{ validator: rule.overLength, trigger: 'blur' }],
 });
 const validatorPassword2 = (rule: any, value: any, callback: any) => {
 	if (value !== passwordFormData.newpassword1) {
@@ -252,7 +221,7 @@ const handleSaveUser = () => {
 		}
 
 		if (formData.value.phone && formData.value.phone.includes('*')) {
-			formData.value.phone = undefined;
+			formData.value.phone = '';
 		}
 
 		editInfo(formData.value)
@@ -318,31 +287,35 @@ const unbinding = (type: SocialLoginEnum) => {
 			useMessage().error(err.msg);
 		})
 		.finally(() => {
-			initUserInfo(formData.value.userId);
+			// 重新获取用户信息
+			useUserInfo()
+				.setUserInfos()
+				.then(() => {
+					const data = useUserInfo().userInfos;
+					initUserInfo(data.user);
+				});
 		});
 };
 
 const open = () => {
 	visible.value = true;
 	const data = useUserInfo().userInfos;
-	initUserInfo(data.user.userId);
-	// Object.assign(formData, data.user);
+	initUserInfo(data.user);
 };
 
 const loading = ref(false);
-const initUserInfo = (userId: any) => {
+const initUserInfo = (userData: any) => {
 	loading.value = true;
-	getObj(userId)
-		.then((res) => {
-			formData.value = res.data;
-			initSocialList();
-		})
-		.catch((err) => {
-			useMessage().error(err.msg);
-		})
-		.finally(() => {
-			loading.value = false;
-		});
+	// 过滤掉 null 和 undefined 值
+	const filteredData: any = {};
+	Object.keys(userData).forEach((key) => {
+		if (userData[key] !== null && userData[key] !== undefined) {
+			filteredData[key] = userData[key];
+		}
+	});
+	formData.value = filteredData;
+	initSocialList();
+	loading.value = false;
 };
 
 // 暴露变量

+ 284 - 16
src/views/home/index.vue

@@ -1,24 +1,292 @@
 <template>
-  <div>
-    <div v-if="pageLoading">
-      <el-main>
-        <el-card shadow="never">
-          <el-skeleton :rows="1"></el-skeleton>
-        </el-card>
-        <el-card shadow="never" style="margin-top: 15px;">
-          <el-skeleton></el-skeleton>
-        </el-card>
-      </el-main>
-    </div>
-    <widgets/>
-  </div>
+	<div class="home-container">
+		<el-row :gutter="20" v-loading="loading">
+			<!-- 提交订单 -->
+			<el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
+				<el-card shadow="hover" class="stat-card">
+					<div class="stat-item">
+						<div class="stat-icon" style="background: #409eff">
+							<el-icon :size="28"><DocumentAdd /></el-icon>
+						</div>
+						<div class="stat-info">
+							<div class="stat-title">提交订单</div>
+							<div class="stat-number">{{ statistics.submitCreateNum || 0 }}</div>
+							<div class="stat-desc">金额: {{ formatAmount(statistics.submitCreateAmount) }}</div>
+						</div>
+					</div>
+				</el-card>
+			</el-col>
+
+			<!-- 支付订单 -->
+			<el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
+				<el-card shadow="hover" class="stat-card">
+					<div class="stat-item">
+						<div class="stat-icon" style="background: #67c23a">
+							<el-icon :size="28"><Wallet /></el-icon>
+						</div>
+						<div class="stat-info">
+							<div class="stat-title">支付订单</div>
+							<div class="stat-number">{{ statistics.payOrderNum || 0 }}</div>
+							<div class="stat-desc">金额: {{ formatAmount(statistics.payOrderAmount) }}</div>
+						</div>
+					</div>
+				</el-card>
+			</el-col>
+
+			<!-- 提现订单 -->
+			<el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
+				<el-card shadow="hover" class="stat-card">
+					<div class="stat-item">
+						<div class="stat-icon" style="background: #e6a23c">
+							<el-icon :size="28"><Money /></el-icon>
+						</div>
+						<div class="stat-info">
+							<div class="stat-title">提现订单</div>
+							<div class="stat-number">{{ statistics.withdrawOrderNum || 0 }}</div>
+							<div class="stat-desc">金额: {{ formatAmount(statistics.withdrawOrderAmount) }}</div>
+						</div>
+					</div>
+				</el-card>
+			</el-col>
+
+			<!-- 提现成功 -->
+			<el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
+				<el-card shadow="hover" class="stat-card">
+					<div class="stat-item">
+						<div class="stat-icon" style="background: #f56c6c">
+							<el-icon :size="28"><CircleCheck /></el-icon>
+						</div>
+						<div class="stat-info">
+							<div class="stat-title">提现成功</div>
+							<div class="stat-number">{{ statistics.withdrawOrderSuccessNum || 0 }}</div>
+							<div class="stat-desc">金额: {{ formatAmount(statistics.withdrawOrderSuccessAmount) }}</div>
+						</div>
+					</div>
+				</el-card>
+			</el-col>
+		</el-row>
+
+		<!-- 支付成功率 -->
+		<el-row :gutter="20" style="margin-top: 20px">
+			<el-col :span="24">
+				<el-card shadow="hover" class="rate-card">
+					<div class="rate-item">
+						<div class="rate-icon">
+							<el-icon :size="40"><TrendCharts /></el-icon>
+						</div>
+						<div class="rate-info">
+							<div class="rate-title">支付成功率</div>
+							<div class="rate-number">{{ formatRate(statistics.paySuccessRate) }}</div>
+						</div>
+						<div class="rate-progress">
+							<el-progress :percentage="statistics.paySuccessRate || 0" :stroke-width="20" :text-inside="true" />
+						</div>
+					</div>
+				</el-card>
+			</el-col>
+		</el-row>
+	</div>
 </template>
 
 <script setup lang="ts" name="dashboard">
-const Widgets = defineAsyncComponent(() => import('./widgets/index.vue'));
-const pageLoading = ref(true);
+import { ref, onMounted } from 'vue';
+import { DocumentAdd, Wallet, Money, CircleCheck, TrendCharts } from '@element-plus/icons-vue';
+import { getIndexStatistics } from '/@/api/statistics/index';
+import { ElMessage } from 'element-plus';
+
+// 统计数据
+const statistics = ref({
+	submitCreateNum: 0,
+	payOrderNum: 0,
+	submitCreateAmount: 0,
+	payOrderAmount: 0,
+	withdrawOrderNum: 0,
+	withdrawOrderSuccessNum: 0,
+	withdrawOrderAmount: 0,
+	withdrawOrderSuccessAmount: 0,
+	paySuccessRate: 0,
+});
+
+const loading = ref(false);
+
+// 格式化金额
+const formatAmount = (amount: number | undefined) => {
+	if (!amount) return '¥0.00';
+	return `¥${amount.toFixed(2)}`;
+};
+
+// 格式化百分比
+const formatRate = (rate: number | undefined) => {
+	if (!rate) return '0%';
+	return `${rate.toFixed(2)}%`;
+};
+
+// 加载统计数据
+const loadStatistics = async () => {
+	loading.value = true;
+	try {
+		const res = await getIndexStatistics();
+		if (res.code === 0) {
+			statistics.value = res.data;
+		} else {
+			ElMessage.error(res.msg || '加载统计数据失败');
+		}
+	} catch (error) {
+		ElMessage.error('加载统计数据失败');
+	} finally {
+		loading.value = false;
+	}
+};
 
 onMounted(() => {
-  pageLoading.value = false;
+	loadStatistics();
 });
 </script>
+
+<style scoped lang="scss">
+.home-container {
+	padding: 20px;
+
+	.stat-card {
+		margin-bottom: 20px;
+		transition: all 0.3s;
+
+		&:hover {
+			transform: translateY(-2px);
+		}
+
+		.stat-item {
+			display: flex;
+			align-items: center;
+			padding: 10px;
+
+			.stat-icon {
+				width: 60px;
+				height: 60px;
+				border-radius: 8px;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				color: #fff;
+				margin-right: 20px;
+			}
+
+			.stat-info {
+				flex: 1;
+
+				.stat-title {
+					font-size: 14px;
+					color: var(--el-text-color-secondary);
+					margin-bottom: 8px;
+				}
+
+				.stat-number {
+					font-size: 28px;
+					font-weight: bold;
+					color: var(--el-text-color-primary);
+					margin-bottom: 5px;
+				}
+
+				.stat-desc {
+					font-size: 13px;
+					color: var(--el-text-color-regular);
+				}
+			}
+		}
+	}
+
+	.rate-card {
+		.rate-item {
+			display: flex;
+			align-items: center;
+			padding: 20px 10px;
+
+			.rate-icon {
+				width: 80px;
+				height: 80px;
+				border-radius: 50%;
+				background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				color: #fff;
+				margin-right: 30px;
+			}
+
+			.rate-info {
+				margin-right: 30px;
+
+				.rate-title {
+					font-size: 16px;
+					color: var(--el-text-color-secondary);
+					margin-bottom: 10px;
+				}
+
+				.rate-number {
+					font-size: 36px;
+					font-weight: bold;
+					color: var(--el-text-color-primary);
+				}
+			}
+
+			.rate-progress {
+				flex: 1;
+				min-width: 300px;
+			}
+		}
+	}
+}
+
+@media (max-width: 768px) {
+	.home-container {
+		padding: 10px;
+
+		.stat-card {
+			margin-bottom: 10px;
+
+			.stat-item {
+				.stat-icon {
+					width: 50px;
+					height: 50px;
+					margin-right: 15px;
+				}
+
+				.stat-info {
+					.stat-number {
+						font-size: 24px;
+					}
+				}
+			}
+		}
+
+		.rate-card {
+			.rate-item {
+				flex-direction: column;
+				align-items: flex-start;
+				padding: 15px;
+
+				.rate-icon {
+					width: 60px;
+					height: 60px;
+					margin-right: 0;
+					margin-bottom: 15px;
+				}
+
+				.rate-info {
+					margin-right: 0;
+					margin-bottom: 15px;
+
+					.rate-number {
+						font-size: 28px;
+					}
+				}
+
+				.rate-progress {
+					width: 100%;
+					min-width: auto;
+				}
+			}
+		}
+	}
+}
+</style>

+ 20 - 2
src/views/login/component/password.vue

@@ -39,6 +39,23 @@
 				</template>
 			</el-input>
 		</el-form-item>
+		<el-form-item class="login-animation3" prop="googleCaptcha">
+			<el-input
+				text
+				maxlength="6"
+				placeholder="谷歌验证码(若没有可以不填)"
+				v-model="state.ruleForm.googleCaptcha"
+				clearable
+				autocomplete="off"
+				class="dark:bg-slate-700 dark:text-slate-200"
+			>
+				<template #prefix>
+					<el-icon class="el-input__icon dark:text-slate-400">
+						<ele-Key />
+					</el-icon>
+				</template>
+			</el-input>
+		</el-form-item>
 		<el-form-item class="login-animation2" prop="code" v-if="verifyImageEnable">
 			<el-col :span="15">
 				<el-input text maxlength="4" :placeholder="$t('mobile.placeholder2')" v-model="state.ruleForm.code" clearable autocomplete="off">
@@ -108,8 +125,9 @@ const state = reactive({
 	isShowPassword: false, // 是否显示密码
 	ruleForm: {
 		// 表单数据
-		username: 'test1', // 用户名
-		password: '123456', // 密码
+		username: '', // 用户名
+		password: '', // 密码
+		googleCaptcha: '', // Google验证码
 		code: '', // 验证码
 		randomStr: 'blockPuzzle', // 验证码随机数
 	},

+ 110 - 0
src/views/order/payOrder/detail.vue

@@ -0,0 +1,110 @@
+<template>
+	<el-dialog :close-on-click-modal="false" title="代收订单详情" draggable v-model="visible" width="900px">
+		<el-descriptions :column="2" border v-loading="loading">
+			<el-descriptions-item label="订单ID">{{ orderData.id }}</el-descriptions-item>
+			<el-descriptions-item label="AppId">{{ orderData.appId }}</el-descriptions-item>
+			<el-descriptions-item label="商户订单号" :span="2">{{ orderData.mchOrderNo }}</el-descriptions-item>
+			<el-descriptions-item label="平台订单号" :span="2">{{ orderData.transactionId || '-' }}</el-descriptions-item>
+			<el-descriptions-item label="订单状态">
+				<el-tag v-if="orderData.orderStatus === 'CREATE_ORDER'" type="info">创建订单</el-tag>
+				<el-tag v-else-if="orderData.orderStatus === 'PAY_SUCCESS'" type="success">支付成功</el-tag>
+				<el-tag v-else-if="orderData.orderStatus === 'PAY_FAIL'" type="danger">支付失败</el-tag>
+				<el-tag v-else-if="orderData.orderStatus === 'CANCEL_ORDER'" type="warning">取消订单</el-tag>
+				<el-tag v-else-if="orderData.orderStatus === 'PAY_TIMEOUT'" type="info">支付超时</el-tag>
+				<el-tag v-else>{{ orderData.orderStatus }}</el-tag>
+			</el-descriptions-item>
+			<el-descriptions-item label="币种">{{ orderData.currency }}</el-descriptions-item>
+
+			<el-descriptions-item label="订单金额">
+				<span style="color: #f56c6c; font-weight: bold; font-size: 16px">¥{{ orderData.amount ? orderData.amount.toFixed(2) : '0.00' }}</span>
+			</el-descriptions-item>
+			<el-descriptions-item label="实付金额">
+				<span v-if="orderData.noticeAmount" style="color: #67c23a; font-weight: bold; font-size: 16px"
+					>¥{{ orderData.noticeAmount.toFixed(2) }}</span
+				>
+				<span v-else>-</span>
+			</el-descriptions-item>
+
+			<el-descriptions-item label="支付通道ID">{{ orderData.paymentChannelId }}</el-descriptions-item>
+			<el-descriptions-item label="支付通道名称">{{ orderData.paymentChannelName || '-' }}</el-descriptions-item>
+
+			<el-descriptions-item label="商户ID">{{ orderData.merchantUserId }}</el-descriptions-item>
+			<el-descriptions-item label="代理商ID">{{ orderData.agentUserId }}</el-descriptions-item>
+
+			<el-descriptions-item label="商户手续费类型">
+				<el-tag v-if="orderData.mFeeType === 'PERCENTAGE'" type="info">百分比</el-tag>
+				<el-tag v-else-if="orderData.mFeeType === 'FIXED'" type="warning">固定</el-tag>
+				<el-tag v-else-if="orderData.mFeeType === 'MIXED'" type="success">百分比+固定</el-tag>
+				<span v-else>-</span>
+			</el-descriptions-item>
+			<el-descriptions-item label="商户手续费">
+				{{ orderData.mFee ? `¥${orderData.mFee.toFixed(2)}` : '-' }}
+			</el-descriptions-item>
+
+			<el-descriptions-item label="商户手续费比例">
+				{{ orderData.mFeeRate ? `${orderData.mFeeRate}%` : '-' }}
+			</el-descriptions-item>
+			<el-descriptions-item label="商户手续费固定金额">
+				{{ orderData.mFeeEvery ? `¥${orderData.mFeeEvery.toFixed(2)}` : '-' }}
+			</el-descriptions-item>
+
+			<el-descriptions-item label="代理商手续费类型">
+				<el-tag v-if="orderData.aFeeType === 'PERCENTAGE'" type="info">百分比</el-tag>
+				<el-tag v-else-if="orderData.aFeeType === 'FIXED'" type="warning">固定</el-tag>
+				<el-tag v-else-if="orderData.aFeeType === 'MIXED'" type="success">百分比+固定</el-tag>
+				<span v-else>-</span>
+			</el-descriptions-item>
+			<el-descriptions-item label="代理商手续费">
+				{{ orderData.aFee ? `¥${orderData.aFee.toFixed(2)}` : '-' }}
+			</el-descriptions-item>
+
+			<el-descriptions-item label="代理商手续费比例">
+				{{ orderData.aFeeRate ? `${orderData.aFeeRate}%` : '-' }}
+			</el-descriptions-item>
+			<el-descriptions-item label="代理商手续费固定金额">
+				{{ orderData.aFeeEvery ? `¥${orderData.aFeeEvery.toFixed(2)}` : '-' }}
+			</el-descriptions-item>
+
+			<el-descriptions-item label="客户端IP">{{ orderData.clientIp || '-' }}</el-descriptions-item>
+			<el-descriptions-item label="设备">{{ orderData.device || '-' }}</el-descriptions-item>
+
+			<el-descriptions-item label="主题" :span="2">{{ orderData.subject || '-' }}</el-descriptions-item>
+			<el-descriptions-item label="内容" :span="2">{{ orderData.body || '-' }}</el-descriptions-item>
+
+			<el-descriptions-item label="前端跳转URL" :span="2">{{ orderData.returnUrl || '-' }}</el-descriptions-item>
+			<el-descriptions-item label="后台回调URL" :span="2">{{ orderData.notifyUrl || '-' }}</el-descriptions-item>
+
+			<el-descriptions-item label="扩展参数" :span="2">{{ orderData.extra || '-' }}</el-descriptions-item>
+
+			<el-descriptions-item label="请求时间">{{ orderData.reqTime || '-' }}</el-descriptions-item>
+			<el-descriptions-item label="支付完成时间">{{ orderData.noticeDatetime || '-' }}</el-descriptions-item>
+
+			<el-descriptions-item label="创建时间">{{ orderData.createTime }}</el-descriptions-item>
+			<el-descriptions-item label="更新时间">{{ orderData.updateTime }}</el-descriptions-item>
+		</el-descriptions>
+
+		<template #footer>
+			<span class="dialog-footer">
+				<el-button @click="visible = false">关闭</el-button>
+			</span>
+		</template>
+	</el-dialog>
+</template>
+
+<script lang="ts" name="PayOrderDetailDialog" setup>
+// 定义变量
+const visible = ref(false);
+const loading = ref(false);
+const orderData = reactive<any>({});
+
+// 打开弹窗
+const openDialog = (row: any) => {
+	visible.value = true;
+	Object.assign(orderData, row);
+};
+
+// 暴露方法
+defineExpose({
+	openDialog,
+});
+</script>

+ 242 - 0
src/views/order/payOrder/index.vue

@@ -0,0 +1,242 @@
+<template>
+	<div class="layout-padding">
+		<div class="layout-padding-auto layout-padding-view">
+			<el-row class="ml10" v-show="showSearch">
+				<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList" ref="queryRef">
+					<el-form-item label="商户订单号" prop="mchOrderNo">
+						<el-input placeholder="请输入商户订单号" style="max-width: 180px" v-model="state.queryForm.mchOrderNo" clearable />
+					</el-form-item>
+					<el-form-item label="平台订单号" prop="transactionId">
+						<el-input placeholder="请输入平台订单号" style="max-width: 180px" v-model="state.queryForm.transactionId" clearable />
+					</el-form-item>
+					<el-form-item label="商户名称" prop="merchantName">
+						<el-input placeholder="请输入商户名称" style="max-width: 180px" v-model="state.queryForm.merchantName" clearable />
+					</el-form-item>
+					<el-form-item label="订单状态" prop="orderStatus">
+						<el-select v-model="state.queryForm.orderStatus" placeholder="请选择订单状态" style="max-width: 180px" clearable>
+							<el-option label="创建订单" value="CREATE_ORDER" />
+							<el-option label="支付成功" value="PAY_SUCCESS" />
+							<el-option label="支付失败" value="PAY_FAIL" />
+							<el-option label="取消订单" value="CANCEL_ORDER" />
+							<el-option label="支付超时" value="PAY_TIMEOUT" />
+						</el-select>
+					</el-form-item>
+					<el-form-item>
+						<el-button @click="getDataList" icon="search" type="primary">查询</el-button>
+						<el-button @click="resetQuery" icon="Refresh">重置</el-button>
+					</el-form-item>
+				</el-form>
+			</el-row>
+			<el-row>
+				<div class="mb8" style="width: 100%">
+					<right-toolbar
+						@queryTable="getDataList"
+						class="ml10"
+						style="float: right; margin-right: 20px"
+						v-model:showSearch="showSearch"
+					></right-toolbar>
+				</div>
+			</el-row>
+
+			<!-- 统计信息卡片 -->
+			<el-row :gutter="20" class="mb20" v-if="statistics">
+				<el-col :span="6">
+					<el-card shadow="hover">
+						<div class="statistic-item">
+							<div class="statistic-label">提交订单数</div>
+							<div class="statistic-value">{{ statistics.numberOrders || 0 }}</div>
+						</div>
+					</el-card>
+				</el-col>
+				<el-col :span="6">
+					<el-card shadow="hover">
+						<div class="statistic-item">
+							<div class="statistic-label">订单总金额</div>
+							<div class="statistic-value">¥{{ (statistics.totalOrderAmount || 0).toFixed(2) }}</div>
+						</div>
+					</el-card>
+				</el-col>
+				<el-col :span="6">
+					<el-card shadow="hover">
+						<div class="statistic-item">
+							<div class="statistic-label">已付订单数</div>
+							<div class="statistic-value success">{{ statistics.paidOrders || 0 }}</div>
+						</div>
+					</el-card>
+				</el-col>
+				<el-col :span="6">
+					<el-card shadow="hover">
+						<div class="statistic-item">
+							<div class="statistic-label">已付总金额</div>
+							<div class="statistic-value success">¥{{ (statistics.totalAmountPaid || 0).toFixed(2) }}</div>
+						</div>
+					</el-card>
+				</el-col>
+			</el-row>
+
+			<el-table
+				:data="state.dataList"
+				@selection-change="handleSelectionChange"
+				@sort-change="sortChangeHandle"
+				style="width: 100%"
+				v-loading="state.loading"
+				border
+				:cell-style="tableStyle.cellStyle"
+				:header-cell-style="tableStyle.headerCellStyle"
+			>
+				<el-table-column align="center" type="selection" width="40" />
+				<el-table-column label="商户订单号" prop="mchOrderNo" show-overflow-tooltip>
+					<template #default="scope">
+						{{ scope.row.mchOrderNo || '--' }}
+					</template>
+				</el-table-column>
+				<el-table-column label="平台订单号" prop="transactionId" show-overflow-tooltip>
+					<template #default="scope">
+						{{ scope.row.transactionId || '--' }}
+					</template>
+				</el-table-column>
+				<el-table-column label="商户名称" prop="merchantName" show-overflow-tooltip>
+					<template #default="scope">
+						{{ scope.row.merchantName || '--' }}
+					</template>
+				</el-table-column>
+				<el-table-column label="通道名称" prop="paymentChannelName" show-overflow-tooltip>
+					<template #default="scope">
+						{{ scope.row.paymentChannelName || '--' }}
+					</template>
+				</el-table-column>
+				<el-table-column label="金额" prop="amount" show-overflow-tooltip>
+					<template #default="scope">
+						<span style="color: #f56c6c; font-weight: bold">¥{{ scope.row.amount ? scope.row.amount.toFixed(2) : '0.00' }}</span>
+					</template>
+				</el-table-column>
+				<el-table-column label="订单状态" prop="orderStatus" show-overflow-tooltip>
+					<template #default="scope">
+						<el-tag :type="orderStatusMap[scope.row.orderStatus]?.type || 'info'" size="small">
+							{{ orderStatusMap[scope.row.orderStatus]?.label || scope.row.orderStatus }}
+						</el-tag>
+					</template>
+				</el-table-column>
+				<el-table-column label="创建时间" prop="createTime" show-overflow-tooltip width="180">
+					<template #default="scope">
+						{{ scope.row.createTime || '--' }}
+					</template>
+				</el-table-column>
+				<el-table-column label="操作">
+					<template #default="scope">
+						<!-- <el-button icon="View" @click="handleView(scope.row)" text type="primary" size="small">查看</el-button> -->
+						<el-button icon="Document" @click="handleNotifyLog(scope.row)" text type="warning" size="small">回调日志</el-button>
+					</template>
+				</el-table-column>
+			</el-table>
+			<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination" />
+		</div>
+
+		<!-- 回调日志弹窗 -->
+		<notify-log-dialog ref="notifyLogDialogRef" />
+	</div>
+</template>
+
+<script lang="ts" name="orderPayOrder" setup>
+import { BasicTableProps, useTable } from '/@/hooks/table';
+import { fetchPayOrderList, fetchPayOrderStatistics } from '/@/api/order';
+import { useMessage } from '/@/hooks/message';
+
+// 引入组件
+const NotifyLogDialog = defineAsyncComponent(() => import('./notifyLog.vue'));
+
+// 订单状态映射
+const orderStatusMap: Record<string, { label: string; type: any }> = {
+	CREATE_ORDER: { label: '创建订单', type: 'info' },
+	PAY_SUCCESS: { label: '支付成功', type: 'success' },
+	PAY_FAIL: { label: '支付失败', type: 'danger' },
+	CANCEL_ORDER: { label: '取消订单', type: 'warning' },
+	PAY_TIMEOUT: { label: '支付超时', type: 'info' },
+};
+
+// 定义变量内容
+const notifyLogDialogRef = ref();
+const queryRef = ref();
+const showSearch = ref(true);
+const selectObjs = ref([]);
+const multiple = ref(true);
+const statistics = ref<any>(null);
+
+// 定义表格查询、变更
+const state: BasicTableProps = reactive<BasicTableProps>({
+	queryForm: {
+		mchOrderNo: '',
+		transactionId: '',
+		merchantName: '',
+		agentName: '',
+		orderStatus: '',
+	},
+	pageList: fetchPayOrderList,
+	descs: ['create_time'],
+	createdIsNeed: false, // 禁用自动加载,由 getDataListWithStats 手动调用
+});
+
+const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, tableStyle } = useTable(state);
+
+// 加载统计数据
+const loadStatistics = async () => {
+	try {
+		const res = await fetchPayOrderStatistics(state.queryForm);
+		statistics.value = res.data;
+	} catch (err: any) {
+		useMessage().error(err.msg);
+	}
+};
+
+// 重写 getDataList,同时加载统计数据
+const originalGetDataList = getDataList;
+const getDataListWithStats = async (reload = true) => {
+	await originalGetDataList(reload);
+	await loadStatistics();
+};
+
+// 重置查询
+const resetQuery = () => {
+	queryRef.value?.resetFields();
+	getDataListWithStats();
+};
+
+// 多选事件
+const handleSelectionChange = (objs: any) => {
+	selectObjs.value = objs.map((val: any) => val.id);
+	multiple.value = !objs.length;
+};
+
+// 查看回调日志
+const handleNotifyLog = (row: any) => {
+	notifyLogDialogRef.value.openDialog(row.appId, row.mchOrderNo);
+};
+
+// 初始化加载数据
+onMounted(() => {
+	getDataListWithStats();
+});
+</script>
+
+<style scoped>
+.statistic-item {
+	text-align: center;
+	padding: 10px 0;
+}
+
+.statistic-label {
+	font-size: 14px;
+	color: #909399;
+	margin-bottom: 8px;
+}
+
+.statistic-value {
+	font-size: 24px;
+	font-weight: bold;
+	color: #303133;
+}
+
+.statistic-value.success {
+	color: #67c23a;
+}
+</style>

+ 168 - 0
src/views/order/payOrder/notifyLog.vue

@@ -0,0 +1,168 @@
+<template>
+	<el-dialog :close-on-click-modal="false" title="回调日志" draggable v-model="visible" width="1000px">
+		<div v-loading="loading">
+			<!-- 回调信息概览 -->
+			<el-descriptions :column="2" border class="mb20" v-if="notifyData.id">
+				<el-descriptions-item label="订单号">{{ notifyData.orderId || '-' }}</el-descriptions-item>
+				<el-descriptions-item label="通知状态">
+					<el-tag :type="getStatusType(notifyData.status)" size="default">
+						{{ getStatusLabel(notifyData.status) }}
+					</el-tag>
+				</el-descriptions-item>
+				<el-descriptions-item label="当前通知次数">
+					<el-tag type="info">{{ notifyData.notifyTimes || 0 }}</el-tag>
+				</el-descriptions-item>
+				<el-descriptions-item label="最大通知次数">
+					<el-tag type="info">{{ notifyData.maxNotifyTimes || 0 }}</el-tag>
+				</el-descriptions-item>
+				<el-descriptions-item label="最后执行时间" :span="2">{{ notifyData.lastExecuteTime || '-' }}</el-descriptions-item>
+				<el-descriptions-item label="下次通知时间" :span="2">{{ notifyData.nextNotifyTime || '-' }}</el-descriptions-item>
+				<el-descriptions-item label="通知地址" :span="2">
+					<el-text class="notify-url" truncated>{{ notifyData.notifyUrl || '-' }}</el-text>
+				</el-descriptions-item>
+				<el-descriptions-item label="失败原因" :span="2">
+					<span style="color: #f56c6c">{{ notifyData.errorMsg || '-' }}</span>
+				</el-descriptions-item>
+			</el-descriptions>
+
+			<!-- 通知日志列表 -->
+			<el-divider content-position="left">通知日志</el-divider>
+			<el-timeline v-if="notifyData.payNotifyLogs && notifyData.payNotifyLogs.length > 0">
+				<el-timeline-item
+					v-for="(log, index) in notifyData.payNotifyLogs"
+					:key="index"
+					:timestamp="log.createTime"
+					placement="top"
+					:type="getStatusType(log.status)"
+				>
+					<el-card shadow="hover">
+						<div class="log-item">
+							<div class="log-header">
+								<span class="log-title">第 {{ log.notifyTimes }} 次通知</span>
+								<el-tag :type="getStatusType(log.status)" size="small">
+									{{ getStatusLabel(log.status) }}
+								</el-tag>
+							</div>
+							<div class="log-content">
+								<div class="log-label">响应结果:</div>
+								<pre class="log-response">{{ log.response || '无响应' }}</pre>
+							</div>
+						</div>
+					</el-card>
+				</el-timeline-item>
+			</el-timeline>
+			<el-empty v-else description="暂无通知日志" />
+		</div>
+
+		<template #footer>
+			<span class="dialog-footer">
+				<el-button @click="visible = false">关闭</el-button>
+			</span>
+		</template>
+	</el-dialog>
+</template>
+
+<script lang="ts" name="NotifyLogDialog" setup>
+import { fetchPayNotifyLog } from '/@/api/order';
+import { useMessage } from '/@/hooks/message';
+
+// 通知状态映射
+const statusMap: Record<number, { label: string; type: any }> = {
+	0: { label: '待通知', type: 'info' },
+	10: { label: '通知成功', type: 'success' },
+	20: { label: '通知失败', type: 'danger' },
+	30: { label: '通知中', type: 'warning' },
+};
+
+// 定义变量
+const visible = ref(false);
+const loading = ref(false);
+const notifyData = reactive<any>({});
+
+// 获取状态标签类型
+const getStatusType = (status: number) => {
+	return statusMap[status]?.type || 'info';
+};
+
+// 获取状态标签文本
+const getStatusLabel = (status: number) => {
+	return statusMap[status]?.label || `未知状态(${status})`;
+};
+
+// 打开弹窗
+const openDialog = async (appId: string, orderId: string) => {
+	visible.value = true;
+	loading.value = true;
+
+	// 清空数据
+	Object.keys(notifyData).forEach((key) => delete notifyData[key]);
+
+	try {
+		const res = await fetchPayNotifyLog(appId, orderId);
+		Object.assign(notifyData, res.data);
+	} catch (err: any) {
+		useMessage().error(err.msg || '获取回调日志失败');
+	} finally {
+		loading.value = false;
+	}
+};
+
+// 暴露方法
+defineExpose({
+	openDialog,
+});
+</script>
+
+<style scoped>
+.notify-url {
+	max-width: 100%;
+	word-break: break-all;
+}
+
+.log-item {
+	padding: 10px 0;
+}
+
+.log-header {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	margin-bottom: 10px;
+}
+
+.log-title {
+	font-size: 16px;
+	font-weight: bold;
+	color: #303133;
+}
+
+.log-content {
+	margin-top: 10px;
+}
+
+.log-label {
+	font-size: 14px;
+	color: #606266;
+	margin-bottom: 5px;
+	font-weight: 500;
+}
+
+.log-response {
+	background: #f5f7fa;
+	padding: 12px;
+	border-radius: 4px;
+	font-size: 13px;
+	color: #303133;
+	white-space: pre-wrap;
+	word-wrap: break-word;
+	max-height: 200px;
+	overflow-y: auto;
+	margin: 0;
+	border: 1px solid #e4e7ed;
+	font-family: 'Courier New', Courier, monospace;
+}
+
+.mb20 {
+	margin-bottom: 20px;
+}
+</style>

+ 114 - 0
src/views/order/withdrawOrder/detail.vue

@@ -0,0 +1,114 @@
+<template>
+	<el-dialog :close-on-click-modal="false" title="代付订单详情" draggable v-model="visible" width="900px">
+		<el-descriptions :column="2" border v-loading="loading">
+			<el-descriptions-item label="订单ID">{{ orderData.id }}</el-descriptions-item>
+			<el-descriptions-item label="AppId">{{ orderData.appId }}</el-descriptions-item>
+			<el-descriptions-item label="商户订单号" :span="2">{{ orderData.mchOrderNo }}</el-descriptions-item>
+			<el-descriptions-item label="平台订单号" :span="2">{{ orderData.transactionId || '-' }}</el-descriptions-item>
+			<el-descriptions-item label="订单状态">
+				<el-tag v-if="orderData.orderStatus === 'CREATE_ORDER'" type="info">创建订单</el-tag>
+				<el-tag v-else-if="orderData.orderStatus === 'WITHDRAW_SUCCESS'" type="success">提现成功</el-tag>
+				<el-tag v-else-if="orderData.orderStatus === 'WITHDRAW_FAIL'" type="danger">提现失败</el-tag>
+				<el-tag v-else>{{ orderData.orderStatus }}</el-tag>
+			</el-descriptions-item>
+			<el-descriptions-item label="币种">{{ orderData.currencyType }}</el-descriptions-item>
+
+			<el-descriptions-item label="订单金额">
+				<span style="color: #f56c6c; font-weight: bold; font-size: 16px">¥{{ orderData.amount ? orderData.amount.toFixed(2) : '0.00' }}</span>
+			</el-descriptions-item>
+			<el-descriptions-item label="实付金额">
+				<span v-if="orderData.noticeAmount" style="color: #67c23a; font-weight: bold; font-size: 16px"
+					>¥{{ orderData.noticeAmount.toFixed(2) }}</span
+				>
+				<span v-else>-</span>
+			</el-descriptions-item>
+
+			<el-descriptions-item label="支付通道ID">{{ orderData.paymentChannelId }}</el-descriptions-item>
+			<el-descriptions-item label="通道类型ID">{{ orderData.paymentTypeId }}</el-descriptions-item>
+
+			<el-descriptions-item label="商户ID">{{ orderData.merchantUserId }}</el-descriptions-item>
+			<el-descriptions-item label="费率状态">
+				<el-tag v-if="orderData.feeStatus" type="success">成功</el-tag>
+				<el-tag v-else type="danger">失败</el-tag>
+			</el-descriptions-item>
+
+			<el-descriptions-item label="商户手续费类型">
+				<el-tag v-if="orderData.mFeeType === 'PERCENTAGE'" type="info">百分比</el-tag>
+				<el-tag v-else-if="orderData.mFeeType === 'FIXED'" type="warning">固定</el-tag>
+				<el-tag v-else-if="orderData.mFeeType === 'MIXED'" type="success">百分比+固定</el-tag>
+				<span v-else>-</span>
+			</el-descriptions-item>
+			<el-descriptions-item label="商户手续费">
+				{{ orderData.mFee ? `¥${orderData.mFee.toFixed(2)}` : '-' }}
+			</el-descriptions-item>
+
+			<el-descriptions-item label="商户手续费比例">
+				{{ orderData.mFeeRate ? `${orderData.mFeeRate}%` : '-' }}
+			</el-descriptions-item>
+			<el-descriptions-item label="商户手续费固定金额">
+				{{ orderData.mFeeEvery ? `¥${orderData.mFeeEvery.toFixed(2)}` : '-' }}
+			</el-descriptions-item>
+
+			<el-descriptions-item label="代理商手续费类型">
+				<el-tag v-if="orderData.aFeeType === 'PERCENTAGE'" type="info">百分比</el-tag>
+				<el-tag v-else-if="orderData.aFeeType === 'FIXED'" type="warning">固定</el-tag>
+				<el-tag v-else-if="orderData.aFeeType === 'MIXED'" type="success">百分比+固定</el-tag>
+				<span v-else>-</span>
+			</el-descriptions-item>
+			<el-descriptions-item label="代理商手续费">
+				{{ orderData.aFee ? `¥${orderData.aFee.toFixed(2)}` : '-' }}
+			</el-descriptions-item>
+
+			<el-descriptions-item label="代理商手续费比例">
+				{{ orderData.aFeeRate ? `${orderData.aFeeRate}%` : '-' }}
+			</el-descriptions-item>
+			<el-descriptions-item label="代理商手续费固定金额">
+				{{ orderData.aFeeEvery ? `¥${orderData.aFeeEvery.toFixed(2)}` : '-' }}
+			</el-descriptions-item>
+
+			<el-descriptions-item label="收款人姓名">{{ orderData.payUsername || '-' }}</el-descriptions-item>
+			<el-descriptions-item label="收款人电话">{{ orderData.payMobile || '-' }}</el-descriptions-item>
+
+			<el-descriptions-item label="银行账号" :span="2">{{ orderData.payBankNumber || '-' }}</el-descriptions-item>
+			<el-descriptions-item label="银行名称" :span="2">{{ orderData.payBankName || '-' }}</el-descriptions-item>
+
+			<el-descriptions-item label="收款人邮箱" :span="2">{{ orderData.payEmail || '-' }}</el-descriptions-item>
+
+			<el-descriptions-item label="主题" :span="2">{{ orderData.subject || '-' }}</el-descriptions-item>
+
+			<el-descriptions-item label="后台回调URL" :span="2">{{ orderData.notifyUrl || '-' }}</el-descriptions-item>
+
+			<el-descriptions-item label="扩展参数" :span="2">{{ orderData.payAttach || '-' }}</el-descriptions-item>
+
+			<el-descriptions-item label="请求时间">{{ orderData.reqTime || '-' }}</el-descriptions-item>
+			<el-descriptions-item label="完成时间">{{ orderData.noticeDatetime || '-' }}</el-descriptions-item>
+
+			<el-descriptions-item label="创建时间">{{ orderData.createTime }}</el-descriptions-item>
+			<el-descriptions-item label="更新时间">{{ orderData.updateTime }}</el-descriptions-item>
+		</el-descriptions>
+
+		<template #footer>
+			<span class="dialog-footer">
+				<el-button @click="visible = false">关闭</el-button>
+			</span>
+		</template>
+	</el-dialog>
+</template>
+
+<script lang="ts" name="WithdrawOrderDetailDialog" setup>
+// 定义变量
+const visible = ref(false);
+const loading = ref(false);
+const orderData = reactive<any>({});
+
+// 打开弹窗
+const openDialog = (row: any) => {
+	visible.value = true;
+	Object.assign(orderData, row);
+};
+
+// 暴露方法
+defineExpose({
+	openDialog,
+});
+</script>

+ 155 - 0
src/views/order/withdrawOrder/index.vue

@@ -0,0 +1,155 @@
+<template>
+	<div class="layout-padding">
+		<div class="layout-padding-auto layout-padding-view">
+			<el-row class="ml10" v-show="showSearch">
+				<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList" ref="queryRef">
+					<el-form-item label="商户订单号" prop="mchOrderNo">
+						<el-input placeholder="请输入商户订单号" style="max-width: 180px" v-model="state.queryForm.mchOrderNo" clearable />
+					</el-form-item>
+					<el-form-item label="平台订单号" prop="transactionId">
+						<el-input placeholder="请输入平台订单号" style="max-width: 180px" v-model="state.queryForm.transactionId" clearable />
+					</el-form-item>
+					<el-form-item label="商户名称" prop="merchantName">
+						<el-input placeholder="请输入商户名称" style="max-width: 180px" v-model="state.queryForm.merchantName" clearable />
+					</el-form-item>
+					<el-form-item label="订单状态" prop="orderStatus">
+						<el-select v-model="state.queryForm.orderStatus" placeholder="请选择订单状态" style="max-width: 180px" clearable>
+							<el-option label="创建订单" value="CREATE_ORDER" />
+							<el-option label="提现成功" value="WITHDRAW_SUCCESS" />
+							<el-option label="提现失败" value="WITHDRAW_FAIL" />
+						</el-select>
+					</el-form-item>
+					<el-form-item>
+						<el-button @click="getDataList" icon="search" type="primary">查询</el-button>
+						<el-button @click="resetQuery" icon="Refresh">重置</el-button>
+					</el-form-item>
+				</el-form>
+			</el-row>
+			<el-row>
+				<div class="mb8" style="width: 100%">
+					<right-toolbar
+						@queryTable="getDataList"
+						class="ml10"
+						style="float: right; margin-right: 20px"
+						v-model:showSearch="showSearch"
+					></right-toolbar>
+				</div>
+			</el-row>
+
+			<el-table
+				:data="state.dataList"
+				@selection-change="handleSelectionChange"
+				@sort-change="sortChangeHandle"
+				style="width: 100%"
+				v-loading="state.loading"
+				border
+				:cell-style="tableStyle.cellStyle"
+				:header-cell-style="tableStyle.headerCellStyle"
+			>
+				<el-table-column align="center" type="selection" width="40" />
+				<el-table-column label="商户订单号" prop="mchOrderNo" show-overflow-tooltip>
+					<template #default="scope">
+						{{ scope.row.mchOrderNo || '--' }}
+					</template>
+				</el-table-column>
+				<el-table-column label="平台订单号" prop="transactionId" show-overflow-tooltip>
+					<template #default="scope">
+						{{ scope.row.transactionId || '--' }}
+					</template>
+				</el-table-column>
+				<el-table-column label="商户名称" prop="merchantName" show-overflow-tooltip>
+					<template #default="scope">
+						{{ scope.row.merchantName || '--' }}
+					</template>
+				</el-table-column>
+				<el-table-column label="通道名称" prop="paymentChannelName" show-overflow-tooltip>
+					<template #default="scope">
+						{{ scope.row.paymentChannelName || '--' }}
+					</template>
+				</el-table-column>
+				<el-table-column label="金额" prop="amount" show-overflow-tooltip>
+					<template #default="scope">
+						<span style="color: #f56c6c; font-weight: bold">¥{{ scope.row.amount ? scope.row.amount.toFixed(2) : '0.00' }}</span>
+					</template>
+				</el-table-column>
+				<el-table-column label="订单状态" prop="orderStatus" show-overflow-tooltip>
+					<template #default="scope">
+						<el-tag :type="orderStatusMap[scope.row.orderStatus]?.type || 'info'" size="small">
+							{{ orderStatusMap[scope.row.orderStatus]?.label || scope.row.orderStatus }}
+						</el-tag>
+					</template>
+				</el-table-column>
+				<el-table-column label="创建时间" prop="createTime" show-overflow-tooltip width="180">
+					<template #default="scope">
+						{{ scope.row.createTime || '--' }}
+					</template>
+				</el-table-column>
+				<el-table-column label="操作">
+					<template #default="scope">
+						<!-- <el-button icon="View" @click="handleView(scope.row)" text type="primary" size="small">查看</el-button> -->
+						<el-button icon="Document" @click="handleNotifyLog(scope.row)" text type="warning" size="small">回调日志</el-button>
+					</template>
+				</el-table-column>
+			</el-table>
+			<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination" />
+		</div>
+
+		<!-- 回调日志弹窗 -->
+		<notify-log-dialog ref="notifyLogDialogRef" />
+	</div>
+</template>
+
+<script lang="ts" name="orderWithdrawOrder" setup>
+import { BasicTableProps, useTable } from '/@/hooks/table';
+import { fetchWithdrawOrderList } from '/@/api/order';
+
+// 引入组件
+const NotifyLogDialog = defineAsyncComponent(() => import('../payOrder/notifyLog.vue'));
+
+// 订单状态映射
+const orderStatusMap: Record<string, { label: string; type: any }> = {
+	CREATE_ORDER: { label: '创建订单', type: 'info' },
+	WITHDRAW_SUCCESS: { label: '提现成功', type: 'success' },
+	WITHDRAW_FAIL: { label: '提现失败', type: 'danger' },
+};
+
+// 定义变量内容
+const notifyLogDialogRef = ref();
+const queryRef = ref();
+const showSearch = ref(true);
+const selectObjs = ref([]);
+const multiple = ref(true);
+
+// 定义表格查询、变更
+const state: BasicTableProps = reactive<BasicTableProps>({
+	queryForm: {
+		mchOrderNo: '',
+		transactionId: '',
+		merchantName: '',
+		agentName: '',
+		orderStatus: '',
+	},
+	pageList: fetchWithdrawOrderList,
+	descs: ['create_time'],
+	createdIsNeed: true, // 使用自动加载
+});
+
+const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, tableStyle } = useTable(state);
+
+// 重置查询
+const resetQuery = () => {
+	queryRef.value?.resetFields();
+	getDataList();
+};
+
+// 多选事件
+const handleSelectionChange = (objs: any) => {
+	selectObjs.value = objs.map((val: any) => val.id);
+	multiple.value = !objs.length;
+};
+
+// 查看回调日志
+const handleNotifyLog = (row: any) => {
+	notifyLogDialogRef.value.openDialog(row.appId, row.mchOrderNo);
+};
+</script>

+ 177 - 0
src/views/payment/channel/index.vue

@@ -0,0 +1,177 @@
+<template>
+	<div class="layout-padding">
+		<div class="layout-padding-auto layout-padding-view">
+			<el-row class="ml10" v-show="showSearch">
+				<el-form :inline="true" :model="queryForm" @keyup.enter="handleSearch" ref="queryRef">
+					<el-form-item label="通道名称" prop="channelName">
+						<el-input placeholder="请输入通道名称" style="max-width: 180px" v-model="queryForm.channelName" clearable />
+					</el-form-item>
+					<el-form-item label="支付方式" prop="paymentName">
+						<el-input placeholder="请输入支付方式" style="max-width: 180px" v-model="queryForm.paymentName" clearable />
+					</el-form-item>
+					<el-form-item label="支付类别" prop="paymentType">
+						<el-select v-model="queryForm.paymentType" placeholder="请选择支付类别" style="max-width: 180px" clearable>
+							<el-option label="代付" value="PAY" />
+							<el-option label="代收" value="HARVEST" />
+						</el-select>
+					</el-form-item>
+					<el-form-item>
+						<el-button @click="handleSearch" icon="search" type="primary">查询</el-button>
+						<el-button @click="resetQuery" icon="Refresh">重置</el-button>
+					</el-form-item>
+				</el-form>
+			</el-row>
+			<el-row>
+				<div class="mb8" style="width: 100%">
+					<el-button @click="loadData" class="ml10" icon="Refresh" type="primary">刷新</el-button>
+					<right-toolbar
+						@queryTable="handleSearch"
+						class="ml10"
+						style="float: right; margin-right: 20px"
+						v-model:showSearch="showSearch"
+					></right-toolbar>
+				</div>
+			</el-row>
+
+			<el-table
+				:data="filteredData"
+				style="width: 100%"
+				v-loading="loading"
+				border
+				:cell-style="tableStyle.cellStyle"
+				:header-cell-style="tableStyle.headerCellStyle"
+			>
+				<el-table-column label="通道名称" prop="channelName" show-overflow-tooltip align="center">
+					<template #default="scope">
+						{{ scope.row.channelName || '--' }}
+					</template>
+				</el-table-column>
+				<el-table-column label="支付方式" prop="paymentName" show-overflow-tooltip align="center">
+					<template #default="scope">
+						{{ scope.row.paymentName || '--' }}
+					</template>
+				</el-table-column>
+				<el-table-column label="支付类别" prop="paymentType" show-overflow-tooltip align="center">
+					<template #default="scope">
+						<el-tag :type="scope.row.paymentType === 'PAY' ? 'warning' : 'info'">
+							{{ paymentTypeMap[scope.row.paymentType] || scope.row.paymentType }}
+						</el-tag>
+					</template>
+				</el-table-column>
+				<el-table-column label="代理商手续费类型" prop="afeeType" show-overflow-tooltip align="center">
+					<template #default="scope">
+						{{ feeTypeMap[scope.row.afeeType] || scope.row.afeeType }}
+					</template>
+				</el-table-column>
+				<el-table-column label="代理商手续费比例" prop="afeeRate" show-overflow-tooltip align="center">
+					<template #default="scope">
+						{{ scope.row.afeeRate ? `${scope.row.afeeRate}%` : '0%' }}
+					</template>
+				</el-table-column>
+				<el-table-column label="代理商手续费固定金额" prop="afeeEvery" show-overflow-tooltip align="center">
+					<template #default="scope">
+						<span class="amount-value">¥{{ scope.row.afeeEvery ? scope.row.afeeEvery.toFixed(2) : '0.00' }}</span>
+					</template>
+				</el-table-column>
+			</el-table>
+		</div>
+	</div>
+</template>
+
+<script lang="ts" name="paymentChannel" setup>
+import { getMerchantRate } from '/@/api/payment/channel';
+import { useMessage } from '/@/hooks/message';
+
+// 支付类别映射
+const paymentTypeMap: Record<string, string> = {
+	PAY: '代付',
+	HARVEST: '代收',
+};
+
+// 手续费类型映射
+const feeTypeMap: Record<string, string> = {
+	PERCENTAGE: '百分比',
+	FIXED: '固定金额',
+	MIXED: '混合',
+};
+
+// 定义变量
+const queryRef = ref();
+const showSearch = ref(true);
+const loading = ref(false);
+const tableData = ref<any[]>([]);
+const queryForm = ref({
+	channelName: '',
+	paymentName: '',
+	paymentType: '',
+});
+
+// 表格样式
+const tableStyle = {
+	cellStyle: {
+		textAlign: 'center',
+	},
+	headerCellStyle: {
+		textAlign: 'center',
+		background: '#fafafa',
+	},
+};
+
+// 前端筛选数据
+const filteredData = computed(() => {
+	let data = tableData.value;
+
+	// 通道名称筛选
+	if (queryForm.value.channelName) {
+		data = data.filter((item) => item.channelName?.toLowerCase().includes(queryForm.value.channelName.toLowerCase()));
+	}
+
+	// 支付方式筛选
+	if (queryForm.value.paymentName) {
+		data = data.filter((item) => item.paymentName?.toLowerCase().includes(queryForm.value.paymentName.toLowerCase()));
+	}
+
+	// 支付类别筛选
+	if (queryForm.value.paymentType) {
+		data = data.filter((item) => item.paymentType === queryForm.value.paymentType);
+	}
+
+	return data;
+});
+
+// 查询
+const handleSearch = () => {
+	// 前端筛选,无需额外操作
+};
+
+// 重置查询
+const resetQuery = () => {
+	queryRef.value?.resetFields();
+};
+
+// 加载数据
+const loadData = async () => {
+	loading.value = true;
+	try {
+		const res = await getMerchantRate();
+		tableData.value = res.data || [];
+	} catch (err: any) {
+		useMessage().error(err.msg || '获取费率信息失败');
+	} finally {
+		loading.value = false;
+	}
+};
+
+// 初始化加载
+onMounted(() => {
+	loadData();
+});
+</script>
+
+<style scoped lang="scss">
+.amount-value {
+	font-size: 14px;
+	font-weight: bold;
+	color: #f56c6c;
+}
+</style>

+ 735 - 0
src/views/settings/security/index.vue

@@ -0,0 +1,735 @@
+<template>
+	<div class="security-container">
+		<div class="security-header">
+			<h2 class="page-title">
+				<el-icon :size="24"><Lock /></el-icon>
+				安全中心
+			</h2>
+			<p class="page-desc">管理您的账号安全设置,保护账户信息</p>
+		</div>
+
+		<div class="security-grid">
+			<!-- 支付密码卡片 -->
+			<el-card class="security-card" shadow="hover">
+				<div class="card-icon payment-icon">
+					<el-icon :size="40"><Lock /></el-icon>
+				</div>
+				<div class="card-content">
+					<h3 class="card-title">支付密码</h3>
+					<p class="card-desc">{{ isPaymentPwdSet ? '已设置支付密码' : '未设置支付密码,建议设置以提高账号安全性' }}</p>
+					<div class="card-status">
+						<el-tag :type="isPaymentPwdSet ? 'success' : 'info'" size="small">
+							{{ isPaymentPwdSet ? '已开启' : '未开启' }}
+						</el-tag>
+					</div>
+				</div>
+				<div class="card-action">
+					<el-button v-if="!isPaymentPwdSet" type="primary" @click="handleSetPaymentPwd">立即设置</el-button>
+					<el-button v-else type="primary" plain @click="handleSetPaymentPwd">修改密码</el-button>
+				</div>
+			</el-card>
+
+			<!-- 白名单卡片 -->
+			<!-- <el-card class="security-card" shadow="hover">
+				<div class="card-icon whitelist-icon">
+					<el-icon :size="40"><List /></el-icon>
+				</div>
+				<div class="card-content">
+					<h3 class="card-title">白名单设置</h3>
+					<p class="card-desc">设置允许访问的IP地址白名单</p>
+				</div>
+				<div class="card-action">
+					<el-button type="primary" @click="handleSetWhitelist">设置白名单</el-button>
+				</div>
+			</el-card> -->
+
+			<!-- Google验证器卡片 -->
+			<el-card class="security-card" shadow="hover">
+				<div class="card-icon google-icon">
+					<svg class="google-svg" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+						<path
+							d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
+							fill="#4285F4"
+						/>
+						<path
+							d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
+							fill="#34A853"
+						/>
+						<path
+							d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
+							fill="#FBBC05"
+						/>
+						<path
+							d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
+							fill="#EA4335"
+						/>
+					</svg>
+				</div>
+				<div class="card-content">
+					<h3 class="card-title">Google验证器</h3>
+					<p class="card-desc">{{ isGoogleBound ? '已绑定Google验证器' : '未绑定Google验证器,建议开启以提高账号安全性' }}</p>
+					<div class="card-status">
+						<el-tag :type="isGoogleBound ? 'success' : 'info'" size="small">
+							{{ isGoogleBound ? '已开启' : '未开启' }}
+						</el-tag>
+					</div>
+				</div>
+				<div class="card-action">
+					<el-button v-if="!isGoogleBound" type="primary" @click="handleBindGoogle">立即绑定</el-button>
+					<el-button v-else type="danger" plain @click="handleUnbindGoogle">解除绑定</el-button>
+				</div>
+			</el-card>
+		</div>
+
+		<!-- Google验证器绑定对话框 -->
+		<el-dialog v-model="googleDialogVisible" title="绑定Google验证器" width="500px" :close-on-click-modal="false">
+			<div class="google-bind-content">
+				<el-steps :active="googleBindStep" finish-status="success" align-center>
+					<el-step title="扫描二维码" />
+					<el-step title="输入验证码" />
+					<el-step title="完成" />
+				</el-steps>
+
+				<div v-if="googleBindStep === 0" class="step-content">
+					<div class="qrcode-container">
+						<div v-show="googleAuth.loading" class="loading-box">
+							<el-icon class="is-loading" :size="40"><Loading /></el-icon>
+							<p>正在生成二维码...</p>
+						</div>
+						<div v-show="!googleAuth.loading && googleAuth.qrCode" class="qrcode-box">
+							<canvas ref="qrcodeCanvas"></canvas>
+							<p class="qrcode-tip">请使用Google Authenticator扫描二维码</p>
+						</div>
+					</div>
+					<div class="step-actions">
+						<el-button @click="googleDialogVisible = false">取消</el-button>
+						<el-button type="primary" @click="googleBindStep = 1" :disabled="!googleAuth.qrCode">下一步</el-button>
+					</div>
+				</div>
+
+				<div v-if="googleBindStep === 1" class="step-content">
+					<el-form ref="googleFormRef" :model="googleForm" :rules="googleRules" label-width="100px">
+						<el-form-item label="验证码" prop="code">
+							<el-input v-model="googleForm.code" placeholder="请输入6位验证码" maxlength="6" show-word-limit clearable />
+						</el-form-item>
+						<el-alert title="请打开Google Authenticator应用,输入显示的6位数字验证码" type="info" :closable="false" />
+					</el-form>
+					<div class="step-actions">
+						<el-button @click="googleBindStep = 0">上一步</el-button>
+						<el-button type="primary" @click="submitGoogleBind" :loading="googleAuth.binding">确认绑定</el-button>
+					</div>
+				</div>
+
+				<div v-if="googleBindStep === 2" class="step-content">
+					<el-result icon="success" title="绑定成功" sub-title="您已成功绑定Google验证器">
+						<template #extra>
+							<el-button type="primary" @click="closeGoogleDialog">完成</el-button>
+						</template>
+					</el-result>
+				</div>
+			</div>
+		</el-dialog>
+
+		<!-- Google验证器解绑对话框 -->
+		<el-dialog v-model="unbindDialogVisible" title="解除绑定Google验证器" width="450px" :close-on-click-modal="false">
+			<el-form ref="unbindFormRef" :model="unbindForm" :rules="unbindRules" label-width="80px">
+				<el-alert title="解除绑定后,您将无法使用Google验证器进行二次验证" type="warning" :closable="false" style="margin-bottom: 20px" />
+				<el-form-item label="登录密码" prop="pwd">
+					<el-input v-model="unbindForm.pwd" type="password" placeholder="请输入登录密码" show-password clearable />
+				</el-form-item>
+			</el-form>
+			<template #footer>
+				<el-button @click="unbindDialogVisible = false">取消</el-button>
+				<el-button type="danger" @click="submitGoogleUnbind" :loading="googleAuth.unbinding">确认解绑</el-button>
+			</template>
+		</el-dialog>
+
+		<!-- 支付密码设置对话框 -->
+		<el-dialog
+			v-model="paymentPwdDialogVisible"
+			:title="isPaymentPwdSet ? '修改支付密码' : '设置支付密码'"
+			width="450px"
+			:close-on-click-modal="false"
+		>
+			<el-form ref="paymentPwdFormRef" :model="paymentPwdForm" :rules="paymentPwdRules" label-width="100px">
+				<el-alert v-if="!isPaymentPwdSet" title="首次设置支付密码,无需输入旧密码" type="info" :closable="false" style="margin-bottom: 20px" />
+				<el-form-item v-if="isPaymentPwdSet" label="旧密码" prop="oldPassword">
+					<el-input v-model="paymentPwdForm.oldPassword" type="password" placeholder="请输入旧支付密码" show-password clearable />
+				</el-form-item>
+				<el-form-item label="新密码" prop="newPassword">
+					<el-input v-model="paymentPwdForm.newPassword" type="password" placeholder="请输入新支付密码" show-password clearable />
+				</el-form-item>
+				<el-form-item label="确认密码" prop="confirmPassword">
+					<el-input v-model="paymentPwdForm.confirmPassword" type="password" placeholder="请再次输入新支付密码" show-password clearable />
+				</el-form-item>
+			</el-form>
+			<template #footer>
+				<el-button @click="paymentPwdDialogVisible = false">取消</el-button>
+				<el-button type="primary" @click="submitPaymentPwd" :loading="paymentPwdSubmitting">确认</el-button>
+			</template>
+		</el-dialog>
+
+		<!-- 白名单设置对话框 -->
+		<el-dialog v-model="whitelistDialogVisible" title="白名单设置" width="550px" :close-on-click-modal="false">
+			<el-form ref="whitelistFormRef" :model="whitelistForm" :rules="whitelistRules" label-width="80px">
+				<el-alert title="请输入允许访问的IP地址,多个IP用英文逗号分隔" type="info" :closable="false" style="margin-bottom: 20px" />
+				<el-form-item label="IP地址" prop="whitelist">
+					<el-input v-model="whitelistForm.whitelist" type="textarea" :rows="6" placeholder="例如: 192.168.1.1,192.168.1.2" clearable />
+				</el-form-item>
+				<el-form-item>
+					<el-text type="info" size="small">提示: 留空表示不限制IP访问</el-text>
+				</el-form-item>
+			</el-form>
+			<template #footer>
+				<el-button @click="whitelistDialogVisible = false">取消</el-button>
+				<el-button type="primary" @click="submitWhitelist" :loading="whitelistSubmitting">确认</el-button>
+			</template>
+		</el-dialog>
+	</div>
+</template>
+
+<script setup lang="ts" name="settingsSecurity">
+import { ref, reactive, computed, nextTick } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { Lock, Loading, List } from '@element-plus/icons-vue';
+import { getGoogleCode, bindingGoogle, unbindingGoogle, getWhitelist, editWhitelist, editSecondaryPwd } from '/@/api/admin/setting';
+import { useUserInfo } from '/@/stores/userInfo';
+import { Session } from '/@/utils/storage';
+import QRCode from 'qrcode';
+
+// 获取用户信息store
+const userStore = useUserInfo();
+
+// 二维码canvas引用
+const qrcodeCanvas = ref<HTMLCanvasElement>();
+
+// 从全局用户信息获取绑定状态
+const isGoogleBound = computed(() => userStore.userInfos?.user?.googleSecretFlag || false);
+
+// 从全局用户信息获取支付密码设置状态
+const isPaymentPwdSet = computed(() => userStore.userInfos?.user?.secondaryPwdFlag || false);
+
+// 白名单数据
+const whitelistData = ref('');
+
+const googleAuth = reactive({
+	qrCode: '', // 二维码数据
+	loading: false, // 加载中
+	binding: false, // 绑定中
+	unbinding: false, // 解绑中
+});
+
+// Google绑定对话框
+const googleDialogVisible = ref(false);
+const googleBindStep = ref(0); // 0:扫描二维码 1:输入验证码 2:完成
+const googleFormRef = ref();
+const googleForm = reactive({
+	code: '',
+});
+
+// Google绑定表单验证
+const googleRules = {
+	code: [
+		{ required: true, message: '请输入验证码', trigger: 'blur' },
+		{ len: 6, message: '验证码必须为6位数字', trigger: 'blur' },
+		{ pattern: /^\d{6}$/, message: '验证码必须为6位数字', trigger: 'blur' },
+	],
+};
+
+// Google解绑对话框
+const unbindDialogVisible = ref(false);
+const unbindFormRef = ref();
+const unbindForm = reactive({
+	pwd: '',
+});
+
+// Google解绑表单验证
+const unbindRules = {
+	pwd: [{ required: true, message: '请输入登录密码', trigger: 'blur' }],
+};
+
+// 支付密码设置对话框
+const paymentPwdDialogVisible = ref(false);
+const paymentPwdFormRef = ref();
+const paymentPwdSubmitting = ref(false);
+const paymentPwdForm = reactive({
+	oldPassword: '',
+	newPassword: '',
+	confirmPassword: '',
+});
+
+// 支付密码表单验证
+const validateConfirmPassword = (rule: any, value: any, callback: any) => {
+	if (value === '') {
+		callback(new Error('请再次输入新支付密码'));
+	} else if (value !== paymentPwdForm.newPassword) {
+		callback(new Error('两次输入的密码不一致'));
+	} else {
+		callback();
+	}
+};
+
+const paymentPwdRules = computed(() => {
+	const rules: any = {
+		newPassword: [
+			{ required: true, message: '请输入新支付密码', trigger: 'blur' },
+			{ min: 6, message: '密码长度不能少于6位', trigger: 'blur' },
+		],
+		confirmPassword: [{ validator: validateConfirmPassword, trigger: 'blur' }],
+	};
+	if (isPaymentPwdSet.value) {
+		rules.oldPassword = [{ required: true, message: '请输入旧支付密码', trigger: 'blur' }];
+	}
+	return rules;
+});
+
+// 白名单设置对话框
+const whitelistDialogVisible = ref(false);
+const whitelistFormRef = ref();
+const whitelistSubmitting = ref(false);
+const whitelistForm = reactive({
+	whitelist: '',
+});
+
+// 白名单表单验证
+const whitelistRules = {
+	whitelist: [
+		{
+			pattern: /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(,\s*\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})*$/,
+			message: '请输入正确的IP地址格式',
+			trigger: 'blur',
+			required: false,
+		},
+	],
+};
+
+// 获取Google验证器二维码
+const getGoogleQrCode = async () => {
+	googleAuth.loading = true;
+	try {
+		const res = await getGoogleCode();
+		if (res.code === 0) {
+			googleAuth.qrCode = res.data; // 保存二维码数据字符串
+			googleAuth.loading = false; // 先关闭 loading
+			// 等待DOM更新后生成二维码
+			await nextTick();
+			if (qrcodeCanvas.value) {
+				try {
+					await QRCode.toCanvas(qrcodeCanvas.value, res.data, {
+						width: 250,
+						margin: 2,
+						color: {
+							dark: '#000000',
+							light: '#ffffff',
+						},
+						errorCorrectionLevel: 'M',
+					});
+				} catch (err) {
+					ElMessage.error('二维码生成失败');
+				}
+			} else {
+				ElMessage.error('二维码生成失败');
+			}
+			return; // 提前返回,不执行 finally
+		} else {
+			ElMessage.error(res.msg || '获取二维码失败');
+		}
+	} catch (error) {
+		ElMessage.error('获取二维码失败');
+	} finally {
+		googleAuth.loading = false;
+	}
+};
+
+// 绑定Google验证器
+const handleBindGoogle = () => {
+	googleDialogVisible.value = true;
+	googleBindStep.value = 0;
+	googleForm.code = '';
+	googleAuth.qrCode = '';
+	getGoogleQrCode();
+};
+
+// 提交Google绑定
+const submitGoogleBind = async () => {
+	if (!googleFormRef.value) return;
+	await googleFormRef.value.validate(async (valid: boolean) => {
+		if (valid) {
+			googleAuth.binding = true;
+			try {
+				const res = await bindingGoogle(googleForm.code);
+				if (res.code === 0) {
+					googleBindStep.value = 2;
+					// 使用确认框提示用户
+					await ElMessageBox.alert('绑定成功,点击确定后将自动注销并跳转到登录页', '绑定成功', {
+						confirmButtonText: '确定',
+						type: 'success',
+						showClose: false,
+						callback: () => {
+							Session.clear();
+							window.location.href = '/login';
+						},
+					});
+				}
+			} catch (error) {
+				// 全局已经显示错误消息,这里只需要捕获异常
+			} finally {
+				googleAuth.binding = false;
+			}
+		}
+	});
+};
+
+// 关闭Google绑定对话框
+const closeGoogleDialog = () => {
+	googleDialogVisible.value = false;
+	googleBindStep.value = 0;
+	googleForm.code = '';
+};
+
+// 解绑Google验证器
+const handleUnbindGoogle = () => {
+	unbindDialogVisible.value = true;
+	unbindForm.pwd = '';
+};
+
+// 提交Google解绑
+const submitGoogleUnbind = async () => {
+	if (!unbindFormRef.value) return;
+	await unbindFormRef.value.validate(async (valid: boolean) => {
+		if (valid) {
+			googleAuth.unbinding = true;
+			try {
+				const res = await unbindingGoogle(unbindForm.pwd);
+				if (res.code === 0) {
+					unbindDialogVisible.value = false;
+					// 使用确认框提示用户
+					await ElMessageBox.alert('解绑成功,点击确定后将自动注销并跳转到登录页', '解绑成功', {
+						confirmButtonText: '确定',
+						type: 'success',
+						showClose: false,
+						callback: () => {
+							Session.clear();
+							window.location.href = '/login';
+						},
+					});
+				}
+			} catch (error) {
+				// 全局已经显示错误消息,这里只需要捕获异常
+			} finally {
+				googleAuth.unbinding = false;
+			}
+		}
+	});
+};
+
+// 打开支付密码设置对话框
+const handleSetPaymentPwd = () => {
+	paymentPwdDialogVisible.value = true;
+	paymentPwdForm.oldPassword = '';
+	paymentPwdForm.newPassword = '';
+	paymentPwdForm.confirmPassword = '';
+};
+
+// 提交支付密码设置
+const submitPaymentPwd = async () => {
+	if (!paymentPwdFormRef.value) return;
+	await paymentPwdFormRef.value.validate(async (valid: boolean) => {
+		if (valid) {
+			paymentPwdSubmitting.value = true;
+			try {
+				const res = await editSecondaryPwd(paymentPwdForm.newPassword, isPaymentPwdSet.value ? paymentPwdForm.oldPassword : undefined);
+				if (res.code === 0) {
+					paymentPwdDialogVisible.value = false;
+					// 使用确认框提示用户
+					await ElMessageBox.alert('设置成功,点击确定后将自动注销并跳转到登录页', '设置成功', {
+						confirmButtonText: '确定',
+						type: 'success',
+						showClose: false,
+						callback: () => {
+							Session.clear();
+							window.location.href = '/login';
+						},
+					});
+				}
+			} catch (error) {
+				// 全局已经显示错误消息
+			} finally {
+				paymentPwdSubmitting.value = false;
+			}
+		}
+	});
+};
+
+// 打开白名单设置对话框
+const handleSetWhitelist = async () => {
+	whitelistDialogVisible.value = true;
+	// 加载当前白名单
+	try {
+		const res = await getWhitelist();
+		if (res.code === 0) {
+			whitelistForm.whitelist = res.data || '';
+		}
+	} catch (error) {
+		// 加载失败不影响打开对话框
+	}
+};
+
+// 提交白名单设置
+const submitWhitelist = async () => {
+	if (!whitelistFormRef.value) return;
+	await whitelistFormRef.value.validate(async (valid: boolean) => {
+		if (valid) {
+			whitelistSubmitting.value = true;
+			try {
+				const res = await editWhitelist(whitelistForm.whitelist.trim());
+				if (res.code === 0) {
+					ElMessage.success('设置成功');
+					whitelistDialogVisible.value = false;
+					whitelistData.value = whitelistForm.whitelist.trim();
+				}
+			} catch (error) {
+				// 全局已经显示错误消息
+			} finally {
+				whitelistSubmitting.value = false;
+			}
+		}
+	});
+};
+
+// 初始化加载白名单数据
+const loadWhitelist = async () => {
+	try {
+		const res = await getWhitelist();
+		if (res.code === 0) {
+			whitelistData.value = res.data || '';
+		}
+	} catch (error) {
+		// 加载失败不影响页面显示
+	}
+};
+
+// 页面加载时获取白名单数据
+loadWhitelist();
+</script>
+
+<style scoped lang="scss">
+.security-container {
+	padding: 24px;
+	min-height: calc(100vh - 100px);
+
+	.security-header {
+		margin-bottom: 32px;
+
+		.page-title {
+			display: flex;
+			align-items: center;
+			gap: 12px;
+			font-size: 24px;
+			font-weight: 600;
+			color: var(--el-text-color-primary);
+			margin: 0 0 8px 0;
+		}
+
+		.page-desc {
+			font-size: 14px;
+			color: var(--el-text-color-secondary);
+			margin: 0;
+		}
+	}
+
+	.security-grid {
+		display: grid;
+		grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
+		gap: 24px;
+		max-width: 1400px;
+
+		.security-card {
+			position: relative;
+			padding: 28px;
+			transition: all 0.3s ease;
+			border-radius: 12px;
+			cursor: default;
+
+			&:hover {
+				transform: translateY(-4px);
+				box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
+			}
+
+			.card-icon {
+				width: 64px;
+				height: 64px;
+				border-radius: 12px;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				margin-bottom: 20px;
+
+				&.payment-icon {
+					background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
+					color: #fff;
+				}
+
+				&.whitelist-icon {
+					background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
+					color: #fff;
+				}
+
+				&.google-icon {
+					background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+					padding: 12px;
+
+					.google-svg {
+						width: 40px;
+						height: 40px;
+					}
+				}
+			}
+
+			.card-content {
+				flex: 1;
+				min-height: 80px;
+
+				.card-title {
+					font-size: 18px;
+					font-weight: 600;
+					color: var(--el-text-color-primary);
+					margin: 0 0 8px 0;
+				}
+
+				.card-desc {
+					font-size: 13px;
+					color: var(--el-text-color-secondary);
+					line-height: 1.6;
+					margin: 0 0 12px 0;
+				}
+
+				.card-status {
+					margin-top: 8px;
+				}
+			}
+
+			.card-action {
+				margin-top: 20px;
+				display: flex;
+				gap: 12px;
+
+				.el-button {
+					flex: 1;
+				}
+			}
+		}
+	}
+
+	// Google绑定对话框样式
+	.google-bind-content {
+		padding: 20px 0;
+
+		.el-steps {
+			margin-bottom: 40px;
+
+			// 自定义步骤条样式
+			:deep(.el-step__head) {
+				.el-step__icon {
+					width: 30px;
+					height: 30px;
+
+					.el-step__icon-inner {
+						font-size: 18px !important;
+						font-weight: 600 !important;
+					}
+				}
+			}
+
+			:deep(.el-step__title) {
+				font-size: 13px;
+			}
+		}
+
+		.step-content {
+			min-height: 300px;
+
+			.qrcode-container {
+				display: flex;
+				justify-content: center;
+				align-items: center;
+				min-height: 280px;
+
+				.loading-box {
+					text-align: center;
+
+					p {
+						margin-top: 16px;
+						color: var(--el-text-color-secondary);
+					}
+				}
+
+				.qrcode-box {
+					text-align: center;
+
+					canvas {
+						border: 1px solid var(--el-border-color);
+						border-radius: 8px;
+						padding: 12px;
+						background: #fff;
+					}
+
+					.qrcode-tip {
+						margin-top: 16px;
+						font-size: 14px;
+						color: var(--el-text-color-secondary);
+					}
+				}
+			}
+
+			.step-actions {
+				display: flex;
+				justify-content: center;
+				gap: 12px;
+				margin-top: 32px;
+			}
+
+			.el-result {
+				padding: 40px 0;
+			}
+		}
+	}
+}
+
+// 响应式布局
+@media (max-width: 1200px) {
+	.security-container {
+		.security-grid {
+			grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
+		}
+	}
+}
+
+@media (max-width: 768px) {
+	.security-container {
+		padding: 16px;
+
+		.security-header {
+			margin-bottom: 24px;
+
+			.page-title {
+				font-size: 20px;
+			}
+		}
+
+		.security-grid {
+			grid-template-columns: 1fr;
+			gap: 16px;
+
+			.security-card {
+				padding: 20px;
+
+				.card-icon {
+					width: 56px;
+					height: 56px;
+				}
+			}
+		}
+	}
+}
+</style>

+ 202 - 0
src/views/settlement/apply/index.vue

@@ -0,0 +1,202 @@
+<template>
+	<div class="layout-padding">
+		<div class="layout-padding-auto layout-padding-view">
+			<el-row :gutter="20">
+				<el-col :xs="24" :sm="20" :md="16" :lg="12" :xl="10">
+					<el-card shadow="hover" class="apply-card">
+						<template #header>
+							<div class="card-header">
+								<span class="card-title">申请结算</span>
+								<span class="available-balance"
+									>可提现金额:<span class="balance-amount">¥{{ formatAmount(availableBalance) }}</span></span
+								>
+							</div>
+						</template>
+
+						<el-form :model="formData" :rules="rules" ref="formRef" label-width="120px">
+							<el-form-item label="提现金额" prop="amount">
+								<el-input-number v-model="formData.amount" :min="0" :controls="false" placeholder="请输入提现金额" style="width: 100%" />
+							</el-form-item>
+
+							<el-form-item label="提现类型" prop="type">
+								<el-radio-group v-model="formData.type">
+									<el-radio :label="1">银行卡</el-radio>
+									<el-radio :label="2">USDT</el-radio>
+								</el-radio-group>
+							</el-form-item>
+
+							<el-form-item label="银行名称" prop="bankName" v-if="formData.type === 1">
+								<el-input v-model="formData.bankName" placeholder="请输入银行名称" clearable />
+							</el-form-item>
+
+							<el-form-item label="银行卡账号" prop="bankAccount">
+								<el-input v-model="formData.bankAccount" placeholder="请输入银行卡账号/USDT地址" clearable />
+							</el-form-item>
+
+							<el-form-item label="真实姓名" prop="realName">
+								<el-input v-model="formData.realName" placeholder="请输入真实姓名" clearable />
+							</el-form-item>
+
+							<el-form-item>
+								<el-button type="primary" @click="handleSubmit" :loading="loading" size="large" style="width: 120px"> 提交申请 </el-button>
+								<el-button @click="handleReset" size="large" style="width: 120px">重置</el-button>
+							</el-form-item>
+						</el-form>
+					</el-card>
+				</el-col>
+			</el-row>
+		</div>
+	</div>
+</template>
+
+<script lang="ts" name="settlementApply" setup>
+import { applyWithdraw, getAgentBalance } from '/@/api/settlement';
+import { useMessage } from '/@/hooks/message';
+
+// 定义变量
+const formRef = ref();
+const loading = ref(false);
+const availableBalance = ref(0);
+const formData = ref({
+	amount: null,
+	type: 1,
+	bankName: '',
+	bankAccount: '',
+	realName: '',
+});
+
+// 表单验证规则
+const rules = {
+	amount: [{ required: true, message: '请输入提现金额', trigger: 'blur' }],
+	type: [{ required: true, message: '请选择提现类型', trigger: 'change' }],
+	bankName: [{ required: true, message: '请输入银行名称', trigger: 'blur' }],
+	bankAccount: [{ required: true, message: '请输入银行卡账号/USDT地址', trigger: 'blur' }],
+	realName: [{ required: true, message: '请输入真实姓名', trigger: 'blur' }],
+};
+
+// 提交申请
+const handleSubmit = async () => {
+	try {
+		await formRef.value?.validate();
+
+		// 验证提现金额不能超过可提现金额
+		if (formData.value.amount && formData.value.amount > availableBalance.value) {
+			useMessage().error(`提现金额不能超过可提现金额 ¥${formatAmount(availableBalance.value)}`);
+			return;
+		}
+
+		loading.value = true;
+
+		const submitData: any = {
+			amount: formData.value.amount,
+			type: formData.value.type,
+			bankAccount: formData.value.bankAccount,
+			realName: formData.value.realName,
+		};
+
+		// 银行卡类型才需要银行名称
+		if (formData.value.type === 1) {
+			submitData.bankName = formData.value.bankName;
+		}
+
+		await applyWithdraw(submitData);
+		useMessage().success('申请提交成功');
+		handleReset();
+	} catch (err: any) {
+		if (err.msg) {
+			useMessage().error(err.msg);
+		}
+	} finally {
+		loading.value = false;
+	}
+};
+
+// 重置表单
+const handleReset = () => {
+	formRef.value?.resetFields();
+	formData.value = {
+		amount: null,
+		type: 1,
+		bankName: '',
+		bankAccount: '',
+		realName: '',
+	};
+};
+
+// 加载商户余额
+const loadBalance = async () => {
+	try {
+		const res = await getAgentBalance();
+		if (res.code === 0 && res.data) {
+			availableBalance.value = res.data.availableAmount || 0;
+		}
+	} catch (err) {
+		// 加载余额失败
+	}
+};
+
+// 格式化金额
+const formatAmount = (amount: number) => {
+	return amount ? amount.toFixed(2) : '0.00';
+};
+
+// 页面加载时获取余额
+onMounted(() => {
+	loadBalance();
+});
+</script>
+
+<style scoped lang="scss">
+.apply-card {
+	margin-top: 20px;
+}
+
+.card-header {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+
+	.card-title {
+		font-size: 18px;
+		font-weight: bold;
+		color: #303133;
+	}
+
+	.available-balance {
+		font-size: 14px;
+		color: #606266;
+
+		.balance-amount {
+			font-size: 18px;
+			font-weight: bold;
+			color: #f56c6c;
+			margin-left: 5px;
+		}
+	}
+}
+
+:deep(.el-form) {
+	padding: 20px 0;
+}
+
+:deep(.el-form-item) {
+	margin-bottom: 28px;
+}
+
+:deep(.el-input-number) {
+	width: 100%;
+
+	.el-input__inner {
+		text-align: left;
+	}
+}
+
+:deep(.el-input__inner) {
+	height: 40px;
+	line-height: 40px;
+}
+
+:deep(.el-radio) {
+	margin-right: 30px;
+}
+</style>

+ 263 - 0
src/views/settlement/fundFlow/index.vue

@@ -0,0 +1,263 @@
+<template>
+	<div class="layout-padding">
+		<div class="layout-padding-auto layout-padding-view">
+			<!-- 余额信息卡片 -->
+			<el-row :gutter="20" class="mb20">
+				<el-col :span="6" v-for="item in balanceCards" :key="item.key">
+					<el-card shadow="hover" class="balance-card">
+						<div class="balance-item">
+							<div class="balance-label">{{ item.label }}</div>
+							<div class="balance-value">¥{{ formatAmount(balanceData[item.key]) }}</div>
+						</div>
+					</el-card>
+				</el-col>
+			</el-row>
+
+			<!-- 搜索表单 -->
+			<el-row class="ml10" v-show="showSearch">
+				<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList" ref="queryRef">
+					<el-form-item label="订单号" prop="query">
+						<el-input placeholder="请输入订单号" style="max-width: 180px" v-model="state.queryForm.query" clearable />
+					</el-form-item>
+					<el-form-item label="订单类型" prop="orderFlowType">
+						<el-select v-model="state.queryForm.orderFlowType" placeholder="请选择订单类型" style="max-width: 180px" clearable>
+							<el-option label="支付订单" value="PAY" />
+							<el-option label="提现订单" value="WITHDRAW" />
+						</el-select>
+					</el-form-item>
+					<el-form-item label="订单创建时间" prop="orderCreateTime">
+						<el-date-picker
+							v-model="state.queryForm.orderCreateTime"
+							type="datetimerange"
+							range-separator="至"
+							start-placeholder="开始时间"
+							end-placeholder="结束时间"
+							style="max-width: 360px"
+							clearable
+						/>
+					</el-form-item>
+					<el-form-item label="支付时间" prop="payTime">
+						<el-date-picker
+							v-model="state.queryForm.payTime"
+							type="datetimerange"
+							range-separator="至"
+							start-placeholder="开始时间"
+							end-placeholder="结束时间"
+							style="max-width: 360px"
+							clearable
+						/>
+					</el-form-item>
+					<el-form-item>
+						<el-button @click="getDataList" icon="search" type="primary">查询</el-button>
+						<el-button @click="resetQuery" icon="Refresh">重置</el-button>
+					</el-form-item>
+				</el-form>
+			</el-row>
+			<el-row>
+				<div class="mb8" style="width: 100%">
+					<right-toolbar
+						@queryTable="getDataList"
+						class="ml10"
+						style="float: right; margin-right: 20px"
+						v-model:showSearch="showSearch"
+					></right-toolbar>
+				</div>
+			</el-row>
+
+			<!-- 数据表格 -->
+			<el-table
+				:data="state.dataList"
+				@selection-change="handleSelectionChange"
+				@sort-change="sortChangeHandle"
+				style="width: 100%"
+				v-loading="state.loading"
+				border
+				:cell-style="tableStyle.cellStyle"
+				:header-cell-style="tableStyle.headerCellStyle"
+			>
+				<el-table-column align="center" type="selection" width="40" />
+				<el-table-column label="变更前余额" prop="beforeAmount" show-overflow-tooltip>
+					<template #default="scope">
+						<span>¥{{ scope.row.beforeAmount ? scope.row.beforeAmount.toFixed(2) : '0.00' }}</span>
+					</template>
+				</el-table-column>
+				<el-table-column label="变更金额" prop="operationAmount" show-overflow-tooltip>
+					<template #default="scope">
+						<span :class="scope.row.amountType === 'ADD' ? 'amount-add' : 'amount-sub'">
+							{{ scope.row.amountType === 'ADD' ? '+' : '-' }} ¥{{ scope.row.operationAmount ? scope.row.operationAmount.toFixed(2) : '0.00' }}
+						</span>
+					</template>
+				</el-table-column>
+				<el-table-column label="变更后余额" prop="afterAmount" show-overflow-tooltip>
+					<template #default="scope">
+						<span>¥{{ scope.row.afterAmount ? scope.row.afterAmount.toFixed(2) : '0.00' }}</span>
+					</template>
+				</el-table-column>
+				<el-table-column label="订单类型" prop="orderType" show-overflow-tooltip align="center">
+					<template #default="scope">
+						<el-tag :type="orderTypeMap[scope.row.orderType]?.type || 'info'" size="small">
+							{{ orderTypeMap[scope.row.orderType]?.label || '--' }}
+						</el-tag>
+					</template>
+				</el-table-column>
+				<el-table-column label="业务订单" prop="orderNo" show-overflow-tooltip width="200">
+					<template #default="scope">
+						{{ scope.row.orderNo || '--' }}
+					</template>
+				</el-table-column>
+				<el-table-column label="订单金额" prop="orderAmount" show-overflow-tooltip>
+					<template #default="scope">
+						<span class="amount-value">¥{{ scope.row.orderAmount ? scope.row.orderAmount.toFixed(2) : '0.00' }}</span>
+					</template>
+				</el-table-column>
+				<!-- <el-table-column label="手续费" prop="feeAmount" show-overflow-tooltip>
+					<template #default="scope">
+						<span class="amount-value">¥{{ calculateFee(scope.row) }}</span>
+					</template>
+				</el-table-column> -->
+				<el-table-column label="创建时间" prop="payTime" show-overflow-tooltip width="180">
+					<template #default="scope">
+						{{ scope.row.payTime || scope.row.orderCreateTime || '--' }}
+					</template>
+				</el-table-column>
+			</el-table>
+			<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination" />
+		</div>
+	</div>
+</template>
+
+<script lang="ts" name="settlementFundFlow" setup>
+import { BasicTableProps, useTable } from '/@/hooks/table';
+import { fetchFundFlowList, getAgentBalance } from '/@/api/settlement';
+
+// 余额卡片配置
+const balanceCards = [
+	{ key: 'availableAmount', label: '可用余额' },
+	{ key: 'totalAmount', label: '总金额' },
+	{ key: 'freezeAmount', label: '总冻结金额' },
+	{ key: 'withdrawnAmount', label: '总提现金额' },
+];
+
+// 余额数据
+const balanceData = ref({
+	totalAmount: 0,
+	totalUseAmount: 0,
+	freezeAmount: 0,
+	withdrawnAmount: 0,
+	withdrawnTotalAmount: 0,
+});
+
+// 订单类型映射
+const orderTypeMap: Record<string, { label: string; type: any }> = {
+	PAY: { label: '支付订单', type: 'success' },
+	WITHDRAW: { label: '提现订单', type: 'warning' },
+};
+
+// 定义变量内容
+const queryRef = ref();
+const showSearch = ref(true);
+const selectObjs = ref([]);
+const multiple = ref(true);
+
+// 定义表格查询、变更
+const state: BasicTableProps = reactive<BasicTableProps>({
+	queryForm: {
+		query: '',
+		orderFlowType: '',
+		walletType: 'USE', // 默认传递可用金额
+		orderCreateTime: '',
+		payTime: '',
+	},
+	pageList: fetchFundFlowList,
+	createdIsNeed: false,
+});
+
+const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, tableStyle } = useTable(state);
+
+// 加载余额数据
+const loadBalance = async () => {
+	try {
+		const res = await getAgentBalance();
+		if (res.code === 0 && res.data) {
+			balanceData.value = res.data;
+		}
+	} catch (err) {
+		// 加载余额数据失败
+	}
+};
+
+// 格式化金额
+const formatAmount = (amount: number) => {
+	return amount ? amount.toFixed(2) : '0.00';
+};
+
+// 重置查询
+const resetQuery = () => {
+	queryRef.value?.resetFields();
+	state.queryForm = {
+		query: '',
+		orderFlowType: '',
+		walletType: 'USE', // 重置时也保持默认值为可用金额
+		orderCreateTime: '',
+		payTime: '',
+	};
+	getDataList();
+};
+
+// 多选事件
+const handleSelectionChange = (objs: any) => {
+	selectObjs.value = objs.map((val: any) => val.id);
+	multiple.value = !objs.length;
+};
+
+// 页面加载时获取数据
+onMounted(() => {
+	loadBalance();
+	getDataList();
+});
+
+// 页面激活时刷新数据(用于 keep-alive 缓存的页面)
+onActivated(() => {
+	loadBalance();
+	getDataList();
+});
+</script>
+
+<style scoped lang="scss">
+.balance-card {
+	.balance-item {
+		text-align: center;
+		padding: 10px 0;
+
+		.balance-label {
+			font-size: 14px;
+			color: #909399;
+			margin-bottom: 10px;
+		}
+
+		.balance-value {
+			font-size: 24px;
+			font-weight: bold;
+			color: #303133;
+		}
+	}
+}
+
+.amount-value {
+	font-size: 14px;
+	font-weight: bold;
+	color: #f56c6c;
+}
+
+.amount-add {
+	font-size: 14px;
+	font-weight: bold;
+	color: #67c23a;
+}
+
+.amount-sub {
+	font-size: 14px;
+	font-weight: bold;
+	color: #f56c6c;
+}
+</style>

+ 163 - 0
src/views/settlement/record/index.vue

@@ -0,0 +1,163 @@
+<template>
+	<div class="layout-padding">
+		<div class="layout-padding-auto layout-padding-view">
+			<el-row class="ml10" v-show="showSearch">
+				<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList" ref="queryRef">
+					<el-form-item label="真实姓名" prop="realName">
+						<el-input placeholder="请输入真实姓名" style="max-width: 180px" v-model="state.queryForm.realName" clearable />
+					</el-form-item>
+					<el-form-item label="银行卡账号" prop="bankAccount">
+						<el-input placeholder="请输入银行卡账号" style="max-width: 180px" v-model="state.queryForm.bankAccount" clearable />
+					</el-form-item>
+					<el-form-item label="审核状态" prop="auditStatus">
+						<el-select v-model="state.queryForm.auditStatus" placeholder="请选择审核状态" style="max-width: 180px" clearable>
+							<el-option label="待审核" :value="0" />
+							<el-option label="通过" :value="1" />
+							<el-option label="拒绝" :value="2" />
+						</el-select>
+					</el-form-item>
+					<el-form-item>
+						<el-button @click="getDataList" icon="search" type="primary">查询</el-button>
+						<el-button @click="resetQuery" icon="Refresh">重置</el-button>
+					</el-form-item>
+				</el-form>
+			</el-row>
+			<el-row>
+				<div class="mb8" style="width: 100%">
+					<right-toolbar
+						@queryTable="getDataList"
+						class="ml10"
+						style="float: right; margin-right: 20px"
+						v-model:showSearch="showSearch"
+					></right-toolbar>
+				</div>
+			</el-row>
+
+			<el-table
+				:data="state.dataList"
+				@selection-change="handleSelectionChange"
+				@sort-change="sortChangeHandle"
+				style="width: 100%"
+				v-loading="state.loading"
+				border
+				:cell-style="tableStyle.cellStyle"
+				:header-cell-style="tableStyle.headerCellStyle"
+			>
+				<el-table-column align="center" type="selection" width="40" />
+				<el-table-column label="代理名称" prop="agentName" show-overflow-tooltip>
+					<template #default="scope">
+						{{ scope.row.agentName || '--' }}
+					</template>
+				</el-table-column>
+				<el-table-column label="提现金额" prop="amount" show-overflow-tooltip>
+					<template #default="scope">
+						<span class="amount-value">¥{{ scope.row.amount ? scope.row.amount.toFixed(2) : '0.00' }}</span>
+					</template>
+				</el-table-column>
+				<el-table-column label="提现类型" prop="type" show-overflow-tooltip align="center">
+					<template #default="scope">
+						{{ typeMap[scope.row.type] || '--' }}
+					</template>
+				</el-table-column>
+				<el-table-column label="银行名称" prop="bankName" show-overflow-tooltip>
+					<template #default="scope">
+						{{ scope.row.bankName || '--' }}
+					</template>
+				</el-table-column>
+				<el-table-column label="银行卡账号" prop="bankAccount" show-overflow-tooltip>
+					<template #default="scope">
+						{{ scope.row.bankAccount || '--' }}
+					</template>
+				</el-table-column>
+				<el-table-column label="真实姓名" prop="realName" show-overflow-tooltip>
+					<template #default="scope">
+						{{ scope.row.realName || '--' }}
+					</template>
+				</el-table-column>
+				<el-table-column label="审核状态" prop="auditStatus" show-overflow-tooltip align="center">
+					<template #default="scope">
+						<el-tag :type="auditStatusMap[scope.row.auditStatus]?.type || 'info'" size="small">
+							{{ auditStatusMap[scope.row.auditStatus]?.label || '--' }}
+						</el-tag>
+					</template>
+				</el-table-column>
+				<el-table-column label="拒绝原因" prop="failReason" show-overflow-tooltip>
+					<template #default="scope">
+						{{ scope.row.failReason || '--' }}
+					</template>
+				</el-table-column>
+				<el-table-column label="申请时间" prop="applyTime" show-overflow-tooltip width="180">
+					<template #default="scope">
+						{{ scope.row.applyTime || '--' }}
+					</template>
+				</el-table-column>
+				<el-table-column label="创建时间" prop="createdTime" show-overflow-tooltip width="180">
+					<template #default="scope">
+						{{ scope.row.createdTime || '--' }}
+					</template>
+				</el-table-column>
+			</el-table>
+			<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination" />
+		</div>
+	</div>
+</template>
+
+<script lang="ts" name="settlementRecord" setup>
+import { BasicTableProps, useTable } from '/@/hooks/table';
+import { fetchWithdrawList } from '/@/api/settlement';
+
+// 提现类型映射
+const typeMap: Record<number, string> = {
+	1: '银行卡',
+	2: 'USDT',
+};
+
+// 审核状态映射
+const auditStatusMap: Record<number, { label: string; type: any }> = {
+	0: { label: '待审核', type: 'info' },
+	1: { label: '通过', type: 'success' },
+	2: { label: '拒绝', type: 'danger' },
+};
+
+// 定义变量内容
+const queryRef = ref();
+const showSearch = ref(true);
+const selectObjs = ref([]);
+const multiple = ref(true);
+
+// 定义表格查询、变更
+const state: BasicTableProps = reactive<BasicTableProps>({
+	queryForm: {
+		merchantName: '',
+		realName: '',
+		bankAccount: '',
+		auditStatus: '',
+		transferStatus: '',
+	},
+	pageList: fetchWithdrawList,
+	descs: ['created_time'],
+	createdIsNeed: true,
+});
+
+const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, tableStyle } = useTable(state);
+
+// 重置查询
+const resetQuery = () => {
+	queryRef.value?.resetFields();
+	getDataList();
+};
+
+// 多选事件
+const handleSelectionChange = (objs: any) => {
+	selectObjs.value = objs.map((val: any) => val.id);
+	multiple.value = !objs.length;
+};
+</script>
+
+<style scoped lang="scss">
+.amount-value {
+	font-size: 14px;
+	font-weight: bold;
+	color: #f56c6c;
+}
+</style>