This commit is contained in:
李远 2026-03-20 10:42:51 +08:00
parent 43906865b7
commit e4b4f5ddb9
16 changed files with 284 additions and 186 deletions

View File

@ -78,21 +78,21 @@ const staticMenuList = ref<ElMenuItem[]>(
icon: 'HomeFilled' // 对应 <HomeFilled /> 图标
},
//
{
id: 2,
label: '模板配置',
path: '2', // el-sub-menu index="2"
icon: 'List', // <List />
children: [
{
id: 21,
label: '头寸模板',
path: '2-1', // el-menu-item index="2-1"
icon: ''
//
}
]
},
// {
// id: 2,
// label: '',
// path: '2', // el-sub-menu index="2"
// icon: 'List', // <List />
// children: [
// {
// id: 21,
// label: '',
// path: '2-1', // el-menu-item index="2-1"
// icon: ''
// //
// }
// ]
// },
//
{
id: 3,
@ -197,18 +197,18 @@ const staticMenuList = ref<ElMenuItem[]>(
// path: '/funding/flowDetail', // index="6-6"
// icon: ''
// },
{
id: 67,
label: '收款账户列表',
path: '/funding/receiverAccount', // index="6-7"
icon: ''
},
{
id: 68,
label: '存款渠道',
path: '/funding/depositChannel', // index="6-8"
icon: ''
}
// {
// id: 67,
// label: '',
// path: '/funding/receiverAccount', // index="6-7"
// icon: ''
// },
// {
// id: 68,
// label: '',
// path: '/funding/depositChannel', // index="6-8"
// icon: ''
// }
]
},
//

View File

