foreign-admin-ui/src/views/mould/risk/index.vue

405 lines
12 KiB
Vue

<template>
<div class="risk table_form-container">
<div class="bar">
<span>风控管理</span>
<el-button type="primary" @click="handleAddRisk" v-auth="'riskControl_createRiskControl'">添加</el-button>
<el-button type="primary" @click="handleModifyRisk" v-auth="'riskControl_editRiskControlSetting'">修改</el-button>
<el-button type="danger" @click="handleChangeStatus(2)"
v-auth="'riskControl_editRiskControlSetting'">停用</el-button>
<el-button type="primary" @click="handleChangeStatus(1)"
v-auth="'riskControl_editRiskControlSetting'">启用</el-button>
<el-button type="primary" :loading="syncLoading" @click="handleSyncRiskControl()"
v-auth="'riskControl_editRiskControlSetting'">
同步风控模板
</el-button>
</div>
<!-- 表格 -->
<div class="risk-table-content">
<RiskTableSwitch :columns="tableColumnsSwitch" :show-column-setting="false" :table-data="tableTreeSwitch"
class="table-switch" @row-click="handleRowClick" row-key="id" highlight-current-row
:current-row-key="selectedRowId" :loading="loading">
<template #status="{ row }">
<span>{{ row.status === 1 ? '启用' : '停用' }}</span>
</template>
</RiskTableSwitch>
<RiskTableContent :show-column-setting="false" :columns="tableColumnsContent" :table-data="currentContent"
class="table-content" row-key="id" :loading="detailLoading">
</RiskTableContent>
</div>
<!-- dialog -->
<RiskDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" :button-loading="submitLoading"
@confirm="handleSubmit" @cancel="handleCancel" @close="handleClose">
<RiskForm :form-data="dialogForm" :form-rules="riskRules" :form-items="RiskFormItems"
:options-map="riskFormOptionsMap" :row-gutter="rowGutter" :col-span="colSpan" ref="riskFormRef" />
</RiskDialog>
</div>
</template>
<script setup lang="ts">
defineOptions({
name: 'Risk'
})
// 组件
import { usePageLoading, useButtonLoading } from '@/composables/useLoading'
const { loading, startLoading, stopLoading } = usePageLoading()
const { buttonLoading: submitLoading, startButtonLoading: startSubmitLoading, stopButtonLoading: stopSubmitLoading } = useButtonLoading()
import RiskTableSwitch from '@/components/common/Table.vue'
import RiskTableContent from '@/components/common/Table.vue'
import RiskDialog from '@/components/common/Dialog.vue'
import RiskForm from '@/components/common/Form.vue'
import { ElMessage } from 'element-plus'
// 配置以及表单验证
import { tableColumnsSwitch, tableColumnsContent, formItem } from './options'
import { rules } from './rules'
// 接口
import { getRiskListApi, addRiskApi, editRiskApi, syncRiskControlApi } from '@/api/modules/risk'
// 公用方法
/**
* @description: 定义表格数据
*/
const tableTreeSwitch = ref([])
const tableTreeContent = ref([])
// 当前选中的行ID
const selectedRowId = ref<number | null>(null)
// 新增:存储接口返回的原始完整数据
const riskOriginData = ref<any[]>([])
// 当前选中的行数据
const selectedRow = ref<any>({})
// 右侧要展示的内容数据
const currentContent = ref<any>([])
/**
* @description: 定义弹窗数据
*/
const dialogVisible = ref(false)
const dialogTitle = ref('')
const dialogType = ref('')
/**
* @description: 定义弹窗表单数据
*/
const dialogForm = ref({
id: null,
name: '',
status: 1,
// 是否默认
isDefault: 1,
maxOpenPosition: null,
maxPositionPerSymbol: null,
maxTotalPosition: null,
strongCloseRatio: null,
maxWithdrawalAmount: null,
isWithdrawalAllowed: 1,
withdrawalTimeSetting: '',
isWithdrawalAuditRequired: 1,
buildCloseTime: null,
minWithdrawalAmount: null,
slippage: null,
minDepositAmount: null,
})
const riskRules = ref(rules())
const riskFormRef = ref()
const detailLoading = ref(false)
const RiskFormItems = ref(formItem)
const riskFormOptionsMap = ref({
// 状态
status: [
{ label: '启用', value: 1 },
{ label: '停用', value: 2 },
],
// 是否默认
isDefault: [
{ label: '是', value: 1 },
{ label: '否', value: 2 },
],
// 是否允许出金
isWithdrawalAllowed: [
{ label: '是', value: 1 },
{ label: '否', value: 2 },
],
// 是否需要出金审核
isWithdrawalAuditRequired: [
{ label: '是', value: 1 },
{ label: '否', value: 2 },
],
})
const rowGutter = ref(20)
const colSpan = ref(12)
/**
* @description: 定义公共数据处理方法
*/
/**
* @description: 定义请求数据方法
*/
const getRiskList = async () => {
startLoading()
const data: any = await getRiskListApi()
stopLoading()
// 保存原始数据
riskOriginData.value = data
// 处理数据
tableTreeSwitch.value = data.map((item: any) => ({
id: item.id,
name: item.name,
status: item.status,
}))
tableTreeContent.value = data.map((item: any) => ({
id: item.id,
isDefault: item.is_default,
// 单笔开仓最大数量
maxOpenPosition: item.max_opening_quantity_per_transaction,
// 单品种最大持仓数量
maxPositionPerSymbol: item.max_holding_quantity_per_variety,
// 总品种最大持仓数量
maxTotalPosition: item.max_holding_quantity_per_transaction,
// 强平比例
strongCloseRatio: item.strong_to_average_ratio,
// 每次出金最大金额
maxWithdrawalAmount: item.max_withdrawal_amount_per_transaction,
// 是否允许出金
isWithdrawalAllowed: item.withdraw_status === 1 ? '是' : '否',
// 出金时间设置
withdrawalTimeSetting: item.withdraw_time,
// 是否需要出金审核
// isWithdrawalAuditRequired: item.withdraw_is_check === 1 ? '是' : '否',
// 建仓多少秒不能平仓
buildCloseTime: item.open_close_time,
// 每次出金最小金额
minWithdrawalAmount: item.min_withdrawal_amount_per_transaction,
// 滑点
slippage: item.slippage,
// 每次入金最小金额
minDepositAmount: item.min_recharge_amount_per_transaction,
}))
// 初始化默认选中第一条数据
if (tableTreeSwitch.value.length > 0) {
handleRowClick(tableTreeSwitch.value[0])
}
}
/**
* @description: 页面加载完成需要请求的数据
*/
onMounted(() => {
getRiskList()
})
// 同步风控模板
const syncLoading = ref(false)
const handleSyncRiskControl = async () => {
syncLoading.value = true
try {
await syncRiskControlApi()
} finally {
syncLoading.value = false
}
}
/**
* @description: 定义bar方法
*/
const handleAddRisk = () => {
dialogVisible.value = true
dialogTitle.value = '添加'
dialogType.value = 'add'
dialogForm.value = {
id: null,
name: '',
status: 1,
// 是否默认
isDefault: 1,
maxOpenPosition: null,
maxPositionPerSymbol: null,
maxTotalPosition: null,
strongCloseRatio: null,
maxWithdrawalAmount: null,
isWithdrawalAllowed: 1,
withdrawalTimeSetting: '',
isWithdrawalAuditRequired: 1,
buildCloseTime: null,
minWithdrawalAmount: null,
slippage: null,
minDepositAmount: null,
}
}
const handleModifyRisk = () => {
dialogVisible.value = true
dialogTitle.value = '修改'
dialogType.value = 'edit'
dialogForm.value = {
name: selectedRow.value.name,
status: selectedRow.value.status,
// 是否默认
isDefault: selectedRow.value.is_default,
maxOpenPosition: selectedRow.value.max_opening_quantity_per_transaction,
maxPositionPerSymbol: selectedRow.value.max_holding_quantity_per_variety,
maxTotalPosition: selectedRow.value.max_holding_quantity_per_transaction,
strongCloseRatio: selectedRow.value.strong_to_average_ratio,
maxWithdrawalAmount: selectedRow.value.max_withdrawal_amount_per_transaction,
isWithdrawalAllowed: selectedRow.value.withdraw_status,
withdrawalTimeSetting: selectedRow.value.withdraw_time.split('-'),
isWithdrawalAuditRequired: selectedRow.value.withdraw_is_check,
buildCloseTime: selectedRow.value.open_close_time,
minWithdrawalAmount: selectedRow.value.min_withdrawal_amount_per_transaction,
slippage: selectedRow.value.slippage,
minDepositAmount: selectedRow.value.min_recharge_amount_per_transaction,
} as any
}
const handleChangeStatus = async (status: any) => {
dialogTitle.value = '停用'
dialogType.value = 'status'
await editRiskApi({ ...selectedRow.value, status: status })
getRiskList()
}
/**
* @description: 定义表格方法
*/
const handleRowClick = (row: any) => {
detailLoading.value = true
// 从原始数据中匹配完整的行信息
const originRow = riskOriginData.value.find((item) => item.id === row.id)
if (originRow) {
selectedRow.value = originRow // 保存完整原始数据
} else {
selectedRow.value = row
}
selectedRowId.value = row.id
// 找到对应的数据并更新右侧内容
const targetItem: any = tableTreeContent.value.find((item: any) => item.id === row.id)
const fields = [
{ text: '单笔开仓最大数量' + targetItem.maxOpenPosition },
{ text: '单品种最大持仓数量' + targetItem.maxPositionPerSymbol },
{ text: '总品种最大持仓数量' + targetItem.maxTotalPosition },
{ text: '强平比例' + targetItem.strongCloseRatio },
{ text: '每次出金最大金额' + targetItem.maxWithdrawalAmount },
{ text: '是否允许出金' + targetItem.isWithdrawalAllowed },
{ text: '出金时间设置' + targetItem.withdrawalTimeSetting },
// { text: '是否需要出金审核' + targetItem.isWithdrawalAuditRequired },
{ text: '建仓多少秒不能平仓' + targetItem.buildCloseTime },
{ text: '每次出金最小金额' + targetItem.minWithdrawalAmount },
{ text: '滑点' + targetItem.slippage },
{ text: '每次入金最小金额' + targetItem.minDepositAmount },
]
if (targetItem) {
currentContent.value = fields
}
detailLoading.value = false
}
/**
* @description: 定义弹窗方法
*/
const handleSubmit = async () => {
startSubmitLoading()
try {
await riskFormRef.value?.validate()
const params = {
name: dialogForm.value.name,
status: dialogForm.value.status,
// 是否默认
is_default: dialogForm.value.isDefault,
// 单笔开仓最大数量
max_opening_quantity_per_transaction: dialogForm.value.maxOpenPosition,
// 单品种最大持仓数量
max_holding_quantity_per_variety: dialogForm.value.maxPositionPerSymbol,
// 总品种最大持仓数量
max_holding_quantity_per_transaction: dialogForm.value.maxTotalPosition,
// 强平比例
strong_to_average_ratio: dialogForm.value.strongCloseRatio,
// 每次出金最大金额
max_withdrawal_amount_per_transaction: dialogForm.value.maxWithdrawalAmount,
// 是否允许出金
withdraw_status: dialogForm.value.isWithdrawalAllowed,
// 出金时间设置
withdraw_time: Array.from(dialogForm.value.withdrawalTimeSetting || []).join('-'),
// 是否需要出金审核
// withdraw_is_check: dialogForm.value.isWithdrawalAuditRequired,
// 建仓多少秒不能平仓
open_close_time: dialogForm.value.buildCloseTime,
// 每次出金最小金额
min_withdrawal_amount_per_transaction: dialogForm.value.minWithdrawalAmount,
// 滑点
slippage: dialogForm.value.slippage,
// 每次入金最小金额
min_recharge_amount_per_transaction: dialogForm.value.minDepositAmount,
}
// 添加风险控制
if (dialogType.value === 'add') {
// 添加风险控制
await addRiskApi(params)
getRiskList()
dialogVisible.value = false
dialogTitle.value = '添加'
dialogType.value = 'add'
} else {
// 编辑风险控制
await editRiskApi({ ...params, id: selectedRow.value.id })
getRiskList()
dialogVisible.value = false
dialogTitle.value = '编辑'
dialogType.value = 'edit'
}
} finally {
stopSubmitLoading()
}
}
const handleCancel = () => {
dialogVisible.value = false
}
const handleClose = () => {
dialogVisible.value = false
}
</script>
<style scoped>
.risk {
.bar {
margin-bottom: 10px;
display: flex;
align-items: center;
span {
margin-right: 10px;
}
}
.risk-table-content {
flex: 1;
display: flex;
gap: 10px;
.table-switch {
flex-basis: 20%;
min-width: 200px;
height: 100%;
:deep(.el-table__row.current-row) {
background-color: #ecf5ff !important;
}
}
}
.table-content {
flex: 1;
height: 100%;
.risk-content-group {
display: flex;
flex-direction: column;
gap: 5px;
}
}
}
</style>