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.
- package/LICENSE +201 -0
- package/README.md +188 -0
- package/jsconfig.json +14 -0
- package/package.json +51 -0
- package/styles/variables.scss +148 -0
- package/utils/arrayToTree.js +115 -0
- package/utils/cleanParams.js +29 -0
- package/utils/fieldClear.js +62 -0
- package/utils/genShortId.js +12 -0
- package/utils/hashPassword.js +9 -0
- package/utils/scanViewsDir.js +120 -0
- package/utils/withDefaultColumns.js +46 -0
- package/views/config/dict/components/edit.vue +120 -0
- package/views/config/dict/index.vue +188 -0
- package/views/config/dictType/components/edit.vue +110 -0
- package/views/config/dictType/index.vue +153 -0
- package/views/config/index.vue +3 -0
- package/views/config/system/components/edit.vue +184 -0
- package/views/config/system/index.vue +188 -0
- package/views/index/components/addonList.vue +148 -0
- package/views/index/components/environmentInfo.vue +116 -0
- package/views/index/components/operationLogs.vue +127 -0
- package/views/index/components/performanceMetrics.vue +153 -0
- package/views/index/components/quickActions.vue +30 -0
- package/views/index/components/serviceStatus.vue +197 -0
- package/views/index/components/systemNotifications.vue +144 -0
- package/views/index/components/systemOverview.vue +194 -0
- package/views/index/components/systemResources.vue +121 -0
- package/views/index/components/userInfo.vue +210 -0
- package/views/index/index.vue +67 -0
- package/views/jsconfig.json +15 -0
- package/views/log/email/index.vue +221 -0
- package/views/log/index.vue +3 -0
- package/views/log/login/index.vue +95 -0
- package/views/log/operate/index.vue +169 -0
- package/views/login_1/index.vue +400 -0
- package/views/people/admin/components/edit.vue +173 -0
- package/views/people/admin/index.vue +121 -0
- package/views/people/index.vue +3 -0
- package/views/permission/api/index.vue +146 -0
- package/views/permission/index.vue +3 -0
- package/views/permission/menu/index.vue +109 -0
- package/views/permission/role/components/api.vue +371 -0
- package/views/permission/role/components/edit.vue +143 -0
- package/views/permission/role/components/menu.vue +310 -0
- package/views/permission/role/index.vue +175 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<PageDialog v-model="dialogVisible" :title="$Prop.actionType === 'upd' ? '更新角色' : '添加角色'" :confirm-loading="$Data.submitting" @confirm="onSubmit">
|
|
3
|
+
<div class="comp-role-edit">
|
|
4
|
+
<TForm :model="$Data.formData" label-width="120px" label-position="left" :rules="$Data2.formRules" ref="formRef">
|
|
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
|
+
</PageDialog>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script setup lang="ts">
|
|
29
|
+
import { computed, reactive, ref } from "vue";
|
|
30
|
+
|
|
31
|
+
import {
|
|
32
|
+
//
|
|
33
|
+
Form as TForm,
|
|
34
|
+
FormItem as TFormItem,
|
|
35
|
+
Input as TInput,
|
|
36
|
+
InputNumber as TInputNumber,
|
|
37
|
+
RadioGroup as TRadioGroup,
|
|
38
|
+
Radio as TRadio,
|
|
39
|
+
Button as TButton,
|
|
40
|
+
MessagePlugin
|
|
41
|
+
} from "tdesign-vue-next";
|
|
42
|
+
import PageDialog from "@/components/pageDialog.vue";
|
|
43
|
+
import { $Http } from "@/plugins/http";
|
|
44
|
+
import { fieldClear } from "befly-admin-ui/utils/fieldClear";
|
|
45
|
+
|
|
46
|
+
const $Prop = defineProps({
|
|
47
|
+
modelValue: {
|
|
48
|
+
type: Boolean,
|
|
49
|
+
default: false
|
|
50
|
+
},
|
|
51
|
+
actionType: {
|
|
52
|
+
type: String,
|
|
53
|
+
default: "add"
|
|
54
|
+
},
|
|
55
|
+
rowData: {
|
|
56
|
+
type: Object,
|
|
57
|
+
default: {}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const $Emit = defineEmits<{
|
|
62
|
+
(e: "update:modelValue", value: boolean): void;
|
|
63
|
+
(e: "success"): void;
|
|
64
|
+
}>();
|
|
65
|
+
|
|
66
|
+
type PageDialogEventContext = {
|
|
67
|
+
close: () => void;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// 表单引用
|
|
71
|
+
type TDesignFormInstance = {
|
|
72
|
+
validate: () => Promise<unknown>;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const formRef = ref<TDesignFormInstance | null>(null);
|
|
76
|
+
|
|
77
|
+
const $Computed = {};
|
|
78
|
+
|
|
79
|
+
const $Data = reactive({
|
|
80
|
+
submitting: false,
|
|
81
|
+
formData: {
|
|
82
|
+
id: 0,
|
|
83
|
+
name: "",
|
|
84
|
+
code: "",
|
|
85
|
+
description: "",
|
|
86
|
+
sort: 0,
|
|
87
|
+
state: 1
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const $Data2 = reactive({
|
|
92
|
+
formRules: {
|
|
93
|
+
name: [{ required: true, message: "请输入角色名称", trigger: "blur" }],
|
|
94
|
+
code: [
|
|
95
|
+
{ required: true, message: "请输入角色代码", trigger: "blur" },
|
|
96
|
+
{ pattern: /^[a-zA-Z0-9_]+$/, message: "角色代码只能包含字母、数字和下划线", trigger: "blur" }
|
|
97
|
+
],
|
|
98
|
+
sort: [{ type: "number", message: "排序必须是数字", trigger: "blur" }]
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
async function initData(): Promise<void> {
|
|
103
|
+
if ($Prop.actionType === "upd" && $Prop.rowData.id) {
|
|
104
|
+
$Data.formData = Object.assign({}, $Prop.rowData);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const dialogVisible = computed({
|
|
109
|
+
get: () => $Prop.modelValue,
|
|
110
|
+
set: (value) => {
|
|
111
|
+
$Emit("update:modelValue", value);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
async function onSubmit(context?: PageDialogEventContext): Promise<void> {
|
|
116
|
+
try {
|
|
117
|
+
const form = formRef.value;
|
|
118
|
+
if (!form) {
|
|
119
|
+
MessagePlugin.warning("表单未就绪");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const valid = await form.validate();
|
|
124
|
+
if (!valid) return;
|
|
125
|
+
|
|
126
|
+
$Data.submitting = true;
|
|
127
|
+
const formData = $Prop.actionType === "add" ? fieldClear($Data.formData, { omitKeys: ["id", "state"] }) : $Data.formData;
|
|
128
|
+
const res = await $Http.post($Prop.actionType === "upd" ? "/core/role/upd" : "/core/role/ins", formData);
|
|
129
|
+
|
|
130
|
+
MessagePlugin.success(res.msg);
|
|
131
|
+
$Emit("success");
|
|
132
|
+
if (context && typeof context.close === "function") {
|
|
133
|
+
context.close();
|
|
134
|
+
}
|
|
135
|
+
} catch (error) {
|
|
136
|
+
MessagePlugin.error((error as { msg?: string }).msg || "提交失败");
|
|
137
|
+
} finally {
|
|
138
|
+
$Data.submitting = false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
initData();
|
|
143
|
+
</script>
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<PageDialog v-model="dialogVisible" title="菜单权限" width="900px" :confirm-loading="$Data.submitting" @confirm="onSubmit">
|
|
3
|
+
<div class="comp-role-menu">
|
|
4
|
+
<!-- 搜索框 -->
|
|
5
|
+
<div class="search-box">
|
|
6
|
+
<TInput v-model="$Data.searchText" placeholder="搜索菜单名称或路径" clearable @change="onSearch">
|
|
7
|
+
<template #prefix-icon>
|
|
8
|
+
<SearchIcon />
|
|
9
|
+
</template>
|
|
10
|
+
</TInput>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div class="menu-container">
|
|
14
|
+
<div class="menu-group" v-for="group in $Data.filteredMenuGroups" :key="group.name">
|
|
15
|
+
<div class="group-header">{{ group.title }}</div>
|
|
16
|
+
<div class="menu-checkbox-list">
|
|
17
|
+
<TCheckboxGroup v-model="$Data.checkedMenuPaths">
|
|
18
|
+
<TCheckbox v-for="menu in group.menus" :key="menu.value" :value="menu.value">
|
|
19
|
+
<div class="menu-checkbox-label">
|
|
20
|
+
<div class="menu-label-main">
|
|
21
|
+
<div class="menu-name" :title="menu.path ? `${menu.name}\n${menu.path}` : menu.name">
|
|
22
|
+
{{ menu.name }}
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</TCheckbox>
|
|
27
|
+
</TCheckboxGroup>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</PageDialog>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<script setup lang="ts">
|
|
36
|
+
import { computed, reactive } from "vue";
|
|
37
|
+
|
|
38
|
+
import { CheckboxGroup as TCheckboxGroup, Checkbox as TCheckbox, Input as TInput, MessagePlugin } from "tdesign-vue-next";
|
|
39
|
+
import { SearchIcon } from "tdesign-icons-vue-next";
|
|
40
|
+
import PageDialog from "@/components/pageDialog.vue";
|
|
41
|
+
import { $Http } from "@/plugins/http";
|
|
42
|
+
import { arrayToTree } from "befly-admin-ui/utils/arrayToTree";
|
|
43
|
+
|
|
44
|
+
const $Prop = defineProps({
|
|
45
|
+
modelValue: {
|
|
46
|
+
type: Boolean,
|
|
47
|
+
default: false
|
|
48
|
+
},
|
|
49
|
+
rowData: {
|
|
50
|
+
type: Object,
|
|
51
|
+
default: () => ({})
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const $Emit = defineEmits<{
|
|
56
|
+
(e: "update:modelValue", value: boolean): void;
|
|
57
|
+
(e: "success"): void;
|
|
58
|
+
}>();
|
|
59
|
+
|
|
60
|
+
type PageDialogEventContext = {
|
|
61
|
+
close: () => void;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const dialogVisible = computed({
|
|
65
|
+
get: () => $Prop.modelValue,
|
|
66
|
+
set: (value) => {
|
|
67
|
+
$Emit("update:modelValue", value);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const $Data = reactive({
|
|
72
|
+
submitting: false,
|
|
73
|
+
searchText: "",
|
|
74
|
+
menuGroups: [],
|
|
75
|
+
filteredMenuGroups: [],
|
|
76
|
+
checkedMenuPaths: []
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
async function initData(): Promise<void> {
|
|
80
|
+
await Promise.all([apiMenuAll(), apiRoleMenuDetail()]);
|
|
81
|
+
$Data.filteredMenuGroups = $Data.menuGroups;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function apiMenuAll(): Promise<void> {
|
|
85
|
+
try {
|
|
86
|
+
const res = await $Http.post(
|
|
87
|
+
"/core/menu/all",
|
|
88
|
+
{},
|
|
89
|
+
{
|
|
90
|
+
dropValues: [""]
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
const lists = Array.isArray(res?.data?.lists) ? res.data.lists : [];
|
|
94
|
+
|
|
95
|
+
const treeResult = arrayToTree(lists, "path", "parentPath", "children", "sort");
|
|
96
|
+
const roots = Array.isArray(treeResult?.tree) ? treeResult.tree : [];
|
|
97
|
+
|
|
98
|
+
const groups: Array<{ name: string; title: string; menus: unknown[] }> = [];
|
|
99
|
+
for (const root of roots) {
|
|
100
|
+
const rootPath = typeof root?.path === "string" ? root.path : "";
|
|
101
|
+
const rootName = typeof root?.name === "string" ? root.name : "";
|
|
102
|
+
|
|
103
|
+
const menus: unknown[] = [];
|
|
104
|
+
|
|
105
|
+
const walk = (node: Record<string, unknown>, depth: number): void => {
|
|
106
|
+
const name = typeof node["name"] === "string" ? String(node["name"]) : "";
|
|
107
|
+
const path = typeof node["path"] === "string" ? String(node["path"]) : "";
|
|
108
|
+
if (path.length > 0) {
|
|
109
|
+
menus.push({
|
|
110
|
+
value: path,
|
|
111
|
+
name: name,
|
|
112
|
+
path: path,
|
|
113
|
+
depth: depth,
|
|
114
|
+
label: `${name} ${path}`.trim()
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const children = Array.isArray(node["children"]) ? (node["children"] as unknown[]) : [];
|
|
119
|
+
for (const child of children) {
|
|
120
|
+
if (child && typeof child === "object") {
|
|
121
|
+
walk(child as Record<string, unknown>, depth + 1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
walk(root as Record<string, unknown>, 0);
|
|
127
|
+
|
|
128
|
+
const groupTitle = rootName.length > 0 ? rootName : rootPath;
|
|
129
|
+
groups.push({
|
|
130
|
+
name: rootPath.length > 0 ? rootPath : groupTitle,
|
|
131
|
+
title: groupTitle.length > 0 ? groupTitle : "未命名菜单",
|
|
132
|
+
menus: menus
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
$Data.menuGroups = groups as never;
|
|
137
|
+
} catch (_error) {
|
|
138
|
+
MessagePlugin.error("加载菜单失败");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function apiRoleMenuDetail(): Promise<void> {
|
|
143
|
+
if (!$Prop.rowData.id) return;
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const res = await $Http.post(
|
|
147
|
+
"/core/role/menus",
|
|
148
|
+
{
|
|
149
|
+
roleCode: $Prop.rowData.code
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
dropValues: [""]
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
$Data.checkedMenuPaths = Array.isArray(res.data) ? res.data : [];
|
|
157
|
+
} catch (_error) {
|
|
158
|
+
MessagePlugin.error("加载数据失败");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function onSearch(): void {
|
|
163
|
+
const kw = typeof $Data.searchText === "string" ? $Data.searchText.trim().toLowerCase() : "";
|
|
164
|
+
if (kw.length === 0) {
|
|
165
|
+
$Data.filteredMenuGroups = $Data.menuGroups;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
$Data.filteredMenuGroups = ($Data.menuGroups as unknown[])
|
|
170
|
+
.map((group) => {
|
|
171
|
+
const groupRecord = group && typeof group === "object" ? (group as Record<string, unknown>) : null;
|
|
172
|
+
const groupMenus = groupRecord && Array.isArray(groupRecord["menus"]) ? (groupRecord["menus"] as unknown[]) : [];
|
|
173
|
+
|
|
174
|
+
const menus = groupMenus.filter((menu) => {
|
|
175
|
+
const menuRecord = menu && typeof menu === "object" ? (menu as Record<string, unknown>) : null;
|
|
176
|
+
const label = menuRecord && typeof menuRecord["label"] === "string" ? String(menuRecord["label"]) : "";
|
|
177
|
+
return label.toLowerCase().includes(kw);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
name: groupRecord && typeof groupRecord["name"] === "string" ? String(groupRecord["name"]) : "",
|
|
182
|
+
title: groupRecord && typeof groupRecord["title"] === "string" ? String(groupRecord["title"]) : "",
|
|
183
|
+
menus: menus
|
|
184
|
+
};
|
|
185
|
+
})
|
|
186
|
+
.filter((group) => group.menus.length > 0) as never;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function onSubmit(context?: PageDialogEventContext): Promise<void> {
|
|
190
|
+
try {
|
|
191
|
+
$Data.submitting = true;
|
|
192
|
+
|
|
193
|
+
const res = await $Http.post("/core/role/menuSave", {
|
|
194
|
+
roleCode: $Prop.rowData.code,
|
|
195
|
+
menuPaths: $Data.checkedMenuPaths
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
if (res.code === 0) {
|
|
199
|
+
MessagePlugin.success("保存成功");
|
|
200
|
+
$Emit("success");
|
|
201
|
+
if (context && typeof context.close === "function") {
|
|
202
|
+
context.close();
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
MessagePlugin.error(res.msg || "保存失败");
|
|
206
|
+
}
|
|
207
|
+
} catch (_error) {
|
|
208
|
+
MessagePlugin.error("保存失败");
|
|
209
|
+
} finally {
|
|
210
|
+
$Data.submitting = false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
initData();
|
|
215
|
+
</script>
|
|
216
|
+
|
|
217
|
+
<style scoped lang="scss">
|
|
218
|
+
.comp-role-menu {
|
|
219
|
+
height: 60vh;
|
|
220
|
+
display: flex;
|
|
221
|
+
flex-direction: column;
|
|
222
|
+
gap: 12px;
|
|
223
|
+
|
|
224
|
+
.menu-container {
|
|
225
|
+
flex: 1;
|
|
226
|
+
overflow-y: auto;
|
|
227
|
+
|
|
228
|
+
.menu-group {
|
|
229
|
+
margin-bottom: 16px;
|
|
230
|
+
border: 1px solid var(--border-color);
|
|
231
|
+
border-radius: var(--border-radius-small);
|
|
232
|
+
overflow: hidden;
|
|
233
|
+
|
|
234
|
+
&:last-child {
|
|
235
|
+
margin-bottom: 0;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.group-header {
|
|
239
|
+
padding: 12px 16px;
|
|
240
|
+
background-color: var(--primary-color-light);
|
|
241
|
+
font-weight: 500;
|
|
242
|
+
font-size: var(--font-size-sm);
|
|
243
|
+
color: var(--text-primary);
|
|
244
|
+
display: flex;
|
|
245
|
+
align-items: center;
|
|
246
|
+
gap: 8px;
|
|
247
|
+
|
|
248
|
+
&::before {
|
|
249
|
+
content: "";
|
|
250
|
+
width: 12px;
|
|
251
|
+
height: 12px;
|
|
252
|
+
border-radius: 50%;
|
|
253
|
+
background-color: var(--primary-color);
|
|
254
|
+
box-shadow: 0 0 0 2px var(--bg-color-container);
|
|
255
|
+
flex-shrink: 0;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.menu-checkbox-list {
|
|
260
|
+
padding: 10px;
|
|
261
|
+
|
|
262
|
+
:deep(.t-checkbox-group) {
|
|
263
|
+
display: flex;
|
|
264
|
+
flex-wrap: wrap;
|
|
265
|
+
gap: 12px;
|
|
266
|
+
width: 100%;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
:deep(.t-checkbox) {
|
|
270
|
+
flex: 0 0 calc(33.333% - 8px);
|
|
271
|
+
margin: 0;
|
|
272
|
+
min-width: 0;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
:deep(.t-checkbox__label) {
|
|
276
|
+
min-width: 0;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.menu-checkbox-label {
|
|
284
|
+
width: 100%;
|
|
285
|
+
display: flex;
|
|
286
|
+
align-items: center;
|
|
287
|
+
justify-content: space-between;
|
|
288
|
+
gap: 8px;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.menu-label-main {
|
|
292
|
+
min-width: 0;
|
|
293
|
+
display: flex;
|
|
294
|
+
flex-direction: column;
|
|
295
|
+
gap: 2px;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.menu-name {
|
|
299
|
+
max-width: 100%;
|
|
300
|
+
overflow: hidden;
|
|
301
|
+
text-overflow: ellipsis;
|
|
302
|
+
white-space: nowrap;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.dialog-footer {
|
|
306
|
+
width: 100%;
|
|
307
|
+
display: flex;
|
|
308
|
+
justify-content: center;
|
|
309
|
+
}
|
|
310
|
+
</style>
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<PagedTableDetail class="page-role page-table" :columns="$Data.columns" :endpoints="$Data.endpoints" :table-slot-names="['state', 'menuCount', 'apiCount']">
|
|
3
|
+
<template #toolLeft>
|
|
4
|
+
<TButton theme="primary" @click="onAdd">
|
|
5
|
+
<template #icon>
|
|
6
|
+
<AddIcon />
|
|
7
|
+
</template>
|
|
8
|
+
</TButton>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<template #toolRight="scope">
|
|
12
|
+
<TButton shape="circle" @click="onReload(scope.reload)">
|
|
13
|
+
<template #icon>
|
|
14
|
+
<RefreshIcon />
|
|
15
|
+
</template>
|
|
16
|
+
</TButton>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<template #state="{ row }">
|
|
20
|
+
<TTag v-if="row.state === 1" shape="round" theme="success" variant="light-outline">正常</TTag>
|
|
21
|
+
<TTag v-else-if="row.state === 2" shape="round" theme="warning" variant="light-outline">禁用</TTag>
|
|
22
|
+
<TTag v-else shape="round" theme="danger" variant="light-outline">已删除</TTag>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<template #menuCount="{ row }">
|
|
26
|
+
{{ getPathCount(row.menus) }}
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<template #apiCount="{ row }">
|
|
30
|
+
{{ getPathCount(row.apis) }}
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<template #operation="{ row, deleteRow }">
|
|
34
|
+
<TDropdown trigger="click" placement="bottom-right" @click="onDropdownAction($event, row, deleteRow)">
|
|
35
|
+
<TButton theme="primary" size="small">
|
|
36
|
+
操作
|
|
37
|
+
<template #suffix><ChevronDownIcon /></template>
|
|
38
|
+
</TButton>
|
|
39
|
+
<TDropdownMenu slot="dropdown">
|
|
40
|
+
<TDropdownItem value="upd">
|
|
41
|
+
<EditIcon />
|
|
42
|
+
编辑
|
|
43
|
+
</TDropdownItem>
|
|
44
|
+
<TDropdownItem value="menu">
|
|
45
|
+
<SettingIcon />
|
|
46
|
+
菜单权限
|
|
47
|
+
</TDropdownItem>
|
|
48
|
+
<TDropdownItem value="api">
|
|
49
|
+
<CodeIcon />
|
|
50
|
+
接口权限
|
|
51
|
+
</TDropdownItem>
|
|
52
|
+
<TDropdownItem value="del" :divider="true">
|
|
53
|
+
<DeleteIcon style="width: 14px; height: 14px; margin-right: 6px" />
|
|
54
|
+
删除
|
|
55
|
+
</TDropdownItem>
|
|
56
|
+
</TDropdownMenu>
|
|
57
|
+
</TDropdown>
|
|
58
|
+
</template>
|
|
59
|
+
|
|
60
|
+
<template #dialogs="scope">
|
|
61
|
+
<!-- 编辑对话框组件 -->
|
|
62
|
+
<EditDialog v-if="$Data.editVisible" v-model="$Data.editVisible" :action-type="$Data.actionType" :row-data="$Data.rowData" @success="onDialogSuccess(scope.reload)" />
|
|
63
|
+
|
|
64
|
+
<!-- 菜单权限对话框组件 -->
|
|
65
|
+
<MenuDialog v-if="$Data.menuVisible" v-model="$Data.menuVisible" :row-data="$Data.rowData" @success="onDialogSuccess(scope.reload)" />
|
|
66
|
+
|
|
67
|
+
<!-- 接口权限对话框组件 -->
|
|
68
|
+
<ApiDialog v-if="$Data.apiVisible" v-model="$Data.apiVisible" :row-data="$Data.rowData" @success="onDialogSuccess(scope.reload)" />
|
|
69
|
+
</template>
|
|
70
|
+
</PagedTableDetail>
|
|
71
|
+
</template>
|
|
72
|
+
|
|
73
|
+
<script setup lang="ts">
|
|
74
|
+
import { reactive } from "vue";
|
|
75
|
+
import { Button as TButton, Dropdown as TDropdown, DropdownItem as TDropdownItem, DropdownMenu as TDropdownMenu, Tag as TTag } from "tdesign-vue-next";
|
|
76
|
+
import { AddIcon, ChevronDownIcon, CodeIcon, DeleteIcon, EditIcon, RefreshIcon, SettingIcon } from "tdesign-icons-vue-next";
|
|
77
|
+
import EditDialog from "./components/edit.vue";
|
|
78
|
+
import MenuDialog from "./components/menu.vue";
|
|
79
|
+
import ApiDialog from "./components/api.vue";
|
|
80
|
+
import PagedTableDetail from "@/components/pagedTableDetail.vue";
|
|
81
|
+
import { withDefaultColumns } from "befly-admin-ui/utils/withDefaultColumns";
|
|
82
|
+
|
|
83
|
+
// 响应式数据
|
|
84
|
+
const $Data = reactive({
|
|
85
|
+
columns: withDefaultColumns([
|
|
86
|
+
{ colKey: "name", title: "角色名称" },
|
|
87
|
+
{ colKey: "code", title: "角色代码", width: 150 },
|
|
88
|
+
{ colKey: "menuCount", title: "菜单数量", align: "center", width: 100 },
|
|
89
|
+
{ colKey: "apiCount", title: "接口数量", align: "center", width: 100 },
|
|
90
|
+
{ colKey: "sort", title: "排序" },
|
|
91
|
+
{ colKey: "state", title: "状态" },
|
|
92
|
+
{ colKey: "description", title: "描述" },
|
|
93
|
+
{ colKey: "operation", title: "操作" }
|
|
94
|
+
]),
|
|
95
|
+
endpoints: {
|
|
96
|
+
list: {
|
|
97
|
+
path: "/core/role/list",
|
|
98
|
+
dropValues: [""]
|
|
99
|
+
},
|
|
100
|
+
delete: {
|
|
101
|
+
path: "/core/role/del",
|
|
102
|
+
idKey: "id",
|
|
103
|
+
confirm: (row) => {
|
|
104
|
+
return {
|
|
105
|
+
header: "确认删除",
|
|
106
|
+
body: `确认删除角色“${row.name}”吗?`,
|
|
107
|
+
confirmBtn: "删除",
|
|
108
|
+
status: "warning"
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
editVisible: false,
|
|
114
|
+
menuVisible: false,
|
|
115
|
+
apiVisible: false,
|
|
116
|
+
actionType: "add",
|
|
117
|
+
rowData: {}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
function onAdd(): void {
|
|
121
|
+
onAction("add", {});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function onReload(reload: (options: { keepSelection?: boolean }) => void): void {
|
|
125
|
+
reload({ keepSelection: true });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function onDialogSuccess(reload: (options: { keepSelection?: boolean }) => void): void {
|
|
129
|
+
reload({ keepSelection: true });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function getPathCount(value: unknown): number {
|
|
133
|
+
if (!Array.isArray(value)) return 0;
|
|
134
|
+
return value.filter((p) => typeof p === "string" && p.trim().length > 0).length;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function onAction(command: string, rowData: Record<string, unknown>): void {
|
|
138
|
+
$Data.actionType = command;
|
|
139
|
+
|
|
140
|
+
if (command === "add") {
|
|
141
|
+
$Data.rowData = {};
|
|
142
|
+
} else {
|
|
143
|
+
$Data.rowData = Object.assign({}, rowData);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (command === "add" || command === "upd") {
|
|
147
|
+
$Data.editVisible = true;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (command === "menu") {
|
|
152
|
+
$Data.menuVisible = true;
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (command === "api") {
|
|
157
|
+
$Data.apiVisible = true;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function onDropdownAction(data: unknown, rowData: Record<string, unknown>, deleteRow: (row: Record<string, unknown>) => void): void {
|
|
162
|
+
const record = data as Record<string, unknown>;
|
|
163
|
+
const rawValue = record && record["value"] ? record["value"] : "";
|
|
164
|
+
const cmd = rawValue ? String(rawValue) : "";
|
|
165
|
+
if (cmd === "del") {
|
|
166
|
+
deleteRow(rowData);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
onAction(cmd, rowData);
|
|
170
|
+
}
|
|
171
|
+
</script>
|
|
172
|
+
|
|
173
|
+
<style scoped lang="scss">
|
|
174
|
+
/* page styles */
|
|
175
|
+
</style>
|