@ -63,7 +63,7 @@ const routes = [
{
path: 'menu',
name: 'Menu',
component: () => import('@/views/system/menuManagement/index.vue'),
component: () => import('@/views/system/menu/index.vue'),
meta: {
title: '菜单管理', // 左侧菜单显示的标题
}
@ -71,7 +71,7 @@ const routes = [
{
path: 'role',
name: 'Role',
component: () => import('@/views/system/roleManagement/index.vue'),
component: () => import('@/views/system/role/index.vue'),
meta: {
title: '角色管理', // 左侧菜单显示的标题
}

View File

@ -0,0 +1,131 @@
// src/utils/excel.ts
import { ElMessageBox, ElNotification, ElMessage } from 'element-plus'
import * as XLSX from 'xlsx'
import { saveAs } from 'file-saver'
import dayjs from 'dayjs'
/**
* Excel
* @param options
* @returns Promise<void>
*/
export const exportToExcel = async (options: {
/** 表格数据(数组) */
tableData: any[]
/** 列配置(包含 label: 表头文字, prop: 字段名) */
columns: Array<{ label: string; prop: string }>
/** 弹窗标题(默认:导出表格) */
title?: string
/** 默认文件名前缀(默认:导出数据) */
defaultFileNamePrefix?: string
/** 时间格式化格式默认YYYYMMDDHHmmss避免特殊字符 */
timeFormat?: string
/** 通知自定义类名(可选) */
notificationClass?: string
/** 工作表名称默认Sheet1 */
sheetName?: string
}) => {
// 解构配置项并设置默认值
const {
tableData,
columns,
title = '导出表格',
defaultFileNamePrefix = '导出数据',
// 优化:默认格式去掉空格和冒号,避免文件名非法
timeFormat = 'YYYY-MM-DD',
notificationClass = 'global-notification',
sheetName = 'Sheet1'
} = options
try {
// 1. 校验表格数据
if (!tableData || tableData.length === 0) {
ElNotification({
message: '暂无表格数据可导出!',
type: 'error',
duration: 2000,
customClass: notificationClass
})
return
}
// 2. 弹出输入框让用户输入文件名(兼容不同版本返回值)
const rawFileName = await ElMessageBox.prompt(
'请输入导出文件名称(会自动附带时间)', // 提示用户会附带时间
title,
{
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPlaceholder: '例如:转账明细',
// 输入框校验:允许空输入(空输入时用默认前缀+时间)
inputValidator: (value) => {
if (value && !value.trim()) {
return '文件名不能仅包含空格!'
}
return true
},
inputErrorMessage: '文件名不能仅包含空格!'
}
)
// 核心修复:兼容 string / { value: string } 两种返回格式
let fileName = ''
if (typeof rawFileName === 'string') {
fileName = rawFileName
} else if (rawFileName && typeof (rawFileName as { value: string }).value === 'string') {
fileName = (rawFileName as { value: string }).value
}
// 3. 处理文件名:强制拼接时间(核心修改点)
const timeStr = dayjs().format(timeFormat)
// 优先级:用户输入的名称 → 默认前缀;无论哪种都拼接时间
const fileNamePrefix = fileName.trim() || defaultFileNamePrefix
const finalFileName = `${fileNamePrefix}_${timeStr}`
// 4. 构建带表头的Excel数据
const exportData = tableData.map(item => {
const row: Record<string, any> = {}
columns.forEach(header => {
row[header.label] = item[header.prop] || ''
})
return row
})
// 5. 创建工作表(包含表头)
const ws = XLSX.utils.json_to_sheet(exportData, {
header: columns.map(h => h.label), // 指定表头顺序
skipHeader: false // 不跳过表头(关键!保证表头显示)
})
// 6. 创建工作簿并添加工作表
const wb = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(wb, ws, sheetName)
// 7. 生成Excel二进制数据
const wbout = XLSX.write(wb, {
bookType: 'xlsx',
bookSST: false,
type: 'array'
})
// 8. 触发下载(拼接.xlsx后缀
saveAs(
new Blob([wbout], { type: 'application/octet-stream' }),
`${finalFileName}.xlsx`
)
// 9. 导出成功提示
ElNotification({
message: `文件「${finalFileName}.xlsx」导出成功`,
type: 'success',
duration: 2000,
customClass: notificationClass
})
} catch (e: any) {
// 取消导出时不提示错误
if (e !== 'cancel') {
ElMessage.error('导出失败,请重试!')
console.error('Excel 导出失败:', e)
}
}
}

View File

@ -280,6 +280,16 @@ const handleChange = (row: any) => {
dialogTitle.value = '修改客户'
dialogType.value = 'change'
useId.value = row.id
clientForm.value = {
//
feeTemplate: row.wallet_capital.fee_setting_id,
//
riskTemplate: row.wallet_capital.risk_control_id,
userName: row.username,
status: row.status,
isUpgrade: 1,
} as any
console.log('rowrowrowrowrowrowrowrowrow', clientForm.value)
getOptions()
}

View File

@ -72,7 +72,7 @@ export const clientAddFormItem = [
export const clientChangeFormItem = [
{ prop: 'feeTemplate', label: '手续费模板', type: 'select' as const, placeholder: '选择手续费模板' },
{ prop: 'riskTemplate', label: '风控模板', type: 'select' as const, placeholder: '选择风控模板' },
{ prop: 'changeName', label: '客户名称', type: 'input' as const, placeholder: '客户名称' },
{ prop: 'userName', label: '客户名称', type: 'input' as const, placeholder: '客户名称' },
{ prop: 'isUpgrade', label: '是否升级为代理', type: 'radio' as const, placeholder: '是否升级为代理', labelWidth: '140px' },
{ prop: 'status', label: '选择状态', type: 'radio' as const, placeholder: '请选择状态', labelWidth: '140px' },
]

View File

@ -3,7 +3,7 @@
<div class="bar">
<el-button type="primary" @click="handleAdd">添加</el-button>
<el-button type="primary" @click="handleModify">编辑</el-button>
<el-button type="danger" @click="handleEnable">启用</el-button>
<el-button type="primary" @click="handleEnable">启用</el-button>
<el-button type="danger" @click="handleDisable">禁用</el-button>
<el-button type="primary" @click="handleExport">导出</el-button>
</div>

View File

@ -4,12 +4,6 @@
<TransferSearch :search-form="searchForm" :search-options="searchOptions" @search="handleSearch"
@reset="handleReset">
<template #button="{ form }">
<el-button type="primary" @click="handlePass()">
通过
</el-button>
<el-button type="primary" @click="handleReject()">
驳回
</el-button>
<el-button type="primary" @click="handleExport()">
导出
</el-button>
@ -31,20 +25,19 @@ import TransferTable from '@/components/common/Table.vue'
import TransferPagination from '@/components/common/Pagination.vue'
//
import { search, tableColumns } from './options'
import { rules } from './rules'
//
import { getTransferDetailListApi } from '@/api/modules/funding/transferDetail'
//
import * as XLSX from 'xlsx'
import { saveAs } from 'file-saver'
import {exportToExcel} from '@/utils/exportToExcel'
/**
* @description: 定义搜索数据
*/
const searchForm = ref({
transferAccount: '',
level: '',
transferTime: '',
})
const searchOptions = ref(search)
/**
@ -59,9 +52,6 @@ const tableRef = ref()
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)
/**
* @description: 定义公共数据处理方法
*/
/**
* @description: 定义公共请求数据方法
*/
@ -70,9 +60,9 @@ const getTransferDetailList = async () => {
page: currentPage.value,
page_size: pageSize.value,
type: '',
start_time: '2026-02-26 02:25:08',
end_time: '2026-03-26 02:25:08',
user_name: '',
start_time: searchForm.value.transferTime?.[0] || '2026-02-20 0:00:00',
end_time: searchForm.value.transferTime?.[1] || '2026-03-20 23:59:59',
user_name: searchForm.value.transferAccount || '',
order_id: '',
}
const data: any = await getTransferDetailListApi(params)
@ -80,26 +70,20 @@ const getTransferDetailList = async () => {
tableTree.value = data.data.map((item: any) => ({
//
index: item.id,
//ID
orderId: item.order_id,
//
transferAccount: item.user.username,
//
agentName: '',
//
transferAccountName: item.to_user_id,
transferAccountName: item.to,
//
transferType: '',
transferType: item.type === 1 ? '内部' : '三方',
//
transferAmount: item.amount,
//
transferCurrency: '',
//
arrivalAmount: item.amount,
//
arrivalCurrency: '',
//
type: '',
//
status: '',
}))
//
@ -120,90 +104,21 @@ const handleSearch = async () => {
getTransferDetailList()
}
const handleReset = async () => {
getTransferDetailList()
}
const handlePass = async () => {
getTransferDetailList()
}
const handleReject = async () => {
searchForm.value.transferAccount = ''
searchForm.value.level = ''
searchForm.value.transferTime = ''
getTransferDetailList()
}
/**
* @description: 导出表格带表头+自定义文件名
*/
const handleExport = async () => {
try {
// 1.
const fileName = await ElMessageBox.prompt(
'请输入导出文件名称', //
'导出表格', //
{
confirmButtonText: '确定',
cancelButtonText: '取消',
inputComponent: ElInput,
//
inputValue: `转账明细_${new Date().getTime()}`,
//
inputValidator: (value) => {
if (!value.trim()) {
return '文件名不能为空!'
}
return true
},
inputErrorMessage: '文件名不能为空!'
}
)
// 2.
const finalFileName = fileName.trim() || `转账明细_${new Date().getTime()}`
// 3.
if (tableTree.value.length === 0) {
ElMessage.warning('暂无表格数据可导出!')
return
}
// 4. Excel
// 4.1 prop
const exportData = tableTree.value.map(item => {
const row: Record<string, any> = {}
tableColumnsOptions.value.forEach(header => {
row[header.label] = item[header.prop] || ''
await exportToExcel({
tableData: tableTree.value,
columns: tableColumnsOptions.value,
title: '导出转账详情',
defaultFileNamePrefix: '转账详情',
})
return row
})
// 4.2
const ws = XLSX.utils.json_to_sheet(exportData, {
header: tableColumnsOptions.value.map(h => h.label), //
skipHeader: false //
})
// 4.3 簿
const wb = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(wb, ws, '转账明细')
// 5. Excel
const wbout = XLSX.write(wb, {
bookType: 'xlsx',
bookSST: false,
type: 'array'
})
// 6. .xlsx
saveAs(
new Blob([wbout], { type: 'application/octet-stream' }),
`${finalFileName}.xlsx`
)
ElMessage.success(`文件「${finalFileName}.xlsx」导出成功`)
} catch (e: any) {
//
if (e !== 'cancel') {
ElMessage.error('导出失败,请重试!')
console.error('导出表格失败:', e)
}
}
}
/**
@ -213,3 +128,17 @@ const handlePaginationChange = async () => {
getTransferDetailList()
}
</script>
<style scoped lang="scss">
.search-container {
:deep(.el-form) {
.el-form-item {
flex: none;
.el-select {
width: 160px;
}
}
}
}
</style>

View File

@ -11,18 +11,27 @@ export const search = [
},
//级别
{
label: '级别',
label: '转账类型',
prop: 'level',
type: 'select' as const,
placeholder: '请选择级别',
placeholder: '请选择转账类型',
options: [
{ label: '客户', value: '1' },
{ label: '三方', value: '2' },
]
},
//全部
//选择时间
{
label: '级别',
prop: 'level',
type: 'select' as const,
placeholder: '全部',
}
label: '时间',
prop: 'transferTime',
type: 'date' as const,
dateType: 'datetimerange' as const,
placeholder: '请选择转账时间',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
},
]
// 表格配置
@ -39,14 +48,6 @@ export const tableColumns = [
{ label: '转账类型', prop: 'transferType' },
//转账金额
{ label: '转账金额', prop: 'transferAmount' },
//转账币种
{ label: '转账币种', prop: 'transferCurrency' },
// 到账金额
{ label: '到账金额', prop: 'arrivalAmount' },
//到账币种
{ label: '到账币种', prop: 'arrivalCurrency' },
//类型
{ label: '类型', prop: 'type' },
//状态
{ label: '状态', slotName: 'status' },
]

View File

@ -30,8 +30,8 @@
<NoticePagination v-model="currentPage" v-model:pageSizeValue="pageSize" :total="total"
@pagination-change="handlePaginationChange" />
<!-- dialog -->
<NoticeDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" :show-footer="dialogType !== 'view'" @confirm="handleSubmit"
@cancel="handleCancel" @close="handleClose">
<NoticeDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType"
:show-footer="dialogType !== 'view'" @confirm="handleSubmit" @cancel="handleCancel" @close="handleClose">
<NoticeForm :form-data="noticeForm" :form-rules="noticeRules" :form-items="noticeFormItem"
:options-map="noticeFormOptionsMap" ref="noticeFormRef" v-if="dialogType !== 'view'">
<template #noticeContent="{ formData }">
@ -156,8 +156,6 @@ const dialogType = ref('')
const noticeForm = ref({
noticeName: '',
noticeContent: '',
publishStartTime: '',
publishEndTime: '',
status: 1,
id: 0,
})
@ -166,8 +164,8 @@ const noticeRules = ref(rules())
const noticeFormItem = ref(formItem)
const noticeFormOptionsMap = ref({
status: [
{ label: '发布', value: 2 },
{ label: '草稿', value: 1 },
{ label: '发布', value: 2 },
],
})
@ -226,6 +224,12 @@ const handleCreate = () => {
dialogVisible.value = true
dialogTitle.value = '新建公告'
dialogType.value = 'create'
noticeForm.value = {
noticeName: '',
noticeContent: '',
status: 1,
id: 0,
} as any
}
/**
@ -259,12 +263,10 @@ const handleEdit = (row: any) => {
dialogType.value = 'edit'
noticeForm.value = {
noticeName: row.noticeName,
noticeContent: row.noticeContent,
publishStartTime: row.startDateTime,
publishEndTime: row.endDateTime,
status: row.status,
id: row.id,
}
} as any
valueHtml.value = row.noticeContent
}
//
const handleDelete = async (row: any) => {
@ -298,7 +300,7 @@ const handleSubmit = async () => {
await createNoticeApi(params)
dialogVisible.value = false
getNoticeList()
}else {
} else {
noticeForm.value.noticeContent = valueHtml.value
const params = {
id: noticeForm.value.id,
@ -357,8 +359,9 @@ const handleCreated = (editor: any) => {
margin-left: 0px;
}
}
.view-content {
h2{
h2 {
margin-bottom: 10px;
}
}

View File

@ -1,38 +1,53 @@
import type { FormRules } from 'element-plus'
/**
*
* @param fieldName
* @returns
* + 5
* @param fieldName
* @returns
*/
const validateMaxFiveDecimals = (fieldName: string) => {
return (rule: any, value: any, callback: any) => {
if (value !== undefined && value !== null && value !== '') {
const decimalPart = value.toString().split('.')[1]
// 1. 空值处理undefined/null/空字符串直接通过如需必填可单独加required规则
if (value === undefined || value === null || value === '') {
return callback()
}
// 2. 先校验是否为有效数字(覆盖字符串数字、纯数字、负数等场景)
const num = Number(value)
if (isNaN(num)) {
return callback(new Error(`${fieldName}必须输入有效数字`))
}
// 3. 校验小数位是否超过5位
const valueStr = value.toString()
// 处理科学计数法(如 1e5的情况转为普通数字字符串
if (valueStr.includes('e') || valueStr.includes('E')) {
return callback(new Error(`${fieldName}不支持科学计数法格式,请输入普通数字`))
}
const decimalPart = valueStr.split('.')[1]
if (decimalPart && decimalPart.length > 5) {
callback(new Error(`${fieldName}小数位不能超过5位`))
} else {
callback()
return callback(new Error(`${fieldName}小数位不能超过5位`))
}
} else {
// 4. 所有校验通过
callback()
}
}
}
export const rules = (): FormRules => {
return {
// 名称
varietyName: [
{ required: true, message: '请输入名称', trigger: ['blur'] }
{ required: true, message: '请输入名称', trigger: ['blur', 'change'] }
],
// 编号
varietyCode: [
{ required: true, message: '请输入编号', trigger: ['blur'] }
{ required: true, message: '请输入编号', trigger: ['blur', 'change'] }
],
// 所属交易组
tradeGroup: [
{ required: true, message: '请输入所属交易组', trigger: ['blur'] }
{ required: true, message: '请输入所属交易组', trigger: ['blur', 'change'] }
],
// 最大点差
maxPoint: [
@ -42,49 +57,58 @@ export const rules = (): FormRules => {
// 最小点差
minPoint: [
{ required: true, message: '请输入最小点差', trigger: ['blur', 'change'] },
{ validator: validateMaxFiveDecimals('最小点差'), trigger: ['blur', 'change'] }
{ validator: validateMaxFiveDecimals('最小点差')}
],
// 合约大小
contractSize: [
{ required: true, message: '请输入合约大小', trigger: ['blur', 'change'] },
{ validator: validateMaxFiveDecimals('合约大小'), trigger: ['blur', 'change'] }
{ validator: validateMaxFiveDecimals('合约大小')}
],
// 多头库存费
longStockFee: [
{ required: true, message: '请输入多头库存费', trigger: ['blur', 'change'] },
{ validator: validateMaxFiveDecimals('多头库存费'), trigger: ['blur', 'change'] }
{ validator: validateMaxFiveDecimals('多头库存费')}
],
// 空头库存费
shortStockFee: [
{ required: true, message: '请输入空头库存费', trigger: ['blur', 'change'] },
{ validator: validateMaxFiveDecimals('空头库存费'), trigger: ['blur', 'change'] }
{ validator: validateMaxFiveDecimals('空头库存费')}
],
// 预付款对冲
prepaymentHedge: [
{ required: true, message: '请输入预付款对冲', trigger: ['blur', 'change'] },
{ validator: validateMaxFiveDecimals('预付款对冲'), trigger: ['blur', 'change'] }
{ validator: validateMaxFiveDecimals('预付款对冲')}
],
// 预付款百分比
prepaymentPercent: [
{ required: true, message: '请输入预付款百分比', trigger: ['blur', 'change'] },
{ validator: validateMaxFiveDecimals('预付款百分比'), trigger: ['blur', 'change'] }
{ validator: validateMaxFiveDecimals('预付款百分比')}
],
// 小数位
decimalPlaces: [
{ required: true, message: '请输入小数位', trigger: ['blur', 'change'] },
{ validator: validateMaxFiveDecimals('小数位'), trigger: ['blur', 'change'] }
{ validator: validateMaxFiveDecimals('小数位')}
],
// 点差小数位
pointDecimalPlaces: [
{ required: true, message: '请输入点差小数位', trigger: ['blur', 'change'] },
{ validator: validateMaxFiveDecimals('点差小数位'), trigger: ['blur', 'change'] }
{ validator: validateMaxFiveDecimals('点差小数位')}
],
// 止盈水平
profitLevel: [
{ required: true, message: '请输入止盈水平', trigger: ['blur', 'change'] },
{ validator: validateMaxFiveDecimals('止盈水平'), trigger: ['blur', 'change'] }
]
{ validator: validateMaxFiveDecimals('止盈水平')}
],
// 单位
unit: [
{ required: true, message: '请输入单位', trigger: ['blur', 'change'] }
],
// 最小波动
minFlux: [
{ required: true, message: '请输入最小波动', trigger: ['blur', 'change'] },
{ validator: validateMaxFiveDecimals('最小波动')}
],
}
}