Handy_Cao 1 éve
szülő
commit
52198e7741

+ 7 - 0
public/config/router.json

@@ -170,6 +170,13 @@
                     {
                         "authType": 2,
                         "sort": 2,
+                        "title": "持仓明细",
+                        "code": "bottom_pricing_detail",
+                        "component": "views/footer/pricing/detail/index.vue"
+                    },
+                    {
+                        "authType": 2,
+                        "sort": 2,
                         "title": "委托",
                         "code": "bottom_pricing_order",
                         "component": "views/footer/pricing/order/index.vue"

+ 152 - 0
src/packages/pc/views/footer/pricing/detail/components/transfer/index.vue

@@ -0,0 +1,152 @@
+<!-- 挂牌点价-持仓明细-转让 -->
+<template>
+    <app-drawer title="转让" :width="800" v-model:show="show" :loading="loading" :refresh="refresh">
+        <el-form ref="formRef" class="el-form--horizontal" label-width="120px" :model="formData" :rules="formRules">
+            <el-form-item label="商品代码/名称">
+                <span>{{ selectedRow.goodscode }}/{{ selectedRow.goodsname }}</span>
+            </el-form-item>
+            <el-form-item label="持仓方向">
+                <span>{{ getBuyOrSellName(selectedRow.buyorsell) }}</span>
+            </el-form-item>
+            <el-form-item label="持仓金额">
+                <span>{{ formatDecimal(selectedRow.holderamount) }}</span>
+            </el-form-item>
+            <el-form-item label="持仓量">
+                <span>{{ selectedRow.holderqty }}</span>
+            </el-form-item>
+            <el-form-item label="持仓价">
+                <span>{{ formatDecimal(selectedRow.holderprice, selectedRow.decimalplace) }}</span>
+            </el-form-item>
+            <el-form-item label="冻结量">
+                <span>{{ selectedRow.freezeqty }}</span>
+            </el-form-item>
+            <el-form-item label="参考损益">
+                <span :class="handlePriceColor(closepl)">{{ formatDecimal(closepl, selectedRow.decimalplace) }}</span>
+            </el-form-item>
+            <el-form-item label="可用量">
+                <span>{{ maxQty }}</span>
+            </el-form-item>
+            <el-form-item prop="OrderPrice" label="转让价格">
+                <el-input-number placeholder="请输入价格" v-model="formData.OrderPrice" :step="quote?.decimalvalue"
+                    :precision="quote?.decimalplace" />
+            </el-form-item>
+            <el-form-item prop="OrderQty" label="转让量">
+                <div class="g-qty-group">
+                    <el-input-number placeholder="请输入数量" v-model="formData.OrderQty" :precision="0" :max="maxQty"
+                        :min="0" />
+                </div>
+            </el-form-item>
+        </el-form>
+        <template #footer>
+            <el-button type="info" @click="onCancel(false)">取消</el-button>
+            <el-button type="primary" @click="onCloseSumit">提交</el-button>
+        </template>
+    </app-drawer>
+</template>
+
+<script lang="ts" setup>
+import { ref, PropType, computed, onMounted } from 'vue'
+import { ElMessage, FormInstance, FormRules } from 'element-plus'
+import { useOrder } from '@/business/trade'
+import { formatDecimal, handlePriceColor, handleRequestBigNumber } from '@/filters'
+import { getBuyOrSellName, BuyOrSell } from '@/constants/order'
+import { useFuturesStore, usePositionStore } from '@/stores'
+import { EBuildType, EDelistingType, EListingSelectType, EPriceMode, EValidType } from '@/constants/client'
+import AppDrawer from '@pc/components/base/drawer/index.vue'
+
+const props = defineProps({
+    selectedRow: {
+        type: Object as PropType<Model.TradeHolderDetailRsp>,
+        required: true
+    }
+})
+
+const futuresStore = useFuturesStore()
+const positionStore = usePositionStore()
+const quote = futuresStore.getGoodsQuote(props.selectedRow.goodscode)
+
+// 可用数量
+const maxQty = computed(() => {
+    const record = positionStore.positionList.find((e) => e.goodsid === props.selectedRow.goodsid && e.buyorsell === props.selectedRow.buyorsell)
+    const qty = props.selectedRow.holderqty - props.selectedRow.freezeqty
+    return Math.min(record?.enableqty ?? 0, qty)
+})
+
+// 损益
+const closepl = computed(() => {
+    const { presettle = 0, last = 0 } = quote.value ?? {}
+    const { holderqty, holderamount, agreeunit, buyorsell } = props.selectedRow
+    const price = last || presettle // 没有最新价取昨结价
+    // 计算市值 = 现价 * 数量 * 合约单位
+    const marketValue = price ? price * holderqty * agreeunit : 0
+
+    return price ? (marketValue - holderamount) * (buyorsell === BuyOrSell.Buy ? 1 : -1) : 0
+})
+
+const { formSubmit, formData, loading } = useOrder()
+const show = ref(true)
+const refresh = ref(false)
+const formRef = ref<FormInstance>()
+
+const formRules: FormRules = {
+    OrderPrice: [{
+        message: '请输入转让价格',
+        validator: () => {
+            return !!formData.OrderPrice
+        }
+    }],
+    OrderQty: [{
+        message: '请输入转让量',
+        validator: () => {
+            return !!formData.OrderQty
+        }
+    }],
+}
+
+const onCancel = (isRefresh = false) => {
+    show.value = false
+    refresh.value = isRefresh
+}
+
+const onCloseSumit = () => {
+    formRef.value?.validate((valid) => {
+        if (valid) {
+            const { marketid, goodsid, buyorsell, tradeid } = props.selectedRow
+            /// 市场ID
+            formData.Header = { MarketID: marketid, GoodsID: goodsid }
+            formData.MarketID = marketid
+            formData.PriceMode = EPriceMode.PRICEMODE_LIMIT
+            formData.BuyOrSell = buyorsell === BuyOrSell.Buy ? BuyOrSell.Sell : BuyOrSell.Buy
+            formData.GoodsID = goodsid
+            formData.ListingSelectType = EListingSelectType.LISTINGSELECTTYPE_DELISTINGTHENLISTING
+            formData.DelistingType = EDelistingType.DELISTINGTYPE_PRICE
+            formData.BuildType = EBuildType.BUILDTYPE_CLOSE
+            formData.TimevalidType = EValidType.VALIDTYPE_DR
+            formData.OperateType = 24
+            formData.RelatedID = handleRequestBigNumber(tradeid)
+
+            formSubmit().then(() => {
+                ElMessage.success('挂牌成功')
+                onCancel(true)
+            }).catch((err) => {
+                ElMessage.error('挂牌失败:' + err)
+            })
+        }
+    })
+}
+
+onMounted(() => {
+    const { bid, ask, presettle = 0 } = quote.value ?? {}
+    switch (props.selectedRow.buyorsell) {
+        case BuyOrSell.Buy:
+            formData.OrderPrice = ask || presettle
+            break
+        case BuyOrSell.Sell:
+            formData.OrderPrice = bid || presettle
+            break
+        default:
+            formData.OrderPrice = presettle
+    }
+    formData.OrderQty = maxQty.value
+})
+</script>

+ 66 - 0
src/packages/pc/views/footer/pricing/detail/index.vue

@@ -0,0 +1,66 @@
+<!-- 挂牌点价-持仓明细 -->
+<template>
+    <app-table :data="tableList" v-model:columns="tableColumns">
+        <!-- 方向 -->
+        <template #buyorsell="{ value }">
+            {{ getBuyOrSellName(value) }}
+        </template>
+        <!-- 操作 -->
+        <template #operate="{ row }">
+            <el-button type="danger" size="small" @click="showComponent('transfer', row)">转让</el-button>
+        </template>
+        <template #append v-if="showLoadMore">
+            <el-button size="small" plain @click="loadMore">加载更多</el-button>
+        </template>
+        <template #footer>
+            <component ref="componentRef" v-bind="{ selectedRow }" :is="componentMap.get(componentId)"
+                @closed="closeComponent" v-if="componentId" />
+        </template>
+    </app-table>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, defineAsyncComponent, onUnmounted } from 'vue'
+import { getBuyOrSellName } from '@/constants/order'
+import { useRequest } from '@/hooks/request'
+import { useComponent } from '@/hooks/component'
+import { useLocalPagination } from '@/hooks/pagination'
+import { queryTradeHolderDetail } from '@/services/api/order'
+import { useTableColumnsStore } from '@/stores'
+import AppTable from '@pc/components/base/table/index.vue'
+import eventBus from '@/services/bus'
+
+const componentMap = new Map<string, unknown>([
+    ['transfer', defineAsyncComponent(() => import('./components/transfer/index.vue'))],
+])
+
+const { getTableColumns } = useTableColumnsStore()
+const { tableList, showLoadMore, initTableData, loadMore } = useLocalPagination<Model.TradeHolderDetailRsp>()
+const selectedRow = shallowRef<Model.TradeHolderDetailRsp>()
+const tableColumns = shallowRef<Model.TableColumn[]>([])
+
+const { run } = useRequest(queryTradeHolderDetail, {
+    params: {
+        trademodes: ' 10'
+    },
+    onSuccess: (res) => initTableData(res.data)
+})
+
+const { componentRef, componentId, openComponent, closeComponent } = useComponent(() => {
+    run()
+})
+
+// 接收头寸变化通知通知
+const posChangedNtf = eventBus.$on('PosChangedNtf', () => run())
+
+const showComponent = (componentName: string, row: Model.TradeHolderDetailRsp) => {
+    selectedRow.value = row
+    openComponent(componentName)
+}
+
+getTableColumns('order-detail', true).then((res) => {
+    tableColumns.value = res
+})
+
+onUnmounted(() => posChangedNtf.cancel())
+</script>

