5dacd665 by lihua

估值模型功能提交

1 parent d0e1f5d2
...@@ -48,3 +48,39 @@ export const sendEntryMsg = (params) => request({ ...@@ -48,3 +48,39 @@ export const sendEntryMsg = (params) => request({
48 method: 'post', 48 method: 'post',
49 params 49 params
50 }); 50 });
51
52 /** ----------------------------------------估值模型接口--------------------------------- */
53
54 /** 获取数据产品估值模型列表 */
55 export const getValuationModelList = (params) => request({
56 url: `${import.meta.env.VITE_API_NEW_PORTAL}/valuation-model/page-list`,
57 method: 'post',
58 data: params
59 })
60
61 /** 保存估值模型 */
62 export const saveValuationMode = (params) => request({
63 url: `${import.meta.env.VITE_API_NEW_PORTAL}/valuation-model/save`,
64 method: 'post',
65 data: params
66 })
67
68 /** 更新估值模型 */
69 export const updateValuationMode = (params) => request({
70 url: `${import.meta.env.VITE_API_NEW_PORTAL}/valuation-model/update`,
71 method: 'put',
72 data: params
73 })
74
75 /** 删除估值模型 */
76 export const deleteValuationMode = (params) => request({
77 url: `${import.meta.env.VITE_API_NEW_PORTAL}/valuation-model/delete`,
78 method: 'delete',
79 data: params
80 })
81
82 /** 获取估值模型详情 */
83 export const getValuationModelDetail = (params) => request({
84 url: `${import.meta.env.VITE_API_NEW_PORTAL}/valuation-model/detail?guid=${params.guid}`,
85 method: 'get'
86 })
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -68,6 +68,45 @@ const routes: RouteRecordRaw[] = [ ...@@ -68,6 +68,45 @@ const routes: RouteRecordRaw[] = [
68 }, 68 },
69 ], 69 ],
70 }, 70 },
71 {
72 path: '/data-entry/valuation-model',
73 component: Layout,
74 meta: {
75 title: '估值模型',
76 icon: 'sidebar-videos',
77 },
78 children: [
79 {
80 path: '',
81 name: 'valuationModel',
82 component: () => import('@/views/data_transaction/valuationModel.vue'),
83 meta: {
84 title: '估值模型',
85 sidebar: false,
86 breadcrumb: false,
87 cache: true
88 },
89 },
90 {
91 path: 'valuation-model-create',
92 name: 'valuationModelCreate',
93 component: () => import('@/views/data_transaction/valuationModelCreate.vue'),
94 meta: {
95 title: '新建估值模型',
96 sidebar: false,
97 breadcrumb: false,
98 cache: true,
99 editPage: true,
100 reuse: true
101 },
102 beforeEnter: (to, from) => {
103 if (to.query.guid) {
104 to.meta.title = `编辑-${to.query.name}`;
105 }
106 }
107 },
108 ],
109 },
71 ] 110 ]
72 111
73 export default routes 112 export default routes
......
1 const useEntryStore = defineStore(
2 // api标签分类guid
3 'isRefresh',
4
5 () => {
6
7 const isRefresh = ref(false);
8 function setIsRefresh(update: boolean) {
9 isRefresh.value = update;
10 }
11
12 return {
13 isRefresh,
14 setIsRefresh,
15 }
16 },
17 )
18
19 export default useEntryStore
...\ No newline at end of file ...\ No newline at end of file
1 <script lang="ts" setup name="valuationModel">
2 import TableTools from "@/components/Tools/table_tools.vue";
3 import { commonPageConfig } from '@/components/PageNav/index';
4 import {
5 getValuationModelList,
6 deleteValuationMode
7 } from '@/api/modules/dataEntry';
8 import { TableColumnWidth } from "@/utils/enum";
9 import { changeNum } from "@/utils/common";
10 import useEntryStore from "@/store/modules/dataEntry";
11
12 const entryStore = useEntryStore();
13
14 const router = useRouter()
15
16 const { proxy } = getCurrentInstance() as any;
17
18 /** 头部搜索框配置 */
19 const searchItemList = ref([
20 {
21 type: "input",
22 label: "",
23 field: "damName",
24 default: "",
25 placeholder: "数据产品名称",
26 clearable: true,
27 },
28 {
29 type: "select",
30 label: "",
31 field: "evaluateMethod",
32 default: "",
33 placeholder: "评估方法",
34 options: [
35 { label: "成本法", value: "1" },
36 { label: "收益法", value: "2" },
37 ],
38 clearable: true,
39 }
40 ]);
41
42 /** 分页及搜索传参信息配置。 */
43 const page = ref({
44 ...commonPageConfig,
45 damName: '',
46 evaluateMethod: ''
47 });
48
49 const tableSelectRowData: any = ref([]);
50
51 const tableInfo = ref({
52 id: 'valuation-model-table',
53 multiple: true,
54 fields: [
55 { label: "序号", type: "index", width: TableColumnWidth.INDEX, align: "center" },
56 { label: "数据产品名称", field: "damName", width: 160 },
57 {
58 label: "评估方法", field: "evaluateMethod", width: 140, getName: (scope) => {
59 return scope.row.evaluateMethod == '1' ? '成本法' : '收益法';
60 }
61 },
62 { label: "评估基准日", field: "evaluateBaseDate", width: TableColumnWidth.DATE, },
63 {
64 label: "评估价值(元)", field: "damValuation", width: 160, align: 'right'
65 },
66 { label: "修改人", field: "updateUserName", width: TableColumnWidth.USERNAME },
67 { label: "修改时间", field: "updateTime", width: TableColumnWidth.DATETIME },
68 ],
69 data: [],
70 page: {
71 type: "normal",
72 rows: 0,
73 ...page.value,
74 },
75 actionInfo: {
76 label: "操作",
77 type: "btn",
78 width: 140,
79 fixed: 'right',
80 btns: (scope) => {
81 let btnsArr: any = [];
82 btnsArr.push({
83 label: "编辑", value: "edit", click: (scope) => {
84 router.push({
85 name: 'valuationModelCreate',
86 query: {
87 guid: scope.row.guid,
88 name: scope.row.damName
89 }
90 })
91 }
92 });
93 btnsArr.push({
94 label: "删除", value: "delete", click: (scope) => {
95 proxy.$openMessageBox('此操作将永久删除, 是否继续?', () => {
96 deleteValuationMode([scope.row.guid]).then((res: any) => {
97 if (res.code == proxy.$passCode) {
98 page.value.curr = 1;
99 getTableData();
100 proxy.$ElMessage({
101 type: "success",
102 message: "删除成功",
103 });
104 } else {
105 proxy.$ElMessage({
106 type: 'error',
107 message: res.msg,
108 })
109 }
110 })
111 }, () => {
112 proxy.$ElMessage.info("已取消删除");
113 })
114 }
115 });
116 return btnsArr
117 },
118 },
119 loading: false
120 })
121
122 const toSearch = (val: any, clear: boolean = false) => {
123 if (clear) {
124 searchItemList.value.map((item) => (item.default = ""));
125 page.value.damName = '';
126 page.value.evaluateMethod = "";
127 } else {
128 page.value.damName = val.damName;
129 page.value.evaluateMethod = val.evaluateMethod;
130 }
131 getTableData();
132 };
133
134 const getTableData = () => {
135 tableInfo.value.loading = true
136 getValuationModelList({
137 pageIndex: page.value.curr,
138 pageSize: page.value.limit,
139 damName: page.value.damName,
140 evaluateMethod: page.value.evaluateMethod
141 }).then((res: any) => {
142 if (res.code == proxy.$passCode) {
143 const data = res.data || {}
144 tableInfo.value.data = data.records || []
145 tableInfo.value.page.limit = data.pageSize
146 tableInfo.value.page.curr = data.pageIndex
147 tableInfo.value.page.rows = data.totalRows
148 } else {
149 proxy.$ElMessage({
150 type: 'error',
151 message: res.msg,
152 })
153 }
154 tableInfo.value.loading = false
155 }).catch(() => {
156 tableInfo.value.loading = false
157 })
158 };
159
160 const tablePageChange = (info) => {
161 page.value.curr = Number(info.curr);
162 page.value.limit = Number(info.limit);
163 tableInfo.value.page.curr = page.value.curr;
164 tableInfo.value.page.limit = page.value.limit;
165 getTableData();
166 };
167
168 const tableSelectionChange = (val) => {
169 tableSelectRowData.value = val;
170 };
171
172 const newCreate = () => {
173 router.push({
174 name: 'valuationModelCreate'
175 });
176 }
177
178 const batchDelete = () => {
179 if (tableSelectRowData.value.length == 0) {
180 proxy.$ElMessage({
181 type: 'error',
182 message: '请选择需要删除的数据',
183 })
184 return
185 }
186 proxy.$openMessageBox('此操作将永久删除, 是否继续?', () => {
187 deleteValuationMode(tableSelectRowData.value.map(d => d.guid)).then((res: any) => {
188 if (res.code == proxy.$passCode) {
189 page.value.curr = 1;
190 getTableData();
191 proxy.$ElMessage.success('删除成功');
192 } else {
193 proxy.$ElMessage.error(res.msg);
194 }
195 })
196 }, () => {
197 proxy.$ElMessage.info("已取消删除");
198 })
199 }
200
201 onBeforeMount(() => {
202 // toSearch({})
203 })
204
205 onActivated(() => {
206 if (entryStore.isRefresh) {
207 getTableData();
208 entryStore.setIsRefresh(false);
209 }
210 })
211
212 </script>
213
214 <template>
215 <div class="container_wrap">
216 <div class="table_tool_wrap">
217 <!-- 头部搜索 -->
218 <TableTools :searchItems="searchItemList" :searchId="'data-source-search'" @search="toSearch" />
219 <div class="tools_btns">
220 <el-button type="primary" @click="newCreate">新建</el-button>
221 <el-button @click="batchDelete">批量删除</el-button>
222 </div>
223 </div>
224 <div class="table_panel_wrap">
225 <Table :tableInfo="tableInfo" @tablePageChange="tablePageChange" @tableSelectionChange="tableSelectionChange" />
226 </div>
227 </div>
228 </template>
229
230 <style lang="scss" scoped>
231 .table_tool_wrap {
232 width: 100%;
233 height: 84px !important;
234 padding: 0 8px;
235
236 .tools_btns {
237 padding: 0px 0 0;
238 }
239 }
240
241 .table_panel_wrap {
242 width: 100%;
243 height: calc(100% - 84px);
244 padding: 0px 8px 0;
245 }
246 </style>
...\ No newline at end of file ...\ No newline at end of file
1 <script lang="ts" setup name="valuationModelCreate">
2 import {
3 getAssetCatalog,
4 saveValuationMode,
5 updateValuationMode,
6 getValuationModelDetail
7 } from "@/api/modules/dataEntry";
8 import { useValidator } from '@/hooks/useValidator';
9 import useUserStore from "@/store/modules/user";
10 import {
11 changeNum,
12 } from "@/utils/common";
13 import moment from "moment";
14 import useEntryStore from "@/store/modules/dataEntry";
15
16 const userStore = useUserStore();
17 const entryStore = useEntryStore();
18 const { required } = useValidator();
19 const { proxy } = getCurrentInstance() as any;
20 const router = useRouter();
21 const route = useRoute();
22 const fullPath = route.fullPath;
23 const fullscreenLoading = ref(false);
24
25 /** 数据产品列表 */
26 const damProductList: any = ref([]);
27
28 const formRef = ref();
29 const valuateFormItems: any = ref([
30 {
31 label: "数据产品名称",
32 type: "select",
33 placeholder: "请选择,来自数据产品目录",
34 field: "damGuid",
35 default: '',
36 options: damProductList.value,
37 props: {
38 label: 'damName',
39 value: 'guid'
40 },
41 disabled: false,
42 filterable: true,
43 clearable: true,
44 required: true,
45 },
46 {
47 label: "基准日",
48 type: "date-month",
49 field: "evaluateBaseDate",
50 default: moment(new Date()).format('YYYY-MM'),
51 placeholder: "请选择",
52 clearable: true,
53 required: true,
54 style: { width: 'calc(33.33% - 70px)', 'margin-right': '8px' },
55 popperClass: 'date-month-popper',
56 disabledDate: (date) => {
57 const curr = new Date();
58 return date.getFullYear() == curr.getFullYear() ? date.getMonth() > curr.getMonth() : false;
59 },
60 },
61 {
62 type: "select",
63 field: "evaluateMethod",
64 default: "1",
65 label: "评估方法",
66 placeholder: "请选择",
67 required: true,
68 options: [
69 { label: "成本法", value: "1" },
70 { label: "收益法", value: "2" },
71 ]
72 },
73 {
74 type: "select",
75 label: "使用年限(1~10)",
76 field: "useYears",
77 default: 1,
78 options: [{
79 value: 1,
80 label: '1'
81 }, {
82 value: 2,
83 label: '2'
84 }, {
85 value: 3,
86 label: '3'
87 }, {
88 value: 4,
89 label: '4'
90 }, {
91 value: 5,
92 label: '5'
93 }, {
94 value: 6,
95 label: '6'
96 }, {
97 value: 7,
98 label: '7'
99 }, {
100 value: 8,
101 label: '8'
102 }, {
103 value: 9,
104 label: '9'
105 }, {
106 value: 10,
107 label: '10'
108 }],
109 placeholder: "年限1~10",
110 clearable: false,
111 filterable: true,
112 required: true,
113 visible: false
114 },
115 ]);
116
117 const valuateFormRules = ref({
118 // damGuid: [required('请选择数据产品名称')],
119 evaluateBaseDate: [required('请选择基准日')],
120 evaluateMethod: [required('请选择评估方法')],
121 useYears: [{ type: 'number', min: 1, max: 10, message: "请填写年限1~10", trigger: "change", },]
122 });
123
124 const handleValudateFormChange = (val, row, info) => {
125 if (row.field == 'evaluateMethod') {
126 valuateFormItems.value.forEach(item => {
127 item.default = info[item.field];
128 if (item.field == 'useYears') {
129 item.visible = item.default != '1';
130 item.default = info.useYears ? info.useYears : 1;
131 }
132 })
133 }
134 }
135
136 /** 获取当月的最后一天。 */
137 const getLastDayOfMonth = (month) => {
138 const year = parseInt(month.split('-')[0], 10);
139 const monthIndex = parseInt(month.split('-')[1], 10) - 1; // JavaScript 的月份是从0开始计数的
140 const date = new Date(year, monthIndex + 1, 0); // 使用0可以得到前一个月的最后一天
141 const yearString = date.getFullYear();
142 const monthString = String(date.getMonth() + 1).padStart(2, '0'); // JavaScript 的月份是从0开始计数的
143 const dayString = String(date.getDate()).padStart(2, '0');
144 return `${yearString}-${monthString}-${dayString}`;
145 }
146
147 const costTableField: any = ref([
148 { label: "环节", field: "link", width: 160 },
149 { label: "一级指标", field: "primaryIndex", width: 160 },
150 { label: "二级指标", field: "secondIndex", width: 160 },
151 { label: '金额(元)', align: 'right', field: 'amount', type: 'input', width: 150, columClass: 'edit_cell' },
152 { label: "通常包含的成本输入项", field: "costInput", width: 380 },
153 { label: "费用科目", field: "expenseAccount", width: 220 },
154 ]);
155
156 const costTableData = ref([{
157 orderNum: 1,
158 link: '顺序性环节',
159 primaryIndex: '数据规划',
160 secondIndex: '数据规划',
161 amount: '',
162 costInput: '包含数据生存周期整体规划所投入的人员薪资、咨询费用及相关资源成本等',
163 expenseAccount: '咨询费/会议费/人工费(拆分)'
164 }, {
165 orderNum: 2,
166 link: '顺序性环节',
167 primaryIndex: '数据采集',
168 secondIndex: '人工采集',
169 amount: '',
170 costInput: '向数据持有人购买数据的价款、注册费、手续费、服务费等',
171 expenseAccount: '人工费、劳保费、劳务费、运输费'
172 }, {
173 orderNum: 3,
174 link: '顺序性环节',
175 primaryIndex: '数据采集',
176 secondIndex: '自动化采集',
177 amount: '',
178 costInput: '在数据采集阶段发生的人员薪酬、打印费、网络费等相关费用',
179 expenseAccount: '人工费、设备费、材料费'
180 }, {
181 orderNum: 4,
182 link: '顺序性环节',
183 primaryIndex: '数据汇聚',
184 secondIndex: '数据传输',
185 amount: '',
186 costInput: '',
187 expenseAccount: ''
188 }, {
189 orderNum: 5,
190 link: '顺序性环节',
191 primaryIndex: '数据汇聚',
192 secondIndex: '网络通讯',
193 amount: '',
194 costInput: '传输数据发生的管道成本',
195 expenseAccount: '网络费用'
196 }, {
197 orderNum: 6,
198 link: '顺序性环节',
199 primaryIndex: '数据加工',
200 secondIndex: '数据脱敏',
201 amount: '',
202 costInput: '对敏感数据进行变形处理所发生的人力成本、技术成本等',
203 expenseAccount: '人工费'
204 }, {
205 orderNum: 7,
206 link: '顺序性环节',
207 primaryIndex: '数据加工',
208 secondIndex: '数据清洗',
209 amount: '',
210 costInput: '去除重复数据、填补缺失值、处理异常值和转换数据格式等投入',
211 expenseAccount: '人工费'
212 }, {
213 orderNum: 8,
214 link: '顺序性环节',
215 primaryIndex: '数据加工',
216 secondIndex: '数据标注',
217 amount: '',
218 costInput: '对数据进行添加标签处理所发生的费用,人工或AI标注',
219 expenseAccount: '人工费、无形资产分摊'
220 }, {
221 orderNum: 9,
222 link: '顺序性环节',
223 primaryIndex: '数据加工',
224 secondIndex: '数据整合',
225 amount: '',
226 costInput: '数据整合成本是指合并整理来自不同数据源的数据所发生的成本',
227 expenseAccount: '人工费、材料费等'
228 }, {
229 orderNum: 10,
230 link: '顺序性环节',
231 primaryIndex: '数据分析',
232 secondIndex: '数据分析',
233 amount: '',
234 costInput: '采用适当的方法对数据进行分析整理所发生的成本费用',
235 expenseAccount: '人工费'
236 }, {
237 orderNum: 11,
238 link: '顺序性环节',
239 primaryIndex: '数据分析',
240 secondIndex: '数据可视化',
241 amount: '',
242 costInput: '通过图形化手段清晰有效地传达信息所发生的成本费用',
243 expenseAccount: '人工费'
244 }, {
245 orderNum: 12,
246 link: '顺序性环节',
247 primaryIndex: '数据产品开发',
248 secondIndex: '数据产品开发',
249 amount: '',
250 costInput: '面向数据应用和服务,开发、封装数据产品所产生的费用',
251 expenseAccount: '人工费,股份支付,生产成本外协技术费'
252 }, {
253 orderNum: 13,
254 link: '全流程环节',
255 primaryIndex: '计算与存储',
256 secondIndex: '数据存储',
257 amount: '',
258 costInput: '存储库的构建、优化等费用',
259 expenseAccount: '云存储资源使用费,数据库使用费'
260 }, {
261 orderNum: 14,
262 link: '全流程环节',
263 primaryIndex: '计算与存储',
264 secondIndex: '计算资源',
265 amount: '',
266 costInput: '按流量计费、云服务分摊',
267 expenseAccount: '计算资源采购费用'
268 }, {
269 orderNum: 15,
270 link: '全流程环节',
271 primaryIndex: '数据维护',
272 secondIndex: '数据维护',
273 amount: '',
274 costInput: '数据权属鉴证、质量评估、登记、交易成本、数据合规费用',
275 expenseAccount: '人工费、技术服务费'
276 }, {
277 orderNum: 16,
278 link: '全流程环节',
279 primaryIndex: '数据维护',
280 secondIndex: '数据维护',
281 amount: '',
282 costInput: '数据加工费用,包括数据调整、补全、标注、更新和脱敏等费用',
283 expenseAccount: '人工费、技术服务费'
284 }, {
285 orderNum: 17,
286 link: '全流程环节',
287 primaryIndex: '数据维护',
288 secondIndex: '数据维护',
289 amount: '',
290 costInput: '数据备份、数据迁移和应急处置等费用',
291 expenseAccount: '人工费、设备费、技术服务费'
292 }, {
293 orderNum: 18,
294 link: '全流程环节',
295 primaryIndex: '数据安全',
296 secondIndex: '信息安全',
297 amount: '',
298 costInput: '软性:等保认证等',
299 expenseAccount: '人工费、等保服务费、质量评价服务费、鉴权咨询费'
300 }, {
301 orderNum: 19,
302 link: '全流程环节',
303 primaryIndex: '数据安全',
304 secondIndex: '硬件或系统安全',
305 amount: '',
306 costInput: '硬件或系统:安全产品、安全管理技术或服务',
307 expenseAccount: '防火墙或安全软件等采购费用'
308 }, {
309 orderNum: 20,
310 link: '全流程环节',
311 primaryIndex: '间接成本',
312 secondIndex: '软硬件成本',
313 amount: '',
314 costInput: '与数据资产相关的软硬件采购或研发以及维护费用',
315 expenseAccount: '材料费,产品检测费,物料消耗费,修理费'
316 }, {
317 orderNum: 21,
318 link: '全流程环节',
319 primaryIndex: '间接成本',
320 secondIndex: '基础设施成本',
321 amount: '',
322 costInput: '包括机房、场地等建设或租赁以及维护费用',
323 expenseAccount: '机房建设,物业费,租金,物联网大数据中心建设费'
324 }, {
325 orderNum: 22,
326 link: '全流程环节',
327 primaryIndex: '间接成本',
328 secondIndex: '公共管理成本',
329 amount: '',
330 costInput: '水电、职工福利、差旅费、折旧费、办公费、通讯费',
331 expenseAccount: '水电、职工福利、差旅费、折旧费、办公费、通讯费'
332 }]);
333
334 const costTableSpanMethod = ({ row, column, rowIndex, columnIndex }) => {
335 if (columnIndex == 0) { //第一列环节
336 let columnValue = costTableData.value[rowIndex].link;
337 if (rowIndex == 0 || columnValue != costTableData.value[rowIndex - 1].link) {
338 let cnt = costTableData.value.filter(d => d.link == columnValue).length;
339 return {
340 rowspan: cnt,
341 colspan: 1
342 }
343 } else {
344 return {
345 rowspan: 0,
346 colspan: 0
347 }
348 }
349 } else if (columnIndex == 1) {//第二列的合并
350 let columnValue = costTableData.value[rowIndex].primaryIndex;
351 if (rowIndex == 0 || columnValue != costTableData.value[rowIndex - 1].primaryIndex) {
352 let cnt = costTableData.value.filter(d => d.primaryIndex == columnValue).length;
353 return {
354 rowspan: cnt,
355 colspan: 1
356 }
357 } else {
358 return {
359 rowspan: 0,
360 colspan: 0
361 }
362 }
363 } else if (columnIndex == 2) {//二级指标,合并数据维护。
364 let columnValue = costTableData.value[rowIndex].secondIndex;
365 if (columnValue == '数据维护') {
366 if (columnValue != costTableData.value[rowIndex - 1].secondIndex) {
367 return {
368 rowspan: 3,
369 colspan: 1
370 }
371 } else {
372 return {
373 rowspan: 0,
374 colspan: 0
375 }
376 }
377 }
378 }
379 return {
380 rowspan: 1,
381 colspan: 1
382 }
383 }
384
385 const costTableSummaryValue: any = ref(0);
386
387 // 表格合计行
388 const costTableSummaryMethod = ({ columns, data }) => {
389 let sums: any[] = [];
390 columns.forEach((column, index) => {
391 if (index === 0) { //需要显示'总金额'的列 坐标 :0
392 sums[index] = '数据资产估值'
393 return
394 } else {
395 if (column.property == 'amount') {
396 const values = data.map(item => parseFloat(item[column.property] ? item[column.property].replace(/,/g, "") : 0));
397 if (!values.every(value => isNaN(value))) {
398 const sum = values.reduce((prev, curr) => {
399 const value = parseFloat(curr || 0)
400 if (!isNaN(value)) {
401 return prev + curr
402 } else {
403 return prev
404 }
405 }, 0)
406 sums[index] = costTableSummaryValue.value = changeNum(sum, 2, true)
407 } else {
408 sums[index] = costTableSummaryValue.value = 'N/A'
409 }
410 }
411 }
412 })
413 return sums
414 }
415
416 /** --------------------------- 收入法 --------------------------- */
417
418 const incomeTableField: any = ref([
419 { label: "指标名称", field: "indexName", width: 160 },
420 { label: "单位", field: "unit", width: 100 },
421 { label: "预测年限", field: "years", showChild: true, align: 'center' },
422 { label: "说明", field: "instructions", width: 380 },
423 ]);
424
425 const incomeYears = computed(() => {
426 let formInline = formRef.value.formInline;
427 if (formInline.evaluateMethod == '1') {
428 return [];
429 }
430 let evaluateBaseDate = formInline.evaluateBaseDate;
431 let useYears = formInline.useYears;
432 let infos = evaluateBaseDate.split('-');
433 let year = parseInt(infos[0]);
434 let month = parseInt(infos[1]);
435 if (month == 12) {
436 let a = [{
437 field: year + '',
438 label: year + '年'
439 }];
440 for (var i = 1; i < useYears + 1; i++) {
441 a.push({
442 field: year + i + '',
443 label: (year + i) + '年'
444 });
445 }
446 return a;
447 } else if (month == 1) {
448 let a = [{
449 field: evaluateBaseDate + '',
450 label: year + '年' + `(1)`
451 }];
452 for (var i = 1; i < useYears + 1; i++) {
453 a.push({
454 field: year + i + '',
455 label: i == useYears ? ((year + i) + '年' + `(1)`) : ((year + i) + '年')
456 });
457 }
458 return a;
459 } else {
460 let a = [{
461 field: evaluateBaseDate + '',
462 label: year + '年' + `(1~${month})`
463 }];
464 for (var i = 1; i < useYears + 1; i++) {
465 a.push({
466 field: year + i + '',
467 label: i == useYears ? ((year + i) + '年' + `(${month}~12)`) : ((year + i) + '年')
468 });
469 }
470 return a;
471 }
472 })
473
474 const incomeTableData: any = ref([{
475 orderNum: 1,
476 indexName: '收入',
477 unit: '元',
478 instructions: '数据创造或者是数据所在应用场景下的收入'
479 }, {
480 orderNum: 2,
481 indexName: '毛利率',
482 unit: '%',
483 instructions: '数据创造或者是数据所在应用场景下的毛利率'
484 }, {
485 orderNum: 3,
486 indexName: '营业利润率',
487 unit: '%',
488 instructions: '数据创造或者是数据所在应用场景下的营业利润率'
489 }, {
490 orderNum: 4,
491 indexName: '净利润率',
492 unit: '%',
493 instructions: '数据创造或者是数据所在应用场景下的净利润率'
494 }, {
495 orderNum: 5,
496 indexName: '行业利润率水平',
497 unit: '%',
498 instructions: '参考同行业上市公司/企业历史年度细分业务利润率水平'
499 }, {
500 orderNum: 6,
501 indexName: '数据资产分成率',
502 unit: '%',
503 instructions: '即关于数据对于实现收入的贡献'
504 }, {
505 orderNum: 7,
506 indexName: '衰减率',
507 unit: '%',
508 instructions: '数据有效期为5年,衰减率可理解为每年20%'
509 }, {
510 orderNum: 8,
511 indexName: '综合分成率',
512 unit: '%',
513 auto: true,
514 instructions: '自动计算'
515 }, {
516 orderNum: 9,
517 indexName: '现金流',
518 unit: '元',
519 auto: true,
520 instructions: '自动计算'
521 }, {
522 orderNum: 10,
523 indexName: '折现率',
524 unit: '%',
525 instructions: '一般行业在12%-16%之间,根据数据资产可变现的情况确认'
526 }, {
527 orderNum: 11,
528 indexName: '折现年期',
529 unit: '年',
530 auto: true,
531 instructions: '自动计算'
532 }, {
533 orderNum: 12,
534 indexName: '折现因子',
535 unit: '',
536 auto: true,
537 instructions: '自动计算'
538 }, {
539 orderNum: 13,
540 indexName: '折现现值',
541 unit: '元',
542 auto: true,
543 instructions: '自动计算,收入*利润率*分成率*折现因子'
544 }, {
545 orderNum: 14,
546 indexName: '数据资产估值',
547 unit: '元',
548 auto: true,
549 instructions: '自动计算'
550 }])
551
552 const inputChange = (val, scope, field) => {
553 let row = scope.row;
554 let strArr = val.split(".");
555 if (strArr.length > 1) {
556 let right = strArr[1];
557 if (right === "" || right.length < 2) {
558 row[field] = val = parseFloat(val || 0).toFixed(2);
559 }
560 } else {
561 row[field] = val = parseFloat(val || 0).toFixed(2);
562 }
563 }
564
565 /** 输入框输入触发事件 */
566 const inputEventChange = (val, scope, field, max: any = null) => {
567 let row = scope.row;
568 row[field] = row[field].toString().replace(/[^\d.]/g, "")
569 row[field] = row[field].toString().replace(/\.{2,}/g, ".")
570 row[field] = row[field].toString().replace(".", "$#$").replace(/\./g, "").replace("$#$", ".")
571 row[field] = row[field].toString().replace(/^(\-)*(\d+)\.(\d\d\d\d\d\d).*$/, "$1$2.$3")
572 row[field] = row[field].toString().replace(/^\D*(\d{0,12}(?:\.\d{0,2})?).*$/g, "$1")
573 if (max !== null && row[field] > max) {
574 row[field] = max;
575 }
576 }
577
578 const incomeCalculateData = computed(() => { //响应式不生效
579 let data = incomeTableData.value;
580 let resultInfo: any = {};
581 resultInfo['综合分成率'] = [];
582 resultInfo['现金流'] = [];
583 resultInfo['折现年期'] = [];
584 resultInfo['折现因子'] = [];
585 resultInfo['折现现值'] = [];
586 resultInfo['数据资产估值'] = 0;
587 let transfer = (v, need = true) => {
588 return v ? (need ? parseFloat(v) / 100 : parseFloat(v)) : 0;
589 }
590 incomeYears.value.forEach((year, i) => {
591 let C6 = transfer(data[5][year.field])
592 let C7 = transfer(data[6][year.field])
593 let sumC7: any = i == 0 ? C7 : incomeYears.value.slice(0, i + 1).reduce(function (prev, curr, idx, arr) {
594 return transfer(data[6][prev.field]) + transfer(data[6][curr.field]);
595 })
596 resultInfo['综合分成率'].push(changeNum(C6 * (1 - sumC7 + C7 / 2) * 100, 2, true)); //TODO综合分成率算法有问题
597 let C1 = transfer(data[0][year.field], false)
598 let C5 = transfer(data[4][year.field])
599 resultInfo['现金流'].push(changeNum(C1 * C5 * resultInfo['综合分成率'][i] / 100, 2, true));
600 if (i == 0) {
601 resultInfo['折现年期'].push(changeNum(10 / 12 / 2, 2, true));
602 } else {
603 resultInfo['折现年期'].push(changeNum(parseFloat(resultInfo['折现年期'][i - 1]) + 1, 2, true))
604 }
605 let C10 = transfer(data[9][year.field]);
606 resultInfo['折现因子'].push(changeNum(1 / Math.pow((1 + C10), parseFloat(resultInfo['折现年期'][i])), 2, true));
607 resultInfo['折现现值'].push(changeNum(parseFloat(resultInfo['折现因子'][i]) * parseFloat(resultInfo['现金流'][i]), 2, true));
608 })
609 resultInfo['数据资产估值'] = changeNum(resultInfo['折现现值'].reduce(function (prev, curr, idx, arr) {
610 return parseFloat(prev) + parseFloat(curr);
611 }), 2, true);
612 return resultInfo;
613 })
614
615 const submit = () => {
616 formRef.value?.ruleFormRef?.validate((valid, errorItem) => {
617 if (valid) {
618 let params = formRef.value.formInline;
619 if (params.evaluateMethod == '1') {
620 params.valuationCostRQVOList = costTableData.value;
621 params.damValuation = costTableSummaryValue.value;
622 } else {
623 params.valuationEarningsRQVOList = incomeTableData.value;
624 params.damValuation = incomeCalculateData.value['数据资产估值'];
625 params.valuationEarningsRQVOList.forEach(d => {
626 let years: any = {};
627 incomeYears.value.forEach(y => {
628 years[y.field] = d[y.field];
629 })
630 d.predictedYears = years;
631 })
632 }
633 params.evaluateBaseDate = getLastDayOfMonth(params.evaluateBaseDate);
634 fullscreenLoading.value = true;
635 if (!route.query.guid) {
636 saveValuationMode(params).then((res: any) => {
637 fullscreenLoading.value = false;
638 if (res.code == proxy.$passCode) {
639 proxy.$ElMessage.success('新建估值模型提交保存成功');
640 userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath));
641 router.push({
642 name: 'valuationModel'
643 });
644 entryStore.setIsRefresh(true);
645 } else {
646 proxy.$ElMessage({
647 type: 'error',
648 message: res.msg,
649 })
650 }
651 })
652 } else {
653 params.guid = route.query.guid;
654 updateValuationMode(params).then((res: any) => {
655 fullscreenLoading.value = false;
656 if (res.code == proxy.$passCode) {
657 proxy.$ElMessage.success('编辑估值模型提交成功');
658 userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath));
659 router.push({
660 name: 'valuationModel'
661 });
662 entryStore.setIsRefresh(true);
663 } else {
664 proxy.$ElMessage({
665 type: 'error',
666 message: res.msg,
667 })
668 }
669 })
670 }
671 } else {
672 var obj = Object.keys(errorItem);
673 formRef.value.ruleFormRef.scrollToField(obj[0])
674 }
675 })
676 }
677
678 const cancel = () => {
679 proxy.$openMessageBox("当前页面尚未保存,确定放弃修改吗?", () => {
680 userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath));
681 router.push({
682 name: 'valuationModel'
683 });
684 }, () => {
685 proxy.$ElMessage.info("已取消");
686 });
687 }
688
689 const getDamProductListData = () => {
690 getAssetCatalog({
691 pageSize: -1
692 }).then((res: any) => {
693 if (res.code == proxy.$passCode) {
694 damProductList.value = res.data || [];
695 valuateFormItems.value[0].options = damProductList.value;
696 } else {
697 proxy.$ElMessage({
698 type: 'error',
699 message: res.msg,
700 })
701 }
702 })
703 }
704
705 onBeforeMount(() => {
706 getDamProductListData();
707 if (route.query.guid) {
708 fullscreenLoading.value = true;
709 getValuationModelDetail({ guid: route.query.guid }).then((res: any) => {
710 fullscreenLoading.value = false;
711 if (res.code == proxy.$passCode) {
712 let detailData = res.data || {};
713 valuateFormItems.value.forEach(item => {
714 item.default = detailData[item.field];
715 if (item.field == 'evaluateMethod') {
716 valuateFormItems.value.at(-1).visible = item.default == '2';
717 } else if (item.field == 'evaluateBaseDate') {
718 item.default = item.default.substring(0, 7);
719 }
720 })
721 if (detailData.evaluateMethod == '2') {
722 incomeTableData.value = detailData.valuationEarningsRSVOList || [];
723 incomeTableData.value.forEach((d, index) => {
724 Object.assign(d, d.predictedYears || {});
725 if (d.indexName == '综合分成率' || d.indexName == '现金流' || d.indexName == '折现年期' || d.indexName == '折现因子' || d.indexName == '折现现值' || d.indexName == '数据资产估值') {
726 d.auto = true;
727 }
728 });
729 } else {
730 costTableData.value = detailData.valuationCostRSVOList || [];
731 costTableSummaryValue.value = detailData.damValuation || '';
732 }
733 } else {
734 proxy.$ElMessage({
735 type: 'error',
736 message: res.msg,
737 })
738 }
739 });
740 }
741 })
742
743 onMounted(async () => {
744 await nextTick();
745 await nextTick();
746 const tables: any = document.querySelectorAll(
747 "#cost-table .el-table__footer-wrapper tr>td"
748 );
749 tables[0].colSpan = 3;
750 tables[0].style.textAlign = "center";
751 tables[1].style.display = "none";
752 tables[2].style.display = "none";
753 tables[4].style.display = "none";
754 tables[5].style.display = "none";
755 })
756
757 </script>
758
759 <template>
760 <div class="container_wrap" v-loading="fullscreenLoading">
761 <div class="content_main">
762 <ContentWrap id="id-baseInfo" title="估值类型" instructions="" style="margin-top: 8px;">
763 <Form ref="formRef" :itemList="valuateFormItems" :rules="valuateFormRules" formId="main-model-edit"
764 @select-change="handleValudateFormChange" col="col3" />
765 </ContentWrap>
766 <ContentWrap id="id-grade-info" title="填写成本明细"
767 :instructions="formRef?.formInline?.evaluateMethod == '1' ? '填写时请按照所选数据产品的成本投入进行填写,跟数据产品产生的成本一致' : ''"
768 style="margin-top: 16px;">
769 <el-table id="cost-table" v-show="formRef?.formInline?.evaluateMethod == '1'" ref="costTableRef"
770 :data="costTableData" :span-method="costTableSpanMethod" :summary-method="costTableSummaryMethod" show-summary
771 border tooltip-effect="light" :tooltip-options="{ placement: 'top', popperClass: 'table_cell_tooltip' }">
772 <el-table-column v-for="(item, i) in costTableField" :label="item.label" :width="item.width"
773 :min-width="item.minWidth" :fixed="item.fixed" :align="item.align" :sortable="item.sortable ?? false"
774 :prop="item.field" :class-name="item.columClass" show-overflow-tooltip>
775 <template #default="scope">
776 <div class="input_cell" v-if="item.type == 'input'">
777 <el-input v-model.trim="scope.row[item.field]" placeholder="请输入" :maxlength="item.maxlength ?? ''"
778 @change="(val) => inputChange(val, scope, item.field)"
779 @input="(val) => inputEventChange(val, scope, item.field)" clearable></el-input>
780 </div>
781 <span v-else>
782 {{ item.getName ? item.getName(scope) : scope.row[item.field] !== 0 && !scope.row[item.field] ?
783 "--" : scope.row[item.field] }}
784 </span>
785 </template>
786 </el-table-column>
787 </el-table>
788 <el-table id="income-table" v-show="formRef?.formInline?.evaluateMethod != '1'" ref="costTableRef"
789 :data="incomeTableData" border tooltip-effect="light"
790 :tooltip-options="{ placement: 'top', popperClass: 'table_cell_tooltip' }">
791 <el-table-column type="index" label="序号" width="80" align="center" />
792 <el-table-column v-for="(item, i) in incomeTableField" :label="item.label" :width="item.width"
793 :min-width="item.minWidth" :fixed="item.fixed" :align="item.align" :sortable="item.sortable ?? false"
794 :prop="item.field" :class-name="item.columClass" show-overflow-tooltip>
795 <template #default="scope">
796 <template v-if="item.showChild == true">
797 <el-table-column v-for="(year, j) in incomeYears" :label="year.label" :width="150" align="right"
798 :prop="year.field" show-overflow-tooltip>
799 <template #default="scope">
800 <div v-if="scope.row.auto != true" class="input_cell">
801 <el-input v-model.trim="scope.row[year.field]" placeholder="请输入"
802 @change="(val) => inputChange(val, scope, year.field)"
803 @input="(val) => inputEventChange(val, scope, year.field, scope.row.unit == '%' ? 100 : null)"
804 clearable></el-input>
805 </div>
806 <span v-else>
807 {{ scope.row.indexName == '数据资产估值' ? (j > 0 ? '-' : incomeCalculateData[scope.row.indexName])
808 : (incomeCalculateData[scope.row.indexName][j]) }}
809 </span>
810 </template>
811 </el-table-column>
812 </template>
813 <span v-else>{{ scope.row[item.field] || '-' }}</span>
814 </template>
815 </el-table-column>
816 </el-table>
817 </ContentWrap>
818 </div>
819 <div class="bottom_tool_wrap">
820 <el-button @click="cancel">取消</el-button>
821 <el-button type="primary" @click="submit">提交</el-button>
822 </div>
823 </div>
824 </template>
825
826 <style lang="scss" scoped>
827 .container_wrap {
828 padding: 0px;
829 }
830
831 .content_main {
832 height: calc(100% - 44px);
833 padding: 10px 16px;
834 overflow: auto;
835
836 .table-top-btns {
837 margin-bottom: 12px;
838 }
839 }
840
841 .bottom_tool_wrap {
842 height: 44px;
843 padding: 0 16px;
844 border-top: 1px solid #d9d9d9;
845 display: flex;
846 justify-content: center;
847 align-items: center;
848 }
849
850 :deep(.el-table) {
851 td.el-table__cell {
852 padding: 2px 0;
853 height: 36px;
854
855 .el-input .el-input__inner {
856 text-align: right;
857 }
858 }
859 }
860 </style>
...\ No newline at end of file ...\ No newline at end of file
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!