li.shaoyi 3 лет назад
Родитель
Сommit
973e075684

+ 11 - 2
src/services/go/ermcp/exposure/index.ts

@@ -1,7 +1,7 @@
 import { getUserId } from "@/services/bus/user";
 import { getAreaUserId, getUserAccountType } from "@/services/bus/user";
 import { commonSearch_go } from '@/services/go';
-import { Ermcp3AreaSpot, Ermcp3AreaSpotDetail, Ermcp3AreaSpotDetailReq, Ermcp3ExposureDetail, Ermcp3ExposureReq, ErmcpExposurePostion, ErmcpExposurePostionReq, ErmcpHedgePosition, ErmcpHedgePositionDetail, ErmcpHedgePositionDetailReq, ErmcpRealExposureModel } from '@/services/go/ermcp/exposure/interface';
+import { Ermcp3AreaSpot, Ermcp3AreaSpotDetail, Ermcp3AreaSpotDetailReq, Ermcp3ExposureDetail, Ermcp3ExposureReq, ErmcpExposurePostion, ErmcpExposurePostionReq, ErmcpHedgePosition, ErmcpHedgePositionDetail, ErmcpHedgePositionDetailReq, ErmcpRealExposureModel, ErmcpQueryExposureGoodsReq, ErmcpQueryExposureGoodsRsp } from '@/services/go/ermcp/exposure/interface';
 
 /** ================================= 业务 - 敞口 ================================**/
 
@@ -89,4 +89,13 @@ export function QueryExposureHedgePositionDetail(req: ErmcpHedgePositionDetailRe
     });
 }
 
-
+/**
+ * 查询敞口主力合约
+ * @param req 
+ * @returns 
+ */
+export function queryExposureGoods(req: ErmcpQueryExposureGoodsReq): Promise<ErmcpQueryExposureGoodsRsp[]> {
+    return commonSearch_go('/Ermcp/QueryExposureGoods', req).catch((err) => {
+        throw new Error(`查询敞口主力合约: ${err}`);
+    });
+}

+ 24 - 1
src/services/go/ermcp/exposure/interface.ts

@@ -210,5 +210,28 @@ export interface ErmcpHedgePositionDetail {
     tradetime: string;//时间(成交时间)
 }
 
+/**
+ * 查询敞口主力合约 请求
+ */
+export interface ErmcpQueryExposureGoodsReq {
+    middlegoodsid: number; // 套保商品id
+}
 
-
+/**
+ * 查询敞口主力合约 返回
+ */
+export interface ErmcpQueryExposureGoodsRsp {
+    agreeunit: number;// 合约乘数
+    convertratio: number;// 折算系数
+    enumdicname: string;// 单位名称(交易商品)
+    goodscode: string;// 交易商品代码
+    goodsgroupid: number;// 交易品种id
+    goodsid: number;// 商品Id
+    goodsname: string;// 交易商品名称
+    goodunitid: number;// 交易商品单位id
+    middlegoodsid: number;// 套保商品id
+    middlegoodsname: string;// 套保商品名称
+    middlegoodsswcode: string;// 套保商品代码
+    needarbitrageratio: number;// 应套利比例
+    needhedgeratio: number;// 应套保比例
+}

+ 1 - 1
src/services/proto/futures/interface.ts

