imeik-bizui 2.5.3 → 2.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bizui/api/mms.js +24 -0
- package/dist/bizui/src/FieldFloorPriceBoard/README.md +103 -0
- package/dist/bizui/src/FieldFloorPriceBoard/adapters/ApplyCenterAdapter.vue +175 -0
- package/dist/bizui/src/FieldFloorPriceBoard/adapters/MeetingAdapter.vue +269 -0
- package/dist/bizui/src/FieldFloorPriceBoard/adapters/SuppliesAdapter.vue +211 -0
- package/dist/bizui/src/FieldFloorPriceBoard/core/FloorPriceBoard.vue +542 -0
- package/dist/bizui/src/FieldFloorPriceBoard/core/FloorPriceDashboard.vue +311 -0
- package/dist/bizui/src/FieldFloorPriceBoard/core/FloorPriceDetailDialog.vue +557 -0
- package/dist/bizui/src/FieldFloorPriceBoard/core/FloorPriceSectionPager.vue +106 -0
- package/dist/bizui/src/FieldFloorPriceBoard/core/mock.js +127 -0
- package/dist/bizui/src/FieldFloorPriceBoard/core/normalize.js +220 -0
- package/dist/bizui/src/FieldFloorPriceBoard/core/utils.js +92 -0
- package/dist/bizui/src/FieldFloorPriceBoard/index.vue +41 -0
- package/dist/imeik-bizui.common.js +3848 -263
- package/dist/imeik-bizui.common.js.gz +0 -0
- package/dist/imeik-bizui.css +2 -2
- package/dist/imeik-bizui.css.gz +0 -0
- package/dist/imeik-bizui.umd.js +3848 -263
- package/dist/imeik-bizui.umd.js.gz +0 -0
- package/dist/imeik-bizui.umd.min.js +8 -6
- package/dist/imeik-bizui.umd.min.js.gz +0 -0
- package/package.json +1 -1
package/dist/bizui/api/mms.js
CHANGED
|
@@ -57,3 +57,27 @@ export function searchCustomerInfo(data) {
|
|
|
57
57
|
data
|
|
58
58
|
})
|
|
59
59
|
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 客户底价概述(规则中心实时计算)
|
|
63
|
+
* POST /mms/admin/custom/getProductPrice
|
|
64
|
+
*/
|
|
65
|
+
export function getProductPrice(data) {
|
|
66
|
+
return request({
|
|
67
|
+
url: '/mms/admin/custom/getProductPrice',
|
|
68
|
+
method: 'post',
|
|
69
|
+
data
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 客户底价明细快照
|
|
75
|
+
* POST /mms/admin/custom/getProductPriceDetail
|
|
76
|
+
*/
|
|
77
|
+
export function getProductPriceDetail(data) {
|
|
78
|
+
return request({
|
|
79
|
+
url: '/mms/admin/custom/getProductPriceDetail',
|
|
80
|
+
method: 'post',
|
|
81
|
+
data
|
|
82
|
+
})
|
|
83
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# 底价看板(FieldFloorPriceBoard)
|
|
2
|
+
|
|
3
|
+
> 日期:2026-09-21
|
|
4
|
+
> 范围:物资系统 · 会议系统 · 申请中心 · **imeik-bizui**
|
|
5
|
+
> 目标:**不在每个系统、每个端重写一遍**——域适配、业务接口、UI/编排都在 bizui;宿主只传业务数据 + 权限
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 一、目录结构
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
imeik-bizui/src/bizui/
|
|
13
|
+
├── api/mms.js # getProductPrice / getProductPriceDetail(mmsRequest)
|
|
14
|
+
└── src/
|
|
15
|
+
└── FieldFloorPriceBoard/ # 唯一全局组件(index.vue)
|
|
16
|
+
├── index.vue # scene 选适配器
|
|
17
|
+
├── adapters/ # 各域字段 → payload(互不混用 props)
|
|
18
|
+
│ ├── SuppliesAdapter.vue
|
|
19
|
+
│ ├── MeetingAdapter.vue
|
|
20
|
+
│ └── ApplyCenterAdapter.vue
|
|
21
|
+
├── core/ # Board / Dashboard / Detail / normalize / mock
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`Vue.use(bizui)` 后页面直接写:
|
|
25
|
+
|
|
26
|
+
```vue
|
|
27
|
+
<!-- 物资 -->
|
|
28
|
+
<FieldFloorPriceBoard
|
|
29
|
+
scene="supplies"
|
|
30
|
+
:apply-customer-info="..."
|
|
31
|
+
:snapshot-list="..."
|
|
32
|
+
:apply-dept-code="..."
|
|
33
|
+
:cost-bear-dept-code="..."
|
|
34
|
+
:cost-type="..."
|
|
35
|
+
:apply-code="..."
|
|
36
|
+
:show-detail-link="hasPerm"
|
|
37
|
+
/>
|
|
38
|
+
|
|
39
|
+
<!-- 会议 -->
|
|
40
|
+
<FieldFloorPriceBoard scene="meeting" :customer-info="..." :inject-rows="..." ... />
|
|
41
|
+
|
|
42
|
+
<!-- 申请中心 -->
|
|
43
|
+
<FieldFloorPriceBoard scene="applyCenter" :customer-info="..." :product-lines="..." ... />
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`scene` 取值:`supplies` | `meeting` | `apply` / `applyCenter`。
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 二、分层原则(对齐 bizui 既有写法)
|
|
51
|
+
|
|
52
|
+
1. **接口写在 `api/mms.js`**,走 `utils/mmsRequest`(按 `$bizui.env` 切网关 + `userTokenFunc`)。
|
|
53
|
+
2. **对外只注册一个全局组件** `FieldFloorPriceBoard`;域差异落在 `adapters/`。
|
|
54
|
+
3. **adapter 互不 import**;共用只依赖本目录 `core` 与 `api/mms`。
|
|
55
|
+
4. **宿主页面尽量薄**:传本域字段 + 权限布尔;不写 debounce / normalize / 看板 UI / 底价 API。
|
|
56
|
+
5. **适配层组装请求**:各 adapter 产出同一份 `requestPayload` 同时作为 overview / detail;`FloorPriceDashboard` 只负责拉数 + 展示。
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
60
|
+
│ bizui │
|
|
61
|
+
│ api/mms + FieldFloorPriceBoard(core/adapters) │
|
|
62
|
+
└──────────────────────────────▲───────────────────────────────┘
|
|
63
|
+
│ scene + 本域 props
|
|
64
|
+
┌──────────┴──────────┐
|
|
65
|
+
物资 / 会议 / 申请中心 PC·H5
|
|
66
|
+
<FieldFloorPriceBoard />
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 三、标准契约
|
|
72
|
+
|
|
73
|
+
| 类型 | 内容 |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| 概述入参 | `customUniqueCode`、`businessLineNumber`、`isGroupCustomer`、`applyDeptCode`、`costBearDeptCode`、`costType`、`materialList[]`(允许 `[]`) |
|
|
76
|
+
| 明细入参 | 与概述同形(含 `applyCode` + 表单字段);接口自取所需字段 |
|
|
77
|
+
| 展示 | 看板 `normalizeOverview`;明细顶部汇总取概览;**差距 = 申请后 − 申请底价(前端算)**,变低红 / 变高绿 |
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 四、各域列表映射
|
|
82
|
+
|
|
83
|
+
| 域 | scene | adapter | 物料来源 |
|
|
84
|
+
| --- | --- | --- | --- |
|
|
85
|
+
| 物资 | `supplies` | `adapters/SuppliesAdapter.vue` | `snapshotList`(组合拆子项) |
|
|
86
|
+
| 会议 | `meeting` | `adapters/MeetingAdapter.vue` | inject / deduct / materialRows |
|
|
87
|
+
| 申请中心 | `apply` / `applyCenter` | `adapters/ApplyCenterAdapter.vue` | `productLines` |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 五、落地顺序
|
|
92
|
+
|
|
93
|
+
| 阶段 | 内容 |
|
|
94
|
+
| --- | --- |
|
|
95
|
+
| **P0** | 物资页挂 `FieldFloorPriceBoard scene="supplies"`(联调后关闭 `use-mock`) |
|
|
96
|
+
| **P1** | bizui 发版;会议/申请中心确认字段后补齐 adapter |
|
|
97
|
+
| **P2** | 物资 H5 直接复用同一全局组件 |
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 六、一句话
|
|
102
|
+
|
|
103
|
+
**bizui 收齐「域适配 + mms 底价 API + 看板」;宿主 `Vue.use(bizui)` 后挂 `<FieldFloorPriceBoard scene="..." />` 传本域数据即可。**
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
ApplyCenterAdapter · 申请中心域底价适配器
|
|
3
|
+
|
|
4
|
+
职责
|
|
5
|
+
- 把申请中心表单字段(客户、部门、费用类型、产品行 productLines)组装成
|
|
6
|
+
getProductPrice / getProductPriceDetail 请求体
|
|
7
|
+
- 交给 FloorPriceDashboard 拉数与展示(本组件不写看板 UI)
|
|
8
|
+
|
|
9
|
+
入参策略
|
|
10
|
+
- 概述、明细共用同一份 requestPayload;可带字段尽量都带上,用不用由接口决定
|
|
11
|
+
- 新增/编辑:主要靠表单字段(客户、部门、费用类型、materialList)
|
|
12
|
+
- 已落库详情:通常有 applyCode,后端可只认单号;前端仍附带表单字段,便于草稿/兼容
|
|
13
|
+
- materialList 仅来自 productLines(申请中心产品明细,无组合拆分)
|
|
14
|
+
|
|
15
|
+
与物资 / 会议差异
|
|
16
|
+
- 客户 prop 名为 customerInfo(物资为 applyCustomerInfo)
|
|
17
|
+
- 物料来源单一:productLines,字段名兼容面更宽(含 productErpCode、count 等)
|
|
18
|
+
- 缺客户唯一编码 / 业务线 / 部门 / 费用类型时 requestPayload 为 null(Dashboard 不发请求)
|
|
19
|
+
|
|
20
|
+
宿主用法
|
|
21
|
+
<FieldFloorPriceBoard
|
|
22
|
+
scene="applyCenter"
|
|
23
|
+
:customer-info="..."
|
|
24
|
+
:cost-type="..."
|
|
25
|
+
:apply-dept-code="..."
|
|
26
|
+
:cost-bear-dept-code="..."
|
|
27
|
+
:apply-code="..."
|
|
28
|
+
:product-lines="..."
|
|
29
|
+
:show-detail-link="hasPerm"
|
|
30
|
+
/>
|
|
31
|
+
|
|
32
|
+
scene 也可传 apply(index.vue 会映射到本适配器)
|
|
33
|
+
|
|
34
|
+
接口:api/mms.getProductPrice / getProductPriceDetail(mmsRequest,随 $bizui.env 切环境)
|
|
35
|
+
-->
|
|
36
|
+
<template>
|
|
37
|
+
<FloorPriceDashboard
|
|
38
|
+
:overview-payload="requestPayload"
|
|
39
|
+
:detail-payload="requestPayload"
|
|
40
|
+
:customer-name="customerName"
|
|
41
|
+
:fetch-overview="useMock ? null : getProductPrice"
|
|
42
|
+
:fetch-detail="useMock ? null : getProductPriceDetail"
|
|
43
|
+
:use-demo="useMock"
|
|
44
|
+
:show-title="false"
|
|
45
|
+
:show-detail-link="showDetailLink"
|
|
46
|
+
/>
|
|
47
|
+
</template>
|
|
48
|
+
|
|
49
|
+
<script>
|
|
50
|
+
import FloorPriceDashboard from '../core/FloorPriceDashboard.vue'
|
|
51
|
+
import { getProductPrice, getProductPriceDetail } from '../../../api/mms'
|
|
52
|
+
|
|
53
|
+
export default {
|
|
54
|
+
name: 'ApplyCenterAdapter',
|
|
55
|
+
components: { FloorPriceDashboard },
|
|
56
|
+
props: {
|
|
57
|
+
/**
|
|
58
|
+
* 申请客户信息(选客组件 / 表单 customerInfo)
|
|
59
|
+
*
|
|
60
|
+
* 读取字段(大小写兼容):
|
|
61
|
+
* - customUniqueCode / CustomUniqueCode → 客户唯一编码(缺则不发请求)
|
|
62
|
+
* - businessLineNumber / BusinessLineNumber → 业务线(缺则不发请求)
|
|
63
|
+
* - isGroupCustomer → 是否集团客户(原样透传)
|
|
64
|
+
* - CustomerName / customerName / customName → 看板展示用客户名
|
|
65
|
+
*/
|
|
66
|
+
customerInfo: { type: Object, default: () => ({}) },
|
|
67
|
+
/**
|
|
68
|
+
* 费用类型(表单 costType);为空则不发请求
|
|
69
|
+
*/
|
|
70
|
+
costType: { type: [String, Number], default: undefined },
|
|
71
|
+
/**
|
|
72
|
+
* 申请部门业务编码;为空则不发请求
|
|
73
|
+
*/
|
|
74
|
+
applyDeptCode: { type: [String, Number], default: undefined },
|
|
75
|
+
/**
|
|
76
|
+
* 费用承担部门业务编码;为空则不发请求
|
|
77
|
+
*/
|
|
78
|
+
costBearDeptCode: { type: [String, Number], default: undefined },
|
|
79
|
+
/**
|
|
80
|
+
* 已落库申请单编码(objectCode / applyCode)
|
|
81
|
+
* - 草稿 / 新增未保存:传空,payload 里为 undefined
|
|
82
|
+
* - 编辑 / 查看:有值则带上;详情接口可主要依赖此字段,其它字段仍一并传递
|
|
83
|
+
*/
|
|
84
|
+
applyCode: { type: String, default: '' },
|
|
85
|
+
/**
|
|
86
|
+
* 申请中心产品明细行 → 映射为接口 materialList
|
|
87
|
+
*
|
|
88
|
+
* 编码候选:erpCode / productCode / 物料编码 / productErpCode
|
|
89
|
+
* 数量候选:applyCount / 数量 / qty / count / quantity
|
|
90
|
+
* 过滤:无 erpCode 或 applyCount<=0 的行丢弃;无产品时 materialList 为 []
|
|
91
|
+
*/
|
|
92
|
+
productLines: { type: Array, default: () => [] },
|
|
93
|
+
/**
|
|
94
|
+
* 是否展示「查看底价明细」入口
|
|
95
|
+
* 权限由宿主页传入,本组件不读权限中心
|
|
96
|
+
*/
|
|
97
|
+
showDetailLink: { type: Boolean, default: true },
|
|
98
|
+
/**
|
|
99
|
+
* 本地演示:true 时不调真实接口,Dashboard 走 mock 数据
|
|
100
|
+
* 联调 / 上线请传 false
|
|
101
|
+
*/
|
|
102
|
+
useMock: { type: Boolean, default: false }
|
|
103
|
+
},
|
|
104
|
+
computed: {
|
|
105
|
+
/** 看板标题旁展示的客户名称 */
|
|
106
|
+
customerName() {
|
|
107
|
+
const c = this.customerInfo || {}
|
|
108
|
+
return c.CustomerName || c.customerName || c.customName || ''
|
|
109
|
+
},
|
|
110
|
+
/**
|
|
111
|
+
* productLines → 接口 materialList[{ erpCode, applyCount }]
|
|
112
|
+
* 仅普通行映射,不做物资组合(isGroup)拆分
|
|
113
|
+
*/
|
|
114
|
+
materialList() {
|
|
115
|
+
const rows = this.productLines
|
|
116
|
+
if (!Array.isArray(rows) || !rows.length) return []
|
|
117
|
+
const erpCodeKeys = ['erpCode', 'productCode', '物料编码', 'productErpCode']
|
|
118
|
+
const countKeys = ['applyCount', '数量', 'qty', 'count', 'quantity']
|
|
119
|
+
const pick = (row, keys) => {
|
|
120
|
+
for (let i = 0; i < keys.length; i++) {
|
|
121
|
+
const k = keys[i]
|
|
122
|
+
if (row[k] != null && row[k] !== '') return row[k]
|
|
123
|
+
}
|
|
124
|
+
return ''
|
|
125
|
+
}
|
|
126
|
+
return rows
|
|
127
|
+
.filter(Boolean)
|
|
128
|
+
.map((row) => ({
|
|
129
|
+
erpCode: String(pick(row, erpCodeKeys) || ''),
|
|
130
|
+
applyCount: Number(pick(row, countKeys)) || 0
|
|
131
|
+
}))
|
|
132
|
+
.filter((r) => r.erpCode && r.applyCount > 0)
|
|
133
|
+
},
|
|
134
|
+
/**
|
|
135
|
+
* 概述 / 明细共用请求体
|
|
136
|
+
*
|
|
137
|
+
* 必填缺一则返回 null(Dashboard 不发请求):
|
|
138
|
+
* customUniqueCode、businessLineNumber、applyDeptCode、costBearDeptCode、costType
|
|
139
|
+
*
|
|
140
|
+
* 字段说明:
|
|
141
|
+
* - applyCode 可选;有则带上
|
|
142
|
+
* - customUniqueCode 客户唯一编码
|
|
143
|
+
* - businessLineNumber 业务线
|
|
144
|
+
* - isGroupCustomer 是否集团客户(透传)
|
|
145
|
+
* - applyDeptCode 申请部门
|
|
146
|
+
* - costBearDeptCode 费用承担部门
|
|
147
|
+
* - costType 费用类型
|
|
148
|
+
* - materialList 产品行;允许 []
|
|
149
|
+
*/
|
|
150
|
+
requestPayload() {
|
|
151
|
+
const c = this.customerInfo || {}
|
|
152
|
+
const customUniqueCode = c.customUniqueCode || c.CustomUniqueCode || ''
|
|
153
|
+
const businessLineNumber = c.businessLineNumber || c.BusinessLineNumber || ''
|
|
154
|
+
const { applyDeptCode, costBearDeptCode, costType, materialList } = this
|
|
155
|
+
if (!customUniqueCode || !businessLineNumber) return null
|
|
156
|
+
if (!applyDeptCode || !costBearDeptCode) return null
|
|
157
|
+
if (costType == null || costType === '') return null
|
|
158
|
+
return {
|
|
159
|
+
applyCode: this.applyCode || undefined,
|
|
160
|
+
customUniqueCode: String(customUniqueCode),
|
|
161
|
+
businessLineNumber: String(businessLineNumber),
|
|
162
|
+
isGroupCustomer: c.isGroupCustomer,
|
|
163
|
+
applyDeptCode: String(applyDeptCode),
|
|
164
|
+
costBearDeptCode: String(costBearDeptCode),
|
|
165
|
+
costType: String(costType),
|
|
166
|
+
materialList: Array.isArray(materialList) ? materialList : []
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
methods: {
|
|
171
|
+
getProductPrice,
|
|
172
|
+
getProductPriceDetail
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
</script>
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
MeetingAdapter · 会议域底价适配器
|
|
3
|
+
|
|
4
|
+
职责
|
|
5
|
+
- 把会议申请页字段(客户、部门、费用类型、注射行 / 扣减行 / 物资行)组装成
|
|
6
|
+
getProductPrice / getProductPriceDetail 请求体
|
|
7
|
+
- 交给 FloorPriceDashboard 拉数与展示(本组件不写看板 UI)
|
|
8
|
+
|
|
9
|
+
入参策略
|
|
10
|
+
- 概述、明细共用同一份 requestPayload;可带字段尽量都带上,用不用由接口决定
|
|
11
|
+
- 新增/编辑:主要靠表单字段(客户、部门、费用类型、materialList)
|
|
12
|
+
- 已落库详情:通常有 applyCode,后端可只认单号;前端仍附带表单字段,便于草稿/兼容
|
|
13
|
+
- materialList 由 injectRows + materialRows(+ 可选 deductRows)合并去重累加
|
|
14
|
+
|
|
15
|
+
与物资域差异
|
|
16
|
+
- 客户 prop 名为 customerInfo(物资为 applyCustomerInfo)
|
|
17
|
+
- 物料来自多块子表,而非单一 snapshotList
|
|
18
|
+
- 缺客户唯一编码 / 业务线 / 部门 / 费用类型时 requestPayload 为 null(Dashboard 不发请求)
|
|
19
|
+
|
|
20
|
+
宿主用法
|
|
21
|
+
<FieldFloorPriceBoard
|
|
22
|
+
scene="meeting"
|
|
23
|
+
:customer-info="..."
|
|
24
|
+
:cost-type="..."
|
|
25
|
+
:apply-dept-code="..."
|
|
26
|
+
:cost-bear-dept-code="..."
|
|
27
|
+
:apply-code="..."
|
|
28
|
+
:inject-rows="..."
|
|
29
|
+
:deduct-rows="..."
|
|
30
|
+
:include-deduct="false"
|
|
31
|
+
:material-rows="..."
|
|
32
|
+
:material-as-snapshot="false"
|
|
33
|
+
:show-detail-link="hasPerm"
|
|
34
|
+
/>
|
|
35
|
+
|
|
36
|
+
接口:api/mms.getProductPrice / getProductPriceDetail(mmsRequest,随 $bizui.env 切环境)
|
|
37
|
+
-->
|
|
38
|
+
<template>
|
|
39
|
+
<FloorPriceDashboard
|
|
40
|
+
:overview-payload="requestPayload"
|
|
41
|
+
:detail-payload="requestPayload"
|
|
42
|
+
:customer-name="customerName"
|
|
43
|
+
:fetch-overview="useMock ? null : getProductPrice"
|
|
44
|
+
:fetch-detail="useMock ? null : getProductPriceDetail"
|
|
45
|
+
:use-demo="useMock"
|
|
46
|
+
:show-title="false"
|
|
47
|
+
:show-detail-link="showDetailLink"
|
|
48
|
+
/>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<script>
|
|
52
|
+
import FloorPriceDashboard from '../core/FloorPriceDashboard.vue'
|
|
53
|
+
import { getProductPrice, getProductPriceDetail } from '../../../api/mms'
|
|
54
|
+
|
|
55
|
+
export default {
|
|
56
|
+
name: 'MeetingAdapter',
|
|
57
|
+
components: { FloorPriceDashboard },
|
|
58
|
+
props: {
|
|
59
|
+
/**
|
|
60
|
+
* 会议申请客户信息(选客组件 / 表单 customerInfo)
|
|
61
|
+
*
|
|
62
|
+
* 读取字段(大小写兼容):
|
|
63
|
+
* - customUniqueCode / CustomUniqueCode → 客户唯一编码(缺则不发请求)
|
|
64
|
+
* - businessLineNumber / BusinessLineNumber → 业务线(缺则不发请求)
|
|
65
|
+
* - isGroupCustomer → 是否集团客户(原样透传)
|
|
66
|
+
* - CustomerName / customerName / customName → 看板展示用客户名
|
|
67
|
+
*/
|
|
68
|
+
customerInfo: { type: Object, default: () => ({}) },
|
|
69
|
+
/**
|
|
70
|
+
* 费用类型(表单 costType);为空则不发请求
|
|
71
|
+
*/
|
|
72
|
+
costType: { type: [String, Number], default: undefined },
|
|
73
|
+
/**
|
|
74
|
+
* 申请部门业务编码;为空则不发请求
|
|
75
|
+
*/
|
|
76
|
+
applyDeptCode: { type: [String, Number], default: undefined },
|
|
77
|
+
/**
|
|
78
|
+
* 费用承担部门业务编码;为空则不发请求
|
|
79
|
+
*/
|
|
80
|
+
costBearDeptCode: { type: [String, Number], default: undefined },
|
|
81
|
+
/**
|
|
82
|
+
* 已落库申请单编码(objectCode / applyCode)
|
|
83
|
+
* - 草稿 / 新增未保存:传空,payload 里为 undefined
|
|
84
|
+
* - 编辑 / 查看:有值则带上;详情接口可主要依赖此字段,其它字段仍一并传递
|
|
85
|
+
*/
|
|
86
|
+
applyCode: { type: String, default: '' },
|
|
87
|
+
/**
|
|
88
|
+
* 注射使用等产品行(会议主产品预算)
|
|
89
|
+
* 兼容字段:erpCode / productCode / 物料编码;quantity / applyCount / 数量 / qty
|
|
90
|
+
*/
|
|
91
|
+
injectRows: { type: Array, default: () => [] },
|
|
92
|
+
/**
|
|
93
|
+
* 扣减行(与 inject 同形);仅当 includeDeduct=true 时并入 materialList
|
|
94
|
+
*/
|
|
95
|
+
deductRows: { type: Array, default: () => [] },
|
|
96
|
+
/**
|
|
97
|
+
* 是否把 deductRows 计入 materialList
|
|
98
|
+
* 默认 false:扣减仅展示/记账,不参与底价试算
|
|
99
|
+
*/
|
|
100
|
+
includeDeduct: { type: Boolean, default: false },
|
|
101
|
+
/**
|
|
102
|
+
* 会议物资申请子表
|
|
103
|
+
* - materialAsSnapshot=false:按普通行映射(erpCode / applyCount)
|
|
104
|
+
* - materialAsSnapshot=true:按物资快照规则拆组合(见 mapSnapshotRows)
|
|
105
|
+
*/
|
|
106
|
+
materialRows: { type: Array, default: () => [] },
|
|
107
|
+
/**
|
|
108
|
+
* materialRows 是否按物资 snapshotList 规则解析(组合 isGroup=1 拆子项)
|
|
109
|
+
*/
|
|
110
|
+
materialAsSnapshot: { type: Boolean, default: false },
|
|
111
|
+
/**
|
|
112
|
+
* 是否展示「查看底价明细」入口
|
|
113
|
+
* 权限由宿主页传入,本组件不读权限中心
|
|
114
|
+
*/
|
|
115
|
+
showDetailLink: { type: Boolean, default: true },
|
|
116
|
+
/**
|
|
117
|
+
* 本地演示:true 时不调真实接口,Dashboard 走 mock 数据
|
|
118
|
+
* 联调 / 上线请传 false
|
|
119
|
+
*/
|
|
120
|
+
useMock: { type: Boolean, default: false }
|
|
121
|
+
},
|
|
122
|
+
computed: {
|
|
123
|
+
/** 看板标题旁展示的客户名称 */
|
|
124
|
+
customerName() {
|
|
125
|
+
const c = this.customerInfo || {}
|
|
126
|
+
return c.CustomerName || c.customerName || c.customName || ''
|
|
127
|
+
},
|
|
128
|
+
/**
|
|
129
|
+
* 多块预算行 → 接口 materialList[{ erpCode, applyCount }]
|
|
130
|
+
* 合并顺序:injectRows → materialRows →(可选)deductRows;同 erpCode 数量累加
|
|
131
|
+
*/
|
|
132
|
+
materialList() {
|
|
133
|
+
const parts = [
|
|
134
|
+
this.mapInjectRows(this.injectRows),
|
|
135
|
+
this.mapMaterialRows(this.materialRows, this.materialAsSnapshot)
|
|
136
|
+
]
|
|
137
|
+
if (this.includeDeduct) {
|
|
138
|
+
parts.push(this.mapInjectRows(this.deductRows))
|
|
139
|
+
}
|
|
140
|
+
return this.mergeMaterialLists(parts)
|
|
141
|
+
},
|
|
142
|
+
/**
|
|
143
|
+
* 概述 / 明细共用请求体
|
|
144
|
+
*
|
|
145
|
+
* 必填缺一则返回 null(Dashboard 不发请求):
|
|
146
|
+
* customUniqueCode、businessLineNumber、applyDeptCode、costBearDeptCode、costType
|
|
147
|
+
*
|
|
148
|
+
* 字段说明:
|
|
149
|
+
* - applyCode 可选;有则带上
|
|
150
|
+
* - customUniqueCode 客户唯一编码
|
|
151
|
+
* - businessLineNumber 业务线
|
|
152
|
+
* - isGroupCustomer 是否集团客户(透传)
|
|
153
|
+
* - applyDeptCode 申请部门
|
|
154
|
+
* - costBearDeptCode 费用承担部门
|
|
155
|
+
* - costType 费用类型
|
|
156
|
+
* - materialList 合并后的物料行;允许 []
|
|
157
|
+
*/
|
|
158
|
+
requestPayload() {
|
|
159
|
+
const c = this.customerInfo || {}
|
|
160
|
+
const customUniqueCode = c.customUniqueCode || c.CustomUniqueCode || ''
|
|
161
|
+
const businessLineNumber = c.businessLineNumber || c.BusinessLineNumber || ''
|
|
162
|
+
const { applyDeptCode, costBearDeptCode, costType, materialList } = this
|
|
163
|
+
if (!customUniqueCode || !businessLineNumber) return null
|
|
164
|
+
if (!applyDeptCode || !costBearDeptCode) return null
|
|
165
|
+
if (costType == null || costType === '') return null
|
|
166
|
+
return {
|
|
167
|
+
applyCode: this.applyCode || undefined,
|
|
168
|
+
customUniqueCode: String(customUniqueCode),
|
|
169
|
+
businessLineNumber: String(businessLineNumber),
|
|
170
|
+
isGroupCustomer: c.isGroupCustomer,
|
|
171
|
+
applyDeptCode: String(applyDeptCode),
|
|
172
|
+
costBearDeptCode: String(costBearDeptCode),
|
|
173
|
+
costType: String(costType),
|
|
174
|
+
materialList: Array.isArray(materialList) ? materialList : []
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
methods: {
|
|
179
|
+
getProductPrice,
|
|
180
|
+
getProductPriceDetail,
|
|
181
|
+
/** 按候选键顺序取第一个非空字段(兼容多宿主字段名) */
|
|
182
|
+
pickField(row, keys) {
|
|
183
|
+
for (let i = 0; i < keys.length; i++) {
|
|
184
|
+
const k = keys[i]
|
|
185
|
+
if (row[k] != null && row[k] !== '') return row[k]
|
|
186
|
+
}
|
|
187
|
+
return ''
|
|
188
|
+
},
|
|
189
|
+
/**
|
|
190
|
+
* 通用行映射 → { erpCode, applyCount }
|
|
191
|
+
* 过滤:无 erpCode 或 applyCount<=0
|
|
192
|
+
*/
|
|
193
|
+
mapRows(rows, erpCodeKeys, countKeys) {
|
|
194
|
+
if (!Array.isArray(rows) || !rows.length) return []
|
|
195
|
+
return rows
|
|
196
|
+
.filter(Boolean)
|
|
197
|
+
.map((row) => ({
|
|
198
|
+
erpCode: String(this.pickField(row, erpCodeKeys) || ''),
|
|
199
|
+
applyCount: Number(this.pickField(row, countKeys)) || 0
|
|
200
|
+
}))
|
|
201
|
+
.filter((r) => r.erpCode && r.applyCount > 0)
|
|
202
|
+
},
|
|
203
|
+
/** 注射使用 / 扣减等普通产品行 */
|
|
204
|
+
mapInjectRows(rows) {
|
|
205
|
+
return this.mapRows(
|
|
206
|
+
rows,
|
|
207
|
+
['erpCode', 'productCode', '物料编码'],
|
|
208
|
+
['quantity', 'applyCount', '数量', 'qty']
|
|
209
|
+
)
|
|
210
|
+
},
|
|
211
|
+
/**
|
|
212
|
+
* 会议物资子表
|
|
213
|
+
* - asSnapshot=false:普通行(erpCode / applyCount)
|
|
214
|
+
* - asSnapshot=true:走物资组合拆分规则
|
|
215
|
+
*/
|
|
216
|
+
mapMaterialRows(rows, asSnapshot) {
|
|
217
|
+
if (asSnapshot) return this.mapSnapshotRows(rows)
|
|
218
|
+
return this.mapRows(rows, ['erpCode', 'productCode'], ['applyCount', 'quantity', '数量'])
|
|
219
|
+
},
|
|
220
|
+
/**
|
|
221
|
+
* 物资快照规则(与 SuppliesAdapter.materialList 一致)
|
|
222
|
+
* - 普通行:直接取 erpCode / applyCount
|
|
223
|
+
* - 组合行(isGroup=1):按 groupDetails 拆子项,数量 = 父 applyCount × 子 groupCount
|
|
224
|
+
*/
|
|
225
|
+
mapSnapshotRows(list) {
|
|
226
|
+
if (!Array.isArray(list) || !list.length) return []
|
|
227
|
+
const rows = []
|
|
228
|
+
list.forEach((item) => {
|
|
229
|
+
if (!item) return
|
|
230
|
+
if (String(item.isGroup) === '1' && Array.isArray(item.groupDetails) && item.groupDetails.length) {
|
|
231
|
+
const parentCount = Number(item.applyCount) || 0
|
|
232
|
+
item.groupDetails.forEach((g) => {
|
|
233
|
+
const gc = Number(g.groupCount) || 0
|
|
234
|
+
rows.push({
|
|
235
|
+
applyCount: parentCount * gc,
|
|
236
|
+
erpCode: (g && g.erpCode) || ''
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
} else {
|
|
240
|
+
rows.push({
|
|
241
|
+
applyCount: Number(item.applyCount) || 0,
|
|
242
|
+
erpCode: item.erpCode || ''
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
})
|
|
246
|
+
return rows.filter((r) => r.erpCode && r.applyCount > 0)
|
|
247
|
+
},
|
|
248
|
+
/** 多块列表按 erpCode 合并,applyCount 累加 */
|
|
249
|
+
mergeMaterialLists(lists) {
|
|
250
|
+
const map = Object.create(null)
|
|
251
|
+
lists
|
|
252
|
+
.filter(Array.isArray)
|
|
253
|
+
.forEach((list) => {
|
|
254
|
+
list.forEach((row) => {
|
|
255
|
+
if (!row || !row.erpCode) return
|
|
256
|
+
const key = String(row.erpCode)
|
|
257
|
+
const count = Number(row.applyCount) || 0
|
|
258
|
+
if (count <= 0) return
|
|
259
|
+
map[key] = (map[key] || 0) + count
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
return Object.keys(map).map((erpCode) => ({
|
|
263
|
+
erpCode,
|
|
264
|
+
applyCount: map[erpCode]
|
|
265
|
+
}))
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
</script>
|