bxs-tools-ui 3.3.2 → 3.4.0

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.
Files changed (63) hide show
  1. package/es/add-and-take/index.js +1 -7969
  2. package/es/adjust-baoe/index.js +196 -8195
  3. package/es/index.css +1 -1
  4. package/es/index.js +8788 -43744
  5. package/es/insure-calculate/index.css +1 -1
  6. package/es/insure-calculate/index.js +1046 -36665
  7. package/es/person-info/index.css +1 -1
  8. package/es/person-info/index.js +83 -8042
  9. package/es/product-info/index.css +1 -1
  10. package/es/product-info/index.js +3005 -10614
  11. package/es/share-sheet/index.js +69 -8040
  12. package/es/utils/index.js +1250 -9334
  13. package/lib/add-and-take/index.js +4 -7973
  14. package/lib/adjust-baoe/index.js +195 -8195
  15. package/lib/index.css +1 -1
  16. package/lib/index.js +8717 -43678
  17. package/lib/insure-calculate/index.css +1 -1
  18. package/lib/insure-calculate/index.js +1034 -36662
  19. package/lib/person-info/index.css +1 -1
  20. package/lib/person-info/index.js +86 -8046
  21. package/lib/product-info/index.css +1 -1
  22. package/lib/product-info/index.js +2807 -10417
  23. package/lib/share-sheet/index.js +72 -8044
  24. package/lib/utils/index.js +1252 -9337
  25. package/package.json +5 -3
  26. package/packages/add-and-take/demo/index.vue +3 -6
  27. package/packages/adjust-baoe/index.vue +21 -34
  28. package/packages/adjust-baoe/readme.md +0 -1
  29. package/packages/bxs-utils/planbook/common.ts +5 -0
  30. package/packages/bxs-utils/planbook/insurance.ts +19 -0
  31. package/packages/bxs-utils/planbook/person.ts +3 -2
  32. package/packages/bxs-utils/planbook/product.ts +57 -164
  33. package/packages/bxs-utils/readme.md +3 -1
  34. package/packages/index.ts +1 -1
  35. package/packages/insure-calculate/demo/index.vue +1 -1
  36. package/packages/insure-calculate/index.vue +18 -17
  37. package/packages/person-info/demo/index.vue +2 -4
  38. package/packages/planbook-input/components/product-table.vue +3 -14
  39. package/packages/planbook-input/helper.js +16 -53
  40. package/packages/planbook-input/index.vue +20 -28
  41. package/packages/planbook-input/readme.md +3 -3
  42. package/packages/product-info/components/product-insurance-form.md +46 -0
  43. package/packages/product-info/components/product-insurance-form.vue +360 -0
  44. package/packages/product-info/demo/index.vue +4 -5
  45. package/packages/product-info/handler.js +307 -0
  46. package/packages/product-info/handler.md +26 -0
  47. package/packages/product-info/index.less +33 -0
  48. package/packages/product-info/index.vue +306 -534
  49. package/packages/product-info/readme.md +7 -2
  50. package/packages/share-sheet/constant.js +1 -2
  51. package/src/api/adjust-baoe.js +2 -2
  52. package/src/api/planbook.js +4 -20
  53. package/src/api/trade.js +31 -0
  54. package/src/components/common/common-group-title.less +54 -0
  55. package/src/components/common/common-group-title.md +34 -0
  56. package/src/components/common/common-group-title.vue +72 -0
  57. package/src/components/common/common-title.less +4 -0
  58. package/src/components/common/common-title.vue +8 -2
  59. package/src/components/profession-list/README.md +114 -0
  60. package/src/components/profession-list/constant.js +4 -0
  61. package/src/components/profession-list/handler.js +155 -0
  62. package/src/components/profession-list/index.less +182 -0
  63. package/src/components/profession-list/index.vue +443 -0
