li.shaoyi hai 9 meses
pai
achega
f2a691a5f0

+ 1 - 1
src/hooks/enum/index.ts

@@ -11,7 +11,7 @@ export function useEnum(code: string) {
     const readyCallbackMap = new Map<symbol, () => void>()
 
     // 获取枚举名称
-    const getEnumTypeName = (value: number) => {
+    const getEnumTypeName = (value?: number) => {
         const item = state.enums.find((e) => e.enumitemname === value)
         const { enumdicname = value?.toString() } = item ?? {}
         return enumdicname

+ 22 - 0
src/packages/pc/assets/themes/global/global.less

@@ -79,4 +79,26 @@
 .g-text-message {
     font-size: 16px;
     line-height: normal;
+}
+
+.g-view-tree {
+    display: flex;
+
+    .app-view:first-child {
+        flex: none;
+        min-width: 240px;
+
+        .app-view {
+
+            &__header,
+            &__footer,
+            &__main {
+                padding-right: 0;
+            }
+
+            &__container {
+                min-width: 240px;
+            }
+        }
+    }
 }

+ 14 - 19
src/packages/pc/views/investor/custom/accountcfg/components/edit/index.vue

@@ -111,7 +111,7 @@
 </template>
 
 <script lang="ts" setup>
-import { reactive, shallowRef, PropType, computed } from 'vue'
+import { reactive, shallowRef, PropType, computed, onMounted } from 'vue'
 import { ElMessage, FormInstance } from 'element-plus'
 import { handleNoneValue } from '@/filters'
 import { useEnum } from '@/hooks/enum'
@@ -172,24 +172,6 @@ const { data: resultData } = useRequest(loadSelectOption, {
         accountid: formData.accountid,
         marketid: formData.marketid,
         goodsid: formData.goodsid
-    },
-    onSuccess: (res) => {
-        res.data.rulesConfigVos.forEach((e) => {
-            const rule = props.record.druleList.find((d) => d.ruleid === e.ruleid)
-            formData.ruleList.push({
-                paramid: e.ruleid,
-                paramvalue: rule?.cvalue
-            })
-        })
-
-        res.data.feesConfigVos.forEach((e) => {
-            const fee = props.record.dfeeList.find((d) => d.tradefeeid === e.tradefeeid)
-            formData.feeList.push({
-                feealgorithm: e.feealgorithm,
-                paramid: e.tradefeeid,
-                paramvalue: fee?.cvalue
-            })
-        })
     }
 })
 
@@ -244,4 +226,17 @@ const onCancel = (isRefresh = false) => {
     show.value = false
     refresh.value = isRefresh
 }
+
+onMounted(() => {
+    formData.ruleList = props.record.druleList.map((e) => ({
+        paramid: e.ruleid,
+        paramvalue: e.cvalue
+    }))
+
+    formData.feeList = props.record.dfeeList.map((e) => ({
+        feealgorithm: e.feealgorithm,
+        paramid: e.tradefeeid,
+        paramvalue: e.cvalue
+    }))
+})
 </script>

+ 149 - 208
src/packages/pc/views/investor/custom/accountcfg/index.vue

@@ -1,131 +1,110 @@
 <!-- 交易商管理-个性化管理-资金账户个性化 -->
 <template>
-    <app-view>
-        <template #header>
-            <el-form ref="formRef" class="el-form--filter" :model="qs" :rules="formRules" :show-message="false">
-                <el-form-item label="交易商" prop="userid">
-                    <el-select v-model="selectedInvestor" value-key="id" :validate-event="false"
-                        @change="onGroupChange">
-                        <template v-for="item in investorList" :key="item.id">
-                            <el-option :label="item.name" :value="item" />
-                        </template>
-                    </el-select>
-                </el-form-item>
-                <el-form-item label="资金账户" prop="accountid">
-                    <el-select v-model="selectedAccount" value-key="id" :validate-event="false"
-                        @change="onMarketChange">
-                        <template v-for="item in accountList" :key="item.id">
-                            <el-option :label="item.name" :value="item" />
-                        </template>
-                    </el-select>
-                </el-form-item>
-                <el-form-item label="商品" prop="goodsid">
-                    <el-select v-model="selectedGoods" value-key="id" :validate-event="false">
-                        <template v-for="item in goodsList" :key="item.id">
-                            <el-option :label="item.name" :value="item" />
-                        </template>
-                    </el-select>
-                </el-form-item>
-                <el-form-item>
-                    <el-button type="primary" @click="onSearch">{{ t('operation.search') }}</el-button>
-                </el-form-item>
-            </el-form>
-        </template>
-        <div style="display: flex;justify-content: space-between;">
-            <app-operation :data-list="getActionButtons(['investor_custom_accountcfg_add'])" @click="openComponent" />
-            <app-operation
-                :data-list="getActionButtons(['investor_custom_accountcfg_modify', 'investor_custom_accountcfg_delete'])"
-                @click="openComponent" v-if="data" />
-        </div>
-        <template v-if="data">
-            <app-table-details title="基本信息设置" :data="data" :label-width="160" :cell-props="detailProps" :column="2" />
-            <app-table :data="data.druleList" :columns="ruleColumns">
-                <template #headerLeft>
-                    <b>交易规则设置</b>
-                </template>
-                <!-- 平台 -->
-                <template #paramvalue="{ row }">
-                    <span v-if="row.ruleid === 105">{{ feetypeEnum.getEnumTypeName(row.paramvalue) }}</span>
-                    <span v-else>{{ row.paramvalue || 0 }}</span>
-                </template>
-                <!-- 会员 -->
-                <template #dvalue="{ row }">
-                    <span v-if="row.ruleid === 105">
-                        {{ handleNoneValue(feetypeEnum.getEnumTypeName(row.dvalue)) }}
-                    </span>
-                    <span v-else>{{ handleNoneValue(row.dvalue) }}</span>
-                </template>
-                <!-- 分组 -->
-                <template #gvalue="{ row }">
-                    <span v-if="row.ruleid === 105">
-                        {{ handleNoneValue(feetypeEnum.getEnumTypeName(row.gvalue)) }}
-                    </span>
-                    <span v-else>{{ handleNoneValue(row.gvalue) }}</span>
-                </template>
-                <!-- 个性化 -->
-                <template #cvalue="{ row }">
-                    <span v-if="row.ruleid === 105">
-                        {{ handleNoneValue(feetypeEnum.getEnumTypeName(row.cvalue)) }}
-                    </span>
-                    <span v-else>{{ handleNoneValue(row.cvalue) }}</span>
-                </template>
-            </app-table>
-            <app-table :data="data.dfeeList" :columns="feeColumns">
-                <template #headerLeft>
-                    <b>交易服务费设置</b>
-                </template>
-                <!-- 平台 -->
-                <template #exchangevalue="{ row }">
-                    <template v-if="row.feealgorithm">
-                        <span>{{ scfRiskMode.getEnumTypeName(row.feealgorithm) }}:</span>
-                        <span>{{ row.exchangevalue + (row.feealgorithm === 1 && '‱') }}</span>
+    <div class="g-view-tree">
+        <app-view>
+            <el-tree :data="nodeList" node-key="id" :props="{ label: 'name' }" :default-expanded-keys="['-1']"
+                @node-click="nodeClick" highlight-current />
+        </app-view>
+        <app-view>
+            <div style="display: flex;justify-content: space-between;">
+                <app-operation :data-list="getActionButtons(['investor_custom_accountcfg_add'])"
+                    @click="openComponent" />
+                <app-operation
+                    :data-list="getActionButtons(['investor_custom_accountcfg_modify', 'investor_custom_accountcfg_delete'])"
+                    @click="openComponent" v-if="data" />
+            </div>
+            <template v-if="data">
+                <app-table-details title="基本信息" :data="currentInfo" :label-width="160" :cell-props="detailProps"
+                    :column="2" />
+                <app-table :data="data.druleList" :columns="ruleColumns">
+                    <template #headerLeft>
+                        <b>交易规则</b>
                     </template>
-                    <template v-else>
-                        {{ handleNoneValue() }}
+                    <!-- 平台 -->
+                    <template #paramvalue="{ row }">
+                        <span v-if="row.ruleid === 105">{{ feetypeEnum.getEnumTypeName(row.paramvalue) }}</span>
+                        <span v-else>{{ row.paramvalue || 0 }}</span>
                     </template>
-                </template>
-                <!-- 会员 -->
-                <template #dvalue="{ row }">
-                    <template v-if="row.drithm">
-                        <span>{{ scfRiskMode.getEnumTypeName(row.drithm) }}:</span>
-                        <span>{{ row.dvalue + (row.drithm === 1 && '‱') }}</span>
+                    <!-- 会员 -->
+                    <template #dvalue="{ row }">
+                        <span v-if="row.ruleid === 105">
+                            {{ handleNoneValue(feetypeEnum.getEnumTypeName(row.dvalue)) }}
+                        </span>
+                        <span v-else>{{ handleNoneValue(row.dvalue) }}</span>
                     </template>
-                    <template v-else>
-                        {{ handleNoneValue() }}
+                    <!-- 分组 -->
+                    <template #gvalue="{ row }">
+                        <span v-if="row.ruleid === 105">
+                            {{ handleNoneValue(feetypeEnum.getEnumTypeName(row.gvalue)) }}
+                        </span>
+                        <span v-else>{{ handleNoneValue(row.gvalue) }}</span>
                     </template>
-                </template>
-                <!-- 分组 -->
-                <template #gvalue="{ row }">
-                    <template v-if="row.grithm">
-                        <span>{{ scfRiskMode.getEnumTypeName(row.grithm) }}:</span>
-                        <span>{{ row.gvalue + (row.grithm === 1 && '‱') }}</span>
+                    <!-- 个性化 -->
+                    <template #cvalue="{ row }">
+                        <span v-if="row.ruleid === 105">
+                            {{ handleNoneValue(feetypeEnum.getEnumTypeName(row.cvalue)) }}
+                        </span>
+                        <span v-else>{{ handleNoneValue(row.cvalue) }}</span>
                     </template>
-                    <template v-else>
-                        {{ handleNoneValue() }}
+                </app-table>
+                <app-table :data="data.dfeeList" :columns="feeColumns">
+                    <template #headerLeft>
+                        <b>交易服务费</b>
                     </template>
-                </template>
-                <!-- 个性化 -->
-                <template #cvalue="{ row }">
-                    <template v-if="row.crithm">
-                        <span>{{ scfRiskMode.getEnumTypeName(row.crithm) }}:</span>
-                        <span>{{ row.cvalue + (row.crithm === 1 && '‱') }}</span>
+                    <!-- 平台 -->
+                    <template #exchangevalue="{ row }">
+                        <template v-if="row.feealgorithm">
+                            <span>{{ scfRiskMode.getEnumTypeName(row.feealgorithm) }}:</span>
+                            <span>{{ row.exchangevalue + (row.feealgorithm === 1 && '‱') }}</span>
+                        </template>
+                        <template v-else>
+                            {{ handleNoneValue() }}
+                        </template>
                     </template>
-                    <template v-else>
-                        {{ handleNoneValue() }}
+                    <!-- 会员 -->
+                    <template #dvalue="{ row }">
+                        <template v-if="row.drithm">
+                            <span>{{ scfRiskMode.getEnumTypeName(row.drithm) }}:</span>
+                            <span>{{ row.dvalue + (row.drithm === 1 && '‱') }}</span>
+                        </template>
+                        <template v-else>
+                            {{ handleNoneValue() }}
+                        </template>
                     </template>
-                </template>
-            </app-table>
-        </template>
-        <el-empty v-else />
+                    <!-- 分组 -->
+                    <template #gvalue="{ row }">
+                        <template v-if="row.grithm">
+                            <span>{{ scfRiskMode.getEnumTypeName(row.grithm) }}:</span>
+                            <span>{{ row.gvalue + (row.grithm === 1 && '‱') }}</span>
+                        </template>
+                        <template v-else>
+                            {{ handleNoneValue() }}
+                        </template>
+                    </template>
+                    <!-- 个性化 -->
+                    <template #cvalue="{ row }">
+                        <template v-if="row.crithm">
+                            <span>{{ scfRiskMode.getEnumTypeName(row.crithm) }}:</span>
+                            <span>{{ row.cvalue + (row.crithm === 1 && '‱') }}</span>
+                        </template>
+                        <template v-else>
+                            {{ handleNoneValue() }}
+                        </template>
+                    </template>
+                </app-table>
+            </template>
+            <el-empty v-else />
+        </app-view>
         <component :is="componentMap.get(componentId)" v-bind="{ record: toRaw(data) }" @closed="closeComponent"
             v-if="componentId" />
-    </app-view>
+    </div>
 </template>
 
 <script lang="ts" setup>
-import { shallowRef, computed, nextTick, toRaw } from 'vue'
-import { ElMessage, FormInstance, FormRules } from 'element-plus'
-import { handleNoneValue } from '@/filters'
+import { ref, reactive, computed, toRaw } from 'vue'
+import { ElMessage } from 'element-plus'
+import type Node from 'element-plus/es/components/tree/src/model/node'
+import { handleNoneValue, buildTree } from '@/filters'
 import { useEnum } from '@/hooks/enum'
 import { useRequest } from '@/hooks/request'
 import { useOperation } from '@/hooks/operation'
@@ -134,67 +113,79 @@ import { CellProp } from '@pc/components/base/table-details/types'
 import AppTable from '@pc/components/base/table/index.vue'
 import AppOperation from '@pc/components/base/operation/index.vue'
 import AppTableDetails from '@pc/components/base/table-details/index.vue'
-import { i18n } from '@/stores'
+
+const customerTypeEnum = useEnum('customerType')
+const feetypeEnum = useEnum('feetype')
+const scfRiskMode = useEnum('SCFRiskMode')
+
+const nodeList = ref<(Model.AccTradeTreeRsp & { children: Model.AccTradeTreeRsp[]; })[]>([])
+const selectedNode = ref<Node>()
+
+const currentInfo = reactive({
+    userName: '',
+    accountName: '',
+    goodsName: '',
+    customerType: ''
+})
+
+const qs = computed<Model.AccTradeConfigViewReq>(() => {
+    const data = selectedNode.value?.data
+    return {
+        userid: data?.userid,
+        accountid: data?.accountid,
+        marketid: data?.marketid,
+        goodsid: data?.goodsid,
+        tradetype: data?.tradetype
+    }
+})
 
 const { componentMap, componentId, openComponent, closeComponent, getActionButtons } = useOperation<Model.AccTradeConfigViewRsp>({
     onClose: (componentId) => {
         getNodeList()
         if (componentId === 'investor_custom_accountcfg_modify') {
-            onSearch()
+            getDetails(qs.value)
         }
         if (componentId === 'investor_custom_accountcfg_delete') {
-            selectedInvestor.value = undefined
-            selectedAccount.value = undefined
-            selectedGoods.value = undefined
             data.value = undefined
         }
     }
 })
 
-const customerTypeEnum = useEnum('customerType')
-const feetypeEnum = useEnum('feetype')
-const scfRiskMode = useEnum('SCFRiskMode')
-
-const { global: { t } } = i18n
-const formRef = shallowRef<FormInstance>()
-const qs = shallowRef<Partial<Model.AccTradeConfigViewReq>>({})
-
-const info = shallowRef<Partial<{
-    userName: string,
-    accountName: string,
-    goodsName: string
-}>>({})
-
-const selectedInvestor = shallowRef<Model.AccTradeTreeRsp>() // 选中的交易商对象
-const selectedAccount = shallowRef<Model.AccTradeTreeRsp>() // 选中的资金账户对象
-const selectedGoods = shallowRef<Model.AccTradeTreeRsp>() // 选中的商品对象
-
-// 交易商列表
-const investorList = computed(() => nodeList.value.filter((e) => e.pid === '-1'))
-// 资金账户列表
-const accountList = computed(() => nodeList.value.filter((e) => e.pid === selectedInvestor.value?.id))
-// 商品列表
-const goodsList = computed(() => nodeList.value.filter((e) => e.pid === selectedAccount.value?.id))
-
-const { dataList: nodeList, run: getNodeList } = useRequest(getAccTradeTree, {
+const { run: getNodeList } = useRequest(getAccTradeTree, {
+    onSuccess: (res) => {
+        res.data.push(
+            {
+                accountid: 0,
+                goodsid: 0,
+                id: '-1',
+                level: 0,
+                marketid: 0,
+                name: '交易商账户个性化设置',
+                pid: '0',
+                tradetype: 1,
+                userid: 0,
+            }
+        )
+        nodeList.value = buildTree(res.data, 'id', 'pid', (node) => node.pid === '0')
+    },
     onError: (err) => {
         ElMessage.error(err)
     }
 })
 
-const { data, run } = useRequest(accTradeConfigView, {
+const { data, run: getDetails } = useRequest(accTradeConfigView, {
     manual: true,
     onError: (err) => {
         ElMessage.error(err)
     }
 })
 
-const detailProps: CellProp[] = [
-    { prop: 'usergroupid', label: '交易商', formatValue: () => info.value.userName },
-    { prop: 'usergroupid', label: '资金账户', formatValue: () => info.value.accountName },
-    { prop: 'usergroupid', label: '商品', formatValue: () => info.value.goodsName },
-    { prop: 'paramid', label: '保证金类别', formatValue: (val) => customerTypeEnum.getEnumTypeName(val) },
-]
+const detailProps = computed<CellProp[]>(() => [
+    { prop: 'userName', label: '交易商' },
+    { prop: 'accountName', label: '资金账户' },
+    { prop: 'goodsName', label: '商品' },
+    { prop: 'customerType', label: '保证金类别', formatValue: () => customerTypeEnum.getEnumTypeName(data.value?.paramid) },
+])
 
 const ruleColumns = computed<Model.TableColumn[]>(() => [
     { field: 'rulename', label: '扩展项' },
@@ -212,63 +203,13 @@ const feeColumns = computed<Model.TableColumn[]>(() => [
     { field: 'cvalue', label: '个性化' },
 ])
 
-// 表单验证规则
-const formRules: FormRules = {
-    userid: [{
-        required: true,
-        validator: (rule, value, callback) => {
-            if (selectedInvestor.value) {
-                callback()
-            } else {
-                callback(new Error('请选择交易商'))
-            }
-        },
-    }],
-    accountid: [{
-        required: true,
-        validator: (rule, value, callback) => {
-            if (selectedAccount.value) {
-                callback()
-            } else {
-                callback(new Error('请选择资金账户'))
-            }
-        },
-    }],
-    goodsid: [{ required: true }],
-}
-
-// 选择交易商
-const onGroupChange = () => {
-    selectedAccount.value = undefined
-    selectedGoods.value = undefined
-}
-
-// 选择市场
-const onMarketChange = () => {
-    selectedGoods.value = undefined
-}
-
-const onSearch = () => {
-    qs.value = {
-        userid: selectedGoods.value?.userid,
-        accountid: selectedGoods.value?.accountid,
-        marketid: selectedGoods.value?.marketid,
-        goodsid: selectedGoods.value?.goodsid,
-        tradetype: selectedGoods.value?.tradetype
+const nodeClick = (data: Model.CreateTreeRsp, node: Node) => {
+    if (data.goodsid) {
+        selectedNode.value = node
+        currentInfo.goodsName = node.label
+        currentInfo.accountName = node.parent.label
+        currentInfo.userName = node.parent.parent.label
+        getDetails(qs.value)
     }
-    nextTick(() => {
-        formRef.value?.validate((valid) => {
-            if (valid) {
-                info.value = {
-                    userName: selectedInvestor.value?.name,
-                    accountName: selectedAccount.value?.name,
-                    goodsName: selectedGoods.value?.name,
-                }
-                run(qs.value)
-            } else {
-                data.value = undefined
-            }
-        })
-    })
 }
 </script>

+ 14 - 19
src/packages/pc/views/investor/custom/tradecfg/components/edit/index.vue

@@ -92,7 +92,7 @@
 </template>
 
 <script lang="ts" setup>
-import { reactive, shallowRef, PropType, computed } from 'vue'
+import { reactive, shallowRef, PropType, computed, onMounted } from 'vue'
 import { ElMessage, FormInstance } from 'element-plus'
 import { handleNoneValue } from '@/filters'
 import { useEnum } from '@/hooks/enum'
@@ -146,24 +146,6 @@ const { data: resultData } = useRequest(initInvestorPerson, {
         usergroupid: props.record.usergroupid,
         marketid: props.record.marketid,
         goodsid: props.record.goodsid
-    },
-    onSuccess: (res) => {
-        res.data.rulesConfigVos.forEach((e) => {
-            const rule = props.record.druleList.find((d) => d.ruleid === e.ruleid)
-            formData.ruleList.push({
-                paramid: e.ruleid,
-                paramvalue: rule?.cvalue
-            })
-        })
-
-        res.data.feesConfigVos.forEach((e) => {
-            const fee = props.record.dfeeList.find((d) => d.tradefeeid === e.tradefeeid)
-            formData.feeList.push({
-                feealgorithm: e.feealgorithm,
-                paramid: e.tradefeeid,
-                paramvalue: fee?.cvalue
-            })
-        })
     }
 })
 
@@ -216,4 +198,17 @@ const onCancel = (isRefresh = false) => {
     show.value = false
     refresh.value = isRefresh
 }
+
+onMounted(() => {
+    formData.ruleList = props.record.druleList.map((e) => ({
+        paramid: e.ruleid,
+        paramvalue: e.cvalue
+    }))
+
+    formData.feeList = props.record.dfeeList.map((e) => ({
+        feealgorithm: e.feealgorithm,
+        paramid: e.tradefeeid,
+        paramvalue: e.cvalue
+    }))
+})
 </script>

+ 133 - 172
src/packages/pc/views/investor/custom/tradecfg/index.vue

@@ -1,113 +1,92 @@
 <!-- 交易商管理-个性化管理-交易商个性化 -->
 <template>
-    <app-view>
-        <template #header>
-            <el-form ref="formRef" class="el-form--filter" :model="qs" :rules="formRules" :show-message="false">
-                <el-form-item :label="t('investor.custom.tradecfg.usergroupid')" prop="usergroupid">
-                    <el-select v-model="selectedGroup" value-key="id" :validate-event="false" @change="onGroupChange">
-                        <template v-for="item in groupList" :key="item.id">
-                            <el-option :label="item.name" :value="item" />
-                        </template>
-                    </el-select>
-                </el-form-item>
-                <el-form-item :label="t('investor.custom.tradecfg.marketid')" prop="marketid">
-                    <el-select v-model="selectedMarket" value-key="id" :validate-event="false" @change="onMarketChange">
-                        <template v-for="item in marketList" :key="item.id">
-                            <el-option :label="item.name" :value="item" />
-                        </template>
-                    </el-select>
-                </el-form-item>
-                <el-form-item :label="t('investor.custom.tradecfg.goodsid')" prop="goodsid">
-                    <el-select v-model="selectedGoods" value-key="id" :validate-event="false">
-                        <template v-for="item in goodsList" :key="item.id">
-                            <el-option :label="item.name" :value="item" />
-                        </template>
-                    </el-select>
-                </el-form-item>
-                <el-form-item>
-                    <el-button type="primary" @click="onSearch">{{ t('operation.search') }}</el-button>
-                </el-form-item>
-            </el-form>
-        </template>
-        <div style="display: flex;justify-content: space-between;">
-            <app-operation :data-list="getActionButtons(['investor_custom_tradecfg_add'])" @click="openComponent" />
-            <app-operation
-                :data-list="getActionButtons(['investor_custom_tradecfg_modify', 'investor_custom_tradecfg_delete'])"
-                @click="openComponent" v-if="data" />
-        </div>
-        <template v-if="data">
-            <app-table-details :title="t('common.baseinfo')" :data="data" :label-width="160" :cell-props="detailProps"
-                :column="3" />
-            <app-table :data="data.druleList" :columns="ruleColumns">
-                <template #headerLeft>
-                    <b>{{ t('investor.custom.tradecfg.subtitle') }}</b>
-                </template>
-                <!-- 商品设置值 -->
-                <template #paramvalue="{ row }">
-                    <span v-if="row.ruleid === 105">{{ feetypeEnum.getEnumTypeName(row.paramvalue) }}</span>
-                    <span v-else>{{ row.paramvalue || 0 }}</span>
-                </template>
-                <!-- 会员设置值 -->
-                <template #dvalue="{ row }">
-                    <span v-if="row.ruleid === 105">
-                        {{ handleNoneValue(feetypeEnum.getEnumTypeName(row.dvalue)) }}
-                    </span>
-                    <span v-else>{{ handleNoneValue(row.dvalue) }}</span>
-                </template>
-                <!-- 个性化 -->
-                <template #cvalue="{ row }">
-                    <span v-if="row.ruleid === 105">
-                        {{ handleNoneValue(feetypeEnum.getEnumTypeName(row.cvalue)) }}
-                    </span>
-                    <span v-else>{{ handleNoneValue(row.cvalue) }}</span>
-                </template>
-            </app-table>
-            <app-table :data="data.dfeeList" :columns="feeColumns">
-                <template #headerLeft>
-                    <b>{{ t('investor.custom.tradecfg.subtitle1') }}</b>
-                </template>
-                <!-- 商品设置值 -->
-                <template #exchangevalue="{ row }">
-                    <template v-if="row.feealgorithm">
-                        <span>{{ scfRiskMode.getEnumTypeName(row.feealgorithm) }}:</span>
-                        <span>{{ row.exchangevalue + (row.feealgorithm === 1 && '‱') }}</span>
+    <div class="g-view-tree">
+        <app-view>
+            <el-tree :data="nodeList" node-key="id" :props="{ label: 'name' }" :default-expanded-keys="['-1']"
+                @node-click="nodeClick" highlight-current />
+        </app-view>
+        <app-view>
+            <div style="display: flex;justify-content: space-between;">
+                <app-operation :data-list="getActionButtons(['investor_custom_tradecfg_add'])" @click="openComponent" />
+                <app-operation
+                    :data-list="getActionButtons(['investor_custom_tradecfg_modify', 'investor_custom_tradecfg_delete'])"
+                    @click="openComponent" v-if="data" />
+            </div>
+            <template v-if="data">
+                <app-table-details :title="t('common.baseinfo')" :data="currentInfo" :label-width="160"
+                    :cell-props="detailProps" :column="3" />
+                <app-table :data="data.druleList" :columns="ruleColumns">
+                    <template #headerLeft>
+                        <b>{{ t('investor.custom.tradecfg.subtitle') }}</b>
                     </template>
-                    <template v-else>
-                        {{ handleNoneValue() }}
+                    <!-- 商品设置值 -->
+                    <template #paramvalue="{ row }">
+                        <span v-if="row.ruleid === 105">{{ feetypeEnum.getEnumTypeName(row.paramvalue) }}</span>
+                        <span v-else>{{ row.paramvalue || 0 }}</span>
                     </template>
-                </template>
-                <!-- 会员设置值 -->
-                <template #dvalue="{ row }">
-                    <template v-if="row.drithm">
-                        <span>{{ scfRiskMode.getEnumTypeName(row.drithm) }}:</span>
-                        <span>{{ row.dvalue + (row.drithm === 1 && '‱') }}</span>
+                    <!-- 会员设置值 -->
+                    <template #dvalue="{ row }">
+                        <span v-if="row.ruleid === 105">
+                            {{ handleNoneValue(feetypeEnum.getEnumTypeName(row.dvalue)) }}
+                        </span>
+                        <span v-else>{{ handleNoneValue(row.dvalue) }}</span>
                     </template>
-                    <template v-else>
-                        {{ handleNoneValue() }}
+                    <!-- 个性化 -->
+                    <template #cvalue="{ row }">
+                        <span v-if="row.ruleid === 105">
+                            {{ handleNoneValue(feetypeEnum.getEnumTypeName(row.cvalue)) }}
+                        </span>
+                        <span v-else>{{ handleNoneValue(row.cvalue) }}</span>
                     </template>
-                </template>
-                <!-- 个性化 -->
-                <template #cvalue="{ row }">
-                    <template v-if="row.crithm">
-                        <span>{{ scfRiskMode.getEnumTypeName(row.crithm) }}:</span>
-                        <span>{{ row.cvalue + (row.crithm === 1 && '‱') }}</span>
+                </app-table>
+                <app-table :data="data.dfeeList" :columns="feeColumns">
+                    <template #headerLeft>
+                        <b>{{ t('investor.custom.tradecfg.subtitle1') }}</b>
+                    </template>
+                    <!-- 商品设置值 -->
+                    <template #exchangevalue="{ row }">
+                        <template v-if="row.feealgorithm">
+                            <span>{{ scfRiskMode.getEnumTypeName(row.feealgorithm) }}:</span>
+                            <span>{{ row.exchangevalue + (row.feealgorithm === 1 && '‱') }}</span>
+                        </template>
+                        <template v-else>
+                            {{ handleNoneValue() }}
+                        </template>
                     </template>
-                    <template v-else>
-                        {{ handleNoneValue() }}
+                    <!-- 会员设置值 -->
+                    <template #dvalue="{ row }">
+                        <template v-if="row.drithm">
+                            <span>{{ scfRiskMode.getEnumTypeName(row.drithm) }}:</span>
+                            <span>{{ row.dvalue + (row.drithm === 1 && '‱') }}</span>
+                        </template>
+                        <template v-else>
+                            {{ handleNoneValue() }}
+                        </template>
+                    </template>
+                    <!-- 个性化 -->
+                    <template #cvalue="{ row }">
+                        <template v-if="row.crithm">
+                            <span>{{ scfRiskMode.getEnumTypeName(row.crithm) }}:</span>
+                            <span>{{ row.cvalue + (row.crithm === 1 && '‱') }}</span>
+                        </template>
+                        <template v-else>
+                            {{ handleNoneValue() }}
+                        </template>
                     </template>
-                </template>
-            </app-table>
-        </template>
-        <el-empty v-else />
+                </app-table>
+            </template>
+            <el-empty v-else />
+        </app-view>
         <component :is="componentMap.get(componentId)" v-bind="{ record: toRaw(data) }" @closed="closeComponent"
             v-if="componentId" />
-    </app-view>
+    </div>
 </template>
 
 <script lang="ts" setup>
-import { shallowRef, computed, nextTick, toRaw } from 'vue'
-import { ElMessage, FormInstance, FormRules } from 'element-plus'
-import { parseTenThousand, handleNoneValue } from '@/filters'
+import { ref, computed, reactive, toRaw } from 'vue'
+import { ElMessage } from 'element-plus'
+import type Node from 'element-plus/es/components/tree/src/model/node'
+import { buildTree, handleNoneValue } from '@/filters'
 import { useEnum } from '@/hooks/enum'
 import { useRequest } from '@/hooks/request'
 import { useOperation } from '@/hooks/operation'
@@ -118,66 +97,78 @@ import AppOperation from '@pc/components/base/operation/index.vue'
 import AppTableDetails from '@pc/components/base/table-details/index.vue'
 import { i18n } from '@/stores'
 
+const customerTypeEnum = useEnum('customerType')
+const feetypeEnum = useEnum('feetype')
+const scfRiskMode = useEnum('SCFRiskMode')
+
+const nodeList = ref<(Model.InvestorTreeRsp & { children: Model.InvestorTreeRsp[]; })[]>([])
+const selectedNode = ref<Node>()
+
+const currentInfo = reactive({
+    groupName: '',
+    marketName: '',
+    goodsName: '',
+    customerType: '',
+    marginValue: ''
+})
+
+const qs = computed<Model.TradeConfigViewReq>(() => {
+    const data = selectedNode.value?.data
+    return {
+        usergroupid: data?.usergroupid,
+        marketid: data?.marketid,
+        goodsid: data?.goodsid,
+    }
+})
+
+const { global: { t } } = i18n
+
 const { componentMap, componentId, openComponent, closeComponent, getActionButtons } = useOperation<Model.TradeConfigViewRsp>({
     onClose: (componentId) => {
         getNodeList()
         if (componentId === 'investor_custom_tradecfg_modify') {
-            onSearch()
+            getDetails(qs.value)
         }
         if (componentId === 'investor_custom_tradecfg_delete') {
-            selectedGroup.value = undefined
-            selectedMarket.value = undefined
-            selectedGoods.value = undefined
             data.value = undefined
         }
     }
 })
 
-const customerTypeEnum = useEnum('customerType')
-const feetypeEnum = useEnum('feetype')
-const scfRiskMode = useEnum('SCFRiskMode')
-
-const { global: { t } } = i18n
-const formRef = shallowRef<FormInstance>()
-const qs = shallowRef<Partial<Model.TradeConfigViewReq>>({})
-
-const info = shallowRef<Partial<{
-    groupName: string,
-    marketName: string,
-    goodsName: string
-}>>({})
-
-const selectedGroup = shallowRef<Model.InvestorTreeRsp>() // 选中的分组对象
-const selectedMarket = shallowRef<Model.InvestorTreeRsp>() // 选中的市场对象
-const selectedGoods = shallowRef<Model.InvestorTreeRsp>() // 选中的商品对象
-
-// 分组列表
-const groupList = computed(() => nodeList.value.filter((e) => e.pid === '-1'))
-// 市场列表
-const marketList = computed(() => nodeList.value.filter((e) => e.pid === selectedGroup.value?.id))
-// 商品列表
-const goodsList = computed(() => nodeList.value.filter((e) => e.pid === selectedMarket.value?.id))
-
-const { dataList: nodeList, run: getNodeList } = useRequest(getInvestorTree, {
+const { run: getNodeList } = useRequest(getInvestorTree, {
+    onSuccess: (res) => {
+        res.data.push(
+            {
+                goodsid: 0,
+                id: '-1',
+                level: 0,
+                marketid: 0,
+                name: '交易商交易个性化设置',
+                pid: '0',
+                usergroupid: 0
+            }
+        )
+        nodeList.value = buildTree(res.data, 'id', 'pid', (node) => node.pid === '0')
+    },
     onError: (err) => {
         ElMessage.error(err)
     }
 })
 
-const { data, run } = useRequest(tradeConfigView, {
+const { data, run: getDetails } = useRequest(tradeConfigView, {
     manual: true,
     onError: (err) => {
         ElMessage.error(err)
     }
 })
 
-const detailProps: CellProp[] = [
-    { prop: 'usergroupid', label: 'investor.custom.tradecfg.usergroupid1', formatValue: () => info.value.groupName },
-    { prop: 'marketid', label: 'investor.custom.tradecfg.marketid1', formatValue: () => info.value.marketName },
-    { prop: 'goodsid', label: 'investor.custom.tradecfg.goodsid1', formatValue: () => info.value.goodsName },
-    { prop: 'paramid', label: 'investor.custom.tradecfg.paramid', formatValue: (val) => customerTypeEnum.getEnumTypeName(val) },
-    { prop: 'marketmarginvaluedisplay', label: 'investor.custom.tradecfg.marketmarginvaluedisplay' },
-]
+const detailProps = computed<CellProp[]>(() => [
+    { prop: 'groupName', label: 'investor.custom.tradecfg.usergroupid1' },
+    { prop: 'marketName', label: 'investor.custom.tradecfg.marketid1' },
+    { prop: 'goodsName', label: 'investor.custom.tradecfg.goodsid1' },
+    { prop: 'customerType', label: 'investor.custom.tradecfg.paramid', formatValue: () => customerTypeEnum.getEnumTypeName(data.value?.paramid) },
+    { prop: 'marginValue', label: 'investor.custom.tradecfg.marketmarginvaluedisplay', formatValue: () => data.value?.marketmarginvaluedisplay },
+])
 
 const ruleColumns = computed<Model.TableColumn[]>(() => [
     { field: 'rulename', label: 'investor.custom.tradecfg.rulename' },
@@ -193,43 +184,13 @@ const feeColumns = computed<Model.TableColumn[]>(() => [
     { field: 'cvalue', label: 'investor.custom.tradecfg.cvalue' },
 ])
 
-// 表单验证规则
-const formRules: FormRules = {
-    usergroupid: [{ required: true }],
-    marketid: [{ required: true }],
-    goodsid: [{ required: true }],
-}
-
-// 选择分组
-const onGroupChange = () => {
-    selectedMarket.value = undefined
-    selectedGoods.value = undefined
-}
-
-// 选择市场
-const onMarketChange = () => {
-    selectedGoods.value = undefined
-}
-
-const onSearch = () => {
-    qs.value = {
-        usergroupid: selectedGroup.value?.usergroupid,
-        marketid: selectedMarket.value?.marketid,
-        goodsid: selectedGoods.value?.goodsid,
+const nodeClick = (data: Model.InvestorTreeRsp, node: Node) => {
+    if (data.goodsid) {
+        selectedNode.value = node
+        currentInfo.goodsName = node.label
+        currentInfo.marketName = node.parent.label
+        currentInfo.groupName = node.parent.parent.label
+        getDetails(qs.value)
     }
-    nextTick(() => {
-        formRef.value?.validate((valid) => {
-            if (valid) {
-                info.value = {
-                    groupName: selectedGroup.value?.name,
-                    marketName: selectedMarket.value?.name,
-                    goodsName: selectedGoods.value?.name,
-                }
-                run(qs.value)
-            } else {
-                data.value = undefined
-            }
-        })
-    })
 }
 </script>

+ 304 - 0
src/packages/pc/views/member/institution/tradecfg/components/add/index.vue

@@ -0,0 +1,304 @@
+<!-- 会员机构管理-机构管理-账户个性化设置-新增 -->
+<template>
+    <app-drawer :title="t('investor.custom.tradecfg.edit.title')" width="1100" v-model:show="show" :refresh="refresh"
+        :loading="loading">
+        <el-form ref="formRef" label-width="140px" :model="formData" :rules="formRules" :show-message="false">
+            <fieldset class="g-fieldset el-form--horizontal">
+                <legend class="g-fieldset__legend">{{ t('investor.custom.tradecfg.edit.subtitle1') }}</legend>
+                <el-form-item label="个性化设置" @change="onTradeTypeChange">
+                    <el-radio-group v-model="formData.tradetype">
+                        <el-radio label="自营会员" :value="1" />
+                        <el-radio label="做市会员" :value="2" />
+                    </el-radio-group>
+                </el-form-item>
+                <el-form-item label="自营会员" prop="userid" v-if="formData.tradetype === 1">
+                    <app-select-member v-model="formData.userid" :params="{ usertype: '2', roles: '6' }"
+                        @change="onMemberChange" />
+                </el-form-item>
+                <el-form-item label="做市会员" prop="userid" v-if="formData.tradetype === 2">
+                    <app-select-member v-model="formData.userid" :params="{ usertype: '2', roles: '8' }"
+                        @change="onMemberChange" />
+                </el-form-item>
+                <el-form-item label="资金账户" prop="accountid">
+                    <el-select v-model="formData.accountid" @change="onAccountChange">
+                        <template v-for="(value, index) in accountData?.accountids" :key="index">
+                            <el-option :label="value" :value="value" />
+                        </template>
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="市场" prop="marketid">
+                    <el-select v-model="formData.marketid" @change="onMarketChange">
+                        <template v-for="(item, index) in marketData?.markets" :key="index">
+                            <el-option :label="item.marketname" :value="item.marketid" />
+                        </template>
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="商品" prop="goodsid">
+                    <el-select v-model="formData.goodsid" @change="onGoodsChange">
+                        <template v-for="(item, index) in goodsData?.goods" :key="index">
+                            <el-option :label="item.goodsname" :value="item.goodsid" />
+                        </template>
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="保证金类别" prop="paramid">
+                    <el-select v-model="formData.paramid" @change="onResultChange" clearable>
+                        <template v-for="(item, index) in resultData?.results" :key="index">
+                            <el-option :label="item.enumdicname" :value="item.enumitemname" />
+                        </template>
+                    </el-select>
+                </el-form-item>
+            </fieldset>
+            <app-table :data="resultData?.rulesConfigVos" :columns="ruleColumns">
+                <!-- 平台 -->
+                <template #paramvalue="{ row }">
+                    <span v-if="row.ruleid === 105">{{ feetypeEnum.getEnumTypeName(row.paramvalue) }}</span>
+                    <span v-else>{{ row.paramvalue || 0 }}</span>
+                </template>
+                <!-- 个性化 -->
+                <template #cvalue="{ row, index }">
+                    <template v-if="formData.ruleList[index]">
+                        <el-select v-model="formData.ruleList[index].paramvalue" clearable v-if="row.ruleid === 105">
+                            <template v-for="item in feetypeEnum.getEnumOptions()" :key="item.id">
+                                <el-option :label="item.label" :value="item.value" />
+                            </template>
+                        </el-select>
+                        <el-input-number v-model="formData.ruleList[index].paramvalue" :min="0"
+                            :placeholder="t('common.pleaseenter')" v-else />
+                    </template>
+                </template>
+            </app-table>
+            <app-table :data="resultData?.feesConfigVos" :columns="feeColumns">
+                <!-- 平台 -->
+                <template #exchangevalue="{ row }">
+                    <template v-if="row.feealgorithm">
+                        <span>{{ scfRiskMode.getEnumTypeName(row.feealgorithm) }}:</span>
+                        <span>{{ row.exchangevalue + (row.feealgorithm === 1 && '‱') }}</span>
+                    </template>
+                    <template v-else>
+                        {{ handleNoneValue() }}
+                    </template>
+                </template>
+                <!-- 服务费 -->
+                <template #fee="{ index }">
+                    <template v-if="formData.feeList[index]">
+                        <el-input-number v-model="formData.feeList[index].paramvalue" :min="0"
+                            :placeholder="t('common.pleaseenter')" />
+                    </template>
+                </template>
+            </app-table>
+        </el-form>
+        <template #footer>
+            <el-button @click="onCancel(false)">{{ t('operation.close') }}</el-button>
+            <el-button type="primary" @click="onSubmit">{{ t('operation.save') }}</el-button>
+        </template>
+    </app-drawer>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, computed } from 'vue'
+import { ElMessage, FormInstance, FormRules } from 'element-plus'
+import { handleNoneValue } from '@/filters'
+import { useEnum } from '@/hooks/enum'
+import { useRequest } from '@/hooks/request'
+import { loadSelectOption, tradeConfigAdd } from '@/services/api/member'
+import { i18n } from '@/stores'
+import AppDrawer from '@pc/components/base/drawer/index.vue'
+import AppTable from '@pc/components/base/table/index.vue'
+import AppSelectMember from '@pc/components/modules/select-member/index.vue'
+
+const feetypeEnum = useEnum('feetype')
+const scfRiskMode = useEnum('SCFRiskMode')
+
+const { global: { t } } = i18n
+const formRef = ref<FormInstance>()
+const show = ref(true)
+const refresh = ref(false)
+const loading = ref(false)
+
+const formData = reactive<Model.TradeConfigAddReq>({
+    memberuserid: 0,
+    feeList: [],
+    ruleList: [],
+    tradetype: 1
+})
+
+// 获取资金账户列表
+const { data: accountData, run: getAccountIds } = useRequest(loadSelectOption, {
+    manual: true,
+    params: {
+        memberuserid: 0
+    }
+})
+
+// 获取市场列表
+const { data: marketData, run: getMarketList } = useRequest(loadSelectOption, {
+    manual: true,
+    params: {
+        memberuserid: 0
+    }
+})
+
+// 获取商品列表
+const { data: goodsData, run: getGoodsList } = useRequest(loadSelectOption, {
+    manual: true,
+    params: {
+        memberuserid: 0
+    }
+})
+
+// 获取保证金类别列表
+const { data: resultData, run: getResultList } = useRequest(loadSelectOption, {
+    manual: true,
+    params: {
+        memberuserid: 0
+    },
+    onSuccess: (res) => {
+        formData.ruleList = []
+        formData.feeList = []
+
+        res.data.rulesConfigVos.forEach((e) => {
+            formData.ruleList.push({
+                paramid: e.ruleid,
+            })
+        })
+
+        res.data.feesConfigVos.forEach((e) => {
+            formData.feeList.push({
+                feealgorithm: e.feealgorithm,
+                paramid: e.tradefeeid,
+            })
+        })
+    }
+})
+
+const ruleColumns = computed<Model.TableColumn[]>(() => [
+    { field: 'rulename', label: '扩展项' },
+    { field: 'paramvalue', label: '平台' },
+    { field: 'cvalue', label: '个性化' },
+])
+
+const feeColumns = computed<Model.TableColumn[]>(() => [
+    { field: 'tradefeename', label: '费用项' },
+    { field: 'exchangevalue', label: '平台' },
+    { field: 'feealgorithm', label: '费用算法', formatValue: (val) => scfRiskMode.getEnumTypeName(val) },
+    { field: 'fee', label: '服务费', width: 200 },
+])
+
+// 表单验证规则
+const formRules: FormRules = {
+    userid: [{ required: true }],
+    accountid: [{ required: true }],
+    marketid: [{ required: true }],
+    goodsid: [{ required: true }],
+}
+
+// 选择个性化
+const onTradeTypeChange = () => {
+    formData.userid = undefined
+    formData.accountid = undefined
+    formData.marketid = undefined
+    formData.goodsid = undefined
+    formData.paramid = undefined
+    formData.feealgorithm = undefined
+    accountData.value = undefined
+    marketData.value = undefined
+    goodsData.value = undefined
+    resultData.value = undefined
+}
+
+// 选择会员
+const onMemberChange = (item?: Model.InitAccTradeRsp) => {
+    formData.accountid = undefined
+    formData.marketid = undefined
+    formData.goodsid = undefined
+    formData.paramid = undefined
+    formData.feealgorithm = undefined
+    accountData.value = undefined
+    marketData.value = undefined
+    goodsData.value = undefined
+    resultData.value = undefined
+    if (item) {
+        getAccountIds({
+            tradetype: formData.tradetype,
+            userid: item.userid,
+        })
+    }
+}
+
+// 选择资金账户
+const onAccountChange = (value: number) => {
+    formData.marketid = undefined
+    formData.goodsid = undefined
+    formData.paramid = undefined
+    formData.feealgorithm = undefined
+    marketData.value = undefined
+    goodsData.value = undefined
+    resultData.value = undefined
+    getMarketList({
+        tradetype: formData.tradetype,
+        userid: formData.userid,
+        accountid: value,
+    })
+}
+
+// 选择市场
+const onMarketChange = (value: number) => {
+    formData.goodsid = undefined
+    formData.paramid = undefined
+    formData.feealgorithm = undefined
+    goodsData.value = undefined
+    resultData.value = undefined
+    getGoodsList({
+        tradetype: formData.tradetype,
+        userid: formData.userid,
+        accountid: formData.accountid,
+        marketid: value,
+    })
+}
+
+// 选择商品
+const onGoodsChange = (value: number) => {
+    formData.paramid = undefined
+    formData.feealgorithm = undefined
+    getResultList({
+        tradetype: formData.tradetype,
+        userid: formData.userid,
+        accountid: formData.accountid,
+        marketid: formData.marketid,
+        goodsid: value
+    })
+}
+
+// 选择保证金类别
+const onResultChange = (value?: number) => {
+    const res = resultData.value?.results.find((e) => e.enumitemname === value)
+    formData.feealgorithm = res?.marginalgorithm
+}
+
+const onSubmit = () => {
+    formRef.value?.validate((valid) => {
+        if (valid) {
+            if (formData.paramid || formData.ruleList.some((e) => e.paramid === 105 && e.paramvalue)) {
+                loading.value = true
+                tradeConfigAdd({
+                    data: formData
+                }).then(() => {
+                    ElMessage.success(t('common.tips3'))
+                    onCancel(true)
+                }).catch((err) => {
+                    ElMessage.error(t('common.tips4') + err)
+                }).finally(() => {
+                    loading.value = false
+                })
+            } else {
+                ElMessage.warning('保证金类别,交易规则,交易服务费均未设置')
+            }
+        }
+    })
+}
+
+const onCancel = (isRefresh = false) => {
+    show.value = false
+    refresh.value = isRefresh
+}
+</script>

+ 57 - 0
src/packages/pc/views/member/institution/tradecfg/components/delete/index.vue

@@ -0,0 +1,57 @@
+<!-- 会员机构管理-机构管理-账户个性化设置-删除 -->
+<template>
+    <app-drawer :title="t('common.alert')" v-model:show="show" :loading="loading" :refresh="refresh">
+        <div class="g-text-message">{{ t('investor.custom.tradecfg.delete.tips') }}</div>
+        <template #footer>
+            <el-button @click="onCancel(false)">{{ t('operation.cancel') }}</el-button>
+            <el-button type="primary" @click="onSubmit">{{ t('operation.confirm') }}</el-button>
+        </template>
+    </app-drawer>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, PropType } from 'vue'
+import { ElMessage } from 'element-plus'
+import { accTradeConfigDelete } from '@/services/api/investor'
+import AppDrawer from '@pc/components/base/drawer/index.vue'
+import { i18n } from '@/stores'
+
+const props = defineProps({
+    record: {
+        type: Object as PropType<Model.AccTradeConfigViewRsp>,
+        required: true
+    }
+})
+
+const { global: { t } } = i18n
+const show = shallowRef(true)
+const refresh = shallowRef(false)
+const loading = shallowRef(false)
+
+const onCancel = (isRefresh = false) => {
+    show.value = false
+    refresh.value = isRefresh
+}
+
+const onSubmit = () => {
+    loading.value = true
+    accTradeConfigDelete({
+        data: {
+            memberuserid: props.record.memberuserid,
+            userid: props.record.userid,
+            marketid: props.record.marketid,
+            accountid: props.record.accountid,
+            goodsid: props.record.goodsid,
+            tradetype: props.record.tradetype,
+        }
+    }).then(() => {
+        ElMessage.success(t('common.tips5'))
+        onCancel(true)
+    }).catch((err) => {
+        ElMessage.error(t('common.tips6') + err)
+        onCancel()
+    }).finally(() => {
+        loading.value = false
+    })
+}
+</script>

+ 194 - 0
src/packages/pc/views/member/institution/tradecfg/components/edit/index.vue

@@ -0,0 +1,194 @@
+<!-- 会员机构管理-机构管理-账户个性化设置-编辑 -->
+<template>
+    <app-drawer :title="t('investor.custom.tradecfg.edit.title')" width="1100" v-model:show="show" :refresh="refresh"
+        :loading="loading">
+        <el-form ref="formRef" label-width="140px" :model="formData" :show-message="false">
+            <fieldset class="g-fieldset el-form--horizontal">
+                <legend class="g-fieldset__legend">{{ t('investor.custom.tradecfg.edit.subtitle1') }}</legend>
+                <el-form-item :label="memberItem?.tradetype === 1 ? '自营会员' : '做市会员'">
+                    {{ handleNoneValue(memberItem?.name) }}
+                </el-form-item>
+                <el-form-item label="资金账户">
+                    {{ handleNoneValue(accountItem?.name) }}
+                </el-form-item>
+                <el-form-item label="商品">
+                    {{ handleNoneValue(goodsItem?.name) }}
+                </el-form-item>
+                <el-form-item :label="t('investor.custom.tradecfg.edit.paramid')" prop="paramid">
+                    <el-select v-model="formData.paramid" @change="onResultChange" clearable>
+                        <template v-for="(item, index) in resultData?.results" :key="index">
+                            <el-option :label="item.enumdicname" :value="item.enumitemname" />
+                        </template>
+                    </el-select>
+                </el-form-item>
+            </fieldset>
+            <app-table :data="record.druleList" :columns="ruleColumns">
+                <!-- 平台 -->
+                <template #paramvalue="{ row }">
+                    <span v-if="row.ruleid === 105">{{ feetypeEnum.getEnumTypeName(row.paramvalue) }}</span>
+                    <span v-else>{{ row.paramvalue || 0 }}</span>
+                </template>
+                <!-- 个性化 -->
+                <template #cvalue="{ row, index }">
+                    <template v-if="formData.ruleList[index]">
+                        <el-select v-model="formData.ruleList[index].paramvalue" clearable v-if="row.ruleid === 105">
+                            <template v-for="item in feetypeEnum.getEnumOptions()" :key="item.id">
+                                <el-option :label="item.label" :value="item.value" />
+                            </template>
+                        </el-select>
+                        <el-input-number v-model="formData.ruleList[index].paramvalue"
+                            :placeholder="t('common.pleaseenter')" v-else />
+                    </template>
+                </template>
+            </app-table>
+            <app-table :data="record.dfeeList" :columns="feeColumns">
+                <!-- 平台 -->
+                <template #exchangevalue="{ row }">
+                    <template v-if="row.feealgorithm">
+                        <span>{{ scfRiskMode.getEnumTypeName(row.feealgorithm) }}:</span>
+                        <span>{{ row.exchangevalue + (row.feealgorithm === 1 && '‱') }}</span>
+                    </template>
+                    <template v-else>
+                        {{ handleNoneValue() }}
+                    </template>
+                </template>
+                <!-- 服务费 -->
+                <template #fee="{ index }">
+                    <template v-if="formData.feeList[index]">
+                        <el-input-number v-model="formData.feeList[index].paramvalue" :min="0"
+                            :placeholder="t('common.pleaseenter')" />
+                    </template>
+                </template>
+            </app-table>
+        </el-form>
+        <template #footer>
+            <el-button @click="onCancel(false)">{{ t('operation.close') }}</el-button>
+            <el-button type="primary" @click="onSubmit">{{ t('operation.save') }}</el-button>
+        </template>
+    </app-drawer>
+</template>
+
+<script lang="ts" setup>
+import { reactive, shallowRef, PropType, computed, onMounted } from 'vue'
+import { ElMessage, FormInstance } from 'element-plus'
+import { handleNoneValue } from '@/filters'
+import { useEnum } from '@/hooks/enum'
+import { useRequest } from '@/hooks/request'
+import { createTree } from '@/services/api/member'
+import { tradeConfigEdit } from '@/services/api/member'
+import { loadSelectOption } from '@/services/api/member'
+import AppDrawer from '@pc/components/base/drawer/index.vue'
+import AppTable from '@pc/components/base/table/index.vue'
+import { i18n } from '@/stores'
+
+const props = defineProps({
+    record: {
+        type: Object as PropType<Model.AccTradeConfigViewRsp>,
+        required: true
+    }
+})
+
+const feetypeEnum = useEnum('feetype')
+const scfRiskMode = useEnum('SCFRiskMode')
+
+const { global: { t } } = i18n
+const formRef = shallowRef<FormInstance>()
+const show = shallowRef(true)
+const refresh = shallowRef(false)
+const loading = shallowRef(false)
+
+const formData = reactive<Model.TradeConfigAddReq>({
+    memberuserid: props.record.memberuserid,
+    accountid: props.record.accountid,
+    feeList: [],
+    goodsid: props.record.goodsid,
+    marketid: props.record.marketid,
+    ruleList: [],
+    paramid: props.record.paramid,
+    tradetype: props.record.tradetype,
+    userid: props.record.userid,
+    feealgorithm: props.record.feealgorithm,
+})
+
+// 获取树列表
+const { dataList: nodeList } = useRequest(createTree)
+
+// 会员对象
+const memberItem = computed(() => nodeList.value.find((e) => e.pid === (-props.record.tradetype).toString() && e.userid === formData.userid))
+
+// 资金账户对象
+const accountItem = computed(() => nodeList.value.find((e) => e.pid === memberItem.value?.id && e.accountid === formData.accountid))
+
+// 商品对象
+const goodsItem = computed(() => nodeList.value.find((e) => e.pid === accountItem.value?.id && e.goodsid === formData.goodsid))
+
+// 获取保证金类别列表
+const { data: resultData } = useRequest(loadSelectOption, {
+    params: {
+        memberuserid: formData.marketid,
+        userid: formData.userid,
+        accountid: formData.accountid,
+        marketid: formData.marketid,
+        goodsid: formData.goodsid
+    }
+})
+
+const ruleColumns = computed<Model.TableColumn[]>(() => [
+    { field: 'rulename', label: '扩展项' },
+    { field: 'paramvalue', label: '平台' },
+    { field: 'cvalue', label: '个性化' },
+])
+
+const feeColumns = computed<Model.TableColumn[]>(() => [
+    { field: 'tradefeename', label: '费用项' },
+    { field: 'exchangevalue', label: '平台' },
+    { field: 'feealgorithm', label: '费用算法', formatValue: (val) => scfRiskMode.getEnumTypeName(val) },
+    { field: 'fee', label: '服务费', width: 200 },
+])
+
+// 选择保证金类别
+const onResultChange = (value?: number) => {
+    const res = resultData.value?.results.find((e) => e.enumitemname === value)
+    formData.feealgorithm = res?.marginalgorithm
+}
+
+const onSubmit = () => {
+    formRef.value?.validate((valid) => {
+        if (valid) {
+            if (formData.paramid || formData.ruleList.some((e) => e.paramid === 105 && e.paramvalue)) {
+                loading.value = true
+                tradeConfigEdit({
+                    data: formData
+                }).then(() => {
+                    ElMessage.success(t('common.tips3'))
+                    onCancel(true)
+                }).catch((err) => {
+                    ElMessage.error(t('common.tips4') + err)
+                }).finally(() => {
+                    loading.value = false
+                })
+            } else {
+                ElMessage.warning('保证金类别,交易规则,交易服务费均未设置')
+            }
+        }
+    })
+}
+
+const onCancel = (isRefresh = false) => {
+    show.value = false
+    refresh.value = isRefresh
+}
+
+onMounted(() => {
+    formData.ruleList = props.record.druleList.map((e) => ({
+        paramid: e.ruleid,
+        paramvalue: e.cvalue
+    }))
+
+    formData.feeList = props.record.dfeeList.map((e) => ({
+        feealgorithm: e.feealgorithm,
+        paramid: e.tradefeeid,
+        paramvalue: e.cvalue
+    }))
+})
+</script>

+ 183 - 1
src/packages/pc/views/member/institution/tradecfg/index.vue

@@ -1,7 +1,189 @@
 <!-- 会员机构管理-机构管理-账户个性化设置 -->
 <template>
-    <app-view></app-view>
+    <div class="g-view-tree">
+        <app-view>
+            <el-tree :data="nodeList" node-key="id" :props="{ label: 'name' }" :default-expanded-keys="['-1', '-2']"
+                @node-click="nodeClick" highlight-current />
+        </app-view>
+        <app-view>
+            <div style="display: flex;justify-content: space-between;">
+                <app-operation :data-list="getActionButtons(['member_institution_tradecfg_add'])"
+                    @click="openComponent" />
+                <app-operation
+                    :data-list="getActionButtons(['member_institution_tradecfg_modify', 'member_institution_tradecfg_delete'])"
+                    @click="openComponent" v-if="data" />
+            </div>
+            <template v-if="data">
+                <app-table-details title="基本信息" :data="currentInfo" :label-width="160" :cell-props="detailProps"
+                    :column="2" />
+                <app-table :data="data.druleList" :columns="ruleColumns">
+                    <template #headerLeft>
+                        <b>交易规则</b>
+                    </template>
+                    <!-- 平台 -->
+                    <template #paramvalue="{ row }">
+                        <span v-if="row.ruleid === 105">{{ feetypeEnum.getEnumTypeName(row.paramvalue) }}</span>
+                        <span v-else>{{ row.paramvalue || 0 }}</span>
+                    </template>
+                    <!-- 个性化 -->
+                    <template #cvalue="{ row }">
+                        <span v-if="row.ruleid === 105">
+                            {{ handleNoneValue(feetypeEnum.getEnumTypeName(row.cvalue)) }}
+                        </span>
+                        <span v-else>{{ handleNoneValue(row.cvalue) }}</span>
+                    </template>
+                </app-table>
+                <app-table :data="data.dfeeList" :columns="feeColumns">
+                    <template #headerLeft>
+                        <b>交易服务费</b>
+                    </template>
+                    <!-- 平台 -->
+                    <template #exchangevalue="{ row }">
+                        <template v-if="row.feealgorithm">
+                            <span>{{ scfRiskMode.getEnumTypeName(row.feealgorithm) }}:</span>
+                            <span>{{ row.exchangevalue + (row.feealgorithm === 1 && '‱') }}</span>
+                        </template>
+                        <template v-else>
+                            {{ handleNoneValue() }}
+                        </template>
+                    </template>
+                    <!-- 个性化 -->
+                    <template #cvalue="{ row }">
+                        <template v-if="row.crithm">
+                            <span>{{ scfRiskMode.getEnumTypeName(row.crithm) }}:</span>
+                            <span>{{ row.cvalue + (row.crithm === 1 && '‱') }}</span>
+                        </template>
+                        <template v-else>
+                            {{ handleNoneValue() }}
+                        </template>
+                    </template>
+                </app-table>
+            </template>
+            <el-empty v-else />
+        </app-view>
+        <component :is="componentMap.get(componentId)" v-bind="{ record: toRaw(data) }" @closed="closeComponent"
+            v-if="componentId" />
+    </div>
 </template>
 
 <script lang="ts" setup>
+import { ref, reactive, computed, toRaw } from 'vue'
+import { ElMessage } from 'element-plus'
+import type Node from 'element-plus/es/components/tree/src/model/node'
+import { handleNoneValue, buildTree } from '@/filters'
+import { useEnum } from '@/hooks/enum'
+import { useRequest } from '@/hooks/request'
+import { useOperation } from '@/hooks/operation'
+import { createTree, OrganDetailTradeConfigView } from '@/services/api/member'
+import { CellProp } from '@pc/components/base/table-details/types'
+import AppTable from '@pc/components/base/table/index.vue'
+import AppOperation from '@pc/components/base/operation/index.vue'
+import AppTableDetails from '@pc/components/base/table-details/index.vue'
+
+const customerTypeEnum = useEnum('customerType')
+const feetypeEnum = useEnum('feetype')
+const scfRiskMode = useEnum('SCFRiskMode')
+
+const nodeList = ref<(Model.CreateTreeRsp & { children: Model.CreateTreeRsp[]; })[]>([])
+const selectedNode = ref<Node>()
+
+const currentInfo = reactive({
+    userName: '',
+    accountName: '',
+    goodsName: '',
+    customerType: ''
+})
+
+const qs = computed<Model.OrganDetailTradeConfigViewReq>(() => {
+    const data = selectedNode.value?.data
+    return {
+        userid: data?.userid,
+        accountid: data?.accountid,
+        marketid: data?.marketid,
+        goodsid: data?.goodsid,
+        tradetype: data?.tradetype,
+        ruletype: 1
+    }
+})
+
+const { componentMap, componentId, openComponent, closeComponent, getActionButtons } = useOperation<Model.OrganDetailTradeConfigViewRsp>({
+    onClose: (componentId) => {
+        getNodeList()
+        if (componentId === 'member_institution_tradecfg_modify') {
+            getDetails(qs.value)
+        }
+        if (componentId === 'member_institution_tradecfg_delete') {
+            data.value = undefined
+        }
+    }
+})
+
+const { run: getNodeList } = useRequest(createTree, {
+    onSuccess: (res) => {
+        res.data.push(
+            {
+                accountid: 0,
+                goodsid: 0,
+                id: '-1',
+                level: 0,
+                marketid: 0,
+                name: '自营会员账户个性化设置',
+                pid: '0',
+                tradetype: 1,
+                userid: 0,
+            },
+            {
+                accountid: 0,
+                goodsid: 0,
+                id: '-2',
+                level: 0,
+                marketid: 0,
+                name: '做市会员账户个性化设置',
+                pid: '0',
+                tradetype: 2,
+                userid: 0,
+            }
+        )
+        nodeList.value = buildTree(res.data, 'id', 'pid', (node) => node.pid === '0')
+    },
+    onError: (err) => {
+        ElMessage.error(err)
+    }
+})
+
+const { data, run: getDetails } = useRequest(OrganDetailTradeConfigView, {
+    manual: true,
+    onError: (err) => {
+        ElMessage.error(err)
+    }
+})
+
+const detailProps = computed<CellProp[]>(() => [
+    { prop: 'userName', formatLabel: () => (data.value?.tradetype === 1 ? '自营' : '做市') + '会员' },
+    { prop: 'accountName', label: '资金账户' },
+    { prop: 'goodsName', label: '商品' },
+    { prop: 'customerType', label: '保证金类别', formatValue: () => customerTypeEnum.getEnumTypeName(data.value?.paramid) },
+])
+
+const ruleColumns = computed<Model.TableColumn[]>(() => [
+    { field: 'rulename', label: '扩展项' },
+    { field: 'paramvalue', label: '平台' },
+    { field: 'cvalue', label: '个性化' },
+])
+
+const feeColumns = computed<Model.TableColumn[]>(() => [
+    { field: 'tradefeename', label: '费用项' },
+    { field: 'exchangevalue', label: '平台' },
+    { field: 'cvalue', label: '个性化' },
+])
+
+const nodeClick = (data: Model.CreateTreeRsp, node: Node) => {
+    if (data.goodsid) {
+        selectedNode.value = node
+        currentInfo.goodsName = node.label
+        currentInfo.accountName = node.parent.label
+        currentInfo.userName = node.parent.parent.label
+        getDetails(qs.value)
+    }
+}
 </script>

+ 0 - 21
src/packages/pc/views/member/subinstitution/manage/index.less

@@ -1,21 +0,0 @@
-.split-layout {
-    display: flex;
-    padding: 10px;
-
-    &__left,
-    &__right {
-        background-color: #fff;
-        border-radius: 4px;
-        padding: 20px;
-        margin: 10px;
-    }
-
-    &__left{
-        min-width: 240px;
-    }
-
-    &__right{
-        flex: 1;
-        overflow-x: auto;
-    }
-}

+ 8 - 12
src/packages/pc/views/member/subinstitution/manage/index.vue

@@ -1,7 +1,7 @@
 <!-- 会员机构管理-子机构管理-子机构管理 -->
 <template>
-    <div class="split-layout">
-        <div class="split-layout__left">
+    <div class="g-view-tree">
+        <app-view>
             <el-tree ref=treeRef :data="treeList" node-key="userid" :expand-on-click-node="false"
                 @node-click="nodeClick" default-expand-all highlight-current>
                 <template #default="{ data }">
@@ -10,8 +10,8 @@
                     </span>
                 </template>
             </el-tree>
-        </div>
-        <div class="split-layout__right">
+        </app-view>
+        <app-view>
             <app-operation :data-list="handleHeaderButtons()" @click="openComponent" v-if="selectedParent" />
             <app-table-details :data="selectedParent" :label-width="120" label-align="left" :cell-props="detailProps"
                 :column="3" />
@@ -26,7 +26,7 @@
                         @change="onSearch" />
                 </template>
             </app-table>
-        </div>
+        </app-view>
         <component :is="componentMap.get(componentId)" v-bind="{ selectedParent, record }" @closed="closeComponent"
             v-if="componentId" />
     </div>
@@ -167,12 +167,8 @@ const onSearch = (clear = false) => {
     })
 }
 
-const nodeClick = (node: Model.AreaAndAllChildsRsp) => {
-    selectedParent.value = node
+const nodeClick = (data: Model.AreaAndAllChildsRsp) => {
+    selectedParent.value = data
     onSearch(true)
 }
-</script>
-
-<style lang="less">
-@import './index.less';
-</style>
+</script>

+ 1 - 1
src/services/api/investor/index.ts

@@ -298,7 +298,7 @@ export function modifyAudit(options: CommonFetchOptions<{ request: Model.ModifyA
 /**
  * 交易商管理-->个性化管理-->资金账户个性化-->获取树结构
  */
-export function getAccTradeTree(options: CommonFetchOptions<{ response: Model.AccTradeTreeRsp; }>) {
+export function getAccTradeTree(options: CommonFetchOptions<{ response: Model.AccTradeTreeRsp[]; }>) {
     return httpClient.commonRequest('/investor/getAccTradeTree', 'get', options)
 }
 

+ 14 - 0
src/services/api/member/index.ts

@@ -377,4 +377,18 @@ export function tradeConfigAdd(options: CommonFetchOptions<{ request: Model.Trad
  */
 export function tradeConfigEdit(options: CommonFetchOptions<{ request: Model.TradeConfigAddReq; }>) {
     return httpClient.commonRequest('/organDetail/tradeConfigEdit', 'post', options)
+}
+
+/**
+ * 会员机构管理-->机构管理-->账户个性化设置--> 获取树结构
+ */
+export function createTree(options: CommonFetchOptions<{ response: Model.CreateTreeRsp[]; }>) {
+    return httpClient.commonRequest('/organDetail/createTree', 'get', options)
+}
+
+/**
+ * 会员机构管理-->机构管理-->账户个性化设置--> 详情
+ */
+export function OrganDetailTradeConfigView(options: CommonFetchOptions<{ request: Model.OrganDetailTradeConfigViewReq; response: Model.OrganDetailTradeConfigViewRsp; }>) {
+    return httpClient.commonRequest('/organDetail/tradeConfigView', 'get', options)
 }

+ 73 - 0
src/types/model/member.d.ts

@@ -2007,4 +2007,77 @@ declare namespace Model {
         tradetype: number;
         userid?: number;
     }
+
+    /** 会员机构管理-->机构管理-->账户个性化设置--> 获取树结构 响应 */
+    interface CreateTreeRsp {
+        accountid: number;
+        goodsid: number;
+        id: string;
+        level: number;
+        marketid: number;
+        name: string;
+        pid: string;
+        tradetype: number;
+        userid: number;
+    }
+
+    /** 会员机构管理-->机构管理-->账户个性化设置--> 详情 请求 */
+    interface OrganDetailTradeConfigViewReq {
+        accountid: number;
+        goodsid: number;
+        marketid: number;
+        memberuserid?: number;
+        ruletype: number;
+        tradetype: number;
+        usergroupid?: number;
+        usergroupid2?: number;
+        userid: number;
+    }
+
+    /** 会员机构管理-->机构管理-->账户个性化设置--> 详情 响应 */
+    interface OrganDetailTradeConfigViewRsp {
+        accountid: number; // 资金账号
+        accountname: string;
+        autoid: number; // 自增ID
+        createtime: string; // 创建时间
+        creatorid: number; // 创建人
+        dfeeList: {
+            crithm: number;
+            cvalue: number;
+            drithm: number;
+            dvalue: number;
+            exchangevalue: number;
+            feealgorithm: number;
+            grithm: number;
+            gvalue: number;
+            membermaxvalue: number;
+            memberminvalue: number;
+            qtydecimalplace: number;
+            tradefeeid: number;
+            tradefeename: string;
+        }[];
+        druleList: {
+            cvalue: number;
+            defaultvalue: number;
+            dvalue: number;
+            gvalue: number;
+            paramvalue: number;
+            qtydecimalplace: number;
+            regexpress: string;
+            remark: string;
+            ruleid: number;
+            rulename: string;
+        }[];
+        feealgorithm: number; // 费用算法 - 1:比率 2:固定
+        goodsid: number; // 商品ID
+        goodsname: string;
+        marketid: number; // 市场ID
+        marketname: string;
+        memberuserid: number; // 所属会员ID
+        paramid: number; // 个性化参数ID - 1. 参照交易模式规则费用对应-参考表 可个性化类型 2.保证金类保存交易所创建的投资者客户类别ID
+        paramvalue: number; // 参数值(保证金类此项为空)
+        ruletype: number; // 参数类型 -1.保证金类 2.交易规则 3.交易费用
+        tradetype: number; // 交易类型[交易费用] - 1:投资者自营 2:做市
+        userid: number; // 用户ID
+    }
 }