befly-admin 3.13.26 → 3.14.5
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 +7 -7
- package/src/main.js +3 -0
- package/src/plugins/config.js +5 -0
- package/src/plugins/errorReport.js +128 -0
- package/src/plugins/router.js +16 -2
- package/src/views/simpleTable/components/edit.vue +123 -0
- package/src/views/simpleTable/index.vue +127 -0
- package/vite.config.js +8 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly-admin",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"gitHead": "
|
|
3
|
+
"version": "3.14.5",
|
|
4
|
+
"gitHead": "ba7fa7cc0534c48de2df4970d69a14853cf6b2a6",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Befly Admin - 基于 Vue3 + TDesign Vue Next 的后台管理系统",
|
|
7
7
|
"files": [
|
|
@@ -28,14 +28,14 @@
|
|
|
28
28
|
"preview": "bunx --bun vite preview"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"befly-admin-ui": "1.8.
|
|
31
|
+
"befly-admin-ui": "^1.8.32",
|
|
32
32
|
"befly-shared": "2.0.3",
|
|
33
|
-
"befly-vite": "^1.5.
|
|
33
|
+
"befly-vite": "^1.5.15",
|
|
34
34
|
"pinia": "^3.0.4",
|
|
35
35
|
"tdesign-icons-vue-next": "^0.4.0",
|
|
36
|
-
"tdesign-vue-next": "^1.18.
|
|
37
|
-
"vite": "^8.0.0
|
|
38
|
-
"vue": "^3.5.
|
|
36
|
+
"tdesign-vue-next": "^1.18.5",
|
|
37
|
+
"vite": "^8.0.0",
|
|
38
|
+
"vue": "^3.5.30",
|
|
39
39
|
"vue-router": "^5.0.3"
|
|
40
40
|
},
|
|
41
41
|
"engines": {
|
package/src/main.js
CHANGED
|
@@ -5,6 +5,7 @@ import "befly-admin-ui/styles/variables.scss";
|
|
|
5
5
|
// 引入全局基础样式(reset、通用类、滚动条等)
|
|
6
6
|
import "@/styles/global.scss";
|
|
7
7
|
import App from "./App.vue";
|
|
8
|
+
import { setupErrorReport } from "@/plugins/errorReport.js";
|
|
8
9
|
|
|
9
10
|
const app = createApp(App);
|
|
10
11
|
|
|
@@ -14,4 +15,6 @@ app.use(createPinia());
|
|
|
14
15
|
// 使用路由
|
|
15
16
|
app.use($Router);
|
|
16
17
|
|
|
18
|
+
setupErrorReport(app);
|
|
19
|
+
|
|
17
20
|
app.mount("#app");
|
package/src/plugins/config.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import adminPackageInfo from "../../package.json";
|
|
2
|
+
|
|
1
3
|
export const $Config = {
|
|
2
4
|
appTitle: "野蜂飞舞",
|
|
3
5
|
appDesc: "轻量级业务快速开发框架",
|
|
6
|
+
productName: "野蜂飞舞",
|
|
7
|
+
productCode: adminPackageInfo.name,
|
|
8
|
+
productVersion: adminPackageInfo.version,
|
|
4
9
|
loginFootnote: "",
|
|
5
10
|
tokenName: "befly-admin-token",
|
|
6
11
|
loginPath: "/core/login",
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { $Http } from "@/plugins/http.js";
|
|
2
|
+
import { $Config } from "@/plugins/config.js";
|
|
3
|
+
import { $Router } from "@/plugins/router.js";
|
|
4
|
+
|
|
5
|
+
let isReportingError = false;
|
|
6
|
+
let lastErrorKey = "";
|
|
7
|
+
let lastErrorTime = 0;
|
|
8
|
+
|
|
9
|
+
function getRouteInfo() {
|
|
10
|
+
const currentRoute = $Router.currentRoute.value || {};
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
pagePath: String(currentRoute.fullPath || currentRoute.path || window.location.hash || window.location.pathname || ""),
|
|
14
|
+
pageName: String(currentRoute.name || currentRoute.meta?.title || currentRoute.path || document.title || "")
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getErrorMessage(error) {
|
|
19
|
+
if (typeof error === "string") {
|
|
20
|
+
return error;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (error && typeof error.message === "string") {
|
|
24
|
+
return error.message;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return "未知错误";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getErrorDetail(error) {
|
|
31
|
+
if (!error) {
|
|
32
|
+
return "";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (typeof error === "string") {
|
|
36
|
+
return error;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (typeof error.stack === "string" && error.stack) {
|
|
40
|
+
return error.stack;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
return JSON.stringify(error);
|
|
45
|
+
} catch {
|
|
46
|
+
return String(error);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function shouldSkipError(message, detail, errorType) {
|
|
51
|
+
const now = Date.now();
|
|
52
|
+
const key = `${errorType}|${message}|${detail}`;
|
|
53
|
+
if (key === lastErrorKey && now - lastErrorTime < 3000) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
lastErrorKey = key;
|
|
58
|
+
lastErrorTime = now;
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function reportClientError(errorType, message, detail) {
|
|
63
|
+
const safeMessage = String(message || "未知错误").slice(0, 500);
|
|
64
|
+
const safeDetail = String(detail || "").slice(0, 5000);
|
|
65
|
+
if (shouldSkipError(safeMessage, safeDetail, errorType)) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (isReportingError) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const routeInfo = getRouteInfo();
|
|
74
|
+
isReportingError = true;
|
|
75
|
+
|
|
76
|
+
void $Http(
|
|
77
|
+
"/core/tongJi/errorReport",
|
|
78
|
+
{
|
|
79
|
+
pagePath: routeInfo.pagePath,
|
|
80
|
+
pageName: routeInfo.pageName,
|
|
81
|
+
source: "admin",
|
|
82
|
+
productName: $Config.productName,
|
|
83
|
+
productCode: $Config.productCode,
|
|
84
|
+
productVersion: $Config.productVersion,
|
|
85
|
+
errorType: String(errorType || "unknown"),
|
|
86
|
+
message: safeMessage,
|
|
87
|
+
detail: safeDetail
|
|
88
|
+
},
|
|
89
|
+
[""]
|
|
90
|
+
)
|
|
91
|
+
.catch(() => {
|
|
92
|
+
// 静默失败:避免错误上报再次制造错误
|
|
93
|
+
})
|
|
94
|
+
.finally(() => {
|
|
95
|
+
isReportingError = false;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function setupErrorReport(app) {
|
|
100
|
+
app.config.errorHandler = (error, _instance, info) => {
|
|
101
|
+
const message = getErrorMessage(error);
|
|
102
|
+
const detail = `${String(info || "")}${info ? "\n" : ""}${getErrorDetail(error)}`;
|
|
103
|
+
|
|
104
|
+
reportClientError("vue", message, detail);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
window.addEventListener("error", (event) => {
|
|
108
|
+
const parts = [];
|
|
109
|
+
if (event.filename) {
|
|
110
|
+
parts.push(`file: ${event.filename}`);
|
|
111
|
+
}
|
|
112
|
+
if (event.lineno || event.colno) {
|
|
113
|
+
parts.push(`line: ${event.lineno || 0}, col: ${event.colno || 0}`);
|
|
114
|
+
}
|
|
115
|
+
const stack = getErrorDetail(event.error);
|
|
116
|
+
if (stack) {
|
|
117
|
+
parts.push(stack);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
reportClientError("window", getErrorMessage(event.message || event.error), parts.join("\n"));
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
window.addEventListener("unhandledrejection", (event) => {
|
|
124
|
+
const reason = event.reason;
|
|
125
|
+
|
|
126
|
+
reportClientError("promise", getErrorMessage(reason), getErrorDetail(reason));
|
|
127
|
+
});
|
|
128
|
+
}
|
package/src/plugins/router.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Layouts } from "befly-vite";
|
|
|
2
2
|
import { createRouter, createWebHashHistory } from "vue-router";
|
|
3
3
|
import { routes } from "vue-router/auto-routes";
|
|
4
4
|
import { $Config } from "@/plugins/config.js";
|
|
5
|
+
import { $Http } from "@/plugins/http.js";
|
|
5
6
|
|
|
6
7
|
// 应用自定义布局系统(同时可选注入根路径重定向)
|
|
7
8
|
const finalRoutes = Layouts(routes, $Config.homePath, (layoutName) => {
|
|
@@ -54,6 +55,19 @@ $Router.beforeEach((to, _from) => {
|
|
|
54
55
|
});
|
|
55
56
|
|
|
56
57
|
// 路由就绪后处理
|
|
57
|
-
$Router.afterEach((
|
|
58
|
-
|
|
58
|
+
$Router.afterEach((to) => {
|
|
59
|
+
void $Http(
|
|
60
|
+
"/core/tongJi/visitReport",
|
|
61
|
+
{
|
|
62
|
+
pagePath: to.fullPath || to.path || "",
|
|
63
|
+
pageName: String(to.name || to.meta?.title || to.path || ""),
|
|
64
|
+
source: "admin",
|
|
65
|
+
productName: $Config.productName,
|
|
66
|
+
productCode: $Config.productCode,
|
|
67
|
+
productVersion: $Config.productVersion
|
|
68
|
+
},
|
|
69
|
+
[""]
|
|
70
|
+
).catch(() => {
|
|
71
|
+
// 静默失败:不阻断路由切换
|
|
72
|
+
});
|
|
59
73
|
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<PageDialog v-model="dialogVisible" :title="$Prop.actionType === 'upd' ? '编辑角色' : '新增角色'" :confirm-loading="$Data.submitting" @confirm="onSubmit">
|
|
3
|
+
<div class="comp-simple-role-edit">
|
|
4
|
+
<TForm :model="$Data.formData" label-width="100px" label-position="left" :rules="$Data2.formRules" ref="formRef">
|
|
5
|
+
<TFormItem label="角色名称" prop="name">
|
|
6
|
+
<TInput v-model="$Data.formData.name" placeholder="请输入角色名称" />
|
|
7
|
+
</TFormItem>
|
|
8
|
+
<TFormItem label="角色代码" prop="code">
|
|
9
|
+
<TInput v-model="$Data.formData.code" placeholder="请输入角色代码,如:operator" />
|
|
10
|
+
</TFormItem>
|
|
11
|
+
<TFormItem label="角色描述" prop="description">
|
|
12
|
+
<TInput v-model="$Data.formData.description" placeholder="请输入角色描述" />
|
|
13
|
+
</TFormItem>
|
|
14
|
+
<TFormItem label="排序" prop="sort">
|
|
15
|
+
<TInputNumber v-model="$Data.formData.sort" :min="0" :max="9999" />
|
|
16
|
+
</TFormItem>
|
|
17
|
+
</TForm>
|
|
18
|
+
</div>
|
|
19
|
+
</PageDialog>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script setup>
|
|
23
|
+
import PageDialog from "befly-admin-ui/components/pageDialog.vue";
|
|
24
|
+
import { fieldClear } from "befly-admin-ui/utils/fieldClear";
|
|
25
|
+
|
|
26
|
+
const $Prop = defineProps({
|
|
27
|
+
modelValue: {
|
|
28
|
+
type: Boolean,
|
|
29
|
+
default: false
|
|
30
|
+
},
|
|
31
|
+
actionType: {
|
|
32
|
+
type: String,
|
|
33
|
+
default: "add"
|
|
34
|
+
},
|
|
35
|
+
rowData: {
|
|
36
|
+
type: Object,
|
|
37
|
+
default: {}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const $Emit = defineEmits(["update:modelValue", "success"]);
|
|
42
|
+
const formRef = ref(null);
|
|
43
|
+
|
|
44
|
+
const $Data = reactive({
|
|
45
|
+
submitting: false,
|
|
46
|
+
formData: {
|
|
47
|
+
id: 0,
|
|
48
|
+
name: "",
|
|
49
|
+
code: "",
|
|
50
|
+
description: "",
|
|
51
|
+
sort: 0
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const $Data2 = reactive({
|
|
56
|
+
formRules: {
|
|
57
|
+
name: [{ required: true, message: "请输入角色名称", trigger: "blur" }],
|
|
58
|
+
code: [
|
|
59
|
+
{ required: true, message: "请输入角色代码", trigger: "blur" },
|
|
60
|
+
{ pattern: /^[a-zA-Z0-9_]+$/, message: "角色代码只能包含字母、数字和下划线", trigger: "blur" }
|
|
61
|
+
],
|
|
62
|
+
sort: [{ type: "number", message: "排序必须是数字", trigger: "blur" }]
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const dialogVisible = computed({
|
|
67
|
+
get: () => $Prop.modelValue,
|
|
68
|
+
set: (value) => {
|
|
69
|
+
$Emit("update:modelValue", value);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
function initData() {
|
|
74
|
+
if ($Prop.actionType === "upd" && $Prop.rowData.id) {
|
|
75
|
+
$Data.formData = Object.assign({}, $Prop.rowData);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
$Data.formData.id = 0;
|
|
80
|
+
$Data.formData.name = "";
|
|
81
|
+
$Data.formData.code = "";
|
|
82
|
+
$Data.formData.description = "";
|
|
83
|
+
$Data.formData.sort = 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function onSubmit(context) {
|
|
87
|
+
try {
|
|
88
|
+
const form = formRef.value;
|
|
89
|
+
if (!form) {
|
|
90
|
+
MessagePlugin.warning("表单未就绪");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const valid = await form.validate();
|
|
95
|
+
if (!valid) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
$Data.submitting = true;
|
|
100
|
+
const payload = $Prop.actionType === "upd" ? $Data.formData : fieldClear($Data.formData, { omitKeys: ["id"] });
|
|
101
|
+
const response = await $Http($Prop.actionType === "upd" ? "/core/role/upd" : "/core/role/ins", payload);
|
|
102
|
+
|
|
103
|
+
MessagePlugin.success(response.msg || "操作成功");
|
|
104
|
+
$Emit("success");
|
|
105
|
+
|
|
106
|
+
if (context && typeof context.close === "function") {
|
|
107
|
+
context.close();
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
MessagePlugin.error(error.msg || error.message || "提交失败");
|
|
111
|
+
} finally {
|
|
112
|
+
$Data.submitting = false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
initData();
|
|
117
|
+
</script>
|
|
118
|
+
|
|
119
|
+
<style scoped lang="scss">
|
|
120
|
+
.comp-simple-role-edit {
|
|
121
|
+
padding-top: 6px;
|
|
122
|
+
}
|
|
123
|
+
</style>
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<PageTableDetail class="page-simple-table page-table" :columns="$Data.columns" :endpoints="$Data.endpoints">
|
|
3
|
+
<template #toolLeft>
|
|
4
|
+
<TButton theme="primary" @click="onAdd">
|
|
5
|
+
<template #icon>
|
|
6
|
+
<AddIcon />
|
|
7
|
+
</template>
|
|
8
|
+
</TButton>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<template #toolRight="scope">
|
|
12
|
+
<TButton shape="circle" @click="onReload(scope.reload)">
|
|
13
|
+
<template #icon>
|
|
14
|
+
<RefreshIcon />
|
|
15
|
+
</template>
|
|
16
|
+
</TButton>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<template #operation="{ row, deleteRow }">
|
|
20
|
+
<TDropdown trigger="click" placement="bottom-right" @click="onDropdownAction($event, row, deleteRow)">
|
|
21
|
+
<TButton theme="primary" size="small">
|
|
22
|
+
操作
|
|
23
|
+
<template #suffix><ChevronDownIcon /></template>
|
|
24
|
+
</TButton>
|
|
25
|
+
<TDropdownMenu slot="dropdown">
|
|
26
|
+
<TDropdownItem value="upd">
|
|
27
|
+
<EditIcon />
|
|
28
|
+
编辑
|
|
29
|
+
</TDropdownItem>
|
|
30
|
+
<TDropdownItem value="del" :divider="true">
|
|
31
|
+
<DeleteIcon style="width: 14px; height: 14px; margin-right: 6px" />
|
|
32
|
+
删除
|
|
33
|
+
</TDropdownItem>
|
|
34
|
+
</TDropdownMenu>
|
|
35
|
+
</TDropdown>
|
|
36
|
+
</template>
|
|
37
|
+
|
|
38
|
+
<template #dialogs="scope">
|
|
39
|
+
<EditDialog v-if="$Data.editVisible" v-model="$Data.editVisible" :action-type="$Data.actionType" :row-data="$Data.rowData" @success="onDialogSuccess(scope.reload)" />
|
|
40
|
+
</template>
|
|
41
|
+
</PageTableDetail>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<script setup>
|
|
45
|
+
import EditDialog from "./components/edit.vue";
|
|
46
|
+
import PageTableDetail from "befly-admin-ui/components/pageTableDetail.vue";
|
|
47
|
+
import { withDefaultColumns } from "befly-admin-ui/utils/withDefaultColumns";
|
|
48
|
+
|
|
49
|
+
const $Data = reactive({
|
|
50
|
+
columns: withDefaultColumns([
|
|
51
|
+
{ colKey: "name", title: "角色名称" },
|
|
52
|
+
{ colKey: "code", title: "角色代码", width: 180 },
|
|
53
|
+
{ colKey: "description", title: "角色描述" },
|
|
54
|
+
{ colKey: "sort", title: "排序", width: 100 },
|
|
55
|
+
{ colKey: "operation", title: "操作" }
|
|
56
|
+
]),
|
|
57
|
+
endpoints: {
|
|
58
|
+
list: {
|
|
59
|
+
path: "/core/role/list",
|
|
60
|
+
dropValues: [""]
|
|
61
|
+
},
|
|
62
|
+
delete: {
|
|
63
|
+
path: "/core/role/del",
|
|
64
|
+
idKey: "id",
|
|
65
|
+
confirm: (row) => {
|
|
66
|
+
return {
|
|
67
|
+
header: "确认删除",
|
|
68
|
+
body: `确认删除角色“${row.name}”吗?`,
|
|
69
|
+
confirmBtn: "删除",
|
|
70
|
+
status: "warning"
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
editVisible: false,
|
|
76
|
+
actionType: "add",
|
|
77
|
+
rowData: {}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
function onAdd() {
|
|
81
|
+
onAction("add", {});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function onReload(reload) {
|
|
85
|
+
reload({ keepSelection: true });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function onDialogSuccess(reload) {
|
|
89
|
+
reload({ keepSelection: true });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function onAction(type, row) {
|
|
93
|
+
if (type === "add") {
|
|
94
|
+
$Data.actionType = "add";
|
|
95
|
+
$Data.rowData = {};
|
|
96
|
+
$Data.editVisible = true;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (type === "upd") {
|
|
101
|
+
$Data.actionType = "upd";
|
|
102
|
+
$Data.rowData = Object.assign({}, row);
|
|
103
|
+
$Data.editVisible = true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function onDropdownAction(data, row, deleteRow) {
|
|
108
|
+
const record = data;
|
|
109
|
+
const rawValue = record && record["value"] ? record["value"] : "";
|
|
110
|
+
const command = rawValue ? String(rawValue) : "";
|
|
111
|
+
if (command === "del") {
|
|
112
|
+
deleteRow(row);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
onAction(command, row);
|
|
116
|
+
}
|
|
117
|
+
</script>
|
|
118
|
+
|
|
119
|
+
<style scoped lang="scss">
|
|
120
|
+
.page-simple-table {
|
|
121
|
+
.main-tool .right {
|
|
122
|
+
display: flex;
|
|
123
|
+
gap: 8px;
|
|
124
|
+
align-items: center;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
</style>
|
package/vite.config.js
CHANGED
|
@@ -3,5 +3,12 @@ import { fileURLToPath } from "node:url";
|
|
|
3
3
|
import { createBeflyViteConfig } from "befly-vite";
|
|
4
4
|
|
|
5
5
|
export default createBeflyViteConfig({
|
|
6
|
-
root: fileURLToPath(new URL(".", import.meta.url))
|
|
6
|
+
root: fileURLToPath(new URL(".", import.meta.url)),
|
|
7
|
+
analyzer: true,
|
|
8
|
+
devtool: false,
|
|
9
|
+
viteConfig: {
|
|
10
|
+
server: {
|
|
11
|
+
port: 5206
|
|
12
|
+
}
|
|
13
|
+
}
|
|
7
14
|
});
|