befly-admin 3.0.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.
Files changed (57) hide show
  1. package/.env.development +4 -0
  2. package/.env.production +4 -0
  3. package/LICENSE +201 -0
  4. package/README.md +143 -0
  5. package/index.html +13 -0
  6. package/libs/auto-routes-template.js +104 -0
  7. package/libs/autoRouter.ts +67 -0
  8. package/libs/icons.ts +543 -0
  9. package/package.json +42 -0
  10. package/public/logo.svg +106 -0
  11. package/src/App.vue +17 -0
  12. package/src/api/auth.ts +60 -0
  13. package/src/components/Icon.vue +41 -0
  14. package/src/env.d.ts +9 -0
  15. package/src/layouts/0.vue +207 -0
  16. package/src/layouts/1.vue +22 -0
  17. package/src/layouts/2.vue +166 -0
  18. package/src/main.ts +19 -0
  19. package/src/plugins/http.ts +94 -0
  20. package/src/plugins/router.ts +47 -0
  21. package/src/plugins/store.ts +19 -0
  22. package/src/styles/index.scss +198 -0
  23. package/src/styles/mixins.scss +98 -0
  24. package/src/styles/variables.scss +75 -0
  25. package/src/types/env.d.ts +23 -0
  26. package/src/util.ts +28 -0
  27. package/src/views/403/403.vue +33 -0
  28. package/src/views/admin/components/edit.vue +147 -0
  29. package/src/views/admin/components/role.vue +135 -0
  30. package/src/views/admin/index.vue +169 -0
  31. package/src/views/dict/components/edit.vue +156 -0
  32. package/src/views/dict/index.vue +159 -0
  33. package/src/views/index/components/AddonList.vue +125 -0
  34. package/src/views/index/components/EnvironmentInfo.vue +97 -0
  35. package/src/views/index/components/OperationLogs.vue +112 -0
  36. package/src/views/index/components/PerformanceMetrics.vue +148 -0
  37. package/src/views/index/components/QuickActions.vue +27 -0
  38. package/src/views/index/components/ServiceStatus.vue +193 -0
  39. package/src/views/index/components/SystemNotifications.vue +136 -0
  40. package/src/views/index/components/SystemOverview.vue +188 -0
  41. package/src/views/index/components/SystemResources.vue +104 -0
  42. package/src/views/index/components/UserInfo.vue +136 -0
  43. package/src/views/index/index.vue +62 -0
  44. package/src/views/login/index_1.vue +694 -0
  45. package/src/views/menu/components/edit.vue +150 -0
  46. package/src/views/menu/index.vue +168 -0
  47. package/src/views/news/detail/detail_2.vue +26 -0
  48. package/src/views/news/detail/index.vue +26 -0
  49. package/src/views/news/news.vue +26 -0
  50. package/src/views/role/components/api.vue +280 -0
  51. package/src/views/role/components/edit.vue +129 -0
  52. package/src/views/role/components/menu.vue +143 -0
  53. package/src/views/role/index.vue +179 -0
  54. package/src/views/user/user.vue +320 -0
  55. package/temp/router.js +71 -0
  56. package/tsconfig.json +34 -0
  57. package/vite.config.ts +100 -0