+ 0 - 137
src/packages/pc/views/footer/pricing/position/close/index.vue

@@ -1,137 +0,0 @@
-<!-- 掉期市场-持仓汇总-平仓 -->
-<template>
-    <app-drawer :title="`${position.goodscode}/${position.goodsname}`" v-model:show="show" :width="1100" :loading="loading"
-        :refresh="refresh">
-        <app-table :data="computedList" v-model:columns="tableColumns" :loading="loading" :row-key="rowKey"
-            :expand-row-keys="expandKeys" @row-click="rowClick">
-            <!-- 方向 -->
-            <template #buyorsell="{ value }">
-                {{ getBuyOrSellName(value) }}
-            </template>
-            <!-- 持仓金额 -->
-            <template #holderamount="{ value }">
-                {{ formatDecimal(value) }}
-            </template>
-            <!-- 可用数量 -->
-            <template #enableqty="{ row }">
-                {{ row.holderqty - row.freezeqty }}
-            </template>
-            <!-- 到期日 -->
-            <template #expiredate="{ value }">
-                {{ formatDate(value, 'YYYY/MM/DD') }}
-            </template>
-            <!-- 平仓盈亏 -->
-            <template #closepl="{ row }">
-                <span :class="handlePriceColor(row.closepl)">{{ formatDecimal(row.closepl, quote?.decimalplace) }}</span>
-            </template>
-            <!-- 操作 -->
-            <template #operate="{ row }">
-                <div class="buttonbar" v-if="useStore.userType === 5 && [1, 3].includes(quote?.goodstradetype ?? 0)">
-                    <el-button type="danger" size="small" @click="onCloseSubmit(row)">平仓</el-button>
-                </div>
-                <span v-else>--</span>
-            </template>
-        </app-table>
-        <template #footer>
-            <el-button type="info" @click="show = false">取消</el-button>
-        </template>
-    </app-drawer>
-</template>
-
-<script lang="ts" setup>
-import { shallowRef, ref, PropType, computed } from 'vue'
-import { ElMessage, ElMessageBox } from 'element-plus'
-import { useComposeTable } from '@pc/components/base/table'
-import { useRequest } from '@/hooks/request'
-import { useHolderClose } from '@/business/trade'
-import { queryTradeHolderDetail } from '@/services/api/order'
-import { formatDate, formatDecimal, handlePriceColor, handleRequestBigNumber, round } from '@/filters'
-import { getBuyOrSellName } from '@/constants/order'
-import { ETradeMode } from '@/constants/client'
-import AppDrawer from '@pc/components/base/drawer/index.vue'
-import AppTable from '@pc/components/base/table/index.vue'
-import { useFuturesStore, useUserStore } from '@/stores'
-import { BuyOrSell } from '@/constants/order'
-
-const props = defineProps({
-    position: {
-        type: Object as PropType<Model.TradePositionRsp>,
-        required: true,
-    }
-})
-
-const { rowKey, expandKeys, rowClick } = useComposeTable<Model.TradeHolderDetailRsp>({ rowKey: 'tradeid' })
-
-const { holderCloseSubmit, formData } = useHolderClose()
-const show = ref(true)
-const refresh = ref(false)
-const useStore = useUserStore()
-const futuresStore = useFuturesStore()
-
-const refQuote = futuresStore.getGoodsQuote(props.position.refgoodscode)
-const quote = futuresStore.getGoodsQuote(props.position.goodscode)
-
-const { dataList, loading, run } = useRequest(queryTradeHolderDetail, {
-    params: {
-        /// 交易模式, 格式 1,2,3
-        trademodes: ETradeMode.TRADEMODE_TJMD.toString(),
-        /// marketid
-        marketids: props.position.marketid.toString(),
-        /// 商品id
-        goodsid: props.position.goodsid,
-        /// 买卖方向 0-买 1-卖
-        buyorsell: props.position.buyorsell
-    }
-})
-
-const computedList = computed(() => dataList.value.map((item) => {
-    const last = refQuote.value?.last || quote.value?.last || 0 // 有 refgoodscode 的优先取 refgoodscode 行情
-    const presettle = quote.value?.presettle || 0
-    const price = last || presettle // 没有最新价取昨结价
-
-    // 计算市值 = 现价 * 数量 * 合约单位
-    const marketValue = price ? price * item.holderqty * item.agreeunit : 0
-    const roundedMarketValue = round(marketValue, quote.value?.decimalplace)
-    // 计算浮动盈亏
-    const closepl = price ? (roundedMarketValue - item.holderamount) * (item.buyorsell === BuyOrSell.Buy ? 1 : -1) : 0
-
-    return {
-        ...item,
-        closepl
-    }
-}))
-
-const tableColumns = shallowRef<Model.TableColumn[]>([
-    { field: 'tradeid', label: '单号' },
-    { field: 'buyorsell', label: '方向' },
-    { field: 'holderqty', label: '持有量' },
-    { field: 'freezeqty', label: '冻结量' },
-    { field: 'enableqty', label: '可用量' },
-    { field: 'holderprice', label: '订单价格' },
-    { field: 'holderamount', label: '订单金额' },
-    { field: 'closepl', label: '参考损益' },
-    { field: 'expiredate', label: '到期日' },
-    { field: 'operate', label: '操作', fixed: 'right', width: 100 }
-])
-
-const onCloseSubmit = (row: Model.TradeHolderDetailRsp) => {
-    ElMessageBox.confirm(
-        '是否立即平仓?',
-        '提示'
-    ).then(() => {
-        const { marketid, goodsid, buyorsell, tradeid } = row
-        formData.Header = { MarketID: marketid, GoodsID: goodsid }
-        formData.GoodsID = goodsid
-        formData.BuyOrSell = buyorsell
-        formData.MarketID = marketid
-        formData.TradeID = handleRequestBigNumber(tradeid)
-
-        holderCloseSubmit().then(() => {
-            ElMessage.success('提交成功')
-            run()
-        }).catch((err) => {
-            ElMessage.error('提交失败:' + err)
-        })
-    })
-}
-</script>

