Compare commits
6 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
21938f330f | |
|
|
019d09df3e | |
|
|
e9c9dcd9c5 | |
|
|
242cc83843 | |
|
|
3986db42f5 | |
|
|
0c291f2870 |
|
|
@ -0,0 +1,17 @@
|
||||||
|
# 生产环境标识(Vite 内置变量,无需修改)
|
||||||
|
NODE_ENV = production
|
||||||
|
|
||||||
|
# 生产环境接口基础地址
|
||||||
|
VITE_API_BASE_URL = https://adminapi.apimcapital.top/api
|
||||||
|
|
||||||
|
# 请求超时时间(毫秒)
|
||||||
|
VITE_REQUEST_TIMEOUT = 10000
|
||||||
|
|
||||||
|
# 可选:生产环境其他配置(如关闭调试)
|
||||||
|
VITE_DEBUG = false
|
||||||
|
|
||||||
|
# 交易mqtt
|
||||||
|
VITE_MQTT_TRANSACTION_HOST=wss://adminapi.apimcapital.top/mqtt
|
||||||
|
|
||||||
|
# exe 下载基础地址
|
||||||
|
VITE_EXE_DOWNLOAD_BASE_URL = https://adminh5.apimcapital.top
|
||||||
|
|
@ -14,4 +14,7 @@ VITE_DEBUG = false
|
||||||
VITE_MQTT_TRANSACTION_HOST=wss://adminapi.apimcapital.top/mqtt
|
VITE_MQTT_TRANSACTION_HOST=wss://adminapi.apimcapital.top/mqtt
|
||||||
|
|
||||||
# exe 下载基础地址
|
# exe 下载基础地址
|
||||||
VITE_EXE_DOWNLOAD_BASE_URL = https://adminh5.apimcapital.top
|
VITE_EXE_DOWNLOAD_BASE_URL = https://adminh5.apimcapital.top
|
||||||
|
|
||||||
|
# config.json 独立部署在 h5 域名下,需要该域名侧配置 CORS
|
||||||
|
VITE_UPDATE_URL = https://adminh5.apimcapital.top/config.json
|
||||||
|
|
@ -15,3 +15,7 @@ VITE_MQTT_TRANSACTION_HOST = wss://adminapi.duxiang.wiki/mqtt
|
||||||
|
|
||||||
# exe 下载基础地址
|
# exe 下载基础地址
|
||||||
VITE_EXE_DOWNLOAD_BASE_URL = https://adminh5.duxiang.wiki
|
VITE_EXE_DOWNLOAD_BASE_URL = https://adminh5.duxiang.wiki
|
||||||
|
|
||||||
|
|
||||||
|
# config.json 独立部署在 h5 域名下,需要该域名侧配置 CORS
|
||||||
|
VITE_UPDATE_URL = https://adminh5.duxiang.wiki/config.json
|
||||||
|
|
@ -6,8 +6,9 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"dev:test": "vite --mode test",
|
"dev:test": "vite --mode test",
|
||||||
|
"dev:pro": "vite --mode pro",
|
||||||
"build": "run-p type-check \"build-only {@}\" --",
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
"build:prod": "vite build --mode production",
|
"build:prod": "vite build --mode pro",
|
||||||
"build:test": "vite build --mode test",
|
"build:test": "vite build --mode test",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"build-only": "vite build",
|
"build-only": "vite build",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
import axios from 'axios'
|
||||||
|
import { request as baseRequest } from '@/api'
|
||||||
|
|
||||||
|
// 抑制未使用导入告警
|
||||||
|
void baseRequest
|
||||||
|
|
||||||
|
/**
|
||||||
|
* config.json 中与 ios 相关的配置结构
|
||||||
|
*/
|
||||||
|
export type IosConfig = {
|
||||||
|
/** iOS 下载地址(跳转目标) */
|
||||||
|
upload_url?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* /config.json 整体结构(按需扩展字段)
|
||||||
|
*/
|
||||||
|
export type AppConfig = {
|
||||||
|
ios?: IosConfig
|
||||||
|
// 后续若有 android / pc / 版本号等配置也可在此扩展
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 静态文件请求专用的 axios 实例:
|
||||||
|
* - 不带 baseURL(绕开 src/api/index.ts 的 VITE_API_BASE_URL)
|
||||||
|
* - 不带业务 token、不触发全局 loading / 错误通知
|
||||||
|
* - 与项目里 request 拦截器彻底解耦
|
||||||
|
*/
|
||||||
|
const staticRequest = axios.create({
|
||||||
|
timeout: 10000,
|
||||||
|
headers: { 'Content-Type': 'application/json;charset=utf-8' },
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前环境下的 config.json
|
||||||
|
*
|
||||||
|
* 关键说明:
|
||||||
|
* 1. 项目里 src/api/index.ts 的 service 默认 baseURL = VITE_API_BASE_URL(业务后端),
|
||||||
|
* 直接 request.get('/config.json') 会请求业务后端下的 /config.json,自然 404。
|
||||||
|
* 2. config.json 通常独立部署在 h5 域名(如 https://adminh5.xxx.com/config.json),
|
||||||
|
* 需要直接请求该地址。这里用 VITE_UPDATE_URL(无协议/域名兜底为 VITE_EXE_DOWNLOAD_BASE_URL)作为基础域名。
|
||||||
|
* 3. 跨域:若管理后台与 config.json 部署不同源,需要 h5 域名侧 Nginx / CDN 配置 CORS 头:
|
||||||
|
* Access-Control-Allow-Origin: <管理后台域名>
|
||||||
|
* Access-Control-Allow-Methods: GET
|
||||||
|
* 否则浏览器会拦截请求。
|
||||||
|
*
|
||||||
|
* @returns Promise<AppConfig> 配置文件内容(业务调用方按需取字段)
|
||||||
|
*/
|
||||||
|
export async function getConfigApi(): Promise<AppConfig> {
|
||||||
|
// 使用相对路径 /config.json:
|
||||||
|
// - 开发环境由 vite.config.ts 的 server.proxy 转发到 h5 域名,避免 CORS
|
||||||
|
// - 生产环境需要运维在 Nginx / 网关上做同等代理(例如把 /config.json 反代到 h5 域名)
|
||||||
|
const url = '/config.json'
|
||||||
|
const sep = url.includes('?') ? '&' : '?'
|
||||||
|
const finalUrl = `${url}${sep}t=${new Date().getTime()}&channel=web`
|
||||||
|
|
||||||
|
const res = await staticRequest.get<AppConfig>(finalUrl)
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { request } from '@/api';
|
||||||
|
import type {
|
||||||
|
WorkerListResponse,
|
||||||
|
WorkerFormParams,
|
||||||
|
EditWorkerParams,
|
||||||
|
WorkerRoleOption,
|
||||||
|
WorkerRoleOptionResponse,
|
||||||
|
} from '@/api/types/system/worker.d';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取员工列表
|
||||||
|
* @param params 查询参数(用户名)
|
||||||
|
* @returns Promise<WorkerListResponse> 员工列表数据
|
||||||
|
*/
|
||||||
|
export async function getWorkerListApi(params: { username?: string } = {}): Promise<WorkerListResponse> {
|
||||||
|
return request.get<WorkerListResponse>('/worker/getWorkerList', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取员工角色下拉选项
|
||||||
|
* @returns Promise<WorkerRoleOptionResponse> 角色选项列表
|
||||||
|
*/
|
||||||
|
export async function getWorkerRoleIdOptionApi(): Promise<WorkerRoleOptionResponse> {
|
||||||
|
return request.get<WorkerRoleOptionResponse>('/worker/getWorkerRoleIdOptions', undefined, { showMessage: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增员工
|
||||||
|
* @param params 员工表单参数(username、email、role_id)
|
||||||
|
* @returns Promise<void>
|
||||||
|
*/
|
||||||
|
export async function addWorkerApi(params: WorkerFormParams): Promise<void> {
|
||||||
|
return request.post<void>('/worker/addWorker', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑员工
|
||||||
|
* @param params 编辑员工参数(user_id、username、email、status)
|
||||||
|
* @returns Promise<void>
|
||||||
|
*/
|
||||||
|
export async function editWorkerApi(params: EditWorkerParams): Promise<void> {
|
||||||
|
return request.post<void>('/worker/editWorker', params, { showMessage: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { WorkerRoleOption };
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
/**
|
||||||
|
* 账户盈利统计 - 接口
|
||||||
|
*
|
||||||
|
* 后端约定:
|
||||||
|
* - GET /api/trade/profit/statistics 查询账户盈利统计列表
|
||||||
|
* - POST /api/trade/profit/export 导出账户盈利统计(后端返回流或 url)
|
||||||
|
*
|
||||||
|
* 入参:
|
||||||
|
* - mainAccountName 主账户名
|
||||||
|
* - subAccountName 子账户名
|
||||||
|
* - startDate 开始日期 (YYYY-MM-DD)
|
||||||
|
* - endDate 结束日期 (YYYY-MM-DD)
|
||||||
|
*
|
||||||
|
* 出参(列表项):
|
||||||
|
* - id 唯一标识
|
||||||
|
* - mainAccountName 主账户名
|
||||||
|
* - subAccountName 子账户名
|
||||||
|
* - profitAmount 盈利金额
|
||||||
|
* - orderCount 单数
|
||||||
|
* - lotCount 手数
|
||||||
|
* - winRate 赢率(0~1 或 0~100)
|
||||||
|
*
|
||||||
|
* 说明:项目内 request 是带 get/post 方法的对象(见 src/api/index.ts),
|
||||||
|
* 所以这里使用 request.get / request.post 调用方式。
|
||||||
|
*/
|
||||||
|
import { request } from '@/api'
|
||||||
|
|
||||||
|
export interface IProfitStatisticsQuery {
|
||||||
|
mainAccountName?: string
|
||||||
|
subAccountName?: string
|
||||||
|
startDate?: string
|
||||||
|
endDate?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProfitStatisticsItem {
|
||||||
|
id?: string | number
|
||||||
|
mainAccountName?: string
|
||||||
|
subAccountName?: string
|
||||||
|
profitAmount?: number | string
|
||||||
|
orderCount?: number | string
|
||||||
|
lotCount?: number | string
|
||||||
|
winRate?: number | string
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询账户盈利统计列表 */
|
||||||
|
export const getProfitStatisticsList = (params: IProfitStatisticsQuery) => {
|
||||||
|
return request.get<{
|
||||||
|
list?: IProfitStatisticsItem[]
|
||||||
|
records?: IProfitStatisticsItem[]
|
||||||
|
data?: IProfitStatisticsItem[]
|
||||||
|
}>('/api/trade/profit/statistics', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出账户盈利统计(后端返回文件流 / 下载链接) */
|
||||||
|
export const exportProfitStatistics = (params: IProfitStatisticsQuery) => {
|
||||||
|
return request.post<Blob>('/api/trade/profit/export', params, {
|
||||||
|
responseType: 'blob',
|
||||||
|
} as any)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
import type { ApiResponse } from '../index';
|
||||||
|
|
||||||
|
/** 员工角色信息 */
|
||||||
|
export interface WorkerRoleInfo {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 员工列表单条数据 */
|
||||||
|
export interface WorkerItem {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
role_id: number;
|
||||||
|
status: number; // 1:正常 2:封禁
|
||||||
|
created_at: string;
|
||||||
|
role: WorkerRoleInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 员工列表响应 */
|
||||||
|
export type WorkerListResponse = WorkerItem[];
|
||||||
|
|
||||||
|
/** 员工角色下拉选项 */
|
||||||
|
export interface WorkerRoleOption {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 员工角色下拉选项响应 */
|
||||||
|
export type WorkerRoleOptionResponse = WorkerRoleOption[];
|
||||||
|
|
||||||
|
/** 新增员工表单参数 */
|
||||||
|
export interface WorkerFormParams {
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
role_id: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 编辑员工表单参数 */
|
||||||
|
export interface EditWorkerParams {
|
||||||
|
user_id: number;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
status: number; // 1:正常 2:封禁
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 接口返回结构(兼容 data 包裹场景) */
|
||||||
|
export interface WorkerListApiResponse extends ApiResponse<WorkerItem[]> {}
|
||||||
|
export interface WorkerRoleOptionApiResponse extends ApiResponse<WorkerRoleOption[]> {}
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
<el-button type="primary" :loading="buttonLoading" @click="$emit('search')">
|
<el-button type="primary" :loading="buttonLoading" @click="$emit('search')">
|
||||||
{{ buttonConfig.searchText || '搜索' }}
|
{{ buttonConfig.searchText || '搜索' }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button :loading="buttonLoading" @click="$emit('reset')">
|
<el-button :loading="buttonLoading" @click="$emit('reset')" v-if="isShowReset">
|
||||||
{{ buttonConfig.resetText || '重置' }}
|
{{ buttonConfig.resetText || '重置' }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<!-- 按钮区插槽 -->
|
<!-- 按钮区插槽 -->
|
||||||
|
|
@ -97,6 +97,10 @@ const props = defineProps({
|
||||||
type: Array as () => SearchOption[],
|
type: Array as () => SearchOption[],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
isShowReset:{
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
buttonConfig: {
|
buttonConfig: {
|
||||||
type: Object as () => ButtonConfig,
|
type: Object as () => ButtonConfig,
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
|
|
|
||||||
|
|
@ -258,19 +258,34 @@ onMounted(() => {
|
||||||
router.replace({ path: targetTab.path, query: targetTab.query })
|
router.replace({ path: targetTab.path, query: targetTab.query })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始检查滚动状态
|
// 初始检查滚动状态
|
||||||
nextTick(checkScroll)
|
nextTick(checkScroll)
|
||||||
|
|
||||||
// 添加滚动监听
|
// 添加滚动监听
|
||||||
tabsContainerRef.value?.addEventListener('scroll', checkScroll)
|
tabsContainerRef.value?.addEventListener('scroll', checkScroll)
|
||||||
|
|
||||||
// 全局左键点击关闭右键菜单
|
// 全局左键点击关闭右键菜单
|
||||||
document.addEventListener('click', handleGlobalClick)
|
document.addEventListener('click', handleGlobalClick)
|
||||||
// 全局右键监听,如果右键在其他tab上,关闭当前右键菜单
|
// 全局右键监听,如果右键在其他tab上,关闭当前右键菜单
|
||||||
document.addEventListener('contextmenu', handleGlobalContextMenu)
|
document.addEventListener('contextmenu', handleGlobalContextMenu)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 监听动态路由恢复完成(router/index.ts 中通过 dynamicRoutesReady 标记),
|
||||||
|
// 在动态路由加载完毕后,如果当前路由与保存的激活 tab 不一致,则跳转过去
|
||||||
|
watch(
|
||||||
|
() => route.fullPath,
|
||||||
|
() => {
|
||||||
|
// 仅当保存的激活 tab 存在且与当前路由不一致时才跳转
|
||||||
|
if (tabsStore.activeTab && tabsStore.activeTab !== route.path) {
|
||||||
|
const targetTab = tabsStore.tabs.find(item => item.path === tabsStore.activeTab)
|
||||||
|
if (targetTab && targetTab.path !== route.path) {
|
||||||
|
router.replace({ path: targetTab.path, query: targetTab.query })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
tabsContainerRef.value?.removeEventListener('scroll', checkScroll)
|
tabsContainerRef.value?.removeEventListener('scroll', checkScroll)
|
||||||
document.removeEventListener('click', handleGlobalClick)
|
document.removeEventListener('click', handleGlobalClick)
|
||||||
|
|
|
||||||
|
|
@ -128,18 +128,21 @@ export const useTabsStore = defineStore('tabs', () => {
|
||||||
const existIndex = tabs.value.findIndex(item => item.path === tab.path)
|
const existIndex = tabs.value.findIndex(item => item.path === tab.path)
|
||||||
// 计算面包屑路径
|
// 计算面包屑路径
|
||||||
const breadcrumbs = findBreadcrumbPath(tab.path)
|
const breadcrumbs = findBreadcrumbPath(tab.path)
|
||||||
|
|
||||||
if (existIndex === -1) {
|
if (existIndex === -1) {
|
||||||
// 新标签页,添加到列表
|
// 新标签页,添加到列表
|
||||||
tabs.value.push({ ...tab, breadcrumbs })
|
tabs.value.push({ ...tab, breadcrumbs })
|
||||||
|
// 只有新增标签时才激活它
|
||||||
|
activeTab.value = tab.path
|
||||||
} else {
|
} else {
|
||||||
// 已存在的标签页,更新面包屑(确保数据是最新的)
|
// 已存在的标签页,更新面包屑(确保数据是最新的)
|
||||||
const existingTab = tabs.value[existIndex]
|
const existingTab = tabs.value[existIndex]
|
||||||
if (existingTab) {
|
if (existingTab) {
|
||||||
existingTab.breadcrumbs = breadcrumbs
|
existingTab.breadcrumbs = breadcrumbs
|
||||||
}
|
}
|
||||||
|
// 已存在的标签,不要覆盖当前激活状态,
|
||||||
|
// 避免在页面刷新/路由重新匹配时把激活的 tab 错误地切回当前路由
|
||||||
}
|
}
|
||||||
activeTab.value = tab.path
|
|
||||||
saveTabs()
|
saveTabs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ const searchForm = ref({
|
||||||
username: '',
|
username: '',
|
||||||
subAccountId: '',
|
subAccountId: '',
|
||||||
type: '',
|
type: '',
|
||||||
|
orderNo: '',
|
||||||
startTime: initTime.start,
|
startTime: initTime.start,
|
||||||
endTime: initTime.end,
|
endTime: initTime.end,
|
||||||
})
|
})
|
||||||
|
|
@ -92,6 +93,7 @@ const getFundDetailList = async () => {
|
||||||
: '2026-03-15 23:59:59',
|
: '2026-03-15 23:59:59',
|
||||||
user_name: searchForm.value.username,
|
user_name: searchForm.value.username,
|
||||||
account_id: searchForm.value.subAccountId,
|
account_id: searchForm.value.subAccountId,
|
||||||
|
trade_id: searchForm.value.orderNo,
|
||||||
type: searchForm.value.type || '',
|
type: searchForm.value.type || '',
|
||||||
}
|
}
|
||||||
// 调用接口获取数据
|
// 调用接口获取数据
|
||||||
|
|
@ -137,6 +139,7 @@ const handleReset = () => {
|
||||||
username: '',
|
username: '',
|
||||||
subAccountId: '',
|
subAccountId: '',
|
||||||
type: '',
|
type: '',
|
||||||
|
orderNo: '',
|
||||||
startTime: initTime.start,
|
startTime: initTime.start,
|
||||||
endTime: initTime.end,
|
endTime: initTime.end,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
*/
|
*/
|
||||||
export const search = [
|
export const search = [
|
||||||
//单号
|
//单号
|
||||||
// { prop: 'orderNo', label: '单号', type: 'input' as const, placeholder: '请输入流水单号', width: '140px' },
|
{ prop: 'orderNo', label: '单号', type: 'input' as const, placeholder: '请输入流水单号', width: '140px' },
|
||||||
//用户名
|
//用户名
|
||||||
{
|
{
|
||||||
prop: 'username',
|
prop: 'username',
|
||||||
|
|
|
||||||
|
|
@ -1,65 +1,25 @@
|
||||||
<template>
|
<template>
|
||||||
<Dialog :visible="dialogVisible"
|
<Dialog :visible="dialogVisible"
|
||||||
:title="currentStep === 1 ? t('withdraw.dialog.title1') : t('withdraw.dialog.title2')" width="600px"
|
:title="currentStep === 1 ? t('withdraw.dialog.title1') : t('withdraw.dialog.title2')" width="600px"
|
||||||
:show-footer="false" @close="handleUpdateVisible">
|
:show-footer="false" @close="handleCloseDialog">
|
||||||
<!-- 第一步:输入金额并展示计算明细 -->
|
<!-- 第一步:输入金额 -->
|
||||||
<div v-if="currentStep === 1" class="step1-container">
|
<div v-if="currentStep === 1" class="step1-container">
|
||||||
<!-- 钱包余额展示 -->
|
<!-- 钱包余额(简化展示) -->
|
||||||
<div class="wallet-section">
|
<div class="wallet-balance">
|
||||||
<div class="section-label">{{ t('withdraw.dialog.walletBalance') }}</div>
|
<span class="wallet-label">{{ t('withdraw.dialog.walletBalance') }}:</span>
|
||||||
<div class="wallet-grid">
|
<span class="wallet-value">{{ walletBalance }}</span>
|
||||||
<div v-for="(item, index) in walletItems" :key="index" class="wallet-item">
|
<!-- <span class="wallet-currency">{{ currentCurrency }}</span> -->
|
||||||
<span class="wallet-label">{{ item.label }}</span>
|
|
||||||
<span class="wallet-value" :class="{ available: item.field === 'available' }">{{ item.value }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 金额输入 -->
|
<!-- 金额输入 -->
|
||||||
<div class="amount-section">
|
<div class="amount-section">
|
||||||
<div class="section-label">
|
<div class="section-label">
|
||||||
{{ t('withdraw.dialog.amount') }}
|
{{ t('withdraw.dialog.amount') }}
|
||||||
<span class="max-balance">(最大可取:{{ totalWalletBalance }})</span>
|
<span class="max-balance">(最大可取:{{ walletBalance }})</span>
|
||||||
</div>
|
</div>
|
||||||
<el-input v-model="withdrawAmount" type="number" :placeholder="t('withdraw.dialog.amountPlaceholder')"
|
<el-input v-model="withdrawAmount" type="number" :placeholder="t('withdraw.dialog.amountPlaceholder')">
|
||||||
@input="handleAmountInput">
|
<!-- <template #prepend>{{ currentCurrency }}</template> -->
|
||||||
<template #prepend>{{ currentCurrency }}</template>
|
|
||||||
</el-input>
|
</el-input>
|
||||||
<div v-if="amountError" class="amount-error">{{ amountError }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 合计扣除 -->
|
|
||||||
<div v-if="totalDeducted && parseFloat(totalDeducted) > 0" class="total-section">
|
|
||||||
<div class="total-label">{{ t('withdraw.dialog.totalDeducted') }}:</div>
|
|
||||||
<div class="total-value">-{{ totalDeducted }} {{ currentCurrency }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 扣除计算明细 - 用盒子展示 -->
|
|
||||||
<div v-if="withdrawAmount && calculatedData.length > 0" class="calculation-section">
|
|
||||||
<div class="section-label">{{ t('withdraw.dialog.deductionDetails') }}</div>
|
|
||||||
<div class="calculation-grid">
|
|
||||||
<div v-for="(item, index) in calculatedData" :key="index" class="calc-item">
|
|
||||||
<div class="calc-type">{{ item.type }}</div>
|
|
||||||
<div class="calc-values">
|
|
||||||
<div class="calc-original">
|
|
||||||
<span class="label">{{ t('withdraw.dialog.originalBalance') }}</span>
|
|
||||||
<span class="value">{{ item.original }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="calc-deducted">
|
|
||||||
<span class="label">{{ t('withdraw.dialog.deductedAmount') }}</span>
|
|
||||||
<span class="value deducted">-{{ item.deducted }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="calc-remaining">
|
|
||||||
<span class="label">{{ t('withdraw.dialog.remaining') }}</span>
|
|
||||||
<span class="value">{{ item.remaining }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="calc-summary">
|
|
||||||
<span class="label">{{ t('withdraw.dialog.totalDeducted') }}:</span>
|
|
||||||
<span class="total-value">{{ totalDeducted }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 快捷金额 -->
|
<!-- 快捷金额 -->
|
||||||
|
|
@ -84,11 +44,7 @@
|
||||||
<div class="amount-info">
|
<div class="amount-info">
|
||||||
<div class="info-row">
|
<div class="info-row">
|
||||||
<span class="info-label">{{ t('withdraw.dialog.withdrawAmount') }}:</span>
|
<span class="info-label">{{ t('withdraw.dialog.withdrawAmount') }}:</span>
|
||||||
<span class="info-value">{{ withdrawAmount }} USD</span>
|
<span class="info-value">{{ withdrawAmount }} </span>
|
||||||
</div>
|
|
||||||
<div class="info-row" v-for="(item, index) in calculatedData" :key="index">
|
|
||||||
<span class="info-label">{{ item.typeLabel }}:</span>
|
|
||||||
<span class="info-value">{{ item.deducted }} USD</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -139,8 +95,7 @@ import { ref, computed, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||||||
import Dialog from '@/components/common/Dialog.vue'
|
import Dialog from '@/components/common/Dialog.vue'
|
||||||
import { getWalletCapital, agentWithdraw, withdraw } from '@/api/modules/capital/withdraw'
|
import { getWalletCapital, withdraw } from '@/api/modules/capital/withdraw'
|
||||||
import { getStorage } from '@/utils/storage'
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
|
@ -154,7 +109,6 @@ interface Props {
|
||||||
channel_code?: string
|
channel_code?: string
|
||||||
currency?: {
|
currency?: {
|
||||||
symbol: string
|
symbol: string
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -165,29 +119,9 @@ interface Emits {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WalletInfo {
|
interface WalletInfo {
|
||||||
user_id: string
|
|
||||||
amount: string
|
amount: string
|
||||||
commission_point_diff: string
|
|
||||||
freeze: string
|
|
||||||
risk: string
|
|
||||||
service: string
|
|
||||||
toucun_percent: string
|
|
||||||
fee_handling_percent: string
|
|
||||||
commission_fee_handling: string
|
|
||||||
bail: string
|
bail: string
|
||||||
bail_stop_order: string
|
freeze: string
|
||||||
bail_change_order: string
|
|
||||||
risk_control_id: string
|
|
||||||
fee_setting_id: string
|
|
||||||
closing_profit: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CalculatedItem {
|
|
||||||
type: string
|
|
||||||
typeLabel: string
|
|
||||||
original: string
|
|
||||||
deducted: string
|
|
||||||
remaining: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
|
@ -197,179 +131,40 @@ const dialogVisible = ref(props.visible)
|
||||||
const currentStep = ref(1)
|
const currentStep = ref(1)
|
||||||
const withdrawAmount = ref('')
|
const withdrawAmount = ref('')
|
||||||
const submitLoading = ref(false)
|
const submitLoading = ref(false)
|
||||||
const amountError = ref('')
|
|
||||||
|
|
||||||
// 处理金额输入
|
|
||||||
const handleAmountInput = (value: string) => {
|
|
||||||
const numValue = parseFloat(value)
|
|
||||||
if (isNaN(numValue)) {
|
|
||||||
amountError.value = ''
|
|
||||||
calculatedData.value = []
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (numValue > totalWalletBalance.value) {
|
|
||||||
amountError.value = `输入金额不能超过最大可取金额 ${totalWalletBalance.value.toFixed(2)}`
|
|
||||||
calculatedData.value = []
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
amountError.value = ''
|
|
||||||
calculateWithdraw()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 用户信息
|
|
||||||
const userInfo: any = getStorage('userInfo')
|
|
||||||
const roleId = userInfo?.role_id || 6
|
|
||||||
|
|
||||||
// 角色判断
|
|
||||||
const isAgent = computed(() => roleId >= 5)
|
|
||||||
|
|
||||||
// 当前货币类型(从渠道信息获取)
|
// 当前货币类型(从渠道信息获取)
|
||||||
const currentCurrency = computed(() => props.channelInfo?.currency?.symbol || '-')
|
// const currentCurrency = computed(() => props.channelInfo?.currency?.symbol || '-')
|
||||||
|
|
||||||
// 计算可用余额 = amount - freeze - (手续费 + 点差 + 服务费 + 风险金 + 盈亏)
|
// 钱包信息(只保留需要的三个字段)
|
||||||
const availableBalance = computed(() => {
|
|
||||||
const amount = parseFloat(walletInfo.value.amount || '0')
|
|
||||||
const freeze = parseFloat(walletInfo.value.freeze || '0')
|
|
||||||
// 钱包余额总和
|
|
||||||
let walletSum =
|
|
||||||
Math.max(0, parseFloat(walletInfo.value.freeze)) +
|
|
||||||
Math.max(0, parseFloat(walletInfo.value.risk || '0')) +
|
|
||||||
Math.max(0, parseFloat(walletInfo.value.service || '0')) +
|
|
||||||
Math.max(0, parseFloat(walletInfo.value.commission_fee_handling || '0')) +
|
|
||||||
Math.max(0, parseFloat(walletInfo.value.commission_point_diff || '0'));
|
|
||||||
|
|
||||||
if (+roleId === 3 || +roleId === 4) {
|
|
||||||
walletSum += Math.max(0, parseFloat(walletInfo.value.closing_profit || '0'))
|
|
||||||
}
|
|
||||||
return floorMoney(amount - walletSum)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 计算钱包余额总和(用于验证输入金额上限)
|
|
||||||
// roleId >= 5 时,totalWalletBalance 需要包含 availableBalance
|
|
||||||
const totalWalletBalance = computed(() => {
|
|
||||||
const amount = parseFloat(walletInfo.value.amount || '0')
|
|
||||||
const freeze = parseFloat(walletInfo.value.freeze || '0')
|
|
||||||
console.log('walletInfo.value:', amount, freeze);
|
|
||||||
|
|
||||||
// 钱包余额总和(手续费 + 点差 + 服务费 + 风险金 + 盈亏)
|
|
||||||
const walletSum =
|
|
||||||
parseFloat(walletInfo.value.commission_fee_handling || '0') +
|
|
||||||
parseFloat(walletInfo.value.commission_point_diff || '0') +
|
|
||||||
parseFloat(walletInfo.value.service || '0') +
|
|
||||||
parseFloat(walletInfo.value.risk || '0') +
|
|
||||||
parseFloat(Math.max(0, +walletInfo.value.closing_profit) + '')
|
|
||||||
if (roleId >= 5) {
|
|
||||||
// roleId >= 5 时,总余额包含可用余额
|
|
||||||
// availableBalance = amount - freeze - walletSum
|
|
||||||
// const availableBal = Math.max(0, amount - walletSum)
|
|
||||||
return floorMoney(amount - freeze)
|
|
||||||
}
|
|
||||||
return floorMoney(walletSum)
|
|
||||||
})
|
|
||||||
|
|
||||||
const floorMoney = (num: number) => {
|
|
||||||
const cents = Math.floor(num * 100);
|
|
||||||
return cents / 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 费用项标签映射
|
|
||||||
const feeLabelMap: Record<string, string> = {
|
|
||||||
'手续费': '手续费佣金',
|
|
||||||
'点差': '点差佣金',
|
|
||||||
'服务费': '服务费',
|
|
||||||
'风险金': '风险金',
|
|
||||||
'盈亏': '盈亏',
|
|
||||||
'可用余额': '可用余额',
|
|
||||||
}
|
|
||||||
|
|
||||||
// 费用项对应的字段
|
|
||||||
const feeFieldMap: Record<string, keyof WalletInfo | 'available'> = {
|
|
||||||
'手续费': 'commission_fee_handling',
|
|
||||||
'点差': 'commission_point_diff',
|
|
||||||
'服务费': 'service',
|
|
||||||
'风险金': 'risk',
|
|
||||||
'盈亏': 'closing_profit',
|
|
||||||
'可用余额': 'available', // 特殊处理,用 computed 计算
|
|
||||||
}
|
|
||||||
|
|
||||||
// 费用项顺序
|
|
||||||
const feeOrder = computed(() => {
|
|
||||||
const orders: Record<number, string[]> = {
|
|
||||||
1: ['手续费', '点差', '服务费', '风险金', '盈亏'], // 平台
|
|
||||||
2: ['手续费', '点差', '服务费'], // 承包商
|
|
||||||
3: ['手续费', '点差', '服务费', '盈亏'], // 特殊做市商
|
|
||||||
4: ['手续费', '点差', '服务费', '盈亏'], // 普通做市商
|
|
||||||
5: ['手续费', '点差', '可用余额'], // 代理
|
|
||||||
6: ['手续费', '点差', '可用余额'], // 用户
|
|
||||||
}
|
|
||||||
return orders[roleId] ?? orders[6] ?? []
|
|
||||||
})
|
|
||||||
|
|
||||||
// 根据角色动态计算钱包项
|
|
||||||
const walletItems = computed(() => {
|
|
||||||
const items: { label: string; value: string; field: string }[] = []
|
|
||||||
|
|
||||||
for (const feeType of feeOrder.value) {
|
|
||||||
const field = feeFieldMap[feeType]
|
|
||||||
if (!field) continue
|
|
||||||
|
|
||||||
let value: string
|
|
||||||
if (field === 'available') {
|
|
||||||
value = availableBalance.value.toFixed(2)
|
|
||||||
} else {
|
|
||||||
value = walletInfo.value[field] || '0.00'
|
|
||||||
}
|
|
||||||
|
|
||||||
items.push({
|
|
||||||
label: feeLabelMap[feeType] || feeType,
|
|
||||||
value,
|
|
||||||
field,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return items
|
|
||||||
})
|
|
||||||
|
|
||||||
// 钱包信息
|
|
||||||
const walletInfo = ref<WalletInfo>({
|
const walletInfo = ref<WalletInfo>({
|
||||||
user_id: '',
|
amount: '0',
|
||||||
amount: '',
|
|
||||||
commission_point_diff: '0',
|
|
||||||
freeze: '0',
|
|
||||||
risk: '0',
|
|
||||||
service: '0',
|
|
||||||
toucun_percent: '0',
|
|
||||||
fee_handling_percent: '0',
|
|
||||||
commission_fee_handling: '0',
|
|
||||||
bail: '0',
|
bail: '0',
|
||||||
bail_stop_order: '0',
|
freeze: '0',
|
||||||
bail_change_order: '0',
|
|
||||||
risk_control_id: '',
|
|
||||||
fee_setting_id: '',
|
|
||||||
closing_profit: '0',
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 计算后的扣除明细
|
// 钱包余额 = amount - bail - freeze(直接截取最后2位小数,不四舍五入)
|
||||||
const calculatedData = ref<CalculatedItem[]>([])
|
const walletBalance = computed(() => {
|
||||||
|
const amount = parseFloat(walletInfo.value.amount || '0')
|
||||||
// 合计扣除
|
const bail = parseFloat(walletInfo.value.bail || '0')
|
||||||
const totalDeducted = computed(() => {
|
const freeze = parseFloat(walletInfo.value.freeze || '0')
|
||||||
return calculatedData.value.reduce((sum, item) => sum + parseFloat(item.deducted || '0'), 0).toFixed(2)
|
const result = amount - bail - freeze
|
||||||
|
// 转字符串后直接截取最后两位小数,避开 toFixed 的四舍五入和 JS 浮点精度问题
|
||||||
|
const [intPart, decPart = ''] = String(result).split('.')
|
||||||
|
const truncatedDec = (decPart + '00').slice(0, 2)
|
||||||
|
return `${intPart}.${truncatedDec}`
|
||||||
})
|
})
|
||||||
|
|
||||||
// 快捷金额
|
// 快捷金额
|
||||||
const quickAmounts = ['100', '500', '1000', '5000']
|
const quickAmounts = ['100', '500', '1000', '5000']
|
||||||
|
|
||||||
// 当前选中的渠道编码(优先从 props 获取,否则从 channelInfo 获取)
|
// 当前选中的渠道编码
|
||||||
const currentChannelCode = computed(() => {
|
const currentChannelCode = computed(() => {
|
||||||
return props.channelCode || props.channelInfo?.channel_code || ''
|
return props.channelCode || props.channelInfo?.channel_code || ''
|
||||||
})
|
})
|
||||||
|
|
||||||
// 是否为加密货币渠道(TRC/ERC)
|
// 是否为加密货币渠道(TRC/ERC)
|
||||||
const isCryptoChannel = computed(() => {
|
const isCryptoChannel = computed(() => {
|
||||||
return ['TRC', 'ERC', "ADJUST"].includes(currentChannelCode.value)
|
return ['TRC', 'ERC', 'ADJUST'].includes(currentChannelCode.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 是否为银行渠道(BANK)
|
// 是否为银行渠道(BANK)
|
||||||
|
|
@ -380,21 +175,10 @@ const isBankChannel = computed(() => {
|
||||||
// 表单数据
|
// 表单数据
|
||||||
const formRef = ref<FormInstance>()
|
const formRef = ref<FormInstance>()
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
channel_id: '',
|
|
||||||
withdraw_address: '',
|
withdraw_address: '',
|
||||||
// 代理取款字段
|
|
||||||
withdraw_fee_handle: '',
|
|
||||||
withdraw_point_diff: '',
|
|
||||||
withdraw_service_fee: '',
|
|
||||||
withdraw_head_fee: '',
|
|
||||||
withdraw_risk_fee: '',
|
|
||||||
withdraw_blance: '', // 可用余额使用量
|
|
||||||
// 用户取款额外字段
|
|
||||||
beneficiary_name: '',
|
beneficiary_name: '',
|
||||||
beneficiary_enname: '',
|
beneficiary_enname: '',
|
||||||
beneficiary_bank_name: '',
|
beneficiary_bank_name: '',
|
||||||
swift_bic_code: '',
|
|
||||||
beneficiary_bank_address: '',
|
|
||||||
beneficiary_bank_account: '',
|
beneficiary_bank_account: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -419,28 +203,22 @@ watch(() => props.visible, (val) => {
|
||||||
const initData = async () => {
|
const initData = async () => {
|
||||||
currentStep.value = 1
|
currentStep.value = 1
|
||||||
withdrawAmount.value = ''
|
withdrawAmount.value = ''
|
||||||
calculatedData.value = []
|
formData.value = {
|
||||||
|
withdraw_address: '',
|
||||||
|
beneficiary_name: '',
|
||||||
|
beneficiary_enname: '',
|
||||||
|
beneficiary_bank_name: '',
|
||||||
|
beneficiary_bank_account: '',
|
||||||
|
}
|
||||||
|
|
||||||
// 获取钱包信息
|
// 获取钱包信息
|
||||||
try {
|
try {
|
||||||
const res: any = await getWalletCapital()
|
const res: any = await getWalletCapital()
|
||||||
if (res) {
|
if (res) {
|
||||||
walletInfo.value = {
|
walletInfo.value = {
|
||||||
commission_fee_handling: res.commission_fee_handling || '0',
|
|
||||||
commission_point_diff: '' + floorMoney(res.commission_point_diff - res.freeze || 0) || '0',
|
|
||||||
service: res.service || '0',
|
|
||||||
risk: res.risk || '0',
|
|
||||||
closing_profit: res.closing_profit || '0',
|
|
||||||
toucun_percent: res.toucun_percent || '0',
|
|
||||||
amount: res.amount || '0',
|
amount: res.amount || '0',
|
||||||
user_id: res.user_id || '',
|
|
||||||
freeze: res.freeze || '0',
|
|
||||||
bail: res.bail || '0',
|
bail: res.bail || '0',
|
||||||
bail_stop_order: res.bail_stop_order || '0',
|
freeze: res.freeze || '0',
|
||||||
bail_change_order: res.bail_change_order || '0',
|
|
||||||
risk_control_id: res.risk_control_id || '',
|
|
||||||
fee_setting_id: res.fee_setting_id || '',
|
|
||||||
fee_handling_percent: res.fee_handling_percent || '0',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -448,58 +226,14 @@ const initData = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算扣除明细
|
|
||||||
const calculateWithdraw = () => {
|
|
||||||
const amount = parseFloat(withdrawAmount.value) || 0
|
|
||||||
if (amount <= 0) {
|
|
||||||
calculatedData.value = []
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const results: CalculatedItem[] = []
|
|
||||||
let remaining = amount
|
|
||||||
|
|
||||||
for (const feeType of feeOrder.value) {
|
|
||||||
const field = feeFieldMap[feeType]
|
|
||||||
if (!field) continue
|
|
||||||
|
|
||||||
// 获取原始值(特殊处理 available)
|
|
||||||
let original: number
|
|
||||||
if (field === 'available') {
|
|
||||||
original = availableBalance.value
|
|
||||||
} else {
|
|
||||||
original = parseFloat(walletInfo.value[field] || '0')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (original <= 0) continue
|
|
||||||
|
|
||||||
const deducted = Math.min(remaining, original).toFixed(2)
|
|
||||||
const newRemaining = Math.max(0, remaining - original)
|
|
||||||
|
|
||||||
results.push({
|
|
||||||
type: feeType,
|
|
||||||
typeLabel: feeLabelMap[feeType] || feeType,
|
|
||||||
original: original.toFixed(2),
|
|
||||||
deducted: deducted,
|
|
||||||
remaining: Math.max(0, original - parseFloat(deducted)).toFixed(2),
|
|
||||||
})
|
|
||||||
|
|
||||||
remaining = newRemaining
|
|
||||||
if (remaining <= 0) break
|
|
||||||
}
|
|
||||||
|
|
||||||
calculatedData.value = results
|
|
||||||
}
|
|
||||||
|
|
||||||
// 快捷金额
|
// 快捷金额
|
||||||
const handleQuickAmount = (amount: string) => {
|
const handleQuickAmount = (amount: string) => {
|
||||||
withdrawAmount.value = amount
|
withdrawAmount.value = amount
|
||||||
calculateWithdraw()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭弹窗
|
// Dialog 关闭事件(不带参数)
|
||||||
const handleUpdateVisible = (val: boolean) => {
|
const handleCloseDialog = () => {
|
||||||
emit('update:visible', val)
|
emit('update:visible', false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 取消
|
// 取消
|
||||||
|
|
@ -516,50 +250,17 @@ const handleBack = () => {
|
||||||
// 下一步
|
// 下一步
|
||||||
const handleNextStep = () => {
|
const handleNextStep = () => {
|
||||||
const amount = parseFloat(withdrawAmount.value)
|
const amount = parseFloat(withdrawAmount.value)
|
||||||
if (!withdrawAmount.value || amount <= 0) {
|
if (!withdrawAmount.value || isNaN(amount) || amount <= 0) {
|
||||||
ElMessage.warning('请输入取款金额')
|
ElMessage.warning('请输入取款金额')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (amount > totalWalletBalance.value) {
|
const maxBalance = parseFloat(walletBalance.value)
|
||||||
ElMessage.warning(`输入金额不能超过最大可取金额 ${totalWalletBalance.value.toFixed(2)}`)
|
if (amount > maxBalance) {
|
||||||
|
ElMessage.warning(`输入金额不能超过钱包余额 ${walletBalance.value}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清空所有费用字段
|
|
||||||
formData.value.withdraw_fee_handle = '0'
|
|
||||||
formData.value.withdraw_point_diff = '0'
|
|
||||||
formData.value.withdraw_service_fee = '0'
|
|
||||||
formData.value.withdraw_risk_fee = '0'
|
|
||||||
formData.value.withdraw_head_fee = '0'
|
|
||||||
formData.value.withdraw_blance = '0'
|
|
||||||
|
|
||||||
// 根据当前角色的费用项顺序填充表单数据
|
|
||||||
for (const feeType of feeOrder.value) {
|
|
||||||
const deducted = calculatedData.value.find(item => item.type === feeType)?.deducted || '0'
|
|
||||||
|
|
||||||
switch (feeType) {
|
|
||||||
case '手续费':
|
|
||||||
formData.value.withdraw_fee_handle = deducted
|
|
||||||
break
|
|
||||||
case '点差':
|
|
||||||
formData.value.withdraw_point_diff = deducted
|
|
||||||
break
|
|
||||||
case '服务费':
|
|
||||||
formData.value.withdraw_service_fee = deducted
|
|
||||||
break
|
|
||||||
case '风险金':
|
|
||||||
formData.value.withdraw_risk_fee = deducted
|
|
||||||
break
|
|
||||||
case '盈亏':
|
|
||||||
formData.value.withdraw_head_fee = deducted
|
|
||||||
break
|
|
||||||
case '可用余额':
|
|
||||||
formData.value.withdraw_blance = deducted
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
currentStep.value = 2
|
currentStep.value = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -577,75 +278,30 @@ const handleSubmit = async () => {
|
||||||
const params: any = {
|
const params: any = {
|
||||||
channel_id: props.channelId,
|
channel_id: props.channelId,
|
||||||
amount: +withdrawAmount.value,
|
amount: +withdrawAmount.value,
|
||||||
withdraw_address: formData.value.withdraw_address,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据当前角色的费用项顺序添加对应的费用参数
|
if (isCryptoChannel.value) {
|
||||||
for (const feeType of feeOrder.value) {
|
// 加密货币渠道 - 只传钱包地址,其他字段默认 --
|
||||||
switch (feeType) {
|
params.withdraw_address = formData.value.withdraw_address
|
||||||
case '手续费':
|
params.beneficiary_name = '--'
|
||||||
params.withdraw_fee_handle = formData.value.withdraw_fee_handle
|
params.beneficiary_enname = '--'
|
||||||
break
|
params.beneficiary_bank_name = '--'
|
||||||
case '点差':
|
params.beneficiary_bank_account = '--'
|
||||||
params.withdraw_point_diff = formData.value.withdraw_point_diff
|
params.swift_bic_code = '--'
|
||||||
break
|
params.beneficiary_bank_address = '--'
|
||||||
case '服务费':
|
} else if (isBankChannel.value) {
|
||||||
params.withdraw_service_fee = formData.value.withdraw_service_fee
|
// 银行渠道 - 传银行相关字段,不需要钱包地址
|
||||||
break
|
params.withdraw_address = '--'
|
||||||
case '风险金':
|
params.beneficiary_name = formData.value.beneficiary_name
|
||||||
params.withdraw_risk_fee = formData.value.withdraw_risk_fee
|
params.beneficiary_enname = formData.value.beneficiary_enname
|
||||||
break
|
params.beneficiary_bank_name = formData.value.beneficiary_bank_name
|
||||||
case '盈亏':
|
params.beneficiary_bank_account = formData.value.beneficiary_bank_account
|
||||||
params.withdraw_head_fee = formData.value.withdraw_head_fee
|
|
||||||
break
|
|
||||||
case '可用余额':
|
|
||||||
params.withdraw_blance = formData.value.withdraw_blance
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAgent.value) {
|
|
||||||
// 用户取款 - 根据渠道类型处理字段
|
|
||||||
if (isCryptoChannel.value) {
|
|
||||||
// 加密货币渠道 - 只传钱包地址,其他字段默认 --
|
|
||||||
params.beneficiary_name = '--'
|
|
||||||
params.beneficiary_enname = '--'
|
|
||||||
params.beneficiary_bank_name = '--'
|
|
||||||
params.beneficiary_bank_account = '--'
|
|
||||||
} else if (isBankChannel.value) {
|
|
||||||
// 银行渠道 - 传银行相关字段
|
|
||||||
params.beneficiary_name = formData.value.beneficiary_name
|
|
||||||
params.beneficiary_enname = formData.value.beneficiary_enname
|
|
||||||
params.beneficiary_bank_name = formData.value.beneficiary_bank_name
|
|
||||||
params.beneficiary_bank_account = formData.value.beneficiary_bank_account
|
|
||||||
// 银行渠道不需要钱包地址
|
|
||||||
params.withdraw_address = '--'
|
|
||||||
}
|
|
||||||
// 统一默认 -- 的字段
|
|
||||||
params.swift_bic_code = '--'
|
params.swift_bic_code = '--'
|
||||||
params.beneficiary_bank_address = '--'
|
params.beneficiary_bank_address = '--'
|
||||||
|
|
||||||
await withdraw(params)
|
|
||||||
} else {
|
|
||||||
// 代理取款 - 所有未展示字段默认传 --
|
|
||||||
|
|
||||||
if (isBankChannel.value) {
|
|
||||||
params.withdraw_address = '--'
|
|
||||||
params.beneficiary_name = formData.value.beneficiary_name
|
|
||||||
params.beneficiary_enname = formData.value.beneficiary_enname
|
|
||||||
params.beneficiary_bank_name = formData.value.beneficiary_bank_name
|
|
||||||
params.beneficiary_bank_account = formData.value.beneficiary_bank_account
|
|
||||||
} else {
|
|
||||||
params.beneficiary_name = '--'
|
|
||||||
params.beneficiary_enname = '--'
|
|
||||||
params.beneficiary_bank_name = '--'
|
|
||||||
params.beneficiary_bank_account = '--'
|
|
||||||
params.swift_bic_code = '--'
|
|
||||||
params.beneficiary_bank_address = '--'
|
|
||||||
}
|
|
||||||
await agentWithdraw(params)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await withdraw(params)
|
||||||
|
|
||||||
ElMessage.success('取款申请已提交')
|
ElMessage.success('取款申请已提交')
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
emit('update:visible', false)
|
emit('update:visible', false)
|
||||||
|
|
@ -660,187 +316,62 @@ const handleSubmit = async () => {
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.step1-container {
|
.step1-container {
|
||||||
.section-label {
|
.wallet-balance {
|
||||||
margin-bottom: 12px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.wallet-section {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding: 16px;
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
border-radius: 8px;
|
|
||||||
color: #fff;
|
|
||||||
|
|
||||||
.section-label {
|
|
||||||
color: rgba(255, 255, 255, 0.9);
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wallet-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wallet-item {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: baseline;
|
||||||
padding: 12px;
|
padding: 16px 20px;
|
||||||
background: rgba(255, 255, 255, 0.15);
|
margin-bottom: 20px;
|
||||||
border-radius: 6px;
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
.wallet-label {
|
.wallet-label {
|
||||||
margin-bottom: 6px;
|
margin-right: 8px;
|
||||||
font-size: 12px;
|
font-size: 14px;
|
||||||
color: rgba(255, 255, 255, 0.8);
|
color: rgba(255, 255, 255, 0.85);
|
||||||
}
|
}
|
||||||
|
|
||||||
.wallet-value {
|
.wallet-value {
|
||||||
font-size: 18px;
|
font-size: 24px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.amount-section {
|
.wallet-currency {
|
||||||
margin-bottom: 16px;
|
margin-left: 6px;
|
||||||
|
font-size: 14px;
|
||||||
.section-label {
|
color: rgba(255, 255, 255, 0.85);
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #303133;
|
|
||||||
|
|
||||||
.max-balance {
|
|
||||||
margin-left: 8px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #909399;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.amount-error {
|
.amount-section {
|
||||||
margin-top: 8px;
|
margin-bottom: 16px;
|
||||||
font-size: 12px;
|
|
||||||
color: #f56c6c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.total-section {
|
.section-label {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
align-items: center;
|
||||||
align-items: center;
|
margin-bottom: 12px;
|
||||||
padding: 16px;
|
font-size: 14px;
|
||||||
margin-bottom: 16px;
|
font-weight: 500;
|
||||||
background: #fef0f0;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid #fde2e2;
|
|
||||||
|
|
||||||
.total-label {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #606266;
|
|
||||||
}
|
|
||||||
|
|
||||||
.total-value {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #f56c6c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.calculation-section {
|
|
||||||
margin-bottom: 24px;
|
|
||||||
|
|
||||||
.section-label {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calculation-grid {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calc-item {
|
|
||||||
padding: 16px;
|
|
||||||
background: #f5f7fa;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid #ebeef5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calc-type {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calc-values {
|
|
||||||
display: flex;
|
|
||||||
gap: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calc-original,
|
|
||||||
.calc-deducted,
|
|
||||||
.calc-remaining {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.label {
|
|
||||||
margin-bottom: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #909399;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
color: #303133;
|
||||||
|
|
||||||
&.deducted {
|
.max-balance {
|
||||||
color: #f56c6c;
|
margin-left: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.calc-summary {
|
.quick-amounts {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
gap: 12px;
|
||||||
align-items: center;
|
flex-wrap: wrap;
|
||||||
margin-top: 16px;
|
margin-bottom: 8px;
|
||||||
padding-top: 12px;
|
|
||||||
border-top: 1px solid #ebeef5;
|
|
||||||
|
|
||||||
.label {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #606266;
|
|
||||||
}
|
|
||||||
|
|
||||||
.total-value {
|
|
||||||
margin-left: 8px;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #f56c6c;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-amounts {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-footer {
|
.dialog-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|
|
||||||
|
|
@ -41,11 +41,11 @@ export const withdrawCheckTableColumns = [
|
||||||
{ prop: 'real_amount', label: '到账金额', align: 'center' as const, width: 120, isNumber: true },
|
{ prop: 'real_amount', label: '到账金额', align: 'center' as const, width: 120, isNumber: true },
|
||||||
{ prop: 'rate', label: '汇率', align: 'center' as const, width: 100 },
|
{ prop: 'rate', label: '汇率', align: 'center' as const, width: 100 },
|
||||||
{ prop: 'channel_name', label: '渠道', align: 'center' as const, width: 140 },
|
{ prop: 'channel_name', label: '渠道', align: 'center' as const, width: 140 },
|
||||||
{ prop: 'withdraw_service_fee', label: '服务费', align: 'center' as const, width: 100, isNumber: true },
|
// { prop: 'withdraw_service_fee', label: '服务费', align: 'center' as const, width: 100, isNumber: true },
|
||||||
{ prop: 'withdraw_head_fee', label: '头寸', align: 'center' as const, width: 120, isNumber: true },
|
// { prop: 'withdraw_head_fee', label: '头寸', align: 'center' as const, width: 120, isNumber: true },
|
||||||
{ prop: 'withdraw_fee_handle', label: '代理手续费', align: 'center' as const, width: 120, isNumber: true },
|
// { prop: 'withdraw_fee_handle', label: '代理手续费', align: 'center' as const, width: 120, isNumber: true },
|
||||||
{ prop: 'withdraw_point_diff', label: '点差', align: 'center' as const, width: 100, isNumber: true },
|
// { prop: 'withdraw_point_diff', label: '点差', align: 'center' as const, width: 100, isNumber: true },
|
||||||
{ prop: 'withdraw_risk_fee', label: '风控费', align: 'center' as const, isNumber: true , width: 180},
|
// { prop: 'withdraw_risk_fee', label: '风控费', align: 'center' as const, isNumber: true , width: 180},
|
||||||
{ prop: 'withdraw_address', label: '提现地址', align: 'center' as const, width: 180},
|
{ prop: 'withdraw_address', label: '提现地址', align: 'center' as const, width: 180},
|
||||||
{ prop: 'beneficiary_bank_account', label: '银行账号', align: 'center' as const, width: 180},
|
{ prop: 'beneficiary_bank_account', label: '银行账号', align: 'center' as const, width: 180},
|
||||||
{ prop: 'beneficiary_bank_name', label: '银行名称', align: 'center' as const, width: 180},
|
{ prop: 'beneficiary_bank_name', label: '银行名称', align: 'center' as const, width: 180},
|
||||||
|
|
|
||||||
|
|
@ -136,11 +136,11 @@ const tagType = computed<'danger' | 'success' | 'warning' | 'primary'>(() => {
|
||||||
|
|
||||||
const cardData = ref([
|
const cardData = ref([
|
||||||
{ label: '总金额', value: '0', icon: markRaw(Money) },
|
{ label: '总金额', value: '0', icon: markRaw(Money) },
|
||||||
{ label: '盈利', value: '0', icon: markRaw(Trophy) },
|
// { label: '盈利', value: '0', icon: markRaw(Trophy) },
|
||||||
{ label: '手续费佣金', value: '0', icon: markRaw(Coin) },
|
// { label: '手续费佣金', value: '0', icon: markRaw(Coin) },
|
||||||
{ label: '可提现点差佣金', value: '0', icon: markRaw(Wallet) },
|
// { label: '可提现点差佣金', value: '0', icon: markRaw(Wallet) },
|
||||||
{ label: '冻结点差佣金', value: '0', icon: markRaw(Lock) },
|
{ label: '冻结点差佣金', value: '0', icon: markRaw(Lock) },
|
||||||
{ label: '服务费', value: '0', icon: markRaw(DataAnalysis) },
|
// { label: '服务费', value: '0', icon: markRaw(DataAnalysis) },
|
||||||
{ label: '保证金', value: '0', icon: markRaw(TrendCharts) }
|
{ label: '保证金', value: '0', icon: markRaw(TrendCharts) }
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
@ -198,11 +198,11 @@ const fetchWalletData = async () => {
|
||||||
if (res) {
|
if (res) {
|
||||||
cardData.value = [
|
cardData.value = [
|
||||||
{ label: '总金额', value: res.amount ?? 0, icon: markRaw(Money) },
|
{ label: '总金额', value: res.amount ?? 0, icon: markRaw(Money) },
|
||||||
{ label: '盈利', value: res.closing_profit ?? 0, icon: markRaw(Trophy) },
|
// { label: '盈利', value: res.closing_profit ?? 0, icon: markRaw(Trophy) },
|
||||||
{ label: '手续费佣金', value: res.commission_fee_handling ?? 0, icon: markRaw(Coin) },
|
// { label: '手续费佣金', value: res.commission_fee_handling ?? 0, icon: markRaw(Coin) },
|
||||||
{ label: '可提现点差佣金', value: ((res.commission_point_diff || 0) - (res.freeze || 0)) , icon: markRaw(Wallet) },
|
// { label: '可提现点差佣金', value: ((res.commission_point_diff || 0) - (res.freeze || 0)) , icon: markRaw(Wallet) },
|
||||||
{ label: '冻结点差佣金', value: res.freeze ?? 0, icon: markRaw(Lock) },
|
{ label: '冻结点差佣金', value: res.freeze ?? 0, icon: markRaw(Lock) },
|
||||||
{ label: '服务费', value: res.service ?? 0, icon: markRaw(DataAnalysis) },
|
// { label: '服务费', value: res.service ?? 0, icon: markRaw(DataAnalysis) },
|
||||||
{ label: '保证金', value: res.bail ?? 0, icon: markRaw(TrendCharts) }
|
{ label: '保证金', value: res.bail ?? 0, icon: markRaw(TrendCharts) }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,17 +111,11 @@
|
||||||
<span class="value">
|
<span class="value">
|
||||||
<AnimatedNumber :value="walletCapital.amount || 0" :decimals="2" />
|
<AnimatedNumber :value="walletCapital.amount || 0" :decimals="2" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="isAgent">
|
|
||||||
<div class="detail-item">
|
<div class="detail-item">
|
||||||
<span class="label">点差</span>
|
<span class="label">冻结点差</span>
|
||||||
<span class="value">{{ walletCapital.commission_point_diff || '0.00' }}</span>
|
<span class="value">{{ walletCapital.freeze || '0.00' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="detail-item">
|
|
||||||
<span class="label">手续费</span>
|
|
||||||
<span class="value">{{ walletCapital.commission_fee_handling || '0.00' }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="wallet-actions">
|
<div class="wallet-actions">
|
||||||
|
|
@ -142,69 +136,69 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- {{ userInfo.role_id }} -->
|
||||||
<!-- 个人账户 -->
|
<!-- 个人账户 -->
|
||||||
<section class="section my-accounts">
|
<section class="section my-accounts" v-if="(userInfo.type*1 !== 1 && userInfo.role_id * 1 === 5) || userInfo.role_id * 1 === 6" >
|
||||||
<h2 class="section-title">
|
<h2 class="section-title">
|
||||||
<el-icon><User /></el-icon>
|
<el-icon><User /></el-icon>
|
||||||
个人账户
|
个人账户
|
||||||
<el-button type="primary" size="small" @click="handleAddSubAccount" v-auth="'user_createAccount'">
|
<el-button type="primary" size="small" @click="handleAddSubAccount" v-auth="'user_createAccount'">
|
||||||
<el-icon><Plus /></el-icon>
|
<el-icon><Plus /></el-icon>
|
||||||
添加子账户
|
添加子账户
|
||||||
</el-button>
|
</el-button>
|
||||||
</h2>
|
</h2>
|
||||||
<div class="account-grid">
|
<div class="account-grid">
|
||||||
<div
|
<div
|
||||||
v-for="item in accountList"
|
v-for="item in accountList"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
class="account-card"
|
class="account-card"
|
||||||
@click="handleAccountClick(item)"
|
@click="handleAccountClick(item)"
|
||||||
>
|
>
|
||||||
<div class="account-header">
|
<div class="account-header">
|
||||||
<div class="account-name">
|
<div class="account-name">
|
||||||
<span v-if="isEdit !== item.account_id">{{ item.account_name }}</span>
|
<span v-if="isEdit !== item.account_id">{{ item.account_name }}</span>
|
||||||
<el-input
|
<el-input
|
||||||
v-else
|
v-else
|
||||||
v-model="editName"
|
v-model="editName"
|
||||||
size="small"
|
size="small"
|
||||||
@blur="handleSave"
|
@blur="handleSave"
|
||||||
@keyup.enter="handleSave"
|
@keyup.enter="handleSave"
|
||||||
ref="inputRef"
|
ref="inputRef"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div class="account-actions">
|
|
||||||
<el-icon size="18" @click.stop="handleEdit(item.account_name, item.account_id)">
|
|
||||||
<EditPen />
|
|
||||||
</el-icon>
|
|
||||||
<el-icon size="18" @click.stop="handleSetting(item.account_id)">
|
|
||||||
<Setting />
|
|
||||||
</el-icon>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="account-body">
|
|
||||||
<div class="account-number">{{ item.account_id || '--' }}</div>
|
|
||||||
<div class="account-stats">
|
|
||||||
<div class="stat-item">
|
|
||||||
<span class="stat-label">净资产</span>
|
|
||||||
<span class="stat-value">
|
|
||||||
<AnimatedNumber :value="item?.wallets_trade?.amount || 0" :decimals="2" />
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item">
|
<div class="account-actions">
|
||||||
<span class="stat-label">余额</span>
|
<el-icon size="18" @click.stop="handleEdit(item.account_name, item.account_id)">
|
||||||
<span class="stat-value">
|
<EditPen />
|
||||||
<AnimatedNumber :value="item?.wallets_trade?.amount || 0" :decimals="2" />
|
</el-icon>
|
||||||
</span>
|
<el-icon size="18" @click.stop="handleSetting(item.account_id)">
|
||||||
|
<Setting />
|
||||||
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item">
|
</div>
|
||||||
<span class="stat-label">杠杆</span>
|
<div class="account-body">
|
||||||
<span class="stat-value">1:{{ item?.wallets_trade?.lever || '--' }}</span>
|
<div class="account-number">{{ item.account_id || '--' }}</div>
|
||||||
|
<div class="account-stats">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">净资产</span>
|
||||||
|
<span class="stat-value">
|
||||||
|
<AnimatedNumber :value="item?.wallets_trade?.amount || 0" :decimals="2" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">余额</span>
|
||||||
|
<span class="stat-value">
|
||||||
|
<AnimatedNumber :value="item?.wallets_trade?.amount || 0" :decimals="2" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">杠杆</span>
|
||||||
|
<span class="stat-value">1:{{ item?.wallets_trade?.lever || '--' }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- 推广板块 -->
|
<!-- 推广板块 -->
|
||||||
<section class="section promotion" v-if="userInfo.type * 1 === 2 && userInfo.role_id * 1 === 5">
|
<section class="section promotion" v-if="userInfo.type * 1 === 2 && userInfo.role_id * 1 === 5">
|
||||||
|
|
@ -310,7 +304,6 @@ import AnimatedNumber from '@/views/agent/user/AnimatedNumber.vue'
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
// 判断是否为代理(role_id === 5)
|
// 判断是否为代理(role_id === 5)
|
||||||
const isAgent = computed(() => userStore.userInfo?.role_id === 5)
|
|
||||||
|
|
||||||
// 如何开始步骤
|
// 如何开始步骤
|
||||||
const startSteps = ref([
|
const startSteps = ref([
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,12 @@
|
||||||
</header>
|
</header>
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<div class="table-container-left">
|
<div class="table-container-left">
|
||||||
<Table ref="singleTableRef" :show-column-setting="false" :table-data="tableData" :columns="tableColumns" row-key="id" :loading="loading"
|
<Table ref="singleTableRef" :show-column-setting="false" :table-data="tableData" :columns="tableColumns"
|
||||||
:highlight-current-row="true" @current-change="handleCurrentChange" />
|
row-key="id" :loading="loading" :highlight-current-row="true" @current-change="handleCurrentChange" />
|
||||||
</div>
|
</div>
|
||||||
<div class="table-container-right">
|
<div class="table-container-right">
|
||||||
<Table :show-column-setting="false" row-key="id" :table-data="tableDataDetail" :columns="tableColumnsDetail" :loading="detailLoading" />
|
<Table :show-column-setting="false" row-key="id" :table-data="tableDataDetail" :columns="tableColumnsDetail"
|
||||||
|
:loading="detailLoading" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-dialog v-model="dialogVisible" :title="dialogTitleType === 1 ? '添加手续费模板' : '编辑手续费模板'" width="500">
|
<el-dialog v-model="dialogVisible" :title="dialogTitleType === 1 ? '添加手续费模板' : '编辑手续费模板'" width="500">
|
||||||
|
|
@ -94,7 +95,8 @@ const { buttonLoading: submitDetailLoading, startButtonLoading: startSubmitDetai
|
||||||
const tableData = ref<any[]>([])
|
const tableData = ref<any[]>([])
|
||||||
const tableColumns = [
|
const tableColumns = [
|
||||||
{ prop: 'name', label: '名称' },
|
{ prop: 'name', label: '名称' },
|
||||||
{ prop: 'type', label: '状态' },
|
{ prop: 'typeName', label: '状态' },
|
||||||
|
{ prop: 'statusName', label: '是否默认' },
|
||||||
]
|
]
|
||||||
const currentRow = ref<Record<string, any>>({})
|
const currentRow = ref<Record<string, any>>({})
|
||||||
const singleTableRef = ref()
|
const singleTableRef = ref()
|
||||||
|
|
@ -117,6 +119,7 @@ const setCurrent = (row: any) => {
|
||||||
// 主表格行选中变化
|
// 主表格行选中变化
|
||||||
const handleCurrentChange = (row: any) => {
|
const handleCurrentChange = (row: any) => {
|
||||||
currentRow.value = row
|
currentRow.value = row
|
||||||
|
console.log('currentRow.value:', currentRow.value);
|
||||||
fetchItemDetail(row.id)
|
fetchItemDetail(row.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,7 +136,13 @@ const getTableData = async () => {
|
||||||
startLoading()
|
startLoading()
|
||||||
try {
|
try {
|
||||||
const res = await getFeeSettingList()
|
const res = await getFeeSettingList()
|
||||||
tableData.value = Array.isArray(res) ? res : []
|
tableData.value = Array.isArray(res) ? res.map((item) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
typeName: item.type === 1 ? '是' : "否",
|
||||||
|
statusName: item.type === 1 ? '正常' : "封禁"
|
||||||
|
}
|
||||||
|
}) : []
|
||||||
handleTableDataAfterLoad()
|
handleTableDataAfterLoad()
|
||||||
} finally {
|
} finally {
|
||||||
stopLoading()
|
stopLoading()
|
||||||
|
|
@ -152,8 +161,8 @@ const handleTableDataAfterLoad = () => {
|
||||||
const targetRow = savedId ? tableData.value.find((item) => item.id === savedId) : null
|
const targetRow = savedId ? tableData.value.find((item) => item.id === savedId) : null
|
||||||
const rowToSelect = targetRow || tableData.value[0]
|
const rowToSelect = targetRow || tableData.value[0]
|
||||||
|
|
||||||
|
// 仅设置当前行,由 el-table 的 current-change 事件触发 handleCurrentChange,避免重复请求详情接口
|
||||||
setCurrent(rowToSelect)
|
setCurrent(rowToSelect)
|
||||||
handleCurrentChange(rowToSelect)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取详情数据
|
// 获取详情数据
|
||||||
|
|
@ -182,6 +191,7 @@ const editFee = () => {
|
||||||
id: currentRow.value.id,
|
id: currentRow.value.id,
|
||||||
name: currentRow.value.name,
|
name: currentRow.value.name,
|
||||||
type: currentRow.value.type,
|
type: currentRow.value.type,
|
||||||
|
status: currentRow.value.status * 1,
|
||||||
description: currentRow.value.description,
|
description: currentRow.value.description,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -277,4 +287,3 @@ header {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="position-stat table_form-container">
|
<div class="position-stat table_form-container">
|
||||||
<!-- 搜索 -->
|
<!-- 搜索 -->
|
||||||
<PositionStatSearch
|
<PositionStatSearch :buttonLoading="loading" :search-form="searchForm" :search-options="searchOptions"
|
||||||
:buttonLoading="loading" :search-form="searchForm" :search-options="searchOptions" @search="handleSearch"
|
@search="handleSearch" @reset="handleReset" :isShowReset="false">
|
||||||
@reset="handleReset">
|
|
||||||
<!-- <template #button="{ form }">-->
|
<!-- <template #button="{ form }">-->
|
||||||
<!-- <el-button type="primary" @click="handleExport()">-->
|
<!-- <el-button type="primary" @click="handleExport()">-->
|
||||||
<!-- 导出-->
|
<!-- 导出-->
|
||||||
|
|
@ -55,6 +54,9 @@ const tableColumnsOptions = ref(tableColumns)
|
||||||
* @description: 定义搜索方法
|
* @description: 定义搜索方法
|
||||||
*/
|
*/
|
||||||
const handleSearch = async () => {
|
const handleSearch = async () => {
|
||||||
|
if (!searchForm.value.account_id) {
|
||||||
|
return ElMessage.warning('请输入账户ID')
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
startLoading()
|
startLoading()
|
||||||
const data = await checkAccountIdIsSonForUser({ account_id: searchForm.value.account_id })
|
const data = await checkAccountIdIsSonForUser({ account_id: searchForm.value.account_id })
|
||||||
|
|
@ -66,6 +68,7 @@ const handleSearch = async () => {
|
||||||
} finally { stopLoading() }
|
} finally { stopLoading() }
|
||||||
}
|
}
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
|
searchForm.value.account_id = ''
|
||||||
console.log('重置搜索')
|
console.log('重置搜索')
|
||||||
}
|
}
|
||||||
const handleExport = () => {
|
const handleExport = () => {
|
||||||
|
|
@ -80,7 +83,7 @@ onMounted(() => {
|
||||||
transactionStore?.disconnect()
|
transactionStore?.disconnect()
|
||||||
transactionStore.initMarketConditionsMQTT()
|
transactionStore.initMarketConditionsMQTT()
|
||||||
})
|
})
|
||||||
onUnmounted(()=>{
|
onUnmounted(() => {
|
||||||
transactionStore?.disconnect()
|
transactionStore?.disconnect()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,12 +42,12 @@ export const tableColumns = [
|
||||||
{ prop: 'real_currency_symbol', label: '到账币种', align: 'center' as const, width: 100 },
|
{ prop: 'real_currency_symbol', label: '到账币种', align: 'center' as const, width: 100 },
|
||||||
{ prop: 'real_amount', label: '到账金额', align: 'center' as const, width: 120, isNumber: true },
|
{ prop: 'real_amount', label: '到账金额', align: 'center' as const, width: 120, isNumber: true },
|
||||||
{ prop: 'channel_name', label: '渠道', align: 'center' as const, width: 140 },
|
{ prop: 'channel_name', label: '渠道', align: 'center' as const, width: 140 },
|
||||||
{ prop: 'withdraw_service_fee', label: '服务费', align: 'center' as const, width: 100, isNumber: true },
|
// { prop: 'withdraw_service_fee', label: '服务费', align: 'center' as const, width: 100, isNumber: true },
|
||||||
{ prop: 'withdraw_head_fee', label: '头寸', align: 'center' as const, width: 120, isNumber: true },
|
// { prop: 'withdraw_head_fee', label: '头寸', align: 'center' as const, width: 120, isNumber: true },
|
||||||
{ prop: 'withdraw_fee_handle', label: '代理手续费', align: 'center' as const, width: 120, isNumber: true },
|
// { prop: 'withdraw_fee_handle', label: '代理手续费', align: 'center' as const, width: 120, isNumber: true },
|
||||||
{ prop: 'withdraw_point_diff', label: '点差', align: 'center' as const, width: 100, isNumber: true },
|
// { prop: 'withdraw_point_diff', label: '点差', align: 'center' as const, width: 100, isNumber: true },
|
||||||
{ prop: 'withdraw_risk_fee', label: '风控费', width: 140, align: 'center' as const, isNumber: true },
|
// { prop: 'withdraw_risk_fee', label: '风控费', width: 140, align: 'center' as const, isNumber: true },
|
||||||
{ prop: 'withdraw_blance', label: '余额费用', align: 'center' as const, width: 180},
|
// { prop: 'withdraw_blance', label: '余额费用', align: 'center' as const, width: 180},
|
||||||
{ prop: 'withdraw_address', label: '提现地址', width: 140, align: 'center' as const },
|
{ prop: 'withdraw_address', label: '提现地址', width: 140, align: 'center' as const },
|
||||||
{
|
{
|
||||||
prop: 'status', label: '状态', align: 'center' as const, width: 140, options: [{
|
prop: 'status', label: '状态', align: 'center' as const, width: 140, options: [{
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,9 @@
|
||||||
<template #pointSpread="{ row }">
|
<template #pointSpread="{ row }">
|
||||||
<el-button type="primary" size="small" @click="handleViewPointDiff(row)"> 查看 </el-button>
|
<el-button type="primary" size="small" @click="handleViewPointDiff(row)"> 查看 </el-button>
|
||||||
</template>
|
</template>
|
||||||
|
<template #ratio="{ row }">
|
||||||
|
<span :style="getRatioStyle(row.ratio)">{{ row.ratio }}</span>
|
||||||
|
</template>
|
||||||
</MarketTable>
|
</MarketTable>
|
||||||
<!-- 行右键菜单 -->
|
<!-- 行右键菜单 -->
|
||||||
<context-menu v-model:show="menuVisible" :options="menuOptions">
|
<context-menu v-model:show="menuVisible" :options="menuOptions">
|
||||||
|
|
@ -406,6 +409,25 @@ const getMarketFloatProfit = (accountId: string | number): string => {
|
||||||
return parts[0] + '.' + (parts[1] + '00').slice(0, 2)
|
return parts[0] + '.' + (parts[1] + '00').slice(0, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 获取风险率样式
|
||||||
|
* @param ratio 风险率字符串,如 "100%" 或 "--"
|
||||||
|
* @returns 颜色样式对象
|
||||||
|
*/
|
||||||
|
const getRatioStyle = (ratio: string | number | undefined | null) => {
|
||||||
|
// 空值或 '--' 不着色
|
||||||
|
if (ratio === undefined || ratio === null || ratio === '' || ratio === '--') {
|
||||||
|
return { color: '' }
|
||||||
|
}
|
||||||
|
// 去除百分号并转为数字
|
||||||
|
const num = Number(String(ratio).replace('%', ''))
|
||||||
|
if (isNaN(num)) return { color: '' }
|
||||||
|
// 小于等于 20 红色,20-30 之间黄色,大于 30 绿色
|
||||||
|
if (num <= 20) return { color: '#f56c6c' }
|
||||||
|
if (num > 20 && num <= 30) return { color: '#e6a23c' }
|
||||||
|
return { color: '#67c23a' }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 获取做市商的平仓盈亏值
|
* @description 获取做市商的平仓盈亏值
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ export const marketTableColumns = [
|
||||||
{ label: '级别', prop: 'level', align: 'center' as const, width: '120px' },
|
{ label: '级别', prop: 'level', align: 'center' as const, width: '120px' },
|
||||||
{ label: '邮箱', prop: 'email', align: 'center' as const, width: '160px' },
|
{ label: '邮箱', prop: 'email', align: 'center' as const, width: '160px' },
|
||||||
{ label: '手机号', prop: 'mobile', align: 'center' as const, width: '180' },
|
{ label: '手机号', prop: 'mobile', align: 'center' as const, width: '180' },
|
||||||
{ label: '风险率', prop: 'ratio', align: 'center' as const, width: '120px' },
|
{ label: '风险率', prop: 'ratio', align: 'center' as const, width: '120px', slotName: 'ratio' },
|
||||||
{ label: '接单状态', prop: 'market_status', align: 'center' as const, width: '120px' },
|
{ label: '接单状态', prop: 'market_status', align: 'center' as const, width: '120px' },
|
||||||
//基本账户
|
//基本账户
|
||||||
{ label: '基本账户', prop: 'basicAccount', align: 'center' as const, isNumber: true , width: '200px' },
|
{ label: '基本账户', prop: 'basicAccount', align: 'center' as const, isNumber: true , width: '200px' },
|
||||||
|
|
@ -41,21 +41,21 @@ export const marketTableColumns = [
|
||||||
//头寸比率
|
//头寸比率
|
||||||
{ label: '头寸比率', prop: 'positionRatio', align: 'center' as const, width: '150px', isNumber: true },
|
{ label: '头寸比率', prop: 'positionRatio', align: 'center' as const, width: '150px', isNumber: true },
|
||||||
//点差返佣
|
//点差返佣
|
||||||
{ label: '点差返佣', prop: 'pointSpreadCommission', align: 'center' as const, width: '200px', isNumber: true },
|
// { label: '点差返佣', prop: 'pointSpreadCommission', align: 'center' as const, width: '200px', isNumber: true },
|
||||||
//点差
|
//点差
|
||||||
{ label: '点差', slotName: 'pointSpread', align: 'center' as const, width: '150px' },
|
{ label: '点差', slotName: 'pointSpread', align: 'center' as const, width: '150px' },
|
||||||
//手续费比例
|
//手续费比例
|
||||||
{ label: '手续费比例', prop: 'commissionRatio', align: 'center' as const, width: '150px', isNumber: true },
|
{ label: '手续费比例', prop: 'commissionRatio', align: 'center' as const, width: '150px', isNumber: true },
|
||||||
//手续费返佣
|
//手续费返佣
|
||||||
{ label: '手续费返佣', prop: 'commissionReturn', align: 'center' as const, width: '150px', isNumber: true },
|
// { label: '手续费返佣', prop: 'commissionReturn', align: 'center' as const, width: '150px', isNumber: true },
|
||||||
//浮动盈亏
|
//浮动盈亏
|
||||||
{ label: '浮动盈亏', slotName: 'floatProfitLoss', align: 'center' as const, width: '100px', isNumber: true },
|
{ label: '浮动盈亏', slotName: 'floatProfitLoss', align: 'center' as const, width: '100px', isNumber: true },
|
||||||
//平仓盈亏
|
//平仓盈亏
|
||||||
{ label: '平仓盈亏', prop: 'closeProfitLoss', align: 'center' as const, width: '150px', isNumber: true },
|
{ label: '平仓盈亏', prop: 'closeProfitLoss', align: 'center' as const, width: '150px', isNumber: true },
|
||||||
//风险金
|
//风险金
|
||||||
{ label: '风险金', prop: 'riskMargin', align: 'center' as const, width: '150px', isNumber: true },
|
// { label: '风险金', prop: 'riskMargin', align: 'center' as const, width: '150px', isNumber: true },
|
||||||
//服务费
|
// //服务费
|
||||||
{ label: '服务费', prop: 'serviceFee', align: 'center' as const, width: '150px', isNumber: true },
|
// { label: '服务费', prop: 'serviceFee', align: 'center' as const, width: '150px', isNumber: true },
|
||||||
]
|
]
|
||||||
|
|
||||||
//做市商添加配置
|
//做市商添加配置
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,285 @@
|
||||||
|
<template>
|
||||||
|
<div class="system table_form-container">
|
||||||
|
<!-- 搜索 -->
|
||||||
|
<WorkerSearch
|
||||||
|
:search-form="searchForm"
|
||||||
|
:buttonLoading="loading"
|
||||||
|
:search-options="searchOptions"
|
||||||
|
@search="handleSearch"
|
||||||
|
@reset="handleReset"
|
||||||
|
>
|
||||||
|
<template #button>
|
||||||
|
<el-button type="primary" @click="handleAdd" v-auth="'worker_addWorker'">新增员工</el-button>
|
||||||
|
</template>
|
||||||
|
</WorkerSearch>
|
||||||
|
|
||||||
|
<!-- 表格 -->
|
||||||
|
<WorkerTable
|
||||||
|
:table-data="workerList"
|
||||||
|
:columns="tableColumnsOptions"
|
||||||
|
row-key="id"
|
||||||
|
:loading="loading"
|
||||||
|
>
|
||||||
|
<template #status="{ row }">
|
||||||
|
<el-tag :type="row.status === 1 ? 'success' : 'danger'">
|
||||||
|
{{ row.status === 1 ? '正常' : '封禁' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
<template #action="{ row }">
|
||||||
|
<el-button link type="primary" size="small" @click="handleEdit(row)" v-auth="'worker_editWorker'">
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</WorkerTable>
|
||||||
|
|
||||||
|
<!-- 新增/编辑弹窗 -->
|
||||||
|
<WorkerDialog
|
||||||
|
:visible="dialogVisible"
|
||||||
|
:title="dialogTitle"
|
||||||
|
:type="dialogType"
|
||||||
|
:button-loading="submitLoading"
|
||||||
|
@confirm="handleSubmit"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
@close="handleClose"
|
||||||
|
>
|
||||||
|
<WorkerForm
|
||||||
|
:form-data="workerForm"
|
||||||
|
:form-rules="workerFormRules"
|
||||||
|
:form-items="workerFormItemOptions"
|
||||||
|
:options-map="workerFormOptionsMap"
|
||||||
|
ref="workerFormRef"
|
||||||
|
:key="formKey"
|
||||||
|
/>
|
||||||
|
</WorkerDialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineOptions({
|
||||||
|
name: 'Worder'
|
||||||
|
})
|
||||||
|
|
||||||
|
import type { FormInstance } from 'element-plus'
|
||||||
|
import { usePageLoading, useButtonLoading } from '@/composables/useLoading'
|
||||||
|
|
||||||
|
const { loading, startLoading, stopLoading } = usePageLoading()
|
||||||
|
const { buttonLoading: submitLoading, startButtonLoading: startSubmitLoading, stopButtonLoading: stopSubmitLoading } = useButtonLoading()
|
||||||
|
|
||||||
|
// 组件
|
||||||
|
import WorkerSearch from '@/components/common/Search.vue'
|
||||||
|
import WorkerTable from '@/components/common/Table.vue'
|
||||||
|
import WorkerDialog from '@/components/common/Dialog.vue'
|
||||||
|
import WorkerForm from '@/components/common/Form.vue'
|
||||||
|
|
||||||
|
// 配置以及表单验证
|
||||||
|
import { search, tableColumns, addFormItem, editFormItem } from './options'
|
||||||
|
import { addRules, editRules } from './rules'
|
||||||
|
|
||||||
|
// 接口
|
||||||
|
import {
|
||||||
|
getWorkerListApi,
|
||||||
|
getWorkerRoleIdOptionApi,
|
||||||
|
addWorkerApi,
|
||||||
|
editWorkerApi,
|
||||||
|
} from '@/api/modules/system/worker'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 定义搜索数据
|
||||||
|
*/
|
||||||
|
const searchForm = ref({
|
||||||
|
username: '',
|
||||||
|
})
|
||||||
|
const searchOptions = ref(search)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 定义表格数据
|
||||||
|
*/
|
||||||
|
const workerList = ref<any[]>([])
|
||||||
|
const tableColumnsOptions = ref(tableColumns)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 定义弹窗数据
|
||||||
|
*/
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const dialogTitle = ref('')
|
||||||
|
const dialogType = ref<'add' | 'edit'>('add')
|
||||||
|
// 切换弹窗类型时强制刷新表单组件,避免表单字段切换时旧值残留
|
||||||
|
const formKey = ref(0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 定义表单数据
|
||||||
|
*/
|
||||||
|
const workerForm = ref<any>({
|
||||||
|
user_id: 0,
|
||||||
|
username: '',
|
||||||
|
email: '',
|
||||||
|
role_id: null as number | null,
|
||||||
|
status: 1,
|
||||||
|
})
|
||||||
|
const workerFormRef = ref<FormInstance>()
|
||||||
|
const workerFormRules = ref<any>({})
|
||||||
|
const workerFormItemOptions = ref<any[]>([])
|
||||||
|
// 表单下拉选项映射(角色 / 状态)
|
||||||
|
const workerFormOptionsMap = ref<Record<string, { label: string; value: number }[]>>({
|
||||||
|
role_id: [],
|
||||||
|
status: [
|
||||||
|
{ label: '正常', value: 1 },
|
||||||
|
{ label: '封禁', value: 2 },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 角色下拉数据加载
|
||||||
|
*/
|
||||||
|
const fetchRoleOptions = async () => {
|
||||||
|
try {
|
||||||
|
const data: any = await getWorkerRoleIdOptionApi()
|
||||||
|
const list = Array.isArray(data) ? data : (data?.data || [])
|
||||||
|
workerFormOptionsMap.value.role_id = list.map((item: any) => ({
|
||||||
|
label: item.title,
|
||||||
|
value: item.id,
|
||||||
|
}))
|
||||||
|
} catch (err) {
|
||||||
|
console.error('获取员工角色选项失败', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 获取员工列表
|
||||||
|
*/
|
||||||
|
const fetchWorkerList = async () => {
|
||||||
|
startLoading()
|
||||||
|
try {
|
||||||
|
const params: any = {
|
||||||
|
username: searchForm.value.username || undefined,
|
||||||
|
}
|
||||||
|
const data: any = await getWorkerListApi(params)
|
||||||
|
const list = Array.isArray(data) ? data : (data?.data || [])
|
||||||
|
workerList.value = list.map((item: any) => ({
|
||||||
|
...item,
|
||||||
|
roleTitle: item.role?.title || '-',
|
||||||
|
}))
|
||||||
|
} catch (err) {
|
||||||
|
console.error('获取员工列表失败', err)
|
||||||
|
} finally {
|
||||||
|
stopLoading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 页面挂载
|
||||||
|
*/
|
||||||
|
onMounted(async () => {
|
||||||
|
await Promise.all([fetchRoleOptions(), fetchWorkerList()])
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 搜索 / 重置
|
||||||
|
*/
|
||||||
|
const handleSearch = () => {
|
||||||
|
fetchWorkerList()
|
||||||
|
}
|
||||||
|
const handleReset = () => {
|
||||||
|
searchForm.value = { username: '' }
|
||||||
|
fetchWorkerList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 新增
|
||||||
|
* 入参字段:email、role_id
|
||||||
|
*/
|
||||||
|
const handleAdd = () => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
dialogTitle.value = '新增员工'
|
||||||
|
dialogType.value = 'add'
|
||||||
|
workerFormItemOptions.value = addFormItem as any
|
||||||
|
workerFormRules.value = addRules()
|
||||||
|
formKey.value++
|
||||||
|
workerForm.value = {
|
||||||
|
user_id: 0,
|
||||||
|
email: '',
|
||||||
|
role_id: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 编辑
|
||||||
|
* 入参字段:username、email、status
|
||||||
|
*/
|
||||||
|
const handleEdit = (row: any) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
dialogTitle.value = '编辑员工'
|
||||||
|
dialogType.value = 'edit'
|
||||||
|
workerFormItemOptions.value = editFormItem as any
|
||||||
|
workerFormRules.value = editRules()
|
||||||
|
formKey.value++
|
||||||
|
workerForm.value = {
|
||||||
|
user_id: row.id,
|
||||||
|
username: row.username,
|
||||||
|
email: row.email,
|
||||||
|
status: row.status,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 提交表单
|
||||||
|
*/
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
startSubmitLoading()
|
||||||
|
try {
|
||||||
|
await workerFormRef.value?.validate()
|
||||||
|
if (dialogType.value === 'add') {
|
||||||
|
const params = {
|
||||||
|
email: workerForm.value.email,
|
||||||
|
role_id: workerForm.value.role_id,
|
||||||
|
}
|
||||||
|
await addWorkerApi(params)
|
||||||
|
ElNotification({
|
||||||
|
message: '新增成功',
|
||||||
|
type: 'success',
|
||||||
|
duration: 2000,
|
||||||
|
customClass: 'global-notification',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const params = {
|
||||||
|
user_id: workerForm.value.user_id,
|
||||||
|
username: workerForm.value.username,
|
||||||
|
email: workerForm.value.email,
|
||||||
|
status: workerForm.value.status,
|
||||||
|
}
|
||||||
|
await editWorkerApi(params)
|
||||||
|
ElNotification({
|
||||||
|
message: '编辑成功',
|
||||||
|
type: 'success',
|
||||||
|
duration: 2000,
|
||||||
|
customClass: 'global-notification',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
dialogVisible.value = false
|
||||||
|
await fetchWorkerList()
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err?.message) {
|
||||||
|
ElNotification({
|
||||||
|
message: err.message || '操作失败',
|
||||||
|
type: 'error',
|
||||||
|
duration: 2000,
|
||||||
|
customClass: 'global-notification',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
stopSubmitLoading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 取消 / 关闭弹窗
|
||||||
|
*/
|
||||||
|
const handleCancel = () => {
|
||||||
|
dialogVisible.value = false
|
||||||
|
}
|
||||||
|
const handleClose = () => {
|
||||||
|
dialogVisible.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
/**
|
||||||
|
* @description: 搜索配置
|
||||||
|
*/
|
||||||
|
export const search = [
|
||||||
|
{
|
||||||
|
prop: 'username',
|
||||||
|
label: '用户名',
|
||||||
|
type: 'input' as const,
|
||||||
|
placeholder: '请输入用户名',
|
||||||
|
width: '220px',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 表格列配置
|
||||||
|
*/
|
||||||
|
export const tableColumns = [
|
||||||
|
{ prop: 'username', label: '用户名' },
|
||||||
|
{ prop: 'email', label: '邮箱' },
|
||||||
|
{ prop: 'roleTitle', label: '角色' },
|
||||||
|
{ slotName: 'status', label: '状态' },
|
||||||
|
{ prop: 'created_at', label: '创建时间' },
|
||||||
|
{ slotName: 'action', label: '操作' },
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 新增员工表单配置
|
||||||
|
* 入参:email(校验格式)、role_id(下拉)
|
||||||
|
*/
|
||||||
|
export const addFormItem = [
|
||||||
|
{
|
||||||
|
prop: 'email',
|
||||||
|
label: '邮箱',
|
||||||
|
type: 'input' as const,
|
||||||
|
placeholder: '请输入邮箱',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'role_id',
|
||||||
|
label: '角色',
|
||||||
|
type: 'select' as const,
|
||||||
|
placeholder: '请选择角色',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 编辑员工表单配置
|
||||||
|
* 入参:username、email、status
|
||||||
|
*/
|
||||||
|
export const editFormItem = [
|
||||||
|
{
|
||||||
|
prop: 'username',
|
||||||
|
label: '用户名',
|
||||||
|
type: 'input' as const,
|
||||||
|
placeholder: '请输入用户名',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'email',
|
||||||
|
label: '邮箱',
|
||||||
|
type: 'input' as const,
|
||||||
|
placeholder: '请输入邮箱',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'status',
|
||||||
|
label: '状态',
|
||||||
|
type: 'radio' as const,
|
||||||
|
placeholder: '请选择状态',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
import type { FormRules } from 'element-plus'
|
||||||
|
|
||||||
|
/** 邮箱格式校验 */
|
||||||
|
const emailValidator = (rule: any, value: any, callback: any) => {
|
||||||
|
if (!value) {
|
||||||
|
callback()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const emailReg = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/
|
||||||
|
if (!emailReg.test(value)) {
|
||||||
|
callback(new Error('邮箱格式不正确'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 新增员工表单校验规则
|
||||||
|
* 入参:email、role_id
|
||||||
|
*/
|
||||||
|
export const addRules = (): FormRules => {
|
||||||
|
return {
|
||||||
|
email: [
|
||||||
|
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
||||||
|
{ validator: emailValidator, trigger: 'blur' },
|
||||||
|
],
|
||||||
|
role_id: [
|
||||||
|
{ required: true, message: '请选择角色', trigger: 'change' },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 编辑员工表单校验规则
|
||||||
|
* 入参:username、email、status
|
||||||
|
*/
|
||||||
|
export const editRules = (): FormRules => {
|
||||||
|
return {
|
||||||
|
username: [
|
||||||
|
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||||
|
{ min: 2, max: 50, message: '用户名长度在 2 到 50 个字符', trigger: 'blur' },
|
||||||
|
],
|
||||||
|
email: [
|
||||||
|
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
||||||
|
{ validator: emailValidator, trigger: 'blur' },
|
||||||
|
],
|
||||||
|
status: [
|
||||||
|
{ required: true, message: '请选择状态', trigger: 'change' },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 兼容旧引用 */
|
||||||
|
export const rules = (): FormRules => editRules()
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="download-wrapper table_form-container">
|
<div class="download-wrapper table_formcontainer">
|
||||||
<!-- PC端 -->
|
<!-- PC端 -->
|
||||||
<div class="download-item">
|
<div class="download-item">
|
||||||
<div class="item-left">
|
<div class="item-left">
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- IOS -->
|
<!-- IOS -->
|
||||||
<div class="download-item" v-if="false">
|
<div class="download-item" v-if="iosUploadUrl">
|
||||||
<div class="item-left">
|
<div class="item-left">
|
||||||
<div class="icon-box ios">
|
<div class="icon-box ios">
|
||||||
<svg viewBox="0 0 1024 1024" width="32" height="32">
|
<svg viewBox="0 0 1024 1024" width="32" height="32">
|
||||||
|
|
@ -58,11 +58,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item-center">
|
<div class="item-center">
|
||||||
<span class="desc">前往App Store搜索 InTrade 安装</span>
|
<span class="desc">点击跳转至iOS下载页安装</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="item-right">
|
<div class="item-right">
|
||||||
<el-button type="warning" :icon="Link" @click="handleDownload('ios')">
|
<el-button type="warning" :icon="Link" :loading="iosLoading" @click="handleDownload('ios')">
|
||||||
前往商店
|
前往下载
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -71,6 +71,8 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Monitor, Download, Link } from '@element-plus/icons-vue'
|
import { Monitor, Download, Link } from '@element-plus/icons-vue'
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { getConfigApi } from '@/api/modules/config'
|
||||||
defineOptions({ name: 'Exe' })
|
defineOptions({ name: 'Exe' })
|
||||||
|
|
||||||
type DownloadType = 'pc' | 'android' | 'ios'
|
type DownloadType = 'pc' | 'android' | 'ios'
|
||||||
|
|
@ -86,10 +88,51 @@ const androidApkName = isTestEnv ? 'app-production-debug.apk' : 'app-production-
|
||||||
const downloadUrlMap: Record<DownloadType, string> = {
|
const downloadUrlMap: Record<DownloadType, string> = {
|
||||||
pc: `${exeBaseUrl}/pc/Apim Capital-1.0.27.exe`,
|
pc: `${exeBaseUrl}/pc/Apim Capital-1.0.27.exe`,
|
||||||
android: `${exeBaseUrl}/app/${androidApkName}`,
|
android: `${exeBaseUrl}/app/${androidApkName}`,
|
||||||
ios: `${exeBaseUrl}/app/xxxx.apk`
|
ios: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从 config.json 中动态获取 ios.upload_url
|
||||||
|
// 不同环境部署的 config.json 不同,开发/prod/test 会自动请求对应域名下的 /config.json
|
||||||
|
const iosUploadUrl = ref<string>('')
|
||||||
|
const iosLoading = ref(false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用统一封装在 src/api/modules/config.ts 的接口获取 /config.json,
|
||||||
|
* 从中读取 ios.upload_url 作为 iOS 下载跳转地址。
|
||||||
|
*/
|
||||||
|
const fetchIosUploadUrl = async (): Promise<string> => {
|
||||||
|
try {
|
||||||
|
const data = await getConfigApi()
|
||||||
|
console.log('data:', data);
|
||||||
|
const url = data?.ios?.upload_url
|
||||||
|
if (typeof url === 'string' && url.trim()) {
|
||||||
|
return url.trim()
|
||||||
|
}
|
||||||
|
console.warn('[Exe] /config.json 中未找到 ios.upload_url 字段')
|
||||||
|
return ''
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[Exe] 请求 /config.json 出错:', err)
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
iosLoading.value = true
|
||||||
|
iosUploadUrl.value = await fetchIosUploadUrl()
|
||||||
|
iosLoading.value = false
|
||||||
|
})
|
||||||
|
|
||||||
const handleDownload = (type: DownloadType) => {
|
const handleDownload = (type: DownloadType) => {
|
||||||
|
if (type === 'ios') {
|
||||||
|
if (!iosUploadUrl.value) {
|
||||||
|
// 未拿到地址时静默忽略,避免跳到错误页面
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// iOS 直接新窗口跳转到下载页(App Store / 安装描述文件等)
|
||||||
|
window.open(iosUploadUrl.value, '_blank', 'noopener,noreferrer')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const url: string = downloadUrlMap[type]
|
const url: string = downloadUrlMap[type]
|
||||||
// 使用 window.open 在新页签打开下载链接,
|
// 使用 window.open 在新页签打开下载链接,
|
||||||
// 避免在当前页点击 a.download 时被 SPA 路由拦截跳转到登录页
|
// 避免在当前页点击 a.download 时被 SPA 路由拦截跳转到登录页
|
||||||
|
|
@ -113,7 +156,8 @@ const handleDownload = (type: DownloadType) => {
|
||||||
.download-wrapper {
|
.download-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
padding: 10px 20px 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.download-item {
|
.download-item {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,145 @@
|
||||||
|
<template>
|
||||||
|
<div class="profit-statistics table_form-container">
|
||||||
|
<!-- 搜索 -->
|
||||||
|
<ProfitStatisticsSearch
|
||||||
|
:search-form="searchForm"
|
||||||
|
:button-loading="loading"
|
||||||
|
:search-options="searchOptions"
|
||||||
|
@search="handleSearch"
|
||||||
|
@reset="handleReset"
|
||||||
|
>
|
||||||
|
<!-- 操作按钮:导出 -->
|
||||||
|
<template #button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:loading="exportLoading"
|
||||||
|
:disabled="!tableData.length"
|
||||||
|
v-auth="'trade_profitStatistics_export'"
|
||||||
|
@click="handleExport"
|
||||||
|
>
|
||||||
|
导出
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</ProfitStatisticsSearch>
|
||||||
|
|
||||||
|
<!-- 表格 -->
|
||||||
|
<ProfitStatisticsTable
|
||||||
|
:table-data="tableData"
|
||||||
|
:columns="tableColumnsOptions"
|
||||||
|
:loading="loading"
|
||||||
|
row-key="id"
|
||||||
|
border
|
||||||
|
highlight-current-row
|
||||||
|
>
|
||||||
|
<!-- 赢率:后端返回 0~1 的小数,这里转换为百分比 -->
|
||||||
|
<template #winRate="{ row }">
|
||||||
|
<span>{{ formatWinRate(row.winRate) }}</span>
|
||||||
|
</template>
|
||||||
|
</ProfitStatisticsTable>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import ProfitStatisticsSearch from '@/components/common/Search.vue'
|
||||||
|
import ProfitStatisticsTable from '@/components/common/Table.vue'
|
||||||
|
import { searchOptions, tableColumns } from './options'
|
||||||
|
import { rules as _rules } from './rules'
|
||||||
|
import { getProfitStatisticsList } from '@/api/modules/trade/profitStatistics'
|
||||||
|
import { exportToExcel } from '@/utils/exportToExcel'
|
||||||
|
|
||||||
|
// 搜索表单
|
||||||
|
const searchForm = ref({
|
||||||
|
mainAccountName: '',
|
||||||
|
subAccountName: '',
|
||||||
|
dateRange: [], // [startDate, endDate]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表格数据
|
||||||
|
const tableData = ref<any[]>([])
|
||||||
|
const tableColumnsOptions = ref<any[]>(tableColumns)
|
||||||
|
|
||||||
|
// 加载状态
|
||||||
|
const loading = ref(false)
|
||||||
|
const exportLoading = ref(false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索
|
||||||
|
*/
|
||||||
|
const handleSearch = async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
const [startDate = '', endDate = ''] = (searchForm.value.dateRange as string[]) || []
|
||||||
|
const { data } = await getProfitStatisticsList({
|
||||||
|
mainAccountName: searchForm.value.mainAccountName || undefined,
|
||||||
|
subAccountName: searchForm.value.subAccountName || undefined,
|
||||||
|
startDate: startDate || undefined,
|
||||||
|
endDate: endDate || undefined,
|
||||||
|
})
|
||||||
|
tableData.value = Array.isArray(data) ? data : (data as any)?.list || (data as any)?.records || []
|
||||||
|
} catch (e: any) {
|
||||||
|
ElMessage.error(e?.message || '查询失败')
|
||||||
|
tableData.value = []
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置
|
||||||
|
*/
|
||||||
|
const handleReset = () => {
|
||||||
|
searchForm.value.mainAccountName = ''
|
||||||
|
searchForm.value.subAccountName = ''
|
||||||
|
searchForm.value.dateRange = []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出
|
||||||
|
* 复用项目通用导出工具,导出当前查询结果。
|
||||||
|
*/
|
||||||
|
const handleExport = async () => {
|
||||||
|
if (!tableData.value.length) {
|
||||||
|
ElMessage.warning('当前没有可导出的数据')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
exportLoading.value = true
|
||||||
|
await exportToExcel({
|
||||||
|
tableData: tableData.value,
|
||||||
|
columns: tableColumnsOptions.value,
|
||||||
|
title: '账户盈利统计',
|
||||||
|
filename: `账户盈利统计_${new Date().getTime()}`,
|
||||||
|
})
|
||||||
|
} catch (e: any) {
|
||||||
|
ElMessage.error(e?.message || '导出失败')
|
||||||
|
} finally {
|
||||||
|
exportLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 赢率格式化为百分比:0.62 -> 62.00%
|
||||||
|
*/
|
||||||
|
const formatWinRate = (val: any) => {
|
||||||
|
if (val === null || val === undefined || val === '') return '-'
|
||||||
|
const num = Number(val)
|
||||||
|
if (Number.isNaN(num)) return '-'
|
||||||
|
// 若后端已按 0~100 传值,则原样展示;否则按 0~1 处理
|
||||||
|
const rate = num > 1 ? num : num * 100
|
||||||
|
return `${rate.toFixed(2)}%`
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 初始进入页面不自动查询,保持"用户主动查询"的交互
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.profit-statistics {
|
||||||
|
.cell-content {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
/**
|
||||||
|
* 账户盈利统计 - 搜索/表格列配置
|
||||||
|
*/
|
||||||
|
import type { Ref } from 'vue'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索表单字段配置(传入 Search 通用组件自动渲染)
|
||||||
|
* - 主账户名 / 子账户名:input
|
||||||
|
* - 时间范围:daterange
|
||||||
|
*/
|
||||||
|
export const searchOptions: Ref<any[]> | any[] = [
|
||||||
|
// 主账户名
|
||||||
|
{
|
||||||
|
prop: 'mainAccountName',
|
||||||
|
label: '主账户名',
|
||||||
|
type: 'input' as const,
|
||||||
|
placeholder: '请输入主账户名',
|
||||||
|
clearable: true,
|
||||||
|
width: '200px',
|
||||||
|
},
|
||||||
|
// 子账户名
|
||||||
|
{
|
||||||
|
prop: 'subAccountName',
|
||||||
|
label: '子账户名',
|
||||||
|
type: 'input' as const,
|
||||||
|
placeholder: '请输入子账户名',
|
||||||
|
clearable: true,
|
||||||
|
width: '200px',
|
||||||
|
},
|
||||||
|
// 时间范围
|
||||||
|
{
|
||||||
|
prop: 'dateRange',
|
||||||
|
label: '时间日期',
|
||||||
|
type: 'date' as const,
|
||||||
|
dateType: 'daterange' as const,
|
||||||
|
rangeSeparator: '至',
|
||||||
|
startPlaceholder: '开始日期',
|
||||||
|
endPlaceholder: '结束日期',
|
||||||
|
valueFormat: 'YYYY-MM-DD',
|
||||||
|
format: 'YYYY-MM-DD',
|
||||||
|
width: '320px',
|
||||||
|
clearable: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格列配置(传入 Table 通用组件自动渲染)
|
||||||
|
*/
|
||||||
|
export const tableColumns = [
|
||||||
|
// 序号列
|
||||||
|
{ type: 'index', label: '序号', align: 'center' as const, width: 60 },
|
||||||
|
// 主账户名
|
||||||
|
{ prop: 'mainAccountName', label: '主账户名', align: 'center' as const, minWidth: 140 },
|
||||||
|
// 子账户名
|
||||||
|
{ prop: 'subAccountName', label: '子账户名', align: 'center' as const, minWidth: 140 },
|
||||||
|
// 盈利金额
|
||||||
|
{
|
||||||
|
prop: 'profitAmount',
|
||||||
|
label: '盈利金额',
|
||||||
|
align: 'right' as const,
|
||||||
|
minWidth: 140,
|
||||||
|
isNumber: true,
|
||||||
|
},
|
||||||
|
// 单数
|
||||||
|
{
|
||||||
|
prop: 'orderCount',
|
||||||
|
label: '单数',
|
||||||
|
align: 'center' as const,
|
||||||
|
minWidth: 100,
|
||||||
|
isNumber: true,
|
||||||
|
},
|
||||||
|
// 手数
|
||||||
|
{
|
||||||
|
prop: 'lotCount',
|
||||||
|
label: '手数',
|
||||||
|
align: 'center' as const,
|
||||||
|
minWidth: 100,
|
||||||
|
isNumber: true,
|
||||||
|
},
|
||||||
|
// 赢率(百分比展示)
|
||||||
|
{
|
||||||
|
prop: 'winRate',
|
||||||
|
label: '赢率',
|
||||||
|
align: 'center' as const,
|
||||||
|
minWidth: 120,
|
||||||
|
slotName: 'winRate',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
/**
|
||||||
|
* 账户盈利统计 - 表单校验
|
||||||
|
*
|
||||||
|
* 当前搜索字段均为非必填(用户可不指定主/子账户、不指定时间范围进行全量查询),
|
||||||
|
* 因此保留空对象占位,便于后续扩展。
|
||||||
|
*/
|
||||||
|
export const rules = {}
|
||||||
|
|
@ -115,6 +115,12 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/api/, ''),
|
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||||
},
|
},
|
||||||
|
// 将 /config.json 代理到 h5 域名,避免开发环境跨域
|
||||||
|
'/config.json': {
|
||||||
|
target: env.VITE_EXE_DOWNLOAD_BASE_URL || env.VITE_UPDATE_URL,
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue