befly-admin-ui 1.8.20 → 1.8.22

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">
@@ -16,8 +16,11 @@
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: {
@@ -151,41 +155,51 @@ function mergeDetailColumns(mainColumns, extraColumns) {
151
155
  return out;
152
156
  }
153
157
 
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(() => {
158
+ const columnsMeta = computed(() => {
171
159
  // 只维护一个 columns:
172
160
  // - detail: false(默认)=> 表格展示(同时也会出现在详情里,且顺序靠前)
173
161
  // - detail: true => 仅在详情展示(顺序靠后)
174
- const extras = [];
162
+ const tableColumns = [];
163
+ const extraDetailColumns = [];
164
+ const tableColumnMap = {};
165
+ const formatKeys = [];
166
+
175
167
  const cols = filterValidColumns(props.columns);
176
168
  for (const col of cols) {
177
- if (!isDetail(col)) continue;
178
- extras.push(col);
179
- }
169
+ if (col?.detail === true) {
170
+ extraDetailColumns.push(col);
171
+ continue;
172
+ }
173
+
174
+ tableColumns.push(col);
175
+
176
+ const key = getColKey(col);
177
+ if (!key) {
178
+ continue;
179
+ }
180
+
181
+ tableColumnMap[key] = col;
180
182
 
181
- if (extras.length > 0) {
182
- return mergeDetailColumns(tableColumns.value, extras);
183
+ const format = col.format;
184
+ if ((typeof format === "string" && format.trim().length > 0) || typeof format === "function") {
185
+ formatKeys.push(key);
186
+ }
183
187
  }
184
188
 
185
- // 默认复用表格列
186
- return tableColumns.value;
189
+ const detailFields = extraDetailColumns.length > 0 ? mergeDetailColumns(tableColumns, extraDetailColumns) : tableColumns;
190
+
191
+ return {
192
+ tableColumns: tableColumns,
193
+ detailFields: detailFields,
194
+ tableColumnMap: tableColumnMap,
195
+ formatKeys: formatKeys
196
+ };
187
197
  });
188
198
 
199
+ const tableColumns = computed(() => columnsMeta.value.tableColumns);
200
+
201
+ const detailFields = computed(() => columnsMeta.value.detailFields);
202
+
189
203
  const forwardedTableSlotNames = computed(() => {
190
204
  if (Array.isArray(props.tableSlotNames) && props.tableSlotNames.length > 0) {
191
205
  const out = [];
@@ -212,6 +226,36 @@ const forwardedTableSlotNames = computed(() => {
212
226
  return names;
213
227
  });
214
228
 
229
+ const tableRenderSlotNames = computed(() => {
230
+ const out = [];
231
+ const set = new Set();
232
+
233
+ for (const name of forwardedTableSlotNames.value) {
234
+ if (!name || set.has(name)) {
235
+ continue;
236
+ }
237
+ set.add(name);
238
+ out.push(name);
239
+ }
240
+
241
+ for (const key of columnsMeta.value.formatKeys) {
242
+ if (!key || set.has(key)) {
243
+ continue;
244
+ }
245
+ set.add(key);
246
+ out.push(key);
247
+ }
248
+
249
+ return out;
250
+ });
251
+
252
+ function formatTableCell(row, colKey) {
253
+ const record = row && typeof row === "object" ? row : {};
254
+ const value = record[colKey];
255
+ const field = columnsMeta.value.tableColumnMap[colKey] || {};
256
+ return formatFieldValue(value, field);
257
+ }
258
+
215
259
  function setCurrentRow(row) {
216
260
  $Data.currentRow = row;
217
261
  emit("row-change", { row: row });
@@ -7,7 +7,7 @@
7
7
  <div class="logo-icon">
8
8
  <AppIcon style="width: 24px; height: 24px; color: var(--primary-color)" />
9
9
  </div>
10
- <h2>{{ $Config.appTitle }}</h2>
10
+ <h2>{{ appTitle }}</h2>
11
11
  </div>
12
12
 
13
13
  <!-- 菜单区域 -->
@@ -45,7 +45,7 @@
45
45
  <span>系统设置</span>
46
46
  </div>
47
47
  <div class="footer-user">
48
- <t-upload :action="$Config.uploadUrl" :headers="uploadHeaders" :show-upload-list="false" accept="image/*" @success="onAvatarUploadSuccess">
48
+ <t-upload :action="uploadPath" :headers="uploadHeaders" :show-upload-list="false" accept="image/*" @success="onAvatarUploadSuccess">
49
49
  <div class="user-avatar" :class="{ 'has-avatar': $Data.userInfo.avatar }">
50
50
  <img v-if="$Data.userInfo.avatar" :src="$Data.userInfo.avatar" alt="avatar" />
51
51
  <UserIcon v-else style="width: 16px; height: 16px; color: #fff" />
@@ -81,7 +81,6 @@ import { CloudIcon, CloseCircleIcon, CodeIcon, LinkIcon, MenuIcon, SettingIcon,
81
81
 
82
82
  import { reactive, watch } from "vue";
83
83
  import { useRoute, useRouter } from "vue-router";
84
- import { $Config } from "@/plugins/config.js";
85
84
  import { $Http } from "@/plugins/http.js";
86
85
 
87
86
  const router = useRouter();
@@ -89,6 +88,8 @@ const route = useRoute();
89
88
  const uploadHeaders = { Authorization: localStorage.getItem("yicode-token") || "" };
90
89
 
91
90
  const loginPath = "/core/login";
91
+ const appTitle = import.meta.env.VITE_APP_TITLE;
92
+ const uploadPath = import.meta.env.VITE_UPLOAD_PATH;
92
93
 
93
94
  function isString(value) {
94
95
  return typeof value === "string";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "befly-admin-ui",
3
- "version": "1.8.20",
4
- "gitHead": "f22486b763b5f436a8930e108c385bfb0a79f429",
3
+ "version": "1.8.22",
4
+ "gitHead": "757e491f7d2d22339b35ffd577c14778203e5757",
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
  // 响应式数据
@@ -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
  // 响应式数据