+ 149 - 0
src/packages/pc/views/footer/pricing/position/components/transfer/index.vue

@@ -0,0 +1,149 @@
+<!-- 挂牌点价-合约汇总-转让 -->
+<template>
+    <app-drawer title="转让" :width="800" v-model:show="show" :loading="loading" :refresh="refresh">
+        <el-form ref="formRef" class="el-form--horizontal" label-width="120px" :model="formData" :rules="formRules">
+            <el-form-item label="商品代码/名称">
+                <span>{{ position.goodscode }}/{{ position.goodsname }}</span>
+            </el-form-item>
+            <el-form-item label="持仓方向">
+                <span>{{ getBuyOrSellName(position.buyorsell) }}</span>
+            </el-form-item>
+            <el-form-item label="持仓金额">
+                <span>{{ formatDecimal(position.curholderamount) }}</span>
+            </el-form-item>
+            <el-form-item label="持仓量">
+                <span>{{ position.curpositionqty }}</span>
+            </el-form-item>
+            <el-form-item label="冻结量">
+                <span>{{ position.frozenqty }}</span>
+            </el-form-item>
+            <el-form-item label="可用量">
+                <span>{{ position.enableqty }}</span>
+            </el-form-item>
+            <el-form-item label="持仓均价">
+                <span>{{ formatDecimal(position.averageprice, position.decimalplace) }}</span>
+            </el-form-item>
+            <el-form-item label="参考损益">
+                <span :class="position.closeplColor">
+                    {{ formatDecimal(position.closepl, position.decimalplace) }}
+                </span>
+            </el-form-item>
+            <el-form-item prop="OrderQty" label="转让量">
+                <div class="g-qty-group">
+                    <el-input-number placeholder="请输入数量" v-model="formData.OrderQty" :precision="0"
+                        :max="position.enableqty" :min="0" />
+                    <el-radio-group size="small" v-model="qtyStep" @change="onRadioChange">
+                        <el-radio v-for="(value, index) in qtyStepList" :key="index" :label="value" border
+                            style="width: 25%;">
+                            {{ parsePercent(value, 0) }}
+                        </el-radio>
+                    </el-radio-group>
+                </div>
+            </el-form-item>
+            <el-form-item prop="OrderPrice" label="转让价格">
+                <el-input-number placeholder="请输入价格" v-model="formData.OrderPrice" :step="quote?.decimalvalue"
+                    :precision="quote?.decimalplace" />
+            </el-form-item>
+        </el-form> 
+        <template #footer>
+            <el-button type="info" @click="onCancel(false)">取消</el-button>
+            <el-button type="primary" @click="onCloseSumit">提交</el-button>
+        </template>
+    </app-drawer>
+</template>
+
+<script lang="ts" setup>
+import { ref, PropType, onMounted } from 'vue'
+import { ElMessage, FormInstance, FormRules } from 'element-plus'
+import { useOrder } from '@/business/trade'
+import { formatDecimal, parsePercent } from '@/filters'
+import { getBuyOrSellName, BuyOrSell } from '@/constants/order'
+import { useFuturesStore } from '@/stores'
+import { EBuildType, EDelistingType, EListingSelectType, EOrderOperateType, EPriceMode, EValidType } from '@/constants/client'
+import AppDrawer from '@pc/components/base/drawer/index.vue'
+
+const props = defineProps({
+    position: {
+        type: Object as PropType<Model.TradePositionRsp & {
+            closepl: number; // 浮动盈亏
+            closeplColor: string; // 浮动盈亏颜色
+        }>,
+        required: true
+    }
+})
+
+const futuresStore = useFuturesStore()
+const quote = futuresStore.getGoodsQuote(props.position.goodscode)
+
+const { formSubmit, formData, loading } = useOrder()
+const show = ref(true)
+const refresh = ref(false)
+const formRef = ref<FormInstance>()
+const qtyStepList = [0.25, 0.5, 0.75, 1] // 数量步长列表
+const qtyStep = ref(1) // 数量步长
+
+const formRules: FormRules = {
+    OrderPrice: [{
+        message: '请输入转让价格',
+        validator: () => {
+            return !!formData.OrderPrice
+        }
+    }],
+    OrderQty: [{
+        message: '请输入转让量',
+        validator: () => {
+            return !!formData.OrderQty
+        }
+    }],
+}
+
+const onCancel = (isRefresh = false) => {
+    show.value = false
+    refresh.value = isRefresh
+}
+
+const onCloseSumit = () => {
+    formRef.value?.validate((valid) => {
+        if (valid) {
+            const { marketid, goodsid, buyorsell } = props.position
+            /// 市场ID
+            formData.Header = { GoodsID: goodsid }
+            formData.MarketID = marketid
+            formData.PriceMode = EPriceMode.PRICEMODE_LIMIT
+            formData.BuyOrSell = buyorsell === BuyOrSell.Buy ? BuyOrSell.Sell : BuyOrSell.Buy
+            formData.GoodsID = goodsid
+            formData.ListingSelectType = EListingSelectType.LISTINGSELECTTYPE_DELISTINGTHENLISTING
+            formData.DelistingType = EDelistingType.DELISTINGTYPE_PRICE
+            formData.BuildType = EBuildType.BUILDTYPE_CLOSE
+            formData.TimevalidType = EValidType.VALIDTYPE_DR
+            formData.OperateType = EOrderOperateType.ORDEROPERATETYPE_NORMAL
+
+            formSubmit().then(() => {
+                ElMessage.success('挂牌成功')
+                onCancel(true)
+            }).catch((err) => {
+                ElMessage.error('挂牌失败:' + err)
+            })
+        }
+    })
+}
+
+const onRadioChange = (value: number) => {
+    formData.OrderQty = Math.trunc(props.position.enableqty * value) || 1
+}
+
+onMounted(() => {
+    const { bid, ask, last, presettle = 0 } = quote.value ?? {}
+    switch (props.position.buyorsell) {
+        case BuyOrSell.Buy:
+            formData.OrderPrice = bid || last || presettle
+            break
+        case BuyOrSell.Sell:
+            formData.OrderPrice = ask || last || presettle
+            break
+        default:
+            formData.OrderPrice = last || presettle
+    }
+    formData.OrderQty = props.position.enableqty
+})
+</script>

