@xjw_/vue2-npm-system 1.0.84 → 1.0.85

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.
@@ -0,0 +1,382 @@
1
+ <template>
2
+ <div class="x-split-layout" :style="{ height: xHeight }">
3
+ <!-- 左侧目录 -->
4
+ <div class="x-split-layout__sidebar" :style="{ width: xSidebarWidth }">
5
+ <!-- 顶部信息区 -->
6
+ <div v-if="showSidebarHeader" class="x-split-layout__sidebar-header" :style="sidebarHeaderContainerStyle">
7
+ <slot name="sidebar-header">
8
+ <div v-for="(item, index) in xSidebarHeader" :key="index" class="x-split-layout__header-item" :style="{ marginBottom: index < xSidebarHeader.length - 1 ? xSidebarHeaderItemGap : '0' }">
9
+ <span class="x-split-layout__header-label" :style="xSidebarHeaderLabelStyle">{{ item.label }}:</span>
10
+ <span class="x-split-layout__header-value" :style="xSidebarHeaderValueStyle">{{ item.value }}</span>
11
+ </div>
12
+ </slot>
13
+ </div>
14
+
15
+ <!-- 目录列表 -->
16
+ <div class="x-split-layout__sidebar-menu">
17
+ <div
18
+ v-for="(item, index) in xMenuItems"
19
+ :key="index"
20
+ class="x-split-layout__menu-item"
21
+ :class="{ 'is-active': activeIndex === index }"
22
+ @click="handleMenuClick(index, item)"
23
+ >
24
+ <slot name="menu-item" :item="item" :index="index" :active="activeIndex === index">
25
+ <span class="x-split-layout__menu-icon">
26
+ <span class="x-split-layout__menu-dot" :class="{ 'is-active-dot': activeIndex === index }"></span>
27
+ </span>
28
+ <span class="x-split-layout__menu-label">{{ item.label }}</span>
29
+ </slot>
30
+ <slot name="menu-item-right" :item="item" :index="index" :active="activeIndex === index"></slot>
31
+ </div>
32
+ </div>
33
+
34
+ <!-- 侧边栏右侧自定义内容 -->
35
+ <slot name="sidebar-right"></slot>
36
+ </div>
37
+
38
+ <!-- 右侧内容区 -->
39
+ <div class="x-split-layout__content">
40
+ <div class="x-split-layout__content-inner" :class="contentAlignClass">
41
+ <!-- 内容标题和操作按钮 -->
42
+ <div v-if="showContentHeader" class="x-split-layout__content-header">
43
+ <div class="x-split-layout__content-title">
44
+ <slot name="content-title">
45
+ <span>{{ currentContentTitle }}</span>
46
+ </slot>
47
+ </div>
48
+ <div v-if="showActions" class="x-split-layout__content-actions">
49
+ <slot name="actions">
50
+ <el-button size="small" @click="handleCancel">取消</el-button>
51
+ <el-button type="primary" size="small" @click="handleSave">保存</el-button>
52
+ </slot>
53
+ </div>
54
+ </div>
55
+
56
+ <!-- 内容主体 -->
57
+ <div class="x-split-layout__content-body" :style="contentMaxWidthStyle">
58
+ <slot :active-item="activeItem" :active-index="activeIndex"></slot>
59
+ </div>
60
+ </div>
61
+ </div>
62
+ </div>
63
+ </template>
64
+
65
+ <script>
66
+ /**
67
+ * @author xjw
68
+ * @since 2026/4/30
69
+ * @description 左右分栏布局组件,左侧目录导航,右侧内容区域
70
+ */
71
+ export default {
72
+ name: 'XSplitLayout',
73
+
74
+ props: {
75
+ // 组件高度
76
+ xHeight: {
77
+ type: String,
78
+ default: '100%'
79
+ },
80
+ // 左侧宽度
81
+ xSidebarWidth: {
82
+ type: String,
83
+ default: '240px'
84
+ },
85
+ // 菜单项列表
86
+ xMenuItems: {
87
+ type: Array,
88
+ default: () => []
89
+ },
90
+ // 当前激活的菜单索引
91
+ xActiveIndex: {
92
+ type: Number,
93
+ default: 0
94
+ },
95
+ // 内容区对齐方式:center | left | right
96
+ xContentAlign: {
97
+ type: String,
98
+ default: 'center',
99
+ validator: (val) => ['center', 'left', 'right'].includes(val)
100
+ },
101
+ // 内容区最大宽度(当 align 为 center 时生效)
102
+ xContentMaxWidth: {
103
+ type: String,
104
+ default: '500px'
105
+ },
106
+ // 是否显示内容头部
107
+ xShowContentHeader: {
108
+ type: Boolean,
109
+ default: true
110
+ },
111
+ // 是否显示操作按钮
112
+ xShowActions: {
113
+ type: Boolean,
114
+ default: true
115
+ },
116
+ // 侧边栏头部信息
117
+ xSidebarHeader: {
118
+ type: Array,
119
+ default: () => []
120
+ },
121
+ // 是否显示侧边栏头部
122
+ xShowSidebarHeader: {
123
+ type: Boolean,
124
+ default: true
125
+ },
126
+ // 侧边栏头部样式
127
+ xSidebarHeaderStyle: {
128
+ type: Object,
129
+ default: () => ({})
130
+ },
131
+ // 侧边栏头部标签样式
132
+ xSidebarHeaderLabelStyle: {
133
+ type: Object,
134
+ default: () => ({ color: '#409EFF', fontWeight: '500' })
135
+ },
136
+ // 侧边栏头部值样式
137
+ xSidebarHeaderValueStyle: {
138
+ type: Object,
139
+ default: () => ({ color: '#303133' })
140
+ },
141
+ // 侧边栏头部项之间的间距
142
+ xSidebarHeaderItemGap: {
143
+ type: String,
144
+ default: '8px'
145
+ },
146
+ // 侧边栏头部内边距
147
+ xSidebarHeaderPadding: {
148
+ type: String,
149
+ default: '16px'
150
+ },
151
+ // 侧边栏头部底部边框
152
+ xSidebarHeaderBorderBottom: {
153
+ type: String,
154
+ default: '1px solid #EAEFFE'
155
+ }
156
+ },
157
+
158
+ data() {
159
+ return {
160
+ activeIndex: this.xActiveIndex,
161
+ activeItem: null
162
+ }
163
+ },
164
+
165
+ computed: {
166
+ showSidebarHeader() {
167
+ return this.xShowSidebarHeader && (this.$slots['sidebar-header'] || this.xSidebarHeader.length > 0)
168
+ },
169
+ showContentHeader() {
170
+ return this.xShowContentHeader
171
+ },
172
+ showActions() {
173
+ return this.xShowActions
174
+ },
175
+ currentContentTitle() {
176
+ if (this.activeItem && this.activeItem.title) {
177
+ return this.activeItem.title
178
+ }
179
+ return ''
180
+ },
181
+ contentAlignClass() {
182
+ return `x-split-layout__content--${this.xContentAlign}`
183
+ },
184
+ sidebarHeaderContainerStyle() {
185
+ return {
186
+ ...this.xSidebarHeaderStyle,
187
+ padding: this.xSidebarHeaderPadding,
188
+ borderBottom: this.xSidebarHeaderBorderBottom
189
+ }
190
+ },
191
+ contentMaxWidthStyle() {
192
+ if (this.xContentAlign === 'center' || this.xContentAlign === 'right') {
193
+ return { maxWidth: this.xContentMaxWidth }
194
+ }
195
+ return {}
196
+ }
197
+ },
198
+
199
+ watch: {
200
+ xActiveIndex(newVal) {
201
+ this.activeIndex = newVal
202
+ }
203
+ },
204
+
205
+ created() {
206
+ this.updateActiveItem()
207
+ },
208
+
209
+ methods: {
210
+ handleMenuClick(index, item) {
211
+ this.activeIndex = index
212
+ this.activeItem = item
213
+ this.$emit('menu-click', index, item)
214
+ this.$emit('update:xActiveIndex', index)
215
+ },
216
+ handleCancel() {
217
+ this.$emit('cancel')
218
+ },
219
+ handleSave() {
220
+ this.$emit('save', {
221
+ index: this.activeIndex,
222
+ item: this.activeItem
223
+ })
224
+ },
225
+ updateActiveItem() {
226
+ if (this.xMenuItems.length > 0 && this.activeIndex >= 0 && this.activeIndex < this.xMenuItems.length) {
227
+ this.activeItem = this.xMenuItems[this.activeIndex]
228
+ }
229
+ }
230
+ }
231
+ }
232
+ </script>
233
+
234
+ <style lang="scss" scoped>
235
+ .x-split-layout {
236
+ display: flex;
237
+ width: 100%;
238
+ gap: 16px;
239
+
240
+ &__sidebar {
241
+ flex-shrink: 0;
242
+ border-right: 1px solid #EAEFFE;
243
+ display: flex;
244
+ flex-direction: column;
245
+ background: #FFFFFF;
246
+ position: relative;
247
+ }
248
+
249
+ &__sidebar-header {
250
+ padding: 16px;
251
+ border-bottom: 1px solid #EAEFFE;
252
+ }
253
+
254
+ &__header-item {
255
+ margin-bottom: 8px;
256
+ font-size: 13px;
257
+ line-height: 20px;
258
+
259
+ &:last-child {
260
+ margin-bottom: 0;
261
+ }
262
+ }
263
+
264
+ &__header-label {
265
+ color: #909399;
266
+ }
267
+
268
+ &__header-value {
269
+ color: #303133;
270
+ }
271
+
272
+ &__sidebar-menu {
273
+ flex: 1;
274
+ overflow-y: auto;
275
+ padding: 8px 0;
276
+ }
277
+
278
+ &__menu-item {
279
+ display: flex;
280
+ align-items: center;
281
+ padding: 12px 16px;
282
+ cursor: pointer;
283
+ transition: all 0.3s;
284
+ color: #606266;
285
+ font-size: 14px;
286
+
287
+ &:hover {
288
+ background: #ECF5FF;
289
+ color: #409EFF;
290
+ }
291
+
292
+ &.is-active {
293
+ background: #ECF5FF;
294
+ color: #409EFF;
295
+ font-weight: 500;
296
+ }
297
+ }
298
+
299
+ &__menu-icon {
300
+ margin-right: 8px;
301
+ display: flex;
302
+ align-items: center;
303
+ }
304
+
305
+ &__menu-dot {
306
+ width: 6px;
307
+ height: 6px;
308
+ border-radius: 50%;
309
+ background: #C0C4CC;
310
+ }
311
+
312
+ .is-active &__menu-dot {
313
+ background: #409EFF;
314
+ }
315
+
316
+ &__menu-label {
317
+ flex: 1;
318
+ overflow: hidden;
319
+ text-overflow: ellipsis;
320
+ white-space: nowrap;
321
+ }
322
+
323
+ &__content {
324
+ flex: 1;
325
+ overflow: hidden;
326
+ display: flex;
327
+ flex-direction: column;
328
+ background: #FFFFFF;
329
+ }
330
+
331
+ &__content-inner {
332
+ flex: 1;
333
+ overflow-y: auto;
334
+ padding: 20px 24px;
335
+ }
336
+
337
+ &__content--center {
338
+ display: flex;
339
+ flex-direction: column;
340
+ align-items: center;
341
+ }
342
+
343
+ &__content--center &__content-header,
344
+ &__content--center &__content-body {
345
+ width: 100%;
346
+ }
347
+
348
+ &__content--left &__content-header,
349
+ &__content--left &__content-body {
350
+ width: 100%;
351
+ }
352
+
353
+ &__content--right &__content-header,
354
+ &__content--right &__content-body {
355
+ width: 100%;
356
+ margin-left: auto;
357
+ }
358
+
359
+ &__content-header {
360
+ display: flex;
361
+ justify-content: space-between;
362
+ align-items: center;
363
+ margin-bottom: 16px;
364
+ }
365
+
366
+ &__content-title {
367
+ font-size: 16px;
368
+ font-weight: 500;
369
+ color: #303133;
370
+ }
371
+
372
+ &__content-actions {
373
+ display: flex;
374
+ gap: 8px;
375
+ }
376
+
377
+ &__content-body {
378
+ border-radius: 4px;
379
+ padding: 20px;
380
+ }
381
+ }
382
+ </style>
package/lib/index.js CHANGED
@@ -65,6 +65,12 @@ Object.defineProperty(exports, "XSearchBar", {
65
65
  return _XSearchBar["default"];
66
66
  }
67
67
  });
