@befly-addon/admin 1.1.28 → 1.1.30
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/apis/admin/cacheRefresh.ts +1 -4
- package/apis/admin/del.ts +4 -4
- package/apis/admin/detail.ts +8 -40
- package/apis/admin/ins.ts +21 -14
- package/apis/admin/list.ts +3 -3
- package/apis/admin/upd.ts +43 -8
- package/apis/api/all.ts +7 -3
- package/apis/api/list.ts +8 -17
- package/apis/auth/login.ts +19 -16
- package/apis/dict/all.ts +21 -4
- package/apis/dict/del.ts +7 -15
- package/apis/dict/detail.ts +28 -14
- package/apis/dict/ins.ts +34 -16
- package/apis/dict/items.ts +27 -0
- package/apis/dict/list.ts +30 -5
- package/apis/dict/upd.ts +51 -17
- package/apis/dictType/all.ts +11 -0
- package/apis/dictType/del.ts +32 -0
- package/apis/dictType/detail.ts +17 -0
- package/apis/dictType/ins.ts +30 -0
- package/apis/dictType/list.ts +24 -0
- package/apis/dictType/upd.ts +42 -0
- package/apis/email/logList.ts +2 -2
- package/apis/loginLog/list.ts +2 -2
- package/apis/menu/all.ts +1 -2
- package/apis/menu/list.ts +0 -1
- package/apis/operateLog/list.ts +2 -2
- package/apis/role/apiSave.ts +4 -3
- package/apis/role/{apiDetail.ts → apis.ts} +8 -3
- package/apis/role/del.ts +18 -10
- package/apis/role/detail.ts +0 -1
- package/apis/role/menuSave.ts +4 -3
- package/apis/role/{menuDetail.ts → menus.ts} +8 -3
- package/apis/role/save.ts +2 -3
- package/apis/sysConfig/all.ts +0 -1
- package/apis/sysConfig/del.ts +1 -1
- package/apis/sysConfig/get.ts +1 -2
- package/apis/sysConfig/upd.ts +1 -1
- package/package.json +4 -4
- package/tables/admin.json +0 -6
- package/tables/dict.json +13 -19
- package/tables/dictType.json +28 -0
- package/views/config/dict/components/edit.vue +76 -122
- package/views/config/dict/index.vue +76 -54
- package/views/config/dict/meta.json +1 -1
- package/views/config/dictType/components/edit.vue +106 -0
- package/views/config/dictType/index.vue +206 -0
- package/views/config/dictType/meta.json +4 -0
- package/views/login_1/index.vue +184 -2
- package/views/people/admin/components/edit.vue +5 -5
- package/views/permission/role/components/api.vue +3 -3
- package/views/permission/role/components/menu.vue +4 -4
- package/apis/admin/roleDetail.ts +0 -29
- package/apis/admin/roleSave.ts +0 -39
- package/apis/auth/logout.ts +0 -17
- package/apis/auth/register.ts +0 -43
- package/apis/dashboard/changelog.ts +0 -31
- package/views/login_1/components/emailLoginForm.vue +0 -174
- package/views/login_1/components/registerForm.vue +0 -175
- package/views/login_1/components/welcomePanel.vue +0 -61
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="page-dict-type page-table">
|
|
3
|
+
<div class="main-tool">
|
|
4
|
+
<div class="left">
|
|
5
|
+
<TButton theme="primary" @click="$Method.onAction('add', {})">
|
|
6
|
+
<template #icon>
|
|
7
|
+
<ILucidePlus />
|
|
8
|
+
</template>
|
|
9
|
+
</TButton>
|
|
10
|
+
</div>
|
|
11
|
+
<div class="right">
|
|
12
|
+
<TInput v-model="$Data.searchKeyword" placeholder="搜索类型名称" clearable @enter="$Method.handleSearch" @clear="$Method.handleSearch">
|
|
13
|
+
<template #suffix-icon>
|
|
14
|
+
<ILucideSearch />
|
|
15
|
+
</template>
|
|
16
|
+
</TInput>
|
|
17
|
+
<TButton shape="circle" @click="$Method.handleRefresh">
|
|
18
|
+
<template #icon>
|
|
19
|
+
<ILucideRotateCw />
|
|
20
|
+
</template>
|
|
21
|
+
</TButton>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="main-content">
|
|
25
|
+
<div class="main-table">
|
|
26
|
+
<TTable :data="$Data.tableData" :columns="$Data.columns" :loading="$Data.loading" :active-row-keys="$Data.activeRowKeys" row-key="id" height="calc(100vh - var(--search-height) - var(--pagination-height) - var(--layout-gap) * 4)" active-row-type="single" @active-change="$Method.onActiveChange">
|
|
27
|
+
<template #operation="{ row }">
|
|
28
|
+
<TDropdown trigger="click" placement="bottom-right" @click="(data) => $Method.onAction(data.value, row)">
|
|
29
|
+
<TButton theme="primary" size="small">
|
|
30
|
+
操作
|
|
31
|
+
<template #suffix> <ILucideChevronDown /></template>
|
|
32
|
+
</TButton>
|
|
33
|
+
<TDropdownMenu slot="dropdown">
|
|
34
|
+
<TDropdownItem value="upd">
|
|
35
|
+
<ILucidePencil />
|
|
36
|
+
编辑
|
|
37
|
+
</TDropdownItem>
|
|
38
|
+
<TDropdownItem value="del" :divider="true">
|
|
39
|
+
<ILucideTrash2 style="width: 14px; height: 14px; margin-right: 6px" />
|
|
40
|
+
删除
|
|
41
|
+
</TDropdownItem>
|
|
42
|
+
</TDropdownMenu>
|
|
43
|
+
</TDropdown>
|
|
44
|
+
</template>
|
|
45
|
+
</TTable>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<div class="main-detail">
|
|
49
|
+
<DetailPanel :data="$Data.currentRow" :fields="$Data.columns" />
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div class="main-page">
|
|
54
|
+
<TPagination :current-page="$Data.pagerConfig.currentPage" :page-size="$Data.pagerConfig.limit" :total="$Data.pagerConfig.total" @current-change="$Method.onPageChange" @page-size-change="$Method.handleSizeChange" />
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<EditDialog v-if="$Data.editVisible" v-model="$Data.editVisible" :action-type="$Data.actionType" :row-data="$Data.rowData" @success="$Method.apiDictTypeList" />
|
|
58
|
+
</div>
|
|
59
|
+
</template>
|
|
60
|
+
|
|
61
|
+
<script setup>
|
|
62
|
+
import { Button as TButton, Table as TTable, Input as TInput, Dropdown as TDropdown, DropdownMenu as TDropdownMenu, DropdownItem as TDropdownItem, Pagination as TPagination, MessagePlugin, DialogPlugin } from 'tdesign-vue-next';
|
|
63
|
+
import ILucidePlus from '~icons/lucide/plus';
|
|
64
|
+
import ILucideRotateCw from '~icons/lucide/rotate-cw';
|
|
65
|
+
import ILucideSearch from '~icons/lucide/search';
|
|
66
|
+
import ILucidePencil from '~icons/lucide/pencil';
|
|
67
|
+
import ILucideTrash2 from '~icons/lucide/trash-2';
|
|
68
|
+
import ILucideChevronDown from '~icons/lucide/chevron-down';
|
|
69
|
+
import EditDialog from './components/edit.vue';
|
|
70
|
+
import DetailPanel from '@/components/DetailPanel.vue';
|
|
71
|
+
import { $Http } from '@/plugins/http';
|
|
72
|
+
import { withDefaultColumns } from 'befly-shared/withDefaultColumns';
|
|
73
|
+
|
|
74
|
+
const $Data = $ref({
|
|
75
|
+
tableData: [],
|
|
76
|
+
loading: false,
|
|
77
|
+
activeRowKeys: [],
|
|
78
|
+
currentRow: null,
|
|
79
|
+
searchKeyword: '',
|
|
80
|
+
columns: withDefaultColumns([
|
|
81
|
+
{ colKey: 'id', title: 'ID' },
|
|
82
|
+
{ colKey: 'code', title: '类型代码' },
|
|
83
|
+
{ colKey: 'name', title: '类型名称' },
|
|
84
|
+
{ colKey: 'description', title: '描述' },
|
|
85
|
+
{ colKey: 'sort', title: '排序' },
|
|
86
|
+
{ colKey: 'operation', title: '操作' }
|
|
87
|
+
]),
|
|
88
|
+
pagerConfig: {
|
|
89
|
+
currentPage: 1,
|
|
90
|
+
limit: 30,
|
|
91
|
+
total: 0,
|
|
92
|
+
align: 'right',
|
|
93
|
+
layout: 'total, prev, pager, next, jumper'
|
|
94
|
+
},
|
|
95
|
+
editVisible: false,
|
|
96
|
+
actionType: 'add',
|
|
97
|
+
rowData: {}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const $Method = {
|
|
101
|
+
async initData() {
|
|
102
|
+
await $Method.apiDictTypeList();
|
|
103
|
+
},
|
|
104
|
+
async apiDictTypeList() {
|
|
105
|
+
$Data.loading = true;
|
|
106
|
+
try {
|
|
107
|
+
const res = await $Http('/addon/admin/dictType/list', {
|
|
108
|
+
page: $Data.pagerConfig.currentPage,
|
|
109
|
+
limit: $Data.pagerConfig.limit,
|
|
110
|
+
keyword: $Data.searchKeyword
|
|
111
|
+
});
|
|
112
|
+
$Data.tableData = res.data.lists || [];
|
|
113
|
+
$Data.pagerConfig.total = res.data.total || 0;
|
|
114
|
+
|
|
115
|
+
if ($Data.tableData.length > 0) {
|
|
116
|
+
$Data.currentRow = $Data.tableData[0];
|
|
117
|
+
$Data.activeRowKeys = [$Data.tableData[0].id];
|
|
118
|
+
} else {
|
|
119
|
+
$Data.currentRow = null;
|
|
120
|
+
$Data.activeRowKeys = [];
|
|
121
|
+
}
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error('加载字典类型列表失败:', error);
|
|
124
|
+
MessagePlugin.error('加载数据失败');
|
|
125
|
+
} finally {
|
|
126
|
+
$Data.loading = false;
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
async apiDictTypeDel(row) {
|
|
130
|
+
DialogPlugin.confirm({
|
|
131
|
+
header: '确认删除',
|
|
132
|
+
body: `确定要删除类型"${row.name}"吗?删除前会检查是否有字典项引用此类型。`,
|
|
133
|
+
status: 'warning'
|
|
134
|
+
}).then(async () => {
|
|
135
|
+
try {
|
|
136
|
+
const res = await $Http('/addon/admin/dictType/del', { id: row.id });
|
|
137
|
+
if (res.code === 0) {
|
|
138
|
+
MessagePlugin.success('删除成功');
|
|
139
|
+
$Method.apiDictTypeList();
|
|
140
|
+
} else {
|
|
141
|
+
MessagePlugin.error(res.msg || '删除失败');
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.error('删除失败:', error);
|
|
145
|
+
MessagePlugin.error('删除失败');
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
},
|
|
149
|
+
handleSearch() {
|
|
150
|
+
$Data.pagerConfig.currentPage = 1;
|
|
151
|
+
$Method.apiDictTypeList();
|
|
152
|
+
},
|
|
153
|
+
handleRefresh() {
|
|
154
|
+
$Data.searchKeyword = '';
|
|
155
|
+
$Data.pagerConfig.currentPage = 1;
|
|
156
|
+
$Method.apiDictTypeList();
|
|
157
|
+
},
|
|
158
|
+
onPageChange({ currentPage }) {
|
|
159
|
+
$Data.pagerConfig.currentPage = currentPage;
|
|
160
|
+
$Method.apiDictTypeList();
|
|
161
|
+
},
|
|
162
|
+
handleSizeChange({ pageSize }) {
|
|
163
|
+
$Data.pagerConfig.limit = pageSize;
|
|
164
|
+
$Data.pagerConfig.currentPage = 1;
|
|
165
|
+
$Method.apiDictTypeList();
|
|
166
|
+
},
|
|
167
|
+
onActiveChange(value, context) {
|
|
168
|
+
if (value.length === 0 && $Data.activeRowKeys.length > 0) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
$Data.activeRowKeys = value;
|
|
172
|
+
$Data.currentRow = context.currentRowData;
|
|
173
|
+
},
|
|
174
|
+
onAction(type, row) {
|
|
175
|
+
if (type === 'add') {
|
|
176
|
+
$Data.actionType = 'add';
|
|
177
|
+
$Data.rowData = {};
|
|
178
|
+
$Data.editVisible = true;
|
|
179
|
+
} else if (type === 'upd') {
|
|
180
|
+
$Data.actionType = 'upd';
|
|
181
|
+
$Data.rowData = { ...row };
|
|
182
|
+
$Data.editVisible = true;
|
|
183
|
+
} else if (type === 'del') {
|
|
184
|
+
$Method.apiDictTypeDel(row);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
onMounted(() => {
|
|
190
|
+
$Method.initData();
|
|
191
|
+
});
|
|
192
|
+
</script>
|
|
193
|
+
|
|
194
|
+
<style scoped lang="scss">
|
|
195
|
+
.page-dict-type {
|
|
196
|
+
.main-tool .right {
|
|
197
|
+
display: flex;
|
|
198
|
+
gap: 8px;
|
|
199
|
+
align-items: center;
|
|
200
|
+
|
|
201
|
+
.t-input {
|
|
202
|
+
width: 240px;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
</style>
|
package/views/login_1/index.vue
CHANGED
|
@@ -21,7 +21,39 @@
|
|
|
21
21
|
<p class="login-subtitle">请登录您的账户</p>
|
|
22
22
|
</div>
|
|
23
23
|
|
|
24
|
-
<
|
|
24
|
+
<TForm :model="$Data.formData" :rules="$Data2.formRules" :ref="(el) => ($From.form = el)" class="login-form" :show-message="false" label-width="0">
|
|
25
|
+
<TFormItem prop="account">
|
|
26
|
+
<TInputAdornment>
|
|
27
|
+
<template #prepend>
|
|
28
|
+
<TSelect v-model="$Data.formData.loginType" :style="{ width: '110px' }" size="large" :popup-props="{ overlayClassName: 'login-type-select-popup' }">
|
|
29
|
+
<TOption value="username" label="用户名" />
|
|
30
|
+
<TOption value="email" label="邮箱" />
|
|
31
|
+
<TOption value="phone" label="手机号" />
|
|
32
|
+
</TSelect>
|
|
33
|
+
</template>
|
|
34
|
+
<TInput v-model="$Data.formData.account" :placeholder="$Data.formData.loginType === 'username' ? '请输入用户名' : $Data.formData.loginType === 'email' ? '请输入邮箱' : '请输入手机号'" size="large" clearable @enter="$Method.apiLogin">
|
|
35
|
+
<template #prefix-icon>
|
|
36
|
+
<ILucideUser />
|
|
37
|
+
</template>
|
|
38
|
+
</TInput>
|
|
39
|
+
</TInputAdornment>
|
|
40
|
+
</TFormItem>
|
|
41
|
+
|
|
42
|
+
<TFormItem prop="password">
|
|
43
|
+
<TInput v-model="$Data.formData.password" type="password" placeholder="密码" size="large" clearable @enter="$Method.apiLogin">
|
|
44
|
+
<template #prefix-icon>
|
|
45
|
+
<ILucideLock />
|
|
46
|
+
</template>
|
|
47
|
+
</TInput>
|
|
48
|
+
</TFormItem>
|
|
49
|
+
|
|
50
|
+
<div class="form-options">
|
|
51
|
+
<TCheckbox v-model="$Data.rememberMe">记住我</TCheckbox>
|
|
52
|
+
<a href="#" class="link-text">忘记密码?</a>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<TButton theme="primary" class="login-btn" size="large" block :loading="$Data.loading" @click="$Method.apiLogin"> 登录 </TButton>
|
|
56
|
+
</TForm>
|
|
25
57
|
|
|
26
58
|
<div class="login-footer">
|
|
27
59
|
<p class="copyright">© 2024 Befly. All rights reserved.</p>
|
|
@@ -32,7 +64,75 @@
|
|
|
32
64
|
</template>
|
|
33
65
|
|
|
34
66
|
<script setup>
|
|
35
|
-
import
|
|
67
|
+
import { useRouter } from 'vue-router';
|
|
68
|
+
import { Form as TForm, FormItem as TFormItem, Input as TInput, Button as TButton, Checkbox as TCheckbox, InputAdornment as TInputAdornment, Select as TSelect, Option as TOption, MessagePlugin } from 'tdesign-vue-next';
|
|
69
|
+
import ILucideUser from '~icons/lucide/user';
|
|
70
|
+
import ILucideLock from '~icons/lucide/lock';
|
|
71
|
+
import { $Http } from '@/plugins/http';
|
|
72
|
+
import { $Storage } from '@/plugins/storage';
|
|
73
|
+
import { hashPassword } from 'befly-shared/hashPassword';
|
|
74
|
+
|
|
75
|
+
const router = useRouter();
|
|
76
|
+
|
|
77
|
+
// 表单引用
|
|
78
|
+
const $From = $shallowRef({
|
|
79
|
+
form: null
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// 数据定义
|
|
83
|
+
const $Data = $ref({
|
|
84
|
+
loading: false,
|
|
85
|
+
rememberMe: false,
|
|
86
|
+
formData: {
|
|
87
|
+
loginType: 'username',
|
|
88
|
+
account: '',
|
|
89
|
+
password: ''
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const $Data2 = $shallowRef({
|
|
94
|
+
formRules: {
|
|
95
|
+
account: [{ required: true, message: '请输入账号', trigger: 'blur' }],
|
|
96
|
+
password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// 方法定义
|
|
101
|
+
const $Method = {
|
|
102
|
+
async apiLogin() {
|
|
103
|
+
try {
|
|
104
|
+
const valid = await $From.form.validate();
|
|
105
|
+
|
|
106
|
+
$Data.loading = true;
|
|
107
|
+
|
|
108
|
+
// 对密码进行 SHA-256 加密
|
|
109
|
+
const hashedPassword = await hashPassword($Data.formData.password);
|
|
110
|
+
|
|
111
|
+
const res = await $Http('/addon/admin/auth/login', {
|
|
112
|
+
loginType: $Data.formData.loginType,
|
|
113
|
+
account: $Data.formData.account,
|
|
114
|
+
password: hashedPassword
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// 先保存 token
|
|
118
|
+
$Storage.local.set('token', res.data.token);
|
|
119
|
+
|
|
120
|
+
// 如果返回用户信息,也可以存储
|
|
121
|
+
if (res.data.userInfo) {
|
|
122
|
+
$Storage.local.set('userInfo', res.data.userInfo);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
MessagePlugin.success(res.msg || '登录成功');
|
|
126
|
+
|
|
127
|
+
// 跳转到首页,路由守卫会自动加载菜单
|
|
128
|
+
await router.push('/');
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.log('🔥[ error ]-77', error);
|
|
131
|
+
} finally {
|
|
132
|
+
$Data.loading = false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
36
136
|
</script>
|
|
37
137
|
|
|
38
138
|
<style scoped lang="scss">
|
|
@@ -170,6 +270,88 @@ import EmailLoginForm from './components/emailLoginForm.vue';
|
|
|
170
270
|
color: var(--login-subtitle);
|
|
171
271
|
}
|
|
172
272
|
|
|
273
|
+
.login-form {
|
|
274
|
+
width: 100%;
|
|
275
|
+
|
|
276
|
+
:deep(.t-form__item) {
|
|
277
|
+
margin-bottom: 1.25rem;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
:deep(.t-form__controls) {
|
|
281
|
+
width: 100%;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
:deep(.t-input-adornment) {
|
|
285
|
+
width: 100%;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
:deep(.t-input-adornment__prepend) {
|
|
289
|
+
padding: 0;
|
|
290
|
+
border-right: 1px solid var(--login-card-border);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
:deep(.t-input) {
|
|
294
|
+
width: 100%;
|
|
295
|
+
border-radius: 8px;
|
|
296
|
+
transition: all 0.3s;
|
|
297
|
+
|
|
298
|
+
&:hover {
|
|
299
|
+
border-color: #667eea;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
&:focus-within {
|
|
303
|
+
border-color: #667eea;
|
|
304
|
+
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
:deep(.t-input__wrap) {
|
|
309
|
+
width: 100%;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.form-options {
|
|
314
|
+
display: flex;
|
|
315
|
+
justify-content: space-between;
|
|
316
|
+
align-items: center;
|
|
317
|
+
margin-bottom: 1.5rem;
|
|
318
|
+
font-size: 0.875rem;
|
|
319
|
+
|
|
320
|
+
.link-text {
|
|
321
|
+
color: var(--login-link);
|
|
322
|
+
text-decoration: none;
|
|
323
|
+
transition: color 0.3s;
|
|
324
|
+
|
|
325
|
+
&:hover {
|
|
326
|
+
color: var(--login-link-hover);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.login-btn {
|
|
332
|
+
width: 100%;
|
|
333
|
+
height: 48px;
|
|
334
|
+
border-radius: 8px;
|
|
335
|
+
font-size: 1rem;
|
|
336
|
+
font-weight: 600;
|
|
337
|
+
background: linear-gradient(135deg, var(--login-btn-gradient-start) 0%, var(--login-btn-gradient-end) 100%);
|
|
338
|
+
border: none;
|
|
339
|
+
transition: all 0.3s;
|
|
340
|
+
|
|
341
|
+
&:hover {
|
|
342
|
+
transform: translateY(-2px);
|
|
343
|
+
box-shadow: 0 8px 20px var(--login-btn-shadow);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
&:active {
|
|
347
|
+
transform: translateY(0);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
:deep(.t-button__text) {
|
|
351
|
+
color: #fff;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
173
355
|
// 响应式设计
|
|
174
356
|
@media (max-width: 1024px) {
|
|
175
357
|
.login-container {
|
|
@@ -2,8 +2,8 @@
|
|
|
2
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
3
|
<div class="dialog-wrapper">
|
|
4
4
|
<TForm :model="$Data.formData" label-width="80px" label-position="left" label-align="left" :rules="$Data2.formRules" :ref="(el) => ($From.form = el)">
|
|
5
|
-
<TFormItem label="角色" prop="
|
|
6
|
-
<TSelect v-model="$Data.formData.
|
|
5
|
+
<TFormItem label="角色" prop="roleCode">
|
|
6
|
+
<TSelect v-model="$Data.formData.roleCode" :options="$Data.allRoleLists" :keys="$Data.keys" placeholder="请选择角色" />
|
|
7
7
|
</TFormItem>
|
|
8
8
|
<TFormItem label="用户名" prop="username">
|
|
9
9
|
<TInput v-model="$Data.formData.username" placeholder="请输入用户名" :disabled="$Prop.actionType === 'upd'" />
|
|
@@ -78,14 +78,14 @@ const $Data = $ref({
|
|
|
78
78
|
allRoleLists: [],
|
|
79
79
|
keys: {
|
|
80
80
|
label: 'name',
|
|
81
|
-
value: '
|
|
81
|
+
value: 'code'
|
|
82
82
|
},
|
|
83
83
|
formData: {
|
|
84
84
|
id: null,
|
|
85
85
|
username: '',
|
|
86
86
|
password: '',
|
|
87
87
|
nickname: '',
|
|
88
|
-
|
|
88
|
+
roleCode: null,
|
|
89
89
|
state: 1
|
|
90
90
|
}
|
|
91
91
|
});
|
|
@@ -97,7 +97,7 @@ const $Data2 = $shallowRef({
|
|
|
97
97
|
{ required: true, message: '请输入密码', trigger: 'blur' },
|
|
98
98
|
{ min: 6, message: '密码至少6位', trigger: 'blur' }
|
|
99
99
|
],
|
|
100
|
-
|
|
100
|
+
roleCode: [{ required: true, message: '请选择角色', trigger: 'change' }],
|
|
101
101
|
nickname: [{ min: 2, max: 50, message: '昵称长度在 2 到 50 个字符', trigger: 'blur' }]
|
|
102
102
|
}
|
|
103
103
|
});
|
|
@@ -121,8 +121,8 @@ const $Method = {
|
|
|
121
121
|
if (!$Prop.rowData.id) return;
|
|
122
122
|
|
|
123
123
|
try {
|
|
124
|
-
const res = await $Http('/addon/admin/role/
|
|
125
|
-
|
|
124
|
+
const res = await $Http('/addon/admin/role/apis', {
|
|
125
|
+
roleCode: $Prop.rowData.code
|
|
126
126
|
});
|
|
127
127
|
|
|
128
128
|
$Data.checkedApiIds = res.data.apiIds || [];
|
|
@@ -153,7 +153,7 @@ const $Method = {
|
|
|
153
153
|
$Data.submitting = true;
|
|
154
154
|
|
|
155
155
|
const res = await $Http('/addon/admin/role/apiSave', {
|
|
156
|
-
|
|
156
|
+
roleCode: $Prop.rowData.code,
|
|
157
157
|
apiIds: $Data.checkedApiIds
|
|
158
158
|
});
|
|
159
159
|
|
|
@@ -73,11 +73,11 @@ const $Method = {
|
|
|
73
73
|
if (!$Prop.rowData.id) return;
|
|
74
74
|
|
|
75
75
|
try {
|
|
76
|
-
const res = await $Http('/addon/admin/role/
|
|
77
|
-
|
|
76
|
+
const res = await $Http('/addon/admin/role/menus', {
|
|
77
|
+
roleCode: $Prop.rowData.code
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
-
//
|
|
80
|
+
// menus 返回的 data 直接就是菜单 ID 数组
|
|
81
81
|
$Data.menuTreeCheckedKeys = Array.isArray(res.data) ? res.data : [];
|
|
82
82
|
} catch (error) {
|
|
83
83
|
console.error('加载角色菜单失败:', error);
|
|
@@ -90,7 +90,7 @@ const $Method = {
|
|
|
90
90
|
$Data.submitting = true;
|
|
91
91
|
|
|
92
92
|
const res = await $Http('/addon/admin/role/menuSave', {
|
|
93
|
-
|
|
93
|
+
roleCode: $Prop.rowData.code,
|
|
94
94
|
menuIds: $Data.menuTreeCheckedKeys
|
|
95
95
|
});
|
|
96
96
|
|
package/apis/admin/roleDetail.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
name: '获取管理员角色',
|
|
3
|
-
handler: async (befly, ctx) => {
|
|
4
|
-
// 查询管理员信息(框架自动转换为小驼峰)
|
|
5
|
-
const admin = await befly.db.getOne({
|
|
6
|
-
table: 'addon_admin_admin',
|
|
7
|
-
where: { id: ctx.body.adminId }
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
if (!admin) {
|
|
11
|
-
return befly.tool.No('管理员不存在');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// 如果有角色编码,查询角色详细信息(使用 roleCode 而非 roleId)
|
|
15
|
-
let roleInfo = null;
|
|
16
|
-
if (admin.roleCode) {
|
|
17
|
-
roleInfo = await befly.db.getOne({
|
|
18
|
-
table: 'addon_admin_role',
|
|
19
|
-
where: { code: admin.roleCode }
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return befly.tool.Yes('操作成功', {
|
|
24
|
-
roleId: admin.roleId,
|
|
25
|
-
roleCode: admin.roleCode,
|
|
26
|
-
role: roleInfo
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
};
|
package/apis/admin/roleSave.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
name: '管理员角色保存',
|
|
3
|
-
fields: {
|
|
4
|
-
roleCode: {
|
|
5
|
-
name: '角色编码',
|
|
6
|
-
type: 'string',
|
|
7
|
-
min: 2,
|
|
8
|
-
max: 50,
|
|
9
|
-
regexp: '^[a-zA-Z0-9_]+$'
|
|
10
|
-
}
|
|
11
|
-
},
|
|
12
|
-
handler: async (befly, ctx) => {
|
|
13
|
-
// 查询角色是否存在(使用 roleCode 而非 roleId)
|
|
14
|
-
const role = await befly.db.getOne({
|
|
15
|
-
table: 'addon_admin_role',
|
|
16
|
-
where: { code: ctx.body.roleCode }
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
if (!role) {
|
|
20
|
-
return befly.tool.No('角色不存在');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// 根据角色编码判断角色类型(硬编码规则)
|
|
24
|
-
const roleType = role.code === 'dev' || role.code === 'admin' ? 'admin' : 'user';
|
|
25
|
-
|
|
26
|
-
// 更新管理员的角色ID、角色编码和角色类型
|
|
27
|
-
await befly.db.updData({
|
|
28
|
-
table: 'addon_admin_admin',
|
|
29
|
-
where: { id: ctx.body.id },
|
|
30
|
-
data: {
|
|
31
|
-
roleId: role.id,
|
|
32
|
-
roleCode: role.code,
|
|
33
|
-
roleType: roleType
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
return befly.tool.Yes('操作成功');
|
|
38
|
-
}
|
|
39
|
-
};
|
package/apis/auth/logout.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
name: '退出登录',
|
|
3
|
-
handler: async (befly, ctx) => {
|
|
4
|
-
// JWT token 是无状态的,前端删除 token 即可
|
|
5
|
-
// 如果需要实现 token 黑名单,可以在这里将 token 加入 Redis 黑名单
|
|
6
|
-
|
|
7
|
-
const token = ctx.headers.authorization?.replace('Bearer ', '');
|
|
8
|
-
|
|
9
|
-
if (token && befly.redis) {
|
|
10
|
-
// 将 token 加入黑名单,有效期设置为 token 的剩余有效期
|
|
11
|
-
const key = `token_blacklist:${token}`;
|
|
12
|
-
await befly.redis.set(key, '1', 'EX', 7 * 24 * 60 * 60); // 7天
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return befly.tool.Yes('退出成功');
|
|
16
|
-
}
|
|
17
|
-
};
|
package/apis/auth/register.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import adminTable from '../../tables/admin.json';
|
|
2
|
-
|
|
3
|
-
export default {
|
|
4
|
-
name: '管理员注册',
|
|
5
|
-
auth: false,
|
|
6
|
-
fields: {
|
|
7
|
-
name: adminTable.name,
|
|
8
|
-
email: adminTable.email,
|
|
9
|
-
password: adminTable.password
|
|
10
|
-
},
|
|
11
|
-
required: ['name', 'email', 'password'],
|
|
12
|
-
handler: async (befly, ctx) => {
|
|
13
|
-
// 检查邮箱是否已存在
|
|
14
|
-
const existingAdmin = await befly.db.getOne({
|
|
15
|
-
table: 'addon_admin_admin',
|
|
16
|
-
where: { email: ctx.body.email }
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
if (existingAdmin) {
|
|
20
|
-
return befly.tool.No('该邮箱已被注册');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// 加密密码
|
|
24
|
-
const hashedPassword = await befly.cipher.hashPassword(ctx.body.password);
|
|
25
|
-
|
|
26
|
-
// 创建管理员
|
|
27
|
-
const adminId = await befly.db.insData({
|
|
28
|
-
table: 'addon_admin_admin',
|
|
29
|
-
data: {
|
|
30
|
-
name: ctx.body.name,
|
|
31
|
-
email: ctx.body.email,
|
|
32
|
-
password: hashedPassword,
|
|
33
|
-
role: 'user' // 默认为普通用户,state 由框架自动设置为 1
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
return befly.tool.Yes('注册成功', {
|
|
38
|
-
id: adminId,
|
|
39
|
-
name: ctx.body.name,
|
|
40
|
-
email: ctx.body.email
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
name: '获取更新日志',
|
|
3
|
-
handler: async (befly, ctx) => {
|
|
4
|
-
// 更新日志数据(实际项目中可以从配置文件或数据库读取)
|
|
5
|
-
const changelog = [
|
|
6
|
-
{
|
|
7
|
-
version: 'v1.0.0',
|
|
8
|
-
date: '2025-10-25',
|
|
9
|
-
changes: ['新增角色权限管理功能', '新增菜单权限分配功能', '新增接口权限管理功能', '优化菜单同步性能', '优化字段类型验证', '修复数组类型字段验证bug']
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
version: 'v0.9.0',
|
|
13
|
-
date: '2025-10-20',
|
|
14
|
-
changes: ['初始版本发布', '完成基础框架搭建', '实现用户认证功能', '实现RBAC权限系统']
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
version: 'v0.8.0',
|
|
18
|
-
date: '2025-10-15',
|
|
19
|
-
changes: ['完成数据库设计', '实现核心API', '添加字段验证器', '集成Redis缓存']
|
|
20
|
-
}
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
// 根据 limit 参数返回指定数量的日志
|
|
24
|
-
const limit = ctx.body.limit || 5;
|
|
25
|
-
const lists = changelog.slice(0, limit);
|
|
26
|
-
|
|
27
|
-
return befly.tool.Yes('获取成功', {
|
|
28
|
-
lists: lists
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
};
|