基金联调完成

V0.1
yujialong 1 year ago
parent 8e63fe4b83
commit 7e291e6722
  1. 2
      .env
  2. 7
      src/api/fund.ts
  3. 0
      src/assets/images/role/fund.png
  4. 0
      src/assets/images/role/fund1.png
  5. 9
      src/layout/components/AppSidebar/Menu.vue
  6. 2
      src/router/index.ts
  7. 6
      src/views/Role.vue
  8. 2
      src/views/config/level/Index.vue
  9. 596
      src/views/product/fund/Add.vue
  10. 137
      src/views/product/fund/CardList.vue
  11. 21
      src/views/product/fund/Detail.vue
  12. 105
      src/views/product/fund/Info.vue
  13. 155
      src/views/product/fund/List.vue
  14. 16
      src/views/product/insurance/Add.vue

@ -2,7 +2,7 @@ VITE_APP_TITLE=金融产品设计及数字化营销沙盘
VITE_PORT=9520 VITE_PORT=9520
VITE_PROXY=http://192.168.31.125:8080 VITE_PROXY=http://192.168.31.125:8080
VITE_PUBLIC_PATH=./ VITE_PUBLIC_PATH=./
VITE_BASE_API=http://192.168.31.51:9000 VITE_BASE_API=http://192.168.31.217:9000
# VITE_BASE_API=http://121.37.12.51 # VITE_BASE_API=http://121.37.12.51
VITE_I18N_LOCALE=zh-cn VITE_I18N_LOCALE=zh-cn
VITE_I18N_FALLBACK_LOCALE=zh-cn VITE_I18N_FALLBACK_LOCALE=zh-cn

@ -0,0 +1,7 @@
import axios from '@/utils/request';
// 保险
export const saveFund = async (data: Record<string, any>): Promise<any> => (await axios.post('/product/fundProducts/save', data)).data;
export const batchDeletion = async (data: number[]): Promise<any> => (await axios.post(`/product/fundProducts/batchDeletion`, data)).data;
export const fundProductList = async (data: Record<string, any>): Promise<any> => (await axios.post(`/product/fundProducts/fundProductList`, data)).data;
export const findById = async (id: number): Promise<any> => (await axios.post(`/product/fundProducts/findById?id=${id}`)).data;
export const getAListOfAShares = async (keyWord: string): Promise<any> => (await axios.post(`/product/aShares/data/getAListOfAShares?keyWord=${keyWord}`)).data;

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

@ -81,6 +81,14 @@
alt="" /> alt="" />
<p class="text">保险产品</p> <p class="text">保险产品</p>
</li> </li>
<!-- 基金 -->
<li v-else-if="route.path.includes('fund')"
class="active">
<img class="icon-1"
src="@/assets/images/icon3-1.png"
alt="" />
<p class="text">基金产品</p>
</li>
<!-- 配置 --> <!-- 配置 -->
<template v-else> <template v-else>
<li :class="{ active: route.path === '/config/index' }" <li :class="{ active: route.path === '/config/index' }"
@ -104,7 +112,6 @@
<p class="text">关卡配置</p> <p class="text">关卡配置</p>
</li> </li>
</template> </template>
</ul> </ul>
</template> </template>

