This commit is contained in:
李远 2026-03-18 14:43:24 +08:00
parent 59fd39341b
commit bb9134f78d
11 changed files with 348 additions and 159 deletions

View File

@ -4,6 +4,10 @@ import { request } from '@/api';
export async function getVarietyListApi(params: any): Promise<any> {
return request.get<any>('/contractVariety/getList', {...params})
}
//获取交易组列表
export async function getTradeGroupListApi(): Promise<any> {
return request.get<any>('/contractVariety/getGroupList')
}
//创建合约品种
export async function createVarietyApi(params: any): Promise<any> {
@ -11,6 +15,6 @@ export async function createVarietyApi(params: any): Promise<any> {
}
//编辑合约品种
export async function updateVarietyApi(params: any): Promise<any> {
export async function editVarietyApi(params: any): Promise<any> {
return request.post<any>('/contractVariety/editVariety', {...params})
}

View File

@ -1,5 +1,6 @@
<template>
<el-dialog :model-value="visible" :title="title" :width="width" :destroy-on-close="destroyOnClose"
:fullscreen="fullscreen"
@close="handleClose" v-bind="$attrs">
<!-- 对话框内容插槽 -->
<slot />
@ -22,6 +23,7 @@ interface Props {
destroyOnClose?: boolean
type?: string
showFooter?: boolean
fullscreen?: boolean
}
interface Emits {
@ -32,9 +34,10 @@ interface Emits {
}
const props = withDefaults(defineProps<Props>(), {
width: '600px',
width: '750px',
destroyOnClose: true,
showFooter: true,
fullscreen: false,
})
const emit = defineEmits<Emits>()

View File

@ -1,15 +1,19 @@
<template>
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-bind="$attrs">
<!-- 动态表单项 -->
<el-row :gutter="props.rowGutter || 0">
<template v-for="(item, index) in formItems" :key="index">
<el-form-item :label="item.label" :prop="item.prop" :required="item.required" label-position="left" :label-width="item.labelWidth || '100px'" :class="item.class">
<el-col :span="props.colSpan || 24">
<el-form-item :label="item.label" :prop="item.prop" :required="item.required" label-position="left"
:label-width="item.labelWidth || '100px'" :class="item.class">
<!-- 输入框 -->
<el-input v-if="item.type === 'input'" v-model="formData[item.prop]" :placeholder="item.placeholder"
:disabled="item.disabled" clearable />
<el-input v-if="item.type === 'input'" v-model="formData[item.prop]"
:placeholder="item.placeholder" :disabled="item.disabled" clearable />
<!-- 下拉选择框 -->
<el-select v-else-if="item.type === 'select'" v-model="formData[item.prop]"
:placeholder="item.placeholder" :disabled="item.disabled" clearable style="width: 100%" @change="handleItemChange(item.prop, formData[item.prop])">
:placeholder="item.placeholder" :disabled="item.disabled" clearable style="width: 100%"
@change="handleItemChange(item.prop, formData[item.prop])">
<el-option v-for="option in props.optionsMap?.[item.prop] || []" :key="option.value"
:label="option.label" :value="option.value">
<template v-if="option.icon" #label>
@ -24,8 +28,9 @@
</el-select>
<!-- 数字输入框 -->
<el-input-number v-else-if="item.type === 'number'" v-model="formData[item.prop]" :min="item.min"
:max="item.max" :placeholder="item.placeholder" :disabled="item.disabled" style="width: 100%" />
<el-input-number v-else-if="item.type === 'number'" v-model="formData[item.prop]"
:min="item.min" :max="item.max" :placeholder="item.placeholder" :disabled="item.disabled"
style="width: 100%" />
<!-- 单选框组 -->
<el-radio-group v-else-if="item.type === 'radio'" v-model="formData[item.prop]">
@ -42,11 +47,14 @@
{{ option.label }}
</el-checkbox>
</el-checkbox-group>
<el-switch v-else-if="item.type === 'switch'" v-model="formData[item.prop]" />
<el-switch v-else-if="item.type === 'switch'" v-model="formData[item.prop]" :active-value="1"
:inactive-value="2" />
<!-- 自定义插槽 -->
<slot v-else :name="item.slotName" :formData="formData" />
</el-form-item>
</el-col>
</template>
</el-row>
</el-form>
</template>
@ -67,6 +75,7 @@ interface FormItem {
labelPosition?: 'left' | 'right'
labelWidth?: string
class?: string
}
interface Props {
@ -78,6 +87,8 @@ interface Props {
value: string | number
icon?: Component
}>>
rowGutter?: number // 0
colSpan?: number // 24
}
const formRef = ref<FormInstance>()

View File

@ -24,24 +24,7 @@
:start-placeholder="item.startPlaceholder || '开始日期'"
:end-placeholder="item.endPlaceholder || '结束日期'"
:value-format="item.valueFormat || 'YYYY-MM-DD'" :clearable="item.clearable ?? true"
:disabled-date="item.disabledDate" :locale="'zhCn'"
/>
<!-- 单选框组 -->
<el-radio-group v-else-if="item.type === 'radio'" v-model="searchForm[item.prop]"
:disabled="item.disabled">
<el-radio v-for="option in item.options" :key="option.value" :label="option.value">
{{ option.label }}
</el-radio>
</el-radio-group>
<!-- 复选框组 -->
<el-checkbox-group v-else-if="item.type === 'checkbox'" v-model="searchForm[item.prop]"
:disabled="item.disabled">
<el-checkbox v-for="option in item.options" :key="option.value" :label="option.value">
{{ option.label }}
</el-checkbox>
</el-checkbox-group>
:disabled-date="item.disabledDate" :locale="'zhCn'" />
</el-form-item>
<!-- 自定义插槽项 -->
@ -69,13 +52,11 @@
</template>
<script setup lang="ts">
import { Search } from '@element-plus/icons-vue'
//
interface SearchOption {
prop: string; //
label: string; //
type: 'input' | 'select' | 'date' | 'radio' | 'checkbox'; //
type: 'input' | 'select' | 'date'; //
slot?: boolean; //
placeholder?: string; //
clearable?: boolean; //

View File

@ -62,6 +62,7 @@ const handleRowClick = (row: any) => {
<style scoped lang="scss">
.table-container {
max-width: calc(100vw - 200px);
max-height: calc(100vh - 180px);
overflow: auto;
}

View File

@ -368,4 +368,8 @@ const handleClose = () => {
}
</script>
<style scoped lang="scss"></style>
<style scoped lang="scss">
.table-container{
max-width: 100%;
}
</style>

View File

@ -30,7 +30,7 @@
@cancel="handleCancel" @close="handleClose">
<ClientForm :form-data="clientForm" :form-rules="clientFormRules" :form-items="clientFormOptions"
:options-map="clientFormOptionsMap" ref="clientFormRef">
<template #phoneOrEmail="{ formData }">
<template #phoneOrEmail="{ formData }" style="margin-left: 0;">
<div class="phone-email">
<div class="tab-btn">
<el-button :type="activeTab === 'email' ? 'primary' : 'default'" size="small"
@ -338,6 +338,10 @@ const handleClose = () => {
</script>
<style scoped lang="scss">
:deep(.el-form-item__content) {
margin-left: 0 !important;
}
.phone-email {
display: flex;
flex: 1;
@ -366,7 +370,7 @@ const handleClose = () => {
}
.el-select {
width: 110px;
width: 120px;
}
}
}

View File

@ -554,6 +554,9 @@ const handleClose = async () => {
</script>
<style scoped lang="scss">
:deep(.el-form-item__content){
margin-left: 0 !important;
}
.market {
.search-container {
:deep(.el-form) {
@ -592,9 +595,12 @@ const handleClose = async () => {
}
.el-select {
width: 110px;
width: 120px;
}
}
}
}
.table-container{
max-width: 100%;
}
</style>

View File

@ -4,7 +4,7 @@
<VarietySearch :search-form="searchForm" :search-options="searchOptions" @search="handleSearch"
@reset="handleReset">
<template #button="{ form }">
<el-button type="primary" @click="handleEdit()">
<el-button type="primary" :disabled="!selectedRow" @click="handleEdit()">
编辑
</el-button>
<el-button type="primary" @click="handleAdd()">
@ -12,25 +12,24 @@
</el-button>
</template>
</VarietySearch>
<!-- 表格 -->
<VarietyTable :table-data="varietyTree" :columns="varietyTableColumnsOptions" row-key="id">
<VarietyTable :table-data="varietyTree" :columns="varietyTableColumnsOptions" row-key="id"
@row-click="handleRowClick">
<template #onlineStatus="{ row }">
<el-switch :model-value="row.onlineStatus === 1" :active-value="true" :inactive-value="false" />
</template>
<template #tradeStatus="{ row }">
<el-switch :model-value="row.status === 1" :active-value="true" :inactive-value="false" />
</template>
</VarietyTable>
<!-- 分页 -->
<VarietyPagination v-model="currentPage" v-model:pageSizeValue="pageSize" :total="total"
@pagination-change="handlePaginationChange" />
</div>
<!-- dialog -->
<VarietyDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" :fullscreen="dialogFullscreen" :width="dialogWidth"
@confirm="handleSubmit" @cancel="handleCancel" @close="handleClose">
<VarietyDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" @confirm="handleSubmit"
@cancel="handleCancel" @close="handleClose">
<VarietyForm :form-data="varietyForm" :form-rules="varietyFormRules" :form-items="dialogFormItemOptions"
:options-map="varietyFormOptionsMap" ref="varietyFormRef" inline></VarietyForm>
:options-map="varietyFormOptionsMap" :row-gutter="rowGutter" :col-span="colSpan" ref="varietyFormRef">
</VarietyForm>
</VarietyDialog>
<!-- dialog表格 -->
@ -40,17 +39,15 @@
//
import VarietySearch from '@/components/common/Search.vue'
import VarietyTable from '@/components/common/Table.vue'
import VarietyPagination from '@/components/common/Pagination.vue'
import VarietyDialog from '@/components/common/Dialog.vue'
import VarietyForm from '@/components/common/Form.vue'
//
import { search, tableColumns, editDialogFormItem } from './options'
import { rules } from './rules'
//
import { getVarietyListApi } from '@/api/modules/trade/variety'
//
import { getVarietyListApi, getTradeGroupListApi, editVarietyApi, createVarietyApi } from '@/api/modules/trade/variety'
/**
* @description: 定义搜索数据
@ -66,99 +63,119 @@ const searchOptions = ref(search)
const varietyTree = ref([])
const varietyTableColumnsOptions = ref(tableColumns)
/**
* @description: 定义分页数据
*/
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)
/**
* @description: 定义弹窗数据
*/
const dialogWidth = ref('700px')
const dialogVisible = ref(false)
const dialogTitle = ref('')
const dialogType = ref('')
const dialogFullscreen = ref(false)
/**
* @description: 定义弹窗表数据
* @description: 定义弹窗表数据
*/
const varietyForm = ref({
//
varietyName: '',
//
varietyCode: '',
// 线
onlineStatus: 1,
status: 1,
currency: '',
minPoint: 0,
maxPoint: 0,
contractSize: 0,
minFlux: 0,
//
tradeStatus: 1,
//
minPoint: '',
//
maxPoint: '',
// ()
contractSize: '',
//
minFlux: '',
//
unit: '',
//
tradeGroup: '',
longStockFee: 0,
shortStockFee: 0,
profitLevel: 0,
//
longStockFee: '',
//
shortStockFee: '',
//
profitLevel: '',
id: '',
groupId: '',
//
prepaymentHedge: '',
//
prepaymentPercent: '',
//
decimalPlaces: '',
//
pointDecimalPlaces: '',
})
const varietyFormRules = ref(rules)
const dialogFormItemOptions = computed(() => dialogType.value === 'edit' ? editDialogFormItem : [])
const varietyFormRef = ref<any>()
const varietyFormRules = ref(rules())
const dialogFormItemOptions = ref(editDialogFormItem)
const varietyFormOptionsMap = ref({
currency: [],
tradeGroup: [],
})
/**
* @description: 定义公共数据处理方法
*/
//
const selectedRow = ref<any>(null)
const rowGutter = ref(20)
const colSpan = ref(12)
/**
* @description: 定义公共请求数据方法
*/
//
const getVarietyList = async () => {
const params = {
page: currentPage.value,
page_size: pageSize.value,
user_name: searchForm.value.varietyName,
name: searchForm.value.varietyName,
}
const data = await getVarietyListApi(params)
//
console.log('data', data)
//
varietyTree.value = data.map((item: any) => ({
...item,
id: item.id,
varietyName: item.name,
varietyCode: item.code,
onlineStatus: item.online_status,
// tradeStatus: item.status,
currency: 'currency',
tradeStatus: item.status,
minPoint: item.point_diff,
maxPoint: item.point_diff_max,
contractSize: 'contractSize',
contractSize: item.multiplier,
minFlux: item.undulate_min,
unit: item.unit,
tradeGroup: item.group.name,
longStockFee: item.fee_inventory_long,
shortStockFee: item.fee_inventory_short,
profitLevel: item.stop_loss_level
profitLevel: item.stop_loss_level,
}))
//
total.value = data.total
currentPage.value = data.current_page
pageSize.value = Number(data.per_page)
}
//
const getTradeGroupList = async () => {
const data = await getTradeGroupListApi()
varietyFormOptionsMap.value.tradeGroup = data.map((item: any) => ({
label: item.name,
value: item.id,
}))
}
/**
* @description: 页面加载完成需要请求的数据
*/
onMounted(() => {
getVarietyList()
getTradeGroupList()
})
/**
* @description: 定义搜索方法
*/
const handleSearch = async () => {
getVarietyList()
}
const handleReset = () => {
searchForm.value = {
@ -166,31 +183,95 @@ const handleReset = () => {
}
handleSearch()
}
console.log('varietyForm.value)', varietyForm.value)
const handleEdit = () => {
getTradeGroupList()
const row = selectedRow.value;
varietyForm.value = {
...row,
id: row.id,
groupId: row.group.id,
//
prepaymentHedge: row.bail_hedge,
//
prepaymentPercent: row.bail_percent,
//
decimalPlaces: row.decimal_place,
//
pointDecimalPlaces: row.point_diff_decimal_place,
} as any
//
dialogVisible.value = true
dialogTitle.value = '编辑合约品种'
dialogType.value = 'edit'
}
const handleAdd = () => {
dialogVisible.value = true
dialogTitle.value = '添加合约品种'
dialogType.value = 'add'
}
const handleEdit = () => {
dialogVisible.value = true
dialogTitle.value = '编辑合约品种'
dialogType.value = 'edit'
varietyForm.value = {
varietyName: '',
varietyCode: '',
onlineStatus: 1,
tradeStatus: 1,
minPoint: '',
maxPoint: '',
contractSize: '',
minFlux: '',
unit: '',
tradeGroup: '',
longStockFee: '',
shortStockFee: '',
profitLevel: '',
id: '',
groupId: '',
prepaymentHedge: '',
prepaymentPercent: '',
decimalPlaces: '',
pointDecimalPlaces: '',
}
}
/**
* @description: 定义表格方法
*/
/**
* @description: 定义分页方法
*/
const handlePaginationChange = async (page: { currentPage: number, pageSize: number }) => {
currentPage.value = page.currentPage
pageSize.value = page.pageSize
await getVarietyList()
}
/**
* @description: 定义弹窗方法
*/
const handleSubmit = async () => {
await varietyFormRef.value?.validate()
const params = {
id: varietyForm.value.id,
code: varietyForm.value.varietyCode,
name: varietyForm.value.varietyName,
group_id: varietyForm.value.groupId,
point_diff: varietyForm.value.minPoint,
fee_inventory_short: varietyForm.value.shortStockFee,
status: varietyForm.value.tradeStatus,
bail_hedge: varietyForm.value.prepaymentHedge,
bail_percent: varietyForm.value.prepaymentPercent,
decimal_place: varietyForm.value.decimalPlaces,
stop_loss_level: varietyForm.value.profitLevel,
point_diff_max: varietyForm.value.maxPoint,
fee_inventory_long: varietyForm.value.longStockFee,
point_diff_decimal_place: varietyForm.value.pointDecimalPlaces,
unit: varietyForm.value.unit,
multiplier: varietyForm.value.contractSize,
undulate_min: varietyForm.value.minFlux,
online_status: varietyForm.value.onlineStatus,
}
if (dialogType.value === 'edit') {
console.log(params)
await editVarietyApi(params)
getVarietyList()
} else {
await createVarietyApi({...params, group_id: varietyForm.value.tradeGroup})
getVarietyList()
}
dialogVisible.value = false
}
const handleCancel = () => {
@ -203,6 +284,14 @@ const handleClose = () => {
/**
* @description: 定义弹窗表格方法
*/
//
const handleRowClick = (row: any) => {
if (!row) {
selectedRow.value = null
return
}
selectedRow.value = row
}
</script>
<style scoped lang="scss">

View File

@ -20,8 +20,6 @@ export const tableColumns = [
{ slotName: 'onlineStatus', label: '上下线状态', align: 'center' as const },
// 交易状态
{ slotName: 'tradeStatus', label: '交易状态', align: 'center' as const },
// 币种
{ prop: 'currency', label: '币种', align: 'center' as const },
// 最小点差
{ prop: 'minPoint', label: '最小点差', align: 'center' as const },
// 最大点差
@ -50,29 +48,35 @@ export const editDialogFormItem = [
{ prop: 'varietyName', label: '名称', type: 'input' as const, placeholder: '请输入名称' },
// 编号
{ prop: 'varietyCode', label: '编号', type: 'input' as const, placeholder: '请输入编号' },
// 上下线状态
{ prop: 'onlineStatus', label: '上下线状态', type: 'switch' as const, class: 'online-status-switch' },
// 交易状态
{ prop: 'tradeStatus', label: '交易状态', type: 'switch' as const, class: 'trade-status-switch' },
// 币种
{ prop: 'currency', label: '币种', type: 'input' as const, placeholder: '请输入币种' },
// 最小点差
{ prop: 'minPoint', label: '最小点差', type: 'input' as const, placeholder: '请输入最小点差' },
// 所属交易组
{ prop: 'tradeGroup', label: '所属交易组', type: 'select' as const, placeholder: '请输入所属交易组' },
// 最大点差
{ prop: 'maxPoint', label: '最大点差', type: 'input' as const, placeholder: '请输入最大点差' },
// 最小点差
{ prop: 'minPoint', label: '最小点差', type: 'input' as const, placeholder: '请输入最小点差' },
// 合约大小
{ prop: 'contractSize', label: '合约大小', type: 'input' as const, placeholder: '请输入合约大小' },
// 最小波动
{ prop: 'minFlux', label: '最小波动', type: 'input' as const, placeholder: '请输入最小波动' },
// 单位
{ prop: 'unit', label: '单位', type: 'input' as const, placeholder: '请输入单位' },
// 所属交易组
{ prop: 'tradeGroup', label: '所属交易组', type: 'input' as const, placeholder: '请输入所属交易组' },
// 多头库存费
{ prop: 'longStockFee', label: '多头库存费', type: 'input' as const, placeholder: '请输入多头库存费' },
// 空头库存费
{ prop: 'shortStockFee', label: '空头库存费', type: 'input' as const, placeholder: '请输入空头库存费' },
// 预付款对冲
{ prop: 'prepaymentHedge', label: '预付款对冲', type: 'input' as const, placeholder: '请输入预付款对冲' },
// 预付款百分比
{ prop: 'prepaymentPercent', label: '预付款百分比', type: 'input' as const, placeholder: '请输入预付款百分比' },
// 小数位
{ prop: 'decimalPlaces', label: '小数位', type: 'input' as const, placeholder: '请输入小数位' },
// 点差小数位
{ prop: 'pointDecimalPlaces', label: '点差小数位', type: 'input' as const, placeholder: '请输入点差小数位' },
// 止盈水平
{ prop: 'profitLevel', label: '止盈水平', type: 'input' as const, placeholder: '请输入止盈水平' },
// 单位
{ prop: 'unit', label: '单位', type: 'input' as const, placeholder: '请输入单位' },
// 最小波动
{ prop: 'minFlux', label: '最小波动', type: 'input' as const, placeholder: '请输入最小波动' },
// 上下线状态
{ prop: 'onlineStatus', label: '上下线状态', type: 'switch' as const},
// 交易状态
{ prop: 'tradeStatus', label: '交易状态', type: 'switch' as const},
]

View File

@ -1,8 +1,90 @@
import type { FormRules } from 'element-plus'
export const rules = (): FormRules => {
return {
name: [
{ required: true, message: '请输入名称', trigger: ['blur'] }
],
/**
*
* @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]
if (decimalPart && decimalPart.length > 5) {
callback(new Error(`${fieldName}小数位不能超过5位`))
} else {
callback()
}
} else {
callback()
}
}
}
export const rules = (): FormRules => {
return {
// 名称
varietyName: [
{ required: true, message: '请输入名称', trigger: ['blur'] }
],
// 编号
varietyCode: [
{ required: true, message: '请输入编号', trigger: ['blur'] }
],
// 所属交易组
tradeGroup: [
{ required: true, message: '请输入所属交易组', trigger: ['blur'] }
],
// 最大点差
maxPoint: [
{ required: true, message: '请输入最大点差', trigger: ['blur', 'change'] },
{ validator: validateMaxFiveDecimals('最大点差'), trigger: ['blur', 'change'] }
],
// 最小点差
minPoint: [
{ required: true, message: '请输入最小点差', trigger: ['blur', 'change'] },
{ validator: validateMaxFiveDecimals('最小点差'), trigger: ['blur', 'change'] }
],
// 合约大小
contractSize: [
{ required: true, message: '请输入合约大小', trigger: ['blur', 'change'] },
{ validator: validateMaxFiveDecimals('合约大小'), trigger: ['blur', 'change'] }
],
// 多头库存费
longStockFee: [
{ required: true, message: '请输入多头库存费', trigger: ['blur', 'change'] },
{ validator: validateMaxFiveDecimals('多头库存费'), trigger: ['blur', 'change'] }
],
// 空头库存费
shortStockFee: [
{ required: true, message: '请输入空头库存费', trigger: ['blur', 'change'] },
{ validator: validateMaxFiveDecimals('空头库存费'), trigger: ['blur', 'change'] }
],
// 预付款对冲
prepaymentHedge: [
{ required: true, message: '请输入预付款对冲', trigger: ['blur', 'change'] },
{ validator: validateMaxFiveDecimals('预付款对冲'), trigger: ['blur', 'change'] }
],
// 预付款百分比
prepaymentPercent: [
{ required: true, message: '请输入预付款百分比', trigger: ['blur', 'change'] },
{ validator: validateMaxFiveDecimals('预付款百分比'), trigger: ['blur', 'change'] }
],
// 小数位
decimalPlaces: [
{ required: true, message: '请输入小数位', trigger: ['blur', 'change'] },
{ validator: validateMaxFiveDecimals('小数位'), trigger: ['blur', 'change'] }
],
// 点差小数位
pointDecimalPlaces: [
{ required: true, message: '请输入点差小数位', trigger: ['blur', 'change'] },
{ validator: validateMaxFiveDecimals('点差小数位'), trigger: ['blur', 'change'] }
],
// 止盈水平
profitLevel: [
{ required: true, message: '请输入止盈水平', trigger: ['blur', 'change'] },
{ validator: validateMaxFiveDecimals('止盈水平'), trigger: ['blur', 'change'] }
]
}
}