@@ -0,0 +1,360 @@
1
+ <template>
2
+ <van-form ref="insuranceForm" :class="formClassList" :show-error="false" @submit.native.prevent="closeKeyBoard()">
3
+ <template v-for="ele in ins.eleOrderList">
4
+ <van-field v-if="['cmCommonTitle'].includes(ele.componentName)" :key="ele.key" :name="ele.key" :rules="getElementRules(ele)">
5
+ <template #input>
6
+ <CommonTitle
7
+ v-model="ele.value"
8
+ :key="`${productKey}_${ins.key}_${ele.key}`"
9
+ :name="ele.key"
10
+ :code="ins.code"
11
+ :huomian-tag="getInsuranceTitleHmTag(ins)"
12
+ :status-tag="getInsuranceSaleStatusTag(ins)"
13
+ :huomian-tips="showHuomianTips && getHuomianTips(ins, ele)"
14
+ :label="ele.labelName"
15
+ :style="{
16
+ fontSize: '17px',
17
+ fontWeight: 'bold'
18
+ }"
19
+ :disabled="getElementDisabled(ele)"
20
+ :always-show-checkbox="isGroupSub"
21
+ :data-stat-id="`product_insurance_${ele.key}`"
22
+ @change="onTitleChange(ele)"
23
+ @openCrmPerson="$emit('openCrmPerson')"
24
+ />
25
+ </template>
26
+ </van-field>
27
+
28
+ <template v-if="isInsuranceChecked && !ele.isHide && ele.componentName !== 'cmCommonTitle'">
29
+ <van-field
30
+ v-if="['cmCommonInput'].includes(ele.componentName)"
31
+ v-model.number="ele.value"
32
+ :ref="ins.key + ele.key + 'item'"
33
+ :key="ins.key + ele.key"
34
+ :id="`id_${ins.key}_${ele.key}`"
35
+ :type="ele.inputType || 'tel'"
36
+ :class="{
37
+ 'van-field-unit': activeInputKey === ins.key + ele.key && numberUnitStr
38
+ }"
39
+ :prop="ele.key"
40
+ :label="ele.labelName"
41
+ :placeholder="ele.placeholder"
42
+ :disabled="getElementDisabled(ele)"
43
+ :readonly="getElementDisabled(ele)"
44
+ :maxlength="ele.maxlength || 9"
45
+ :rules="getElementRules(ele)"
46
+ autocomplete="off"
47
+ input-align="right"
48
+ error-message-align="right"
49
+ clearable
50
+ :inputmode="ele.inputType || 'text'"
51
+ @focus="
52
+ activeInputKey = ins.key + ele.key
53
+ numberUnitStr = getAmountUnit(ele)
54
+ "
55
+ @blur="activeInputKey = ''"
56
+ @input="numberUnitStr = getAmountUnit(ele)"
57
+ @change="emitChange(ele)"
58
+ @clear="emitChange(ele)"
59
+ >
60
+ <template v-if="!ele.isDisabled" #right-icon>
61
+ <i class="icon iconfont icon-edit" style="font-size: 20px;" @click="onClickIcon(`id_${ins.key}_${ele.key}`)" />
62
+ </template>
63
+ </van-field>
64
+ <div v-if="activeInputKey === ins.key + ele.key && numberUnitStr" :key="ins.key + ele.key + 'unit'" class="bxt-ins-unit-tips">
65
+ <em />
66
+ <span>
67
+ {{ numberUnitStr }}
68
+ </span>
69
+ </div>
70
+ <div v-if="getRecommendValues(ele)" :key="ins.key + ele.key + 'recommend'" class="bxt-ins-recommend-values">
71
+ <span v-for="v in getRecommendValues(ele)" :key="v" @click="onClickRecommendValue(v, ele)">
72
+ {{ v }}
73
+ </span>
74
+ </div>
75
+
76
+ <CommonRadio
77
+ v-if="['cmCommonRadioBtn'].includes(ele.componentName)"
78
+ v-model="ele.value"
79
+ class="bxt-ins-form-radio-btn"
80
+ :key="ins.key + ele.key"
81
+ :name="ele.key"
82
+ :label="ele.labelName"
83
+ :disabled="getElementDisabled(ele)"
84
+ :readonly="getElementDisabled(ele)"
85
+ :rules="getElementRules(ele)"
86
+ :options="ele.opts.filter(o => !o.isHide && !o.isDisabled && !o.isInvalid)"
87
+ input-align="right"
88
+ border
89
+ :data-stat-id="`product_insurance_${ele.key}`"
90
+ @input="emitChange(ele)"
91
+ />
92
+
93
+ <van-field
94
+ v-if="['cmCommonRadio'].includes(ele.componentName)"
95
+ class="bxt-ins-form-radio"
96
+ :key="ins.key + ele.key"
97
+ :name="ele.key"
98
+ :label="ele.labelName"
99
+ :rules="getElementRules(ele)"
100
+ >
101
+ <template #input>
102
+ <van-radio-group v-model="ele.value" :disabled="getElementDisabled(ele)" @change="emitChange(ele)">
103
+ <van-radio
104
+ v-for="opt in ele.opts"
105
+ :id="`${productKey}_${ins.key}_${ele.key}_${opt.val}`"
106
+ :key="opt.val"
107
+ :name="typeof ele.value === 'number' && !isNaN(Number(opt.val)) ? opt.val * 1 : opt.val"
108
+ :disabled="opt.val != ele.value && opt.isDisabled"
109
+ :label-disabled="opt.val != ele.value && opt.isDisabled"
110
+ >
111
+ <template #icon="props">
112
+ <van-icon v-if="props.checked" class-prefix="iconfont icon" name="multiple_choice" size="20px" />
113
+ <van-icon v-else class-prefix="iconfont icon" name="choose_none_line" size="20px" />
114
+ </template>
115
+ <div class="bxt-ins-form-radio-content">
116
+ <span v-if="opt.code" class="bxt-ins-form-radio--code">{{ opt.code }}</span>
117
+ <span
118
+ class="bxt-ins-form-radio--tx"
119
+ :class="[genInsuranceHmTag(opt) || getInsuranceSaleStatusTagByKey(opt.val) ? 'bxt-ins-form-radio--width' : '']"
120
+ >
121
+ {{ opt.desc }}
122
+ </span>
123
+ <span v-if="getInsuranceSaleStatusTagByKey(opt.val)" class="bxt-ins-form-radio--sale-tag">
124
+ {{ getInsuranceSaleStatusTagByKey(opt.val) }}
125
+ </span>
126
+ <span v-if="genInsuranceHmTag(opt)" class="bxt-ins-form-radio--tag">
127
+ {{ genInsuranceHmTag(opt) }}
128
+ </span>
129
+ </div>
130
+ </van-radio>
131
+ </van-radio-group>
132
+ </template>
133
+ </van-field>
134
+
135
+ <CommonSelect
136
+ v-if="['cmCommonSelect'].includes(ele.componentName)"
137
+ v-model="ele.value"
138
+ :key="ele.componentName + ins.key + ele.key"
139
+ :name="ele.key"
140
+ :label="ele.labelName"
141
+ :placeholder="ele.placeholder"
142
+ :disabled="getElementDisabled(ele)"
143
+ :readonly="getElementDisabled(ele)"
144
+ :rules="getElementRules(ele)"
145
+ :options="ele.opts.filter(o => !o.isHide && !o.isDisabled && !o.isInvalid)"
146
+ :right-icon="getElementDisabled(ele) ? '' : 'arrow'"
147
+ input-align="right"
148
+ border
149
+ :data-stat-id="`product_insurance_${ele.key}`"
150
+ @change="emitChange(ele)"
151
+ />
152
+
153
+ <div :key="`${ins.key}${ele.key}_note`" v-if="ele.note" class="bxt-form-item-label-note">
154
+ {{ ele.note }}
155
+ </div>
156
+ </template>
157
+ </template>
158
+ </van-form>
159
+ </template>
160
+ <script lang="ts">
161
+ import Vue from 'vue'
162
+ import { Component, Prop } from 'vue-property-decorator'
163
+ import { get as _get } from 'lodash'
164
+ import { convertEleOrderListToObj } from 'packages/bxs-utils/index'
165
+ import CommonTitle from '../../../src/components/common/common-title.vue'
166
+ import CommonRadio from '../../../src/components/common/common-radio.vue'
167
+ import CommonSelect from '../../../src/components/common/common-select.vue'
168
+
169
+ @Component({
170
+ name: 'ProductInsuranceForm',
171
+ components: {
172
+ CommonTitle,
173
+ CommonRadio,
174
+ CommonSelect
175
+ }
176
+ })
177
+ export default class ProductInsuranceForm extends Vue {
178
+ activeInputKey = ''
179
+ numberUnitStr = ''
180
+
181
+ @Prop({ required: true })
182
+ ins!: any
183
+
184
+ /** 险种表单场景:normal 普通险 | groupSub 组合子险 */
185
+ @Prop({ default: 'normal' })
186
+ insMode!: string
187
+
188
+ @Prop({ default: null })
189
+ productKey!: number | string
190
+
191
+ @Prop({ default: () => [] })
192
+ mulInsOrderList!: any[]
193
+
194
+ @Prop({ default: () => ({}) })
195
+ personData!: any
196
+
197
+ @Prop({ default: () => ({}) })
198
+ recommendValues!: any
199
+
200
+ @Prop({ default: () => ({}) })
201
+ insuranceSaleStatus!: any
202
+
203
+ @Prop({ default: false })
204
+ showHuomianTips!: boolean
205
+
206
+ @Prop({ default: false })
207
+ isFollow!: boolean
208
+
209
+ /** 组合险 locked:其他子险 title 不可点 */
210
+ @Prop({ default: false })
211
+ lockTitleClick!: boolean
212
+
213
+ get isGroupSub() {
214
+ return this.insMode === 'groupSub'
215
+ }
216
+
217
+ get formClassList() {
218
+ return [
219
+ 'bxt-product-info-form',
220
+ `bxt-product-insurance-id-${this.ins.key}`,
221
+ this.isGroupSub ? 'bxt-product-group-sub' : '',
222
+ this.isGroupSub ? 'bxt-product-insurance-type-group' : '',
223
+ this.isFollow ? 'bxt-product-info-form-follow' : ''
224
+ ]
225
+ }
226
+
227
+ get isInsuranceChecked() {
228
+ const titleEle = (this.ins.eleOrderList || []).find(e => e.key === 'title')
229
+ return this.ins.key === 'common' || (!!titleEle && titleEle.value)
230
+ }
231
+
232
+ validate() {
233
+ const form: any = this.$refs.insuranceForm
234
+ return form && form.validate ? form.validate() : Promise.resolve()
235
+ }
236
+
237
+ emitChange(ele) {
238
+ this.$emit('change', { insData: this.ins, eleData: ele })
239
+ }
240
+ onTitleChange(ele) {
241
+ if (this.isGroupSub) {
242
+ this.$emit('title-change', { insData: this.ins, eleData: ele })
243
+ } else {
244
+ this.emitChange(ele)
245
+ }
246
+ }
247
+ onClickIcon(id) {
248
+ const inputDom: any = document.querySelector(`#${id}`)
249
+ if (inputDom) {
250
+ inputDom.focus()
251
+ }
252
+ }
253
+ closeKeyBoard() {
254
+ const activeElement: any = document.activeElement
255
+ if (!activeElement) {
256
+ return false
257
+ }
258
+ activeElement.blur()
259
+ return false
260
+ }
261
+ getElementRules(ele) {
262
+ let _rules = ele.rules || []
263
+ if (ele.isIgnoreValidate) {
264
+ _rules = []
265
+ }
266
+ return _rules
267
+ }
268
+ getElementDisabled(ele) {
269
+ if (ele.key === 'title' && this.lockTitleClick) {
270
+ return true
271
+ }
272
+ const opts = (ele.opts || []).filter(o => !o.isHide && !o.isDisabled && !o.isInvalid)
273
+ return ele.isDisabled || (opts.length === 1 && _get(opts, '[0].val') === ele.value)
274
+ }
275
+ genInsuranceHmTag(opt) {
276
+ const refInsurance = (this.mulInsOrderList || []).find(ins => ins.key === opt.val)
277
+ if (!refInsurance || !refInsurance.huomian) {
278
+ return ''
279
+ }
280
+ if (refInsurance.huomian === 'beiHuomian') {
281
+ return '被保人'
282
+ }
283
+ return '投保人'
284
+ }
285
+ getInsuranceTitleHmTag(insurance) {
286
+ if (!insurance.huomian) {
287
+ return ''
288
+ }
289
+ if (insurance.huomian === 'beiHuomian') {
290
+ return '被保人'
291
+ }
292
+ return '投保人'
293
+ }
294
+ getInsuranceSaleStatusTagByKey(key: string | number) {
295
+ const map = this.insuranceSaleStatus
296
+ if (!map || typeof map !== 'object' || key === undefined || key === null) {
297
+ return ''
298
+ }
299
+ const status = map[key] ?? map[String(key)]
300
+ if (status === undefined || status === null) {
301
+ return ''
302
+ }
303
+ return Number(status) === 2 ? '停' : ''
304
+ }
305
+ getInsuranceSaleStatusTag(insurance) {
306
+ return this.getInsuranceSaleStatusTagByKey(insurance.key)
307
+ }
308
+ getHuomianTips(insurance, element) {
309
+ const personOrderList = _get(this.personData, 'personOrderList') || []
310
+ const applicationInfo = personOrderList.find(p => p.key === 'applicantInfo')
311
+ const applicationTitle = _get(applicationInfo, 'eleOrderList', []).find(e => e.key === 'title')
312
+ const applicationSame = _get(applicationInfo, 'eleOrderList', []).find(e => e.key === 'applicantAndInsuredSame')
313
+ if (!(['touHuomian', 'shuangHuomian'].includes(insurance.huomian) && element.isDisabled)) {
314
+ return ''
315
+ }
316
+ if (!_get(applicationTitle, 'value')) {
317
+ return '<em>设置</em>投保人信息后选择'
318
+ }
319
+ if (_get(applicationSame, 'value') && !insurance.allowSamePerson) {
320
+ return '投被保人为同一人,<em>修改</em>投保人信息后选择'
321
+ }
322
+ return '当前投保人不能加该险种,<em>修改</em>投保人信息后选择'
323
+ }
324
+ getAmountUnit(ele) {
325
+ let digits = isNaN(ele.value * 1) ? 0 : Math.floor(Math.log10(ele.value) + 1)
326
+ switch (digits) {
327
+ case 5:
328
+ return '万'
329
+ case 6:
330
+ return '十万'
331
+ case 7:
332
+ return '百万'
333
+ case 8:
334
+ return '千万'
335
+ case 9:
336
+ return '亿'
337
+ default:
338
+ return ''
339
+ }
340
+ }
341
+ getRecommendValues(ele) {
342
+ if (!['baoe', 'baof'].includes(ele.key) || ele.isDisabled) {
343
+ return null
344
+ }
345
+ const insurance = convertEleOrderListToObj(this.ins)
346
+ return this.recommendValues && this.recommendValues[insurance.key]
347
+ }
348
+ onClickRecommendValue(value, ele) {
349
+ ele.value = value
350
+ this.emitChange(ele)
351
+ this.$nextTick(() => {
352
+ const ref: any = this.$refs[this.ins.key + ele.key + 'item']
353
+ const ref0 = Array.isArray(ref) ? ref[0] : ref
354
+ if (ref0 && ref0.validate) {
355
+ ref0.validate()
356
+ }
357
+ })
358
+ }
359
+ }
360
+ </script>
@@ -6,6 +6,7 @@
6
6
  <script lang="ts">
