befly-admin 3.4.54 → 3.4.55

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "befly-admin",
3
- "version": "3.4.54",
3
+ "version": "3.4.55",
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.54",
29
+ "@befly-addon/admin": "^1.0.55",
30
30
  "@iconify-json/lucide": "^1.2.76",
31
31
  "axios": "^1.13.2",
32
- "befly-util": "^1.0.7",
33
- "befly-vite": "^1.0.12",
32
+ "befly-shared": "^1.1.1",
33
+ "befly-vite": "^1.0.13",
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": "1f3cacbe4134ee60654b75338bcaeda535898a84"
45
+ "gitHead": "2031550167896390ac58c50aeb52b8c23426844e"
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,122 @@
1
+ <template>
2
+ <div class="detail-panel">
3
+ <div class="detail-content">
4
+ <div v-if="data">
5
+ <div v-for="field in fields" :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 { Tag as TTag } from 'tdesign-vue-next';
35
+
36
+ defineProps({
37
+ /**
38
+ * 当前行数据
39
+ */
40
+ data: {
41
+ type: Object,
42
+ default: null
43
+ },
44
+ /**
45
+ * 字段配置
46
+ * @example [{ key: 'id', label: 'ID' }, { key: 'name', label: '名称', default: '-' }]
47
+ */
48
+ fields: {
49
+ type: Array,
50
+ required: true
51
+ },
52
+ /**
53
+ * 空数据时的提示文字
54
+ */
55
+ emptyText: {
56
+ type: String,
57
+ default: '暂无数据'
58
+ }
59
+ });
60
+
61
+ /**
62
+ * 格式化字段值
63
+ * @param {any} value - 字段值
64
+ * @param {Object} field - 字段配置
65
+ * @returns {string} 格式化后的值
66
+ */
67
+ function formatValue(value, field) {
68
+ if (value === null || value === undefined || value === '') {
69
+ return field.default || '-';
70
+ }
71
+ if (field.formatter) {
72
+ return field.formatter(value);
73
+ }
74
+ return value;
75
+ }
76
+ </script>
77
+
78
+ <style scoped lang="scss">
79
+ .detail-panel {
80
+ height: 100%;
81
+ overflow: auto;
82
+ }
83
+
84
+ .detail-content {
85
+ padding: 16px;
86
+ }
87
+
88
+ .detail-item {
89
+ margin-bottom: 16px;
90
+
91
+ &:last-child {
92
+ margin-bottom: 0;
93
+ }
94
+ }
95
+
96
+ .detail-label {
97
+ color: var(--text-secondary);
98
+ margin-bottom: 4px;
99
+ font-size: 12px;
100
+ }
101
+
102
+ .detail-value {
103
+ color: var(--text-primary);
104
+ font-size: 14px;
105
+ word-break: break-all;
106
+ }
107
+
108
+ .detail-empty {
109
+ text-align: center;
110
+ padding: 48px 0;
111
+ color: var(--text-placeholder);
112
+ }
113
+
114
+ .empty-icon {
115
+ font-size: 48px;
116
+ margin-bottom: 8px;
117
+ }
118
+
119
+ .empty-text {
120
+ font-size: 14px;
121
+ }
122
+ </style>
@@ -61,7 +61,7 @@
61
61
  </template>
62
62
 
63
63
  <script setup>
64
- import { arrayToTree } from 'befly-util/arrayToTree';
64
+ import { arrayToTree } from 'befly-shared/arrayToTree';
65
65
 
66
66
  const router = useRouter();
67
67
  const route = useRoute();
@@ -1,10 +1,10 @@
1
1
  import { createRouter, createWebHashHistory } from 'vue-router';
2
2
  import { routes } from 'vue-router/auto-routes';
3
3
  import { $Storage } from '@/plugins/storage';
4
- import { Layouts } from 'befly-util/layouts';
4
+ import { Layouts } from 'befly-shared/layouts';
5
5
 
6
6
  /**
7
- * @typedef {import('befly-util').LayoutConfig} LayoutConfig
7
+ * @typedef {import('befly-shared').LayoutConfig} LayoutConfig
8
8
  */
9
9
 
