befly-admin-ui 1.8.21 → 1.8.23

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.
@@ -17,7 +17,7 @@
17
17
  </template>
18
18
  <!-- 默认显示 -->
19
19
  <template v-else>
20
- {{ formatValue(data[field.colKey], field) }}
20
+ {{ formatFieldValue(data[field.colKey], field) }}
21
21
  </template>
22
22
  </div>
23
23
  </div>
@@ -33,6 +33,7 @@
33
33
  <script setup>
34
34
  import { computed } from "vue";
35
35
  import { Tag as TTag } from "tdesign-vue-next";
36
+ import { formatFieldValue } from "../utils/formatFieldValue.js";
36
37
 
37
38
  const props = defineProps({
38
39
  /**
@@ -96,7 +97,7 @@ const normalizedFields = computed(() => {
96
97
  colKey: item.colKey,
97
98
  title: item.title || item.colKey,
98
99
  default: item.default,
99
- formatter: item.formatter
100
+ format: item.format
100
101
  };
101
102
  })
102
103
  .filter((item) => {
@@ -109,7 +110,7 @@ const normalizedFields = computed(() => {
109
110
  colKey: "id",
110
111
  title: "ID",
111
112
  default: "-",
112
- formatter: undefined
113
+ format: undefined
113
114
  });
114
115
  }
115
116
 
@@ -122,17 +123,6 @@ const normalizedFields = computed(() => {
122
123
 
123
124
  return safeFields;
124
125
  });
125
-
126
- function formatValue(value, field) {
127
- if (value === null || value === undefined || value === "") {
128
- return field.default || "-";
129
- }
130
- if (field.formatter) {
131
- const result = field.formatter(value);
132
- return result;
133
- }
134
- return value;
135
- }
136
126
  </script>
137
127
 
138
128
  <style scoped lang="scss">
@@ -11,13 +11,16 @@
11
11
 
12
12
  <div class="main-content">
13
13
  <div class="main-table">
14
- <TTable :data="$Data.rows" :columns="tableColumns" :loading="$Data.loading" :active-row-keys="$Data.activeRowKeys" :row-key="rowKey" :height="tableHeight" active-row-type="single" @active-change="onActiveChange">
14
+ <TTable :data="$Data.rows" :columns="tableColumns" :loading="$Data.loading" :active-row-keys="$Data.activeRowKeys" :row-key="rowKey" :height="resolvedTableHeight" active-row-type="single" @active-change="onActiveChange">
15
15
  <template #operation="scope">
16
16
  <slot name="operation" v-bind="buildOperationSlotProps(scope)"></slot>
17
17
  </template>
18
18
 
19
- <template v-for="name in forwardedTableSlotNames" :key="name" v-slot:[name]="scope">
20
- <slot :name="name" v-bind="scope"></slot>
19
+ <template v-for="name in tableRenderSlotNames" :key="name" v-slot:[name]="scope">
20
+ <slot v-if="$slots[name]" :name="name" v-bind="scope"></slot>
21
+ <template v-else>
22
+ {{ formatTableCell(scope.row, name) }}
23
+ </template>
21
24
  </template>
22
25
  </TTable>
23
26
  </div>
@@ -42,6 +45,7 @@ import { computed, onMounted, reactive, useSlots } from "vue";
42
45
  import { DialogPlugin, MessagePlugin, Pagination as TPagination, Table as TTable } from "tdesign-vue-next";
43
46
  import DetailPanel from "./detailPanel.vue";
44
47
  import { $Http } from "@/plugins/http";
48
+ import { formatFieldValue } from "../utils/formatFieldValue.js";
45
49
 
46
50
  const props = defineProps({
47
51
  columns: {
@@ -56,6 +60,10 @@ const props = defineProps({
56
60
  type: String,
57
61
  default: "calc(100vh - var(--search-height) - var(--pagination-height) - var(--layout-gap) * 4)"
58
62
  },
63
+ isPagination: {
64
+ type: Boolean,
65
+ default: true
66
+ },
59
67
  pageSize: {
60
68
  type: Number,
61
69
  default: 30
@@ -151,39 +159,56 @@ function mergeDetailColumns(mainColumns, extraColumns) {
151
159
  return out;
152
160
  }
153
161
 
154
- function isDetail(col) {
155
- const record = col;
156
- if (record["detail"] === true) return true;
157
- return false;
158
- }
159
-
160
- const tableColumns = computed(() => {
161
- const out = [];
162
- const cols = filterValidColumns(props.columns);
163
- for (const col of cols) {
164
- if (isDetail(col)) continue;
165
- out.push(col);
166
- }
167
- return out;
168
- });
169
-
170
- const detailFields = computed(() => {
162
+ const columnsMeta = computed(() => {
171
163
  // 只维护一个 columns:
172
164
  // - detail: false(默认)=> 表格展示(同时也会出现在详情里,且顺序靠前)
173
165
  // - detail: true => 仅在详情展示(顺序靠后)
174
- const extras = [];
166
+ const tableColumns = [];
167
+ const extraDetailColumns = [];
168
+ const tableColumnMap = {};
169
+ const formatKeys = [];
170
+
175
171
  const cols = filterValidColumns(props.columns);
176
172
  for (const col of cols) {
177
- if (!isDetail(col)) continue;
178
- extras.push(col);
179
- }
173
+ if (col?.detail === true) {
174
+ extraDetailColumns.push(col);
175
+ continue;
176
+ }
177
+
178
+ tableColumns.push(col);
179
+
180
+ const key = getColKey(col);
181
+ if (!key) {
182
+ continue;
183
+ }
184
+
185
+ tableColumnMap[key] = col;
180
186
 
181
- if (extras.length > 0) {
182
- return mergeDetailColumns(tableColumns.value, extras);
187
+ const format = col.format;
188
+ if ((typeof format === "string" && format.trim().length > 0) || typeof format === "function") {
189
+ formatKeys.push(key);
190
+ }
183
191
  }
184
192
 
185
- // 默认复用表格列
186
- return tableColumns.value;
193
+ const detailFields = extraDetailColumns.length > 0 ? mergeDetailColumns(tableColumns, extraDetailColumns) : tableColumns;
194
+
195
+ return {
196
+ tableColumns: tableColumns,
197
+ detailFields: detailFields,
198
+ tableColumnMap: tableColumnMap,
199
+ formatKeys: formatKeys
200
+ };
201
+ });
202
+
203
+ const tableColumns = computed(() => columnsMeta.value.tableColumns);
204
+
205
+ const detailFields = computed(() => columnsMeta.value.detailFields);
206
+
207
+ const resolvedTableHeight = computed(() => {
208
+ if (props.isPagination) {
209
+ return props.tableHeight;
210
+ }
211
+ return "calc(100vh - var(--search-height) - var(--layout-gap) * 2)";
187
212
  });
188
213
 
189
214
  const forwardedTableSlotNames = computed(() => {
@@ -212,6 +237,36 @@ const forwardedTableSlotNames = computed(() => {
212
237
  return names;
213
238
  });
214
239
 
240
+ const tableRenderSlotNames = computed(() => {
241
+ const out = [];
242
+ const set = new Set();
243
+
244
+ for (const name of forwardedTableSlotNames.value) {
245
+ if (!name || set.has(name)) {
246
+ continue;
247
+ }
248
+ set.add(name);
249
+ out.push(name);
250
+ }
251
+
252
+ for (const key of columnsMeta.value.formatKeys) {
253
+ if (!key || set.has(key)) {
254
+ continue;
255
+ }
256
+ set.add(key);
257
+ out.push(key);
258
+ }
259
+
260
+ return out;
261
+ });
262
+
263
+ function formatTableCell(row, colKey) {
264
+ const record = row && typeof row === "object" ? row : {};
265
+ const value = record[colKey];
266
+ const field = columnsMeta.value.tableColumnMap[colKey] || {};
267
+ return formatFieldValue(value, field);
268
+ }
269
+
215
270
  function setCurrentRow(row) {
216
271
  $Data.currentRow = row;
217
272
  emit("row-change", { row: row });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "befly-admin-ui",
3
- "version": "1.8.21",
4
- "gitHead": "b1db9861a3f9f50bd3cda8b2f934d4499b32039d",
3
+ "version": "1.8.23",
4
+ "gitHead": "92ce66448bbb82aea5a864b0430c3b670c09f47f",
5
5
  "private": false,
6
6
  "description": "Befly - 管理后台功能组件",
7
7
  "keywords": [
@@ -0,0 +1,106 @@
1
+ function toDate(value) {
2
+ if (value instanceof Date) {
3
+ return Number.isFinite(value.getTime()) ? value : null;
4
+ }
5
+
6
+ if (typeof value === "number") {
7
+ if (!Number.isFinite(value)) {
8
+ return null;
9
+ }
10
+ const ms = Math.abs(value) < 1000000000000 ? value * 1000 : value;
11
+ const date = new Date(ms);
12
+ return Number.isFinite(date.getTime()) ? date : null;
13
+ }
14
+
15
+ if (typeof value === "string") {
16
+ const text = value.trim();
17
+ if (text.length === 0) {
18
+ return null;
19
+ }
20
+
21
+ if (/^\d+$/.test(text)) {
22
+ const num = Number(text);
23
+ if (!Number.isFinite(num)) {
24
+ return null;
25
+ }
26
+ const ms = Math.abs(num) < 1000000000000 ? num * 1000 : num;
27
+ const date = new Date(ms);
28
+ return Number.isFinite(date.getTime()) ? date : null;
29
+ }
30
+
31
+ const date = new Date(text);
32
+ return Number.isFinite(date.getTime()) ? date : null;
33
+ }
34
+
35
+ return null;
36
+ }
37
+
38
+ function getDateParts(value) {
39
+ const date = toDate(value);
40
+ if (!date) {
41
+ return null;
42
+ }
43
+
44
+ return {
45
+ year: date.getFullYear(),
46
+ month: String(date.getMonth() + 1).padStart(2, "0"),
47
+ day: String(date.getDate()).padStart(2, "0"),
48
+ hours: String(date.getHours()).padStart(2, "0"),
49
+ minutes: String(date.getMinutes()).padStart(2, "0"),
50
+ seconds: String(date.getSeconds()).padStart(2, "0")
51
+ };
52
+ }
53
+
54
+ function formatDateTime(value) {
55
+ const parts = getDateParts(value);
56
+ if (!parts) {
57
+ return value;
58
+ }
59
+
60
+ return `${parts.year}-${parts.month}-${parts.day} ${parts.hours}:${parts.minutes}:${parts.seconds}`;
61
+ }
62
+
63
+ function formatDate(value) {
64
+ const parts = getDateParts(value);
65
+ if (!parts) {
66
+ return value;
67
+ }
68
+
69
+ return `${parts.year}-${parts.month}-${parts.day}`;
70
+ }
71
+
72
+ function formatTime(value) {
73
+ const parts = getDateParts(value);
74
+ if (!parts) {
75
+ return value;
76
+ }
77
+
78
+ return `${parts.hours}:${parts.minutes}:${parts.seconds}`;
79
+ }
80
+
81
+ export function formatFieldValue(value, field = {}) {
82
+ if (value === null || value === undefined || value === "") {
83
+ return field.default || "-";
84
+ }
85
+
86
+ const format = field.format;
87
+
88
+ if (typeof format === "function") {
89
+ return format(value);
90
+ }
91
+
92
+ if (typeof format === "string") {
93
+ const normalized = format.trim();
94
+ if (normalized === "date") {
95
+ return formatDate(value);
96
+ }
97
+ if (normalized === "date-time") {
98
+ return formatDateTime(value);
99
+ }
100
+ if (normalized === "time") {
101
+ return formatTime(value);
102
+ }
103
+ }
104
+
105
+ return value;
106
+ }
@@ -56,7 +56,7 @@ import { Button as TButton, Dropdown as TDropdown, DropdownItem as TDropdownItem
56
56
  import { AddIcon, ChevronDownIcon, DeleteIcon, EditIcon, RefreshIcon, SearchIcon } from "tdesign-icons-vue-next";
57
57
  import EditDialog from "./components/edit.vue";
58
58
  import { $Http } from "@/plugins/http";
59
- import PageTableDetail from "befly-admin-ui/components/pagedTableDetail.vue";
59
+ import PageTableDetail from "befly-admin-ui/components/pageTableDetail.vue";
60
60
  import { withDefaultColumns } from "befly-admin-ui/utils/withDefaultColumns";
61
61
 
62
62
  const $Data = reactive({
@@ -51,7 +51,7 @@ import { reactive } from "vue";
51
51
  import { Button as TButton, Dropdown as TDropdown, DropdownItem as TDropdownItem, DropdownMenu as TDropdownMenu, Input as TInput } from "tdesign-vue-next";
52
52
  import { AddIcon, ChevronDownIcon, DeleteIcon, EditIcon, RefreshIcon, SearchIcon } from "tdesign-icons-vue-next";
53
53
  import EditDialog from "./components/edit.vue";
54
- import PageTableDetail from "befly-admin-ui/components/pagedTableDetail.vue";
54
+ import PageTableDetail from "befly-admin-ui/components/pageTableDetail.vue";
55
55
  import { withDefaultColumns } from "befly-admin-ui/utils/withDefaultColumns";
56
56
 
57
57
  const $Data = reactive({
@@ -80,7 +80,7 @@ import { Button as TButton, Dropdown as TDropdown, DropdownItem as TDropdownItem
80
80
  import { AddIcon, ChevronDownIcon, DeleteIcon, EditIcon, RefreshIcon } from "tdesign-icons-vue-next";
81
81
  import EditDialog from "./components/edit.vue";
82
82
  import DetailPanel from "befly-admin-ui/components/detailPanel.vue";
83
- import PageTableDetail from "befly-admin-ui/components/pagedTableDetail.vue";
83
+ import PageTableDetail from "befly-admin-ui/components/pageTableDetail.vue";
84
84
  import { withDefaultColumns } from "befly-admin-ui/utils/withDefaultColumns";
85
85
 
86
86
  // 响应式数据
@@ -75,7 +75,7 @@ import { CheckCircleIcon, RefreshIcon, SendIcon } from "tdesign-icons-vue-next";
75
75
  import PageDialog from "befly-admin-ui/components/pageDialog.vue";
76
76
  import DetailPanel from "befly-admin-ui/components/detailPanel.vue";
77
77
  import { $Http } from "@/plugins/http";
78
- import PageTableDetail from "befly-admin-ui/components/pagedTableDetail.vue";
78
+ import PageTableDetail from "befly-admin-ui/components/pageTableDetail.vue";
79
79
  import { withDefaultColumns } from "befly-admin-ui/utils/withDefaultColumns";
80
80
  const sendFormRef = ref(null);
81
81
 
@@ -43,7 +43,7 @@ import { reactive } from "vue";
43
43
  import { Button as TButton, Tag as TTag } from "tdesign-vue-next";
44
44
  import { RefreshIcon } from "tdesign-icons-vue-next";
45
45
  import DetailPanel from "befly-admin-ui/components/detailPanel.vue";
46
- import PageTableDetail from "befly-admin-ui/components/pagedTableDetail.vue";
46
+ import PageTableDetail from "befly-admin-ui/components/pageTableDetail.vue";
47
47
  import { withDefaultColumns } from "befly-admin-ui/utils/withDefaultColumns";
48
48
 
49
49
  // 响应式数据
@@ -54,8 +54,9 @@ const $Data = reactive({
54
54
  { colKey: "browserName", title: "浏览器" },
55
55
  { colKey: "osName", title: "操作系统" },
56
56
  { colKey: "deviceType", title: "设备类型" },
57
- { colKey: "loginTime", title: "登录时间" },
57
+ { colKey: "createdAt", title: "登录时间", format: "date-time" },
58
58
  { colKey: "loginResult", title: "登录结果" },
59
+ // 详情面板
59
60
  { colKey: "nickname", title: "昵称", detail: true },
60
61
  { colKey: "browserVersion", title: "浏览器版本", detail: true },
61
62
  { colKey: "osVersion", title: "系统版本", detail: true },
@@ -66,7 +66,7 @@ import { reactive } from "vue";
66
66
  import { Button as TButton, Option as TOption, Select as TSelect, Tag as TTag } from "tdesign-vue-next";
67
67
  import { RefreshIcon } from "tdesign-icons-vue-next";
68
68
  import DetailPanel from "befly-admin-ui/components/detailPanel.vue";
69
- import PageTableDetail from "befly-admin-ui/components/pagedTableDetail.vue";
69
+ import PageTableDetail from "befly-admin-ui/components/pageTableDetail.vue";
70
70
  import { withDefaultColumns } from "befly-admin-ui/utils/withDefaultColumns";
71
71
 
72
72
  // 响应式数据
@@ -117,7 +117,7 @@ async function apiLogin() {
117
117
 
118
118
  MessagePlugin.success(res.msg || "登录成功");
119
119
 
120
- await router.push("/");
120
+ await router.push(import.meta.env.VITE_HOME_PATH);
121
121
  } catch (error) {
122
122
  MessagePlugin.error(error.msg || error.message || "登录失败");
123
123
  } finally {
@@ -52,7 +52,7 @@ import { reactive } from "vue";
52
52
  import { Button as TButton, Dropdown as TDropdown, DropdownItem as TDropdownItem, DropdownMenu as TDropdownMenu, Tag as TTag } from "tdesign-vue-next";
53
53
  import { AddIcon, ChevronDownIcon, DeleteIcon, EditIcon, RefreshIcon } from "tdesign-icons-vue-next";
54
54
  import EditDialog from "./components/edit.vue";
55
- import PageTableDetail from "befly-admin-ui/components/pagedTableDetail.vue";
55
+ import PageTableDetail from "befly-admin-ui/components/pageTableDetail.vue";
56
56
  import { withDefaultColumns } from "befly-admin-ui/utils/withDefaultColumns";
57
57
 
58
58
  // 响应式数据
@@ -77,7 +77,7 @@ import { AddIcon, ChevronDownIcon, CodeIcon, DeleteIcon, EditIcon, RefreshIcon,
77
77
  import EditDialog from "./components/edit.vue";
78
78
  import MenuDialog from "./components/menu.vue";
79
79
  import ApiDialog from "./components/api.vue";
80
- import PageTableDetail from "befly-admin-ui/components/pagedTableDetail.vue";
80
+ import PageTableDetail from "befly-admin-ui/components/pageTableDetail.vue";
81
81
  import { withDefaultColumns } from "befly-admin-ui/utils/withDefaultColumns";
82
82
 
83
83
  // 响应式数据