+ 17 - 17
src/packages/pc/views/footer/pricing/position/index.vue

@@ -13,7 +13,7 @@
         <!-- 最新价 -->
         <template #lastprice="{ row }">
             <span :class="row.lastColor">
-                {{ handleNumberValue(row.lastprice) }}
+                {{ handleNumberValue(formatDecimal(row.lastprice, row.decimalplace)) }}
             </span>
         </template>
         <!-- 持仓均价 -->
@@ -24,35 +24,33 @@
         <template #closepl="{ row }">
             <span :class="row.closeplColor">{{ formatDecimal(row.closepl, row.decimalplace) }}</span>
         </template>
-        <!-- 展开行 -->
-        <template #expand="{ row }">
-            <div class="buttonbar">
-                <el-button type="danger" size="small" @click="showComponent('close', row)">明细</el-button>
-            </div>
+        <!-- 操作 -->
+        <template #operate="{ row }">
+            <el-button type="danger" size="small" @click="showComponent('transfer', row)">转让</el-button>
         </template>
-        <!-- <template #footer>
+        <template #footer>
             <component ref="componentRef" v-bind="{ position: selectedRow }" :is="componentMap.get(componentId)"
                 @closed="closeComponent" v-if="componentId" />
-        </template> -->
+        </template>
     </app-table>
 </template>
 
 <script lang="ts" setup>
