befly-admin-ui 1.8.14

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.
Files changed (46) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +188 -0
  3. package/jsconfig.json +14 -0
  4. package/package.json +51 -0
  5. package/styles/variables.scss +148 -0
  6. package/utils/arrayToTree.js +115 -0
  7. package/utils/cleanParams.js +29 -0
  8. package/utils/fieldClear.js +62 -0
  9. package/utils/genShortId.js +12 -0
  10. package/utils/hashPassword.js +9 -0
  11. package/utils/scanViewsDir.js +120 -0
  12. package/utils/withDefaultColumns.js +46 -0
  13. package/views/config/dict/components/edit.vue +120 -0
  14. package/views/config/dict/index.vue +188 -0
  15. package/views/config/dictType/components/edit.vue +110 -0
  16. package/views/config/dictType/index.vue +153 -0
  17. package/views/config/index.vue +3 -0
  18. package/views/config/system/components/edit.vue +184 -0
  19. package/views/config/system/index.vue +188 -0
  20. package/views/index/components/addonList.vue +148 -0
  21. package/views/index/components/environmentInfo.vue +116 -0
  22. package/views/index/components/operationLogs.vue +127 -0
  23. package/views/index/components/performanceMetrics.vue +153 -0
  24. package/views/index/components/quickActions.vue +30 -0
  25. package/views/index/components/serviceStatus.vue +197 -0
  26. package/views/index/components/systemNotifications.vue +144 -0
  27. package/views/index/components/systemOverview.vue +194 -0
  28. package/views/index/components/systemResources.vue +121 -0
  29. package/views/index/components/userInfo.vue +210 -0
  30. package/views/index/index.vue +67 -0
  31. package/views/jsconfig.json +15 -0
  32. package/views/log/email/index.vue +221 -0
  33. package/views/log/index.vue +3 -0
  34. package/views/log/login/index.vue +95 -0
  35. package/views/log/operate/index.vue +169 -0
  36. package/views/login_1/index.vue +400 -0
  37. package/views/people/admin/components/edit.vue +173 -0
  38. package/views/people/admin/index.vue +121 -0
  39. package/views/people/index.vue +3 -0
  40. package/views/permission/api/index.vue +146 -0
  41. package/views/permission/index.vue +3 -0
  42. package/views/permission/menu/index.vue +109 -0
  43. package/views/permission/role/components/api.vue +371 -0
  44. package/views/permission/role/components/edit.vue +143 -0
  45. package/views/permission/role/components/menu.vue +310 -0
  46. package/views/permission/role/index.vue +175 -0
