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.
- package/components/detailPanel.vue +4 -14
- package/components/{pagedTableDetail.vue → pageTableDetail.vue} +71 -27
- package/layouts/default.vue +4 -3
- package/package.json +2 -2
- package/utils/formatFieldValue.js +106 -0
- package/views/config/dict/index.vue +1 -1
- package/views/config/dictType/index.vue +1 -1
- package/views/config/system/index.vue +1 -1
- package/views/log/email/index.vue +1 -1
- package/views/log/login/index.vue +3 -2
- package/views/log/operate/index.vue +1 -1
- package/views/people/admin/index.vue +1 -1
- package/views/permission/role/index.vue +1 -1
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
</template>
|
|
18
18
|
<!-- 默认显示 -->
|
|
19
19
|
<template v-else>
|
|
20
|
-
{{
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 (
|
|
178
|
-
|
|
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
|
-
|
|
182
|
-
|
|
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
|
-
|
|
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 });
|
package/layouts/default.vue
CHANGED
|
@@ -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>{{
|
|
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="
|
|
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
|
@@ -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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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: "
|
|
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/
|
|
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/
|
|
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/
|
|
80
|
+
import PageTableDetail from "befly-admin-ui/components/pageTableDetail.vue";
|
|
81
81
|
import { withDefaultColumns } from "befly-admin-ui/utils/withDefaultColumns";
|
|
82
82
|
|
|
83
83
|
// 响应式数据
|