@befly-addon/admin 1.2.1 → 1.2.3

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 (36) hide show
  1. package/adminViews/403_1/index.vue +75 -0
  2. package/adminViews/config/dict/components/edit.vue +109 -0
  3. package/adminViews/config/dict/index.vue +266 -0
  4. package/adminViews/config/dictType/components/edit.vue +100 -0
  5. package/adminViews/config/dictType/index.vue +244 -0
  6. package/adminViews/config/index.vue +12 -0
  7. package/adminViews/config/system/components/edit.vue +171 -0
  8. package/adminViews/config/system/index.vue +286 -0
  9. package/adminViews/index/components/addonList.vue +132 -0
  10. package/adminViews/index/components/environmentInfo.vue +100 -0
  11. package/adminViews/index/components/operationLogs.vue +112 -0
  12. package/adminViews/index/components/performanceMetrics.vue +145 -0
  13. package/adminViews/index/components/quickActions.vue +30 -0
  14. package/adminViews/index/components/serviceStatus.vue +192 -0
  15. package/adminViews/index/components/systemNotifications.vue +137 -0
  16. package/adminViews/index/components/systemOverview.vue +190 -0
  17. package/adminViews/index/components/systemResources.vue +111 -0
  18. package/adminViews/index/components/userInfo.vue +204 -0
  19. package/adminViews/index/index.vue +74 -0
  20. package/adminViews/log/email/index.vue +292 -0
  21. package/adminViews/log/index.vue +12 -0
  22. package/adminViews/log/login/index.vue +187 -0
  23. package/adminViews/log/operate/index.vue +249 -0
  24. package/adminViews/login_1/index.vue +415 -0
  25. package/adminViews/people/admin/components/edit.vue +168 -0
  26. package/adminViews/people/admin/index.vue +240 -0
  27. package/adminViews/people/index.vue +12 -0
  28. package/adminViews/permission/api/index.vue +149 -0
  29. package/adminViews/permission/index.vue +12 -0
  30. package/adminViews/permission/menu/index.vue +130 -0
  31. package/adminViews/permission/role/components/api.vue +361 -0
  32. package/adminViews/permission/role/components/edit.vue +142 -0
  33. package/adminViews/permission/role/components/menu.vue +118 -0
  34. package/adminViews/permission/role/index.vue +263 -0
  35. package/package.json +12 -10
  36. package/tsconfig.json +15 -0
