风控策略

V0.1
yujialong 1 year ago
parent cda5fee482
commit 16855109ff
  1. 2
      .env
  2. 5
      src/api/bank.ts
  3. 8
      src/layout/components/AppSidebar/Menu.vue
  4. 3
      src/router/index.ts
  5. 10
      src/styles/index.scss
  6. 9
      src/views/config/Buyer.vue
  7. 28
      src/views/product/Detail.vue
  8. 12
      src/views/product/List.vue
  9. 2
      src/views/product/bank/Add.vue
  10. 2
      src/views/product/bank/Approve.vue
  11. 24
      src/views/product/bank/CardList.vue
  12. 2
      src/views/product/bank/Config.vue
  13. 21
      src/views/product/bank/Detail.vue
  14. 0
      src/views/product/bank/Info.vue
  15. 532
      src/views/product/strategy/Add.vue
  16. 131
      src/views/product/strategy/Approve.vue
  17. 133
      src/views/product/strategy/CardList.vue
  18. 348
      src/views/product/strategy/Config.vue
  19. 21
      src/views/product/strategy/Detail.vue
  20. 181
      src/views/product/strategy/Info.vue

@ -2,6 +2,6 @@ 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_I18N_LOCALE=zh-cn VITE_I18N_LOCALE=zh-cn
VITE_I18N_FALLBACK_LOCALE=zh-cn VITE_I18N_FALLBACK_LOCALE=zh-cn

