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,150 @@
1
+ <template>
2
+ <tiny-dialog-box v-model:visible="$Data.visible" :title="$Prop.actionType === 'add' ? '添加菜单' : '编辑菜单'" width="600px" :append-to-body="true" :show-footer="true" top="10vh">
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="name">
5
+ <tiny-input v-model="$Data.formData.name" placeholder="请输入菜单名称" />
6
+ </tiny-form-item>
7
+ <tiny-form-item label="菜单路径" prop="path">
8
+ <tiny-input v-model="$Data.formData.path" placeholder="请输入菜单路径,如:/user" />
9
+ </tiny-form-item>
10
+ <tiny-form-item label="图标" prop="icon">
11
+ <tiny-input v-model="$Data.formData.icon" placeholder="请输入图标名称,如:User" />
12
+ </tiny-form-item>
13
+ <tiny-form-item label="菜单类型" prop="type">
14
+ <tiny-radio-group v-model="$Data.formData.type">
15
+ <tiny-radio :label="0">目录</tiny-radio>
16
+ <tiny-radio :label="1">菜单</tiny-radio>
17
+ </tiny-radio-group>
18
+ </tiny-form-item>
19
+ <tiny-form-item label="排序" prop="sort">
20
+ <tiny-numeric v-model="$Data.formData.sort" :min="0" :max="9999" />
21
+ </tiny-form-item>
22
+ <tiny-form-item 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
+ name: '',
64
+ path: '',
65
+ icon: '',
66
+ type: 1,
67
+ sort: 0,
68
+ state: 1
69
+ }
70
+ });
71
+
72
+ const $Data2 = $shallowRef({
73
+ formRules: {
74
+ name: [{ required: true, message: '请输入菜单名称', trigger: 'blur' }],
75
+ path: [{ required: true, message: '请输入菜单路径', trigger: 'blur' }],
76
+ type: [{ required: true, message: '请选择菜单类型', trigger: 'change' }]
77
+ }
78
+ });
79
+
80
+ // 方法集合
81
+ const $Method = {
82
+ async initData() {
83
+ $Method.onShow();
84
+ },
85
+
86
+ onShow() {
87
+ $Data.visible = true;
88
+ if ($Prop.actionType === 'upd' && $Prop.rowData) {
89
+ $Data.formData.id = $Prop.rowData.id || 0;
90
+ $Data.formData.name = $Prop.rowData.name || '';
91
+ $Data.formData.path = $Prop.rowData.path || '';
92
+ $Data.formData.icon = $Prop.rowData.icon || '';
93
+ $Data.formData.type = $Prop.rowData.type ?? 1;
94
+ $Data.formData.sort = $Prop.rowData.sort || 0;
95
+ $Data.formData.state = $Prop.rowData.state ?? 1;
96
+ } else {
97
+ // 重置表单
98
+ $Data.formData.id = 0;
99
+ $Data.formData.name = '';
100
+ $Data.formData.path = '';
101
+ $Data.formData.icon = '';
102
+ $Data.formData.type = 1;
103
+ $Data.formData.sort = 0;
104
+ $Data.formData.state = 1;
105
+ }
106
+ },
107
+
108
+ onClose() {
109
+ $Data.visible = false;
110
+ setTimeout(() => {
111
+ $Emit('update:modelValue', false);
112
+ }, 300);
113
+ },
114
+
115
+ async onSubmit() {
116
+ try {
117
+ const valid = await $Form.form.validate();
118
+ if (!valid) return;
119
+
120
+ const res = await $Http($Prop.actionType === 'add' ? '/addon/admin/menuIns' : '/addon/admin/menuUpd', $Data.formData);
121
+
122
+ Modal.message({
123
+ message: $Prop.actionType === 'add' ? '添加成功' : '编辑成功',
124
+ status: 'success'
125
+ });
126
+ $Method.onClose();
127
+ $Emit('success');
128
+ } catch (error) {
129
+ console.error('提交失败:', error);
130
+ }
131
+ }
132
+ };
133
+
134
+ // 监听 modelValue 变化
135
+ watch(
136
+ () => $Prop.modelValue,
137
+ (val) => {
138
+ if (val && !$Data.visible) {
139
+ $Method.initData();
140
+ } else if (!val && $Data.visible) {
141
+ $Data.visible = false;
142
+ }
143
+ },
144
+ { immediate: true }
145
+ );
146
+ </script>
147
+
148
+ <style scoped lang="scss">
149
+ // 可根据需要添加样式
150
+ </style>
@@ -0,0 +1,168 @@
1
+ <template>
2
+ <div class="page-menu page-table">
3
+ <div class="main-tool">
4
+ <div class="left">
5
+ <tiny-button type="primary" @click="$Method.onAction('add', {})">
6
+ <template #icon>
7
+ <Icon name="Plus" :size="16" />
8
+ </template>
9
+ 添加菜单
10
+ </tiny-button>
11
+ </div>
12
+ <div class="right">
13
+ <tiny-button @click="$Method.handleRefresh">
14
+ <template #icon>
15
+ <Icon name="RotateCw" :size="16" />
16
+ </template>
17
+ 刷新
18
+ </tiny-button>
19
+ </div>
20
+ </div>
21
+ <div class="main-table">
22
+ <tiny-grid :data="$Data.menuList" header-cell-class-name="custom-table-cell-class" size="small" height="100%" seq-serial>
23
+ <tiny-grid-column type="index" title="序号" :width="60" />
24
+ <tiny-grid-column field="name" title="菜单名称" />
25
+ <tiny-grid-column field="path" title="路径" :width="200" />
26
+ <tiny-grid-column field="icon" title="图标" :width="100">
27
+ <template #default="{ row }">
28
+ <Icon v-if="row.icon" :name="row.icon" :size="16" />
29
+ <span v-else>-</span>
30
+ </template>
31
+ </tiny-grid-column>
32
+ <tiny-grid-column field="type" title="类型" :width="100">
33
+ <template #default="{ row }">
34
+ <tiny-tag v-if="row.type === 0" type="info">目录</tiny-tag>
35
+ <tiny-tag v-else type="success">菜单</tiny-tag>
36
+ </template>
37
+ </tiny-grid-column>
38
+ <tiny-grid-column field="sort" title="排序" :width="80" />
39
+ <tiny-grid-column field="state" title="状态" :width="100">
40
+ <template #default="{ row }">
41
+ <tiny-tag v-if="row.state === 1" type="success">正常</tiny-tag>
42
+ <tiny-tag v-else-if="row.state === 2" type="warning">禁用</tiny-tag>
43
+ <tiny-tag v-else type="danger">已删除</tiny-tag>
44
+ </template>
45
+ </tiny-grid-column>
46
+ <tiny-grid-column title="操作" :width="120" align="right">
47
+ <template #default="{ row }">
48
+ <tiny-dropdown title="操作" trigger="click" size="small" border visible-arrow @item-click="(data) => $Method.onAction(data.itemData.command, row)">
49
+ <template #dropdown>
50
+ <tiny-dropdown-menu>
51
+ <tiny-dropdown-item :item-data="{ command: 'upd' }">
52
+ <Icon name="Edit" />
53
+ 编辑
54
+ </tiny-dropdown-item>
55
+ <tiny-dropdown-item :item-data="{ command: 'del' }" divided>
56
+ <Icon name="Trash2" />
57
+ 删除
58
+ </tiny-dropdown-item>
59
+ </tiny-dropdown-menu>
60
+ </template>
61
+ </tiny-dropdown>
62
+ </template>
63
+ </tiny-grid-column>
64
+ </tiny-grid>
65
+ </div>
66
+
67
+ <div class="main-page">
68
+ <tiny-pager :current-page="$Data.pagerConfig.currentPage" :page-size="$Data.pagerConfig.pageSize" :total="$Data.pagerConfig.total" @current-change="$Method.onPageChange" @size-change="$Method.handleSizeChange" />
69
+ </div>
70
+
71
+ <!-- 编辑对话框组件 -->
72
+ <EditDialog v-if="$Data.editVisible" v-model="$Data.editVisible" :action-type="$Data.actionType" :row-data="$Data.rowData" @success="$Method.apiMenuList" />
73
+ </div>
74
+ </template>
75
+
76
+ <script setup>
77
+ import EditDialog from './components/edit.vue';
78
+
79
+ // 响应式数据
80
+ const $Data = $ref({
81
+ menuList: [],
82
+ pagerConfig: {
83
+ currentPage: 1,
84
+ pageSize: 30,
85
+ total: 0,
86
+ align: 'right',
87
+ layout: 'total, prev, pager, next, jumper'
88
+ },
89
+ editVisible: false,
90
+ actionType: 'add',
91
+ rowData: {}
92
+ });
93
+
94
+ // 方法
95
+ const $Method = {
96
+ async initData() {
97
+ await $Method.apiMenuList();
98
+ },
99
+
100
+ // 加载菜单列表
101
+ async apiMenuList() {
102
+ try {
103
+ const res = await $Http('/addon/admin/menuList', {
104
+ page: $Data.pagerConfig.currentPage,
105
+ limit: $Data.pagerConfig.pageSize
106
+ });
107
+ $Data.menuList = res.data.lists || [];
108
+ $Data.pagerConfig.total = res.data.total || 0;
109
+ } catch (error) {
110
+ console.error('加载菜单列表失败:', error);
111
+ Modal.message({
112
+ message: '加载数据失败',
113
+ status: 'error'
114
+ });
115
+ }
116
+ },
117
+
118
+ // 删除菜单
119
+ async apiMenuDel(row) {
120
+ Modal.confirm({
121
+ header: '确认删除',
122
+ body: `确定要删除菜单"${row.name}" 吗?`,
123
+ status: 'warning'
124
+ }).then(async () => {
125
+ try {
126
+ const res = await $Http('/addon/admin/menuDel', { id: row.id });
127
+ if (res.code === 0) {
128
+ Modal.message({ message: '删除成功', status: 'success' });
129
+ $Method.apiMenuList();
130
+ } else {
131
+ Modal.message({ message: res.msg || '删除失败', status: 'error' });
132
+ }
133
+ } catch (error) {
134
+ console.error('删除失败:', error);
135
+ Modal.message({ message: '删除失败', status: 'error' });
136
+ }
137
+ });
138
+ },
139
+
140
+ // 刷新
141
+ handleRefresh() {
142
+ $Method.apiMenuList();
143
+ },
144
+
145
+ // 分页改变
146
+ onPageChange({ currentPage }) {
147
+ $Data.pagerConfig.currentPage = currentPage;
148
+ $Method.apiMenuList();
149
+ },
150
+
151
+ // 操作菜单点击
152
+ onAction(command, rowData) {
153
+ $Data.actionType = command;
154
+ $Data.rowData = rowData;
155
+ if (command === 'add' || command === 'upd') {
156
+ $Data.editVisible = true;
157
+ } else if (command === 'del') {
158
+ $Method.apiMenuDel(rowData);
159
+ }
160
+ }
161
+ };
162
+
163
+ $Method.initData();
164
+ </script>
165
+
166
+ <style scoped lang="scss">
167
+ // 样式继承自全局 page-table
168
+ </style>
@@ -0,0 +1,26 @@
1
+ <template>
2
+ <div class="news-page">
3
+ <h1>新闻页面2</h1>
4
+ <p>这个页面使用布局 1.vue</p>
5
+ <div class="news-content">
6
+ <p>这里是新闻内容...</p>
7
+ </div>
8
+ </div>
9
+ </template>
10
+
11
+ <script setup>
12
+ // 新闻页面逻辑
13
+ </script>
14
+
15
+ <style scoped>
16
+ .news-page {
17
+ padding: 20px;
18
+ }
19
+
20
+ .news-content {
21
+ margin-top: 20px;
22
+ padding: 20px;
23
+ background: #f5f5f5;
24
+ border-radius: 8px;
25
+ }
26
+ </style>
@@ -0,0 +1,26 @@
1
+ <template>
2
+ <div class="news-page">
3
+ <h1>新闻页面 index</h1>
4
+ <p>这个页面使用布局 1.vue</p>
5
+ <div class="news-content">
6
+ <p>这里是新闻内容...</p>
7
+ </div>
8
+ </div>
9
+ </template>
10
+
11
+ <script setup>
12
+ // 新闻页面逻辑
13
+ </script>
14
+
15
+ <style scoped>
16
+ .news-page {
17
+ padding: 20px;
18
+ }
19
+
20
+ .news-content {
21
+ margin-top: 20px;
22
+ padding: 20px;
23
+ background: #f5f5f5;
24
+ border-radius: 8px;
25
+ }
26
+ </style>
@@ -0,0 +1,26 @@
1
+ <template>
2
+ <div class="news-page">
3
+ <h1>新闻页面</h1>
4
+ <p>这个页面使用布局 1.vue</p>
5
+ <div class="news-content">
6
+ <p>这里是新闻内容...</p>
7
+ </div>
8
+ </div>
9
+ </template>
10
+
11
+ <script setup>
12
+ // 新闻页面逻辑
13
+ </script>
14
+
15
+ <style scoped>
16
+ .news-page {
17
+ padding: 20px;
18
+ }
19
+
20
+ .news-content {
21
+ margin-top: 20px;
22
+ padding: 20px;
23
+ background: #f5f5f5;
24
+ border-radius: 8px;
25
+ }
26
+ </style>
@@ -0,0 +1,280 @@
1
+ <template>
2
+ <tiny-dialog-box 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
+ <tiny-search v-model="$Data.searchText" placeholder="搜索接口名称或路径" clearable @update:modelValue="$Method.onSearch" />
7
+ </div>
8
+
9
+ <!-- 接口分组列表 -->
10
+ <div class="api-container">
11
+ <div v-for="group in $Data.filteredApiData" :key="group.name" class="api-group">
12
+ <div class="group-header">{{ group.title }}</div>
13
+ <div class="api-checkbox-list">
14
+ <tiny-checkbox-group v-model="$Data.checkedApiIds">
15
+ <tiny-checkbox v-for="api in group.apis" :key="api.id" :label="api.id"> {{ api.label }} </tiny-checkbox>
16
+ </tiny-checkbox-group>
17
+ </div>
18
+ </div>
19
+ </div>
20
+ </div>
21
+
22
+ <template #footer>
23
+ <div class="footer-left">
24
+ <tiny-button size="small" @click="$Method.onCheckAll">全选</tiny-button>
25
+ <tiny-button size="small" @click="$Method.onUncheckAll">取消全选</tiny-button>
26
+ </div>
27
+ <div class="footer-right">
28
+ <tiny-button @click="$Method.onClose">取消</tiny-button>
29
+ <tiny-button type="primary" @click="$Method.onSubmit">保存</tiny-button>
30
+ </div>
31
+ </template>
32
+ </tiny-dialog-box>
33
+ </template>
34
+
35
+ <script setup>
36
+ const $Prop = defineProps({
37
+ modelValue: {
38
+ type: Boolean,
39
+ default: false
40
+ },
41
+ rowData: {
42
+ type: Object,
43
+ default: () => ({})
44
+ }
45
+ });
46
+
47
+ const $Emit = defineEmits(['update:modelValue', 'success']);
48
+
49
+ const $Data = $ref({
50
+ visible: false,
51
+ apiData: [],
52
+ filteredApiData: [],
53
+ searchText: '',
54
+ checkedApiIds: []
55
+ });
56
+
57
+ // 方法集合
58
+ const $Method = {
59
+ async initData() {
60
+ $Method.onShow();
61
+ await Promise.all([$Method.apiApiAll(), $Method.apiRoleApiDetail()]);
62
+ $Data.filteredApiData = $Data.apiData;
63
+ },
64
+
65
+ onShow() {
66
+ setTimeout(() => {
67
+ $Data.visible = $Prop.modelValue;
68
+ }, 100);
69
+ },
70
+
71
+ onClose() {
72
+ $Data.visible = false;
73
+ setTimeout(() => {
74
+ $Emit('update:modelValue', false);
75
+ }, 300);
76
+ },
77
+
78
+ // 加载所有接口
79
+ async apiApiAll() {
80
+ try {
81
+ const res = await $Http('/addon/admin/apiAll');
82
+
83
+ // 将接口列表按 addonTitle 分组
84
+ const apiMap = new Map();
85
+
86
+ res.data.lists.forEach((api) => {
87
+ const addonTitle = api.addonTitle || api.addonName || '项目接口';
88
+ const addonName = api.addonName || 'project';
89
+
90
+ if (!apiMap.has(addonName)) {
91
+ apiMap.set(addonName, {
92
+ name: addonName,
93
+ title: addonTitle,
94
+ apis: []
95
+ });
96
+ }
97
+
98
+ apiMap.get(addonName).apis.push({
99
+ id: api.id,
100
+ label: `${api.name}`,
101
+ description: api.description
102
+ });
103
+ });
104
+
105
+ $Data.apiData = Array.from(apiMap.values());
106
+ } catch (error) {
107
+ console.error('加载接口失败:', error);
108
+ Modal.message({ message: '加载接口失败', status: 'error' });
109
+ }
110
+ },
111
+
112
+ // 加载该角色已分配的接口
113
+ async apiRoleApiDetail() {
114
+ if (!$Prop.rowData.id) return;
115
+
116
+ try {
117
+ const res = await $Http('/addon/admin/roleApiDetail', {
118
+ roleId: $Prop.rowData.id
119
+ });
120
+
121
+ $Data.checkedApiIds = res.data.apiIds || [];
122
+ } catch (error) {
123
+ console.error('加载角色接口失败:', error);
124
+ }
125
+ },
126
+
127
+ // 搜索过滤
128
+ onSearch() {
129
+ if (!$Data.searchText) {
130
+ $Data.filteredApiData = $Data.apiData;
131
+ return;
132
+ }
133
+
134
+ const searchLower = $Data.searchText.toLowerCase();
135
+ $Data.filteredApiData = $Data.apiData
136
+ .map((group) => ({
137
+ ...group,
138
+ apis: group.apis.filter((api) => api.label.toLowerCase().includes(searchLower))
139
+ }))
140
+ .filter((group) => group.apis.length > 0);
141
+ },
142
+
143
+ // 全选
144
+ onCheckAll() {
145
+ const allApiIds = [];
146
+ $Data.apiData.forEach((group) => {
147
+ group.apis.forEach((api) => {
148
+ allApiIds.push(api.id);
149
+ });
150
+ });
151
+ $Data.checkedApiIds = allApiIds;
152
+ },
153
+
154
+ // 取消全选
155
+ onUncheckAll() {
156
+ $Data.checkedApiIds = [];
157
+ },
158
+
159
+ // 提交表单
160
+ async onSubmit() {
161
+ try {
162
+ const res = await $Http('/addon/admin/roleApiSave', {
163
+ roleId: $Prop.rowData.id,
164
+ apiIds: $Data.checkedApiIds
165
+ });
166
+
167
+ if (res.code === 0) {
168
+ Modal.message({
169
+ message: '保存成功',
170
+ status: 'success'
171
+ });
172
+ $Data.visible = false;
173
+ $Emit('success');
174
+ } else {
175
+ Modal.message({
176
+ message: res.msg || '保存失败',
177
+ status: 'error'
178
+ });
179
+ }
180
+ } catch (error) {
181
+ console.error('保存失败:', error);
182
+ Modal.message({
183
+ message: '保存失败',
184
+ status: 'error'
185
+ });
186
+ }
187
+ }
188
+ };
189
+
190
+ $Method.initData();
191
+ </script>
192
+
193
+ <style scoped lang="scss">
194
+ .comp-role-api {
195
+ height: 60vh;
196
+ display: flex;
197
+ flex-direction: column;
198
+ gap: 12px;
199
+
200
+ .search-box {
201
+ }
202
+
203
+ .api-container {
204
+ flex: 1;
205
+ overflow-y: auto;
206
+
207
+ .api-group {
208
+ margin-bottom: 16px;
209
+ border: 1px solid $border-color;
210
+ border-radius: $border-radius-small;
211
+ overflow: hidden;
212
+
213
+ &:last-child {
214
+ margin-bottom: 0;
215
+ }
216
+
217
+ .group-header {
218
+ padding: 12px 16px;
219
+ background-color: $bg-color-hover;
220
+ font-weight: 500;
221
+ font-size: $font-size-sm;
222
+ color: $text-primary;
223
+ display: flex;
224
+ align-items: center;
225
+ gap: 8px;
226
+
227
+ &::before {
228
+ content: '';
229
+ width: 8px;
230
+ height: 8px;
231
+ border-radius: 50%;
232
+ background-color: $primary-color;
233
+ opacity: 0.3;
234
+ flex-shrink: 0;
235
+ }
236
+ }
237
+
238
+ .api-checkbox-list {
239
+ padding: 16px;
240
+ background-color: $bg-color-container;
241
+
242
+ :deep(.tiny-checkbox-group) {
243
+ display: flex;
244
+ flex-wrap: wrap;
245
+ gap: 12px;
246
+ width: 100%;
247
+ }
248
+
249
+ :deep(.tiny-checkbox) {
250
+ flex: 0 0 calc(33.333% - 8px);
251
+ margin: 0;
252
+
253
+ .tiny-checkbox__label {
254
+ white-space: nowrap;
255
+ overflow: hidden;
256
+ text-overflow: ellipsis;
257
+ }
258
+ }
259
+ }
260
+ }
261
+ }
262
+ }
263
+
264
+ // 底部操作栏布局
265
+ :deep(.tiny-dialog-box__footer) {
266
+ display: flex;
267
+ justify-content: space-between;
268
+ align-items: center;
269
+
270
+ .footer-left {
271
+ display: flex;
272
+ gap: 8px;
273
+ }
274
+
275
+ .footer-right {
276
+ display: flex;
277
+ gap: 8px;
278
+ }
279
+ }
280
+ </style>