-import { shallowRef, computed } from 'vue'
+import { shallowRef, computed, defineAsyncComponent } from 'vue'
 import { formatDecimal, handleNumberValue } from '@/filters'
-// import { useComponent } from '@/hooks/component'
 import { getBuyOrSellName } from '@/constants/order'
+import { useComponent } from '@/hooks/component'
 import { useComposeTable } from '@pc/components/base/table'
 import { usePositionStore } from '@/stores'
 import AppTable from '@pc/components/base/table/index.vue'
 
-// const componentMap = new Map<string, unknown>([
-//     ['close', defineAsyncComponent(() => import('./close/index.vue'))],
-// ])
+const componentMap = new Map<string, unknown>([
+    ['transfer', defineAsyncComponent(() => import('./components/transfer/index.vue'))],
+])
 
-const positionStore = usePositionStore()
-// const { componentRef, componentId, openComponent, closeComponent } = useComponent()
 const { rowKey, expandKeys, rowClick } = useComposeTable<Model.TradePositionRsp>({ rowKey: 'pkid' })
+const { componentRef, componentId, openComponent, closeComponent } = useComponent()
+const positionStore = usePositionStore()
 const selectedRow = shallowRef<Model.TradePositionRsp>()
 
 const positionList = computed(() => positionStore.getPositionListByTradeMode(10))
@@ -66,11 +64,13 @@ const tableColumns = shallowRef<Model.TableColumn[]>([
     { field: 'frozenqty', label: '冻结量' },
     { field: 'curholderamount', label: '订单总额' },
     { field: 'enableqty', label: '可用量' },
-    { field: 'closepl', label: '参考损益' }
+    { field: 'closepl', label: '参考损益' },
+    { field: 'operate', label: '操作', fixed: 'right' },
 ])
 
 const showComponent = (componentName: string, row: Model.TradePositionRsp) => {
     selectedRow.value = row
-    // openComponent(componentName)
+    openComponent(componentName)
 }
+
 </script>