7
7
  import Vue from 'vue'
8
8
  import { Component } from 'vue-property-decorator'
9
+ import { Toast } from 'vant'
9
10
  import QS from 'qs'
10
11
  import { getPlanbookData } from '../../../src/api/planbook.js'
11
12
  import { get as _get } from 'lodash'
@@ -24,9 +25,6 @@ export default class ProductInfoDemo extends Vue {
24
25
  groupKey = null
25
26
  productKey = null
26
27
  created() {
27
- if (!this.URL_PARAM.publicPbUuid) {
28
- this.URL_PARAM.publicPbUuid = '5d6499946bbb47bb8f6a286e9d4eee77'
29
- }
30
28
  document.body.style.backgroundColor = '#f2f2f2'
31
29
  this.getPlanbookData()
32
30
  }
@@ -35,7 +33,8 @@ export default class ProductInfoDemo extends Vue {
35
33
  }
36
34
  getPlanbookData() {
37
35
  getPlanbookData({
38
- publicPbUuid: this.URL_PARAM.publicPbUuid
36
+ insuranceTypeId: this.URL_PARAM.planbookId || 13767,
37
+ resultUuid: this.URL_PARAM.uuid
39
38
  })
40
39
  .then(async (data: any) => {
41
40
  await initPageData(data)
@@ -58,7 +57,7 @@ export default class ProductInfoDemo extends Vue {
58
57
  }
59
58
  cgProduct(product, obj, inst) {
60
59
  if (obj.type === 'change') {
61
- this.$toast(`${_get(obj, 'insData.eleOrderList[0].labelName')} -> ${_get(obj, 'eleData.labelName')}: ${_get(obj, 'eleData.value')}`)
60
+ Toast(`${_get(obj, 'insData.eleOrderList[0].labelName')} -> ${_get(obj, 'eleData.labelName')}: ${_get(obj, 'eleData.value')}`)
62
61
  }
63
62
  }
64
63
  }
@@ -0,0 +1,307 @@
1
+ /**
2
+ * product-info 辅助
3
+ * - 组合险 UI 状态(不进产品数据 / 试算)
4
+ * - 推荐保额保费、自定义结果元素
5
+ */
6
+ import { get as _get } from 'lodash'
7
+ import { initElementValidateRules } from 'packages/bxs-utils/index'
8
+ import { createdProductDataToCalc } from 'packages/planbook-input/helper.js'
9
+ import { getPlanbookRecommendData } from '../../src/api/planbook.js'
10
+
11
+ export function getTitleEle(ins) {
12
+ return (ins.eleOrderList || []).find(e => e.key === 'title')
13
+ }
14
+
15
+ export function isTitleChecked(ins) {
16
+ const t = getTitleEle(ins)
17
+ return !!(t && t.value)
18
+ }
19
+
20
+ export function isTitleEnabled(ins) {
21
+ const t = getTitleEle(ins)
22
+ return !!(t && !t.isDisabled)
23
+ }
24
+
25
+ /** title 已选中且不可改(锁定勾选) */
26
+ export function isTitleLockedChecked(ins) {
27
+ const t = getTitleEle(ins)
28
+ return !!(t && t.value === true && t.isDisabled === true)
29
+ }
30
+
31
+ export function getCheckedIds(siblings) {
32
+ return siblings.filter(isTitleChecked).map(s => s.key)
33
+ }
34
+
35
+ export function isCheckboxGroup(siblings) {
36
+ return siblings.some(s => !!s.isCheckbox)
37
+ }
38
+
39
+ /** 非 checkbox:多个选中时只保留第一个 */
40
+ export function enforceSingleSelect(siblings) {
41
+ if (isCheckboxGroup(siblings)) {
42
+ return
43
+ }
44
+ const checked = siblings.filter(isTitleChecked)
45
+ if (checked.length <= 1) {
46
+ return
47
+ }
48
+ const keepKey = checked[0].key
49
+ checked.forEach(s => {
50
+ if (s.key !== keepKey) {
51
+ const t = getTitleEle(s)
52
+ if (t) {
53
+ t.value = false
54
+ }
55
+ }
56
+ })
57
+ }
58
+
59
+ export function setAllTitles(siblings, value) {
60
+ siblings.forEach(s => {
61
+ const t = getTitleEle(s)
62
+ if (t) {
63
+ t.value = value
64
+ }
65
+ })
66
+ }
67
+
68
+ /** 快照:组合子险 title 是否可选、是否选中 */
69
+ export function snapshotGroupTitleEnabledMap(list) {
70
+ const map = {}
71
+ const insuranceList = list || []
72
+ insuranceList.forEach(ins => {
73
+ if (!ins.groupKey) {
74
+ return
75
+ }
76
+ const t = getTitleEle(ins)
77
+ if (t) {
78
+ map[ins.key] = {
79
+ enabled: !t.isDisabled,
80
+ checked: !!t.value
81
+ }
82
+ }
83
+ })
84
+ return map
85
+ }
86
+
87
+ /**
88
+ * 找出「原先选中 → 现在不可选」的组合子险名称(用于 Toast)
89
+ * 未选中的子险变为不可选时不提示
90
+ */
91
+ export function getGroupSubsBecameUnselectable(list, prevSnapshotMap) {
92
+ const names = []
93
+ const insuranceList = list || []
94
+ insuranceList.forEach(ins => {
95
+ const prev = prevSnapshotMap && prevSnapshotMap[ins.key]
96
+ const wasChecked = !!(prev && prev.checked)
97
+ if (!ins.groupKey || !wasChecked) {
98
+ return
99
+ }
100
+ const t = getTitleEle(ins)
101
+ if (t && t.isDisabled && !t.value) {
102
+ names.push(t.labelName || ins.name || String(ins.key))
103
+ }
104
+ })
105
+ return names
106
+ }
107
+
108
+ /** 按 id 列表写 title;disabled 的不会勾上 */
109
+ export function applySelectedIds(siblings, selectedIds) {
110
+ const idSet = new Set((selectedIds || []).map(String))
111
+ siblings.forEach(s => {
112
+ const t = getTitleEle(s)
113
+ if (!t) {
114
+ return
115
+ }
116
+ t.value = idSet.has(String(s.key)) && !t.isDisabled
117
+ })
118
+ return getCheckedIds(siblings)
119
+ }
120
+
121
+ /**
122
+ * 按 mulInsOrderList 首次出现顺序构建 groupUiState Map
123
+ * - 若有 title.value===true 且 isDisabled===true:active=true,locked=true,selectedIds 取第一个该险种 id
124
+ * locked:组开关开启且不可关,其他子险 title 不可点
125
+ * - 否则 selectedIds:优先用当前 title 选中;否则回退 prev,并剔除已 disabled 的 id
126
+ */
127
+ export function buildGroupUiState(list, prevMap, options = {}) {
128
+ const { isInit } = options
129
+ const next = new Map()
130
+ const insuranceList = (list || []).filter(ins => ins.groupKey)
131
+ insuranceList.forEach(ins => {
132
+ const gk = String(ins.groupKey)
133
+ if (next.has(gk)) {
134
+ return
135
+ }
136
+ const siblings = insuranceList.filter(i => String(i.groupKey) === gk)
137
+ const lockedChecked = siblings.find(isTitleLockedChecked)
138
+ if (lockedChecked) {
139
+ next.set(gk, {
140
+ active: true,
141
+ locked: true,
142
+ selectedIds: [lockedChecked.key],
143
+ groupName: ins.groupName
144
+ })
145
+ return
146
+ }
147
+ enforceSingleSelect(siblings)
148
+
149
+ const enabled = siblings.filter(isTitleEnabled)
150
+ const enabledKeySet = new Set(enabled.map(s => String(s.key)))
151
+ const pickEnabledIds = ids => (ids || []).filter(id => enabledKeySet.has(String(id)))
152
+
153
+ const selectedFromTitle = pickEnabledIds(getCheckedIds(siblings))
154
+ const prev = prevMap && prevMap.get(gk)
155
+ const allDisabled = enabled.length === 0
156
+
157
+ let active = false
158
+ if (allDisabled) {
159
+ active = false
160
+ } else if (isInit || !prev) {
161
+ active = selectedFromTitle.length > 0
162
+ } else {
163
+ active = prev.active || selectedFromTitle.length > 0
164
+ }
165
+
166
+ const selectedIds = selectedFromTitle.length > 0 ? selectedFromTitle : pickEnabledIds(prev && Array.isArray(prev.selectedIds) ? prev.selectedIds : [])
167
+
168
+ next.set(gk, {
169
+ active,
170
+ locked: false,
171
+ selectedIds,
172
+ groupName: ins.groupName
173
+ })
174
+ })
175
+ return next
176
+ }
177
+
178
+ /** 普通险在前,组合险在后(组序 = groupKey 首次出现) */
179
+ export function buildDisplayBlocks(list, groupUiState, normalizeIns) {
180
+ const normals = []
181
+ const groups = []
182
+ const visited = new Set()
183
+ const insuranceList = list || []
184
+ insuranceList.forEach(ins => {
185
+ if (!ins.groupKey) {
186
+ normalizeIns && normalizeIns(ins)
187
+ normals.push({ type: 'normal', ins })
188
+ return
189
+ }
190
+ const gk = String(ins.groupKey)
191
+ if (visited.has(gk)) {
192
+ return
193
+ }
194
+ visited.add(gk)
195
+ const insList = insuranceList.filter(i => String(i.groupKey) === gk)
196
+ insList.forEach(i => normalizeIns && normalizeIns(i))
197
+ const state = groupUiState && groupUiState.get(gk)
198
+ groups.push({
199
+ type: 'group',
200
+ groupKey: gk,
201
+ groupName: (state && state.groupName) || ins.groupName || '',
202
+ insList
203
+ })
204
+ })
205
+ return normals.concat(groups)
206
+ }
207
+
208
+ /**
209
+ * 拉取并过滤推荐保额/保费(读写共用传入的 cache 对象)
210
+ * @returns {Promise<{ recommendValues: object, cacheKey: string, fromCache?: boolean } | null>}
211
+ */
212
+ export async function fetchRecommendValues({ inputData, groupKey, productKey, productData, cache }) {
213
+ const requestDataToCalc = await createdProductDataToCalc(inputData, { groupKey, productKey }).catch(() => null)
214
+ if (!_get(requestDataToCalc, 'jd')) {
215
+ return null
216
+ }
217
+ const inputDataToCalc = JSON.parse(requestDataToCalc.jd)
218
+ const calcList = _get(inputDataToCalc, 'productGroupList[0].prodOrderList[0].mulInsOrderList') || []
219
+ const reqParams = calcList.map(ins => ({
220
+ insuranceId: ins.key,
221
+ period: ins.pPeriod,
222
+ calMethod: ins.calMethod || ins.feeType || ins.feeBaseType
223
+ }))
224
+ const cacheKey = JSON.stringify(reqParams)
225
+ if (cache && cache[cacheKey]) {
226
+ return { recommendValues: cache[cacheKey], fromCache: true, cacheKey }
227
+ }
228
+ const data = await getPlanbookRecommendData(reqParams)
229
+ const recommendValues = (data || []).reduce((obj, ele) => Object.assign(obj, { [ele.insuranceId]: ele.valueList }), {})
230
+ const mulInsOrderList = _get(productData, 'mulInsOrderList') || []
231
+ mulInsOrderList
232
+ .filter(ins => ins.eleOrderList && recommendValues[ins.key])
233
+ .forEach(ins => {
234
+ const calMethod = ins.calMethod || ins.feeType || ins.feeBaseType
235
+ const key = calMethod === 'baof2baoe' ? 'baof' : 'baoe'
236
+ const ele = ins.eleOrderList.find(e => e.key === key)
237
+ if (!ele) {
238
+ return
239
+ }
240
+ if (ele.min && !isNaN(Number(ele.min))) {
241
+ recommendValues[ins.key] = (recommendValues[ins.key] || []).filter(v => v >= ele.min)
242
+ }
243
+ if (ele.max && !isNaN(Number(ele.max))) {
244
+ recommendValues[ins.key] = (recommendValues[ins.key] || []).filter(v => v <= ele.max)
245
+ }
246
+ })
247
+ if (cache) {
248
+ cache[cacheKey] = recommendValues
249
+ }
250
+ return { recommendValues, cacheKey, fromCache: false }
251
+ }
252
+
253
+ /**
254
+ * 为选中险种写入/更新自定义保费 inputResult;未选中则清掉元素(值回写到 ins.inputResult)
255
+ */
256
+ export function createInsuranceInputResultEle(productData, options = {}) {
257
+ const { allowCustomResult, insData, eleData } = options
258
+ const list = _get(productData, 'mulInsOrderList')
259
+ if (allowCustomResult && list) {
260
+ list.forEach(ins => {
261
+ const isCommon = ins.key === 'common'
262
+ const checked = isTitleChecked(ins)
263
+ const isCurrentIns = !!(insData && eleData && insData.key === ins.key)
264
+ const shouldSkipUpdate = isCurrentIns && eleData.key !== 'calMethod'
265
+ const shouldClear = !isCommon && !checked
266
+ const shouldWrite = !isCommon && checked && !shouldSkipUpdate
267
+ const eleOrderList = _get(ins, 'eleOrderList') || []
268
+ const existingInputResult = eleOrderList.find(e => e.key === 'inputResult')
269
+ const existingInputResultValue = existingInputResult ? existingInputResult.value : null
270
+
271
+ if (shouldClear) {
272
+ if (existingInputResult) {
273
+ ins.inputResult = existingInputResultValue
274
+ }
275
+ ins.eleOrderList = eleOrderList.filter(e => e.key !== 'inputResult')
276
+ }
277
+
278
+ if (shouldWrite) {
279
+ const calMethod = eleOrderList.find(e => e.key === 'calMethod')
280
+ const baof = eleOrderList.find(e => e.key === 'baof') || eleOrderList.find(e => e.key === 'payModePremium')
281
+ const labelName = calMethod ? (calMethod.value === 'baof2baoe' ? '保额' : '保费') : baof ? '保额' : '保费'
282
+ let value = existingInputResult ? ins.inputResult : ins.inputResult === undefined ? null : ins.inputResult
283
+ if (isCurrentIns && eleData.key === 'inputResult') {
284
+ value = eleData.value
285
+ }
286
+ const inputResult = {
287
+ componentName: 'cmCommonInput',
288
+ placeholder: `按照保单填写${labelName}`,
289
+ isHide: !productData.inputResultSwitch,
290
+ key: 'inputResult',
291
+ labelName: `自定义${labelName}`,
292
+ inputType: 'text',
293
+ required: false,
294
+ regExp: '/^\\d{1,}(\\.\\d{1,2})?$/',
295
+ regExpMessage: '请输入1-2位小数的值',
296
+ value
297
+ }
298
+ inputResult.rules = initElementValidateRules(inputResult)
299
+ delete ins.inputResult
300
+ ins.eleOrderList = eleOrderList.filter(e => e.key !== 'inputResult')
301
+ if (productData.inputResultSwitch) {
302
+ ins.eleOrderList.push(inputResult)
303
+ }
304
+ }
305
+ })
306
+ }
307
+ }