@@ -7,7 +7,7 @@ export interface ChannelOrderReq {
     ClientType: number; // uint32 终端类型
     LoginID: number; // uint64 登陆账号
     AccountID: number; // uint64 交易账号
-    GoodsID: number; // uint32 商品ID
+    GoodsID: number | undefined; // uint32 商品ID
     MarketID: number; // uint32 市场ID
     ValidType: number; // int32 有效类型 - 1当日有效
     ChannelOperateType: number; // uint32 操作类型:

+ 13 - 2
src/views/business/exposure/list/realTime/index.vue

@@ -13,6 +13,9 @@
     </template>
     <template #default="{ scroll }">
       <a-table :columns="columns" class="srcollYTable" :pagination="false" :expandedRowKeys="expandedRowKeys" :customRow="Rowclick" :rowKey="(record,index)=>index" :data-source="tableList" :scroll="scroll">
+        <template #expandedRowRender="{ record }">
+          <mtp-table-button class="btn-list-sticky" :buttons="buttons" :record="record" @click="openComponent" />
+        </template>
         <template v-if="isPingAnOem()" #index="{ index }">
           <span>{{ index + 1 }}</span>
         </template>
@@ -41,16 +44,18 @@
       <span>{{ getPlanContractType(record.contracttype) }}</span>
     </template>
   </mtp-table-detail>
+  <component :is="componentId" v-if="componentId" :selectedRow="selectedRow" @cancel="closeComponent"></component>
 </template>
 
 <script lang="ts">
 import MtpTableScroll from '@/common/components/tableScroll/index.vue';
-import { defineComponent, onUnmounted, ref, watch } from 'vue';
+import { defineComponent, onUnmounted, ref, watch, defineAsyncComponent } from 'vue';
 import filterCustomTable from './filter.vue';
 import { QueryActualExposure, QueryActualExposureDetail, QueryAutualExposurePosition } from '@/services/go/ermcp/exposure/index';
 import { ErmcpRealExposureModel } from '@/services/go/ermcp/exposure/interface';
 import { queryTableList } from '@/common/setup/table';
 import { ComposeTableDetailParam } from '@/common/setup/table/interface';
+import { MtpTableButton } from '@/common/export/commonTable';
 import { handleComposeTable_detail } from '@/common/setup/table/compose';
 import { queryResultLoadingAndInfo } from '@/common/methods/request/resultInfo';
 import { EnumRouterName } from '@/common/constants/enumRouterName';
@@ -68,7 +73,9 @@ export default defineComponent({
   components: {
     MtpTableScroll,
     MtpTableDetail,
+    MtpTableButton,
     filterCustomTable,
+    exposure_realtime_index_add: defineAsyncComponent(() => import('@/views/market/futures/compoments/futures-trade/index.vue')), // 下单
   },
   setup() {
     const userId = ref(0);
@@ -78,6 +85,7 @@ export default defineComponent({
     const isStart = ref(false);
     const visible = ref(true); // 控制明细显示/隐藏
     const { loading, tableList, queryTable } = queryTableList<ErmcpRealExposureModel>(true, 2); // 表格列表数据
+    const buttons = getTableButton(['exposure_realtime_index_add']); // 权限按钮
 
     // 获取列表数据
     const queryTableAction = (userid = 0) => {
@@ -135,6 +143,9 @@ export default defineComponent({
       detailTableList, // 明细表头数据
       expandedRowKeys,
       selectedRow,
+      componentId,
+      openComponent,
+      closeComponent,
       Rowclick, // 表格事件
     } = handleComposeTable_detail<ErmcpRealExposureModel>(param);
 
@@ -188,7 +199,7 @@ export default defineComponent({
     }
     watch(selectedRow, () => changeTab(tabIndex.value))
 
-    return { loading, tableList, visible, columns, queryTableAction, updateColumn, columnsDetail, detailTableList, expandedRowKeys, selectedRow, Rowclick, tabList, changeTab, formatNumber, getBizTypeName, getPlanContractType, getLogType, second, counter, isStart, setTimerAction, isPingAnOem };
+    return { loading, openComponent, closeComponent, componentId, tableList, visible, columns, buttons, queryTableAction, updateColumn, columnsDetail, detailTableList, expandedRowKeys, selectedRow, Rowclick, tabList, changeTab, formatNumber, getBizTypeName, getPlanContractType, getLogType, second, counter, isStart, setTimerAction, isPingAnOem };
   },
 });
 </script>

+ 92 - 57
src/views/market/futures/compoments/futures-trade/index.vue

@@ -9,17 +9,17 @@
               <span class="white">{{ selectedRow.contractno }}</span>
             </a-form-item>
             <a-form-item label="账号">
-              <a-select class="inlineFormSelect" placeholder="请选择" v-model:value="formData.AccountID" @change="tradeAccountChange">
+              <a-select class="inlineFormSelect" placeholder="请选择账号" v-model:value="formData.AccountID" @change="tradeAccountChange">
                 <a-select-option v-for="item in futuresAccountList" :value="item.accountid" :key="item.accountid">{{item.accountid}}/{{ item.accountname }}</a-select-option>
               </a-select>
             </a-form-item>
-            <a-form-item label="合约">
-              <a-select class="inlineFormSelect" placeholder="请选择" :filterOption="filterOption" v-model:value="formData.GoodsID" @change="goodsChange" show-search>
+            <a-form-item label="合约" name="GoodsID">
+              <a-select class="inlineFormSelect" placeholder="请选择合约" :filterOption="filterOption" v-model:value="formData.GoodsID" @change="goodsChange" show-search>
                 <a-select-option v-for="item in goodsList" :value="item.goodsid" :key="item.goodsid">{{ item.goodsname }}</a-select-option>
               </a-select>
             </a-form-item>
             <a-form-item label="价格类型">
-              <a-select class="inlineFormSelect" placeholder="请选择" v-model:value="selectedPriceType" @change="priceTypeChange">
+              <a-select class="inlineFormSelect" placeholder="请选择价格类型" v-model:value="selectedPriceType" @change="priceTypeChange">
                 <a-select-option v-for="item in priceTypeList" :value="item.priceType" :key="item.priceType">{{ item.priceName }}</a-select-option>
               </a-select>
             </a-form-item>
@@ -31,7 +31,7 @@
                 <PlusOutlined /> -->
               </template>
               <template v-else>
-                <a-input-number class="commonInput" :value="selectedGoods.last" style="width:100%" disabled />
+                <a-input-number class="commonInput" :value="selectedGoods?.last ?? 0" style="width:100%" disabled />
               </template>
             </a-form-item>
             <a-form-item class="inputIconBox" label="交易数量" name="OrderQty">
@@ -86,6 +86,13 @@ import { Ermcp3SellBuyContract } from '@/services/go/ermcp/purchase/interface';
 import { getGoodsQuoteList } from '@/services/bus/goods';
 import { useTradeAccount } from '@/hooks/account';
 import { message } from 'ant-design-vue';
+import { ErmcpRealExposureModel } from '@/services/go/ermcp/exposure/interface';
+import { queryExposureGoods } from '@/services/go/ermcp/exposure'
+
+// 类型判断
+// function interfaceOf<T>(object: T, key: keyof T): object is T {
+//   return key in object;
+// }
 
 export default defineComponent({
   emits: ['cancel', 'update'],
@@ -93,30 +100,52 @@ export default defineComponent({
   components: { Des, Drawer, PlusOutlined, MinusOutlined },
   props: {
     selectedRow: {
-      type: Object as PropType<GoodsQuote & QueryErmcpTradePositionRsp & Ermcp3SellBuyContract>,
+      type: Object as PropType<GoodsQuote & QueryErmcpTradePositionRsp & Ermcp3SellBuyContract & ErmcpRealExposureModel>,
       default: () => ({}),
     },
   },
   setup(props, context) {
     const { visible, cancel } = _closeModal(context);
-    // 是否套保交易 (根据参数 spotcontractid 来判断)
-    const isHedging = Boolean(props.selectedRow.spotcontractid);
-    // 如果是套保交易,只显示该合约所在的商品组下的合约
-    const goodsList = getGoodsQuoteList().filter((item) => isHedging ? item.goodsgroupid === props.selectedRow.goodsgroupid : true);
-    const getGoods = (id: number) => goodsList.find((item) => item.goodsid === id)!;
+    const { goodsid, spotcontractid, goodsgroupid, MiddleGoodsID } = props.selectedRow;
+    const { rules, formData } = handleForm();
+    const formRef = ref();
+    const loading = ref<boolean>(false);
+
+    // 合约列表
+    const goodsList = ref(getGoodsQuoteList());
     // 当前选中的商品合约
-    const selectedGoods = ref<GoodsQuote>(getGoods(props.selectedRow.goodsid));
+    const selectedGoods = ref<GoodsQuote>();
+    // 是否套保交易 (根据参数 spotcontractid 来判断)
+    const isHedging = Boolean(spotcontractid);
+    // 是否实时敞口 (根据参数 MiddleGoodsID 来判断)
+    const isExposure = Boolean(MiddleGoodsID);
 
-    if (!selectedGoods.value) {
-      message.error('合约不存在,不能交易!');
-      cancel();
+    // 套保交易
+    if (isHedging) {
+      goodsList.value = goodsList.value.filter((e) => e.goodsgroupid === goodsgroupid);
     }
 
-    const { futuresAccountList, tradePositionList, tradeAccount, tradeAccountChange } = useTradeAccount();
-    const { rules, formData } = handleForm(selectedGoods.value);
-    const formRef = ref();
-    const loading = ref<boolean>(false);
+    // 实时敞口
+    if (isExposure) {
+      queryExposureGoods({ middlegoodsid: MiddleGoodsID }).then((res) => {
+        const ids = res.map((e) => e.goodsid);
+        goodsList.value = goodsList.value.filter((e) => ids.includes(e.goodsid));
+      }).catch(() => {
+        goodsList.value = [];
+      })
+    } else {
+      const item = goodsList.value.find((e) => e.goodsid === goodsid);
+      if (!item) {
+        message.error('合约不存在,不能交易!');
+        cancel();
+      } else {
+        selectedGoods.value = item;
+        formData.GoodsID = item.goodsid;
+        formData.MarketID = item.marketid;
+      }
+    }
 
+    const { futuresAccountList, tradePositionList, tradeAccount, tradeAccountChange } = useTradeAccount();
     // 表格选中的 rowKey 数据 :rowKey="(record,index)=>index"
     const selectedRowKeys = ref<number[]>([]);
     // 当前选择的持仓单据
@@ -145,7 +174,6 @@ export default defineComponent({
             onSelectChange([0], result);
             return result;
           }
-          return [];
         } else {
           // 过滤出当前商品的持仓单据
           const result = tradePositionList.filter((item) => item.goodsid === goodsid);
@@ -191,40 +219,43 @@ export default defineComponent({
     const sellPrice = computed(() => getPrice(BuyOrSell.sell));
     // 根据买卖方向返回价格
     function getPrice(direction: BuyOrSell): number {
-      const { last, bid, ask, decimalplace, quoteminunit } = selectedGoods.value;
-      switch (selectedPriceType.value) {
-        // 最新价
-        case 0: {
-          return last;
-        }
-        // 市价
-        case 1: {
-          return direction === BuyOrSell.buy ? ask : bid;
-        }
-        // 对手价
-        case 2: {
-          return direction === BuyOrSell.buy ? ask : bid;
-        }
-        // 限价
-        case 3: {
-          return formData.OrderPrice;
-        }
-        // 超价
-        case 4: {
-          if (ask && bid) {
-            // 系统参数
-            const paramValue = APP.get('systemParams').find((el: Systemparam) => el.paramcode === '148')?.paramvalue ?? '0';
-            // 点数
-            const point = decimalplace > 0 ? -decimalplace * quoteminunit * Number(paramValue) : 1;
-            const num = Math.pow(10, point);
-            return direction === BuyOrSell.buy ? ask + num : bid - num;
+      if (selectedGoods.value) {
+        const { last, bid, ask, decimalplace, quoteminunit } = selectedGoods.value;
+        switch (selectedPriceType.value) {
+          // 最新价
+          case 0: {
+            return last;
+          }
+          // 市价
+          case 1: {
+            return direction === BuyOrSell.buy ? ask : bid;
+          }
+          // 对手价
+          case 2: {
+            return direction === BuyOrSell.buy ? ask : bid;
+          }
+          // 限价
+          case 3: {
+            return formData.OrderPrice;
+          }
+          // 超价
+          case 4: {
+            if (ask && bid) {
+              // 系统参数
+              const paramValue = APP.get('systemParams').find((el: Systemparam) => el.paramcode === '148')?.paramvalue ?? '0';
+              // 点数
+              const point = decimalplace > 0 ? -decimalplace * quoteminunit * Number(paramValue) : 1;
+              const num = Math.pow(10, point);
+              return direction === BuyOrSell.buy ? ask + num : bid - num;
+            }
+            return 0;
+          }
+          default: {
+            return 0;
           }
-          return 0;
-        }
-        default: {
-          return 0;
         }
       }
+      return 0;
     }
 
     // 搜索商品合约
@@ -234,11 +265,13 @@ export default defineComponent({
 
     // 选择商品合约
     function goodsChange(id: number) {
+      const item = goodsList.value.find((e) => e.goodsid === id)!;
       formData.OrderPrice = 0;
       formData.OrderQty = 1;
+      formData.MarketID = item.marketid;
       selectedRowKeys.value = [];
       selectedPosition.value = undefined;
-      selectedGoods.value = getGoods(id);
+      selectedGoods.value = item;
     }
 
     // 选择价格类型
@@ -246,6 +279,7 @@ export default defineComponent({
       // 除了市价,其它价格类型都属于限价
       if (priceType === 1) {
         formData.PriceMode = PriceType.market;
+        formRef.value.clearValidate('OrderPrice');
       } else {
         formData.PriceMode = PriceType.limit;
       }
@@ -267,9 +301,9 @@ export default defineComponent({
       let successMsg = '成功';
       let failMsg = '失败';
 
-      const { saleuserid, spotcontractid, bizsubjectid } = props.selectedRow;
       // 判断是否套保交易
       if (isHedging) {
+        const { saleuserid, spotcontractid, bizsubjectid } = props.selectedRow;
         formData.HedgeFlag = 4;
         formData.SaleUserID = saleuserid;
         formData.SpotContractID = spotcontractid;
@@ -318,7 +352,7 @@ export default defineComponent({
 
           // 判断是否母账号
           if (tradeAccount.value?.ismain) {
-            if (selectedGoods.value.outerdealmode === 3 && curtdpositionenabled > 0) {
+            if (selectedGoods.value?.outerdealmode === 3 && curtdpositionenabled > 0) {
               formData.CloseTodayQty = curtdpositionenabled;
             }
           }
@@ -332,6 +366,7 @@ export default defineComponent({
         });
       });
     }
+
     return {
       submit,
       cancel,
@@ -361,9 +396,9 @@ export default defineComponent({
       minusQty,
       plusQty,
       BuyOrSell,
-    };
-  },
-});
+    }
+  }
+})
 </script>
 
 <style lang="less">

+ 4 - 3
src/views/market/futures/compoments/futures-trade/setup.ts

@@ -57,7 +57,7 @@ export function getColumns() {
     })
 }
 
-export function handleForm(selectedRow: GoodsQuote) {
+export function handleForm() {
     // 数据初始化
     const formData = reactive<ChannelOrderReq>({
         ClientSerialNo: uuidv4(), // string 客户端流水号
@@ -65,8 +65,8 @@ export function handleForm(selectedRow: GoodsQuote) {
         ClientType: 4, // uint32 终端类型
         LoginID: geLoginID_number()!, // uint64 登陆账号
         AccountID: getSelectedAccountId(), // uint64 交易账号
-        GoodsID: selectedRow?.goodsid, // uint32 商品ID
-        MarketID: selectedRow?.marketid, // uint32 市场ID
+        GoodsID: undefined, // uint32 商品ID
+        MarketID: 0, // uint32 市场ID
         ValidType: 1, // int32 有效类型 - 1当日有效
         ChannelOperateType: 1, // uint32 操作类型:
         ChannelOrderSrc: 1, // uint32 单据来源委托来源 -  1:客户端 2:管理端 3:风控服务
@@ -91,6 +91,7 @@ export function handleForm(selectedRow: GoodsQuote) {
     }
 
     const rules = {
+        GoodsID: [{ required: true, type: 'number', message: '请选择合约' }],
         OrderPrice: [{ required: true, trigger: 'blur', type: 'number', validator: verifyOrderPrice, }],
         OrderQty: [{ required: true, trigger: 'blur', type: 'number', message: '请输入交易数量' }],
     }