@@ -0,0 +1,198 @@
1
+ /* 全局基础样式 */
2
+
3
+ * {
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ html,
8
+ body {
9
+ margin: 0;
10
+ padding: 0;
11
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'PingFang SC', 'Microsoft YaHei';
12
+ }
13
+
14
+ #app {
15
+ width: 100%;
16
+ min-height: 100vh;
17
+ }
18
+
19
+ /* 滚动条样式 */
20
+ ::-webkit-scrollbar {
21
+ width: 8px;
22
+ height: 8px;
23
+ }
24
+
25
+ ::-webkit-scrollbar-track {
26
+ background: #f5f5f5;
27
+ }
28
+
29
+ ::-webkit-scrollbar-thumb {
30
+ background: #d9d9d9;
31
+ border-radius: 4px;
32
+ }
33
+
34
+ ::-webkit-scrollbar-thumb:hover {
35
+ background: #bfbfbf;
36
+ }
37
+
38
+ .tiny-dialog-box {
39
+ .tiny-dialog-box__header {
40
+ .tiny-dialog-box__headerbtn {
41
+ top: 0 !important;
42
+ right: 0 !important;
43
+ }
44
+ }
45
+ }
46
+
47
+ /* 通用工具类 */
48
+ .text-center {
49
+ text-align: center;
50
+ }
51
+
52
+ .text-left {
53
+ text-align: left;
54
+ }
55
+
56
+ .text-right {
57
+ text-align: right;
58
+ }
59
+
60
+ .flex-center {
61
+ display: flex;
62
+ align-items: center;
63
+ justify-content: center;
64
+ }
65
+
66
+ .flex-between {
67
+ display: flex;
68
+ align-items: center;
69
+ justify-content: space-between;
70
+ }
71
+
72
+ .tiny-form--label-left .tiny-form-item__label {
73
+ padding-left: 0 !important;
74
+ }
75
+
76
+ // 首页组件通用样式
77
+ .section-block {
78
+ background: $dashboard-section-bg;
79
+ border-radius: $dashboard-section-radius;
80
+ padding: $dashboard-section-padding;
81
+ border: 1px solid $dashboard-section-border;
82
+ box-shadow: $shadow-small;
83
+
84
+ .section-header {
85
+ display: flex;
86
+ align-items: center;
87
+ gap: $spacing-sm;
88
+ margin-bottom: $spacing-md;
89
+ padding-bottom: $spacing-sm;
90
+ border-bottom: 1px solid $border-color-light;
91
+
92
+ h2 {
93
+ font-size: $dashboard-header-font-size;
94
+ font-weight: 600;
95
+ color: $text-primary;
96
+ margin: 0;
97
+ flex: 1;
98
+ }
99
+ }
100
+
101
+ .section-content {
102
+ font-size: $dashboard-content-font-size;
103
+ }
104
+ }
105
+
106
+ // 数据列表页面通用布局样式
107
+ .page-table {
108
+ position: absolute;
109
+ top: 0;
110
+ left: 0;
111
+ right: 0;
112
+ bottom: 0;
113
+ padding: 16px;
114
+ background: #f5f7fa;
115
+
116
+ // 工具栏
117
+ .main-tool {
118
+ position: absolute;
119
+ top: 0;
120
+ left: 0;
121
+ right: 0;
122
+ height: 56px;
123
+ display: flex;
124
+ justify-content: space-between;
125
+ align-items: center;
126
+ padding: 0 10px;
127
+ background: #fff;
128
+ border-radius: 8px;
129
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
130
+ border: 1px solid #e8eaed;
131
+
132
+ .left,
133
+ .toolbar-left {
134
+ display: flex;
135
+ gap: 12px;
136
+ align-items: center;
137
+ }
138
+
139
+ .right,
140
+ .toolbar-right {
141
+ display: flex;
142
+ gap: 12px;
143
+ align-items: center;
144
+ }
145
+
146
+ .toolbar-search {
147
+ display: flex;
148
+ gap: 12px;
149
+ align-items: center;
150
+ }
151
+ }
152
+
153
+ // 表格区域
154
+ .main-table {
155
+ position: absolute;
156
+ top: 72px;
157
+ left: 0;
158
+ right: 0;
159
+ bottom: 72px;
160
+ overflow: hidden;
161
+ background: #fff;
162
+ border-radius: 8px;
163
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
164
+ border: 1px solid #e8eaed;
165
+
166
+ .custom-table-cell-class {
167
+ background-color: #f5f7fa !important;
168
+ font-weight: 600;
169
+ color: #1f2329;
170
+ }
171
+ }
172
+
173
+ // 分页器
174
+ .main-page {
175
+ position: absolute;
176
+ left: 0;
177
+ right: 0;
178
+ bottom: 0;
179
+ height: 56px;
180
+ padding: 0 20px;
181
+ background: #fff;
182
+ border-radius: 8px;
183
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
184
+ border: 1px solid #e8eaed;
185
+ display: flex;
186
+ align-items: center;
187
+ .tiny-pager {
188
+ width: 100%;
189
+ }
190
+ }
191
+ }
192
+ .custom-toolbar {
193
+ padding: 0 10px;
194
+ display: flex;
195
+ align-items: center;
196
+ height: 52px;
197
+ justify-content: space-between;
198
+ }
@@ -0,0 +1,98 @@
1
+ // SCSS 混合宏(Mixins)
2
+
3
+ // 文本溢出省略
4
+ @mixin text-ellipsis($lines: 1) {
5
+ @if $lines == 1 {
6
+ overflow: hidden;
7
+ text-overflow: ellipsis;
8
+ white-space: nowrap;
9
+ } @else {
10
+ display: -webkit-box;
11
+ -webkit-box-orient: vertical;
12
+ -webkit-line-clamp: $lines;
13
+ overflow: hidden;
14
+ text-overflow: ellipsis;
15
+ }
16
+ }
17
+
18
+ // Flex 布局
19
+ @mixin flex-center {
20
+ display: flex;
21
+ align-items: center;
22
+ justify-content: center;
23
+ }
24
+
25
+ @mixin flex-between {
26
+ display: flex;
27
+ align-items: center;
28
+ justify-content: space-between;
29
+ }
30
+
31
+ // 响应式断点
32
+ @mixin respond-to($breakpoint) {
33
+ @if $breakpoint == xs {
34
+ @media (max-width: $breakpoint-xs) {
35
+ @content;
36
+ }
37
+ } @else if $breakpoint == sm {
38
+ @media (min-width: $breakpoint-xs) and (max-width: $breakpoint-sm) {
39
+ @content;
40
+ }
41
+ } @else if $breakpoint == md {
42
+ @media (min-width: $breakpoint-sm) and (max-width: $breakpoint-md) {
43
+ @content;
44
+ }
45
+ } @else if $breakpoint == lg {
46
+ @media (min-width: $breakpoint-md) and (max-width: $breakpoint-lg) {
47
+ @content;
48
+ }
49
+ } @else if $breakpoint == xl {
50
+ @media (min-width: $breakpoint-lg) {
51
+ @content;
52
+ }
53
+ }
54
+ }
55
+
56
+ // 清除浮动
57
+ @mixin clearfix {
58
+ &::after {
59
+ content: '';
60
+ display: table;
61
+ clear: both;
62
+ }
63
+ }
64
+
65
+ // 动画过渡
66
+ @mixin transition($property: all, $duration: $transition-normal, $timing: ease) {
67
+ transition: $property $duration $timing;
68
+ }
69
+
70
+ // 阴影
71
+ @mixin shadow($level: medium) {
72
+ @if $level == small {
73
+ box-shadow: $shadow-small;
74
+ } @else if $level == medium {
75
+ box-shadow: $shadow-medium;
76
+ } @else if $level == large {
77
+ box-shadow: $shadow-large;
78
+ }
79
+ }
80
+
81
+ // 绝对居中
82
+ @mixin absolute-center {
83
+ position: absolute;
84
+ top: 50%;
85
+ left: 50%;
86
+ transform: translate(-50%, -50%);
87
+ }
88
+
89
+ // 固定宽高比
90
+ @mixin aspect-ratio($width, $height) {
91
+ position: relative;
92
+
93
+ &::before {
94
+ content: '';
95
+ display: block;
96
+ padding-top: ($height / $width) * 100%;
97
+ }
98
+ }
@@ -0,0 +1,75 @@
1
+ // 全局 SCSS 变量
2
+ // 可以在任何 .vue 或 .scss 文件中直接使用
3
+
4
+ // 主题色
5
+ $primary-color: #0052d9;
6
+ $success-color: #00a870;
7
+ $warning-color: #ed7b2f;
8
+ $error-color: #e34d59;
9
+ $info-color: #0052d9;
10
+
11
+ // 文本颜色
12
+ $text-primary: #1f2329;
13
+ $text-secondary: #646a73;
14
+ $text-placeholder: #8f959e;
15
+ $text-disabled: #c9cdd4;
16
+
17
+ // 背景色
18
+ $bg-color-page: #f5f7fa;
19
+ $bg-color-container: #ffffff;
20
+ $bg-color-overlay: rgba(0, 0, 0, 0.6);
21
+ $bg-color-hover: #f5f7fa;
22
+
23
+ // 边框
24
+ $border-color: #e8eaed;
25
+ $border-color-light: #f0f1f3;
26
+ $border-radius: 8px;
27
+ $border-radius-small: 4px;
28
+ $border-radius-large: 12px;
29
+
30
+ // 间距
31
+ $spacing-xs: 4px;
32
+ $spacing-sm: 8px;
33
+ $spacing-md: 16px;
34
+ $spacing-lg: 24px;
35
+ $spacing-xl: 32px;
36
+
37
+ // 阴影
38
+ $shadow-small: 0 2px 8px rgba(0, 0, 0, 0.04);
39
+ $shadow-medium: 0 4px 12px rgba(0, 0, 0, 0.08);
40
+ $shadow-large: 0 8px 24px rgba(0, 0, 0, 0.12);
41
+ $shadow-card: 0 2px 8px rgba(0, 0, 0, 0.04);
42
+
43
+ // 动画时间
44
+ $transition-fast: 0.15s;
45
+ $transition-normal: 0.3s;
46
+ $transition-slow: 0.5s;
47
+
48
+ // 字体大小
49
+ $font-size-xs: 12px;
50
+ $font-size-sm: 14px;
51
+ $font-size-md: 16px;
52
+ $font-size-lg: 18px;
53
+ $font-size-xl: 20px;
54
+
55
+ // 布局尺寸
56
+ $header-height: 64px;
57
+ $aside-width: 240px;
58
+ $footer-height: 48px;
59
+
60
+ // 响应式断点
61
+ $breakpoint-xs: 480px;
62
+ $breakpoint-sm: 768px;
63
+ $breakpoint-md: 992px;
64
+ $breakpoint-lg: 1200px;
65
+ $breakpoint-xl: 1600px;
66
+
67
+ // 首页专用变量
68
+ $dashboard-section-bg: #ffffff;
69
+ $dashboard-section-border: #e8eaed;
70
+ $dashboard-section-radius: 8px;
71
+ $dashboard-section-padding: 16px;
72
+ $dashboard-section-gap: 16px;
73
+ $dashboard-header-font-size: 16px;
74
+ $dashboard-content-font-size: 14px;
75
+ $dashboard-min-font-size: 14px;
@@ -0,0 +1,23 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ declare module '*.vue' {
4
+ import type { DefineComponent } from 'vue';
5
+ const component: DefineComponent<{}, {}, any>;
6
+ export default component;
7
+ }
8
+
9
+ // 自动路由模块声明
10
+ declare module 'virtual:auto-routes' {
11
+ import type { RouteRecordRaw } from 'vue-router';
12
+ const routes: RouteRecordRaw[];
13
+ export default routes;
14
+ }
15
+
16
+ interface ImportMetaEnv {
17
+ readonly VITE_API_URL: string;
18
+ readonly VITE_APP_TITLE: string;
19
+ }
20
+
21
+ interface ImportMeta {
22
+ readonly env: ImportMetaEnv;
23
+ }
package/src/util.ts ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * 工具函数集合
3
+ */
4
+
5
+ /**
6
+ * 将一维数组转换为树形结构
7
+ * @param items 一维数组
8
+ * @param pid 父节点ID,默认为0
9
+ * @returns 树形结构数组
10
+ */
11
+ export function arrayToTree<T extends { id: number; pid: number; children?: T[] }>(items: T[], pid = 0): T[] {
12
+ const tree: T[] = [];
13
+
14
+ for (const item of items) {
15
+ if (item.pid === pid) {
16
+ const children = arrayToTree(items, item.id);
17
+ const node = { ...item };
18
+
19
+ if (children.length > 0) {
20
+ node.children = children;
21
+ }
22
+
23
+ tree.push(node);
24
+ }
25
+ }
26
+
27
+ return tree;
28
+ }
@@ -0,0 +1,33 @@
1
+ <template>
2
+ <div class="error-page">
3
+ <t-result status="403" title="无权限访问" description="抱歉,您没有访问该页面的权限">
4
+ <template #extra>
5
+ <t-button theme="primary" @click="$Method.goHome">返回首页</t-button>
6
+ <t-button theme="default" @click="$Method.goBack">返回上一页</t-button>
7
+ </template>
8
+ </t-result>
9
+ </div>
10
+ </template>
11
+
12
+ <script setup>
13
+ const router = useRouter();
14
+
15
+ const $Method = {
16
+ goHome() {
17
+ router.push('/');
18
+ },
19
+ goBack() {
20
+ router.back();
21
+ }
22
+ };
23
+ </script>
24
+
25
+ <style scoped lang="scss">
26
+ .error-page {
27
+ display: flex;
28
+ align-items: center;
29
+ justify-content: center;
30
+ min-height: 100vh;
31
+ background: $bg-color-page;
32
+ }
33
+ </style>
@@ -0,0 +1,147 @@
1
+ <template>
2
+ <tiny-dialog-box v-model:visible="$Data.visible" :title="$Prop.actionType === 'upd' ? '编辑管理员' : '添加管理员'" width="600px" :append-to-body="true" :show-footer="true" :esc-closable="false" top="10vh" @close="$Method.onClose">
3
+ <tiny-form :model="$Data.formData" label-width="120px" label-position="left" :rules="$Data2.formRules" :ref="(el) => ($Form.form = el)">
4
+ <tiny-form-item label="用户名" prop="username">
5
+ <tiny-input v-model="$Data.formData.username" placeholder="请输入用户名" :disabled="$Prop.actionType === 'upd'" />
6
+ </tiny-form-item>
7
+ <tiny-form-item label="邮箱" prop="email">
8
+ <tiny-input v-model="$Data.formData.email" placeholder="请输入邮箱" />
9
+ </tiny-form-item>
10
+ <tiny-form-item v-if="$Prop.actionType === 'add'" label="密码" prop="password">
11
+ <tiny-input v-model="$Data.formData.password" type="password" placeholder="请输入密码,至少6位" />
12
+ </tiny-form-item>
13
+ <tiny-form-item label="姓名" prop="name">
14
+ <tiny-input v-model="$Data.formData.name" placeholder="请输入姓名" />
15
+ </tiny-form-item>
16
+ <tiny-form-item label="昵称" prop="nickname">
17
+ <tiny-input v-model="$Data.formData.nickname" placeholder="请输入昵称" />
18
+ </tiny-form-item>
19
+ <tiny-form-item label="手机号" prop="phone">
20
+ <tiny-input v-model="$Data.formData.phone" placeholder="请输入手机号" />
21
+ </tiny-form-item>
22
+ <tiny-form-item v-if="$Prop.actionType === 'upd'" label="状态" prop="state">
23
+ <tiny-radio-group v-model="$Data.formData.state">
24
+ <tiny-radio :label="1">正常</tiny-radio>
25
+ <tiny-radio :label="2">禁用</tiny-radio>
26
+ </tiny-radio-group>
27
+ </tiny-form-item>
28
+ </tiny-form>
29
+ <template #footer>
30
+ <tiny-button @click="$Method.onClose">取消</tiny-button>
31
+ <tiny-button type="primary" @click="$Method.onSubmit">确定</tiny-button>
32
+ </template>
33
+ </tiny-dialog-box>
34
+ </template>
35
+
36
+ <script setup>
37
+ const $Prop = defineProps({
38
+ modelValue: {
39
+ type: Boolean,
40
+ default: false
41
+ },
42
+ actionType: {
43
+ type: String,
44
+ default: 'add'
45
+ },
46
+ rowData: {
47
+ type: Object,
48
+ default: {}
49
+ }
50
+ });
51
+
52
+ const $Emit = defineEmits(['update:modelValue', 'success']);
53
+
54
+ // 表单引用
55
+ const $Form = $shallowRef({
56
+ form: null
57
+ });
58
+
59
+ const $Data = $ref({
60
+ visible: false,
61
+ formData: {
62
+ id: 0,
63
+ username: '',
64
+ email: '',
65
+ password: '',
66
+ name: '',
67
+ nickname: '',
68
+ phone: '',
69
+ state: 1
70
+ }
71
+ });
72
+
73
+ const $Data2 = $shallowRef({
74
+ formRules: {
75
+ username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
76
+ email: [
77
+ { required: true, message: '请输入邮箱', trigger: 'blur' },
78
+ { type: 'email', message: '邮箱格式不正确', trigger: 'blur' }
79
+ ],
80
+ password: [
81
+ { required: true, message: '请输入密码', trigger: 'blur' },
82
+ { min: 6, message: '密码至少6位', trigger: 'blur' }
83
+ ],
84
+ name: [{ min: 2, max: 50, message: '姓名长度在 2 到 50 个字符', trigger: 'blur' }],
85
+ nickname: [{ min: 2, max: 50, message: '昵称长度在 2 到 50 个字符', trigger: 'blur' }],
86
+ phone: [{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }]
87
+ }
88
+ });
89
+
90
+ // 方法集合
91
+ const $Method = {
92
+ async initData() {
93
+ $Method.onShow();
94
+ if ($Prop.actionType === 'upd' && $Prop.rowData.id) {
95
+ // 编辑模式:复制数据
96
+ $Data.formData.id = $Prop.rowData.id || 0;
97
+ $Data.formData.username = $Prop.rowData.username || '';
98
+ $Data.formData.email = $Prop.rowData.email || '';
99
+ $Data.formData.name = $Prop.rowData.name || '';
100
+ $Data.formData.nickname = $Prop.rowData.nickname || '';
101
+ $Data.formData.phone = $Prop.rowData.phone || '';
102
+ $Data.formData.state = $Prop.rowData.state ?? 1;
103
+ }
104
+ },
105
+
106
+ onShow() {
107
+ setTimeout(() => {
108
+ $Data.visible = $Prop.modelValue;
109
+ }, 100);
110
+ },
111
+
112
+ onClose() {
113
+ $Data.visible = false;
114
+ setTimeout(() => {
115
+ $Emit('update:modelValue', false);
116
+ }, 300);
117
+ },
118
+
119
+ async onSubmit() {
120
+ try {
121
+ const valid = await $Form.form.validate();
122
+ if (!valid) return;
123
+
124
+ const res = await $Http($Prop.actionType === 'upd' ? '/addon/admin/adminUpd' : '/addon/admin/adminIns', $Data.formData);
125
+
126
+ Modal.message({
127
+ message: $Prop.actionType === 'upd' ? '编辑成功' : '添加成功',
128
+ status: 'success'
129
+ });
130
+ $Emit('success');
131
+ $Method.onClose();
132
+ } catch (error) {
133
+ console.error('提交失败:', error);
134
+ Modal.message({
135
+ message: '提交失败',
136
+ status: 'error'
137
+ });
138
+ }
139
+ }
140
+ };
141
+
142
+ $Method.initData();
143
+ </script>
144
+
145
+ <style scoped lang="scss">
146
+ // 可根据需要添加样式
147
+ </style>