@@ -0,0 +1,361 @@
1
+ <template>
2
+ <TDialog v-model:visible="$Data.visible" title="接口权限" width="900px" :append-to-body="true" :show-footer="true" top="5vh" @close="$Method.onClose">
3
+ <div class="comp-role-api">
4
+ <!-- 搜索框 -->
5
+ <div class="search-box">
6
+ <TInput v-model="$Data.searchText" placeholder="搜索接口名称或路径" clearable @change="$Method.onSearch">
7
+ <template #prefix-icon>
8
+ <ILucideSearch />
9
+ </template>
10
+ </TInput>
11
+ </div>
12
+
13
+ <!-- 接口分组列表 -->
14
+ <div class="api-container">
15
+ <div class="api-group" v-for="group in $Data.filteredApiData" :key="group.name">
16
+ <div class="group-header">{{ group.title }}</div>
17
+ <div class="api-checkbox-list">
18
+ <TCheckboxGroup v-model="$Data.checkedApiPaths">
19
+ <TCheckbox v-for="api in group.apis" :key="api.value" :value="api.value">
20
+ <div class="api-checkbox-label">
21
+ <div class="api-label-main">
22
+ <div class="api-name" :title="api.name">{{ api.name }}</div>
23
+ <div class="api-path" :title="api.path">{{ api.path }}</div>
24
+ </div>
25
+
26
+ <span class="api-copy" title="复制路径" @click.stop="$Method.copyApiPath(api.path)">
27
+ <ILucideCopy :size="14" />
28
+ </span>
29
+ </div>
30
+ </TCheckbox>
31
+ </TCheckboxGroup>
32
+ </div>
33
+ </div>
34
+ </div>
35
+ </div>
36
+
37
+ <template #footer>
38
+ <div class="dialog-footer">
39
+ <t-space>
40
+ <TButton theme="default" @click="$Method.onClose">取消</TButton>
41
+ <TButton theme="primary" :loading="$Data.submitting" @click="$Method.onSubmit">保存</TButton>
42
+ </t-space>
43
+ </div>
44
+ </template>
45
+ </TDialog>
46
+ </template>
47
+
48
+ <script setup>
49
+ import { Dialog as TDialog, Input as TInput, CheckboxGroup as TCheckboxGroup, Checkbox as TCheckbox, Button as TButton, MessagePlugin } from "tdesign-vue-next";
50
+ import ILucideCopy from "~icons/lucide/copy";
51
+ import ILucideSearch from "~icons/lucide/search";
52
+ import { $Http } from "@/plugins/http";
53
+
54
+ const $Prop = defineProps({
55
+ modelValue: {
56
+ type: Boolean,
57
+ default: false
58
+ },
59
+ rowData: {
60
+ type: Object,
61
+ default: () => ({})
62
+ }
63
+ });
64
+
65
+ const $Emit = defineEmits(["update:modelValue", "success"]);
66
+
67
+ const $Data = $ref({
68
+ visible: false,
69
+ submitting: false,
70
+ apiData: [],
71
+ filteredApiData: [],
72
+ searchText: "",
73
+ checkedApiPaths: []
74
+ });
75
+
76
+ // 方法集合
77
+ const $Method = {
78
+ async initData() {
79
+ $Method.onShow();
80
+ await Promise.all([$Method.apiApiAll(), $Method.apiRoleApiDetail()]);
81
+ $Data.filteredApiData = $Data.apiData;
82
+ },
83
+
84
+ onShow() {
85
+ setTimeout(() => {
86
+ $Data.visible = $Prop.modelValue;
87
+ }, 100);
88
+ },
89
+
90
+ onClose() {
91
+ $Data.visible = false;
92
+ setTimeout(() => {
93
+ $Emit("update:modelValue", false);
94
+ }, 300);
95
+ },
96
+
97
+ // 加载所有接口
98
+ async apiApiAll() {
99
+ try {
100
+ const res = await $Http("/addon/admin/api/all");
101
+
102
+ // 将接口列表按 addonTitle 分组
103
+ const apiMap = new Map();
104
+
105
+ res.data.lists.forEach((api) => {
106
+ const addonTitle = api.addonTitle || api.addonName || "项目接口";
107
+ const addonName = api.addonName || "app";
108
+
109
+ if (!apiMap.has(addonName)) {
110
+ apiMap.set(addonName, {
111
+ name: addonName,
112
+ title: addonTitle,
113
+ apis: []
114
+ });
115
+ }
116
+
117
+ apiMap.get(addonName).apis.push({
118
+ value: api.routePath,
119
+ name: api.name || "",
120
+ path: api.routePath || "",
121
+ label: `${api.name || ""} ${api.routePath ? `(${api.routePath})` : ""}`.trim(),
122
+ description: api.description
123
+ });
124
+ });
125
+
126
+ $Data.apiData = Array.from(apiMap.values());
127
+ } catch (error) {
128
+ MessagePlugin.error("加载接口失败");
129
+ }
130
+ },
131
+
132
+ // 加载该角色已分配的接口
133
+ async apiRoleApiDetail() {
134
+ if (!$Prop.rowData.id) return;
135
+
136
+ try {
137
+ const res = await $Http("/addon/admin/role/apis", {
138
+ roleCode: $Prop.rowData.code
139
+ });
140
+
141
+ $Data.checkedApiPaths = res.data.apiPaths || [];
142
+ } catch (error) {
143
+ MessagePlugin.error("加载数据失败");
144
+ }
145
+ },
146
+
147
+ // 搜索过滤
148
+ onSearch() {
149
+ if (!$Data.searchText) {
150
+ $Data.filteredApiData = $Data.apiData;
151
+ return;
152
+ }
153
+
154
+ const searchLower = $Data.searchText.toLowerCase();
155
+ $Data.filteredApiData = $Data.apiData
156
+ .map((group) => {
157
+ const apis = group.apis.filter((api) => api.label.toLowerCase().includes(searchLower));
158
+ return {
159
+ name: group.name,
160
+ title: group.title,
161
+ apis: apis
162
+ };
163
+ })
164
+ .filter((group) => group.apis.length > 0);
165
+ },
166
+
167
+ async copyApiPath(path) {
168
+ if (typeof path !== "string" || path.trim().length === 0) {
169
+ MessagePlugin.warning("路径为空");
170
+ return;
171
+ }
172
+
173
+ const value = path.trim();
174
+
175
+ try {
176
+ if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
177
+ await navigator.clipboard.writeText(value);
178
+ MessagePlugin.success("已复制");
179
+ return;
180
+ }
181
+ } catch (error) {
182
+ // ignore and fallback
183
+ }
184
+
185
+ try {
186
+ const textarea = document.createElement("textarea");
187
+ textarea.value = value;
188
+ textarea.setAttribute("readonly", "readonly");
189
+ textarea.style.position = "fixed";
190
+ textarea.style.left = "-9999px";
191
+ textarea.style.top = "-9999px";
192
+ document.body.appendChild(textarea);
193
+ textarea.select();
194
+ const ok = document.execCommand("copy");
195
+ document.body.removeChild(textarea);
196
+
197
+ if (ok) {
198
+ MessagePlugin.success("已复制");
199
+ return;
200
+ }
201
+
202
+ MessagePlugin.error("复制失败");
203
+ } catch (error) {
204
+ MessagePlugin.error("复制失败");
205
+ }
206
+ },
207
+
208
+ // 提交表单
209
+ async onSubmit() {
210
+ try {
211
+ $Data.submitting = true;
212
+
213
+ const res = await $Http("/addon/admin/role/apiSave", {
214
+ roleCode: $Prop.rowData.code,
215
+ apiPaths: $Data.checkedApiPaths
216
+ });
217
+
218
+ if (res.code === 0) {
219
+ MessagePlugin.success("保存成功");
220
+ $Data.visible = false;
221
+ $Emit("success");
222
+ } else {
223
+ MessagePlugin.error(res.msg || "保存失败");
224
+ }
225
+ } catch (error) {
226
+ MessagePlugin.error("保存失败");
227
+ } finally {
228
+ $Data.submitting = false;
229
+ }
230
+ }
231
+ };
232
+
233
+ $Method.initData();
234
+ </script>
235
+
236
+ <style scoped lang="scss">
237
+ .comp-role-api {
238
+ height: 60vh;
239
+ display: flex;
240
+ flex-direction: column;
241
+ gap: 12px;
242
+
243
+ .api-container {
244
+ flex: 1;
245
+ overflow-y: auto;
246
+
247
+ .api-group {
248
+ margin-bottom: 16px;
249
+ border: 1px solid var(--border-color);
250
+ border-radius: var(--border-radius-small);
251
+ overflow: hidden;
252
+
253
+ .api-checkbox-list {
254
+ padding: 10px;
255
+ }
256
+
257
+ &:last-child {
258
+ margin-bottom: 0;
259
+ }
260
+
261
+ .group-header {
262
+ padding: 12px 16px;
263
+ background-color: var(--bg-color-hover);
264
+ font-weight: 500;
265
+ font-size: var(--font-size-sm);
266
+ color: var(--text-primary);
267
+ display: flex;
268
+ align-items: center;
269
+ gap: 8px;
270
+
271
+ &::before {
272
+ content: "";
273
+ width: 8px;
274
+ height: 8px;
275
+ border-radius: 50%;
276
+ background-color: var(--primary-color);
277
+ opacity: 0.3;
278
+ flex-shrink: 0;
279
+ }
280
+ }
281
+
282
+ .api-item {
283
+ padding: 8px 16px;
284
+ cursor: pointer;
285
+ transition: background-color 0.2s;
286
+ background-color: var(--bg-color-container);
287
+
288
+ :deep(.t-checkbox-group) {
289
+ display: flex;
290
+ flex-wrap: wrap;
291
+ gap: 12px;
292
+ width: 100%;
293
+ }
294
+
295
+ :deep(.t-checkbox) {
296
+ flex: 0 0 calc(33.333% - 8px);
297
+ margin: 0;
298
+
299
+ .t-checkbox__label {
300
+ white-space: nowrap;
301
+ overflow: hidden;
302
+ text-overflow: ellipsis;
303
+ }
304
+ }
305
+ }
306
+ }
307
+ }
308
+ }
309
+
310
+ .api-checkbox-label {
311
+ width: 100%;
312
+ display: flex;
313
+ align-items: center;
314
+ justify-content: space-between;
315
+ gap: 8px;
316
+ }
317
+
318
+ .api-label-main {
319
+ min-width: 0;
320
+ display: flex;
321
+ flex-direction: column;
322
+ gap: 2px;
323
+ }
324
+
325
+ .api-name {
326
+ max-width: 100%;
327
+ overflow: hidden;
328
+ text-overflow: ellipsis;
329
+ white-space: nowrap;
330
+ }
331
+
332
+ .api-path {
333
+ max-width: 100%;
334
+ overflow: hidden;
335
+ text-overflow: ellipsis;
336
+ white-space: nowrap;
337
+ opacity: 0.7;
338
+ font-size: 12px;
339
+ }
340
+
341
+ .api-copy {
342
+ flex: 0 0 auto;
343
+ display: inline-flex;
344
+ align-items: center;
345
+ justify-content: center;
346
+ width: 24px;
347
+ height: 24px;
348
+ border-radius: 6px;
349
+ cursor: pointer;
350
+ }
351
+
352
+ .api-copy:hover {
353
+ background: rgba(0, 0, 0, 0.06);
354
+ }
355
+
356
+ .dialog-footer {
357
+ width: 100%;
358
+ display: flex;
359
+ justify-content: center;
360
+ }
361
+ </style>
@@ -0,0 +1,142 @@
1
+ <template>
2
+ <TDialog v-model:visible="$Data.visible" :header="$Prop.actionType === 'upd' ? '更新角色' : '添加角色'" width="600px" :append-to-body="true" :show-footer="true" :esc-closable="false" top="10vh" @close="$Method.onClose">
3
+ <div class="comp-role-edit">
4
+ <TForm :model="$Data.formData" label-width="120px" label-position="left" :rules="$Data2.formRules" :ref="(el) => ($From.form = el)">
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="请输入角色代码,如:admin" />
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
+ <TFormItem label="状态" prop="state">
18
+ <TRadioGroup v-model="$Data.formData.state">
19
+ <TRadio :value="1">正常</TRadio>
20
+ <TRadio :value="2">禁用</TRadio>
21
+ </TRadioGroup>
22
+ </TFormItem>
23
+ </TForm>
24
+ </div>
25
+ <template #footer>
26
+ <TButton @click="$Method.onClose">取消</TButton>
27
+ <TButton theme="primary" :loading="$Data.submitting" @click="$Method.onSubmit">确定</TButton>
28
+ </template>
29
+ </TDialog>
30
+ </template>
31
+
32
+ <script setup>
33
+ import {
34
+ //
35
+ Dialog as TDialog,
36
+ Form as TForm,
37
+ FormItem as TFormItem,
38
+ Input as TInput,
39
+ InputNumber as TInputNumber,
40
+ RadioGroup as TRadioGroup,
41
+ Radio as TRadio,
42
+ Button as TButton,
43
+ MessagePlugin
44
+ } from "tdesign-vue-next";
45
+ import { fieldClear } from "befly-vite/utils/fieldClear";
46
+ import { $Http } from "@/plugins/http";
47
+
48
+ const $Prop = defineProps({
49
+ modelValue: {
50
+ type: Boolean,
51
+ default: false
52
+ },
53
+ actionType: {
54
+ type: String,
55
+ default: "add"
56
+ },
57
+ rowData: {
58
+ type: Object,
59
+ default: {}
60
+ }
61
+ });
62
+
63
+ const $Emit = defineEmits(["update:modelValue", "success"]);
64
+
65
+ // 表单引用
66
+ const $From = $shallowRef({
67
+ form: null
68
+ });
69
+
70
+ const $Computed = {};
71
+
72
+ const $Data = $ref({
73
+ visible: false,
74
+ submitting: false,
75
+ formData: {
76
+ id: 0,
77
+ name: "",
78
+ code: "",
79
+ description: "",
80
+ sort: 0,
81
+ state: 1
82
+ }
83
+ });
84
+
85
+ const $Data2 = $shallowRef({
86
+ formRules: {
87
+ name: [{ required: true, message: "请输入角色名称", trigger: "blur" }],
88
+ code: [
89
+ { required: true, message: "请输入角色代码", trigger: "blur" },
90
+ { pattern: /^[a-zA-Z0-9_]+$/, message: "角色代码只能包含字母、数字和下划线", trigger: "blur" }
91
+ ],
92
+ sort: [{ type: "number", message: "排序必须是数字", trigger: "blur" }]
93
+ }
94
+ });
95
+
96
+ // 方法集合
97
+ const $Method = {
98
+ async initData() {
99
+ if ($Prop.actionType === "upd" && $Prop.rowData.id) {
100
+ $Data.formData = Object.assign({}, $Prop.rowData);
101
+ }
102
+ $Method.onShow();
103
+ },
104
+ onShow() {
105
+ setTimeout(() => {
106
+ $Data.visible = $Prop.modelValue;
107
+ }, 100);
108
+ },
109
+ // 关闭抽屉事件
110
+ onClose() {
111
+ $Data.visible = false;
112
+ setTimeout(() => {
113
+ $Emit("update:modelValue", false);
114
+ }, 300);
115
+ },
116
+ async onSubmit() {
117
+ try {
118
+ const valid = await $From.form.validate();
119
+ if (!valid) return;
120
+
121
+ $Data.submitting = true;
122
+ const formData = $Prop.actionType === "add" ? fieldClear($Data.formData, { omitKeys: ["id", "state"] }) : $Data.formData;
123
+ const res = await $Http($Prop.actionType === "upd" ? "/addon/admin/role/upd" : "/addon/admin/role/ins", formData);
124
+
125
+ MessagePlugin.success(res.msg);
126
+ $Emit("success");
127
+ $Method.onClose();
128
+ } catch (error) {
129
+ MessagePlugin.error(error.msg || "提交失败");
130
+ } finally {
131
+ $Data.submitting = false;
132
+ }
133
+ }
134
+ };
135
+
136
+ $Method.initData();
137
+ </script>
138
+
139
+ <style scoped lang="scss">
140
+ .comp-role-edit {
141
+ }
142
+ </style>
@@ -0,0 +1,118 @@
1
+ <template>
2
+ <TDialog v-model:visible="$Data.visible" title="菜单权限" width="600px" :append-to-body="true" :show-footer="true" top="10vh" @close="$Method.onClose">
3
+ <div class="comp-role-menu">
4
+ <TTree v-model:value="$Data.menuTreeCheckedKeys" :data="$Data.menuTreeData" value-mode="all" :keys="{ value: 'path', label: 'name', children: 'children' }" checkable expand-all />
5
+ </div>
6
+ <template #footer>
7
+ <TButton @click="$Method.onClose">取消</TButton>
8
+ <TButton theme="primary" :loading="$Data.submitting" @click="$Method.onSubmit">保存</TButton>
9
+ </template>
10
+ </TDialog>
11
+ </template>
12
+
13
+ <script setup>
14
+ import { Dialog as TDialog, Tree as TTree, Button as TButton, MessagePlugin } from "tdesign-vue-next";
15
+ import { $Http } from "@/plugins/http";
16
+ import { arrayToTree } from "befly-shared/utils/arrayToTree";
17
+
18
+ const $Prop = defineProps({
19
+ modelValue: {
20
+ type: Boolean,
21
+ default: false
22
+ },
23
+ rowData: {
24
+ type: Object,
25
+ default: () => ({})
26
+ }
27
+ });
28
+
29
+ const $Emit = defineEmits(["update:modelValue", "success"]);
30
+
31
+ const $Data = $ref({
32
+ visible: false,
33
+ submitting: false,
34
+ menuTreeData: [],
35
+ menuTreeCheckedKeys: []
36
+ });
37
+
38
+ // 方法集合
39
+ const $Method = {
40
+ async initData() {
41
+ await Promise.all([$Method.apiMenuAll(), $Method.apiRoleMenuDetail()]);
42
+ $Method.onShow();
43
+ },
44
+
45
+ onShow() {
46
+ setTimeout(() => {
47
+ $Data.visible = $Prop.modelValue;
48
+ }, 100);
49
+ },
50
+
51
+ onClose() {
52
+ $Data.visible = false;
53
+ setTimeout(() => {
54
+ $Emit("update:modelValue", false);
55
+ }, 300);
56
+ },
57
+
58
+ // 加载菜单树(用于配置权限)
59
+ async apiMenuAll() {
60
+ try {
61
+ const res = await $Http("/addon/admin/menu/all");
62
+ const lists = Array.isArray(res?.data?.lists) ? res.data.lists : [];
63
+
64
+ const treeResult = arrayToTree(lists, "path", "parentPath", "children", "sort");
65
+ $Data.menuTreeData = treeResult.tree;
66
+ } catch (error) {
67
+ MessagePlugin.error("加载菜单失败");
68
+ }
69
+ },
70
+
71
+ // 加载该角色已分配的菜单
72
+ async apiRoleMenuDetail() {
73
+ if (!$Prop.rowData.id) return;
74
+
75
+ try {
76
+ const res = await $Http("/addon/admin/role/menus", {
77
+ roleCode: $Prop.rowData.code
78
+ });
79
+
80
+ // menus 返回的 data 直接就是菜单 path 数组
81
+ $Data.menuTreeCheckedKeys = Array.isArray(res.data) ? res.data : [];
82
+ } catch (error) {
83
+ MessagePlugin.error("加载数据失败");
84
+ }
85
+ },
86
+
87
+ // 提交表单
88
+ async onSubmit() {
89
+ try {
90
+ $Data.submitting = true;
91
+
92
+ const res = await $Http("/addon/admin/role/menuSave", {
93
+ roleCode: $Prop.rowData.code,
94
+ menuPaths: $Data.menuTreeCheckedKeys
95
+ });
96
+
97
+ if (res.code === 0) {
98
+ MessagePlugin.success("保存成功");
99
+ $Data.visible = false;
100
+ $Emit("success");
101
+ } else {
102
+ MessagePlugin.error(res.msg || "保存失败");
103
+ }
104
+ } catch (error) {
105
+ MessagePlugin.error("保存失败");
106
+ } finally {
107
+ $Data.submitting = false;
108
+ }
109
+ }
110
+ };
111
+
112
+ $Method.initData();
113
+ </script>
114
+
115
+ <style scoped lang="scss">
116
+ .comp-role-menu {
117
+ }
118
+ </style>