68
+ Object.defineProperty(exports, "XSplitLayout", {
69
+ enumerable: true,
70
+ get: function get() {
71
+ return _XSplitLayout["default"];
72
+ }
73
+ });
68
74
  Object.defineProperty(exports, "XVxeColumn", {
69
75
  enumerable: true,
70
76
  get: function get() {
@@ -110,6 +116,7 @@ var _XElSelect = _interopRequireDefault(require("./components/XElSelect"));
110
116
  var _XElOption = _interopRequireDefault(require("./components/XElOption"));
111
117
  var _XVxeColumn = _interopRequireDefault(require("./components/XVxeColumn"));
112
118
  var _XBusinessLog = _interopRequireDefault(require("./components/XBusinessLog"));
119
+ var _XSplitLayout = _interopRequireDefault(require("./components/XSplitLayout"));
113
120
  var _request = require("./utils/request");
114
121
  var _format = require("./utils/format");
115
122
  var _LuoFormat = _interopRequireWildcard(require("./utils/LuoFormat"));
@@ -131,7 +138,7 @@ if (typeof document !== 'undefined') {
131
138
  // Vue.use(VXETable)
132
139
 
133
140
  // 存储组件列表(对外暴露的组件)
134
- var components = [_XSearchBar["default"], _XVxeTable["default"], _XPagination["default"], _XReportTable["default"], _XDatePicker["default"], _XElForm["default"], _XElFormItem["default"], _XElSelect["default"], _XElOption["default"], _XVxeColumn["default"], _XBusinessLog["default"]];
141
+ var components = [_XSearchBar["default"], _XVxeTable["default"], _XPagination["default"], _XReportTable["default"], _XDatePicker["default"], _XElForm["default"], _XElFormItem["default"], _XElSelect["default"], _XElOption["default"], _XVxeColumn["default"], _XBusinessLog["default"], _XSplitLayout["default"]];
135
142
 
136
143
  // 内部组件列表(不对外暴露)
137
144
  var internalComponents = [_SvgIcon["default"]];
@@ -164,6 +171,7 @@ var _default = exports["default"] = {
164
171
  XElOption: _XElOption["default"],
165
172
  XVxeColumn: _XVxeColumn["default"],
166
173
  XBusinessLog: _XBusinessLog["default"],
174
+ XSplitLayout: _XSplitLayout["default"],
167
175
  // 工具函数
168
176
  createRequest: _request.createRequest,
169
177
  parseTime: _format.parseTime,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xjw_/vue2-npm-system",
3
- "version": "1.0.84",
3
+ "version": "1.0.85",
4
4
  "description": "基于 Element-UI 的 Vue2 组件库,二次封装常用组件",
5
5
  "main": "lib/index.js",
6
6
  "files": [