10
10
  /**
@@ -82,11 +82,15 @@ declare global {
82
82
  const useRoute: typeof import('vue-router').useRoute
83
83
  const useRouter: typeof import('vue-router').useRouter
84
84
  const useSlots: typeof import('vue').useSlots
85
+ const useTablePage: typeof import('../utils/useTablePage.js').useTablePage
85
86
  const useTemplateRef: typeof import('vue').useTemplateRef
86
87
  const watch: typeof import('vue').watch
87
88
  const watchEffect: typeof import('vue').watchEffect
88
89
  const watchPostEffect: typeof import('vue').watchPostEffect
89
90
  const watchSyncEffect: typeof import('vue').watchSyncEffect
91
+ const withDefaultColumns: typeof import('../utils/index.js').withDefaultColumns
92
+ const withTableProps: typeof import('../utils/index.js').withTableProps
93
+ const withTreeTableProps: typeof import('../utils/index.js').withTreeTableProps
90
94
  }
91
95
  // for type re-export
92
96
  declare global {
@@ -176,10 +180,14 @@ declare module 'vue' {
176
180
  readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
177
181
  readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
178
182
  readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
183
+ readonly useTablePage: UnwrapRef<typeof import('../utils/useTablePage.js')['useTablePage']>
179
184
  readonly useTemplateRef: UnwrapRef<typeof import('vue')['useTemplateRef']>
180
185
  readonly watch: UnwrapRef<typeof import('vue')['watch']>
181
186
  readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
182
187
  readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
183
188
  readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
189
+ readonly withDefaultColumns: UnwrapRef<typeof import('../utils/index.js')['withDefaultColumns']>
190
+ readonly withTableProps: UnwrapRef<typeof import('../utils/index.js')['withTableProps']>
191
+ readonly withTreeTableProps: UnwrapRef<typeof import('../utils/index.js')['withTreeTableProps']>
184
192
  }
185
193
  }
@@ -11,6 +11,7 @@ export {}
11
11
  /* prettier-ignore */