@ -38,6 +38,8 @@ export const routes: Array<RouteRecordRaw> = [
{ path: 'insurance', component: () => import('@/views/product/insurance/List.vue'), meta: { title: '保险产品' } }, { path: 'insurance', component: () => import('@/views/product/insurance/List.vue'), meta: { title: '保险产品' } },
{ path: 'insurance/:action', component: () => import('@/views/product/insurance/CardList.vue'), meta: { title: '保险产品' } }, { path: 'insurance/:action', component: () => import('@/views/product/insurance/CardList.vue'), meta: { title: '保险产品' } },
{ path: 'interestRate/:action', component: () => import('@/views/product/interestRate/CardList.vue'), meta: { title: '利率定价模型' } }, { path: 'interestRate/:action', component: () => import('@/views/product/interestRate/CardList.vue'), meta: { title: '利率定价模型' } },
{ path: 'fund', component: () => import('@/views/product/fund/List.vue'), meta: { title: '基金产品' } },
{ path: 'fund/:action', component: () => import('@/views/product/fund/CardList.vue'), meta: { title: '基金产品' } },
], ],
}, },
{ {

@ -59,7 +59,7 @@
</el-tooltip> </el-tooltip>
</div> </div>
</div> </div>
<div class="relative min-h-[calc(100vh-64px)] bg-[url('@/assets/images/role/bg.png')] bg-[length:100%_auto] bg-no-repeat"> <div class="relative min-h-[calc(100vh-64px)] bg-[url('@/assets/images/role/bg.png')] bg-[length:100%] bg-no-repeat">
<div class="absolute top-5 left-[18px] w-[204px] h-[68px] bg-[url('@/assets/images/role/2.png')] bg-[length:100%_100%] bg-no-repeat"></div> <div class="absolute top-5 left-[18px] w-[204px] h-[68px] bg-[url('@/assets/images/role/2.png')] bg-[length:100%_100%] bg-no-repeat"></div>
<div class="role top-[310px] left-[340px] bg-[url('@/assets/images/role/product.png')] hover:bg-[url('@/assets/images/role/product1.png')]" <div class="role top-[310px] left-[340px] bg-[url('@/assets/images/role/product.png')] hover:bg-[url('@/assets/images/role/product1.png')]"
@ -70,6 +70,8 @@
@click="selecRole(42)"></div> @click="selecRole(42)"></div>
<div class="role bottom-[50px] left-[100px] bg-[url('@/assets/images/role/insurance.png')] hover:bg-[url('@/assets/images/role/insurance1.png')]" <div class="role bottom-[50px] left-[100px] bg-[url('@/assets/images/role/insurance.png')] hover:bg-[url('@/assets/images/role/insurance1.png')]"
@click="selecRole(275)"></div> @click="selecRole(275)"></div>
<div class="role bottom-[50px] left-[550px] bg-[url('@/assets/images/role/fund.png')] hover:bg-[url('@/assets/images/role/fund1.png')]"
@click="selecRole(276)"></div>
</div> </div>
<!-- <div class="fixed top-[80px] right-[80px]"> <!-- <div class="fixed top-[80px] right-[80px]">
<div class="flex items-center h-[60px] px-4 rounded-tl-[20px] rounded-tr-[20px]" <div class="flex items-center h-[60px] px-4 rounded-tl-[20px] rounded-tr-[20px]"
@ -193,7 +195,7 @@ const getLevel = async () => {
}; };
// //
const selecRole = (id: number) => { const selecRole = (id: number) => {
router.push(id === 275 ? `/product/insurance` : `/product/bank?type=0&i=1&role=${id}`); router.push(id === 275 ? `/product/insurance` : id === 276 ? `/product/fund` : `/product/bank?type=0&i=1&role=${id}`);
}; };
// //
const toLevel = () => { const toLevel = () => {

@ -256,6 +256,7 @@ const toAdd = () => {
behavior: 'smooth', behavior: 'smooth',
}); });
}); });
hadChange.value = 1;
}; };
// //
@ -264,6 +265,7 @@ const edit = async (row: Record<string, any>) => {
row.oldName = row.customsPassName; row.oldName = row.customsPassName;
} }
row.editing = !row.editing; row.editing = !row.editing;
hadChange.value = 1;
}; };
// //
const cancel = async (row: Record<string, any>) => { const cancel = async (row: Record<string, any>) => {

@ -0,0 +1,596 @@
<template>
<div>
<el-tabs v-model="curTab">
<el-tab-pane :label="id ? '产品要素' : '新增产品'"
name="tab1">
<el-form label-width="100px"
label-suffix=":"
class="form"
status-icon>
<el-form-item label="基金名称"
prop="fundName">
<el-input placeholder="取个有吸引力的产品名,限20字。"
maxlength="20"
v-model="form.fundName"></el-input>
</el-form-item>
<el-form-item label="基金类型"
prop="fundType">
<el-select v-model="form.fundType"
placeholder="请选择">
<el-option v-for="item in config[1]?.subject?.itemList"
:key="item"
:label="item.options"
:value="item.itemId" />
</el-select>
</el-form-item>
<el-form-item label="募集规模">
<div>
<el-input placeholder="请输入"
v-model="form.fundraisingScale">
<template #append>万元</template>
</el-input>
</div>
</el-form-item>
<el-form-item label="运作方式"
prop="modeOfOperation">
<el-select v-model="form.modeOfOperation"
placeholder="请选择">
<el-option v-for="item in config[3]?.subject?.itemList"
:key="item"
:label="item.options"
:value="item.itemId" />
</el-select>
</el-form-item>
<el-form-item label="持股配置">
<div class="flex-1">
<p class="">输入心仪的A股代码或名称组建属于你的股票基金比重合计必须为100%最多配置10支股票</p>
<div class="flex">
<p class="field-name w-[300px] mr-32">股票</p>
<p class="field-name w-[100px] mr-32">比重(%)</p>
<div class="field-name">
<el-icon class="cursor-pointer"
:size="16"
color="#333"
@click="addStock">
<Plus />
</el-icon>
</div>
</div>
<div v-for="(item, i) in form.shareholdingAllocationsList"
:key="i"
class="flex items-center mb-2">
<div class="w-[300px] mr-32">
<el-select-v2 class="w-full"
v-model="item.stockCode"
filterable
remote
:remote-method="val => remoteMethod(val, item)"
clearable
:options="item.stocks"
:loading="loading"
placeholder="请输入A股代码或者名称" />
</div>
<div class="w-[100px] mr-32">
<el-input placeholder="请输入"
v-model="item.proportion"></el-input>
</div>
<div>
<el-icon class="cursor-pointer"
:size="16"
color="#333"
@click="delStock(i)">
<Minus />
</el-icon>
</div>
</div>
</div>
</el-form-item>
<el-form-item label="费率配置">
<div class="flex-1">
<p class="mt-1 mb-2 text-base">确认份额时长</p>
<!-- 份额时长 -->
<div class="flex items-center mb-3">
客户T时买入T+
<el-input class="w-[100px] mx-2"
placeholder="请输入"
v-model="form.buyingDuration"></el-input>
<el-select class="w-[100px] mr-2"
v-model="form.buyingUnit"
placeholder="请选择">
<el-option v-for="item in config[5]?.recordChildren[0]?.recordChildren[1]?.subject?.itemList"
:key="item"
:value="item.options" />
</el-select>
确认申购份额期间资金进入冻结账户
</div>
<div class="flex items-center">
客户T时卖出T+
<el-input class="w-[100px] mx-2"
placeholder="请输入"
v-model="form.sellingTime"></el-input>
<el-select class="w-[100px] mr-2"
v-model="form.soldUnit"
placeholder="请选择">
<el-option v-for="item in config[5]?.recordChildren[0]?.recordChildren[1]?.subject?.itemList"
:key="item"
:value="item.options" />
</el-select>
确认赎回金额期间资金进入冻结账户
</div>
<!-- 买入费率 -->
<p class="mt-10 mb-2 text-xs text-[#ef3838]">买入费率单笔费率应小于5.00%5000</p>
<div class="flex">
<p class="field-name w-[400px] mr-32">金额</p>
<p class="field-name w-[100px] mr-32">费率</p>
<div class="field-name">
<el-icon class="cursor-pointer"
:size="16"
color="#333"
@click="addRatio(0)">
<Plus />
</el-icon>
</div>
</div>
<div v-for="(item, i) in form.buySellRatioList0"
:key="i"
class="flex items-center mb-2">
<div class="w-[400px] mr-10">
<el-input v-if="i"
class="w-[100px] mr-2"
placeholder="请输入"
disabled
v-model="form.buySellRatioList0[i - 1].input2"></el-input>
<el-input v-else
class="w-[100px] mr-2"
placeholder="请输入"
disabled
v-model="item.input1"></el-input>
{{ i ? '万' : '&emsp;' }}
<span class="mx-2">
&lt;= 买入金额</span>
<template v-if="i !== form.buySellRatioList0.length - 1">
&lt;
<el-input class="w-[100px] mx-2"
placeholder="请输入"
v-model="item.input2"></el-input>
</template>
</div>
<div class="mr-32">
<el-input class="w-[100px] mr-2"
placeholder="请输入"
v-model="item.rate"></el-input>
<el-select class="w-[80px]"
v-model="item.rateUnit"
placeholder="请选择">
<el-option v-for="item in units"
:key="item"
:value="item" />
</el-select>
</div>
<div v-if="i">
<el-icon class="cursor-pointer"
:size="16"
color="#333"
@click="delRatio(0, i)">
<Minus />
</el-icon>
</div>
</div>
<div class="mt-5 text-xs text-[#7a7a7a]">
<p>申购计算</p>
<p class="my-2">净申购金额 = 申购金额 / (1 + 申购费用)</p>
<p>申购费用 = 申购金额 - 净申购金额</p>
<p class="mt-2">申购份额 = 净申购金额 / T时基金份额净值</p>
</div>
<!-- 运作费率 -->
<p class="mt-10 mb-2 text-xs text-[#ef3838]">运作费率单笔费率应小于5.00%</p>
<div class="flex">
<span class="w-[140px]">管理费</span>
<el-input class="w-[100px] mr-2"
placeholder="请输入"
v-model="form.operationManagementRate"></el-input>%
</div>
<div class="flex my-2">
<span class="w-[140px]">托管费</span>
<el-input class="w-[100px] mr-2"
placeholder="请输入"
v-model="form.operatingEscrowRate"></el-input>%
</div>
<div class="flex">
<span class="w-[140px]">销售服务费</span>
<el-input class="w-[100px] mr-2"
placeholder="请输入"
v-model="form.operatingSalesServiceRates"></el-input>%
</div>
<div class="mt-5 text-xs text-[#7a7a7a]">
<p>运作费计算</p>
<p class="my-2">管理费 = 前一日的基金资产净值 x 管理费率/ 365</p>
<p>托管费 = 前一日的基金资产净值 x 管理费率/ 365</p>
<p class="mt-2">销售服务费 = 前一日的基金资产净值 x 管理费率/ 365</p>
</div>
<!-- 赎回费率 -->
<p class="mt-10 mb-2 text-xs text-[#ef3838]">赎回费率单笔费率应小于5.00%5000</p>
<div class="flex">
<p class="field-name w-[600px] mr-32">持有时长</p>
<p class="field-name w-[100px] mr-32">费率</p>
<div class="field-name">
<el-icon class="cursor-pointer"
:size="16"
color="#333"
@click="addRatio(1)">
<Plus />
</el-icon>
</div>
</div>
<div v-for="(item, i) in form.buySellRatioList1"
:key="i"
class="flex items-center mb-2">
<div class="w-[600px] mr-10">
<el-input v-if="i"
class="w-[100px] mr-2"
placeholder="请输入"
disabled
v-model="form.buySellRatioList1[i - 1].input2"></el-input>
<el-input v-else
class="w-[100px] mr-2"
placeholder="请输入"
disabled
v-model="item.input1"></el-input>
<el-select class="w-[100px]"
v-model="item.soldUnit1"
placeholder="请选择">
<el-option v-for="item in times"
:key="item"
:value="item" />
</el-select>
<span class="mx-2">
&lt;= 持有时长</span>
<template v-if="i !== form.buySellRatioList1.length - 1">
&lt;
<el-input class="w-[100px] mx-2"
placeholder="请输入"
v-model="item.input2"></el-input>
<el-select class="w-[100px]"
v-model="item.soldUnit2"
placeholder="请选择">
<el-option v-for="item in times"
:key="item"
:value="item" />
</el-select>
</template>
</div>
<div class="mr-32">
<el-input class="w-[100px] mr-2"
placeholder="请输入"
v-model="item.rate"></el-input>
<el-select class="w-[80px]"
v-model="item.rateUnit"
placeholder="请选择">
<el-option v-for="item in units"
:key="item"
:value="item" />
</el-select>
</div>
<div>
<el-icon class="cursor-pointer"
:size="16"
color="#333"
@click="delRatio(1, i)">
<Minus />
</el-icon>
</div>
</div>
<div class="mt-5 text-xs text-[#7a7a7a]">
<p>赎回计算</p>
<p class="my-2">赎回费用 = 赎回总额 x 赎回费率</p>
<p>赎回金额 = 赎回总额 - 赎回费用</p>
</div>
</div>
</el-form-item>
<div class="flex justify-end">
<div class="submit"
@click="submit">完成</div>
</div>
</el-form>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch, onMounted, defineEmits } from 'vue';
import { ElMessage } from 'element-plus';
import { Plus, Minus } from '@element-plus/icons-vue';
import { insuranceProductDetails, addInsuranceProducts } from '@/api/insurance';
import { getAListOfAShares, saveFund } from '@/api/fund';
import { getProcessInformationBasedOnRoles, addOperation } from '@/api/judgment';
import { useRouter, useRoute } from 'vue-router';
import { handleId } from '@/utils/common';
import { getStatus } from '@/store/useProduct';
import Info from './Info.vue';
import Cookies from 'js-cookie';
const emit = defineEmits(['getList']);
const router = useRouter();
const route = useRoute();
const id = computed(() => route.query.id);
const projectId = +Cookies.get('sand-projectId');
const levelId = +Cookies.get('sand-level');
const curTab = ref<string>('tab1');
const config = ref<any[]>([]);
const info = ref<Record<string, any>>(null);
const units = ref<string[]>(['%', '元']);
const times = ref<string[]>(['分钟', '天', '小时', '月']);
const loading = ref<boolean>(false);
const form = reactive({
checkPointId: levelId,
projectId,
buyingDuration: '',
buyingUnit: '天',
fundName: '',
fundType: '',
fundraisingScale: '',
modeOfOperation: '',
operatingEscrowRate: '',
operatingSalesServiceRates: '',
operationManagementRate: '',
sellingTime: '',
soldUnit: '天',
buySellRatioList0: [
{
input1: '0',
input2: '',
rate: '',
rateUnit: '%',
type: 0,
},
{
input1: '',
input2: '',
rate: '',
rateUnit: '%',
type: 0,
},
],
buySellRatioList1: [
{
input1: '0',
input2: '',
rate: '',
rateUnit: '%',
soldUnit1: '分钟',
soldUnit2: '分钟',
type: 1,
},
{
input1: '',
input2: '',
rate: '',
rateUnit: '%',
soldUnit1: '分钟',
soldUnit2: '分钟',
type: 1,
},
],
shareholdingAllocationsList: [
{
latestPrice: '',
proportion: '',
stockCode: '',
stockName: '',
stocks: [],
},
],
});
//
const getConfig = async () => {
const { process } = await getProcessInformationBasedOnRoles(1161);
config.value = process;
};
//
const getDetail = async () => {
if (id.value) {
try {
const { data } = await insuranceProductDetails(id.value);
info.value = data;
} finally {
}
}
};
//
const addStock = () => {
form.shareholdingAllocationsList.length < 10
? form.shareholdingAllocationsList.push({
latestPrice: '',
proportion: '',
stockCode: '',
stockName: '',
stocks: [],
})
: ElMessage.error('持股最多不能超过10只!');
};
//
const delStock = (i: number) => {
form.shareholdingAllocationsList.length > 1 && form.shareholdingAllocationsList.splice(i, 1);
};
//
const remoteMethod = (query: string, item: Record<string, any>) => {
if (query !== '') {
loading.value = true;
setTimeout(async () => {
loading.value = false;
const { aShares } = await getAListOfAShares(query);
aShares.forEach((e) => {
e.label = e.f14;
e.value = e.f12;
});
item.stocks = aShares;
}, 200);
} else {
item.stocks = [];
}
};
//
const addRatio = (type: number) => {
if (form['buySellRatioList' + type].length === 5) {
ElMessage.error('规则最多只能添加5条!');
} else {
const temp = {
input1: '',
input2: '',
rate: '',
rateUnit: '%',
type,
};
if (type) {
temp.soldUnit1 = '分钟';
temp.soldUnit2 = '分钟';
}
form['buySellRatioList' + type].push(temp);
}
};
//
const delRatio = (type: number, i: number) => {
form['buySellRatioList' + type].length > 1 && form['buySellRatioList' + type].splice(i, 1);
};
watch(
() => route.query,
() => {
getDetail();
},
{
immediate: true,
},
);
const getId = (str: string): string | number => {
const result = config.value[5]?.recordChildren[0]?.recordChildren[1]?.subject?.itemList?.find((e) => e.options === str);
return result?.itemId || '';
};
//
const addRecord = async (data: Record<string, any>) => {
const preIds = `1,${levelId},1161`; // 1id
const rule = [
handleId(1162, 308, data.fundName, preIds + ',1162', 3),
handleId(1163, 309, 764, preIds + ',1163', 1),
handleId(1164, 310, data.fundraisingScale, preIds + ',1164', 3),
handleId(1165, 311, 765, preIds + ',1165', 1),
];
const stockId = 1167;
data.shareholdingAllocationsList.forEach((e, i) => {
rule.push(handleId(1177, 312, e.stockName, `${preIds},1166,${stockId + i},1177`, 3));
rule.push(handleId(1178, 313, e.proportion + '%', `${preIds},1166,${stockId + i},1178`, 3));
});
rule.push(
handleId(1181, 314, data.buyingDuration, preIds + ',1179,1180,1181', 3),
handleId(1182, 315, getId(data.buyingUnit), preIds + ',1179,1180,1182', 3),
handleId(1184, 314, data.sellingTime, preIds + ',1179,1183,1184', 3),
handleId(1185, 315, getId(data.soldUnit), preIds + ',1179,1183,1185', 3),
);
//
const buyId = 1187;
data.buySellRatioList0.forEach((e, i) => {
e.amountOrDuration && rule.push(handleId(1192, 316, e.amountOrDuration, `${preIds},1179,1186,${buyId + i},1192`, 3));
e.rate && rule.push(handleId(1193, 318, e.rate + e.rateUnit, `${preIds},1179,1186,${buyId + i},1193`, 3));
});
//
const sellId = 1200;
data.buySellRatioList1.forEach((e, i) => {
e.amountOrDuration && rule.push(handleId(1205, 314, e.amountOrDuration, `${preIds},1179,1199,${sellId + i},1205`, 3));
e.rate && rule.push(handleId(1206, 318, e.rate + e.rateUnit, `${preIds},1179,1199,${sellId + i},1206`, 3));
});
rule.push(
handleId(1196, 319, data.operationManagementRate + '%', preIds + ',1179,1195,1196', 3),
handleId(1197, 320, data.operatingEscrowRate + '%', preIds + ',1179,1195,1197', 3),
handleId(1198, 321, data.operatingSalesServiceRates + '%', preIds + ',1179,1195,1198', 3),
);
await addOperation({
checkpointId: levelId,
parentId: preIds,
lcJudgmentRuleReq: rule,
projectId,
});
};
//
const submit = async () => {
try {
const param = JSON.parse(JSON.stringify(form));
if (!param.fundName) return ElMessage.error('请输入保险名称');
if (!param.fundType) return ElMessage.error('请选择基金类型');
if (!param.fundraisingScale) return ElMessage.error('请输入募集规模');
if (!param.modeOfOperation) return ElMessage.error('请选择运作方式');
if (!param.shareholdingAllocationsList.length || !param.shareholdingAllocationsList.filter((e) => e.stockCode).length) return ElMessage.error('请配置持股');
// 100
let sum = 0;
param.shareholdingAllocationsList.forEach((e) => {
if (e.stockCode && e.proportion) {
sum += +e.proportion;
}
});
if (sum !== 100) return ElMessage.error('持股权重总和需等于100,请重新配置');
if (!param.buyingDuration || !param.sellingTime) return ElMessage.error('请输入份额时长');
if (!param.operationManagementRate) return ElMessage.error('请输入管理费');
if (!param.operatingEscrowRate) return ElMessage.error('请输入托管费');
if (!param.operatingSalesServiceRates) return ElMessage.error('请输入销售服务费');
//
param.shareholdingAllocationsList.forEach((e) => {
if (e.stockCode) {
const item = e.stocks.find((n) => n.f12 === e.stockCode);
e.stockName = item.f14;
e.latestPrice = item.f18;
delete e.stocks;
}
});
//
param.buySellRatioList0.forEach((e, i) => {
e.amountOrDuration = i ? param.buySellRatioList0[i - 1].input2 : e.input1;
if (i !== param.buySellRatioList0.length - 1) e.amountOrDuration += `-${e.input2}`;
});
param.buySellRatioList1.forEach((e, i) => {
e.amountOrDuration = i ? param.buySellRatioList1[i - 1].input2 + param.buySellRatioList1[i - 1].soldUnit2 : e.input1;
if (i !== param.buySellRatioList1.length - 1) e.amountOrDuration += `-${e.input2}${e.soldUnit2}`;
});
param.buySellRatioList = [...param.buySellRatioList0, ...param.buySellRatioList1];
const recordParam = JSON.parse(JSON.stringify(param));
delete param.buySellRatioList0;
delete param.buySellRatioList1;
await saveFund(param);
addRecord(recordParam);
ElMessage.success('提交成功!');
emit('getList', 1);
} finally {
}
};
onMounted(() => {
getConfig();
});
</script>
<style lang="scss" scoped>
@import url(../../../styles/form.scss);
</style>

@ -0,0 +1,137 @@
<template>
<div class="block flex"
style="padding-top: 0">
<div class="left w-[241px] min-w-[241px] pr-5 py-4">
<div class="flex justify-center items-center py-2 mb-3 text-sm text-[#006BFF] bg-[rgba(0,107,255,0.1)] border border-solid border-[#006BFF] rounded-[10px] cursor-pointer"
@click="toAdd">
<el-icon class="mr-1"
:size="16"
color="#006BFF">
<Plus />
</el-icon>
新增产品
</div>
<div class="flex justify-end mb-4">
<img src="@/assets/images/fold.png"
alt=""
class="cursor-pointer"
@click="toList" />
</div>
<ul class="products">
<li v-for="(item, i) in list"
:key="i"
:class="{ active: item.id === id }"
@click="switchProduct(item.id)">
<el-popconfirm title="您确定删除吗?"
@confirm="handleDelete(item.id)">
<template #reference>
<img src="@/assets/images/trash.png"
alt=""
class="del" />
</template>
</el-popconfirm>
<h6>{{ item.fundName }}</h6>
<p class="type">{{ item.fundraisingScale }}万募集规模</p>
<p class="type">买入费率{{ item.buying }}赎回费率{{ item.sale }}</p>
<p class="date">创建日期{{ item.createTime.split(' ')[0] }}</p>
</li>
</ul>
</div>
<div class="right flex-1 px-5 pt-2">
<detail v-if="action === 'detail'"></detail>
<add v-else-if="action === 'add'"
@getList="getList"></add>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { Plus } from '@element-plus/icons-vue';
import { fundProductList, batchDeletion } from '@/api/fund';
import { useRouter, useRoute } from 'vue-router';
import Detail from './Detail.vue';
import Add from './Add.vue';
import Cookies from 'js-cookie';
const router = useRouter();
const route = useRoute();
const action = ref<any>('');
const projectId = +Cookies.get('sand-projectId');
const levelId = +Cookies.get('sand-level');
const list = ref<Array<any>>([]);
const loading = ref<boolean>(false);
const id = computed(() => +route.query.id);
//
const getList = async (first: any) => {
loading.value = true;
try {
const { data } = await fundProductList({ pageNum: 1, pageSize: 1000, checkPointId: levelId, projectId });
list.value = data.data.records;
first && list.value.length && switchProduct(list.value[0].id);
} finally {
loading.value = false;
}
};
onMounted(() => {
getList();
});
watch(
route,
(route: any) => {
action.value = route.params.action;
},
{
immediate: true,
},
);
//
const switchProduct = (id: number) => {
router.push(`/product/fund/detail?id=` + id);
};
//
const toAdd = () => {
router.push(`/product/fund/add`);
};
//
const toList = () => {
router.push(`/product/fund`);
};
const handleDelete = async (id: number) => {
await batchDeletion([id]);
getList(1);
ElMessage.success('删除成功!');
};
</script>
<style lang="scss" scoped>
.left {
border-right: 1px solid #e9eff2;
}
.products {
@apply max-h-[calc(100vh-205px)] pr-1 overflow-auto;
li {
@apply relative p-5 pt-7 mb-5 rounded-[10px] cursor-pointer border border-solid border-[transparent] bg-[url('@/assets/images/10.png')] bg-[length:100%_100%] bg-no-repeat;
&.active {
@apply border-[#CAE0FF];
}
}
.del {
@apply absolute top-0 right-0;
}
h6 {
@apply text-[#14436b];
}
.type,
.status {
@apply my-[15px] text-sm text-[#333];
}
.date {
@apply text-sm text-[#8798a9];
}
}
</style>

@ -0,0 +1,21 @@
<template>
<div>
<el-tabs v-model="curTab">
<el-tab-pane label="产品详情"
name="tab1">
<info />
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import Info from './Info.vue';
const curTab = ref<string>('tab1');
</script>
<style lang="scss" scoped>
@import url(../../../styles/form.scss);
</style>

@ -0,0 +1,105 @@
<template>
<div class="info mt-2">
<h2 class="text-center text-lg">{{ info.fundName }}</h2>
<p class="text">基金类型{{ info.fundType }}</p>
<p class="text">基金规模{{ info.fundraisingScale }}万元</p>
<p class="text">运作方式{{ info.modeOfOperation }}</p>
<p class="text mt-10">持股配置</p>
<div class="text">
<div class="flex">
<p class="w-[130px] mr-32">股票</p>
<p class="">比重(%)</p>
</div>
<div v-for="(item, i) in info.shareholdingAllocationsList"
:key="item"
class="flex">
<p class="w-[130px] mr-32">{{ item.stockName }}({{ item.stockCode }})</p>
<p class="">{{ item.proportion }}</p>
</div>
</div>
<p class="text mt-10">费率配置</p>
<p class="text">客户T时买入T+{{ info.buyingDuration }}{{ info.buyingUnit }}确认申购份额期间资金进入冻结账户</p>
<p class="text">客户T时卖出T+{{ info.sellingTime }}{{ info.soldUnit }}确认赎回金额期间资金进入冻结账户</p>
<p class="text mt-5">买入费率</p>
<div class="text">
<div class="flex">
<p class="w-[130px] mr-32">金额</p>
<p class="">费率</p>
</div>
<div v-for="(item, i) in info.purchaseRateList"
:key="item"
class="flex">
<p class="w-[130px] mr-32">{{ item.amountOrDuration }}</p>
<p class="">{{ item.rate }}{{ item.rateUnit
}}</p>
</div>
</div>
<p class="text mt-5">管理费{{ info.operationManagementRate }}%</p>
<p class="text">托管费{{ info.operatingEscrowRate }}%</p>
<p class="text">销售服务费{{ info.operatingSalesServiceRates }}%</p>
<p class="text mt-5">赎回费率</p>
<div class="text">
<div class="flex">
<p class="w-[130px] mr-32">持有时长</p>
<p class="">费率</p>
</div>
<div v-for="(item, i) in info.sellingRateList"
:key="item"
class="flex">
<p class="w-[130px] mr-32">{{ item.amountOrDuration }}</p>
<p class="">{{ item.rate }}{{ item.rateUnit }}</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue';
import { findById } from '@/api/fund';
import { useRouter, useRoute } from 'vue-router';
const router = useRouter();
const route = useRoute();
const id = computed(() => +route.query.id);
const info = ref<Record<string, any>>({});
//
const getDetail = async () => {
if (id.value) {
try {
const { data } = await findById(id.value);
info.value = data;
} finally {
}
}
};
watch(
() => route.query,
() => {
getDetail();
},
{
immediate: true,
},
);
</script>
<style lang="scss" scoped>
.info {
.step-name {
@apply mb-3 text-sm font-semibold text-[#006bff];
}
.line {
@apply flex mb-2;
}
.label {
@apply mr-1 text-sm font-semibold text-[#333] leading-[32px];
}
.text {
@apply text-sm text-[#333] leading-[32px];
}
}
</style>

@ -0,0 +1,155 @@
<template>
<div class="block">
<div class="flex justify-between items-center mb-5">
<search v-model="params.fundName"
@change="initList"></search>
<div class="filter">
<div class="add-btn"
@click="toAdd">
<img src="@/assets/images/plus.png"
alt=""
class="icon" />
新增产品
</div>
<img src="@/assets/images/9.png"
alt=""
class="ml-4 cursor-pointer"
@click="toCardList" />
</div>
</div>
<el-table ref="table"
v-loading="loading"
:data="list"
@sort-change="handleSort">
<el-table-column prop="fundName"
label="基金名称"
min-width="150"></el-table-column>
<el-table-column prop="fundCode"
label="基金代码"
min-width="100"></el-table-column>
<el-table-column prop="fundType"
label="基金类型"
min-width="100"></el-table-column>
<el-table-column prop="fundraisingScale"
label="募集规模"
min-width="100"></el-table-column>
<el-table-column prop="modeOfOperation"
label="运作方式"
min-width="100"></el-table-column>
<el-table-column prop="buying"
label="买入费率"
min-width="120"></el-table-column>
<el-table-column prop="sale"
label="赎回费率"
min-width="120"></el-table-column>
<el-table-column prop="createTime"
label="创建日期"
width="180"
sortable="custom"></el-table-column>
<el-table-column label="操作"
width="140">
<template #default="{ row }">
<el-popconfirm title="您确定删除吗?"
@confirm.stop="handleDelete(row.id)">
<template #reference>
<el-button type="text"
size="small">删除</el-button>
</template>
</el-popconfirm>
<el-button type="text"
@click="toDetail(`/product/fund/detail`, row.id)"
size="small">产品详情</el-button>
</template></el-table-column>
</el-table>
<el-pagination v-model:currentPage="currentPage"
v-model:pageSize="pageSize"
:total="total"
:page-sizes="pageSizes"
:layout="pageLayout"
@size-change="getList()"
@current-change="getList()"
small
background
class="px-3 py-2 justify-end"></el-pagination>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref, reactive, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { pageSizes, pageLayout, toParams, resetParams } from '@/utils/common';
import { fundProductList, batchDeletion } from '@/api/fund';
import Search from '@/components/Search.vue';
import { useRouter, useRoute } from 'vue-router';
import Cookies from 'js-cookie';
import { productState, getExpertStatus, getStatus } from '@/store/useProduct';
const router = useRouter();
const route = useRoute();
const projectId = +Cookies.get('sand-projectId');
const levelId = +Cookies.get('sand-level');
const params = reactive({
createDateSort: '',
fundName: '',
checkPointId: levelId,
projectId,
});
const currentPage = ref<number>(1);
const pageSize = ref<number>(10);
const total = ref<number>(0);
const table = ref<any>();
const list = ref<Array<any>>([]);
const loading = ref<boolean>(false);
const stop = () => {};
//
const getList = async () => {
loading.value = true;
try {
const { data } = await fundProductList({ pageNum: currentPage.value, pageSize: pageSize.value, ...toParams(params) });
list.value = data.data.records;
total.value = data.data.total;
} finally {
loading.value = false;
}
};
//
const initList = async () => {
currentPage.value = 1;
getList();
};
onMounted(() => {
getList();
});
watch([params, () => route.query], initList);
const handleSort = ({ column, prop, order }: { column: any; prop: string; order: string }) => {
params.createDateSort = order === 'descending' ? 'desc' : order === 'ascending' ? 'asc' : '';
getList();
};
//
const toAdd = () => {
router.push({
path: `/product/fund/add`,
});
};
//
const toDetail = async (path: string, id: number) => {
router.push({
path,
query: {
id,
},
});
};
//
const toCardList = () => {
router.push('/product/fund/detail');
};
const handleDelete = async (id: number) => {
await batchDeletion([id]);
getList();
ElMessage.success('删除成功!');
};
</script>

@ -3,20 +3,6 @@
<el-tabs v-model="curTab"> <el-tabs v-model="curTab">
<el-tab-pane :label="id ? '产品要素' : '新增产品'" <el-tab-pane :label="id ? '产品要素' : '新增产品'"
name="tab1"> name="tab1">
<div v-if="info"
class="audit">
<div class="line">
<span class="field">审批意见</span>
<span class="status">{{ getStatus(+info?.status) }}</span>
</div>
<div class="line">
<span class="field">意见描述</span>
{{ info.opinionDescription }}
</div>
<p class="mb-2 text-sm text-[#333] text-right">审查日期{{ info.approvalTime }}</p>
<p class="mb-2 text-sm text-[#333] text-right">审查员公瑾</p>
</div>
<el-form label-width="100px" <el-form label-width="100px"
label-suffix=":" label-suffix=":"
class="form" class="form"
@ -212,7 +198,7 @@ const submit = async () => {
}; };
// //
const addRecord = async (data: Record<string, any>) => { const addRecord = async (data: Record<string, any>) => {
const preIds = `1,${Cookies.get('sand-level')},275,${data.insuranceType === 310 ? 276 : 277}`; // 1id/44/45 const preIds = `1,${Cookies.get('sand-level')},275,${data.insuranceType === 310 ? 276 : 277}`; // 1id
const rule: Array<Record<string, any>> = [handleId(278, 118, data.insuranceName, preIds + ',278', 3), handleId(279, 119, data.insuranceType, preIds + ',279', 1)]; const rule: Array<Record<string, any>> = [handleId(278, 118, data.insuranceName, preIds + ',278', 3), handleId(279, 119, data.insuranceType, preIds + ',279', 1)];
data.minimumAge && rule.push(handleId(680, 219, data.minimumAge, preIds + ',678,680', 3)); data.minimumAge && rule.push(handleId(680, 219, data.minimumAge, preIds + ',678,680', 3));
data.maximumAge && rule.push(handleId(679, 218, data.maximumAge, preIds + ',678,679', 3)); data.maximumAge && rule.push(handleId(679, 218, data.maximumAge, preIds + ',678,679', 3));

Loading…
Cancel
Save