befly-admin-ui 1.8.37 → 1.9.1
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/jsconfig.json +15 -2
- package/layouts/default.vue +41 -4
- package/package.json +2 -2
- package/views/jsconfig.json +26 -7
- package/views/resource/gallery/index.vue +271 -0
package/jsconfig.json
CHANGED
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
{
|
|
2
|
-
"extends": "../../jsconfig.base.json",
|
|
3
2
|
"compilerOptions": {
|
|
4
3
|
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": ["ESNext"],
|
|
7
|
+
"noEmit": true,
|
|
5
8
|
"strict": false,
|
|
9
|
+
"useUnknownInCatchVariables": false,
|
|
10
|
+
"noUncheckedIndexedAccess": false,
|
|
6
11
|
"exactOptionalPropertyTypes": false,
|
|
12
|
+
"noPropertyAccessFromIndexSignature": false,
|
|
7
13
|
"noImplicitReturns": false,
|
|
8
14
|
"noFallthroughCasesInSwitch": false,
|
|
9
|
-
"noImplicitOverride": false
|
|
15
|
+
"noImplicitOverride": false,
|
|
16
|
+
"skipLibCheck": true,
|
|
17
|
+
"esModuleInterop": true,
|
|
18
|
+
"allowSyntheticDefaultImports": true,
|
|
19
|
+
"forceConsistentCasingInFileNames": true,
|
|
20
|
+
"resolveJsonModule": true,
|
|
21
|
+
"allowImportingTsExtensions": false,
|
|
22
|
+
"allowJs": true
|
|
10
23
|
},
|
|
11
24
|
"include": ["apis/**/*.js", "libs/**/*.js", "plugins/**/*.js", "utils/**/*.js", "configs/**/*.json"],
|
|
12
25
|
"exclude": ["node_modules", "dist", "logs", "temp", "tests"]
|
package/layouts/default.vue
CHANGED
|
@@ -86,7 +86,7 @@ import { $Config } from "@/plugins/config.js";
|
|
|
86
86
|
|
|
87
87
|
const router = useRouter();
|
|
88
88
|
const route = useRoute();
|
|
89
|
-
const uploadHeaders = { Authorization: localStorage.getItem($Config.tokenName) || "" };
|
|
89
|
+
const uploadHeaders = { Authorization: `Bearer ${localStorage.getItem($Config.tokenName) || ""}` };
|
|
90
90
|
|
|
91
91
|
function isString(value) {
|
|
92
92
|
return typeof value === "string";
|
|
@@ -134,6 +134,29 @@ const $Data = reactive({
|
|
|
134
134
|
}
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
+
function normalizeAvatarUrl(value) {
|
|
138
|
+
if (!isString(value) || value.length === 0) {
|
|
139
|
+
return "";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function fetchUserInfo() {
|
|
146
|
+
try {
|
|
147
|
+
const res = await $Http("/core/admin/detail", {}, [""]);
|
|
148
|
+
const userInfo = res?.data && typeof res.data === "object" ? res.data : {};
|
|
149
|
+
|
|
150
|
+
$Data.userInfo.nickname = userInfo.nickname || "管理员";
|
|
151
|
+
$Data.userInfo.role = userInfo.roleCode || userInfo.roleType || "超级管理员";
|
|
152
|
+
$Data.userInfo.avatar = normalizeAvatarUrl(userInfo.avatar);
|
|
153
|
+
$Data.userInfo.id = userInfo.id || 0;
|
|
154
|
+
$Data.userInfo.roleCode = userInfo.roleCode || "";
|
|
155
|
+
} catch (error) {
|
|
156
|
+
MessagePlugin.error(error.msg || error.message || "获取管理员信息失败");
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
137
160
|
async function fetchUserMenus() {
|
|
138
161
|
try {
|
|
139
162
|
const { data } = await $Http("/core/menu/all");
|
|
@@ -243,14 +266,28 @@ function handleSettings() {
|
|
|
243
266
|
router.push("/core/settings");
|
|
244
267
|
}
|
|
245
268
|
|
|
246
|
-
function onAvatarUploadSuccess(res) {
|
|
269
|
+
async function onAvatarUploadSuccess(res) {
|
|
247
270
|
if (res.response?.code === 0 && res.response?.data?.url) {
|
|
248
|
-
|
|
249
|
-
|
|
271
|
+
const avatarUrl = res.response.data.url;
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
if ($Data.userInfo.id) {
|
|
275
|
+
await $Http("/core/admin/upd", {
|
|
276
|
+
id: $Data.userInfo.id,
|
|
277
|
+
avatar: avatarUrl
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
$Data.userInfo.avatar = avatarUrl;
|
|
282
|
+
MessagePlugin.success("头像上传成功");
|
|
283
|
+
} catch (error) {
|
|
284
|
+
MessagePlugin.error(error.msg || error.message || "头像保存失败");
|
|
285
|
+
}
|
|
250
286
|
}
|
|
251
287
|
}
|
|
252
288
|
|
|
253
289
|
fetchUserMenus();
|
|
290
|
+
fetchUserInfo();
|
|
254
291
|
|
|
255
292
|
watch(
|
|
256
293
|
() => route.path,
|
package/package.json
CHANGED
package/views/jsconfig.json
CHANGED
|
@@ -1,15 +1,34 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
2
|
+
"$schema": "https://json.schemastore.org/jsconfig",
|
|
3
3
|
"compilerOptions": {
|
|
4
|
+
"target": "ESNext",
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"useDefineForClassFields": true,
|
|
8
|
+
"moduleDetection": "force",
|
|
9
|
+
"strict": true,
|
|
4
10
|
"noEmit": true,
|
|
5
|
-
"
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"noImplicitReturns": true,
|
|
13
|
+
"noImplicitOverride": true,
|
|
14
|
+
"noUncheckedSideEffectImports": true,
|
|
15
|
+
"noUnusedLocals": false,
|
|
16
|
+
"noUnusedParameters": false,
|
|
17
|
+
"noFallthroughCasesInSwitch": true,
|
|
18
|
+
"skipLibCheck": true,
|
|
19
|
+
"esModuleInterop": true,
|
|
20
|
+
"allowSyntheticDefaultImports": true,
|
|
21
|
+
"forceConsistentCasingInFileNames": true,
|
|
22
|
+
"resolveJsonModule": true,
|
|
6
23
|
"allowJs": true,
|
|
7
|
-
"
|
|
8
|
-
"
|
|
24
|
+
"checkJs": false,
|
|
25
|
+
"lib": ["ESNext"],
|
|
26
|
+
"noImplicitAny": false,
|
|
9
27
|
"paths": {
|
|
10
|
-
"@/*": ["
|
|
28
|
+
"@/*": ["./*"],
|
|
29
|
+
"*": ["./*"]
|
|
11
30
|
}
|
|
12
31
|
},
|
|
13
|
-
"include": ["**/*.
|
|
14
|
-
"exclude": ["node_modules", "dist", "
|
|
32
|
+
"include": ["**/*.js"],
|
|
33
|
+
"exclude": ["node_modules", "dist", "**/*.test.js"]
|
|
15
34
|
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<PageTableDetail class="page-gallery page-table" :columns="$Data.columns" :endpoints="$Data.endpoints" :table-slot-names="['thumbnail', 'fileSize', 'isImage', 'createdAt', 'updatedAt']">
|
|
3
|
+
<template #toolLeft="scope">
|
|
4
|
+
<TUpload :action="$Config.uploadPath" :headers="uploadHeaders" :show-image-file-name="false" :file-list-display="() => null" accept="image/*" @success="onUploadSuccess($event, scope.reload)">
|
|
5
|
+
<TButton theme="primary">
|
|
6
|
+
<template #icon>
|
|
7
|
+
<UploadIcon />
|
|
8
|
+
</template>
|
|
9
|
+
上传图片
|
|
10
|
+
</TButton>
|
|
11
|
+
</TUpload>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<template #toolRight="scope">
|
|
15
|
+
<TInput v-model="$Data.keyword" placeholder="搜索图片名称" clearable @enter="handleSearch(scope.reload)" @clear="handleSearch(scope.reload)">
|
|
16
|
+
<template #suffix-icon>
|
|
17
|
+
<SearchIcon />
|
|
18
|
+
</template>
|
|
19
|
+
</TInput>
|
|
20
|
+
<TButton shape="circle" @click="handleRefresh(scope.reload)">
|
|
21
|
+
<template #icon>
|
|
22
|
+
<RefreshIcon />
|
|
23
|
+
</template>
|
|
24
|
+
</TButton>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<template #thumbnail="{ row }">
|
|
28
|
+
<div class="image-cell">
|
|
29
|
+
<TImage class="image-thumb" :src="row.url" :alt="row.fileName" fit="cover" gallery overlay-trigger="hover" />
|
|
30
|
+
</div>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<template #fileSize="{ row }">
|
|
34
|
+
{{ formatFileSize(row.fileSize) }}
|
|
35
|
+
</template>
|
|
36
|
+
|
|
37
|
+
<template #isImage="{ row }">
|
|
38
|
+
<TTag v-if="row.isImage === 1" shape="round" theme="success" variant="light-outline">图片</TTag>
|
|
39
|
+
<TTag v-else shape="round" variant="light-outline">文件</TTag>
|
|
40
|
+
</template>
|
|
41
|
+
|
|
42
|
+
<template #createdAt="{ row }">
|
|
43
|
+
{{ formatDateTime(row.createdAt) }}
|
|
44
|
+
</template>
|
|
45
|
+
|
|
46
|
+
<template #updatedAt="{ row }">
|
|
47
|
+
{{ formatDateTime(row.updatedAt) }}
|
|
48
|
+
</template>
|
|
49
|
+
|
|
50
|
+
<template #detail="scope">
|
|
51
|
+
<div class="gallery-detail" v-if="scope.row">
|
|
52
|
+
<div class="detail-actions">
|
|
53
|
+
<TButton variant="outline" size="small" @click="copyUrl(scope.row.url)">复制链接</TButton>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<DetailPanel :data="scope.row" :fields="$Data.columns">
|
|
57
|
+
<template #thumbnail="slotScope">
|
|
58
|
+
<div class="detail-thumb-wrap">
|
|
59
|
+
<TImage class="detail-thumb" :src="slotScope.row.url" :alt="slotScope.row.fileName" fit="cover" gallery overlay-trigger="hover" />
|
|
60
|
+
</div>
|
|
61
|
+
</template>
|
|
62
|
+
<template #fileSize="slotScope">
|
|
63
|
+
{{ formatFileSize(slotScope.value) }}
|
|
64
|
+
</template>
|
|
65
|
+
<template #isImage="slotScope">
|
|
66
|
+
<TTag v-if="slotScope.value === 1" shape="round" theme="success" variant="light-outline">图片</TTag>
|
|
67
|
+
<TTag v-else shape="round" variant="light-outline">文件</TTag>
|
|
68
|
+
</template>
|
|
69
|
+
<template #createdAt="slotScope">
|
|
70
|
+
{{ formatDateTime(slotScope.value) }}
|
|
71
|
+
</template>
|
|
72
|
+
<template #updatedAt="slotScope">
|
|
73
|
+
{{ formatDateTime(slotScope.value) }}
|
|
74
|
+
</template>
|
|
75
|
+
<template #url="slotScope">
|
|
76
|
+
<a class="detail-link" :href="slotScope.value" target="_blank" rel="noreferrer">{{ slotScope.value }}</a>
|
|
77
|
+
</template>
|
|
78
|
+
</DetailPanel>
|
|
79
|
+
</div>
|
|
80
|
+
<div v-else class="gallery-detail-empty">请选择一张图片查看详情</div>
|
|
81
|
+
</template>
|
|
82
|
+
</PageTableDetail>
|
|
83
|
+
</template>
|
|
84
|
+
|
|
85
|
+
<script setup>
|
|
86
|
+
import { reactive } from "vue";
|
|
87
|
+
import { Button as TButton, Image as TImage, Input as TInput, MessagePlugin, Tag as TTag, Upload as TUpload } from "tdesign-vue-next";
|
|
88
|
+
import { RefreshIcon, SearchIcon, UploadIcon } from "tdesign-icons-vue-next";
|
|
89
|
+
|
|
90
|
+
import DetailPanel from "befly-admin-ui/components/detailPanel.vue";
|
|
91
|
+
import PageTableDetail from "befly-admin-ui/components/pageTableDetail.vue";
|
|
92
|
+
import { withDefaultColumns } from "befly-admin-ui/utils/withDefaultColumns";
|
|
93
|
+
import { $Config } from "@/plugins/config.js";
|
|
94
|
+
|
|
95
|
+
const uploadHeaders = {
|
|
96
|
+
Authorization: `Bearer ${localStorage.getItem($Config.tokenName) || ""}`
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const $Data = reactive({
|
|
100
|
+
keyword: "",
|
|
101
|
+
columns: withDefaultColumns([
|
|
102
|
+
{ colKey: "thumbnail", title: "缩略图", width: 96 },
|
|
103
|
+
{ colKey: "fileName", title: "文件名", fixed: "left", minWidth: 220 },
|
|
104
|
+
{ colKey: "fileExt", title: "扩展名", width: 100, format: formatFileExt },
|
|
105
|
+
{ colKey: "fileSize", title: "大小", width: 110 },
|
|
106
|
+
{ colKey: "isImage", title: "类型", width: 90 },
|
|
107
|
+
{ colKey: "createdAt", title: "创建时间", width: 170 },
|
|
108
|
+
{ colKey: "updatedAt", title: "更新时间", width: 170 },
|
|
109
|
+
{ colKey: "userId", title: "上传人", detail: true },
|
|
110
|
+
{ colKey: "fileType", title: "文件类型值", detail: true },
|
|
111
|
+
{ colKey: "fileKey", title: "文件标识", detail: true },
|
|
112
|
+
{ colKey: "filePath", title: "访问路径", detail: true },
|
|
113
|
+
{ colKey: "url", title: "完整地址", detail: true }
|
|
114
|
+
]),
|
|
115
|
+
endpoints: {
|
|
116
|
+
list: {
|
|
117
|
+
path: "/core/source/imageList",
|
|
118
|
+
dropValues: [""],
|
|
119
|
+
dropKeyValue: {
|
|
120
|
+
keyword: [""]
|
|
121
|
+
},
|
|
122
|
+
buildData: () => {
|
|
123
|
+
return {
|
|
124
|
+
keyword: $Data.keyword
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
function handleSearch(reload) {
|
|
132
|
+
reload({ keepSelection: false, resetPage: true });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function handleRefresh(reload) {
|
|
136
|
+
reload({ keepSelection: true });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function copyUrl(url) {
|
|
140
|
+
try {
|
|
141
|
+
await navigator.clipboard.writeText(url);
|
|
142
|
+
MessagePlugin.success("图片链接已复制");
|
|
143
|
+
} catch (error) {
|
|
144
|
+
MessagePlugin.error(error?.message || "复制失败");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function formatFileSize(fileSize) {
|
|
149
|
+
if (!Number.isFinite(fileSize) || fileSize <= 0) {
|
|
150
|
+
return "0 B";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (fileSize < 1024) {
|
|
154
|
+
return `${fileSize} B`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (fileSize < 1024 * 1024) {
|
|
158
|
+
return `${(fileSize / 1024).toFixed(1)} KB`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return `${(fileSize / 1024 / 1024).toFixed(1)} MB`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function formatFileExt(fileExt) {
|
|
165
|
+
if (typeof fileExt !== "string" || fileExt.length === 0) {
|
|
166
|
+
return "未知格式";
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return fileExt.replace(/^\./, "").toUpperCase();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function formatDateTime(value) {
|
|
173
|
+
if (!value) {
|
|
174
|
+
return "-";
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const date = new Date(value);
|
|
178
|
+
if (!Number.isFinite(date.getTime())) {
|
|
179
|
+
return value;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const year = date.getFullYear();
|
|
183
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
184
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
185
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
186
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
187
|
+
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
188
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function onUploadSuccess(res, reload) {
|
|
192
|
+
if (res.response?.code !== 0 || res.response?.data?.isImage !== 1) {
|
|
193
|
+
MessagePlugin.error(res.response?.msg || "上传失败");
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
MessagePlugin.success("上传成功");
|
|
198
|
+
reload({ keepSelection: false, resetPage: true });
|
|
199
|
+
}
|
|
200
|
+
</script>
|
|
201
|
+
|
|
202
|
+
<style scoped lang="scss">
|
|
203
|
+
.page-gallery {
|
|
204
|
+
:deep(.main-tool .left),
|
|
205
|
+
:deep(.main-tool .right) {
|
|
206
|
+
display: flex;
|
|
207
|
+
align-items: center;
|
|
208
|
+
gap: 12px;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
:deep(.main-tool .right .t-input) {
|
|
212
|
+
width: 280px;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.image-cell {
|
|
217
|
+
display: flex;
|
|
218
|
+
align-items: center;
|
|
219
|
+
width: 100%;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.image-thumb {
|
|
223
|
+
width: 100%;
|
|
224
|
+
height: 56px;
|
|
225
|
+
border-radius: 10px;
|
|
226
|
+
overflow: hidden;
|
|
227
|
+
background: #f3f6fb;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.gallery-detail {
|
|
231
|
+
display: flex;
|
|
232
|
+
flex-direction: column;
|
|
233
|
+
gap: 16px;
|
|
234
|
+
height: 100%;
|
|
235
|
+
padding: 12px;
|
|
236
|
+
overflow: auto;
|
|
237
|
+
background: var(--bg-color-container);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.detail-actions {
|
|
241
|
+
display: flex;
|
|
242
|
+
justify-content: flex-end;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.detail-thumb-wrap {
|
|
246
|
+
display: flex;
|
|
247
|
+
width: 100%;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.detail-thumb {
|
|
251
|
+
width: 100%;
|
|
252
|
+
height: 180px;
|
|
253
|
+
border-radius: 12px;
|
|
254
|
+
overflow: hidden;
|
|
255
|
+
background: #f3f6fb;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.detail-link {
|
|
259
|
+
color: var(--primary-color);
|
|
260
|
+
word-break: break-all;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.gallery-detail-empty {
|
|
264
|
+
display: flex;
|
|
265
|
+
align-items: center;
|
|
266
|
+
justify-content: center;
|
|
267
|
+
height: 100%;
|
|
268
|
+
color: var(--text-placeholder);
|
|
269
|
+
background: var(--bg-color-container);
|
|
270
|
+
}
|
|
271
|
+
</style>
|