befly-admin 3.4.54 → 3.4.56
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/.env +1 -1
- package/package.json +5 -5
- package/src/App.vue +9 -0
- package/src/components/DetailPanel.vue +162 -0
- package/src/config/index.js +1 -1
- package/src/layouts/default.vue +271 -121
- package/src/main.js +0 -4
- package/src/router/index.js +2 -2
- package/src/styles/global.scss +94 -101
- package/src/types/auto-imports.d.ts +4 -0
- package/src/types/components.d.ts +9 -0
- package/src/types/typed-router.d.ts +96 -31
- package/src/utils/index.js +105 -0
- package/vite.config.js +2 -2
package/.env
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly-admin",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.56",
|
|
4
4
|
"description": "Befly Admin - 基于 Vue3 + OpenTiny Vue 的后台管理系统",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
"preview": "vite preview"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@befly-addon/admin": "^1.0.
|
|
29
|
+
"@befly-addon/admin": "^1.0.56",
|
|
30
30
|
"@iconify-json/lucide": "^1.2.76",
|
|
31
31
|
"axios": "^1.13.2",
|
|
32
|
-
"befly-
|
|
33
|
-
"befly-vite": "^1.0.
|
|
32
|
+
"befly-shared": "^1.1.2",
|
|
33
|
+
"befly-vite": "^1.0.14",
|
|
34
34
|
"pinia": "^3.0.4",
|
|
35
35
|
"tdesign-vue-next": "^1.17.5",
|
|
36
36
|
"vite": "^7.2.4",
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"pnpm": ">=10.0.0",
|
|
43
43
|
"bun": ">=1.3.0"
|
|
44
44
|
},
|
|
45
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "d9a4c57539f6eb692d7db6c7fd800e021891a07c"
|
|
46
46
|
}
|
package/src/App.vue
CHANGED
|
@@ -12,6 +12,15 @@ import { ConfigProvider } from 'tdesign-vue-next';
|
|
|
12
12
|
const globalConfig = {
|
|
13
13
|
dialog: {
|
|
14
14
|
closeOnOverlayClick: false
|
|
15
|
+
},
|
|
16
|
+
table: {
|
|
17
|
+
// 表格默认配置
|
|
18
|
+
size: 'medium',
|
|
19
|
+
bordered: false,
|
|
20
|
+
stripe: false,
|
|
21
|
+
showHeader: true,
|
|
22
|
+
// 列默认配置(需要在每个列上单独设置)
|
|
23
|
+
cellEmptyContent: '-'
|
|
15
24
|
}
|
|
16
25
|
};
|
|
17
26
|
</script>
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="detail-panel">
|
|
3
|
+
<div class="detail-content">
|
|
4
|
+
<div v-if="data">
|
|
5
|
+
<div v-for="field in normalizedFields" :key="field.key" class="detail-item">
|
|
6
|
+
<div class="detail-label">{{ field.label }}</div>
|
|
7
|
+
<div class="detail-value">
|
|
8
|
+
<!-- 状态字段特殊处理 -->
|
|
9
|
+
<template v-if="field.key === 'state'">
|
|
10
|
+
<TTag v-if="data.state === 1" shape="round" theme="success" variant="light-outline">正常</TTag>
|
|
11
|
+
<TTag v-else-if="data.state === 2" shape="round" theme="warning" variant="light-outline">禁用</TTag>
|
|
12
|
+
<TTag v-else-if="data.state === 0" shape="round" theme="danger" variant="light-outline">已删除</TTag>
|
|
13
|
+
</template>
|
|
14
|
+
<!-- 自定义插槽 -->
|
|
15
|
+
<template v-else-if="$slots[field.key]">
|
|
16
|
+
<slot :name="field.key" :value="data[field.key]" :row="data"></slot>
|
|
17
|
+
</template>
|
|
18
|
+
<!-- 默认显示 -->
|
|
19
|
+
<template v-else>
|
|
20
|
+
{{ formatValue(data[field.key], field) }}
|
|
21
|
+
</template>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
<div v-else class="detail-empty">
|
|
26
|
+
<div class="empty-icon">📋</div>
|
|
27
|
+
<div class="empty-text">{{ emptyText }}</div>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<script setup>
|
|
34
|
+
import { computed } from 'vue';
|
|
35
|
+
import { Tag as TTag } from 'tdesign-vue-next';
|
|
36
|
+
|
|
37
|
+
const props = defineProps({
|
|
38
|
+
/**
|
|
39
|
+
* 当前行数据
|
|
40
|
+
*/
|
|
41
|
+
data: {
|
|
42
|
+
type: Object,
|
|
43
|
+
default: null
|
|
44
|
+
},
|
|
45
|
+
/**
|
|
46
|
+
* 字段配置,支持两种格式:
|
|
47
|
+
* 1. fields 格式: [{ key: 'id', label: 'ID' }]
|
|
48
|
+
* 2. columns 格式: [{ colKey: 'id', title: 'ID' }]
|
|
49
|
+
* 自动过滤 row-select、operation 等非数据列
|
|
50
|
+
*/
|
|
51
|
+
fields: {
|
|
52
|
+
type: Array,
|
|
53
|
+
required: true
|
|
54
|
+
},
|
|
55
|
+
/**
|
|
56
|
+
* 需要过滤的列 key
|
|
57
|
+
*/
|
|
58
|
+
excludeKeys: {
|
|
59
|
+
type: Array,
|
|
60
|
+
default: () => ['row-select', 'operation', 'index']
|
|
61
|
+
},
|
|
62
|
+
/**
|
|
63
|
+
* 空数据时的提示文字
|
|
64
|
+
*/
|
|
65
|
+
emptyText: {
|
|
66
|
+
type: String,
|
|
67
|
+
default: '暂无数据'
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 标准化字段配置,支持 columns 和 fields 两种格式
|
|
73
|
+
*/
|
|
74
|
+
const normalizedFields = computed(() => {
|
|
75
|
+
return props.fields
|
|
76
|
+
.filter((item) => {
|
|
77
|
+
const key = item.colKey || item.key;
|
|
78
|
+
return key && !props.excludeKeys.includes(key);
|
|
79
|
+
})
|
|
80
|
+
.map((item) => ({
|
|
81
|
+
key: item.colKey || item.key,
|
|
82
|
+
label: item.title || item.label,
|
|
83
|
+
default: item.default,
|
|
84
|
+
formatter: item.formatter
|
|
85
|
+
}));
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 格式化字段值
|
|
90
|
+
* @param {any} value - 字段值
|
|
91
|
+
* @param {Object} field - 字段配置
|
|
92
|
+
* @returns {string} 格式化后的值
|
|
93
|
+
*/
|
|
94
|
+
function formatValue(value, field) {
|
|
95
|
+
if (value === null || value === undefined || value === '') {
|
|
96
|
+
return field.default || '-';
|
|
97
|
+
}
|
|
98
|
+
if (field.formatter) {
|
|
99
|
+
return field.formatter(value);
|
|
100
|
+
}
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
</script>
|
|
104
|
+
|
|
105
|
+
<style scoped lang="scss">
|
|
106
|
+
.detail-panel {
|
|
107
|
+
height: 100%;
|
|
108
|
+
overflow: auto;
|
|
109
|
+
background: var(--bg-color-container);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.detail-content {
|
|
113
|
+
padding: var(--spacing-md);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.detail-item {
|
|
117
|
+
margin-bottom: var(--spacing-sm);
|
|
118
|
+
padding: var(--spacing-sm) 0;
|
|
119
|
+
border-bottom: 1px solid var(--border-color-light);
|
|
120
|
+
|
|
121
|
+
&:first-child {
|
|
122
|
+
padding-top: 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
&:last-child {
|
|
126
|
+
margin-bottom: 0;
|
|
127
|
+
padding-bottom: 0;
|
|
128
|
+
border-bottom: none;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.detail-label {
|
|
133
|
+
color: var(--text-secondary);
|
|
134
|
+
margin-bottom: 6px;
|
|
135
|
+
font-size: var(--font-size-xs);
|
|
136
|
+
font-weight: var(--font-weight-medium);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.detail-value {
|
|
140
|
+
color: var(--text-primary);
|
|
141
|
+
font-size: var(--font-size-sm);
|
|
142
|
+
font-weight: var(--font-weight-medium);
|
|
143
|
+
word-break: break-all;
|
|
144
|
+
line-height: 1.5;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.detail-empty {
|
|
148
|
+
text-align: center;
|
|
149
|
+
padding: var(--spacing-xl) 0;
|
|
150
|
+
color: var(--text-placeholder);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.empty-icon {
|
|
154
|
+
font-size: 40px;
|
|
155
|
+
margin-bottom: var(--spacing-sm);
|
|
156
|
+
opacity: 0.5;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.empty-text {
|
|
160
|
+
font-size: var(--font-size-sm);
|
|
161
|
+
}
|
|
162
|
+
</style>
|
package/src/config/index.js
CHANGED