@@ -0,0 +1,188 @@
1
+ <template>
2
+ <PagedTableDetail class="page-sys-config page-table" :columns="$Data.columns" :endpoints="$Data.endpoints" :table-slot-names="['isSystem', 'valueType', 'state']">
3
+ <template #toolLeft="scope">
4
+ <TButton theme="primary" @click="onAdd">
5
+ <template #icon>
6
+ <AddIcon />
7
+ </template>
8
+ 新增配置
9
+ </TButton>
10
+ <TSelect v-model="$Data.filter.group" placeholder="配置分组" clearable style="width: 150px" @change="handleFilter(scope.reload)">
11
+ <TOption v-for="item in $Data.groupOptions" :key="item" :label="item" :value="item" />
12
+ </TSelect>
13
+ </template>
14
+
15
+ <template #toolRight="scope">
16
+ <TButton shape="circle" @click="onReload(scope.reload)">
17
+ <template #icon>
18
+ <RefreshIcon />
19
+ </template>
20
+ </TButton>
21
+ </template>
22
+
23
+ <template #isSystem="{ row }">
24
+ <TTag v-if="row.isSystem === 1" shape="round" theme="warning" variant="light-outline">系统</TTag>
25
+ <TTag v-else shape="round" variant="light-outline">自定义</TTag>
26
+ </template>
27
+
28
+ <template #valueType="{ row }">
29
+ <TTag shape="round" variant="light-outline">{{ row.valueType }}</TTag>
30
+ </template>
31
+
32
+ <template #state="{ row }">
33
+ <TTag v-if="row.state === 1" shape="round" theme="success" variant="light-outline">正常</TTag>
34
+ <TTag v-else-if="row.state === 2" shape="round" theme="warning" variant="light-outline">禁用</TTag>
35
+ </template>
36
+
37
+ <template #operation="{ row, deleteRow }">
38
+ <TDropdown trigger="click" placement="bottom-right" @click="onDropdownAction($event, row, deleteRow)">
39
+ <TButton theme="primary" size="small">
40
+ 操作
41
+ <template #suffix><ChevronDownIcon /></template>
42
+ </TButton>
43
+ <TDropdownMenu slot="dropdown">
44
+ <TDropdownItem value="upd">
45
+ <EditIcon />
46
+ 编辑
47
+ </TDropdownItem>
48
+ <TDropdownItem v-if="row.isSystem !== 1" value="del" :divider="true">
49
+ <DeleteIcon style="width: 14px; height: 14px; margin-right: 6px" />
50
+ 删除
51
+ </TDropdownItem>
52
+ </TDropdownMenu>
53
+ </TDropdown>
54
+ </template>
55
+
56
+ <template #detail="scope">
57
+ <DetailPanel :data="scope.row" :fields="$Data.columns">
58
+ <template #isSystem="slotScope">
59
+ <TTag v-if="slotScope.value === 1" shape="round" theme="warning" variant="light-outline">系统配置</TTag>
60
+ <TTag v-else shape="round" variant="light-outline">自定义配置</TTag>
61
+ </template>
62
+ <template #valueType="slotScope">
63
+ <TTag shape="round" variant="light-outline">{{ slotScope.value }}</TTag>
64
+ </template>
65
+ <template #value="slotScope">
66
+ <pre class="config-value">{{ slotScope.value }}</pre>
67
+ </template>
68
+ </DetailPanel>
69
+ </template>
70
+
71
+ <template #dialogs="scope">
72
+ <EditDialog v-if="$Data.editVisible" v-model="$Data.editVisible" :action-type="$Data.actionType" :row-data="$Data.rowData" @success="onDialogSuccess(scope.reload)" />
73
+ </template>
74
+ </PagedTableDetail>
75
+ </template>
76
+
77
+ <script setup lang="ts">
78
+ import { reactive } from "vue";
79
+ import { Button as TButton, Dropdown as TDropdown, DropdownItem as TDropdownItem, DropdownMenu as TDropdownMenu, Option as TOption, Select as TSelect, Tag as TTag } from "tdesign-vue-next";
80
+ import { AddIcon, ChevronDownIcon, DeleteIcon, EditIcon, RefreshIcon } from "tdesign-icons-vue-next";
81
+ import EditDialog from "./components/edit.vue";
82
+ import DetailPanel from "@/components/detailPanel.vue";
83
+ import PagedTableDetail from "@/components/pagedTableDetail.vue";
84
+ import { withDefaultColumns } from "befly-admin-ui/utils/withDefaultColumns";
85
+
86
+ // 响应式数据
87
+ const $Data = reactive({
88
+ columns: withDefaultColumns([
89
+ { colKey: "name", title: "配置名称", fixed: "left", width: 150 },
90
+ { colKey: "code", title: "配置代码", ellipsis: true },
91
+ { colKey: "value", title: "配置值", ellipsis: true, width: 200 },
92
+ { colKey: "valueType", title: "值类型", width: 100 },
93
+ { colKey: "group", title: "分组", width: 100 },
94
+ { colKey: "sort", title: "排序", width: 80 },
95
+ { colKey: "isSystem", title: "类型", width: 80 },
96
+ { colKey: "state", title: "状态", width: 80 },
97
+ { colKey: "operation", title: "操作", width: 100 },
98
+ { colKey: "description", title: "描述说明", detail: true }
99
+ ]),
100
+ endpoints: {
101
+ list: {
102
+ path: "/core/sysConfig/list",
103
+ dropValues: [0, ""],
104
+ dropKeyValue: {
105
+ group: [""]
106
+ },
107
+ buildData: () => {
108
+ return {
109
+ group: $Data.filter.group
110
+ };
111
+ }
112
+ },
113
+ delete: {
114
+ path: "/core/sysConfig/del",
115
+ idKey: "id",
116
+ confirm: (row) => {
117
+ return {
118
+ header: "确认删除",
119
+ body: `确认删除配置“${row.name}”吗?`,
120
+ confirmBtn: "删除",
121
+ status: "warning"
122
+ };
123
+ }
124
+ }
125
+ },
126
+ editVisible: false,
127
+ actionType: "add",
128
+ rowData: {},
129
+ filter: {
130
+ group: ""
131
+ },
132
+ groupOptions: ["基础配置", "邮件配置", "存储配置", "安全配置", "其他"]
133
+ });
134
+
135
+ function onAdd(): void {
136
+ onAction("add", {});
137
+ }
138
+
139
+ function handleFilter(reload: (options: { keepSelection?: boolean; resetPage?: boolean }) => void): void {
140
+ reload({ keepSelection: false, resetPage: true });
141
+ }
142
+
143
+ function onReload(reload: (options: { keepSelection?: boolean }) => void): void {
144
+ reload({ keepSelection: true });
145
+ }
146
+
147
+ function onDialogSuccess(reload: (options: { keepSelection?: boolean }) => void): void {
148
+ reload({ keepSelection: true });
149
+ }
150
+
151
+ function onAction(command: string, rowData: Record<string, unknown>): void {
152
+ $Data.actionType = command;
153
+ if (command === "add") {
154
+ $Data.rowData = {};
155
+ } else {
156
+ $Data.rowData = Object.assign({}, rowData);
157
+ }
158
+
159
+ if (command === "add" || command === "upd") {
160
+ $Data.editVisible = true;
161
+ }
162
+ }
163
+
164
+ function onDropdownAction(data: unknown, rowData: Record<string, unknown>, deleteRow: (row: Record<string, unknown>) => void): void {
165
+ const record = data as Record<string, unknown>;
166
+ const rawValue = record && record["value"] ? record["value"] : "";
167
+ const cmd = rawValue ? String(rawValue) : "";
168
+ if (cmd === "del") {
169
+ deleteRow(rowData);
170
+ return;
171
+ }
172
+ onAction(cmd, rowData);
173
+ }
174
+ </script>
175
+
176
+ <style scoped lang="scss">
177
+ .config-value {
178
+ margin: 0;
179
+ padding: 8px;
180
+ background: var(--td-bg-color-container);
181
+ border-radius: 4px;
182
+ font-size: 12px;
183
+ max-height: 150px;
184
+ overflow: auto;
185
+ white-space: pre-wrap;
186
+ word-break: break-all;
187
+ }
188
+ </style>
@@ -0,0 +1,148 @@
1
+ <template>
2
+ <div class="section-block">
3
+ <div class="section-header flex items-center gap-2">
4
+ <MenuIcon />
5
+ <h2>已安装插件</h2>
6
+ </div>
7
+ <div class="section-content">
8
+ <div class="addon-list">
9
+ <div v-for="addon in addonList" :key="addon.name" class="addon-item">
10
+ <div class="addon-icon">
11
+ <SystemStorageIcon />
12
+ </div>
13
+ <div class="addon-info">
14
+ <div class="addon-title">
15
+ <span class="addon-name">{{ addon.title }}</span>
16
+ <t-tag type="success" size="small">{{ addon.version }}</t-tag>
17
+ </div>
18
+ <div class="addon-desc">{{ addon.description }}</div>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ </div>
23
+ </div>
24
+ </template>
25
+
26
+ <script setup lang="ts">
27
+ import { reactive } from "vue";
28
+ import { MenuIcon, SystemStorageIcon } from "tdesign-icons-vue-next";
29
+ import { $Http } from "@/plugins/http";
30
+
31
+ type AddonListItem = {
32
+ name: string;
33
+ title: string;
34
+ version: string;
35
+ description: string;
36
+ };
37
+
38
+ // 组件内部数据
39
+ const addonList = reactive<AddonListItem[]>([]);
40
+
41
+ // 获取数据
42
+ const fetchData = async () => {
43
+ try {
44
+ const { data } = await $Http.post(
45
+ "/core/dashboard/addonList",
46
+ {},
47
+ {
48
+ dropValues: [""]
49
+ }
50
+ );
51
+ addonList.splice(0, addonList.length, ...data);
52
+ } catch (_error) {
53
+ // 静默失败:不阻断页面展示
54
+ }
55
+ };
56
+
57
+ fetchData();
58
+ </script>
59
+
60
+ <style scoped lang="scss">
61
+ .addon-list {
62
+ display: flex;
63
+ flex-direction: column;
64
+ gap: 8px;
65
+
66
+ .addon-item {
67
+ position: relative;
68
+ background: var(--bg-color-container);
69
+ border: 1px solid var(--border-color);
70
+ border-left: 3px solid var(--primary-color);
71
+ border-radius: var(--border-radius-small);
72
+ padding: 10px 12px;
73
+ display: flex;
74
+ align-items: center;
75
+ gap: 10px;
76
+ transition: all 0.3s;
77
+
78
+ &:hover {
79
+ border-left-color: var(--success-color);
80
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
81
+ transform: translateY(-2px);
82
+ }
83
+
84
+ .addon-status-badge {
85
+ position: absolute;
86
+ top: 8px;
87
+ right: 8px;
88
+ width: 8px;
89
+ height: 8px;
90
+ border-radius: 50%;
91
+ background: var(--text-disabled);
92
+ transition: all 0.3s;
93
+
94
+ &::after {
95
+ content: "";
96
+ position: absolute;
97
+ top: -2px;
98
+ right: -2px;
99
+ width: 8px;
100
+ height: 8px;
101
+ border-radius: 50%;
102
+ background: var(--success-color);
103
+ box-shadow: 0 0 0 2px rgba(var(--success-color-rgb), 0.2);
104
+ }
105
+ }
106
+
107
+ .addon-icon {
108
+ width: 32px;
109
+ height: 32px;
110
+ background: linear-gradient(135deg, var(--primary-color), #764ba2);
111
+ border-radius: var(--border-radius-small);
112
+ display: flex;
113
+ align-items: center;
114
+ justify-content: center;
115
+ color: white;
116
+ flex-shrink: 0;
117
+ }
118
+
119
+ .addon-info {
120
+ flex: 1;
121
+ min-width: 0;
122
+ padding-right: 16px;
123
+
124
+ .addon-title {
125
+ display: flex;
126
+ align-items: center;
127
+ gap: 6px;
128
+ margin-bottom: 2px;
129
+
130
+ .addon-name {
131
+ font-size: 14px;
132
+ font-weight: 600;
133
+ color: var(--text-primary);
134
+ }
135
+ }
136
+
137
+ .addon-desc {
138
+ font-size: 14px;
139
+ color: var(--text-secondary);
140
+ line-height: 1.3;
141
+ overflow: hidden;
142
+ text-overflow: ellipsis;
143
+ white-space: nowrap;
144
+ }
145
+ }
146
+ }
147
+ }
148
+ </style>
@@ -0,0 +1,116 @@
1
+ <template>
2
+ <div class="section-block">
3
+ <div class="section-header flex items-center gap-2">
4
+ <ServerIcon />
5
+ <h2>运行环境</h2>
6
+ </div>
7
+ <div class="section-content">
8
+ <div class="env-grid-compact">
9
+ <div class="env-compact-item">
10
+ <span class="env-label">操作系统</span>
11
+ <span class="env-value">{{ environmentInfo.os }}</span>
12
+ </div>
13
+ <div class="env-compact-item">
14
+ <span class="env-label">服务器</span>
15
+ <span class="env-value">{{ environmentInfo.server }}</span>
16
+ </div>
17
+ <div class="env-compact-item">
18
+ <span class="env-label">Node版本</span>
19
+ <span class="env-value">{{ environmentInfo.nodeVersion }}</span>
20
+ </div>
21
+ <div class="env-compact-item">
22
+ <span class="env-label">数据库</span>
23
+ <span class="env-value">{{ environmentInfo.database }}</span>
24
+ </div>
25
+ <div class="env-compact-item">
26
+ <span class="env-label">缓存</span>
27
+ <span class="env-value">{{ environmentInfo.cache }}</span>
28
+ </div>
29
+ <div class="env-compact-item">
30
+ <span class="env-label">时区</span>
31
+ <span class="env-value">{{ environmentInfo.timezone }}</span>
32
+ </div>
33
+ </div>
34
+ </div>
35
+ </div>
36
+ </template>
37
+
38
+ <script setup lang="ts">
39
+ import { reactive } from "vue";
40
+ import { ServerIcon } from "tdesign-icons-vue-next";
41
+ import { $Http } from "@/plugins/http";
42
+
43
+ type EnvironmentInfo = {
44
+ os: string;
45
+ server: string;
46
+ nodeVersion: string;
47
+ database: string;
48
+ cache: string;
49
+ timezone: string;
50
+ };
51
+
52
+ // 组件内部数据
53
+ const environmentInfo = reactive<EnvironmentInfo>({
54
+ os: "",
55
+ server: "",
56
+ nodeVersion: "",
57
+ database: "",
58
+ cache: "",
59
+ timezone: ""
60
+ });
61
+
62
+ // 获取数据
63
+ const fetchData = async () => {
64
+ try {
65
+ const { data } = await $Http.post(
66
+ "/core/dashboard/environmentInfo",
67
+ {},
68
+ {
69
+ dropValues: [""]
70
+ }
71
+ );
72
+ Object.assign(environmentInfo, data);
73
+ } catch (_error) {
74
+ // 静默失败:不阻断页面展示
75
+ }
76
+ };
77
+
78
+ fetchData();
79
+ </script>
80
+
81
+ <style scoped lang="scss">
82
+ .env-grid-compact {
83
+ display: grid;
84
+ grid-template-columns: repeat(4, 1fr);
85
+ gap: 10px;
86
+
87
+ .env-compact-item {
88
+ display: flex;
89
+ justify-content: space-between;
90
+ align-items: center;
91
+ padding: 10px 12px;
92
+ background: var(--bg-color-container);
93
+ border-radius: 6px;
94
+ border: 1px solid var(--border-color);
95
+ transition: all 0.2s ease;
96
+
97
+ &:hover {
98
+ background: rgba(var(--primary-color-rgb), 0.03);
99
+ border-color: rgba(var(--primary-color-rgb), 0.2);
100
+ }
101
+
102
+ .env-label {
103
+ font-size: 14px;
104
+ color: var(--text-secondary);
105
+ font-weight: 500;
106
+ }
107
+
108
+ .env-value {
109
+ font-size: 14px;
110
+ color: var(--text-primary);
111
+ font-weight: 600;
112
+ text-align: right;
113
+ }
114
+ }
115
+ }
116
+ </style>
@@ -0,0 +1,127 @@
1
+ <template>
2
+ <div class="section-block">
3
+ <div class="section-header flex items-center gap-2">
4
+ <CodeIcon />
5
+ <h2>操作日志</h2>
6
+ </div>
7
+ <div class="section-content">
8
+ <div class="operation-table">
9
+ <div class="operation-header">
10
+ <span class="col-time">时间</span>
11
+ <span class="col-user">操作人</span>
12
+ <span class="col-action">操作</span>
13
+ <span class="col-module">模块</span>
14
+ <span class="col-ip">IP地址</span>
15
+ <span class="col-status">状态</span>
16
+ </div>
17
+ <div class="operation-body">
18
+ <div v-for="log in operationLogs" :key="log.id" class="operation-row">
19
+ <span class="col-time">{{ formatTime(log.createdAt) }}</span>
20
+ <span class="col-user">{{ log.userName }}</span>
21
+ <span class="col-action">{{ log.action }}</span>
22
+ <span class="col-module">{{ log.module }}</span>
23
+ <span class="col-ip">{{ log.ip }}</span>
24
+ <span class="col-status">
25
+ <t-tag :type="log.status === 'success' ? 'success' : 'danger'" size="small">
26
+ {{ log.status === "success" ? "成功" : "失败" }}
27
+ </t-tag>
28
+ </span>
29
+ </div>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ </div>
34
+ </template>
35
+
36
+ <script setup lang="ts">
37
+ import { reactive } from "vue";
38
+ import { CodeIcon } from "tdesign-icons-vue-next";
39
+
40
+ type OperationLogStatus = "success" | "failed";
41
+
42
+ type OperationLogItem = {
43
+ id: number;
44
+ userName: string;
45
+ action: string;
46
+ module: string;
47
+ ip: string;
48
+ status: OperationLogStatus;
49
+ createdAt: number;
50
+ };
51
+
52
+ // 组件内部数据
53
+ const operationLogs = reactive<OperationLogItem[]>([
54
+ { id: 1, userName: "管理员", action: "创建角色", module: "权限管理", ip: "192.168.1.100", status: "success", createdAt: Date.now() - 120000 },
55
+ { id: 2, userName: "张三", action: "修改菜单", module: "系统设置", ip: "192.168.1.101", status: "success", createdAt: Date.now() - 900000 },
56
+ { id: 3, userName: "李四", action: "删除接口", module: "接口管理", ip: "192.168.1.102", status: "failed", createdAt: Date.now() - 3600000 },
57
+ { id: 4, userName: "管理员", action: "同步数据库", module: "数据库", ip: "192.168.1.100", status: "success", createdAt: Date.now() - 7200000 },
58
+ { id: 5, userName: "王五", action: "登录系统", module: "系统", ip: "192.168.1.103", status: "success", createdAt: Date.now() - 10800000 }
59
+ ]);
60
+
61
+ const formatTime = (timestamp: number): string => {
62
+ const date = new Date(timestamp);
63
+ const month = String(date.getMonth() + 1).padStart(2, "0");
64
+ const day = String(date.getDate()).padStart(2, "0");
65
+ const hours = String(date.getHours()).padStart(2, "0");
66
+ const minutes = String(date.getMinutes()).padStart(2, "0");
67
+ return `${month}-${day} ${hours}:${minutes}`;
68
+ };
69
+ </script>
70
+
71
+ <style scoped lang="scss">
72
+ .operation-table {
73
+ .operation-header,
74
+ .operation-row {
75
+ display: grid;
76
+ grid-template-columns: 100px 100px 1fr 120px 120px 80px;
77
+ gap: 12px;
78
+ align-items: center;
79
+ }
80
+
81
+ .operation-header {
82
+ padding: 10px 12px;
83
+ background: linear-gradient(135deg, rgba(var(--primary-color-rgb), 0.05) 0%, rgba(var(--primary-color-rgb), 0.02) 100%);
84
+ border-radius: 6px;
85
+ font-size: 14px;
86
+ font-weight: 600;
87
+ color: var(--text-secondary);
88
+ margin-bottom: 6px;
89
+ }
90
+
91
+ .operation-body {
92
+ display: flex;
93
+ flex-direction: column;
94
+ gap: 4px;
95
+ }
96
+
97
+ .operation-row {
98
+ padding: 6px 12px;
99
+ background: var(--bg-color-container);
100
+ border-radius: 6px;
101
+ border: 1px solid var(--border-color);
102
+ font-size: 14px;
103
+ transition: all 0.2s ease;
104
+
105
+ &:hover {
106
+ background: rgba(var(--primary-color-rgb), 0.02);
107
+ border-color: rgba(var(--primary-color-rgb), 0.2);
108
+ }
109
+
110
+ .col-time {
111
+ color: var(--text-secondary);
112
+ font-size: 14px;
113
+ }
114
+
115
+ .col-user,
116
+ .col-action,
117
+ .col-module,
118
+ .col-ip {
119
+ color: var(--text-primary);
120
+ }
121
+
122
+ .col-action {
123
+ font-weight: 600;
124
+ }
125
+ }
126
+ }
127
+ </style>