@bytenew/bn-bus-ui 1.1.461 → 1.1.463
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/install.js +134 -0
- package/package.json +20 -4
- package/packages/BnAddress/index.vue +141 -0
- package/packages/BnAddressParse/index.vue +75 -0
- package/packages/BnAdvancedFilter/BnFilterView.vue +145 -0
- package/packages/BnAdvancedFilter/index.vue +345 -0
- package/packages/BnAdvancedFilter/util.js +171 -0
- package/packages/BnCascader/BnCascader.vue +543 -0
- package/packages/BnCascader/BnCascaderPanel.vue +1331 -0
- package/packages/BnCascader/cascaderDisplay.js +102 -0
- package/packages/BnCheck/BnCheck.vue +253 -0
- package/packages/BnColumnConfig/columnConfigDialog.vue +134 -0
- package/packages/BnColumnConfig/component/childrenFormSetting/assocTable.vue +143 -0
- package/packages/BnColumnConfig/component/childrenFormSetting/assocTableSearch.vue +314 -0
- package/packages/BnColumnConfig/component/childrenFormSetting/index.vue +243 -0
- package/packages/BnColumnConfig/component/defaultSet/defaultSet.vue +114 -0
- package/packages/BnColumnConfig/component/defaultSet/stringDefault.vue +210 -0
- package/packages/BnColumnConfig/component/funcExprDialog/funcExprDialog.vue +126 -0
- package/packages/BnColumnConfig/component/packageFilter/index.vue +141 -0
- package/packages/BnColumnConfig/component/paraExplainSet/index.vue +156 -0
- package/packages/BnColumnConfig/component/setOption/setOption.vue +321 -0
- package/packages/BnColumnConfig/component/setRelationOption/ConditionList.vue +277 -0
- package/packages/BnColumnConfig/component/setRelationOption/LevelHeader.vue +144 -0
- package/packages/BnColumnConfig/component/setRelationOption/setRelationOption.vue +642 -0
- package/packages/BnColumnConfig/data.js +129 -0
- package/packages/BnColumnConfig/index.scss +23 -0
- package/packages/BnColumnConfig/index.vue +309 -0
- package/packages/BnCondVal/BnCondVal.vue +112 -0
- package/packages/BnCondVal/itemVal/dateRegVal.vue +39 -0
- package/packages/BnCondVal/itemVal/dateVal.vue +39 -0
- package/packages/BnCondVal/itemVal/numRegVal.vue +67 -0
- package/packages/BnCondVal/itemVal/numVal.vue +44 -0
- package/packages/BnCondVal/itemVal/relationVal.vue +73 -0
- package/packages/BnCondVal/itemVal/selectColumnVal.vue +58 -0
- package/packages/BnCondVal/itemVal/selectVal.vue +59 -0
- package/packages/BnCondVal/itemVal/stringVal.vue +156 -0
- package/packages/BnCondition/index.vue +551 -0
- package/packages/BnCondition/util.js +232 -0
- package/packages/BnDatePicker/BnDatePicker.vue +194 -0
- package/packages/BnDrop/BnDrop.vue +917 -0
- package/packages/BnEmptyTip/BnEmptyTip.vue +107 -0
- package/packages/BnExportOrder/BnExportOrder.vue +241 -0
- package/packages/BnExportOrder/validatorCode.vue +94 -0
- package/packages/BnFileView/BnFileView.vue +271 -0
- package/packages/BnFileView/FileDetail.vue +125 -0
- package/packages/BnFileView/videoPreview.vue +70 -0
- package/packages/BnFilterOption/BnFilterOption.vue +160 -0
- package/packages/BnFunExpr/BnFunExpr.vue +608 -0
- package/packages/BnImportOrder/BnImportOrder.vue +323 -0
- package/packages/BnImportTemplate/BnImportTemplate.vue +866 -0
- 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 +198 -0
- package/packages/BnImportTemplate/components/TemplateEditDialog.vue +340 -0
- package/packages/BnImportTemplate/components/TemplateFieldsConfig.vue +401 -0
- package/packages/BnImportTemplate/components/TemplateListDialog.vue +297 -0
- package/packages/BnInpNum/BnInpNum.vue +165 -0
- package/packages/BnMenus/BnMenus.vue +502 -0
- package/packages/BnMenus/MenuItem.vue +321 -0
- package/packages/BnOrderCard/BnOrderCard.vue +389 -0
- package/packages/BnOrderCard/orderRemark.vue +178 -0
- package/packages/BnOrderCard/util.js +94 -0
- package/packages/BnOrderDetail/BnOrderDetail.vue +338 -0
- package/packages/BnOrderDetail/filterLogByUser.vue +108 -0
- package/packages/BnOrderDetail/orderLogs.vue +53 -0
- package/packages/BnOrderDetail/showSubForm/index.vue +41 -0
- package/packages/BnOrderDetail/showSubForm/showChildTable.vue +61 -0
- package/packages/BnOrderDetail/showSubForm/showTimeLine.vue +131 -0
- package/packages/BnOrderDetail/showSubForm/tableCellCopy.vue +84 -0
- package/packages/BnOrderShare/index.vue +378 -0
- package/packages/BnOrderShare/shareUrlInp.vue +54 -0
- package/packages/BnRadio/BnRadio.vue +181 -0
- package/packages/BnRelation/BnRelation.vue +120 -0
- package/packages/BnSelect/BnSelect.vue +485 -0
- package/packages/BnSmartForm/BnBatchForm.vue +334 -0
- package/packages/BnSmartForm/BnBatchFormDialog.vue +423 -0
- package/packages/BnSmartForm/BnSmartElf/elfLog/index.vue +57 -0
- package/packages/BnSmartForm/BnSmartElf/index.vue +95 -0
- package/packages/BnSmartForm/BnSmartFormPsychic.js +629 -0
- package/packages/BnSmartForm/BnSmartFormUtil.js +524 -0
- package/packages/BnSmartForm/addressForm/index.vue +73 -0
- package/packages/BnSmartForm/checkboxForm/index.vue +99 -0
- package/packages/BnSmartForm/childrenForm/childFormFile.vue +237 -0
- package/packages/BnSmartForm/childrenForm/childrenFormBody.vue +260 -0
- package/packages/BnSmartForm/childrenForm/index.vue +110 -0
- package/packages/BnSmartForm/childrenForm/searchInventory.vue +115 -0
- package/packages/BnSmartForm/chooseAssocTable/chooseAssocTable.vue +451 -0
- package/packages/BnSmartForm/chooseAssocTable/chooseChildGood.vue +229 -0
- package/packages/BnSmartForm/chooseAssocTable/tableCellCopy.vue +84 -0
- package/packages/BnSmartForm/chooseChildOrder/chooseChildOrder.vue +158 -0
- package/packages/BnSmartForm/chooseIPaasData/chooseIPaasData.vue +307 -0
- package/packages/BnSmartForm/chooseIPaasData/tableCellCopy.vue +84 -0
- package/packages/BnSmartForm/choosePackage/choosePackage.vue +361 -0
- package/packages/BnSmartForm/dateDualForm/index.vue +37 -0
- package/packages/BnSmartForm/dateForm/index.vue +38 -0
- package/packages/BnSmartForm/fileForm/fileFormData.js +50 -0
- package/packages/BnSmartForm/fileForm/index.vue +240 -0
- package/packages/BnSmartForm/formItemFooter/formItemFooter.vue +38 -0
- package/packages/BnSmartForm/formItemTop/formItemTop.vue +42 -0
- package/packages/BnSmartForm/index.vue +1201 -0
- package/packages/BnSmartForm/numberForm/index.vue +63 -0
- package/packages/BnSmartForm/radioForm/index.vue +72 -0
- package/packages/BnSmartForm/receiveForm/index.vue +112 -0
- package/packages/BnSmartForm/relationForm/index.vue +82 -0
- package/packages/BnSmartForm/scoreForm/index.vue +39 -0
- package/packages/BnSmartForm/selectForm/index.vue +55 -0
- package/packages/BnSmartForm/showValForm/index.vue +42 -0
- package/packages/BnSmartForm/showValForm/showValFormTable.vue +104 -0
- package/packages/BnSmartForm/smartFormsCommon.scss +43 -0
- package/packages/BnSmartForm/stringForm/index.vue +170 -0
- package/packages/BnVirtPopover/index.vue +319 -0
- package/packages/BnWebLog/BnWebLog.vue +140 -0
- package/packages/BnWorkSettingDrop/advancedOptions.js +112 -0
- package/packages/BnWorkSettingDrop/benBenAIUtil.js +89 -0
- package/packages/BnWorkSettingDrop/index.vue +292 -0
- package/packages/font/iconfont.css +243 -0
- 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 +645 -0
- package/utils/addressParse.js +735 -0
- package/utils/config.js +68 -0
- package/utils/index.js +1171 -0
- package/dist/assocTableSearch-C14i_zJO.js +0 -349
- package/dist/assocTableSearch.css +0 -1
- package/dist/bn-bus-ui.es.js +0 -65
- package/dist/bn-bus-ui.umd.js +0 -49
- package/dist/chooseIPaasData-Cl_5qkFA.js +0 -391
- package/dist/chooseIPaasData.css +0 -1
- package/dist/defaultSet-I4xpf8a4.js +0 -297
- package/dist/defaultSet.css +0 -1
- package/dist/fileFormData-D_Q1vylE.js +0 -53
- package/dist/formItemFooter-CTA7rDtV.js +0 -108
- package/dist/formItemFooter.css +0 -1
- package/dist/funcExprDialog-Cm5BiRM3.js +0 -139
- package/dist/funcExprDialog.css +0 -1
- package/dist/index-B6b4uj8v.js +0 -51
- package/dist/index-BWdkDTbz.js +0 -109
- package/dist/index-BdZ1zOap.js +0 -89
- package/dist/index-BdzNjjMC.js +0 -64
- package/dist/index-BfpZqab2.js +0 -66
- package/dist/index-BmkiB4hr.js +0 -66
- package/dist/index-Br1sqCoo.js +0 -58
- package/dist/index-C9qrl-yc.js +0 -183
- package/dist/index-CXFJXCn5.js +0 -746
- package/dist/index-DF114jZq.js +0 -135
- package/dist/index-DFPfZ_Il.js +0 -178
- package/dist/index-DSEtu7TN.js +0 -44
- package/dist/index-Did0UUWV.js +0 -221
- package/dist/index-DrPj-tmI.js +0 -45
- package/dist/index-DzDf9s2W.js +0 -89
- package/dist/index-LnjudNEI.js +0 -118
- package/dist/index-ObCfCmDT.js +0 -45
- package/dist/index.css +0 -1
- package/dist/index2.css +0 -1
- package/dist/index3.css +0 -1
- package/dist/index4.css +0 -1
- package/dist/index5.css +0 -1
- package/dist/index6.css +0 -1
- package/dist/index7.css +0 -1
- package/dist/install-C-scqkds.js +0 -23103
- package/dist/install.css +0 -1
- package/dist/setOption-CSnWzOny.js +0 -329
- package/dist/setOption.css +0 -1
- package/dist/setRelationOption-CJcHdfaa.js +0 -5217
- package/dist/setRelationOption.css +0 -1
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-input :placeholder="placeholder" v-model="val" :disabled="disabled" :clearable="clearable"
|
|
3
|
+
:class="[underline ?'bnInput-underline':'']"
|
|
4
|
+
@input="valInput" @change="valChange" @blur="inputBlur" :size="size" :maxlength="maxlength">
|
|
5
|
+
<template #prefix>
|
|
6
|
+
<slot name="prefix"></slot>
|
|
7
|
+
</template>
|
|
8
|
+
<template #suffix>
|
|
9
|
+
<slot></slot>
|
|
10
|
+
</template>
|
|
11
|
+
</el-input>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script>
|
|
15
|
+
import {debounce} from "lodash";
|
|
16
|
+
|
|
17
|
+
export default {
|
|
18
|
+
name: "BnInpNum",
|
|
19
|
+
props: {
|
|
20
|
+
modelValue: [String, Number],
|
|
21
|
+
placeholder: {
|
|
22
|
+
type: String,
|
|
23
|
+
default: ''
|
|
24
|
+
},
|
|
25
|
+
size: String,
|
|
26
|
+
underline: {
|
|
27
|
+
type: Boolean,
|
|
28
|
+
default: false
|
|
29
|
+
},
|
|
30
|
+
disabled: {
|
|
31
|
+
type: Boolean,
|
|
32
|
+
default: false
|
|
33
|
+
},
|
|
34
|
+
clearable: {
|
|
35
|
+
type: Boolean,
|
|
36
|
+
default: true
|
|
37
|
+
},
|
|
38
|
+
max: {
|
|
39
|
+
type: [String, Number],
|
|
40
|
+
},
|
|
41
|
+
min: {
|
|
42
|
+
type: [String, Number],
|
|
43
|
+
},
|
|
44
|
+
maxlength: {
|
|
45
|
+
type: [String, Number],
|
|
46
|
+
},
|
|
47
|
+
precision: {
|
|
48
|
+
type: [String, Number],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
computed: {
|
|
52
|
+
val: {
|
|
53
|
+
get() {
|
|
54
|
+
return this.modelValue;
|
|
55
|
+
},
|
|
56
|
+
set(val) {
|
|
57
|
+
this.$emit("update:modelValue", val);
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
methods: {
|
|
62
|
+
/**
|
|
63
|
+
* input事件 实时变化
|
|
64
|
+
* */
|
|
65
|
+
valInput:debounce(function(val) {
|
|
66
|
+
// 快速处理空值
|
|
67
|
+
if (!val) {
|
|
68
|
+
this.val = '';
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let value = val;
|
|
73
|
+
|
|
74
|
+
// 移除无效字符(一次性处理)
|
|
75
|
+
value = value.replace(/[^0-9.-]/g, '');
|
|
76
|
+
|
|
77
|
+
// 处理负号:只保留开头的第一个负号
|
|
78
|
+
if (value.startsWith('-')) {
|
|
79
|
+
value = '-' + value.slice(1).replace(/-/g, '');
|
|
80
|
+
} else {
|
|
81
|
+
value = value.replace(/-/g, '');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 处理小数点:移除开头的点,保留第一个点
|
|
85
|
+
if (value.startsWith('.')) {
|
|
86
|
+
value = value.slice(1).replace(/\./g, '');
|
|
87
|
+
} else {
|
|
88
|
+
const dotIndex = value.indexOf('.');
|
|
89
|
+
if (dotIndex > 0) {
|
|
90
|
+
const parts = value.split('.');
|
|
91
|
+
value = parts[0] + '.' + parts.slice(1).join('');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 处理精度
|
|
96
|
+
if (value && value !== '-' && this.precision != null && this.precision >= 0) {
|
|
97
|
+
const dotIndex = value.indexOf('.');
|
|
98
|
+
if (this.precision === 0) {
|
|
99
|
+
// 精度为0时,移除小数点及之后的内容
|
|
100
|
+
value = dotIndex > 0 ? value.slice(0, dotIndex) : value;
|
|
101
|
+
} else if (dotIndex > 0) {
|
|
102
|
+
// 限制小数位数
|
|
103
|
+
const decimalPart = value.slice(dotIndex + 1);
|
|
104
|
+
if (decimalPart.length > this.precision) {
|
|
105
|
+
value = value.slice(0, dotIndex + 1 + this.precision);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 实时校验最大值和最小值
|
|
111
|
+
if (value && value !== '-') {
|
|
112
|
+
const numValue = parseFloat(value);
|
|
113
|
+
if (!isNaN(numValue)) {
|
|
114
|
+
// 校验最大值
|
|
115
|
+
if (this.max != null && numValue > parseFloat(this.max)) {
|
|
116
|
+
value = String(this.max);
|
|
117
|
+
}
|
|
118
|
+
// 校验最小值
|
|
119
|
+
if (this.min != null && numValue < parseFloat(this.min)) {
|
|
120
|
+
value = String(this.min);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
this.val = value;
|
|
126
|
+
},200),
|
|
127
|
+
/**
|
|
128
|
+
* change事件上报
|
|
129
|
+
* */
|
|
130
|
+
valChange() {
|
|
131
|
+
// 处理只输入负号的情况
|
|
132
|
+
if (this.val === '-') {
|
|
133
|
+
this.val = '';
|
|
134
|
+
}
|
|
135
|
+
// 最大值最小值校验已在valInput中实时处理,这里保留作为兜底
|
|
136
|
+
if (this.val) {
|
|
137
|
+
const numValue = parseFloat(this.val);
|
|
138
|
+
if (!isNaN(numValue)) {
|
|
139
|
+
if (this.max != null && numValue > parseFloat(this.max)) {
|
|
140
|
+
this.val = String(this.max);
|
|
141
|
+
}
|
|
142
|
+
if (this.min != null && numValue < parseFloat(this.min)) {
|
|
143
|
+
this.val = String(this.min);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
/**
|
|
149
|
+
* blur事件上报
|
|
150
|
+
* */
|
|
151
|
+
inputBlur() {
|
|
152
|
+
this.$emit('blur', this.val);
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
}
|
|
156
|
+
</script>
|
|
157
|
+
<style lang="scss" scoped>
|
|
158
|
+
.bnInput-underline {
|
|
159
|
+
:deep(.el-input__wrapper) {
|
|
160
|
+
box-shadow: none;
|
|
161
|
+
border-bottom: 1px solid #dcdfe6;
|
|
162
|
+
border-radius: 0;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
</style>
|
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@Description: 侧边导航栏组件,纯自研实现,支持多级嵌套、矢量图标及 smart 智能路由模式
|
|
3
|
+
@VERSION: 1.0
|
|
4
|
+
-->
|
|
5
|
+
<template>
|
|
6
|
+
<div class="BnMenu" :class="{ 'BnMenu--collapsed': isCollapsed }">
|
|
7
|
+
<!-- 收起态:隐藏导航内容,仅保留边缘展开按钮 -->
|
|
8
|
+
<div
|
|
9
|
+
v-if="isCollapsed"
|
|
10
|
+
class="BnMenu-edge-open bnUIcon bnUIcon-enlarge"
|
|
11
|
+
title="展开"
|
|
12
|
+
@click="toggleCollapse"
|
|
13
|
+
></div>
|
|
14
|
+
<!-- 展开态:完整导航栏 -->
|
|
15
|
+
<template v-else>
|
|
16
|
+
<!-- Logo / 标题区域,可通过 #menuLogo 插槽自定义 -->
|
|
17
|
+
<div class="BnMenu-logo">
|
|
18
|
+
<div class="BnMenu-logo-main">
|
|
19
|
+
<slot name="menuLogo" :collapsed="isCollapsed">
|
|
20
|
+
<div class="BnMenu-logo-title">{{ title }}</div>
|
|
21
|
+
</slot>
|
|
22
|
+
</div>
|
|
23
|
+
<!-- 收起按钮 -->
|
|
24
|
+
<span
|
|
25
|
+
class="BnMenu-logo-toggle bnUIcon bnUIcon-shrink"
|
|
26
|
+
title="收起"
|
|
27
|
+
@click="toggleCollapse"
|
|
28
|
+
></span>
|
|
29
|
+
</div>
|
|
30
|
+
<!-- 可滚动菜单列表 -->
|
|
31
|
+
<div class="BnMenu-list">
|
|
32
|
+
<el-scrollbar height="100%">
|
|
33
|
+
<div class="BnMenu-body">
|
|
34
|
+
<menu-item
|
|
35
|
+
v-for="(menu, index) in menuList"
|
|
36
|
+
:key="getMenuIndex(menu) || index"
|
|
37
|
+
:menu="menu"
|
|
38
|
+
:level="1"
|
|
39
|
+
:theme-index="index"
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
42
|
+
</el-scrollbar>
|
|
43
|
+
</div>
|
|
44
|
+
</template>
|
|
45
|
+
</div>
|
|
46
|
+
</template>
|
|
47
|
+
<script>
|
|
48
|
+
import MenuItem from './MenuItem.vue'
|
|
49
|
+
import { isEmpty } from 'lodash'
|
|
50
|
+
|
|
51
|
+
/** 一级菜单主题色循环列表,选中高亮按此顺序依次取用,超出后从头循环 */
|
|
52
|
+
const THEME_COLORS = [
|
|
53
|
+
'#1890ff', // 蓝
|
|
54
|
+
'#4f46e5', // 靛紫
|
|
55
|
+
'#d97706', // 琥珀
|
|
56
|
+
'#059669', // 祖母绿
|
|
57
|
+
'#db2777', // 玫红
|
|
58
|
+
'#0891b2', // 青
|
|
59
|
+
'#7c3aed', // 紫
|
|
60
|
+
'#e11d48', // 玫瑰红
|
|
61
|
+
'#009966' // 玫瑰红
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
export default {
|
|
65
|
+
name: "BnMenus",
|
|
66
|
+
components: {
|
|
67
|
+
MenuItem
|
|
68
|
+
},
|
|
69
|
+
/**
|
|
70
|
+
* 向子组件 MenuItem 注入当前实例,供其调用菜单状态与方法
|
|
71
|
+
*/
|
|
72
|
+
provide() {
|
|
73
|
+
return {
|
|
74
|
+
bnMenus: this
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
props: {
|
|
78
|
+
// Logo 区域标题文字,使用 #menuLogo 插槽后失效
|
|
79
|
+
title: String,
|
|
80
|
+
// 智能模式:自动高亮路由、自动跳转、自动缓存、根路径自动重定向首菜单
|
|
81
|
+
smart: Boolean,
|
|
82
|
+
// 智能模式下的 localStorage 缓存主键,用于刷新后恢复上次路由
|
|
83
|
+
cacheKey: String,
|
|
84
|
+
// 导航菜单数据,通过 children 字段实现子菜单嵌套;每项可配置 icon、meta.title 等
|
|
85
|
+
menuList: {
|
|
86
|
+
default() {
|
|
87
|
+
return []
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
// 默认展开的子菜单 name 数组,如 ['company', 'actionEngine']
|
|
91
|
+
defaultOpens: Array,
|
|
92
|
+
// 当前选中项,支持 v-model:activeNav;可为字符串(activeKey 值)或对象(完整菜单项)
|
|
93
|
+
activeNav: {},
|
|
94
|
+
// 匹配选中项的字段名,默认按 name 匹配
|
|
95
|
+
activeKey: { default: 'name' },
|
|
96
|
+
// 是否收起导航栏,支持 v-model:collapsed;收起后宽度为 0,展开按钮绝对定位悬浮在边缘
|
|
97
|
+
collapsed: { type: Boolean, default: undefined },
|
|
98
|
+
// 禁止所有子菜单展开/收起,默认全部展开且不可折叠
|
|
99
|
+
disableFold: { type: Boolean, default: false }
|
|
100
|
+
},
|
|
101
|
+
emits: ['update:activeNav', 'update:collapsed', 'collapse-change', 'change'],
|
|
102
|
+
data() {
|
|
103
|
+
return {
|
|
104
|
+
// 未受控时的内部收起状态
|
|
105
|
+
innerCollapsed: false,
|
|
106
|
+
// 当前已展开的子菜单 name 列表
|
|
107
|
+
openedKeys: []
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
watch: {
|
|
111
|
+
/**
|
|
112
|
+
* 监听 defaultOpens,合并到 openedKeys 作为初始展开项
|
|
113
|
+
*/
|
|
114
|
+
defaultOpens: {
|
|
115
|
+
immediate: true,
|
|
116
|
+
handler(opens) {
|
|
117
|
+
if (opens?.length) {
|
|
118
|
+
this.openedKeys = [...new Set([...this.openedKeys, ...opens])]
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
/**
|
|
123
|
+
* 选中项变化时,自动展开其所有父级子菜单,确保高亮项可见
|
|
124
|
+
*/
|
|
125
|
+
activeKeys: {
|
|
126
|
+
immediate: true,
|
|
127
|
+
handler(keys) {
|
|
128
|
+
keys.forEach(key => {
|
|
129
|
+
const path = this.findMenuPath(this.menuList, key)
|
|
130
|
+
if (path?.length > 1) {
|
|
131
|
+
path.slice(0, -1).forEach(name => this.openSubMenu(name))
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
methods: {
|
|
138
|
+
/**
|
|
139
|
+
* 切换导航栏收起 / 展开
|
|
140
|
+
*/
|
|
141
|
+
toggleCollapse() {
|
|
142
|
+
const next = !this.isCollapsed
|
|
143
|
+
if (this.collapsed !== undefined) {
|
|
144
|
+
this.$emit('update:collapsed', next)
|
|
145
|
+
} else {
|
|
146
|
+
this.innerCollapsed = next
|
|
147
|
+
}
|
|
148
|
+
this.$emit('collapse-change', next)
|
|
149
|
+
},
|
|
150
|
+
/**
|
|
151
|
+
* 根据一级菜单索引获取主题色
|
|
152
|
+
* @param {Number} index 一级菜单在 menuList 中的索引
|
|
153
|
+
* @returns {String} 十六进制颜色值
|
|
154
|
+
*/
|
|
155
|
+
getThemeColor(index) {
|
|
156
|
+
return THEME_COLORS[index % THEME_COLORS.length]
|
|
157
|
+
},
|
|
158
|
+
/**
|
|
159
|
+
* 根据一级菜单索引获取选中背景色(主题色 20% 透明度)
|
|
160
|
+
* @param {Number} index 一级菜单在 menuList 中的索引
|
|
161
|
+
* @returns {String} rgba 颜色值
|
|
162
|
+
*/
|
|
163
|
+
getThemeBg(index) {
|
|
164
|
+
const hex = this.getThemeColor(index)
|
|
165
|
+
const r = parseInt(hex.slice(1, 3), 16)
|
|
166
|
+
const g = parseInt(hex.slice(3, 5), 16)
|
|
167
|
+
const b = parseInt(hex.slice(5, 7), 16)
|
|
168
|
+
return `rgba(${r}, ${g}, ${b}, 0.1)`
|
|
169
|
+
},
|
|
170
|
+
/**
|
|
171
|
+
* 生成菜单项唯一标识,用于高亮匹配与 v-for key
|
|
172
|
+
* @param {Object} menu 菜单项对象
|
|
173
|
+
* @returns {String} name 与 id 拼接后的唯一标识
|
|
174
|
+
*/
|
|
175
|
+
getMenuIndex(menu) {
|
|
176
|
+
return `${menu.name}${menu.id || ''}`
|
|
177
|
+
},
|
|
178
|
+
/**
|
|
179
|
+
* 按菜单声明的字段子集匹配路由 params / query(值统一转字符串比较)
|
|
180
|
+
* @param {Object} expected 菜单项上的 params 或 query
|
|
181
|
+
* @param {Object} actual 当前路由上的 params 或 query
|
|
182
|
+
* @returns {Boolean}
|
|
183
|
+
*/
|
|
184
|
+
matchRouteRecord(expected, actual = {}) {
|
|
185
|
+
return Object.keys(expected).every(key => String(actual[key]) === String(expected[key]))
|
|
186
|
+
},
|
|
187
|
+
/**
|
|
188
|
+
* smart 模式下判断菜单项是否与当前路由匹配(支持同 name 多项靠 query / id 区分)
|
|
189
|
+
* @param {Object} menu 菜单项对象
|
|
190
|
+
* @param {Object} route 当前路由
|
|
191
|
+
* @returns {Boolean}
|
|
192
|
+
*/
|
|
193
|
+
isRouteMatch(menu, route) {
|
|
194
|
+
if (menu.name !== route.name) return false
|
|
195
|
+
const routeParams = route.params || {}
|
|
196
|
+
const routeQuery = route.query || {}
|
|
197
|
+
const hasMenuParams = !isEmpty(menu.params)
|
|
198
|
+
const hasMenuQuery = !isEmpty(menu.query)
|
|
199
|
+
const hasMenuId = menu.id != null && menu.id !== ''
|
|
200
|
+
const hasRouteParams = !isEmpty(routeParams)
|
|
201
|
+
const hasRouteQuery = !isEmpty(routeQuery)
|
|
202
|
+
|
|
203
|
+
if (hasMenuParams) {
|
|
204
|
+
if (!this.matchRouteRecord(menu.params, routeParams)) return false
|
|
205
|
+
} else if (hasRouteParams) {
|
|
206
|
+
return false
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (hasMenuQuery) {
|
|
210
|
+
if (!this.matchRouteRecord(menu.query, routeQuery)) return false
|
|
211
|
+
} else if (hasMenuId) {
|
|
212
|
+
const routeId = routeQuery.id ?? routeParams.id
|
|
213
|
+
if (routeId == null) return !hasRouteQuery
|
|
214
|
+
return String(routeId) === String(menu.id)
|
|
215
|
+
} else if (hasRouteQuery || hasRouteParams) {
|
|
216
|
+
return false
|
|
217
|
+
}
|
|
218
|
+
return true
|
|
219
|
+
},
|
|
220
|
+
/**
|
|
221
|
+
* 菜单项是否具备路由区分标识(同 name 多项时不应仅靠 name 高亮)
|
|
222
|
+
* @param {Object} menu 菜单项对象
|
|
223
|
+
* @returns {Boolean}
|
|
224
|
+
*/
|
|
225
|
+
hasRouteIdentity(menu) {
|
|
226
|
+
return menu.id != null && menu.id !== '' || !isEmpty(menu.query) || !isEmpty(menu.params)
|
|
227
|
+
},
|
|
228
|
+
/**
|
|
229
|
+
* 判断菜单项是否处于选中高亮状态
|
|
230
|
+
* @param {Object} menu 菜单项对象
|
|
231
|
+
* @returns {Boolean}
|
|
232
|
+
*/
|
|
233
|
+
isActive(menu) {
|
|
234
|
+
const index = this.getMenuIndex(menu)
|
|
235
|
+
if (this.activeKeys.includes(index)) return true
|
|
236
|
+
const key = menu[this.activeKey]
|
|
237
|
+
// 看板等同 name 多项场景:禁止仅用 name 匹配,避免全部高亮
|
|
238
|
+
return !!(key && !this.hasRouteIdentity(menu) && this.activeKeys.includes(key))
|
|
239
|
+
},
|
|
240
|
+
/**
|
|
241
|
+
* 判断菜单项是否禁止展开/收起
|
|
242
|
+
* 全局 disableFold 为 true 时全部禁止;单项可通过 menu.disableFold 或 menu.meta.disableFold 单独禁止
|
|
243
|
+
* @param {Object} menu 菜单项对象
|
|
244
|
+
* @returns {Boolean}
|
|
245
|
+
*/
|
|
246
|
+
isDisableFold(menu) {
|
|
247
|
+
if (this.disableFold) return true
|
|
248
|
+
return !!(menu.disableFold || menu.meta?.disableFold)
|
|
249
|
+
},
|
|
250
|
+
/**
|
|
251
|
+
* 判断子菜单是否已展开
|
|
252
|
+
* @param {Object} menu 菜单项对象
|
|
253
|
+
* @returns {Boolean}
|
|
254
|
+
*/
|
|
255
|
+
isOpen(menu) {
|
|
256
|
+
if (this.isDisableFold(menu)) return true
|
|
257
|
+
return this.openedKeys.includes(menu.name)
|
|
258
|
+
},
|
|
259
|
+
/**
|
|
260
|
+
* 切换子菜单展开 / 收起状态
|
|
261
|
+
* @param {Object} menu 菜单项对象
|
|
262
|
+
*/
|
|
263
|
+
toggleSubMenu(menu) {
|
|
264
|
+
if (this.isDisableFold(menu)) return
|
|
265
|
+
const name = menu.name
|
|
266
|
+
if (this.openedKeys.includes(name)) {
|
|
267
|
+
this.openedKeys = this.openedKeys.filter(k => k !== name)
|
|
268
|
+
} else {
|
|
269
|
+
this.openedKeys = [...this.openedKeys, name]
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
/**
|
|
273
|
+
* 展开指定子菜单(若已展开则忽略)
|
|
274
|
+
* @param {String} name 子菜单 name
|
|
275
|
+
*/
|
|
276
|
+
openSubMenu(name) {
|
|
277
|
+
if (!this.openedKeys.includes(name)) {
|
|
278
|
+
this.openedKeys = [...this.openedKeys, name]
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
/**
|
|
282
|
+
* 选中导航项
|
|
283
|
+
* smart 模式:自动路由跳转并可选缓存到 localStorage
|
|
284
|
+
* 普通模式:更新 v-model:activeNav 并触发 change 事件
|
|
285
|
+
* @param {Object} item 被点击的菜单项完整对象
|
|
286
|
+
*/
|
|
287
|
+
selectMenu(item) {
|
|
288
|
+
const navVal = typeof this.activeNav === 'string'
|
|
289
|
+
? item[this.activeKey]
|
|
290
|
+
: item
|
|
291
|
+
if (this.smart) {
|
|
292
|
+
const routeData = { name: item.name, params: item.params, query: item.query }
|
|
293
|
+
if (this.cacheKey) {
|
|
294
|
+
localStorage.setItem(this.cacheKey, JSON.stringify(routeData))
|
|
295
|
+
}
|
|
296
|
+
this.$router.push(routeData)
|
|
297
|
+
} else {
|
|
298
|
+
this.$emit('update:activeNav', navVal)
|
|
299
|
+
}
|
|
300
|
+
// 等待父级 v-model:activeNav 完成同步后再触发 change
|
|
301
|
+
this.$nextTick(() => {
|
|
302
|
+
this.$emit('change', item, navVal)
|
|
303
|
+
})
|
|
304
|
+
},
|
|
305
|
+
/**
|
|
306
|
+
* 深度优先获取第一个可跳转的叶子菜单项(无 children 且含 name)
|
|
307
|
+
* @param {Array} list 菜单树
|
|
308
|
+
* @returns {Object|null}
|
|
309
|
+
*/
|
|
310
|
+
getFirstMenu(list) {
|
|
311
|
+
const queue = [...list]
|
|
312
|
+
while (queue.length) {
|
|
313
|
+
const item = queue.shift()
|
|
314
|
+
if (!item.children || !item.children.length) {
|
|
315
|
+
if (item.name) return item
|
|
316
|
+
} else {
|
|
317
|
+
queue.unshift(...item.children)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return null
|
|
321
|
+
},
|
|
322
|
+
/**
|
|
323
|
+
* 迭代平铺树形菜单为一维数组(避免递归栈溢出)
|
|
324
|
+
* @param {Array} list 菜单树
|
|
325
|
+
* @returns {Array} 不含 children 的菜单项数组
|
|
326
|
+
*/
|
|
327
|
+
flatTree(list) {
|
|
328
|
+
const result = []
|
|
329
|
+
const stack = [...list]
|
|
330
|
+
while (stack.length) {
|
|
331
|
+
const { children, ...rest } = stack.pop()
|
|
332
|
+
result.push(rest)
|
|
333
|
+
if (children?.length) {
|
|
334
|
+
stack.push(...children)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return result
|
|
338
|
+
},
|
|
339
|
+
/**
|
|
340
|
+
* 在菜单树中查找目标项的 name 路径(从根到目标)
|
|
341
|
+
* @param {Array} list 菜单树
|
|
342
|
+
* @param {String} targetKey 目标标识(getMenuIndex 或 activeKey 值)
|
|
343
|
+
* @param {Array} path 当前递归路径
|
|
344
|
+
* @returns {Array|null} name 路径数组,未找到返回 null
|
|
345
|
+
*/
|
|
346
|
+
findMenuPath(list, targetKey, path = []) {
|
|
347
|
+
for (const menu of list) {
|
|
348
|
+
const index = this.getMenuIndex(menu)
|
|
349
|
+
const key = menu[this.activeKey]
|
|
350
|
+
const currentPath = [...path, menu.name]
|
|
351
|
+
if (index === targetKey || key === targetKey) return currentPath
|
|
352
|
+
if (menu.children?.length) {
|
|
353
|
+
const found = this.findMenuPath(menu.children, targetKey, currentPath)
|
|
354
|
+
if (found) return found
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return null
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
mounted() {
|
|
361
|
+
if (!this.smart) return
|
|
362
|
+
// 尝试从 localStorage 恢复上次路由
|
|
363
|
+
if (this.cacheKey) {
|
|
364
|
+
try {
|
|
365
|
+
const cached = localStorage.getItem(this.cacheKey)
|
|
366
|
+
if (cached) {
|
|
367
|
+
this.$router.push(JSON.parse(cached))
|
|
368
|
+
return
|
|
369
|
+
}
|
|
370
|
+
} catch (e) {
|
|
371
|
+
// 缓存数据损坏时清除,避免持续报错
|
|
372
|
+
localStorage.removeItem(this.cacheKey)
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
// 无缓存且当前在根路径时,自动跳转到第一个可用菜单
|
|
376
|
+
if (this.$route?.path === '/') {
|
|
377
|
+
const first = this.getFirstMenu(this.menuList)
|
|
378
|
+
if (first) {
|
|
379
|
+
this.$router.push({ name: first.name, params: first.params, query: first.query })
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
computed: {
|
|
384
|
+
/** 导航栏是否处于收起状态(支持受控与非受控) */
|
|
385
|
+
isCollapsed() {
|
|
386
|
+
return this.collapsed !== undefined ? this.collapsed : this.innerCollapsed
|
|
387
|
+
},
|
|
388
|
+
/**
|
|
389
|
+
* 当前所有选中高亮项的标识列表(统一为字符串数组)
|
|
390
|
+
* smart 模式:根据 $route 自动匹配
|
|
391
|
+
* 普通模式:由 activeNav 转换而来
|
|
392
|
+
*/
|
|
393
|
+
activeKeys() {
|
|
394
|
+
if (this.smart && this.$route?.name) {
|
|
395
|
+
const matched = this.menuMap.find(menu => this.isRouteMatch(menu, this.$route))
|
|
396
|
+
return matched ? [this.getMenuIndex(matched)] : []
|
|
397
|
+
}
|
|
398
|
+
if (typeof this.activeNav === 'string') {
|
|
399
|
+
return this.activeNav ? [this.activeNav] : []
|
|
400
|
+
}
|
|
401
|
+
if (this.activeNav && typeof this.activeNav === 'object') {
|
|
402
|
+
return [this.getMenuIndex(this.activeNav)]
|
|
403
|
+
}
|
|
404
|
+
return []
|
|
405
|
+
},
|
|
406
|
+
/** 拍平后的菜单列表,用于 smart 模式路由匹配 */
|
|
407
|
+
menuMap() {
|
|
408
|
+
return this.flatTree(this.menuList)
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
</script>
|
|
413
|
+
|
|
414
|
+
<style scoped lang="scss">
|
|
415
|
+
@import "../font/iconfont.css";
|
|
416
|
+
|
|
417
|
+
.BnMenu {
|
|
418
|
+
display: flex;
|
|
419
|
+
flex-direction: column;
|
|
420
|
+
width: 100%;
|
|
421
|
+
min-width: 200px;
|
|
422
|
+
height: 100%;
|
|
423
|
+
position: relative;
|
|
424
|
+
|
|
425
|
+
&-logo {
|
|
426
|
+
position: relative;
|
|
427
|
+
flex-shrink: 0;
|
|
428
|
+
|
|
429
|
+
&-main {
|
|
430
|
+
min-height: 56px;
|
|
431
|
+
padding-right: 36px;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
&-title {
|
|
435
|
+
height: 56px;
|
|
436
|
+
padding: 16px 20px;
|
|
437
|
+
font-size: 16px;
|
|
438
|
+
font-weight: 500;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
&-toggle {
|
|
442
|
+
position: absolute;
|
|
443
|
+
top: 20px;
|
|
444
|
+
right: 10px;
|
|
445
|
+
z-index: 1;
|
|
446
|
+
font-size: 16px;
|
|
447
|
+
line-height: 1;
|
|
448
|
+
color: #86909c;
|
|
449
|
+
cursor: pointer;
|
|
450
|
+
transition: color 0.2s;
|
|
451
|
+
|
|
452
|
+
&:hover {
|
|
453
|
+
color: #1890ff;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// flex 子项占满剩余高度,height: 0 触发 flex 收缩以启用滚动
|
|
459
|
+
&-list {
|
|
460
|
+
flex: 1;
|
|
461
|
+
height: 0;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
&-body {
|
|
465
|
+
border: none;
|
|
466
|
+
padding: 0;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// 收起态:不占宽度,展开按钮绝对定位悬浮在边缘
|
|
470
|
+
&--collapsed {
|
|
471
|
+
width: 0;
|
|
472
|
+
min-width: 0;
|
|
473
|
+
overflow: visible;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
&-edge-open {
|
|
477
|
+
position: absolute;
|
|
478
|
+
top: 16px;
|
|
479
|
+
left: 0;
|
|
480
|
+
z-index: 10;
|
|
481
|
+
display: flex;
|
|
482
|
+
align-items: center;
|
|
483
|
+
justify-content: center;
|
|
484
|
+
width: 32px;
|
|
485
|
+
height: 32px;
|
|
486
|
+
font-size: 16px;
|
|
487
|
+
line-height: 1;
|
|
488
|
+
color: #86909c;
|
|
489
|
+
cursor: pointer;
|
|
490
|
+
border: 1px solid #e8eaed;
|
|
491
|
+
border-left: none;
|
|
492
|
+
border-radius: 0 6px 6px 0;
|
|
493
|
+
background: rgba(255,255,255,.5);
|
|
494
|
+
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.06);
|
|
495
|
+
transition: color 0.2s;
|
|
496
|
+
|
|
497
|
+
&:hover {
|
|
498
|
+
color: #1890ff;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
</style>
|