@bytenew/bn-bus-ui 1.1.463 → 1.1.464
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/assocTableSearch-Bfe96864.js +349 -0
- package/dist/assocTableSearch.css +1 -0
- package/dist/bn-bus-ui.es.js +65 -0
- package/dist/bn-bus-ui.umd.js +49 -0
- package/dist/chooseIPaasData-COrrvQKZ.js +391 -0
- package/dist/chooseIPaasData.css +1 -0
- package/dist/defaultSet-CzN1ec_d.js +297 -0
- package/dist/defaultSet.css +1 -0
- package/dist/fileFormData-D_Q1vylE.js +53 -0
- package/dist/formItemFooter-BvptZj_u.js +108 -0
- package/dist/formItemFooter.css +1 -0
- package/dist/funcExprDialog-BguNs9fl.js +139 -0
- package/dist/funcExprDialog.css +1 -0
- package/dist/index-0oB7QocU.js +44 -0
- package/dist/index-69fRP0M8.js +89 -0
- package/dist/index-BdklTJtF.js +66 -0
- package/dist/index-Bu3jHlby.js +45 -0
- package/dist/index-BxWx5kpG.js +89 -0
- package/dist/index-C5LQ0OSs.js +183 -0
- package/dist/index-C8YhrrUN.js +51 -0
- package/dist/index-COuPCIzB.js +45 -0
- package/dist/index-CSvVyjxx.js +178 -0
- package/dist/index-DCv51EXX.js +109 -0
- package/dist/index-DUSI6MuC.js +135 -0
- package/dist/index-DVzxs2vR.js +58 -0
- package/dist/index-Dd4b8zNb.js +746 -0
- package/dist/index-Md-ZlkCW.js +66 -0
- package/dist/index-hsSIY2FP.js +221 -0
- package/dist/index-n13-H3_Y.js +118 -0
- package/dist/index-wUl5aPUD.js +64 -0
- package/dist/index.css +1 -0
- package/dist/index2.css +1 -0
- package/dist/index3.css +1 -0
- package/dist/index4.css +1 -0
- package/dist/index5.css +1 -0
- package/dist/index6.css +1 -0
- package/dist/index7.css +1 -0
- package/dist/install-CNjs0u5x.js +23299 -0
- package/dist/install.css +1 -0
- package/dist/setOption-CXOifQcx.js +329 -0
- package/dist/setOption.css +1 -0
- package/dist/setRelationOption-C7VDhej7.js +5217 -0
- package/dist/setRelationOption.css +1 -0
- package/package.json +4 -20
- package/install.js +0 -134
- package/packages/BnAddress/index.vue +0 -141
- package/packages/BnAddressParse/index.vue +0 -75
- package/packages/BnAdvancedFilter/BnFilterView.vue +0 -145
- package/packages/BnAdvancedFilter/index.vue +0 -345
- package/packages/BnAdvancedFilter/util.js +0 -171
- package/packages/BnCascader/BnCascader.vue +0 -543
- package/packages/BnCascader/BnCascaderPanel.vue +0 -1331
- package/packages/BnCascader/cascaderDisplay.js +0 -102
- package/packages/BnCheck/BnCheck.vue +0 -253
- package/packages/BnColumnConfig/columnConfigDialog.vue +0 -134
- package/packages/BnColumnConfig/component/childrenFormSetting/assocTable.vue +0 -143
- package/packages/BnColumnConfig/component/childrenFormSetting/assocTableSearch.vue +0 -314
- package/packages/BnColumnConfig/component/childrenFormSetting/index.vue +0 -243
- package/packages/BnColumnConfig/component/defaultSet/defaultSet.vue +0 -114
- package/packages/BnColumnConfig/component/defaultSet/stringDefault.vue +0 -210
- package/packages/BnColumnConfig/component/funcExprDialog/funcExprDialog.vue +0 -126
- package/packages/BnColumnConfig/component/packageFilter/index.vue +0 -141
- package/packages/BnColumnConfig/component/paraExplainSet/index.vue +0 -156
- package/packages/BnColumnConfig/component/setOption/setOption.vue +0 -321
- package/packages/BnColumnConfig/component/setRelationOption/ConditionList.vue +0 -277
- package/packages/BnColumnConfig/component/setRelationOption/LevelHeader.vue +0 -144
- package/packages/BnColumnConfig/component/setRelationOption/setRelationOption.vue +0 -642
- package/packages/BnColumnConfig/data.js +0 -129
- package/packages/BnColumnConfig/index.scss +0 -23
- package/packages/BnColumnConfig/index.vue +0 -309
- package/packages/BnCondVal/BnCondVal.vue +0 -112
- package/packages/BnCondVal/itemVal/dateRegVal.vue +0 -39
- package/packages/BnCondVal/itemVal/dateVal.vue +0 -39
- package/packages/BnCondVal/itemVal/numRegVal.vue +0 -67
- package/packages/BnCondVal/itemVal/numVal.vue +0 -44
- package/packages/BnCondVal/itemVal/relationVal.vue +0 -73
- package/packages/BnCondVal/itemVal/selectColumnVal.vue +0 -58
- package/packages/BnCondVal/itemVal/selectVal.vue +0 -59
- package/packages/BnCondVal/itemVal/stringVal.vue +0 -156
- package/packages/BnCondition/index.vue +0 -551
- package/packages/BnCondition/util.js +0 -232
- package/packages/BnDatePicker/BnDatePicker.vue +0 -194
- package/packages/BnDrop/BnDrop.vue +0 -917
- package/packages/BnEmptyTip/BnEmptyTip.vue +0 -107
- package/packages/BnExportOrder/BnExportOrder.vue +0 -241
- package/packages/BnExportOrder/validatorCode.vue +0 -94
- package/packages/BnFileView/BnFileView.vue +0 -271
- package/packages/BnFileView/FileDetail.vue +0 -125
- package/packages/BnFileView/videoPreview.vue +0 -70
- package/packages/BnFilterOption/BnFilterOption.vue +0 -160
- package/packages/BnFunExpr/BnFunExpr.vue +0 -608
- package/packages/BnImportOrder/BnImportOrder.vue +0 -323
- package/packages/BnImportTemplate/BnImportTemplate.vue +0 -866
- package/packages/BnImportTemplate/assets/merge_tip_img.png +0 -0
- package/packages/BnImportTemplate/assets/sheet_tip.png +0 -0
- package/packages/BnImportTemplate/assets/xls.png +0 -0
- package/packages/BnImportTemplate/components/ImportTemplateTrigger.vue +0 -198
- package/packages/BnImportTemplate/components/TemplateEditDialog.vue +0 -340
- package/packages/BnImportTemplate/components/TemplateFieldsConfig.vue +0 -401
- package/packages/BnImportTemplate/components/TemplateListDialog.vue +0 -297
- package/packages/BnInpNum/BnInpNum.vue +0 -165
- package/packages/BnMenus/BnMenus.vue +0 -502
- package/packages/BnMenus/MenuItem.vue +0 -321
- package/packages/BnOrderCard/BnOrderCard.vue +0 -389
- package/packages/BnOrderCard/orderRemark.vue +0 -178
- package/packages/BnOrderCard/util.js +0 -94
- package/packages/BnOrderDetail/BnOrderDetail.vue +0 -338
- package/packages/BnOrderDetail/filterLogByUser.vue +0 -108
- package/packages/BnOrderDetail/orderLogs.vue +0 -53
- package/packages/BnOrderDetail/showSubForm/index.vue +0 -41
- package/packages/BnOrderDetail/showSubForm/showChildTable.vue +0 -61
- package/packages/BnOrderDetail/showSubForm/showTimeLine.vue +0 -131
- package/packages/BnOrderDetail/showSubForm/tableCellCopy.vue +0 -84
- package/packages/BnOrderShare/index.vue +0 -378
- package/packages/BnOrderShare/shareUrlInp.vue +0 -54
- package/packages/BnRadio/BnRadio.vue +0 -181
- package/packages/BnRelation/BnRelation.vue +0 -120
- package/packages/BnSelect/BnSelect.vue +0 -485
- package/packages/BnSmartForm/BnBatchForm.vue +0 -334
- package/packages/BnSmartForm/BnBatchFormDialog.vue +0 -423
- package/packages/BnSmartForm/BnSmartElf/elfLog/index.vue +0 -57
- package/packages/BnSmartForm/BnSmartElf/index.vue +0 -95
- package/packages/BnSmartForm/BnSmartFormPsychic.js +0 -629
- package/packages/BnSmartForm/BnSmartFormUtil.js +0 -524
- package/packages/BnSmartForm/addressForm/index.vue +0 -73
- package/packages/BnSmartForm/checkboxForm/index.vue +0 -99
- package/packages/BnSmartForm/childrenForm/childFormFile.vue +0 -237
- package/packages/BnSmartForm/childrenForm/childrenFormBody.vue +0 -260
- package/packages/BnSmartForm/childrenForm/index.vue +0 -110
- package/packages/BnSmartForm/childrenForm/searchInventory.vue +0 -115
- package/packages/BnSmartForm/chooseAssocTable/chooseAssocTable.vue +0 -451
- package/packages/BnSmartForm/chooseAssocTable/chooseChildGood.vue +0 -229
- package/packages/BnSmartForm/chooseAssocTable/tableCellCopy.vue +0 -84
- package/packages/BnSmartForm/chooseChildOrder/chooseChildOrder.vue +0 -158
- package/packages/BnSmartForm/chooseIPaasData/chooseIPaasData.vue +0 -307
- package/packages/BnSmartForm/chooseIPaasData/tableCellCopy.vue +0 -84
- package/packages/BnSmartForm/choosePackage/choosePackage.vue +0 -361
- package/packages/BnSmartForm/dateDualForm/index.vue +0 -37
- package/packages/BnSmartForm/dateForm/index.vue +0 -38
- package/packages/BnSmartForm/fileForm/fileFormData.js +0 -50
- package/packages/BnSmartForm/fileForm/index.vue +0 -240
- package/packages/BnSmartForm/formItemFooter/formItemFooter.vue +0 -38
- package/packages/BnSmartForm/formItemTop/formItemTop.vue +0 -42
- package/packages/BnSmartForm/index.vue +0 -1201
- package/packages/BnSmartForm/numberForm/index.vue +0 -63
- package/packages/BnSmartForm/radioForm/index.vue +0 -72
- package/packages/BnSmartForm/receiveForm/index.vue +0 -112
- package/packages/BnSmartForm/relationForm/index.vue +0 -82
- package/packages/BnSmartForm/scoreForm/index.vue +0 -39
- package/packages/BnSmartForm/selectForm/index.vue +0 -55
- package/packages/BnSmartForm/showValForm/index.vue +0 -42
- package/packages/BnSmartForm/showValForm/showValFormTable.vue +0 -104
- package/packages/BnSmartForm/smartFormsCommon.scss +0 -43
- package/packages/BnSmartForm/stringForm/index.vue +0 -170
- package/packages/BnVirtPopover/index.vue +0 -319
- package/packages/BnWebLog/BnWebLog.vue +0 -140
- package/packages/BnWorkSettingDrop/advancedOptions.js +0 -112
- package/packages/BnWorkSettingDrop/benBenAIUtil.js +0 -89
- package/packages/BnWorkSettingDrop/index.vue +0 -292
- package/packages/font/iconfont.css +0 -243
- package/packages/font/iconfont.ttf +0 -0
- package/packages/font/iconfont.woff +0 -0
- package/packages/font/iconfont.woff2 +0 -0
- package/utils/addressParse-old.js +0 -645
- package/utils/addressParse.js +0 -735
- package/utils/config.js +0 -68
- package/utils/index.js +0 -1171
|
@@ -1,1331 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
@Description: 级联选择面板(不含触发器),最多 6 级列展示
|
|
3
|
-
@Features:
|
|
4
|
-
- 单选 BnRadio / 多选轻量 checkbox;支持 labelInValue 返回完整对象路径
|
|
5
|
-
- 「选到最后一级」:多选勾选父级时批量选中所有叶子;单选开启后仅叶子可选
|
|
6
|
-
- 远程 tree 一次拉树 / lazy 按级懒加载(postFunc + pathUrl)
|
|
7
|
-
- 搜索:平铺展示完整路径;父级命中时展开其下所有叶子路径;子串 + 顺序模糊匹配
|
|
8
|
-
- 列虚拟滚动:单列选项数超过 virtualThreshold 时仅渲染可视区
|
|
9
|
-
@Usage:
|
|
10
|
-
本地树:v-model + :options="treeOptions"
|
|
11
|
-
远程 lazy::post-func="post" path-url="/api" remote-mode="lazy" lazy-level-key="level"
|
|
12
|
-
远程 tree::post-func="post" path-url="/api" remote-mode="tree"
|
|
13
|
-
多选 + 选到最后一级:multiple :check-to-last="true"
|
|
14
|
-
@modelValue 格式:
|
|
15
|
-
单选 labelInValue=false → [1, 2, 3]
|
|
16
|
-
单选 labelInValue=true → [{id:1, title:'...'}, ...]
|
|
17
|
-
多选 labelInValue=false → [[1,2,3], [4,5,6]]
|
|
18
|
-
多选 labelInValue=true → [[{id:1,...},...], [...]]
|
|
19
|
-
@Emits:
|
|
20
|
-
update:modelValue 选中值变化
|
|
21
|
-
change 选中值变化(与 update 同步触发)
|
|
22
|
-
close 单选选中有效值时触发,供 BnCascader 收起面板
|
|
23
|
-
changeCheckToLast 「选到最后一级」开关变化
|
|
24
|
-
loadOver 远程 tree 模式加载完成
|
|
25
|
-
@Slots:
|
|
26
|
-
top 工具栏右侧扩展区(搜索框之后),如多选清空按钮
|
|
27
|
-
@VERSION: 1.0
|
|
28
|
-
-->
|
|
29
|
-
<template>
|
|
30
|
-
<div class="BnCascaderPanel" :class="{ 'BnCascaderPanel--disabled': disabled }">
|
|
31
|
-
<!-- 工具栏:选到最后一级开关 + 搜索 + top 插槽 -->
|
|
32
|
-
<div class="BnCascaderPanel-toolbar">
|
|
33
|
-
<template v-if="!hideCheckToLast">
|
|
34
|
-
<span class="BnCascaderPanel-toolbar-label">选到最后一级</span>
|
|
35
|
-
<el-switch v-model="checkToLastModel" :disabled="disabled" size="small"
|
|
36
|
-
@change="handleCheckToLastChange"/>
|
|
37
|
-
</template>
|
|
38
|
-
<el-input
|
|
39
|
-
ref="searchInputRef"
|
|
40
|
-
v-model="searchKeyword"
|
|
41
|
-
class="BnCascaderPanel-search"
|
|
42
|
-
placeholder="搜索"
|
|
43
|
-
size="small"
|
|
44
|
-
clearable
|
|
45
|
-
:disabled="disabled"
|
|
46
|
-
/>
|
|
47
|
-
<slot name="top"/>
|
|
48
|
-
</div>
|
|
49
|
-
|
|
50
|
-
<!-- 搜索模式:平铺展示完整路径;父级命中时展开其下所有叶子路径 -->
|
|
51
|
-
<div v-if="hasSearchKeyword" class="BnCascaderPanel-search-results">
|
|
52
|
-
<el-scrollbar :max-height="columnHeight">
|
|
53
|
-
<div
|
|
54
|
-
v-if="rootLoading"
|
|
55
|
-
class="BnCascaderPanel-column-status"
|
|
56
|
-
>加载中...</div>
|
|
57
|
-
<div
|
|
58
|
-
v-else-if="!searchFlatResults.length"
|
|
59
|
-
class="BnCascaderPanel-column-status"
|
|
60
|
-
>无匹配数据</div>
|
|
61
|
-
<template v-else>
|
|
62
|
-
<div
|
|
63
|
-
v-for="result in searchFlatResults"
|
|
64
|
-
:key="result.key"
|
|
65
|
-
class="BnCascaderPanel-item BnCascaderPanel-search-result-item"
|
|
66
|
-
:class="{
|
|
67
|
-
'BnCascaderPanel-item--active': isSearchResultSelected(result),
|
|
68
|
-
'BnCascaderPanel-item--disabled': disabled
|
|
69
|
-
}"
|
|
70
|
-
@click="handleSearchResultClick(result)"
|
|
71
|
-
>
|
|
72
|
-
<span
|
|
73
|
-
v-if="multiple"
|
|
74
|
-
class="BnCascaderPanel-item-check"
|
|
75
|
-
@click.stop="handleSearchResultCheckClick(result)"
|
|
76
|
-
>
|
|
77
|
-
<span
|
|
78
|
-
class="BnCascaderPanel-check"
|
|
79
|
-
:class="{
|
|
80
|
-
'BnCascaderPanel-check--checked': isSearchResultSelected(result),
|
|
81
|
-
'BnCascaderPanel-check--disabled': disabled
|
|
82
|
-
}"
|
|
83
|
-
>
|
|
84
|
-
<span class="BnCascaderPanel-check-inner"/>
|
|
85
|
-
</span>
|
|
86
|
-
</span>
|
|
87
|
-
<span class="BnCascaderPanel-item-label" :title="result.label">{{ result.label }}</span>
|
|
88
|
-
</div>
|
|
89
|
-
</template>
|
|
90
|
-
</el-scrollbar>
|
|
91
|
-
</div>
|
|
92
|
-
|
|
93
|
-
<!-- 级联列:无搜索关键词时每列独立滚动,支持虚拟滚动 -->
|
|
94
|
-
<div v-else class="BnCascaderPanel-columns">
|
|
95
|
-
<el-scrollbar
|
|
96
|
-
v-for="(column, level) in displayColumns"
|
|
97
|
-
:key="level"
|
|
98
|
-
:max-height="columnHeight"
|
|
99
|
-
class="BnCascaderPanel-column"
|
|
100
|
-
@scroll="onColumnScroll(level, $event)"
|
|
101
|
-
>
|
|
102
|
-
<div class="BnCascaderPanel-column-inner" :style="getColumnInnerStyle(column, level)">
|
|
103
|
-
<div
|
|
104
|
-
v-for="item in getColumnVisibleItems(column, level)"
|
|
105
|
-
:key="getItemPathKey(item, level)"
|
|
106
|
-
v-memo="[isItemSelected(item, level), isItemActive(item, level), disabled, item.disabled, isItemLoading(item, level), checkToLastModel]"
|
|
107
|
-
class="BnCascaderPanel-item"
|
|
108
|
-
:class="{
|
|
109
|
-
'BnCascaderPanel-item--active': isItemActive(item, level),
|
|
110
|
-
'BnCascaderPanel-item--disabled': disabled || item.disabled
|
|
111
|
-
}"
|
|
112
|
-
@click="handleItemClick(item, level)"
|
|
113
|
-
>
|
|
114
|
-
<!-- 单选:checkToLast 开启且有子级时不展示 Radio,避免选中非叶子 -->
|
|
115
|
-
<span
|
|
116
|
-
v-if="!multiple && !(checkToLastModel && hasChildren(item, level))"
|
|
117
|
-
class="BnCascaderPanel-item-check"
|
|
118
|
-
@click.stop
|
|
119
|
-
>
|
|
120
|
-
<BnRadio
|
|
121
|
-
v-model="val"
|
|
122
|
-
:true-data="buildPathData(item, level)"
|
|
123
|
-
:option-props="mergedOptionProps"
|
|
124
|
-
:disabled="disabled || item.disabled"
|
|
125
|
-
:is-checked="() => isItemSelected(item, level)"
|
|
126
|
-
/>
|
|
127
|
-
</span>
|
|
128
|
-
<!-- 多选:轻量 checkbox,不绑定整表 v-model,避免大量选中时全量重渲染 -->
|
|
129
|
-
<span
|
|
130
|
-
v-else-if="multiple"
|
|
131
|
-
class="BnCascaderPanel-item-check"
|
|
132
|
-
@click.stop="handleCheckClick(item, level)"
|
|
133
|
-
>
|
|
134
|
-
<span
|
|
135
|
-
class="BnCascaderPanel-check"
|
|
136
|
-
:class="{
|
|
137
|
-
'BnCascaderPanel-check--checked': isItemSelected(item, level),
|
|
138
|
-
'BnCascaderPanel-check--disabled': disabled || item.disabled
|
|
139
|
-
}"
|
|
140
|
-
>
|
|
141
|
-
<span class="BnCascaderPanel-check-inner"/>
|
|
142
|
-
</span>
|
|
143
|
-
</span>
|
|
144
|
-
<span class="BnCascaderPanel-item-label" :title="getItemLabel(item)">{{ getItemLabel(item) }}</span>
|
|
145
|
-
<!-- 有子级或加载中时展示箭头 / loading -->
|
|
146
|
-
<span
|
|
147
|
-
v-if="showExpandSuffix(item, level)"
|
|
148
|
-
class="BnCascaderPanel-item-suffix"
|
|
149
|
-
@click.stop="!isItemLoading(item, level) && handleExpand(item, level)"
|
|
150
|
-
>
|
|
151
|
-
<span v-if="isItemLoading(item, level)" class="BnCascaderPanel-item-loading"/>
|
|
152
|
-
<i v-else class="BnCascaderPanel-item-arrow bnUIcon bnUIcon-arrow"/>
|
|
153
|
-
</span>
|
|
154
|
-
</div>
|
|
155
|
-
</div>
|
|
156
|
-
<!-- 仅第一列展示空态 / 加载态 -->
|
|
157
|
-
<div v-if="level === 0 && (rootLoading || !filterColumnItems(column, level).length)" class="BnCascaderPanel-column-status">
|
|
158
|
-
{{ rootLoading ? '加载中...' : (hasSearchKeyword ? '无匹配数据' : '暂无数据') }}
|
|
159
|
-
</div>
|
|
160
|
-
</el-scrollbar>
|
|
161
|
-
</div>
|
|
162
|
-
</div>
|
|
163
|
-
</template>
|
|
164
|
-
|
|
165
|
-
<script>
|
|
166
|
-
/**
|
|
167
|
-
* 级联选择面板逻辑层
|
|
168
|
-
* 调用层级:template 交互入口 → 至多一层业务方法 → 底层工具
|
|
169
|
-
*/
|
|
170
|
-
import { cloneDeep, debounce, isEmpty, omit } from 'lodash';
|
|
171
|
-
import { markRaw } from 'vue';
|
|
172
|
-
import { joinPathLabels } from './cascaderDisplay.js';
|
|
173
|
-
|
|
174
|
-
/** 最大级联层级 */
|
|
175
|
-
const MAX_LEVEL = 6;
|
|
176
|
-
/** 虚拟滚动单行高度(px),与样式 .BnCascaderPanel-item min-height 一致 */
|
|
177
|
-
const ITEM_HEIGHT = 34;
|
|
178
|
-
/** 虚拟滚动上下缓冲行数,减少快速滚动时的空白闪烁 */
|
|
179
|
-
const VIRTUAL_BUFFER = 3;
|
|
180
|
-
/** 选中路径 Set 键分隔符(控制字符,正常 id 不会包含) */
|
|
181
|
-
const PATH_KEY_SEP = '\x1f';
|
|
182
|
-
/** optionProps 默认字段映射 */
|
|
183
|
-
const DEFAULT_OPTION_PROPS = { value: 'id', label: 'title', children: 'children' };
|
|
184
|
-
|
|
185
|
-
export default {
|
|
186
|
-
name: 'BnCascaderPanel',
|
|
187
|
-
|
|
188
|
-
props: {
|
|
189
|
-
/** 绑定值,格式见文件头 @modelValue 说明 */
|
|
190
|
-
modelValue: {},
|
|
191
|
-
/** 本地级联选项树,默认 [{ id, title, children }] */
|
|
192
|
-
options: { type: Array, default: () => [] },
|
|
193
|
-
/** 是否多选 */
|
|
194
|
-
multiple: Boolean,
|
|
195
|
-
/** 选项字段映射,如 { value: 'id', label: 'title', children: 'children' } */
|
|
196
|
-
optionProps: { default: () => ({}) },
|
|
197
|
-
/** labelInValue 为 true 时,返回对象保留的字段列表 */
|
|
198
|
-
valueProps: { type: Array },
|
|
199
|
-
/** true 返回完整对象路径,false 返回 id 路径 */
|
|
200
|
-
labelInValue: { type: Boolean, default: true },
|
|
201
|
-
/** 是否禁用 */
|
|
202
|
-
disabled: { type: Boolean, default: false },
|
|
203
|
-
/** 「选到最后一级」:多选勾选父级选中所有叶子;单选开启后仅叶子可选 */
|
|
204
|
-
checkToLast: {default: false },
|
|
205
|
-
/** 是否隐藏面板内「选到最后一级」开关 */
|
|
206
|
-
hideCheckToLast: { type: Boolean, default: false },
|
|
207
|
-
/** 远程请求方法,签名 (url, data) => Promise */
|
|
208
|
-
postFunc: Function,
|
|
209
|
-
/** 远程请求附加参数 */
|
|
210
|
-
postData: { type: Object, default: () => ({}) },
|
|
211
|
-
/** 远程接口路径 */
|
|
212
|
-
pathUrl: { type: String, default: '' },
|
|
213
|
-
/** 远程模式:tree 一次拉树 / lazy 按级懒加载 */
|
|
214
|
-
remoteMode: { type: String, default: 'tree' },
|
|
215
|
-
/** lazy 模式请求体中的层级字段名 */
|
|
216
|
-
lazyLevelKey: { type: String, default: 'level' },
|
|
217
|
-
/** lazy 模式起始层级(默认 1,展开第一层后请求下一级为 2) */
|
|
218
|
-
lazyLevelStart: { type: Number, default: 1 },
|
|
219
|
-
/** lazy 模式叶子节点标识字段 */
|
|
220
|
-
leafKey: { type: String, default: 'leaf' },
|
|
221
|
-
/** 远程 tree 模式是否在 mounted 时自动初始化 */
|
|
222
|
-
isInitOptions: { type: Boolean, default: true },
|
|
223
|
-
/** 单列超过该数量启用虚拟滚动;0 禁用 */
|
|
224
|
-
virtualThreshold: { type: Number, default: 40 },
|
|
225
|
-
/** 面板每列最大高度(px) */
|
|
226
|
-
columnHeight: { type: Number, default: 300 }
|
|
227
|
-
},
|
|
228
|
-
|
|
229
|
-
emits: [
|
|
230
|
-
'update:modelValue',
|
|
231
|
-
'change',
|
|
232
|
-
'close',
|
|
233
|
-
'changeCheckToLast',
|
|
234
|
-
'loadOver' // 远程 tree 模式加载完成
|
|
235
|
-
],
|
|
236
|
-
|
|
237
|
-
data() {
|
|
238
|
-
return {
|
|
239
|
-
/** 当前展开路径(value 数组),决定展示到第几列 */
|
|
240
|
-
expandPath: [],
|
|
241
|
-
/** 远程 tree 模式拉取的完整树 */
|
|
242
|
-
remoteOptions: [],
|
|
243
|
-
/** lazy 模式子级缓存:cacheKey → children[] */
|
|
244
|
-
lazyChildrenMap: {},
|
|
245
|
-
/** lazy 模式已加载标记:cacheKey → true */
|
|
246
|
-
lazyLoadedKeys: {},
|
|
247
|
-
/** 节点加载中标记:loadingKey → true */
|
|
248
|
-
loadingKeys: {},
|
|
249
|
-
/** 远程 tree 模式根级加载中 */
|
|
250
|
-
treeLoading: false,
|
|
251
|
-
/** 各列滚动位置,供虚拟滚动计算可视区 */
|
|
252
|
-
columnScrollTops: {},
|
|
253
|
-
/** 搜索关键词 */
|
|
254
|
-
searchKeyword: '',
|
|
255
|
-
/** 「选到最后一级」开关本地状态(与 checkToLast prop 同步) */
|
|
256
|
-
checkToLastModel:this.checkToLast || false
|
|
257
|
-
};
|
|
258
|
-
},
|
|
259
|
-
|
|
260
|
-
created() {
|
|
261
|
-
/** 搜索输入防抖,避免频繁遍历选项树 */
|
|
262
|
-
this.debouncedHandleSearchKeywordChange = debounce(this.handleSearchKeywordChange, 200);
|
|
263
|
-
},
|
|
264
|
-
|
|
265
|
-
watch: {
|
|
266
|
-
/** 单选时根据 modelValue 同步展开路径 */
|
|
267
|
-
modelValue: { handler: 'syncExpandPath', immediate: true },
|
|
268
|
-
options: 'syncExpandPath',
|
|
269
|
-
/** 远程配置变化时重新初始化 */
|
|
270
|
-
pathUrl: 'initRemote',
|
|
271
|
-
postFunc: 'initRemote',
|
|
272
|
-
remoteMode: 'initRemote',
|
|
273
|
-
postData: { handler: 'initRemote', deep: true },
|
|
274
|
-
searchKeyword() { this.debouncedHandleSearchKeywordChange(); }
|
|
275
|
-
},
|
|
276
|
-
|
|
277
|
-
mounted() {
|
|
278
|
-
if (this.isInitOptions) {
|
|
279
|
-
this.initRemote();
|
|
280
|
-
}
|
|
281
|
-
},
|
|
282
|
-
|
|
283
|
-
computed: {
|
|
284
|
-
/** 是否为远程 lazy 按级加载模式 */
|
|
285
|
-
isLazyMode() {
|
|
286
|
-
return this.isRemoteLazy;
|
|
287
|
-
},
|
|
288
|
-
/** 是否配置了远程请求(pathUrl + postFunc) */
|
|
289
|
-
isRemote() {
|
|
290
|
-
return Boolean(this.pathUrl && this.postFunc);
|
|
291
|
-
},
|
|
292
|
-
/** 远程 tree:一次请求返回完整树 */
|
|
293
|
-
isRemoteTree() {
|
|
294
|
-
return this.isRemote && this.remoteMode === 'tree';
|
|
295
|
-
},
|
|
296
|
-
/** 远程 lazy:按级请求子节点 */
|
|
297
|
-
isRemoteLazy() {
|
|
298
|
-
return this.isRemote && this.remoteMode === 'lazy';
|
|
299
|
-
},
|
|
300
|
-
/** 第一列数据源:本地 options / 远程 tree / lazy 根级 */
|
|
301
|
-
rootOptions() {
|
|
302
|
-
if (this.isRemoteTree) {
|
|
303
|
-
return this.remoteOptions.length ? this.remoteOptions : this.options;
|
|
304
|
-
}
|
|
305
|
-
if (this.isRemoteLazy) {
|
|
306
|
-
return this.lazyChildrenMap.root ?? this.options;
|
|
307
|
-
}
|
|
308
|
-
return this.options || [];
|
|
309
|
-
},
|
|
310
|
-
/** 合并后的选项字段映射 */
|
|
311
|
-
mergedOptionProps() {
|
|
312
|
-
return { ...DEFAULT_OPTION_PROPS, ...this.optionProps };
|
|
313
|
-
},
|
|
314
|
-
/**
|
|
315
|
-
* v-model 代理:统一单选/多选、labelInValue 的空值形态
|
|
316
|
-
* 单选写入时同步 expandPath
|
|
317
|
-
*/
|
|
318
|
-
val: {
|
|
319
|
-
get() {
|
|
320
|
-
if (this.multiple) {
|
|
321
|
-
return Array.isArray(this.modelValue) ? this.modelValue : [];
|
|
322
|
-
}
|
|
323
|
-
if (this.labelInValue && isEmpty(this.modelValue)) {
|
|
324
|
-
return null;
|
|
325
|
-
}
|
|
326
|
-
return this.modelValue;
|
|
327
|
-
},
|
|
328
|
-
set(v) {
|
|
329
|
-
let next = v;
|
|
330
|
-
if (!this.multiple) {
|
|
331
|
-
if (isEmpty(v)) {
|
|
332
|
-
next = this.labelInValue ? null : [];
|
|
333
|
-
this.expandPath = [];
|
|
334
|
-
} else {
|
|
335
|
-
this.expandPath = this.normalizePath(v);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
this.emitValue(next);
|
|
339
|
-
}
|
|
340
|
-
},
|
|
341
|
-
/**
|
|
342
|
-
* 根据 expandPath 推导各列数据
|
|
343
|
-
* lazy 模式下子级未加载完成时不追加空列
|
|
344
|
-
*/
|
|
345
|
-
displayColumns() {
|
|
346
|
-
const columns = [this.rootOptions];
|
|
347
|
-
let currentOptions = this.rootOptions;
|
|
348
|
-
|
|
349
|
-
for (let level = 0; level < this.expandPath.length && level < MAX_LEVEL - 1; level++) {
|
|
350
|
-
const activeItem = currentOptions.find(item => this.getItemValue(item) === this.expandPath[level]);
|
|
351
|
-
if (!activeItem || !this.hasChildren(activeItem, level)) {
|
|
352
|
-
break;
|
|
353
|
-
}
|
|
354
|
-
const children = this.getChildren(activeItem);
|
|
355
|
-
if (!children.length && this.isLazyMode && !this.isLazyLoaded(activeItem, level)) {
|
|
356
|
-
break;
|
|
357
|
-
}
|
|
358
|
-
columns.push(children);
|
|
359
|
-
currentOptions = children;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
return columns.slice(0, MAX_LEVEL);
|
|
363
|
-
},
|
|
364
|
-
/** 第一列是否处于加载中(tree 根级 / lazy 根级) */
|
|
365
|
-
rootLoading() {
|
|
366
|
-
return this.treeLoading || Boolean(this.loadingKeys.root);
|
|
367
|
-
},
|
|
368
|
-
/** 是否有有效搜索关键词 */
|
|
369
|
-
hasSearchKeyword() {
|
|
370
|
-
return Boolean(this.searchKeyword.trim());
|
|
371
|
-
},
|
|
372
|
-
/**
|
|
373
|
-
* 搜索平铺结果:父级命中 → 其下所有叶子路径;否则仅展示命中的叶子/中间路径
|
|
374
|
-
*/
|
|
375
|
-
searchFlatResults() {
|
|
376
|
-
if (!this.hasSearchKeyword || this.rootLoading) {
|
|
377
|
-
return [];
|
|
378
|
-
}
|
|
379
|
-
return this.buildSearchFlatResults(this.rootOptions);
|
|
380
|
-
},
|
|
381
|
-
/** 多选已选路径键集合,O(1) 判断选中态 */
|
|
382
|
-
selectedPathKeySet() {
|
|
383
|
-
if (!this.multiple || !Array.isArray(this.modelValue) || !this.modelValue.length) {
|
|
384
|
-
return new Set();
|
|
385
|
-
}
|
|
386
|
-
const set = new Set();
|
|
387
|
-
this.modelValue.forEach(path => {
|
|
388
|
-
const key = this.getPathKey(path);
|
|
389
|
-
if (key) {
|
|
390
|
-
set.add(key);
|
|
391
|
-
}
|
|
392
|
-
});
|
|
393
|
-
return set;
|
|
394
|
-
}
|
|
395
|
-
},
|
|
396
|
-
|
|
397
|
-
methods: {
|
|
398
|
-
// --- 虚拟滚动:padding 占位 + slice 可视区,避免大列表全量渲染 ---
|
|
399
|
-
|
|
400
|
-
/** 当前列是否启用虚拟滚动 */
|
|
401
|
-
shouldVirtualize(column) {
|
|
402
|
-
return this.virtualThreshold > 0 && column.length >= this.virtualThreshold;
|
|
403
|
-
},
|
|
404
|
-
|
|
405
|
-
/** 根据滚动位置计算当前列应渲染的 [start, end) 区间 */
|
|
406
|
-
getVirtualRange(column, level) {
|
|
407
|
-
const scrollTop = this.columnScrollTops[level] || 0;
|
|
408
|
-
const start = Math.max(0, Math.floor(scrollTop / ITEM_HEIGHT) - VIRTUAL_BUFFER);
|
|
409
|
-
const end = Math.min(column.length, Math.ceil((scrollTop + this.columnHeight) / ITEM_HEIGHT) + VIRTUAL_BUFFER);
|
|
410
|
-
return { start, end };
|
|
411
|
-
},
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* 按搜索关键词过滤当前列(禁用项不参与匹配展示)
|
|
415
|
-
* 仅第一列做关键词过滤;已展开子级列展示全部子项,避免点击非叶子匹配项后子级被滤空
|
|
416
|
-
*/
|
|
417
|
-
filterColumnItems(column, level = 0) {
|
|
418
|
-
const keyword = this.searchKeyword.trim();
|
|
419
|
-
if (!keyword || !Array.isArray(column)) {
|
|
420
|
-
return column || [];
|
|
421
|
-
}
|
|
422
|
-
if (level > 0) {
|
|
423
|
-
return column.filter(item => !item.disabled);
|
|
424
|
-
}
|
|
425
|
-
return column.filter(item => !item.disabled && this.hasSearchMatch(item));
|
|
426
|
-
},
|
|
427
|
-
|
|
428
|
-
/** 当前列实际渲染的选项(过滤 + 虚拟滚动切片) */
|
|
429
|
-
getColumnVisibleItems(column, level) {
|
|
430
|
-
const filtered = this.filterColumnItems(column, level);
|
|
431
|
-
if (!this.shouldVirtualize(filtered)) {
|
|
432
|
-
return filtered;
|
|
433
|
-
}
|
|
434
|
-
const { start, end } = this.getVirtualRange(filtered, level);
|
|
435
|
-
return filtered.slice(start, end);
|
|
436
|
-
},
|
|
437
|
-
|
|
438
|
-
/** 虚拟滚动占位样式:用 padding 模拟未渲染行的总高度 */
|
|
439
|
-
getColumnInnerStyle(column, level) {
|
|
440
|
-
const filtered = this.filterColumnItems(column, level);
|
|
441
|
-
if (!this.shouldVirtualize(filtered)) {
|
|
442
|
-
return null;
|
|
443
|
-
}
|
|
444
|
-
const { start, end } = this.getVirtualRange(filtered, level);
|
|
445
|
-
return {
|
|
446
|
-
paddingTop: `${start * ITEM_HEIGHT}px`,
|
|
447
|
-
paddingBottom: `${(filtered.length - end) * ITEM_HEIGHT}px`
|
|
448
|
-
};
|
|
449
|
-
},
|
|
450
|
-
|
|
451
|
-
/** 记录列滚动位置,触发虚拟滚动重算 */
|
|
452
|
-
onColumnScroll(level, payload) {
|
|
453
|
-
const scrollTop = typeof payload === 'number' ? payload : payload?.scrollTop;
|
|
454
|
-
if (this.columnScrollTops[level] !== scrollTop) {
|
|
455
|
-
this.columnScrollTops = { ...this.columnScrollTops, [level]: scrollTop };
|
|
456
|
-
}
|
|
457
|
-
},
|
|
458
|
-
|
|
459
|
-
// --- 字段读取:统一通过 optionProps 取值 ---
|
|
460
|
-
|
|
461
|
-
/** 读取选项 value 字段 */
|
|
462
|
-
getItemValue(item) {
|
|
463
|
-
return item?.[this.mergedOptionProps.value];
|
|
464
|
-
},
|
|
465
|
-
|
|
466
|
-
/** 读取选项 label 字段 */
|
|
467
|
-
getItemLabel(item) {
|
|
468
|
-
return item?.[this.mergedOptionProps.label];
|
|
469
|
-
},
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* 获取节点子级:优先 lazy 缓存,其次节点 children 字段
|
|
473
|
-
* @param {Object|null} item 父节点,null 表示根级
|
|
474
|
-
*/
|
|
475
|
-
getChildren(item) {
|
|
476
|
-
if (!item) {
|
|
477
|
-
return [];
|
|
478
|
-
}
|
|
479
|
-
const cacheKey = String(this.getItemValue(item));
|
|
480
|
-
return this.lazyChildrenMap[cacheKey] || item?.[this.mergedOptionProps.children] || [];
|
|
481
|
-
},
|
|
482
|
-
|
|
483
|
-
/** 按 valueProps 裁剪节点字段,或剔除 children 后返回 */
|
|
484
|
-
getValObj(node) {
|
|
485
|
-
if (this.valueProps?.length) {
|
|
486
|
-
const obj = {};
|
|
487
|
-
this.valueProps.forEach(key => { obj[key] = node[key]; });
|
|
488
|
-
return obj;
|
|
489
|
-
}
|
|
490
|
-
return omit(node, [this.mergedOptionProps.children]);
|
|
491
|
-
},
|
|
492
|
-
|
|
493
|
-
// --- 路径:value 路径 ↔ 节点路径 ↔ modelValue 格式转换 ---
|
|
494
|
-
|
|
495
|
-
/** 构建从根到当前项的 value 路径 */
|
|
496
|
-
buildPathValues(item, level) {
|
|
497
|
-
const prefix = level > 0 ? this.expandPath.slice(0, level) : [];
|
|
498
|
-
return [...prefix, this.getItemValue(item)];
|
|
499
|
-
},
|
|
500
|
-
|
|
501
|
-
/** 按 value 路径在已加载树中查找对应节点序列 */
|
|
502
|
-
buildPathNodes(pathValues) {
|
|
503
|
-
const nodes = [];
|
|
504
|
-
let currentOptions = this.rootOptions;
|
|
505
|
-
pathValues.forEach(value => {
|
|
506
|
-
const node = currentOptions.find(item => this.getItemValue(item) === value);
|
|
507
|
-
if (!node) {
|
|
508
|
-
return;
|
|
509
|
-
}
|
|
510
|
-
nodes.push(node);
|
|
511
|
-
currentOptions = this.getChildren(node);
|
|
512
|
-
});
|
|
513
|
-
return nodes;
|
|
514
|
-
},
|
|
515
|
-
|
|
516
|
-
/** 构建当前项对应的 modelValue 片段(单条路径) */
|
|
517
|
-
buildPathData(item, level) {
|
|
518
|
-
return this.pathValuesToModel(this.buildPathValues(item, level));
|
|
519
|
-
},
|
|
520
|
-
|
|
521
|
-
/** 将 modelValue 路径统一为 value 数组(labelInValue 时提取 value 字段) */
|
|
522
|
-
normalizePath(path) {
|
|
523
|
-
if (!Array.isArray(path) || !path.length) {
|
|
524
|
-
return [];
|
|
525
|
-
}
|
|
526
|
-
if (this.labelInValue) {
|
|
527
|
-
return path.map(item => (item && typeof item === 'object')
|
|
528
|
-
? item[this.mergedOptionProps.value]
|
|
529
|
-
: item);
|
|
530
|
-
}
|
|
531
|
-
return [...path];
|
|
532
|
-
},
|
|
533
|
-
|
|
534
|
-
/** 路径唯一键,供 Set 索引 */
|
|
535
|
-
getPathKey(path) {
|
|
536
|
-
const values = this.normalizePath(path);
|
|
537
|
-
return values.length ? values.join(PATH_KEY_SEP) : '';
|
|
538
|
-
},
|
|
539
|
-
|
|
540
|
-
/** 当前节点路径唯一键 */
|
|
541
|
-
getItemPathKey(item, level) {
|
|
542
|
-
return this.buildPathValues(item, level).join(PATH_KEY_SEP);
|
|
543
|
-
},
|
|
544
|
-
|
|
545
|
-
/** 判断两条路径是否完全一致 */
|
|
546
|
-
isSamePath(pathA, pathB) {
|
|
547
|
-
const a = this.normalizePath(pathA);
|
|
548
|
-
const b = this.normalizePath(pathB);
|
|
549
|
-
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
550
|
-
},
|
|
551
|
-
|
|
552
|
-
/** 判断 path 是否以 prefixPathValues 为前缀(多选取消父级勾选时用) */
|
|
553
|
-
isPathUnderPrefix(path, prefixPathValues) {
|
|
554
|
-
const pathValues = this.normalizePath(path);
|
|
555
|
-
const prefix = this.normalizePath(prefixPathValues);
|
|
556
|
-
return prefix.length > 0
|
|
557
|
-
&& pathValues.length >= prefix.length
|
|
558
|
-
&& prefix.every((value, index) => pathValues[index] === value);
|
|
559
|
-
},
|
|
560
|
-
|
|
561
|
-
// --- 节点状态:叶子判断、展开箭头、选中态 ---
|
|
562
|
-
|
|
563
|
-
/** lazy 子级缓存 key;根级为 'root' */
|
|
564
|
-
getLazyCacheKey(item, level) {
|
|
565
|
-
return (level < 0 || !item) ? 'root' : String(this.getItemValue(item));
|
|
566
|
-
},
|
|
567
|
-
|
|
568
|
-
/** 节点加载中状态 key,用完整路径拼接避免冲突 */
|
|
569
|
-
getItemLoadingKey(item, level) {
|
|
570
|
-
return item ? this.buildPathValues(item, level).join('_') : 'root';
|
|
571
|
-
},
|
|
572
|
-
|
|
573
|
-
/** lazy 模式下该节点子级是否已请求完成 */
|
|
574
|
-
isLazyLoaded(item, level = 0) {
|
|
575
|
-
return Boolean(this.lazyLoadedKeys[this.getLazyCacheKey(item, level)]);
|
|
576
|
-
},
|
|
577
|
-
|
|
578
|
-
/** 节点是否正在请求子级 */
|
|
579
|
-
isItemLoading(item, level) {
|
|
580
|
-
return Boolean(this.loadingKeys[this.getItemLoadingKey(item, level)]);
|
|
581
|
-
},
|
|
582
|
-
|
|
583
|
-
/**
|
|
584
|
-
* 是否为叶子节点
|
|
585
|
-
* lazy 未加载完成时默认非叶子(保留展开箭头以触发加载)
|
|
586
|
-
*/
|
|
587
|
-
isItemLeaf(item, level = 0) {
|
|
588
|
-
if (item[this.leafKey] === true || item.isLeaf === true) {
|
|
589
|
-
return true;
|
|
590
|
-
}
|
|
591
|
-
if (item[this.leafKey] === false || item.hasChildren === true) {
|
|
592
|
-
return false;
|
|
593
|
-
}
|
|
594
|
-
if (this.isLazyMode) {
|
|
595
|
-
if (this.isLazyLoaded(item, level)) {
|
|
596
|
-
return !this.getChildren(item).length;
|
|
597
|
-
}
|
|
598
|
-
return false;
|
|
599
|
-
}
|
|
600
|
-
return !this.getChildren(item).length;
|
|
601
|
-
},
|
|
602
|
-
|
|
603
|
-
/** 是否有子级可展开(含 lazy 未加载的占位展开) */
|
|
604
|
-
hasChildren(item, level = 0) {
|
|
605
|
-
if (this.isItemLeaf(item, level)) {
|
|
606
|
-
return false;
|
|
607
|
-
}
|
|
608
|
-
if (this.getChildren(item).length) {
|
|
609
|
-
return true;
|
|
610
|
-
}
|
|
611
|
-
return this.isLazyMode && !this.isLazyLoaded(item, level);
|
|
612
|
-
},
|
|
613
|
-
|
|
614
|
-
/** 是否展示右侧展开箭头或 loading */
|
|
615
|
-
showExpandSuffix(item, level) {
|
|
616
|
-
return this.isItemLoading(item, level) || this.hasChildren(item, level);
|
|
617
|
-
},
|
|
618
|
-
|
|
619
|
-
/** 当前项是否为该列高亮项(expandPath 对应位匹配) */
|
|
620
|
-
isItemActive(item, level) {
|
|
621
|
-
return this.expandPath[level] === this.getItemValue(item);
|
|
622
|
-
},
|
|
623
|
-
|
|
624
|
-
/** value 路径转为 modelValue 格式(labelInValue 时映射为对象数组) */
|
|
625
|
-
pathValuesToModel(pathValues) {
|
|
626
|
-
return this.labelInValue
|
|
627
|
-
? this.buildPathNodes(pathValues).map(node => this.getValObj(node))
|
|
628
|
-
: pathValues;
|
|
629
|
-
},
|
|
630
|
-
|
|
631
|
-
/**
|
|
632
|
-
* 「选到最后一级」多选时,收集某节点下所有叶子路径
|
|
633
|
-
* @returns {Array} modelValue 格式的路径数组
|
|
634
|
-
*/
|
|
635
|
-
getAllLeafPathsForItem(item, level) {
|
|
636
|
-
const basePathValues = this.buildPathValues(item, level);
|
|
637
|
-
const pathNodes = this.buildPathNodes(basePathValues);
|
|
638
|
-
if (!pathNodes.length || item.disabled) {
|
|
639
|
-
return [];
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
const targetNode = pathNodes[pathNodes.length - 1];
|
|
643
|
-
if (!this.hasChildren(targetNode, level)) {
|
|
644
|
-
return [this.pathValuesToModel(basePathValues)];
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
const leafPathValuesList = [];
|
|
648
|
-
const walk = (node, pathValues, depth) => {
|
|
649
|
-
if (depth >= MAX_LEVEL) {
|
|
650
|
-
leafPathValuesList.push([...pathValues]);
|
|
651
|
-
return;
|
|
652
|
-
}
|
|
653
|
-
const children = this.getChildren(node).filter(child => !child.disabled);
|
|
654
|
-
if (!children.length) {
|
|
655
|
-
leafPathValuesList.push([...pathValues]);
|
|
656
|
-
return;
|
|
657
|
-
}
|
|
658
|
-
children.forEach(child => {
|
|
659
|
-
walk(child, [...pathValues, this.getItemValue(child)], depth + 1);
|
|
660
|
-
});
|
|
661
|
-
};
|
|
662
|
-
|
|
663
|
-
this.getChildren(targetNode)
|
|
664
|
-
.filter(child => !child.disabled)
|
|
665
|
-
.forEach(child => walk(child, [...basePathValues, this.getItemValue(child)], level + 1));
|
|
666
|
-
|
|
667
|
-
return leafPathValuesList.map(pv => this.pathValuesToModel(pv));
|
|
668
|
-
},
|
|
669
|
-
|
|
670
|
-
/**
|
|
671
|
-
* 判断节点是否选中
|
|
672
|
-
* 多选 + checkToLast:父级需其下所有叶子均已选中才算选中
|
|
673
|
-
*/
|
|
674
|
-
isItemSelected(item, level) {
|
|
675
|
-
if (isEmpty(this.val)) {
|
|
676
|
-
return false;
|
|
677
|
-
}
|
|
678
|
-
if (!this.multiple) {
|
|
679
|
-
return this.isSamePath(this.val, this.buildPathData(item, level));
|
|
680
|
-
}
|
|
681
|
-
const selectedKeys = this.selectedPathKeySet;
|
|
682
|
-
if (this.checkToLastModel && this.hasChildren(item, level)) {
|
|
683
|
-
const leafPaths = this.getAllLeafPathsForItem(item, level);
|
|
684
|
-
return leafPaths.length > 0
|
|
685
|
-
&& leafPaths.every(path => selectedKeys.has(this.getPathKey(path)));
|
|
686
|
-
}
|
|
687
|
-
return selectedKeys.has(this.getItemPathKey(item, level));
|
|
688
|
-
},
|
|
689
|
-
|
|
690
|
-
/** 单选 checkToLast 开启时,有子级的节点不可直接选中 */
|
|
691
|
-
isItemSelectable(item, level) {
|
|
692
|
-
return !item.disabled
|
|
693
|
-
&& (this.multiple || !this.checkToLastModel || !this.hasChildren(item, level));
|
|
694
|
-
},
|
|
695
|
-
|
|
696
|
-
// --- 搜索:平铺路径列表,父级命中时展开所有叶子 ---
|
|
697
|
-
|
|
698
|
-
/**
|
|
699
|
-
* 文本匹配:先子串包含,再按字符顺序模糊匹配(如 "fd" 匹配 "Feedback")
|
|
700
|
-
*/
|
|
701
|
-
matchSearchText(label, keyword) {
|
|
702
|
-
const text = String(label ?? '').toLowerCase();
|
|
703
|
-
const query = String(keyword ?? '').trim().toLowerCase();
|
|
704
|
-
if (!query) {
|
|
705
|
-
return true;
|
|
706
|
-
}
|
|
707
|
-
if (text.includes(query)) {
|
|
708
|
-
return true;
|
|
709
|
-
}
|
|
710
|
-
let queryIndex = 0;
|
|
711
|
-
for (let i = 0; i < text.length && queryIndex < query.length; i++) {
|
|
712
|
-
if (text[i] === query[queryIndex]) {
|
|
713
|
-
queryIndex += 1;
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
return queryIndex === query.length;
|
|
717
|
-
},
|
|
718
|
-
|
|
719
|
-
/** 节点自身或任一后代是否匹配搜索词(列模式筛选用) */
|
|
720
|
-
hasSearchMatch(node) {
|
|
721
|
-
if (this.matchSearchText(this.getItemLabel(node), this.searchKeyword)) {
|
|
722
|
-
return true;
|
|
723
|
-
}
|
|
724
|
-
return this.getChildren(node).some(child => this.hasSearchMatch(child));
|
|
725
|
-
},
|
|
726
|
-
|
|
727
|
-
/**
|
|
728
|
-
* 收集某节点下所有叶子 value 路径
|
|
729
|
-
* lazy 未加载子级时,将当前节点视为可选终点
|
|
730
|
-
*/
|
|
731
|
-
collectLeafPathValuesList(node, pathValues, level) {
|
|
732
|
-
if (!node || node.disabled) {
|
|
733
|
-
return [];
|
|
734
|
-
}
|
|
735
|
-
if (!this.hasChildren(node, level)) {
|
|
736
|
-
return [[...pathValues]];
|
|
737
|
-
}
|
|
738
|
-
const children = this.getChildren(node).filter(child => !child.disabled);
|
|
739
|
-
if (!children.length) {
|
|
740
|
-
return [[...pathValues]];
|
|
741
|
-
}
|
|
742
|
-
const results = [];
|
|
743
|
-
children.forEach(child => {
|
|
744
|
-
results.push(
|
|
745
|
-
...this.collectLeafPathValuesList(
|
|
746
|
-
child,
|
|
747
|
-
[...pathValues, this.getItemValue(child)],
|
|
748
|
-
level + 1
|
|
749
|
-
)
|
|
750
|
-
);
|
|
751
|
-
});
|
|
752
|
-
return results;
|
|
753
|
-
},
|
|
754
|
-
|
|
755
|
-
/**
|
|
756
|
-
* 构建搜索平铺结果
|
|
757
|
-
* 父级命中 → 其下所有叶子路径;未命中则继续向下查找
|
|
758
|
-
*/
|
|
759
|
-
buildSearchFlatResults(nodes, parentPathValues = []) {
|
|
760
|
-
const keyword = this.searchKeyword.trim();
|
|
761
|
-
if (!keyword || !Array.isArray(nodes)) {
|
|
762
|
-
return [];
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
const results = [];
|
|
766
|
-
const seenKeys = new Set();
|
|
767
|
-
const appendPath = (pathValues) => {
|
|
768
|
-
const pathData = this.pathValuesToModel(pathValues);
|
|
769
|
-
const key = this.getPathKey(pathData);
|
|
770
|
-
if (!key || seenKeys.has(key)) {
|
|
771
|
-
return;
|
|
772
|
-
}
|
|
773
|
-
seenKeys.add(key);
|
|
774
|
-
results.push({
|
|
775
|
-
key,
|
|
776
|
-
pathValues,
|
|
777
|
-
pathData,
|
|
778
|
-
label: joinPathLabels(this.getPathDisplayLabels(pathValues))
|
|
779
|
-
});
|
|
780
|
-
};
|
|
781
|
-
|
|
782
|
-
nodes.forEach(node => {
|
|
783
|
-
if (node.disabled) {
|
|
784
|
-
return;
|
|
785
|
-
}
|
|
786
|
-
const pathValues = [...parentPathValues, this.getItemValue(node)];
|
|
787
|
-
const level = pathValues.length - 1;
|
|
788
|
-
const selfMatch = this.matchSearchText(this.getItemLabel(node), keyword);
|
|
789
|
-
|
|
790
|
-
if (selfMatch) {
|
|
791
|
-
this.collectLeafPathValuesList(node, pathValues, level).forEach(appendPath);
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
const children = this.getChildren(node);
|
|
796
|
-
if (children.length) {
|
|
797
|
-
results.push(...this.buildSearchFlatResults(children, pathValues));
|
|
798
|
-
}
|
|
799
|
-
});
|
|
800
|
-
|
|
801
|
-
return results;
|
|
802
|
-
},
|
|
803
|
-
|
|
804
|
-
/** 搜索平铺项是否选中 */
|
|
805
|
-
isSearchResultSelected(result) {
|
|
806
|
-
if (isEmpty(this.val)) {
|
|
807
|
-
return false;
|
|
808
|
-
}
|
|
809
|
-
if (this.multiple) {
|
|
810
|
-
return this.selectedPathKeySet.has(result.key);
|
|
811
|
-
}
|
|
812
|
-
return this.isSamePath(this.val, result.pathData);
|
|
813
|
-
},
|
|
814
|
-
|
|
815
|
-
/** 点击搜索平铺项:单选直接选中,多选由 checkbox 处理 */
|
|
816
|
-
handleSearchResultClick(result) {
|
|
817
|
-
if (this.disabled) {
|
|
818
|
-
return;
|
|
819
|
-
}
|
|
820
|
-
if (this.multiple) {
|
|
821
|
-
return;
|
|
822
|
-
}
|
|
823
|
-
this.val = result.pathData;
|
|
824
|
-
},
|
|
825
|
-
|
|
826
|
-
/** 多选:点击搜索平铺项 checkbox 切换选中 */
|
|
827
|
-
handleSearchResultCheckClick(result) {
|
|
828
|
-
if (this.disabled) {
|
|
829
|
-
return;
|
|
830
|
-
}
|
|
831
|
-
this.toggleMultiplePath(result.pathData);
|
|
832
|
-
},
|
|
833
|
-
|
|
834
|
-
/** 多选切换单条路径选中态 */
|
|
835
|
-
toggleMultiplePath(pathData) {
|
|
836
|
-
const values = Array.isArray(this.val) ? [...this.val] : [];
|
|
837
|
-
const key = this.getPathKey(pathData);
|
|
838
|
-
const exists = values.some(value => this.getPathKey(value) === key);
|
|
839
|
-
this.val = exists
|
|
840
|
-
? values.filter(value => this.getPathKey(value) !== key)
|
|
841
|
-
: [...values, pathData];
|
|
842
|
-
},
|
|
843
|
-
|
|
844
|
-
/** 搜索词变化:平铺模式下无需维护 expandPath */
|
|
845
|
-
handleSearchKeywordChange() {
|
|
846
|
-
const keyword = this.searchKeyword.trim();
|
|
847
|
-
if (!keyword) {
|
|
848
|
-
if (!this.multiple) {
|
|
849
|
-
this.syncExpandPath();
|
|
850
|
-
}
|
|
851
|
-
return;
|
|
852
|
-
}
|
|
853
|
-
this.expandPath = [];
|
|
854
|
-
this.columnScrollTops = {};
|
|
855
|
-
},
|
|
856
|
-
|
|
857
|
-
/** 供外部调用:聚焦搜索框 */
|
|
858
|
-
focusSearchInput() {
|
|
859
|
-
this.$nextTick(() => { this.$refs.searchInputRef?.focus?.(); });
|
|
860
|
-
},
|
|
861
|
-
|
|
862
|
-
/** 供外部调用:清空搜索 */
|
|
863
|
-
clearSearch() {
|
|
864
|
-
this.searchKeyword = '';
|
|
865
|
-
},
|
|
866
|
-
|
|
867
|
-
/** 单选时根据 modelValue 还原 expandPath */
|
|
868
|
-
syncExpandPath() {
|
|
869
|
-
if (this.multiple) {
|
|
870
|
-
return;
|
|
871
|
-
}
|
|
872
|
-
this.expandPath = isEmpty(this.modelValue) ? [] : [...this.normalizePath(this.modelValue)];
|
|
873
|
-
},
|
|
874
|
-
|
|
875
|
-
// --- 数据加载:远程 tree / lazy、选项规范化与缓存 ---
|
|
876
|
-
|
|
877
|
-
/** 规范化接口返回项,兼容 id → value 字段 */
|
|
878
|
-
normalizeOptions(list) {
|
|
879
|
-
const valueKey = this.mergedOptionProps.value;
|
|
880
|
-
return (list || []).map(item => {
|
|
881
|
-
const opt = { ...item };
|
|
882
|
-
if (!opt[valueKey] && item.id) {
|
|
883
|
-
opt[valueKey] = item.id;
|
|
884
|
-
}
|
|
885
|
-
return opt;
|
|
886
|
-
});
|
|
887
|
-
},
|
|
888
|
-
|
|
889
|
-
/** 写入 lazy 子级缓存(markRaw 避免 Vue 深度响应式化大数组) */
|
|
890
|
-
cacheLazyChildren(item, cacheKey, list) {
|
|
891
|
-
const cachedList = markRaw(list || []);
|
|
892
|
-
this.lazyChildrenMap = { ...this.lazyChildrenMap, [cacheKey]: cachedList };
|
|
893
|
-
this.lazyLoadedKeys = { ...this.lazyLoadedKeys, [cacheKey]: true };
|
|
894
|
-
if (item) {
|
|
895
|
-
item[this.mergedOptionProps.children] = cachedList;
|
|
896
|
-
}
|
|
897
|
-
},
|
|
898
|
-
|
|
899
|
-
/** 重置 lazy 缓存与列滚动状态 */
|
|
900
|
-
resetLazyState() {
|
|
901
|
-
this.lazyChildrenMap = {};
|
|
902
|
-
this.lazyLoadedKeys = {};
|
|
903
|
-
this.columnScrollTops = {};
|
|
904
|
-
},
|
|
905
|
-
|
|
906
|
-
/** 根据 remoteMode 初始化远程数据(tree 拉整树 / lazy 拉根级) */
|
|
907
|
-
initRemote() {
|
|
908
|
-
if (!this.pathUrl || !this.postFunc) {
|
|
909
|
-
return;
|
|
910
|
-
}
|
|
911
|
-
if (this.isRemoteTree) {
|
|
912
|
-
this.remoteOptions = [];
|
|
913
|
-
this.loadTreeOptions();
|
|
914
|
-
return;
|
|
915
|
-
}
|
|
916
|
-
if (this.isRemoteLazy) {
|
|
917
|
-
this.resetLazyState();
|
|
918
|
-
this.loadLazyChildren(null, -1);
|
|
919
|
-
}
|
|
920
|
-
},
|
|
921
|
-
|
|
922
|
-
/** 远程 tree 模式:一次请求完整树 */
|
|
923
|
-
loadTreeOptions() {
|
|
924
|
-
this.treeLoading = true;
|
|
925
|
-
this.postFunc(this.pathUrl, cloneDeep(this.postData)).then(res => {
|
|
926
|
-
this.treeLoading = false;
|
|
927
|
-
if (!res.code && res.data) {
|
|
928
|
-
const data = res.data;
|
|
929
|
-
this.remoteOptions = Array.isArray(data)
|
|
930
|
-
? this.normalizeOptions(data)
|
|
931
|
-
: this.normalizeOptions(data?.results || []);
|
|
932
|
-
} else {
|
|
933
|
-
this.remoteOptions = [];
|
|
934
|
-
}
|
|
935
|
-
this.$emit('loadOver')
|
|
936
|
-
}).catch(() => {
|
|
937
|
-
this.treeLoading = false;
|
|
938
|
-
this.remoteOptions = [];
|
|
939
|
-
});
|
|
940
|
-
},
|
|
941
|
-
|
|
942
|
-
/**
|
|
943
|
-
* 远程 lazy 模式:按级加载子节点
|
|
944
|
-
* @param {Object|null} item 父节点,null 且 level=-1 表示加载根级
|
|
945
|
-
* @param {number} level 父节点所在列层级
|
|
946
|
-
*/
|
|
947
|
-
loadLazyChildren(item, level) {
|
|
948
|
-
const cacheKey = this.getLazyCacheKey(item, level);
|
|
949
|
-
if (this.lazyLoadedKeys[cacheKey]) {
|
|
950
|
-
return Promise.resolve(this.lazyChildrenMap[cacheKey] || []);
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
const loadingKey = this.getItemLoadingKey(item, level);
|
|
954
|
-
this.loadingKeys = { ...this.loadingKeys, [loadingKey]: true };
|
|
955
|
-
|
|
956
|
-
const finish = (rawList) => {
|
|
957
|
-
this.loadingKeys = { ...this.loadingKeys, [loadingKey]: false };
|
|
958
|
-
const list = this.normalizeOptions(rawList);
|
|
959
|
-
this.cacheLazyChildren(item, cacheKey, list);
|
|
960
|
-
return list;
|
|
961
|
-
};
|
|
962
|
-
|
|
963
|
-
if (!this.postFunc || !this.pathUrl) {
|
|
964
|
-
this.loadingKeys = { ...this.loadingKeys, [loadingKey]: false };
|
|
965
|
-
return Promise.resolve([]);
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
const requestPostData = (() => {
|
|
969
|
-
const requestLevel = this.lazyLevelStart + level + 1;
|
|
970
|
-
const extra = cloneDeep(this.postData);
|
|
971
|
-
if (!item) {
|
|
972
|
-
return { ...extra, [this.lazyLevelKey]: requestLevel };
|
|
973
|
-
}
|
|
974
|
-
return { ...omit(item, [this.mergedOptionProps.children]), ...extra, [this.lazyLevelKey]: requestLevel };
|
|
975
|
-
})();
|
|
976
|
-
|
|
977
|
-
return this.postFunc(this.pathUrl, requestPostData).then(res => {
|
|
978
|
-
this.loadingKeys = { ...this.loadingKeys, [loadingKey]: false };
|
|
979
|
-
const list = (!res.code && res.data)
|
|
980
|
-
? this.normalizeOptions(Array.isArray(res.data) ? res.data : (res.data?.results || []))
|
|
981
|
-
: [];
|
|
982
|
-
this.cacheLazyChildren(item, cacheKey, list);
|
|
983
|
-
return list;
|
|
984
|
-
}).catch(() => {
|
|
985
|
-
this.loadingKeys = { ...this.loadingKeys, [loadingKey]: false };
|
|
986
|
-
this.cacheLazyChildren(item, cacheKey, []);
|
|
987
|
-
return [];
|
|
988
|
-
});
|
|
989
|
-
},
|
|
990
|
-
|
|
991
|
-
// --- 交互入口:点击行 / 展开箭头 / 多选勾选 ---
|
|
992
|
-
|
|
993
|
-
/** 点击展开箭头:更新 expandPath 并在 lazy 模式下触发子级加载 */
|
|
994
|
-
handleExpand(item, level) {
|
|
995
|
-
if (this.disabled || item.disabled || !this.showExpandSuffix(item, level)) {
|
|
996
|
-
return;
|
|
997
|
-
}
|
|
998
|
-
this.expandPath = this.buildPathValues(item, level);
|
|
999
|
-
this.columnScrollTops = { ...this.columnScrollTops, [level + 1]: 0 };
|
|
1000
|
-
if (this.isLazyMode && !this.isItemLeaf(item) && !this.isLazyLoaded(item, level)) {
|
|
1001
|
-
this.loadLazyChildren(item, level);
|
|
1002
|
-
}
|
|
1003
|
-
},
|
|
1004
|
-
|
|
1005
|
-
/**
|
|
1006
|
-
* 点击行:多选仅展开;单选有子级则展开,叶子则选中
|
|
1007
|
-
*/
|
|
1008
|
-
handleItemClick(item, level) {
|
|
1009
|
-
if (this.disabled || item.disabled) {
|
|
1010
|
-
return;
|
|
1011
|
-
}
|
|
1012
|
-
if (this.multiple) {
|
|
1013
|
-
if (this.showExpandSuffix(item, level)) {
|
|
1014
|
-
this.handleExpand(item, level);
|
|
1015
|
-
}
|
|
1016
|
-
return;
|
|
1017
|
-
}
|
|
1018
|
-
if (this.hasChildren(item, level)) {
|
|
1019
|
-
this.handleExpand(item, level);
|
|
1020
|
-
return;
|
|
1021
|
-
}
|
|
1022
|
-
if (!this.isItemSelectable(item, level)) {
|
|
1023
|
-
return;
|
|
1024
|
-
}
|
|
1025
|
-
this.expandPath = this.buildPathValues(item, level);
|
|
1026
|
-
this.val = this.buildPathData(item, level);
|
|
1027
|
-
},
|
|
1028
|
-
|
|
1029
|
-
/** 多选勾选框点击(阻止冒泡,避免触发行点击展开) */
|
|
1030
|
-
handleCheckClick(item, level) {
|
|
1031
|
-
if (this.disabled || item.disabled || !this.isItemSelectable(item, level)) {
|
|
1032
|
-
return;
|
|
1033
|
-
}
|
|
1034
|
-
this.handleSelectMultiple(item, level);
|
|
1035
|
-
},
|
|
1036
|
-
|
|
1037
|
-
/** 多选切换:支持 checkToLast 批量选中/取消叶子路径 */
|
|
1038
|
-
handleSelectMultiple(item, level) {
|
|
1039
|
-
const useLeafPaths = this.checkToLastModel && this.hasChildren(item, level);
|
|
1040
|
-
const targetPaths = useLeafPaths
|
|
1041
|
-
? this.getAllLeafPathsForItem(item, level)
|
|
1042
|
-
: [this.buildPathData(item, level)];
|
|
1043
|
-
const values = Array.isArray(this.val) ? [...this.val] : [];
|
|
1044
|
-
const selectedKeys = new Set(values.map(value => this.getPathKey(value)));
|
|
1045
|
-
const allSelected = targetPaths.length > 0
|
|
1046
|
-
&& targetPaths.every(path => selectedKeys.has(this.getPathKey(path)));
|
|
1047
|
-
|
|
1048
|
-
if (allSelected) {
|
|
1049
|
-
const parentPathValues = this.buildPathValues(item, level);
|
|
1050
|
-
this.val = values.filter(value => !this.isPathUnderPrefix(value, parentPathValues));
|
|
1051
|
-
return;
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
targetPaths.forEach(path => {
|
|
1055
|
-
const key = this.getPathKey(path);
|
|
1056
|
-
if (!selectedKeys.has(key)) {
|
|
1057
|
-
selectedKeys.add(key);
|
|
1058
|
-
values.push(path);
|
|
1059
|
-
}
|
|
1060
|
-
});
|
|
1061
|
-
this.val = values;
|
|
1062
|
-
},
|
|
1063
|
-
|
|
1064
|
-
/** 「选到最后一级」开关切换时清空选中值 */
|
|
1065
|
-
handleCheckToLastChange(value) {
|
|
1066
|
-
this.val = [];
|
|
1067
|
-
this.$emit('changeCheckToLast', value);
|
|
1068
|
-
},
|
|
1069
|
-
|
|
1070
|
-
/** 向外同步 v-model;单选选中有效值时触发 close 收起面板 */
|
|
1071
|
-
emitValue(value) {
|
|
1072
|
-
if (!this.multiple && !isEmpty(value)) {
|
|
1073
|
-
this.$emit('close');
|
|
1074
|
-
}
|
|
1075
|
-
this.$emit('update:modelValue', value);
|
|
1076
|
-
this.$emit('change', value);
|
|
1077
|
-
},
|
|
1078
|
-
|
|
1079
|
-
/** labelInValue=false 时,按 value 路径从已加载选项树解析各级展示文案 */
|
|
1080
|
-
getPathDisplayLabels(pathValues) {
|
|
1081
|
-
if (!Array.isArray(pathValues) || !pathValues.length) {
|
|
1082
|
-
return [];
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
const labels = [];
|
|
1086
|
-
let currentOptions = this.rootOptions || [];
|
|
1087
|
-
|
|
1088
|
-
for (const value of pathValues) {
|
|
1089
|
-
const node = currentOptions.find(item => this.getItemValue(item) === value);
|
|
1090
|
-
if (!node) {
|
|
1091
|
-
labels.push(String(value ?? ''));
|
|
1092
|
-
break;
|
|
1093
|
-
}
|
|
1094
|
-
const label = this.getItemLabel(node);
|
|
1095
|
-
labels.push(label != null && label !== '' ? label : String(value ?? ''));
|
|
1096
|
-
currentOptions = this.getChildren(node);
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
return labels;
|
|
1100
|
-
},
|
|
1101
|
-
|
|
1102
|
-
/** 将路径格式化为展示文本,供 BnCascader 触发器使用 */
|
|
1103
|
-
getPathDisplayText(path, { separator = ' / ', showAllLevels = true } = {}) {
|
|
1104
|
-
const pathValues = this.normalizePath(path);
|
|
1105
|
-
return joinPathLabels(this.getPathDisplayLabels(pathValues), { separator, showAllLevels });
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
};
|
|
1109
|
-
</script>
|
|
1110
|
-
|
|
1111
|
-
<style scoped lang="scss">
|
|
1112
|
-
@import '../font/iconfont.css';
|
|
1113
|
-
|
|
1114
|
-
.BnCascaderPanel {
|
|
1115
|
-
display: inline-flex;
|
|
1116
|
-
flex-direction: column;
|
|
1117
|
-
background: #fff;
|
|
1118
|
-
border: 1px solid #e4e7ed;
|
|
1119
|
-
border-radius: 4px;
|
|
1120
|
-
overflow: hidden;
|
|
1121
|
-
|
|
1122
|
-
&--disabled {
|
|
1123
|
-
cursor: not-allowed;
|
|
1124
|
-
opacity: 0.7;
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
.BnCascaderPanel-toolbar {
|
|
1129
|
-
display: flex;
|
|
1130
|
-
align-items: center;
|
|
1131
|
-
gap: 8px;
|
|
1132
|
-
padding: 8px 12px;
|
|
1133
|
-
border-bottom: 1px solid #e4e7ed;
|
|
1134
|
-
background: #fafafa;
|
|
1135
|
-
|
|
1136
|
-
> :last-child:not(.BnCascaderPanel-search) {
|
|
1137
|
-
margin-left: auto;
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
.BnCascaderPanel-toolbar-label {
|
|
1142
|
-
font-size: 13px;
|
|
1143
|
-
color: #606266;
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
.BnCascaderPanel-search {
|
|
1147
|
-
flex: 1;
|
|
1148
|
-
min-width: 120px;
|
|
1149
|
-
max-width: 220px;
|
|
1150
|
-
|
|
1151
|
-
:deep(.el-input__wrapper) {
|
|
1152
|
-
box-shadow: 0 0 0 1px #dcdfe6 inset;
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
.BnCascaderPanel-search-results {
|
|
1157
|
-
min-width: 220px;
|
|
1158
|
-
max-width: 400px;
|
|
1159
|
-
width: 100%;
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
.BnCascaderPanel-search-result-item {
|
|
1163
|
-
padding-right: 16px;
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
.BnCascaderPanel-columns {
|
|
1167
|
-
display: inline-flex;
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
.BnCascaderPanel-column {
|
|
1171
|
-
width: 220px;
|
|
1172
|
-
flex-shrink: 0;
|
|
1173
|
-
border-right: 1px solid #e4e7ed;
|
|
1174
|
-
|
|
1175
|
-
&:last-child {
|
|
1176
|
-
border-right: none;
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
.BnCascaderPanel-column-inner {
|
|
1181
|
-
overflow: hidden;
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
.BnCascaderPanel-item {
|
|
1185
|
-
display: flex;
|
|
1186
|
-
align-items: center;
|
|
1187
|
-
min-height: 34px;
|
|
1188
|
-
padding: 0 12px 0 8px;
|
|
1189
|
-
cursor: pointer;
|
|
1190
|
-
overflow: hidden;
|
|
1191
|
-
|
|
1192
|
-
&:hover {
|
|
1193
|
-
background: #f5f7fa;
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
&--active {
|
|
1197
|
-
background: #ecf5ff;
|
|
1198
|
-
|
|
1199
|
-
.BnCascaderPanel-item-label,
|
|
1200
|
-
.BnCascaderPanel-item-arrow {
|
|
1201
|
-
color: #409eff;
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
.BnCascaderPanel-item-label {
|
|
1205
|
-
font-weight: 500;
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
&--disabled {
|
|
1210
|
-
cursor: not-allowed;
|
|
1211
|
-
opacity: 0.6;
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
.BnCascaderPanel-item-check {
|
|
1216
|
-
flex-shrink: 0;
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
.BnCascaderPanel-check {
|
|
1220
|
-
box-sizing: content-box !important;
|
|
1221
|
-
position: relative;
|
|
1222
|
-
display: block;
|
|
1223
|
-
width: 14px;
|
|
1224
|
-
height: 14px;
|
|
1225
|
-
background-color: #fff;
|
|
1226
|
-
border: 1px solid #d9d9d9;
|
|
1227
|
-
border-radius: 2px;
|
|
1228
|
-
transition: border-color 0.3s, background-color 0.3s;
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
.BnCascaderPanel-check-inner {
|
|
1232
|
-
position: relative;
|
|
1233
|
-
display: block;
|
|
1234
|
-
width: 14px;
|
|
1235
|
-
height: 14px;
|
|
1236
|
-
background-color: #fff;
|
|
1237
|
-
border-radius: 2px;
|
|
1238
|
-
transition: background-color 0.3s;
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
.BnCascaderPanel-check-inner::after {
|
|
1242
|
-
position: absolute;
|
|
1243
|
-
top: 50%;
|
|
1244
|
-
left: calc(50% - 3px);
|
|
1245
|
-
width: 5px;
|
|
1246
|
-
height: 8px;
|
|
1247
|
-
border: 2px solid #fff;
|
|
1248
|
-
border-top: 0;
|
|
1249
|
-
border-left: 0;
|
|
1250
|
-
transform: rotate(45deg) scale(0) translate(-50%, -50%);
|
|
1251
|
-
opacity: 0;
|
|
1252
|
-
transition: all 0.1s cubic-bezier(0.71, -0.46, 0.88, 0.6), opacity 0.1s;
|
|
1253
|
-
content: "";
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
.BnCascaderPanel-check--checked {
|
|
1257
|
-
border-color: #1890ff;
|
|
1258
|
-
background-color: #1890ff;
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
|
-
.BnCascaderPanel-check--checked .BnCascaderPanel-check-inner {
|
|
1262
|
-
background-color: #1890ff;
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
.BnCascaderPanel-check--checked .BnCascaderPanel-check-inner::after {
|
|
1266
|
-
transform: rotate(45deg) scale(1) translate(-50%, -50%);
|
|
1267
|
-
opacity: 1;
|
|
1268
|
-
transition: all 0.2s cubic-bezier(0.12, 0.4, 0.29, 1.46) 0.1s;
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
.BnCascaderPanel-check--disabled {
|
|
1272
|
-
cursor: not-allowed;
|
|
1273
|
-
background-color: #f0f0f0 !important;
|
|
1274
|
-
border-color: #ccc !important;
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
.BnCascaderPanel-check--disabled .BnCascaderPanel-check-inner {
|
|
1278
|
-
background-color: #f0f0f0 !important;
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
.BnCascaderPanel-check--disabled.BnCascaderPanel-check--checked .BnCascaderPanel-check-inner::after {
|
|
1282
|
-
border-color: #ccc !important;
|
|
1283
|
-
}
|
|
1284
|
-
|
|
1285
|
-
.BnCascaderPanel-item-label {
|
|
1286
|
-
flex: 1;
|
|
1287
|
-
min-width: 0;
|
|
1288
|
-
padding: 0 8px;
|
|
1289
|
-
font-size: 14px;
|
|
1290
|
-
color: #606266;
|
|
1291
|
-
line-height: 34px;
|
|
1292
|
-
white-space: nowrap;
|
|
1293
|
-
overflow: hidden;
|
|
1294
|
-
text-overflow: ellipsis;
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
.BnCascaderPanel-item-suffix {
|
|
1298
|
-
flex-shrink: 0;
|
|
1299
|
-
width: 14px;
|
|
1300
|
-
height: 14px;
|
|
1301
|
-
margin-left: 4px;
|
|
1302
|
-
display: inline-flex;
|
|
1303
|
-
align-items: center;
|
|
1304
|
-
justify-content: center;
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
.BnCascaderPanel-item-loading {
|
|
1308
|
-
width: 12px;
|
|
1309
|
-
height: 12px;
|
|
1310
|
-
border: 2px solid #dcdfe6;
|
|
1311
|
-
border-top-color: #409eff;
|
|
1312
|
-
border-radius: 50%;
|
|
1313
|
-
animation: BnCascaderPanel-spin 0.8s linear infinite;
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
@keyframes BnCascaderPanel-spin {
|
|
1317
|
-
to { transform: rotate(360deg); }
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
.BnCascaderPanel-item-arrow {
|
|
1321
|
-
font-size: 12px;
|
|
1322
|
-
color: #c0c4cc;
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
.BnCascaderPanel-column-status {
|
|
1326
|
-
padding: 20px 0;
|
|
1327
|
-
text-align: center;
|
|
1328
|
-
font-size: 12px;
|
|
1329
|
-
color: #909399;
|
|
1330
|
-
}
|
|
1331
|
-
</style>
|