12
12
  declare module 'vue' {
13
13
  export interface GlobalComponents {
14
+ DetailPanel: typeof import('./../components/DetailPanel.vue')['default']
14
15
  Dialog: typeof import('./../components/Dialog.vue')['default']
15
16
  'ILucide:fileText': typeof import('~icons/lucide/file-text')['default']
16
17
  'ILucide:folder': typeof import('~icons/lucide/folder')['default']
@@ -2,7 +2,7 @@
2
2
  /* prettier-ignore */
3
3
  // @ts-nocheck
4
4
  // noinspection ES6UnusedImports
5
- // Generated by unplugin-vue-router. ‼️ DO NOT MODIFY THIS FILE ‼️
5
+ // Generated by unplugin-vue-router. !! DO NOT MODIFY THIS FILE !!
6
6
  // It's recommended to commit this file.
7
7
  // Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry.
8
8
 
@@ -44,6 +44,13 @@ declare module 'vue-router/auto-routes' {
44
44
  Record<never, never>,
45
45
  | never
46
46
  >,
47
+ '/addon/admin/api/': RouteRecordInfo<
48
+ '/addon/admin/api/',
49
+ '/addon/admin/api',
50
+ Record<never, never>,
51
+ Record<never, never>,
52
+ | never
53
+ >,
47
54
  '/addon/admin/dict/': RouteRecordInfo<
48
55
  '/addon/admin/dict/',
49
56
  '/addon/admin/dict',
@@ -117,6 +124,12 @@ declare module 'vue-router/auto-routes' {
117
124
  views:
118
125
  | never
119
126
  }
127
+ 'node_modules/@befly-addon/admin/views/api/index.vue': {
128
+ routes:
129
+ | '/addon/admin/api/'
130
+ views:
131
+ | never
132
+ }
120
133
  'node_modules/@befly-addon/admin/views/dict/index.vue': {
121
134
  routes:
122
135
  | '/addon/admin/dict/'
@@ -23,3 +23,75 @@ export function arrayToTree(items, pid = 0) {
23
23
 
24
24
  return tree;
25
25
  }
26
+
27
+ /**
28
+ * 表格列默认配置
29
+ * 统一设置超出显示省略号等通用配置
30
+ */
31
+ const defaultColumnConfig = {
32
+ ellipsis: true,
33
+ ellipsisTitle: true
34
+ };
35
+
36
+ /**
37
+ * 为表格列添加默认配置
38
+ * @param {Array} columns - 列配置数组
39
+ * @returns {Array} 添加默认配置后的列数组
40
+ */
41
+ export function withDefaultColumns(columns) {
42
+ return columns.map((col) => ({
43
+ ...defaultColumnConfig,
44
+ ...col
45
+ }));
46
+ }
47
+
48
+ /**
49
+ * 表格通用属性配置
50
+ * 统一管理 height、row-key、active-row-type 等通用配置
51
+ */
52
+ const defaultTableProps = {
53
+ rowKey: 'id',
54
+ height: '100%',
55
+ activeRowType: 'single'
56
+ };
57
+
58
+ /**
59
+ * 生成表格通用属性
60
+ * @param {Object} overrides - 覆盖或扩展的属性
61
+ * @returns {Object} 合并后的表格属性
62
+ */
63
+ export function withTableProps(overrides = {}) {
64
+ return {
65
+ ...defaultTableProps,
66
+ ...overrides
67
+ };
68
+ }
69
+
70
+ /**
71
+ * 树形表格默认配置
72
+ */
73
+ const defaultTreeConfig = {
74
+ childrenKey: 'children',
75
+ treeNodeColumnIndex: 0,
76
+ defaultExpandAll: true
77
+ };
78
+
79
+ /**
80
+ * 生成树形表格通用属性
81
+ * @param {Object} treeOverrides - 树形配置覆盖
82
+ * @param {Object} tableOverrides - 表格属性覆盖
83
+ * @returns {Object} 合并后的树形表格属性
84
+ */
85
+ export function withTreeTableProps(treeOverrides = {}, tableOverrides = {}) {
86
+ return {
87
+ ...defaultTableProps,
88
+ ...tableOverrides,
89
+ tree: {
90
+ ...defaultTreeConfig,
91
+ ...treeOverrides
92
+ }
93
+ };
94
+ }
95
+
96
+ // 导出组合式函数
97
+ export { useTablePage } from './useTablePage.js';
@@ -0,0 +1,174 @@
1
+ /**
2
+ * 表格页面通用组合式函数
3
+ * 封装分页、loading、数据加载等公共逻辑
4
+ */
5
+ import { ref, reactive } from 'vue';
6
+ import { MessagePlugin } from 'tdesign-vue-next';
7
+ import { $Http } from '@/plugins/http';
8
+
9
+ /**
10
+ * 创建表格页面通用逻辑
11
+ * @param {Object} options 配置项
12
+ * @param {string} options.apiUrl - 列表接口地址
13
+ * @param {Function} [options.afterLoad] - 数据加载后的回调
14
+ * @param {Function} [options.transformData] - 数据转换函数
15
+ * @returns {Object} 响应式数据和方法
16
+ */
17
+ export function useTablePage(options) {
18
+ const { apiUrl, afterLoad, transformData } = options;
19
+
20
+ // 分页配置
21
+ const pagerConfig = reactive({
22
+ currentPage: 1,
23
+ limit: 30,
24
+ total: 0
25
+ });
26
+
27
+ // 加载状态
28
+ const loading = ref(false);
29
+
30
+ // 表格数据
31
+ const tableData = ref([]);
32
+
33
+ // 当前选中行
34
+ const currentRow = ref(null);
35
+
36
+ // 选中行的 keys
37
+ const activeRowKeys = ref([]);
38
+ const selectedRowKeys = ref([]);
39
+
40
+ /**
41
+ * 加载列表数据
42
+ * @param {Object} [extraParams={}] - 额外的查询参数
43
+ */
44
+ async function loadData(extraParams = {}) {
45
+ loading.value = true;
46
+ try {
47
+ const res = await $Http(apiUrl, {
48
+ page: pagerConfig.currentPage,
49
+ limit: pagerConfig.limit,
50
+ ...extraParams
51
+ });
52
+
53
+ let data = res.data.lists || [];
54
+
55
+ // 数据转换
56
+ if (transformData) {
57
+ data = transformData(data);
58
+ }
59
+
60
+ tableData.value = data;
61
+ pagerConfig.total = res.data.total || 0;
62
+
63
+ // 自动选中并高亮第一行
64
+ if (tableData.value.length > 0) {
65
+ currentRow.value = tableData.value[0];
66
+ selectedRowKeys.value = [tableData.value[0].id];
67
+ activeRowKeys.value = [tableData.value[0].id];
68
+ } else {
69
+ currentRow.value = null;
70
+ selectedRowKeys.value = [];
71
+ activeRowKeys.value = [];
72
+ }
73
+
74
+ // 执行加载后回调
75
+ if (afterLoad) {
76
+ afterLoad(tableData.value);
77
+ }
78
+ } catch (error) {
79
+ console.error('加载数据失败:', error);
80
+ MessagePlugin.error('加载数据失败');
81
+ } finally {
82
+ loading.value = false;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * 刷新数据
88
+ */
89
+ function handleRefresh() {
90
+ loadData();
91
+ }
92
+
93
+ /**
94
+ * 分页改变
95
+ * @param {Object} param
96
+ * @param {number} param.currentPage - 当前页码
97
+ */
98
+ function onPageChange({ currentPage }) {
99
+ pagerConfig.currentPage = currentPage;
100
+ loadData();
101
+ }
102
+
103
+ /**
104
+ * 每页条数改变
105
+ * @param {Object} param
106
+ * @param {number} param.pageSize - 每页条数
107
+ */
108
+ function handleSizeChange({ pageSize }) {
109
+ pagerConfig.limit = pageSize;
110
+ pagerConfig.currentPage = 1;
111
+ loadData();
112
+ }
113
+
114
+ /**
115
+ * 高亮行变化(点击行选中)
116
+ * @param {Array} value - 选中的行 keys
117
+ * @param {Object} param
118
+ * @param {Array} param.activeRowData - 选中的行数据
119
+ */
120
+ function onActiveChange(value, { activeRowData }) {
121
+ activeRowKeys.value = value;
122
+ selectedRowKeys.value = value;
123
+ // 更新当前高亮的行数据
124
+ if (activeRowData && activeRowData.length > 0) {
125
+ currentRow.value = activeRowData[0];
126
+ } else if (tableData.value.length > 0) {
127
+ // 如果取消高亮,默认显示第一行
128
+ currentRow.value = tableData.value[0];
129
+ selectedRowKeys.value = [tableData.value[0].id];
130
+ activeRowKeys.value = [tableData.value[0].id];
131
+ } else {
132
+ currentRow.value = null;
133
+ }
134
+ }
135
+
136
+ /**
137
+ * 单选变化
138
+ * @param {Array} value - 选中的行 keys
139
+ * @param {Object} param
140
+ * @param {Array} param.selectedRowData - 选中的行数据
141
+ */
142
+ function onSelectChange(value, { selectedRowData }) {
143
+ selectedRowKeys.value = value;
144
+ activeRowKeys.value = value;
145
+ // 更新当前选中的行数据
146
+ if (selectedRowData && selectedRowData.length > 0) {
147
+ currentRow.value = selectedRowData[0];
148
+ } else if (tableData.value.length > 0) {
149
+ // 如果取消选中,默认显示第一行
150
+ currentRow.value = tableData.value[0];
151
+ selectedRowKeys.value = [tableData.value[0].id];
152
+ activeRowKeys.value = [tableData.value[0].id];
153
+ } else {
154
+ currentRow.value = null;
155
+ }
156
+ }
157
+
158
+ return {
159
+ // 数据
160
+ pagerConfig: pagerConfig,
161
+ loading: loading,
162
+ tableData: tableData,
163
+ currentRow: currentRow,
164
+ activeRowKeys: activeRowKeys,
165
+ selectedRowKeys: selectedRowKeys,
166
+ // 方法
167
+ loadData: loadData,
168
+ handleRefresh: handleRefresh,
169
+ onPageChange: onPageChange,
170
+ handleSizeChange: handleSizeChange,
171
+ onActiveChange: onActiveChange,
172
+ onSelectChange: onSelectChange
173
+ };
174
+ }
package/vite.config.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { createBeflyViteConfig } from 'befly-vite';
2
- import { scanViews } from 'befly-util/scanViews';
2
+ import { scanViews } from 'befly-shared/scanViews';
3
3
  import { fileURLToPath } from 'node:url';
4
4
 
5
5
  export default createBeflyViteConfig({
6
6
  root: fileURLToPath(new URL('.', import.meta.url)),
7
7
  scanViews: scanViews,
8
8
  optimizeDeps: {
9
- include: ['befly-util']
9
+ include: ['befly-shared']
10
10
  }
11
11
  });