@ -17,5 +17,8 @@ export const riskUpdate = async (data: Record<string, any>): Promise<any> => (aw
export const riskById = async (id: number): Promise<any> => (await axios.post(`/product/managerOfRiskControl/bankRiskControlAllocation/findById?id=${id}`)).data; export const riskById = async (id: number): Promise<any> => (await axios.post(`/product/managerOfRiskControl/bankRiskControlAllocation/findById?id=${id}`)).data;
export const examineAndApprove = async (id: number | string, opinionDescription: string, status: number, approvalTime: string): Promise<any> => export const examineAndApprove = async (id: number | string, opinionDescription: string, status: number, approvalTime: string): Promise<any> =>
(await axios.post(`/product/product/bank/products/examineAndApprove?id=${id}&opinionDescription=${opinionDescription}&status=${status}&approvalTime=${approvalTime}`)).data; (await axios.post(`/product/product/bank/products/examineAndApprove?id=${id}&opinionDescription=${opinionDescription}&status=${status}&approvalTime=${approvalTime}`)).data;
export const update = async (data: Record<string, any>): Promise<any> => (await axios.post(`/product/product/bank/products/update`, data)).data; export const update = async (data: Record<string, any>): Promise<any> => (await axios.post(`/product/product/bank/products/update`, data)).data;
export const accessStrategyGovernmentBlacklistFind = async (checkpointId: number, projectId: number): Promise<any> =>
(await axios.post(`/product/accessStrategyGovernmentBlacklist/details?checkpointId=${checkpointId}&projectId=${projectId}`)).data;
export const accessStrategyGovernmentBlacklist = async (data: Record<string, any>[]): Promise<any> =>
(await axios.post(`/product/accessStrategyGovernmentBlacklist/saveOrUpdate`), data).data;

@ -23,7 +23,8 @@
</li> </li>
</template> </template>
<template v-else-if="role == 42"> <template v-else-if="role == 42">
<li :class="{ active: active == 1 }"> <li :class="{ active: active == 1 }"
@click="toPage('/product/strategy/config?type=&i=1&role=42')">
<img class="icon" <img class="icon"
src="@/assets/images/icon1.png" src="@/assets/images/icon1.png"
alt="" /> alt="" />
@ -32,14 +33,15 @@
alt="" /> alt="" />
<p class="text">产品风控配置</p> <p class="text">产品风控配置</p>
</li> </li>
<li> <li :class="{ active: active == 2 }"
@click="toPage('/product/strategy/config?type=&i=2&role=42')">
<img class="icon" <img class="icon"
src="@/assets/images/icon2.png" src="@/assets/images/icon2.png"
alt="" /> alt="" />
<img class="icon-1" <img class="icon-1"
src="@/assets/images/icon2-1.png" src="@/assets/images/icon2-1.png"
alt="" /> alt="" />
<p class="text">准入模型</p> <p class="text">贷前准入模型</p>
</li> </li>
<li> <li>
<img class="icon" <img class="icon"

@ -28,7 +28,8 @@ export const routes: Array<RouteRecordRaw> = [
meta: { title: '产品经理' }, meta: { title: '产品经理' },
children: [ children: [
{ path: 'index', component: () => import('@/views/product/List.vue'), meta: { title: '产品列表' } }, { path: 'index', component: () => import('@/views/product/List.vue'), meta: { title: '产品列表' } },
{ path: 'cardList/:action', component: () => import('@/views/product/CardList.vue'), meta: { title: '配置风控' } }, { path: 'bank/:action', component: () => import('@/views/product/bank/CardList.vue'), meta: { title: '产品列表' } },
{ path: 'strategy/:action', component: () => import('@/views/product/strategy/CardList.vue'), meta: { title: '产品列表' } },
], ],
}, },
{ {

@ -39,3 +39,13 @@ body {
border-radius: 6px; border-radius: 6px;
background: #d7d7d7; background: #d7d7d7;
} }
.c-table {
@apply rounded-[10px];
th.el-table__cell {
@apply bg-[#F8FBFC];
}
th.el-table__cell > .cell {
@apply text-[#006BFF];
}
}

@ -420,13 +420,4 @@ onMounted(() => {
@apply w-1 h-4 mr-2 bg-[#006BFF] rounded-sm; @apply w-1 h-4 mr-2 bg-[#006BFF] rounded-sm;
} }
} }
.c-table {
@apply rounded-[10px];
:deep(th.el-table__cell) {
@apply bg-[#F8FBFC];
}
:deep(th.el-table__cell > .cell) {
@apply text-[#006BFF];
}
}
</style> </style>

@ -1,28 +0,0 @@
<template>
<div>
<el-tabs v-model="curTab"
@tab-click="tabChange">
<el-tab-pane label="产品要素"
name="tab1">
<info />
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue';
import type { TabsPaneContext } from 'element-plus';
import Info from './Info.vue';
const curTab = ref<string>('tab1');
// tab
const tabChange = (tab: TabsPaneContext, event: Event) => {
console.log(tab, event);
};
</script>
<style lang="scss" scoped>
@import url(../../styles/form.scss);
</style>

@ -91,11 +91,11 @@
<el-button v-if="row.showElementsOrNot" <el-button v-if="row.showElementsOrNot"
type="text" type="text"
size="small" size="small"
@click="toDetail(`/product/cardList/add`, row.id)">配置要素</el-button> @click="toDetail(`/product/bank/add`, row.id)">配置要素</el-button>
<el-button v-if="row.showRiskControlOrNot" <el-button v-if="row.showRiskControlOrNot"
type="text" type="text"
size="small" size="small"
@click="toDetail(`/product/cardList/config`, row.id)">配置风控</el-button> @click="toDetail(`/product/bank/config`, row.id)">配置风控</el-button>
<el-popconfirm v-if="row.showDeleteOrNot" <el-popconfirm v-if="row.showDeleteOrNot"
title="您确定删除吗?" title="您确定删除吗?"
@confirm.stop="handleDelete(row.id)"> @confirm.stop="handleDelete(row.id)">
@ -107,12 +107,12 @@
</el-popconfirm> </el-popconfirm>
<el-button v-if="row.showDetailsOrNot" <el-button v-if="row.showDetailsOrNot"
type="text" type="text"
@click="toDetail(`/product/cardList/detail`, row.id)" @click="toDetail(`/product/bank/detail`, row.id)"
size="small">{{ params.roleId == 43 ? '审批结果' : '产品详情' }}</el-button> size="small">{{ params.roleId == 43 ? '审批结果' : '产品详情' }}</el-button>
<el-button v-if="row.showApprovalOrNot" <el-button v-if="row.showApprovalOrNot"
type="text" type="text"
size="small" size="small"
@click="toDetail(`/product/cardList/approve`, row.id)">审批</el-button> @click="toDetail(`/product/bank/approve`, row.id)">审批</el-button>
</template></el-table-column> </template></el-table-column>
</el-table> </el-table>
<el-pagination v-model:currentPage="currentPage" <el-pagination v-model:currentPage="currentPage"
@ -204,7 +204,7 @@ const handleSort = ({ column, prop, order }: { column: any; prop: string; order:
// //
const toAdd = () => { const toAdd = () => {
router.push({ router.push({
path: `/product/cardList/add`, path: `/product/bank/add`,
query: route.query, query: route.query,
}); });
}; };
@ -222,7 +222,7 @@ const toDetail = async (path: string, id: number) => {
// //
const toCardList = () => { const toCardList = () => {
router.push({ router.push({
path: '/product/cardList/config', path: '/product/bank/config',
query: route.query, query: route.query,
}); });
}; };

@ -519,7 +519,7 @@ onMounted(() => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import url(../../styles/form.scss); @import url(../../../styles/form.scss);
.audit { .audit {
@apply py-5 px-4 mb-[30px] bg-[#f9fafc] rounded-[10px]; @apply py-5 px-4 mb-[30px] bg-[#f9fafc] rounded-[10px];
.line { .line {

@ -127,5 +127,5 @@ onMounted(() => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import url(../../styles/form.scss); @import url(../../../styles/form.scss);
</style> </style>

@ -2,7 +2,8 @@
<div class="block flex" <div class="block flex"
style="padding-top: 0"> style="padding-top: 0">
<div class="left w-[241px] pr-5 py-4"> <div class="left 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" <div v-if="role == 41"
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"> @click="toAdd">
<el-icon class="mr-1" <el-icon class="mr-1"
:size="16" :size="16"
@ -52,10 +53,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, ref, reactive, toRef, toRefs, watch } from 'vue'; import { computed, onMounted, ref, watch } from 'vue';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { Plus } from '@element-plus/icons-vue'; import { Plus } from '@element-plus/icons-vue';
import { perm } from '@/store/useCurrentUser';
import { bankingProductsList, batchDeletion } from '@/api/bank'; import { bankingProductsList, batchDeletion } from '@/api/bank';
import { useRouter, useRoute } from 'vue-router'; import { useRouter, useRoute } from 'vue-router';
import { getStatus } from '@/store/useProduct'; import { getStatus } from '@/store/useProduct';
@ -70,6 +70,7 @@ const action = ref<any>('');
const list = ref<Array<any>>([]); const list = ref<Array<any>>([]);
const loading = ref<boolean>(false); const loading = ref<boolean>(false);
const productType = computed(() => route.query.type); // / const productType = computed(() => route.query.type); // /
const role = computed(() => +route.query.role || 41);
const id = computed(() => +route.query.id); const id = computed(() => +route.query.id);
// //
const getList = async (first: any) => { const getList = async (first: any) => {
@ -98,7 +99,7 @@ watch(
// //
const switchProduct = (id: number) => { const switchProduct = (id: number) => {
router.push({ router.push({
path: `/product/cardList/detail`, path: `/product/bank/detail`,
query: { query: {
...route.query, ...route.query,
id, id,
@ -108,7 +109,7 @@ const switchProduct = (id: number) => {
// //
const toAdd = () => { const toAdd = () => {
router.push({ router.push({
path: `/product/cardList/add`, path: `/product/bank/add`,
query: route.query, query: route.query,
}); });
}; };
@ -132,22 +133,17 @@ const handleDelete = async (id: number) => {
} }
} }
.del { .del {
position: absolute; @apply absolute top-0 right-0;
top: 0;
right: 0;
} }
h6 { h6 {
color: #14436b; @apply text-[#14436b];
} }
.type, .type,
.status { .status {
margin: 15px 0; @apply my-[15px] text-sm text-[#333];
font-size: 14px;
color: #333;
} }
.date { .date {
font-size: 14px; @apply text-sm text-[#8798a9];
color: #8798a9;
} }
} }
</style> </style>

@ -629,5 +629,5 @@ const addRecord = async (data: Record<string, any>, newId: number) => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import url(../../styles/form.scss); @import url(../../../styles/form.scss);
</style> </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,532 @@
<template>
<div>
<el-tabs v-model="curTab"
@tab-click="tabChange">
<el-tab-pane :label="id ? '产品要素' : '新增产品'"
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 ref="formRef"
:model="form"
:rules="rules"
label-width="100px"
label-suffix=":"
class="form"
status-icon>
<el-form-item label="产品定义"
prop="productDefinition">
<el-input type="textarea"
placeholder="用一段话简单介绍一下这个产品或者描述产品的设计理念。例如:本产品根据个人客户的信用状况,为其提供的一种短期融资便利产品,借款人可在额度金额内可循环周转使用贷款。"
maxlength="200"
v-model="form.productDefinition"></el-input>
</el-form-item>
<el-form-item label="产品名称"
prop="productName">
<el-input placeholder="取个有吸引力的产品名,限20字。"
maxlength="20"
v-model="form.productName"></el-input>
</el-form-item>
<el-form-item label="产品币种"
prop="productCurrency">
<el-select v-model="form.productCurrency"
placeholder="请选择">
<el-option label="人民币"
:value="1" />
</el-select>
</el-form-item>
<el-form-item label="贷款对象"
required>
<div class="flex-1">
<!-- 企业 -->
<template v-if="form.productType">
<div class="flex items-center">
<span class="mr-3 text-[#333] text-sm">企业类型</span>
<el-select v-model="form.productObject"
placeholder="请选择">
<el-option v-for="(item, i) in config.find((e: any) => e.name === '企业产品-贷款对象')?.recordChildren[0]?.subject?.itemList"
:key="i"
:label="item.options"
:value="item.itemId" />
</el-select>
</div>
<div class="flex items-center mt-4">
<span class="mr-3 text-[#333] text-sm">借款人年龄</span>
<div class="num-inputs ml-7">
<el-input placeholder="最小年龄"
v-model.number="form.minimumAge"></el-input>
<span class="split">-</span>
<el-input placeholder="最大年龄"
v-model.number="form.maximumAge"></el-input>
<span class="unit">周岁</span>
</div>
</div>
</template>
<!-- 个人 -->
<template v-else>
<p class="field-name">选择本产品的贷款对象</p>
<div class="flex items-center mb-2">
<el-checkbox class="mt-1"
v-model="form.age"
label="年龄" />
<div v-if="form.age"
class="num-inputs ml-7">
<el-input placeholder="最小年龄"
v-model.number="form.minimumAge"></el-input>
<span class="split">-</span>
<el-input placeholder="最大年龄"
v-model.number="form.maximumAge"></el-input>
</div>
</div>
<div class="flex items-center mb-2">
<el-checkbox v-model="form.edu"
label="学历要求"></el-checkbox>
<el-checkbox-group v-if="form.edu"
class="mt-2 ml-5"
v-model="form.educationalRequirements">
<el-checkbox v-for="(item, i) in config.find((e: any) => e.name === '个人产品-贷款对象')?.recordChildren[1]?.subject?.itemList"
:key="i"
:label="item.itemId">{{ item.options }}</el-checkbox>
</el-checkbox-group>
</div>
<div class="flex items-center mb-2">
<el-checkbox v-model="form.curWL"
label="工作年限"></el-checkbox>
<el-checkbox-group v-if="form.curWL"
class="mt-2 ml-5"
v-model="form.currentWorkingLife">
<el-checkbox v-for="(item, i) in config.find((e: any) => e.name === '个人产品-贷款对象')?.recordChildren[2]?.subject?.itemList"
:key="i"
:label="item.itemId">{{ item.options }}</el-checkbox>
</el-checkbox-group>
</div>
<el-checkbox v-model="form.providentFundAndSocialSecurity"
label="公积金/社保"></el-checkbox>
</template>
</div>
</el-form-item>
<el-form-item label="贷款用途"
prop="loanPurpose"
:rules="[
{ required: true, message: '请选择贷款用途', trigger: 'change' },
{
asyncValidator: async (rule, value, callback) => {
if (value === 107 && !form.otherPurposesOfLoan) {
callback('请输入其他贷款用途')
} else {
callback()
}
},
},
]">
<div class="flex-1">
<p class="field-name">选择本产品贷款资金的用途</p>
<el-radio-group v-model="form.loanPurpose">
<template v-if="form.productType">
<el-radio v-for="(item, i) in config.find((e: any) => e.name === '企业产品-贷款用途')?.subject?.itemList"
:key="i"
:label="item.itemId">{{ item.options }}</el-radio>
</template>
<template v-else>
<el-radio v-for="(item, i) in config.find((e: any) => e.name === '个人产品-贷款用途')?.recordChildren"
:key="i"
:label="item.id">{{ item.name }}</el-radio>
</template>
</el-radio-group>
<el-input v-if="form?.loanPurpose === 107"
class="w-[300px] ml-5"
placeholder="请描述其他贷款用途可用于哪些方面。"
maxlength="10"
v-model="form.otherPurposesOfLoan"></el-input>
</div>
</el-form-item>
<el-form-item label="担保方式"
prop="bankGuaranteeTypeIds"
:rules="[
{ required: true, message: '请选择担保方式', trigger: 'change' },
{
asyncValidator: async (rule, value, callback) => {
if (value.includes(110) && !value.find((e: any) => e > 22 && e < 33)) {
callback('请选择抵押物')
} else if (value.includes(111) && !value.find((e: any) => e > 32 && e < 38)) {
callback('请选择质押贷')
} else {
callback()
}
},
},
]">
<div class="flex-1">
<p class="field-name">选择本产品的担保种类</p>
<div v-for="(item, i) in config.find((e: any) => e.name === '担保方式')?.recordChildren"
:key="i"
class="mb-2">
<el-checkbox-group v-model="form.bankGuaranteeTypeIds">
<el-checkbox :label="item.id">{{ item.name }}</el-checkbox>
<el-checkbox v-show="(item.id === 110 && form.bankGuaranteeTypeIds.includes(110)) || (item.id === 111 && form.bankGuaranteeTypeIds.includes(111))"
v-for="(child, j) in item?.subject?.itemList"
:key="j"
:label="child.itemId">{{ child.options }}</el-checkbox>
</el-checkbox-group>
</div>
</div>
</el-form-item>
<el-form-item label="贷款额度"
prop="minimumLoan"
:rules="[
{ required: true, message: '请输入贷款额度', trigger: 'blur' },
{
asyncValidator: async (rule, value, callback) => {
const max = form.loanCeiling
if (isNaN(value) || value < 0 || max === '' || isNaN(max) || max < 0 || value > max) {
callback('请输入合理的贷款额度')
} else {
callback()
}
},
},
]">
<div class="flex-1">
<div class="num-inputs">
<el-input placeholder="最小额度"
min="0"
v-model.number="form.minimumLoan"></el-input>
<span class="split">-</span>
<el-input placeholder="最高额度"
min="0"
v-model.number="form.loanCeiling"></el-input>
<span class="unit">万元</span>
</div>
</div>
</el-form-item>
<el-form-item label="贷款利率"
prop="minimumAprOnLoan"
:rules="[
{ required: true, message: '请输入贷款利率', trigger: 'blur' },
{
asyncValidator: async (rule, value, callback) => {
const max = form.maximumAnnualInterestRate
if (isNaN(value) || value < 0 || max === '' || isNaN(max) || max < 0 || value > max) {
callback('请输入合理的贷款利率')
} else {
callback()
}
},
},
]">
<div class="flex-1">
<div class="num-inputs">
<el-input placeholder="最小年利率"
min="0"
v-model.number="form.minimumAprOnLoan"></el-input>
<span class="split">-</span>
<el-input placeholder="最高年利率"
min="0"
v-model.number="form.maximumAnnualInterestRate"></el-input>
<span class="unit">%</span>
</div>
</div>
</el-form-item>
<el-form-item label="贷款期限"
prop="minimumTermOfLoan"
:rules="[
{ required: true, message: '请输入贷款期限', trigger: 'blur' },
{
asyncValidator: async (rule, value, callback) => {
const max = form.maximumTermOfLoan
if (isNaN(value) || value < 0 || max === '' || isNaN(max) || max < 0 || value > max) {
callback('请输入合理的贷款期限')
} else {
callback()
}
},
},
]">
<div class="flex-1">
<div class="num-inputs">
<el-input placeholder="最小期限"
min="0"
v-model.number="form.minimumTermOfLoan"></el-input>
<span class="split">-</span>
<el-input placeholder="最大期限"
min="0"
v-model.number="form.maximumTermOfLoan"></el-input>
<span class="unit"></span>
</div>
</div>
</el-form-item>
<el-form-item label="还款方式"
prop="modeRepayment">
<div class="flex-1">
<p class="field-name">选择本产品可以选择的还款方式</p>
<el-checkbox-group v-model="form.modeRepayment">
<el-checkbox v-for="(item, i) in config.find((e: any) => e.name === '还款方式')?.subject?.itemList"
:key="i"
:label="item.itemId">{{ item.options }}</el-checkbox>
</el-checkbox-group>
</div>
</el-form-item>
<el-form-item label="提前还款"
prop="whetherToSupportEarlyRepayment">
<div class="flex-1 flex items-center">
<el-switch v-model="form.whetherToSupportEarlyRepayment" />
<p class="tips ml-4">本产品是否支持提前还款</p>
</div>
</el-form-item>
<div class="flex justify-end">
<div class="submit"
@click="submit(formRef)">完成提交风控经理</div>
</div>
</el-form>
</el-tab-pane>
<el-tab-pane v-if="id"
label="产品风控"
name="tab2">
<info />
</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 type { TabsPaneContext, FormInstance, FormRules } from 'element-plus';
import { findById, save, update } from '@/api/bank';
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';
const emit = defineEmits(['getList']);
interface RuleForm {
productDefinition: string;
productName: string;
productCurrency: number;
bankGuaranteeTypeIds: any[];
currentWorkingLife?: any;
educationalRequirements?: any;
loanCeiling: any;
loanPurpose: any;
productObject: any;
maximumAge: any;
maximumAnnualInterestRate: any;
maximumTermOfLoan: any;
age: boolean;
minimumAge: any;
minimumAprOnLoan: any;
minimumLoan: any;
minimumTermOfLoan: any;
modeRepayment?: any;
otherPurposesOfLoan: string;
productObject: any;
productType: number;
providentFundAndSocialSecurity: any;
whetherToSupportEarlyRepayment?: any;
}
const router = useRouter();
const route = useRoute();
const id = computed(() => route.query.id);
const curTab = ref<string>('tab1');
const config = ref<any[]>([]);
const info = ref<Record<string, any>>(null);
const formRef = ref<FormInstance>();
const form = reactive<RuleForm>({
productDefinition: '',
productName: '',
productCurrency: 1,
bankGuaranteeTypeIds: [],
currentWorkingLife: [],
educationalRequirements: [],
loanCeiling: '',
loanPurpose: '',
productObject: '',
maximumAge: '',
maximumAnnualInterestRate: '',
maximumTermOfLoan: '',
age: false,
minimumAge: '',
minimumAprOnLoan: '',
minimumLoan: '',
minimumTermOfLoan: '',
modeRepayment: [],
otherPurposesOfLoan: '',
productObject: '',
productType: computed(() => +route.query.type),
providentFundAndSocialSecurity: false,
whetherToSupportEarlyRepayment: false,
});
const rules = reactive<FormRules<RuleForm>>({
productDefinition: [{ required: true, message: '请输入产品定义', trigger: 'blur' }],
productName: [{ required: true, message: '请输入产品名称', trigger: 'blur' }],
productCurrency: [{ required: true, message: '请选择产品币种', trigger: 'change' }],
modeRepayment: [{ required: true, message: '请选择还款方式', trigger: 'change' }],
});
// tab
const tabChange = (tab: TabsPaneContext, event: Event) => {
console.log(tab, event);
};
//
const getConfig = async () => {
const { process } = await getProcessInformationBasedOnRoles(form.productType === 1 ? 45 : 44);
config.value = process;
};
//
const getDetail = async () => {
if (id.value) {
try {
const { data } = await findById(id.value);
info.value = data;
} finally {
}
}
};
watch(
() => route.query,
() => {
getDetail();
},
{
immediate: true,
},
);
//
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate(async (valid, fields) => {
if (valid) {
try {
const param = JSON.parse(JSON.stringify(form));
//
if (param.productType) {
if (!param.productObject) return ElMessage.error('请选择企业类型');
}
if ((!param.productType && param.age) || param.productType) {
if (!param.minimumAge) return ElMessage.error('请输入最小年龄');
if (!param.maximumAge) return ElMessage.error('请输入最大年龄');
if (param.minimumAge < 0 || param.minimumAge > 999 || param.maximumAge < 0 || param.maximumAge > 999) return ElMessage.error('请输入合理的年龄');
if (param.minimumAge > param.maximumAge) return ElMessage.error('最小年龄不得大于最大年龄');
}
//
if (!param.productType) {
if (param.edu && !param.educationalRequirements.length) return ElMessage.error('请选择学历要求');
if (param.curWL && !param.currentWorkingLife.length) return ElMessage.error('请选择工作年限');
if (param.curWL && !param.currentWorkingLife.length) return ElMessage.error('请选择工作年限');
}
param.currentWorkingLife = param.currentWorkingLife.join();
param.educationalRequirements = param.educationalRequirements.join();
param.modeRepayment = param.modeRepayment.join();
param.providentFundAndSocialSecurity = param.providentFundAndSocialSecurity ? 1 : '';
param.whetherToSupportEarlyRepayment = param.whetherToSupportEarlyRepayment ? 58 : '';
//
param.addBankProductsGuarantyStyleReqList = [];
param.bankGuaranteeTypeIds.forEach((e: number) => {
param.addBankProductsGuarantyStyleReqList.push({
bankGuaranteeTypeId: (e > 22 && e < 33) || (e > 32 && e < 38) ? e : '',
pid: e > 22 && e < 33 ? 110 : e > 32 && e < 38 ? 111 : e,
});
});
if (id.value) {
param.id = id.value;
param.status = 295;
await update(param);
addRecord(param, id.value);
} else {
const { message } = await save(param);
addRecord(param, message);
}
ElMessage.success('提交成功!');
emit('getList', 1);
} finally {
}
} else {
console.log('error submit!', fields);
}
});
};
//
const addRecord = async (data: Record<string, any>, newId: number) => {
const isEnterprise = data.productType === 1;
const preIds = `1,2,41,${data.productType ? 45 : 44},${newId}`; // 1id/44/45
const lcRule = <Record<string, any>[]>[
handleId(48, 1, data.productDefinition, preIds + ',48', 3),
handleId(49, 2, data.productName, preIds + ',49', 3),
handleId(50, 3, 1, preIds + ',50', 1),
];
//
if (isEnterprise) {
//
lcRule.push(
handleId(62, 7, data.productObject, preIds + ',61,62', 1),
handleId(63, 8, '[' + data.minimumAge + ',' + data.maximumAge + ']' + '', preIds + ',61,63', 5),
handleId(65, 19, data.loanPurpose, preIds + ',65', 1),
);
} else {
//
data.age && lcRule.push(handleId(100, 41, '[' + data.minimumAge + ',' + data.maximumAge + ']' + '', preIds + ',51,100', 5));
data.edu && lcRule.push(handleId(101, 42, data.educationalRequirements, preIds + ',51,101', 1));
data.curWL && lcRule.push(handleId(102, 43, data.currentWorkingLife, preIds + ',51,102', 1));
data.providentFundAndSocialSecurity && lcRule.push(handleId(103, '', '', preIds + ',51,103', '')); //
lcRule.push(
data.loanPurpose === 107 ? handleId(107, 11, data.otherPurposesOfLoan, preIds + ',52,107', 3) : handleId(data.loanPurpose, '', '', preIds + ',52,' + data.loanPurpose, 1),
);
}
//
data.bankGuaranteeTypeIds.includes(108) && lcRule.push(handleId(108, '', '', preIds + ',53,108', ''));
data.bankGuaranteeTypeIds.includes(109) && lcRule.push(handleId(109, '', '', preIds + ',53,109', ''));
data.bankGuaranteeTypeIds.includes(110) && lcRule.push(handleId(110, 13, data.bankGuaranteeTypeIds.filter((e: number) => e > 22 && e < 33).join(), preIds + ',53,110', 1));
data.bankGuaranteeTypeIds.includes(111) && lcRule.push(handleId(111, 14, data.bankGuaranteeTypeIds.filter((e: number) => e > 32 && e < 38).join(), preIds + ',53,111', 1));
lcRule.push(
handleId(54, 15, '[' + data.minimumLoan + ',' + data.loanCeiling + ']', preIds + ',54', 5),
handleId(55, 16, '[' + data.minimumAprOnLoan + ',' + data.maximumAnnualInterestRate + ']', preIds + ',55', 5),
handleId(56, 17, '[' + data.minimumTermOfLoan + ',' + data.maximumTermOfLoan + ']', preIds + ',56', 5),
handleId(57, 18, data.modeRepayment, preIds + ',57', 1),
);
data.whetherToSupportEarlyRepayment && lcRule.push(handleId(58, '', '', preIds + ',58', ''));
await addOperation({
parentId: preIds,
lcJudgmentRuleReq: lcRule,
projectId: 1,
});
};
onMounted(() => {
getConfig();
});
</script>
<style lang="scss" scoped>
@import url(../../../styles/form.scss);
.audit {
@apply py-5 px-4 mb-[30px] bg-[#f9fafc] rounded-[10px];
.line {
@apply mb-[18px] text-sm leading-[1.6];
}
.field {
@apply text-sm font-semibold;
}
}
</style>

@ -0,0 +1,131 @@
<template>
<div>
<el-tabs v-model="curTab"
@tab-click="tabChange">
<el-tab-pane label="产品审批"
name="tab1">
<info />
<el-form ref="formRef"
:model="form"
:rules="rules"
label-width="100px"
label-suffix=":"
class="form pt-5 mt-5 border-t border-t-solid border-t-[#EDF1F5]"
status-icon>
<el-form-item label="审批意见"
prop="status">
<el-select v-model="form.status"
placeholder="请选择">
<el-option v-for="(item, i) in config[0]?.subject?.itemList.slice(2)"
:key="i"
:label="item.options"
:value="item.itemId" />
</el-select>
</el-form-item>
<el-form-item label="意见描述"
prop="opinionDescription">
<el-input type="textarea"
placeholder="审批意见500字以内。"
maxlength="500"
v-model="form.opinionDescription"></el-input>
</el-form-item>
<div class="flex justify-end">
<div class="submit"
@click="submit(formRef)">审批</div>
</div>
</el-form>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup lang="ts">
import { ref, computed, reactive, watch, onMounted } from 'vue';
import { ElMessage } from 'element-plus';
import type { TabsPaneContext, FormInstance, FormRules } from 'element-plus';
import { useRouter, useRoute } from 'vue-router';
import Info from './Info.vue';
import { findById, examineAndApprove } from '@/api/bank';
import { getProcessInformationBasedOnRoles, addOperation } from '@/api/judgment';
import dayjs from 'dayjs';
const emit = defineEmits(['getList']);
interface RuleForm {
id: any;
status: any;
opinionDescription?: string;
}
const router = useRouter();
const route = useRoute();
const curTab = ref<string>('tab1');
const info = ref<any>({});
const config = ref<any[]>([]);
const formRef = ref<FormInstance>();
const form = reactive<RuleForm>({
id: computed(() => +route.query.id),
status: '',
opinionDescription: '',
});
const rules = reactive<FormRules<RuleForm>>({
status: [{ required: true, message: '请选择审批意见', trigger: 'change' }],
opinionDescription: [{ required: true, message: '请输入意见描述', trigger: 'blur' }],
});
// tab
const tabChange = (tab: TabsPaneContext, event: Event) => {
console.log(tab, event);
};
//
const getConfig = async () => {
const { process } = await getProcessInformationBasedOnRoles(43); // 43
config.value = process;
};
watch(
() => route.query,
() => {
// getDetail();
},
{
immediate: true,
},
);
//
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate(async (valid, fields) => {
if (valid) {
try {
const param = JSON.parse(JSON.stringify(form));
param.approvalTime = dayjs(new Date()).format('YYYY年-M月-D日');
// const { message } = await examineAndApprove(param);
const { message } = await examineAndApprove(param.id, param.opinionDescription, param.status, param.approvalTime);
// addRecord(param, message);
ElMessage.success('提交成功!');
emit('getList', 1);
} finally {
}
} else {
console.log('error submit!', fields);
}
});
};
//
const addRecord = async (data: Record<string, any>, newId: number) => {
const preIds = `1,2,43,${data.productType ? 45 : 44},${newId}`; // 1id/44/45
const lcRule = <Record<string, any>[]>[handleId(145, 69, data.status, preIds + ',145', 1), handleId(146, 70, data.opinionDescription, preIds + ',146', 3)];
await addOperation({
parentId: preIds,
lcJudgmentRuleReq: lcRule,
projectId: 1,
});
};
onMounted(() => {
getConfig();
});
</script>
<style lang="scss" scoped>
@import url(../../../styles/form.scss);
</style>

@ -0,0 +1,133 @@
<template>
<div class="block"
style="padding-top: 0">
<el-tabs v-model="curTab"
@tab-click="tabChange">
<el-tab-pane label="准入策略"
name="tab1">
<div class="flex">
<div class="left w-[241px] pr-5 py-4">
<ul class="products">
<li v-for="(item, i) in list[0]?.recordChildren"
:key="i"
:class="{ active: item.id === id }"
@click="switchProduct(item.id)">
<h6>{{ item.name }}</h6>
<!-- <p class="type">{{ item.productNumber + ' ' + item.guarantyStyle }}</p> -->
</li>
</ul>
</div>
<div class="right flex-1 px-5 pt-2">
<!-- <component :is="Config"
v-model="form"></component> -->
<config v-if="action === 'config'"
@getList="getList"></config>
<detail v-if="action === 'detail'"></detail>
<add v-else-if="action === 'add'"
@getList="getList"></add>
<approve v-else-if="action === 'approve'"
@getList="getList"></approve>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="信用评分策略"
name="tab2"> </el-tab-pane>
<el-tab-pane label="风险度策略"
name="tab3"> </el-tab-pane>
</el-tabs>
</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 type { TabsPaneContext } from 'element-plus';
import { bankingProductsList, batchDeletion } from '@/api/bank';
import { getProcessInformationBasedOnRoles } from '@/api/judgment';
import { useRouter, useRoute } from 'vue-router';
import { getStatus } from '@/store/useProduct';
import Config from './Config.vue';
import Detail from './Detail.vue';
import Add from './Add.vue';
import Approve from './Approve.vue';
const router = useRouter();
const route = useRoute();
const curTab = ref<string>('tab1');
const action = ref<any>('');
const list = ref<Array<any>>([]);
const loading = ref<boolean>(false);
const productType = computed(() => route.query.type); // /
const role = computed(() => +route.query.role || 41);
const id = computed(() => +route.query.id);
//
const getList = async () => {
const { process } = await getProcessInformationBasedOnRoles(67);
id.value ||
router.push({
path: `/product/strategy/config`,
query: {
...route.query,
id: process[0].recordChildren[0].id,
},
});
list.value = process;
};
onMounted(() => {
getList();
});
watch(
route,
(route: any) => {
action.value = route.params.action;
},
{
immediate: true,
},
);
//
const switchProduct = (id: number) => {
router.push({
path: `/product/strategy/config`,
query: {
...route.query,
id,
},
});
};
// tab
const tabChange = (tab: TabsPaneContext, event: Event) => {
console.log(tab, event);
};
</script>
<style lang="scss" scoped>
.left {
border-right: 1px solid #e9eff2;
}
.products {
li {
@apply relative p-5 mb-5 rounded-[10px] cursor-pointer border border-solid;
background: url(../../assets/images/10.png) 0 0/100% 100% 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,348 @@
<template>
<el-table class="c-table"
:data="form"
:max-height="height"
:span-method="span"
border>
<el-table-column prop="name"
label="指标"
min-width="150"
align="center">
</el-table-column>
<el-table-column label="规则"
min-width="250"
align="center">
<template #default="{ row }">
<!-- 大病报销 || 贫困户 -->
<div v-if="row.stRecordId == 161 || row.stRecordId == 164"
class="flex items-center">
<span class="whitespace-nowrap">{{ row?.recordChildren[row.span ? 1 : 0]?.name }}</span>
<div class="w-[90px] ">
<el-select class="mx-2"
v-model="row.symbol">
<el-option v-for="item in symbols"
:key="item"
:label="item.name"
:value="item.name" />
</el-select>
</div>
<el-input class="w-[80px]"
v-model="row.num"></el-input>
<span class="ml-2 whitespace-nowrap">万元</span>
</div>
<!-- 大龄未婚 -->
<div v-else-if="row.stRecordId == 167"
class="flex items-center">
<template v-if="row.span">
<span class="whitespace-nowrap">且近一年</span>
<div class="w-[90px] ">
<el-select class="mx-2"
v-model="row.had">
<el-option label="有"
value="1" />
<el-option label="无"
value="2" />
</el-select>
</div>
<span class="ml-2 whitespace-nowrap">缴纳过社保或公积金</span>
</template>
<template v-else>
<span class="whitespace-nowrap">未婚且年龄</span>
<div class="w-[90px] ">
<el-select class="mx-2"
v-model="row.symbol">
<el-option v-for="item in symbols"
:key="item"
:label="item.name"
:value="item.name" />
</el-select>
</div>
<el-input class="w-[80px]"
v-model="row.num"></el-input>
<span class="ml-2 whitespace-nowrap">,</span>
<el-select class="w-[80px] ml-2"
v-model="row.had">
<el-option label="有"
value="1" />
<el-option label="无"
value="2" />
</el-select>
<span class="ml-2 whitespace-nowrap">固定资产</span>
</template>
</div>
<span v-else>{{ '命中' + row.name }}</span>
</template>
</el-table-column>
<el-table-column label="本人命中进黑名单"
width="140"
align="center">
<template #default="{ row }">
<el-checkbox v-model="row.personalHitBlacklist"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="配偶命中拒入"
width="140"
align="center">
<template #default="{ row }">
<el-checkbox v-model="row.mateHitRejected"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="父母/子女命中拒入"
width="150"
align="center">
<template #default="{ row }">
<el-checkbox v-model="row.parentsHitRejected"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="其它家庭成员命中拒入"
width="170"
align="center">
<template #default="{ row }">
<el-checkbox v-model="row.otherFamilyMembersHitRejected"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="企业大股东命中拒入"
width="160"
align="center">
<template #default="{ row }">
<el-checkbox v-model="row.corporateMajorityHitRejected"></el-checkbox>
</template>
</el-table-column>
</el-table>
<div class="flex justify-end">
<div class="submit"
@click="submit">确认完成配置</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch, onMounted } from 'vue';
import { ElMessage } from 'element-plus';
import { accessStrategyGovernmentBlacklistFind, accessStrategyGovernmentBlacklist } from '@/api/bank';
import { getProcessInformationBasedOnRoles, addOperation } from '@/api/judgment';
import type { TableColumnCtx } from 'element-plus';
import { useRouter, useRoute } from 'vue-router';
import { handleId } from '@/utils/common';
const emit = defineEmits(['getList']);
const router = useRouter();
const route = useRoute();
const id = computed(() => +route.query.id);
const form = ref<Record<string, any>[]>([]);
const height = window.innerHeight - 270;
const symbols = <Record<string, any>[]>[
{
name: '>=',
},
{
name: '<',
},
{
name: '>',
},
{
name: '==',
},
{
name: '<=',
},
];
//
const getConfig = async () => {
const { process } = await getProcessInformationBasedOnRoles(150);
const result = [];
process.map((e) => {
let temp = {
checkpointId: 1,
projectId: 1,
name: e.name,
recordChildren: e.recordChildren,
isRule: isRule(e.id) ? 1 : 0,
corporateMajorityHitRejected: false,
mateHitRejected: false,
otherFamilyMembersHitRejected: false,
parentsHitRejected: false,
personalHitBlacklist: false,
symbol: '>=',
had: '',
num: '',
ruleOne: '',
ruleThree: '',
ruleTwo: '',
stRecordId: e.id,
};
result.push(temp);
if (isRule(e.id)) {
temp = JSON.parse(JSON.stringify(temp));
temp.span = 1;
result.push(temp);
}
});
form.value = result;
console.log('🚀 ~ file: CardList.vue:68 ~ getList ~ list.value:', form.value);
};
//
const getDetail = async () => {
if (id.value) {
try {
const { data } = await accessStrategyGovernmentBlacklistFind(1, 1);
// info.value = data;
getConfig();
} finally {
}
}
};
watch(
() => route.query,
() => {
// getDetail();
getConfig();
},
{
immediate: true,
},
);
// 3
const isRule = (rule: number): boolean => {
return rule === 161 || rule === 164 || rule === 167;
};
interface SpanMethodProps {
row: Record<string, any>;
column: TableColumnCtx<Record<string, any>>;
rowIndex: number;
columnIndex: number;
}
//
const span = ({ row, column, rowIndex, columnIndex }: SpanMethodProps) => {
if (columnIndex === 0 || columnIndex === 2 || columnIndex === 3 || columnIndex === 4 || columnIndex === 5 || columnIndex === 6) {
if (rowIndex === 4 || rowIndex === 6 || rowIndex === 8) {
return {
rowspan: 2,
colspan: 1,
};
} else if (rowIndex === 5 || rowIndex === 7 || rowIndex === 9) {
return {
rowspan: 0,
colspan: 0,
};
}
}
};
onMounted(() => {});
//
const submit = async () => {
const param = JSON.parse(JSON.stringify(form.value));
param.map((e) => {
e.corporateMajorityHitRejected = +e.corporateMajorityHitRejected;
e.mateHitRejected = +e.mateHitRejected;
e.otherFamilyMembersHitRejected = +e.otherFamilyMembersHitRejected;
e.parentsHitRejected = +e.parentsHitRejected;
e.personalHitBlacklist = +e.personalHitBlacklist;
if (e.stRecordId == 161 || e.stRecordId == 164) {
if (e.span) {
} else {
e.ruleOne = e.symbol + e.num;
}
}
});
const { message } = await accessStrategyGovernmentBlacklist(param);
// addRecord(param, message);
ElMessage.success('提交成功!');
emit('getList', 1);
};
//
const addRecord = async (data: Record<string, any>, newId: number) => {
const isEnterprise = info.value.productType === 1;
const preIds = `1,2,42,${newId}`; // 1id
const lcRule = <Record<string, any>[]>[];
//
if (isEnterprise) {
lcRule.push(
handleId(120, 44, data.accountMaterials, preIds + ',112,120', 1),
handleId(121, 45, 162, preIds + ',112,121', 1),
handleId(122, 46, data.loanApplicationMethod, preIds + ',113,122', 1),
handleId(123, 47, data.borrowerMaterial, preIds + ',113,123', 1),
handleId(124, 48, data.collateral, preIds + ',113,124', 1),
handleId(125, 49, data.businessMaterials, preIds + ',113,125', 1),
handleId(126, 50, data.supplementaryMaterials, preIds + ',113,126', 1),
handleId(127, 51, data.runBatchObject, preIds + ',114,127', 1),
handleId(128, 52, data.accessStrategy, preIds + ',114,128', 1),
);
data.personalCreditScoringStrategiesCheck && lcRule.push(handleId(129, 53, '240,' + data.personalCreditScoringStrategies, preIds + ',114,129', 1));
data.corporateCreditScoringStrategiesCheck && lcRule.push(handleId(129, 53, '241,' + data.corporateCreditScoringStrategies, preIds + ',114,129', 1));
lcRule.push(handleId(130, 54, data.riskDegreeStrategy, preIds + ',114,130', 1));
data.interestRatePricingModelCheck && lcRule.push(handleId(131, 55, '247,' + data.interestRatePricingModel, preIds + ',114,131', 1));
data.individualInterestRateModel && lcRule.push(handleId(131, 55, data.individualInterestRateModel, preIds + ',114,131', 1));
lcRule.push(
handleId(132, 56, data.dueDiligenceMode, preIds + ',115,132', 1),
handleId(133, 57, data.dueDiligenceContent, preIds + ',115,133', 1),
handleId(134, 58, data.reviewContent, preIds + ',116,134', 1),
handleId(135, 59, data.reviewSignature, preIds + ',116,135', 1),
handleId(136, 60, data.reviewApproveContent, preIds + ',117,136', 1),
handleId(137, 61, data.approvalSignature, preIds + ',117,137', 1),
handleId(138, 62, data.contractMaterials, preIds + ',118,138', 1),
);
data.loanContract && lcRule.push(handleId(139, 63, data.loanContract, preIds + ',118,139', 1));
data.mortgageContract && lcRule.push(handleId(139, 63, data.mortgageContract, preIds + ',118,139', 1));
data.pledgeContract && lcRule.push(handleId(139, 63, data.pledgeContract, preIds + ',118,139', 1));
data.guaranteeContract && lcRule.push(handleId(139, 63, data.guaranteeContract, preIds + ',118,139', 1));
lcRule.push(handleId(140, 64, data.selectionStrategy, preIds + ',119,140', 1));
} else {
lcRule.push(
handleId(75, 20, data.accountMaterials, preIds + ',72,75', 1),
handleId(76, 21, 46, preIds + ',72,76', 1),
handleId(77, 22, data.loanApplicationMethod, preIds + ',73,77', 1),
handleId(78, 23, data.borrowerMaterial, preIds + ',73,78', 1),
handleId(79, 24, data.mateMaterial, preIds + ',73,79', 1),
handleId(80, 25, data.businessMaterials, preIds + ',73,80', 1),
handleId(81, 26, data.supplementaryMaterials, preIds + ',73,81', 1),
handleId(82, 27, data.runBatchObject, preIds + ',74,82', 1),
handleId(83, 28, data.accessStrategy, preIds + ',74,83', 1),
);
data.personalCreditScoringStrategiesCheck && lcRule.push(handleId(84, 29, '94,' + data.personalCreditScoringStrategies, preIds + ',74,84', 1));
data.corporateCreditScoringStrategiesCheck && lcRule.push(handleId(84, 29, '95,' + data.corporateCreditScoringStrategies, preIds + ',74,84', 1));
lcRule.push(handleId(85, 30, data.riskDegreeStrategy, preIds + ',74,85', 1));
data.interestRatePricingModelCheck && lcRule.push(handleId(86, 31, '102,' + data.interestRatePricingModel, preIds + ',74,86', 1));
data.individualInterestRateModel && lcRule.push(handleId(86, 31, data.individualInterestRateModel, preIds + ',74,86', 1));
lcRule.push(
handleId(88, 32, data.dueDiligenceMode, preIds + ',87,88', 1),
handleId(89, 33, data.dueDiligenceContent, preIds + ',87,89', 1),
handleId(91, 34, data.reviewContent, preIds + ',90,91', 1),
handleId(92, 35, data.reviewSignature, preIds + ',90,92', 1),
handleId(94, 36, data.reviewApproveContent, preIds + ',93,94', 1),
handleId(95, 37, data.approvalSignature, preIds + ',93,95', 1),
handleId(97, 38, data.contractMaterials, preIds + ',96,97', 1),
);
data.loanContract && lcRule.push(handleId(98, 39, data.loanContract, preIds + ',96,98', 1));
data.mortgageContract && lcRule.push(handleId(98, 39, data.mortgageContract, preIds + ',96,98', 1));
data.pledgeContract && lcRule.push(handleId(98, 39, data.pledgeContract, preIds + ',96,98', 1));
data.guaranteeContract && lcRule.push(handleId(98, 39, data.guaranteeContract, preIds + ',96,98', 1));
lcRule.push(handleId(99, 40, data.selectionStrategy, preIds + ',99', 1));
}
await addOperation({
parentId: preIds,
lcJudgmentRuleReq: lcRule,
projectId: 1,
});
};
</script>
<style lang="scss" scoped>
@import url(../../../styles/form.scss);
.c-table {
:deep(.el-input__inner) {
@apply px-2;
}
}
</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,181 @@
<template>
<div v-if="info.approvalTime"
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>
<div class="info mt-2">
<h6 class="step-name">产品定义</h6>
<p class="text">{{ info.productDefinition }}</p>
<h6 class="step-name mt-5">产品要素</h6>
<p class="text">产品名称{{ info.productName }}</p>
<p class="text">贷款币种人民币</p>
<p v-if="info.loanPurpose"
class="text">
贷款用途{{
info.loanPurpose === '购房'
? '可用于住房按揭贷款、二手房住房按揭贷款。'
: info.loanPurpose === '消费'
? '贷款用途可用于除购房之外的合法个人消费支出,不得用于投资经营,不得用于无指定用途的个人支出。'
: info.loanPurpose === '经营' && !info.productType
? '可用于个人或其企业生产和经营活动中临时性、季节性等流动资金周转以及购置、安装或修理小型设备和装潢经营性场所所需的人民币贷款业务。'
: info.loanPurpose === '创业'
? '用于创业或再创业过程中的资金需求。'
: info.loanPurpose === '经营' && info.productType
? '用于短期生产经营周转的可循环的人民币信用贷款业务。'
: info.otherPurposesOfLoan
}}
</p>
<p class="text">担保方式{{ info.guarantyStyle }}</p>
<p class="text">贷款期限{{ info.minimumTermOfLoan }} - {{ info.maximumTermOfLoan }}</p>
<p class="text">贷款限额{{ info.minimumLoan }}万元 - {{ info.loanCeiling }}万元</p>
<p class="text">贷款利率{{ info.minimumAprOnLoan }}% - {{ info.maximumAnnualInterestRate }}%</p>
<p class="text">还款方式{{ info.modeRepayment }}</p>
<div class="text">
贷款对象
<p v-if="info.minimumAge">年龄{{ info.minimumAge }} - {{ info.maximumAge }}周岁</p>
<p v-if="info.educationalRequirements">取得{{ info.educationalRequirements }}以上学历</p>
<p v-if="info.currentWorkingLife">{{ info.currentWorkingLife }}</p>
<p v-if="info.providentFundAndSocialSecurity">连续缴纳本市社保或者公积金6个月</p>
<p>持有中国银行I类账户且已关联至手机银行且在我行及其他金融同业无不良信用记录</p>
</div>
<template v-if="riskInfo">
<h6 class="step-name mt-5">材料要求</h6>
<p class="text">办理账户-提供材料{{ riskInfo?.accountMaterials }}</p>
<p v-if="riskInfo?.sendingAccount"
class="text">办理账户-发放账户借记卡或放款专户</p>
<p v-if="riskInfo?.loanApplicationMethod"
class="text">贷款申请-申请方式{{ riskInfo?.loanApplicationMethod }}</p>
<p v-if="riskInfo?.borrowerMaterial"
class="text">贷款申请-提供材料-借款人材料{{ riskInfo?.borrowerMaterial }}</p>
<template v-if="info.productType">
<p v-if="riskInfo?.enterpriseMaterial"
class="text">贷款申请-提供材料-企业材料{{ riskInfo?.enterpriseMaterial }}</p>
<p v-if="riskInfo?.collateral"
class="text">贷款申请-提供材料-抵押物{{ riskInfo?.collateral }}</p>
</template>
<template v-else>
<p v-if="riskInfo?.mateMaterial"
class="text">贷款申请-提供材料-配偶材料{{ riskInfo?.mateMaterial }}</p>
<p v-if="riskInfo?.businessMaterials"
class="text">贷款申请-提供材料-经营类材料{{ riskInfo?.businessMaterials }}</p>
</template>
<p v-if="riskInfo?.supplementaryMaterials"
class="text">贷款申请-提供材料-补充材料{{ riskInfo?.supplementaryMaterials }}</p>
<p v-if="riskInfo?.runBatchObject"
class="text">系统跑批准入风控策略-跑批对象{{ riskInfo?.runBatchObject }}</p>
<p v-if="riskInfo?.accessStrategy"
class="text">系统跑批准入风控策略-准入策略{{ riskInfo?.accessStrategy }}</p>
<p v-if="riskInfo?.personalCreditScoringStrategies"
class="text">系统跑批准入风控策略-信用评分策略{{ riskInfo?.personalCreditScoringStrategies }}</p>
<p v-if="riskInfo?.riskDegreeStrategy"
class="text">系统跑批准入风控策略-风险度策略{{ riskInfo?.riskDegreeStrategy }}</p>
<p v-if="riskInfo?.interestRatePricingModel"
class="text">系统跑批准入风控策略-利率定价模型-个人额度模型{{ riskInfo?.interestRatePricingModel }}</p>
<p v-if="riskInfo?.individualInterestRateModel"
class="text">系统跑批准入风控策略-利率定价模型-个人利率模型</p>
<p v-if="riskInfo?.dueDiligenceMode"
class="text">尽职调查-尽调方式{{ riskInfo?.dueDiligenceMode }}</p>
<p v-if="riskInfo?.dueDiligenceContent"
class="text">尽职调查-尽调内容{{ riskInfo?.dueDiligenceContent }}</p>
<p v-if="riskInfo?.reviewContent"
class="text">贷款审查-审查内容{{ riskInfo?.reviewContent }}</p>
<p v-if="riskInfo?.reviewSignature"
class="text">贷款审查-审查签字{{ riskInfo?.reviewSignature }}</p>
<p v-if="riskInfo?.reviewApproveContent"
class="text">贷款审批-审批内容{{ riskInfo?.reviewApproveContent }}</p>
<p v-if="riskInfo?.approvalSignature"
class="text">贷款审批-审批签字{{ riskInfo?.approvalSignature }}</p>
<p v-if="riskInfo?.contractMaterials"
class="text">签订合同-提供的材料{{ riskInfo?.contractMaterials }}</p>
<p v-if="riskInfo?.loanContract"
class="text">签订合同-合同模板-借贷合同{{ riskInfo?.loanContract }}</p>
<p v-if="riskInfo?.mortgageContract"
class="text">签订合同-合同模板-抵押合同{{ riskInfo?.mortgageContract }}</p>
<p v-if="riskInfo?.pledgeContract"
class="text">签订合同-合同模板-质押合同{{ riskInfo?.pledgeContract }}</p>
<p v-if="riskInfo?.guaranteeContract"
class="text">签订合同-合同模板-担保合同{{ riskInfo?.guaranteeContract }}</p>
<p v-if="riskInfo?.selectionStrategy"
class="text">贷后管理-选择策略{{ riskInfo?.selectionStrategy }}</p>
<h6 class="step-name mt-5">业务流程</h6>
<p class="text">1贷款人APP向智信银行公司业务部门提交借款申请同时提交贷款用途证明文件以及有关资料</p>
<p class="text">2智信银行对贷款人的贷款申请进行审查同时根据实际情况要求借款人提供补充资料</p>
<p class="text">3智信银行内部审批通过后与贷款人签订借款合同</p>
<p class="text">4贷款人落实有关提款前提条件根据贷款合同提款</p>
<h6 class="step-name mt-5">注意要点</h6>
<p class="text">
贷款人在使用款额度时必须明确说明贷款用途贷款人必须在获得循环贷款额度后方可在额度与用途范围内申请贷款受理机构仅限在原经办行同时逐笔上报中心核批单笔贷款金额不得超过贷款人单笔消费或投资总金额的80%
</p>
</template>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue';
import { findById } from '@/api/bank';
import { useRouter, useRoute } from 'vue-router';
import { getStatus } from '@/store/useProduct';
const router = useRouter();
const route = useRoute();
const id = computed(() => +route.query.id);
const info = ref<Record<string, any>>({});
const riskInfo = ref<Record<string, any>>(null);
//
const getDetail = async () => {
if (id.value) {
try {
const { data } = await findById(id.value);
info.value = data;
if (info.value.riskControlDetails) riskInfo.value = data.riskControlDetails;
} 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];
}
}
.audit {
@apply py-5 px-4 mb-[30px] bg-[#f9fafc] rounded-[10px];
.line {
@apply mb-[18px] text-sm leading-[1.6];
}
.field {
@apply text-sm font-semibold;
}
}
</style>
Loading…
Cancel
Save