init
This commit is contained in:
831
src/views/authority/apikey.vue
Normal file
831
src/views/authority/apikey.vue
Normal file
@@ -0,0 +1,831 @@
|
||||
<template>
|
||||
<basic-container>
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
ref="crud"
|
||||
v-model="form"
|
||||
:before-open="beforeOpen"
|
||||
@row-del="rowDel"
|
||||
@row-update="rowUpdate"
|
||||
@row-save="rowSave"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="userInfo.authority.includes('admin')"
|
||||
@click="handleDelete"
|
||||
>删 除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'success' : 'danger'">
|
||||
{{ row.status === 1 ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #apiKey="{ row }">
|
||||
<el-tag>{{ row.apiKey }}</el-tag>
|
||||
</template>
|
||||
<template #expireTime="{ row }">
|
||||
<el-tag>{{ row.expireTimeInfo }}</el-tag>
|
||||
</template>
|
||||
<template #menu="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-circle-check"
|
||||
v-if="row.status === -1 && userInfo.authority.includes('admin')"
|
||||
@click.stop="handleEnable(row)"
|
||||
>启用
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-circle-close"
|
||||
v-if="row.status === 1 && userInfo.authority.includes('admin')"
|
||||
@click.stop="handleDisable(row)"
|
||||
>禁用
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-notebook"
|
||||
v-if="userInfo.authority.includes('admin')"
|
||||
@click.stop="handleRowLog(row)"
|
||||
>日志
|
||||
</el-button>
|
||||
</template>
|
||||
<!-- 访问权限动态表单 -->
|
||||
<template #apiPath-form="{ type }">
|
||||
<el-row
|
||||
v-for="(item, index) in apiPathList"
|
||||
:key="index"
|
||||
:gutter="8"
|
||||
style="margin-bottom: 8px"
|
||||
>
|
||||
<el-col :span="22">
|
||||
<el-input
|
||||
v-model="apiPathList[index]"
|
||||
placeholder="请输入URL路径"
|
||||
:disabled="type === 'view'"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="2" v-if="type !== 'view'">
|
||||
<el-button type="danger" text icon="el-icon-delete" @click="removeApiPath(index)" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-if="type !== 'view'" :gutter="8">
|
||||
<el-col :span="8">
|
||||
<el-button type="primary" text icon="el-icon-circle-plus" @click="addApiPath"
|
||||
>添加路径</el-button
|
||||
>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row style="margin-top: 8px">
|
||||
<el-text type="info" size="small">
|
||||
<el-icon><WarningFilled /></el-icon>
|
||||
提示: 支持 Ant 风格路径匹配,路径需以服务前缀开头,如 /** 匹配所有、/blade-system/user/*
|
||||
匹配单层、/blade-system/user/** 匹配多层。
|
||||
</el-text>
|
||||
</el-row>
|
||||
</template>
|
||||
<!-- 扩展参数动态表单 -->
|
||||
<template #extParams-form="{ type }">
|
||||
<el-row
|
||||
v-for="(item, index) in extParamsList"
|
||||
:key="index"
|
||||
:gutter="8"
|
||||
style="margin-bottom: 8px"
|
||||
>
|
||||
<el-col :span="8">
|
||||
<el-input v-model="item.key" placeholder="参数名" :disabled="type === 'view'" />
|
||||
</el-col>
|
||||
<el-col :span="14">
|
||||
<el-input v-model="item.value" placeholder="参数值" :disabled="type === 'view'" />
|
||||
</el-col>
|
||||
<el-col :span="2" v-if="type !== 'view'">
|
||||
<el-button type="danger" text icon="el-icon-delete" @click="removeExtParam(index)" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-if="type !== 'view'" :gutter="8">
|
||||
<el-col :span="8">
|
||||
<el-button type="primary" text icon="el-icon-circle-plus" @click="addExtParam"
|
||||
>添加参数</el-button
|
||||
>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row style="margin-top: 8px">
|
||||
<el-text type="info" size="small">
|
||||
<el-icon><WarningFilled /></el-icon>
|
||||
提示: 可配置 deptId、postId、roleId 等参数自定义 API Key 属性。
|
||||
</el-text>
|
||||
</el-row>
|
||||
</template>
|
||||
</avue-crud>
|
||||
|
||||
<!-- 单个密钥调用日志 Drawer -->
|
||||
<el-drawer
|
||||
:title="`[${logKeyName}] 调用日志`"
|
||||
v-model="logDrawerVisible"
|
||||
append-to-body
|
||||
size="1200px"
|
||||
>
|
||||
<basic-container>
|
||||
<avue-crud
|
||||
:option="logOption"
|
||||
:table-loading="logLoading"
|
||||
:data="logData"
|
||||
v-model:page="logPage"
|
||||
ref="logCrud"
|
||||
v-model="logForm"
|
||||
:before-open="beforeOpenLog"
|
||||
@search-change="logSearchChange"
|
||||
@search-reset="logSearchReset"
|
||||
@current-change="logCurrentChange"
|
||||
@size-change="logSizeChange"
|
||||
@refresh-change="logRefreshChange"
|
||||
@on-load="onLoadLog"
|
||||
>
|
||||
<template #requestUri="{ row }">
|
||||
<el-tag>{{ row.requestUri }}</el-tag>
|
||||
</template>
|
||||
<template #method="{ row }">
|
||||
<el-tag>{{ row.method }}</el-tag>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</basic-container>
|
||||
</el-drawer>
|
||||
|
||||
<!-- API Key 创建成功弹框 -->
|
||||
<el-dialog
|
||||
v-model="apiKeyDialogVisible"
|
||||
:show-close="false"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
width="620px"
|
||||
align-center
|
||||
title=""
|
||||
>
|
||||
<el-row justify="center">
|
||||
<el-icon :size="48" color="#10b981"><CircleCheckFilled /></el-icon>
|
||||
</el-row>
|
||||
<el-row justify="center" style="margin-top: 16px">
|
||||
<el-text size="large" tag="b">API Key 创建成功</el-text>
|
||||
</el-row>
|
||||
<el-row style="margin-top: 24px">
|
||||
<el-divider content-position="left">
|
||||
<el-text type="primary" tag="b">密钥信息</el-text>
|
||||
</el-divider>
|
||||
</el-row>
|
||||
<el-row style="margin-top: 12px">
|
||||
<el-card
|
||||
shadow="never"
|
||||
style="width: 100%; background: #f5f7fa"
|
||||
:body-style="{ textAlign: 'center', padding: '12px' }"
|
||||
>
|
||||
<el-text tag="code" style="word-break: break-all">{{ createdApiKey }}</el-text>
|
||||
</el-card>
|
||||
</el-row>
|
||||
<el-row justify="center" style="margin-top: 12px">
|
||||
<el-space :size="6">
|
||||
<el-icon color="#3b82f6" :size="16"><Clock /></el-icon>
|
||||
<el-text size="small" v-if="createdExpireTime">过期时间:{{ createdExpireTime }}</el-text>
|
||||
<el-text size="small" v-else>过期时间:永久有效</el-text>
|
||||
</el-space>
|
||||
</el-row>
|
||||
<el-row justify="center" style="margin-top: 12px">
|
||||
<el-space :size="6">
|
||||
<el-icon color="#f59e0b" :size="16"><WarningFilled /></el-icon>
|
||||
<el-text size="small" type="warning">请尽快复制保存,关闭后将无法再次查看</el-text>
|
||||
</el-space>
|
||||
</el-row>
|
||||
<el-row style="margin-top: 24px">
|
||||
<el-divider content-position="left">
|
||||
<el-text type="primary" tag="b">使用说明</el-text>
|
||||
</el-divider>
|
||||
</el-row>
|
||||
<el-row style="margin-top: 12px">
|
||||
<el-text type="info" size="small"> 在调用 API 时,需要在请求头中携带以下信息: </el-text>
|
||||
</el-row>
|
||||
<el-row style="margin-top: 12px">
|
||||
<el-card
|
||||
shadow="never"
|
||||
style="width: 100%; background: #f5f7fa"
|
||||
:body-style="{ padding: '12px' }"
|
||||
>
|
||||
<el-space direction="vertical" :size="8" style="width: 100%; align-items: flex-start">
|
||||
<el-text size="small" style="word-break: break-all">
|
||||
<el-text type="primary" tag="b">Blade-Auth:</el-text> {{ createdApiKey }}
|
||||
</el-text>
|
||||
<el-text size="small">
|
||||
<el-text type="primary" tag="b">Blade-Requested-With:</el-text> BladeHttpRequest
|
||||
</el-text>
|
||||
</el-space>
|
||||
</el-card>
|
||||
</el-row>
|
||||
<el-row style="margin-top: 16px">
|
||||
<el-text type="info" size="small">cURL 调用示例:</el-text>
|
||||
</el-row>
|
||||
<el-row style="margin-top: 8px">
|
||||
<el-card
|
||||
shadow="never"
|
||||
style="width: 100%; background: #1e1e1e"
|
||||
:body-style="{ padding: '12px' }"
|
||||
>
|
||||
<el-text
|
||||
type="success"
|
||||
size="small"
|
||||
tag="code"
|
||||
style="
|
||||
color: #a6e3a1;
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
font-family: 'Courier New', monospace;
|
||||
line-height: 1.6;
|
||||
"
|
||||
>{{ curlCommand }}</el-text
|
||||
>
|
||||
</el-card>
|
||||
</el-row>
|
||||
<template #footer>
|
||||
<el-row justify="center" :gutter="12">
|
||||
<el-col :span="12" style="text-align: right; padding-right: 6px">
|
||||
<el-button type="primary" size="large" @click="copyAndCloseDialog" style="width: 100%">
|
||||
<el-space>
|
||||
<el-icon><DocumentCopy /></el-icon>
|
||||
<span>复制 API Key</span>
|
||||
</el-space>
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="12" style="text-align: left; padding-left: 6px">
|
||||
<el-button size="large" @click="copyCurlCommand" style="width: 100%">
|
||||
<el-space>
|
||||
<el-icon><DocumentCopy /></el-icon>
|
||||
<span>复制 cURL 命令</span>
|
||||
</el-space>
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getList, getDetail, remove, add, update, enable, disable } from '@/api/system/apikey';
|
||||
import { getList as getLogList, getDetail as getLogDetail } from '@/api/system/apikeylog';
|
||||
import { getUserPage, getUser } from '@/api/system/user';
|
||||
import { mapGetters } from 'vuex';
|
||||
import {
|
||||
CircleCheckFilled,
|
||||
Clock,
|
||||
WarningFilled,
|
||||
DocumentCopy,
|
||||
Delete,
|
||||
Plus,
|
||||
Notebook,
|
||||
} from '@element-plus/icons-vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CircleCheckFilled,
|
||||
Clock,
|
||||
WarningFilled,
|
||||
DocumentCopy,
|
||||
Delete,
|
||||
Plus,
|
||||
Notebook,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
query: {},
|
||||
loading: true,
|
||||
apiPathList: [],
|
||||
extParamsList: [],
|
||||
apiKeyDialogVisible: false,
|
||||
createdApiKey: '',
|
||||
createdExpireTime: '',
|
||||
page: {
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
selectionList: [],
|
||||
option: {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
dialogWidth: 700,
|
||||
menuWidth: 350,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
selection: true,
|
||||
dialogClickModal: false,
|
||||
column: [
|
||||
{
|
||||
label: 'API Key',
|
||||
prop: 'apiKey',
|
||||
slot: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
span: 24,
|
||||
width: 280,
|
||||
},
|
||||
{
|
||||
label: '密钥名称',
|
||||
prop: 'name',
|
||||
search: true,
|
||||
editDisabled: true,
|
||||
span: 24,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入密钥名称',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '绑定用户',
|
||||
prop: 'userId',
|
||||
type: 'table',
|
||||
editDisabled: true,
|
||||
props: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
children: {
|
||||
border: true,
|
||||
column: [
|
||||
{
|
||||
label: '账号',
|
||||
prop: 'account',
|
||||
search: true,
|
||||
},
|
||||
{
|
||||
label: '姓名',
|
||||
prop: 'name',
|
||||
search: true,
|
||||
},
|
||||
{
|
||||
label: '角色',
|
||||
prop: 'roleName',
|
||||
},
|
||||
{
|
||||
label: '部门',
|
||||
prop: 'deptName',
|
||||
},
|
||||
{
|
||||
label: '岗位',
|
||||
prop: 'postName',
|
||||
},
|
||||
],
|
||||
},
|
||||
formatter(row) {
|
||||
if (!row.name) return '';
|
||||
return row.name + '(' + row.account + ')';
|
||||
},
|
||||
onLoad: ({ page, value, data }, callback) => {
|
||||
if (value) {
|
||||
getUser(value).then(res => {
|
||||
callback(res.data.data);
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 首次打开弹窗时设置默认每页5条
|
||||
if (page && (!page.pageSizes || !page.pageSizes.includes(5))) {
|
||||
page.pageSize = 5;
|
||||
page.pageSizes = [5, 10, 20];
|
||||
}
|
||||
let currentPage = page ? page.currentPage : 1;
|
||||
let pageSize = page ? page.pageSize : 5;
|
||||
let param = {};
|
||||
if (data) {
|
||||
param = data;
|
||||
}
|
||||
getUserPage(currentPage, pageSize, param.account, param.name).then(res => {
|
||||
callback({
|
||||
total: res.data.data.total,
|
||||
data: res.data.data.records,
|
||||
});
|
||||
});
|
||||
},
|
||||
span: 24,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择用户',
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '过期时间',
|
||||
prop: 'expireTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
span: 24,
|
||||
width: 180,
|
||||
slot: true,
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
slot: true,
|
||||
search: true,
|
||||
dicData: [
|
||||
{ label: '启用', value: 1 },
|
||||
{ label: '禁用', value: 0 },
|
||||
],
|
||||
value: 1,
|
||||
span: 24,
|
||||
display: false,
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
label: '访问权限',
|
||||
labelTip: '配置允许访问的 API 路径,留空则表示不限制访问路径',
|
||||
prop: 'apiPath',
|
||||
formslot: true,
|
||||
hide: true,
|
||||
span: 24,
|
||||
},
|
||||
{
|
||||
label: '扩展参数',
|
||||
labelTip: '配置扩展参数,可用于自定义 API Key 属性',
|
||||
prop: 'extParams',
|
||||
formslot: true,
|
||||
hide: true,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
},
|
||||
data: [],
|
||||
// 单个密钥日志
|
||||
logDrawerVisible: false,
|
||||
logKeyName: '',
|
||||
logApiKeyId: null,
|
||||
logForm: {},
|
||||
logQuery: {},
|
||||
logLoading: false,
|
||||
logData: [],
|
||||
logPage: { pageSize: 10, currentPage: 1, total: 0 },
|
||||
logOption: {
|
||||
height: 'auto',
|
||||
calcHeight: 70,
|
||||
tip: false,
|
||||
header: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
editBtn: false,
|
||||
addBtn: false,
|
||||
delBtn: false,
|
||||
menuWidth: 100,
|
||||
dialogType: 'drawer',
|
||||
dialogWidth: 800,
|
||||
column: [
|
||||
{ label: '服务ID', prop: 'serviceId', width: 120 },
|
||||
{ label: '服务host', prop: 'serverHost', hide: true },
|
||||
{ label: '服务IP', prop: 'serverIp', hide: true, width: 160 },
|
||||
{ label: '软件环境', prop: 'env', hide: true, width: 85 },
|
||||
{ label: '请求接口', prop: 'requestUri', search: true, slot: true },
|
||||
{ label: '请求方法', prop: 'method', slot: true, width: 85 },
|
||||
{ label: '调用IP', prop: 'remoteIp', search: true, width: 140 },
|
||||
{ label: '耗时(ms)', prop: 'time', width: 85 },
|
||||
{ label: '日志时间', prop: 'createTime', span: 24, width: 180 },
|
||||
{ label: '用户代理', prop: 'userAgent', span: 24, hide: true },
|
||||
{ label: '请求数据', prop: 'params', type: 'textarea', span: 24, minRows: 2, hide: true },
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['userInfo']),
|
||||
ids() {
|
||||
let ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
curlCommand() {
|
||||
if (!this.createdApiKey) return '';
|
||||
return `curl -X GET 'https://api.bladex.cn/blade-system/user/info' \\
|
||||
-H 'Blade-Auth: ${this.createdApiKey}' \\
|
||||
-H 'Blade-Requested-With: BladeHttpRequest'`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
copyAndCloseDialog() {
|
||||
navigator.clipboard
|
||||
.writeText(this.createdApiKey)
|
||||
.then(() => {
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: 'API Key 已复制到剪贴板',
|
||||
});
|
||||
this.apiKeyDialogVisible = false;
|
||||
this.createdApiKey = '';
|
||||
this.createdExpireTime = '';
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message({
|
||||
type: 'error',
|
||||
message: '复制失败,请手动复制',
|
||||
});
|
||||
});
|
||||
},
|
||||
copyCurlCommand() {
|
||||
navigator.clipboard
|
||||
.writeText(this.curlCommand)
|
||||
.then(() => {
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: 'cURL 命令已复制到剪贴板',
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message({
|
||||
type: 'error',
|
||||
message: '复制失败,请手动复制',
|
||||
});
|
||||
});
|
||||
},
|
||||
showApiKeyDialog(apiKey, expireTime) {
|
||||
this.createdApiKey = apiKey;
|
||||
this.createdExpireTime = expireTime || '';
|
||||
this.apiKeyDialogVisible = true;
|
||||
},
|
||||
addApiPath() {
|
||||
this.apiPathList.push('');
|
||||
},
|
||||
removeApiPath(index) {
|
||||
this.apiPathList.splice(index, 1);
|
||||
},
|
||||
parseApiPath(apiPath) {
|
||||
if (!apiPath) return [];
|
||||
return apiPath
|
||||
.split(',')
|
||||
.map(path => path.trim())
|
||||
.filter(path => path.length > 0);
|
||||
},
|
||||
buildApiPath() {
|
||||
const paths = this.apiPathList.filter(path => path && path.trim());
|
||||
return paths.length > 0 ? paths.join(',') : '';
|
||||
},
|
||||
addExtParam() {
|
||||
this.extParamsList.push({ key: '', value: '' });
|
||||
},
|
||||
removeExtParam(index) {
|
||||
this.extParamsList.splice(index, 1);
|
||||
},
|
||||
parseExtParams(extParams) {
|
||||
if (!extParams) return [];
|
||||
try {
|
||||
const obj = typeof extParams === 'string' ? JSON.parse(extParams) : extParams;
|
||||
return Object.entries(obj).map(([key, value]) => ({ key, value: String(value) }));
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
buildExtParams() {
|
||||
const result = {};
|
||||
this.extParamsList.forEach(item => {
|
||||
if (item.key && item.key.trim()) {
|
||||
result[item.key.trim()] = item.value;
|
||||
}
|
||||
});
|
||||
return Object.keys(result).length > 0 ? JSON.stringify(result) : '';
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
const data = {
|
||||
...row,
|
||||
apiPath: this.buildApiPath(),
|
||||
extParams: this.buildExtParams(),
|
||||
};
|
||||
add(data).then(
|
||||
res => {
|
||||
this.onLoad(this.page);
|
||||
done();
|
||||
// 显示 API Key 弹框
|
||||
this.showApiKeyDialog(res.data.data, row.expireTime);
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdate(row, index, done, loading) {
|
||||
const data = {
|
||||
...row,
|
||||
apiPath: this.buildApiPath(),
|
||||
extParams: this.buildExtParams(),
|
||||
};
|
||||
update(data).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
return remove(row.id);
|
||||
})
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
});
|
||||
},
|
||||
handleEnable(row) {
|
||||
this.$confirm('确定启用该 API Key?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
return enable(row.id);
|
||||
})
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '启用成功!',
|
||||
});
|
||||
});
|
||||
},
|
||||
handleDisable(row) {
|
||||
this.$confirm('确定禁用该 API Key?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
return disable(row.id);
|
||||
})
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '禁用成功!',
|
||||
});
|
||||
});
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
return remove(this.ids);
|
||||
})
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
this.$refs.crud.toggleSelection();
|
||||
});
|
||||
},
|
||||
beforeOpen(done, type) {
|
||||
if (['add'].includes(type)) {
|
||||
this.apiPathList = [];
|
||||
this.extParamsList = [];
|
||||
}
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getDetail(this.form.id).then(res => {
|
||||
this.form = res.data.data;
|
||||
this.apiPathList = this.parseApiPath(this.form.apiPath);
|
||||
this.extParamsList = this.parseExtParams(this.form.extParams);
|
||||
});
|
||||
}
|
||||
done();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, Object.assign(params, this.query)).then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.loading = false;
|
||||
this.selectionClear();
|
||||
});
|
||||
},
|
||||
// ==================== 单个密钥日志 ====================
|
||||
handleRowLog(row) {
|
||||
this.logKeyName = row.name || 'API Key';
|
||||
this.logApiKeyId = row.id;
|
||||
this.logDrawerVisible = true;
|
||||
this.logPage.currentPage = 1;
|
||||
this.logQuery = {};
|
||||
this.onLoadLog(this.logPage);
|
||||
},
|
||||
beforeOpenLog(done, type) {
|
||||
if (['view'].includes(type)) {
|
||||
getLogDetail(this.logForm.id).then(res => {
|
||||
this.logForm = res.data.data;
|
||||
});
|
||||
}
|
||||
done();
|
||||
},
|
||||
logSearchChange(params, done) {
|
||||
this.logQuery = params;
|
||||
this.logPage.currentPage = 1;
|
||||
this.onLoadLog(this.logPage, params);
|
||||
done();
|
||||
},
|
||||
logSearchReset() {
|
||||
this.logQuery = {};
|
||||
this.onLoadLog(this.logPage);
|
||||
},
|
||||
logCurrentChange(currentPage) {
|
||||
this.logPage.currentPage = currentPage;
|
||||
},
|
||||
logSizeChange(pageSize) {
|
||||
this.logPage.pageSize = pageSize;
|
||||
},
|
||||
logRefreshChange() {
|
||||
this.onLoadLog(this.logPage, this.logQuery);
|
||||
},
|
||||
onLoadLog(page, params = {}) {
|
||||
this.logLoading = true;
|
||||
const query = Object.assign({ apiKeyId: this.logApiKeyId }, params, this.logQuery);
|
||||
getLogList(page.currentPage, page.pageSize, query).then(res => {
|
||||
const data = res.data.data;
|
||||
this.logPage.total = data.total;
|
||||
this.logData = data.records;
|
||||
this.logLoading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
733
src/views/authority/apiscope.vue
Normal file
733
src/views/authority/apiscope.vue
Normal file
@@ -0,0 +1,733 @@
|
||||
<template>
|
||||
<basic-container>
|
||||
<!-- ========== 列表模式 ========== -->
|
||||
<avue-crud
|
||||
v-if="viewMode === 'list'"
|
||||
ref="crudList"
|
||||
v-model="formList"
|
||||
:option="optionList"
|
||||
:table-loading="listLoading"
|
||||
:data="dataList"
|
||||
:page="pageList"
|
||||
:permission="permissionListScope"
|
||||
:before-open="beforeOpenList"
|
||||
@row-del="rowDelList"
|
||||
@row-update="rowUpdateList"
|
||||
@row-save="rowSaveList"
|
||||
@search-change="searchChangeList"
|
||||
@search-reset="searchResetList"
|
||||
@selection-change="selectionChangeList"
|
||||
@current-change="currentChangeList"
|
||||
@size-change="sizeChangeList"
|
||||
@refresh-change="refreshChangeList"
|
||||
@on-load="onLoadList"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button type="danger" icon="el-icon-delete" plain @click="handleDeleteList"
|
||||
>删 除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #menu-right>
|
||||
<el-button :type="viewMode === 'list' ? 'primary' : ''" @click="viewMode = 'list'"
|
||||
>列表模式
|
||||
</el-button>
|
||||
<el-button :type="viewMode === 'tree' ? 'primary' : ''" @click="viewMode = 'tree'"
|
||||
>树形模式
|
||||
</el-button>
|
||||
</template>
|
||||
<template #scopeType="{ row }">
|
||||
<el-tag>{{ row.scopeTypeName }}</el-tag>
|
||||
</template>
|
||||
</avue-crud>
|
||||
|
||||
<!-- ========== 树形模式 ========== -->
|
||||
<avue-crud
|
||||
v-if="viewMode === 'tree'"
|
||||
ref="crudTree"
|
||||
:option="optionTree"
|
||||
:table-loading="treeLoading"
|
||||
:data="dataTree"
|
||||
@search-change="searchChangeTree"
|
||||
@search-reset="searchResetTree"
|
||||
@refresh-change="refreshChangeTree"
|
||||
@on-load="onLoadTree"
|
||||
@tree-load="treeLoad"
|
||||
>
|
||||
<template #menu-right>
|
||||
<el-button :type="viewMode === 'list' ? 'primary' : ''" @click="viewMode = 'list'"
|
||||
>列表模式
|
||||
</el-button>
|
||||
<el-button :type="viewMode === 'tree' ? 'primary' : ''" @click="viewMode = 'tree'"
|
||||
>树形模式
|
||||
</el-button>
|
||||
</template>
|
||||
<template #menu="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-setting"
|
||||
v-if="permission.api_scope_setting"
|
||||
plain
|
||||
style="border: 0; background-color: transparent !important"
|
||||
@click.stop="handleDataScope(row)"
|
||||
>权限配置
|
||||
</el-button>
|
||||
</template>
|
||||
<template #name="{ row }">
|
||||
<i :class="row.source" style="margin-right: 5px" />
|
||||
<span>{{ row.name }}</span>
|
||||
</template>
|
||||
<template #source="{ row }">
|
||||
<div style="text-align: center">
|
||||
<i :class="row.source" />
|
||||
</div>
|
||||
</template>
|
||||
</avue-crud>
|
||||
|
||||
<!-- ========== 接口权限配置抽屉(树形模式) ========== -->
|
||||
<el-drawer
|
||||
:title="`[${scopeMenuName}] 接口权限配置`"
|
||||
v-model="drawerVisible"
|
||||
:direction="direction"
|
||||
append-to-body
|
||||
:before-close="handleDrawerClose"
|
||||
size="1200px"
|
||||
>
|
||||
<basic-container>
|
||||
<avue-crud
|
||||
:option="optionScope"
|
||||
:data="dataScope"
|
||||
:page="pageScope"
|
||||
v-model="formScope"
|
||||
:table-loading="scopeLoading"
|
||||
ref="crudScope"
|
||||
@row-del="rowDelScope"
|
||||
@row-update="rowUpdateScope"
|
||||
@row-save="rowSaveScope"
|
||||
:before-open="beforeOpenScope"
|
||||
@search-change="searchChangeScope"
|
||||
@search-reset="searchResetScope"
|
||||
@selection-change="selectionChangeScope"
|
||||
@current-change="currentChangeScope"
|
||||
@size-change="sizeChangeScope"
|
||||
@on-load="onLoadScope"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button type="danger" icon="el-icon-delete" plain @click="handleDeleteScope"
|
||||
>删 除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #scopeType="{ row }">
|
||||
<el-tag>{{ row.scopeTypeName }}</el-tag>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</basic-container>
|
||||
</el-drawer>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getLazyMenuList } from '@/api/system/menu';
|
||||
import {
|
||||
addApiScope,
|
||||
removeApiScope,
|
||||
updateApiScope,
|
||||
getListApiScope,
|
||||
getMenuApiScope,
|
||||
} from '@/api/system/scope';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
viewMode: 'list',
|
||||
// ========== 列表模式 ==========
|
||||
formList: {},
|
||||
queryList: {},
|
||||
selectionListData: [],
|
||||
listLoading: true,
|
||||
dataList: [],
|
||||
pageList: {
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
optionList: {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
selection: true,
|
||||
labelWidth: 120,
|
||||
searchLabelWidth: 120,
|
||||
menuWidth: 250,
|
||||
dialogWidth: 900,
|
||||
dialogClickModal: false,
|
||||
column: [
|
||||
{
|
||||
label: '所属菜单',
|
||||
labelTip: '仅用于权限规则的分组归类,不参与实际鉴权与数据过滤逻辑',
|
||||
prop: 'menuId',
|
||||
type: 'tree',
|
||||
dicUrl: '/blade-system/menu/tree',
|
||||
props: {
|
||||
label: 'title',
|
||||
},
|
||||
search: true,
|
||||
span: 24,
|
||||
width: 200,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择所属菜单',
|
||||
trigger: 'click',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '权限名称',
|
||||
prop: 'scopeName',
|
||||
search: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入权限名称',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '权限编号',
|
||||
prop: 'resourceCode',
|
||||
search: true,
|
||||
width: 180,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入权限编号',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '权限路径',
|
||||
prop: 'scopePath',
|
||||
width: 180,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入权限路径',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '接口类型',
|
||||
type: 'select',
|
||||
dicUrl: '/blade-system/dict/dictionary?code=api_scope_type',
|
||||
props: {
|
||||
label: 'dictValue',
|
||||
value: 'dictKey',
|
||||
},
|
||||
dataType: 'number',
|
||||
slot: true,
|
||||
width: 100,
|
||||
prop: 'scopeType',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择接口类型',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
span: 24,
|
||||
hide: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
// ========== 树形模式 ==========
|
||||
dataTree: [],
|
||||
treeLoading: true,
|
||||
parentId: 0,
|
||||
queryTree: {},
|
||||
optionTree: {
|
||||
lazy: true,
|
||||
tip: false,
|
||||
simplePage: true,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
tree: true,
|
||||
border: true,
|
||||
index: true,
|
||||
selection: true,
|
||||
viewBtn: false,
|
||||
editBtn: false,
|
||||
addBtn: false,
|
||||
delBtn: false,
|
||||
menuWidth: 200,
|
||||
dialogClickModal: false,
|
||||
column: [
|
||||
{
|
||||
label: '菜单名称',
|
||||
prop: 'name',
|
||||
slot: true,
|
||||
width: 300,
|
||||
search: true,
|
||||
},
|
||||
{
|
||||
label: '菜单编号',
|
||||
prop: 'code',
|
||||
search: true,
|
||||
},
|
||||
{
|
||||
label: '路由地址',
|
||||
prop: 'path',
|
||||
},
|
||||
{
|
||||
label: '菜单图标',
|
||||
prop: 'source',
|
||||
slot: true,
|
||||
width: 85,
|
||||
},
|
||||
{
|
||||
label: '菜单别名',
|
||||
prop: 'alias',
|
||||
},
|
||||
{
|
||||
label: '菜单排序',
|
||||
prop: 'sort',
|
||||
width: 85,
|
||||
},
|
||||
],
|
||||
},
|
||||
// ========== 接口权限抽屉(树形模式) ==========
|
||||
drawerVisible: false,
|
||||
direction: 'rtl',
|
||||
scopeMenuId: 0,
|
||||
scopeMenuName: '菜单',
|
||||
scopeLoading: false,
|
||||
formScope: {},
|
||||
selectionListScope: [],
|
||||
pageScope: {
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
optionScope: {
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
selection: true,
|
||||
labelWidth: 120,
|
||||
menuWidth: 250,
|
||||
dialogWidth: 900,
|
||||
dialogClickModal: false,
|
||||
column: [
|
||||
{
|
||||
label: '所属菜单',
|
||||
labelTip: '仅用于权限规则的分组归类,不参与实际鉴权与数据过滤逻辑',
|
||||
prop: 'menuId',
|
||||
type: 'tree',
|
||||
dicUrl: '/blade-system/menu/tree',
|
||||
props: {
|
||||
label: 'title',
|
||||
},
|
||||
span: 24,
|
||||
disabled: true,
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择所属菜单',
|
||||
trigger: 'click',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '权限名称',
|
||||
prop: 'scopeName',
|
||||
search: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入权限名称',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '权限编号',
|
||||
prop: 'resourceCode',
|
||||
search: true,
|
||||
width: 180,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入权限编号',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '权限路径',
|
||||
prop: 'scopePath',
|
||||
width: 180,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入权限路径',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '接口类型',
|
||||
type: 'select',
|
||||
dicUrl: '/blade-system/dict/dictionary?code=api_scope_type',
|
||||
props: {
|
||||
label: 'dictValue',
|
||||
value: 'dictKey',
|
||||
},
|
||||
dataType: 'number',
|
||||
slot: true,
|
||||
width: 100,
|
||||
prop: 'scopeType',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择接口类型',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
span: 24,
|
||||
hide: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
dataScope: [],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
viewMode() {
|
||||
this.drawerVisible = false;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission']),
|
||||
permissionListScope() {
|
||||
return {
|
||||
addBtn: this.validData(this.permission.api_scope_setting, false),
|
||||
viewBtn: true,
|
||||
delBtn: this.validData(this.permission.api_scope_setting, false),
|
||||
editBtn: this.validData(this.permission.api_scope_setting, false),
|
||||
};
|
||||
},
|
||||
listIds() {
|
||||
let ids = [];
|
||||
this.selectionListData.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
scopeIds() {
|
||||
let ids = [];
|
||||
this.selectionListScope.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// ==================== 列表模式 ====================
|
||||
rowSaveList(row, done, loading) {
|
||||
addApiScope(row).then(
|
||||
() => {
|
||||
this.onLoadList(this.pageList);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdateList(row, index, done, loading) {
|
||||
updateApiScope(row).then(
|
||||
() => {
|
||||
this.onLoadList(this.pageList);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowDelList(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
return removeApiScope(row.id);
|
||||
})
|
||||
.then(() => {
|
||||
this.onLoadList(this.pageList);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
});
|
||||
},
|
||||
handleDeleteList() {
|
||||
if (this.selectionListData.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
return removeApiScope(this.listIds);
|
||||
})
|
||||
.then(() => {
|
||||
this.onLoadList(this.pageList);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
this.$refs.crudList.toggleSelection();
|
||||
});
|
||||
},
|
||||
beforeOpenList(done, type) {
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getMenuApiScope(this.formList.id).then(res => {
|
||||
this.formList = res.data.data;
|
||||
});
|
||||
}
|
||||
done();
|
||||
},
|
||||
searchResetList() {
|
||||
this.queryList = {};
|
||||
this.pageList.currentPage = 1;
|
||||
this.onLoadList(this.pageList);
|
||||
},
|
||||
searchChangeList(params, done) {
|
||||
this.queryList = params;
|
||||
this.pageList.currentPage = 1;
|
||||
this.onLoadList(this.pageList, params);
|
||||
done();
|
||||
},
|
||||
selectionChangeList(list) {
|
||||
this.selectionListData = list;
|
||||
},
|
||||
currentChangeList(currentPage) {
|
||||
this.pageList.currentPage = currentPage;
|
||||
},
|
||||
sizeChangeList(pageSize) {
|
||||
this.pageList.pageSize = pageSize;
|
||||
},
|
||||
refreshChangeList() {
|
||||
this.onLoadList(this.pageList, this.queryList);
|
||||
},
|
||||
onLoadList(page, params = {}) {
|
||||
this.listLoading = true;
|
||||
getListApiScope(page.currentPage, page.pageSize, Object.assign(params, this.queryList)).then(
|
||||
res => {
|
||||
const data = res.data.data;
|
||||
this.pageList.total = data.total;
|
||||
this.dataList = data.records;
|
||||
this.selectionListData = [];
|
||||
this.listLoading = false;
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
// ==================== 树形模式 ====================
|
||||
onLoadTree(page, params = {}) {
|
||||
this.treeLoading = true;
|
||||
getLazyMenuList(this.parentId, Object.assign(params, this.queryTree)).then(res => {
|
||||
this.dataTree = res.data.data;
|
||||
this.treeLoading = false;
|
||||
});
|
||||
},
|
||||
treeLoad(tree, treeNode, resolve) {
|
||||
const parentId = tree.id;
|
||||
getLazyMenuList(parentId).then(res => {
|
||||
resolve(res.data.data);
|
||||
});
|
||||
},
|
||||
searchResetTree() {
|
||||
this.queryTree = {};
|
||||
this.parentId = 0;
|
||||
this.onLoadTree();
|
||||
},
|
||||
searchChangeTree(params, done) {
|
||||
this.queryTree = params;
|
||||
this.parentId = '';
|
||||
this.onLoadTree({}, params);
|
||||
done();
|
||||
},
|
||||
refreshChangeTree() {
|
||||
this.onLoadTree({}, this.queryTree);
|
||||
},
|
||||
handleDataScope(row) {
|
||||
this.drawerVisible = true;
|
||||
this.scopeMenuId = row.id;
|
||||
this.scopeMenuName = row.name;
|
||||
this.onLoadScope(this.pageScope);
|
||||
},
|
||||
handleDrawerClose(hide) {
|
||||
hide();
|
||||
},
|
||||
|
||||
// ==================== 抽屉内权限 CRUD ====================
|
||||
rowSaveScope(row, done, loading) {
|
||||
row = {
|
||||
...row,
|
||||
menuId: this.scopeMenuId,
|
||||
};
|
||||
addApiScope(row).then(
|
||||
() => {
|
||||
this.onLoadScope(this.pageScope);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdateScope(row, index, done, loading) {
|
||||
row = {
|
||||
...row,
|
||||
menuId: this.scopeMenuId,
|
||||
};
|
||||
updateApiScope(row).then(
|
||||
() => {
|
||||
this.onLoadScope(this.pageScope);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowDelScope(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
return removeApiScope(row.id);
|
||||
})
|
||||
.then(() => {
|
||||
this.onLoadScope(this.pageScope);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
});
|
||||
},
|
||||
handleDeleteScope() {
|
||||
if (this.selectionListScope.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
return removeApiScope(this.scopeIds);
|
||||
})
|
||||
.then(() => {
|
||||
this.onLoadScope(this.pageScope);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
this.$refs.crudScope.toggleSelection();
|
||||
});
|
||||
},
|
||||
beforeOpenScope(done, type) {
|
||||
if (['add'].includes(type)) {
|
||||
this.formScope.menuId = this.scopeMenuId;
|
||||
}
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getMenuApiScope(this.formScope.id).then(res => {
|
||||
this.formScope = res.data.data;
|
||||
});
|
||||
}
|
||||
done();
|
||||
},
|
||||
searchResetScope() {
|
||||
this.onLoadScope(this.pageScope);
|
||||
},
|
||||
searchChangeScope(params, done) {
|
||||
this.onLoadScope(this.pageScope, params);
|
||||
done();
|
||||
},
|
||||
selectionChangeScope(list) {
|
||||
this.selectionListScope = list;
|
||||
},
|
||||
currentChangeScope(currentPage) {
|
||||
this.pageScope.currentPage = currentPage;
|
||||
},
|
||||
sizeChangeScope(pageSize) {
|
||||
this.pageScope.pageSize = pageSize;
|
||||
},
|
||||
onLoadScope(page, params = {}) {
|
||||
this.scopeLoading = true;
|
||||
const values = {
|
||||
...params,
|
||||
menuId: this.scopeMenuId,
|
||||
};
|
||||
getListApiScope(page.currentPage, page.pageSize, values).then(res => {
|
||||
const data = res.data.data;
|
||||
this.pageScope.total = data.total;
|
||||
this.dataScope = data.records;
|
||||
this.selectionListScope = [];
|
||||
this.scopeLoading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
868
src/views/authority/datascope.vue
Normal file
868
src/views/authority/datascope.vue
Normal file
@@ -0,0 +1,868 @@
|
||||
<template>
|
||||
<basic-container>
|
||||
<!-- ========== 列表模式 ========== -->
|
||||
<avue-crud
|
||||
v-if="viewMode === 'list'"
|
||||
ref="crudList"
|
||||
v-model="formList"
|
||||
:option="optionList"
|
||||
:table-loading="listLoading"
|
||||
:data="dataList"
|
||||
:page="pageList"
|
||||
:permission="permissionListScope"
|
||||
:before-open="beforeOpenList"
|
||||
@row-del="rowDelList"
|
||||
@row-update="rowUpdateList"
|
||||
@row-save="rowSaveList"
|
||||
@search-change="searchChangeList"
|
||||
@search-reset="searchResetList"
|
||||
@selection-change="selectionChangeList"
|
||||
@current-change="currentChangeList"
|
||||
@size-change="sizeChangeList"
|
||||
@refresh-change="refreshChangeList"
|
||||
@on-load="onLoadList"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button type="danger" icon="el-icon-delete" plain @click="handleDeleteList"
|
||||
>删 除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #menu-right>
|
||||
<el-button :type="viewMode === 'list' ? 'primary' : ''" @click="viewMode = 'list'"
|
||||
>列表模式
|
||||
</el-button>
|
||||
<el-button :type="viewMode === 'tree' ? 'primary' : ''" @click="viewMode = 'tree'"
|
||||
>树形模式
|
||||
</el-button>
|
||||
</template>
|
||||
<template #scopeType="{ row }">
|
||||
<el-tag>{{ row.scopeTypeName }}</el-tag>
|
||||
</template>
|
||||
</avue-crud>
|
||||
|
||||
<!-- ========== 树形模式 ========== -->
|
||||
<avue-crud
|
||||
v-if="viewMode === 'tree'"
|
||||
ref="crudTree"
|
||||
:option="optionTree"
|
||||
:table-loading="treeLoading"
|
||||
:data="dataTree"
|
||||
@search-change="searchChangeTree"
|
||||
@search-reset="searchResetTree"
|
||||
@refresh-change="refreshChangeTree"
|
||||
@on-load="onLoadTree"
|
||||
@tree-load="treeLoad"
|
||||
>
|
||||
<template #menu-right>
|
||||
<el-button :type="viewMode === 'list' ? 'primary' : ''" @click="viewMode = 'list'"
|
||||
>列表模式
|
||||
</el-button>
|
||||
<el-button :type="viewMode === 'tree' ? 'primary' : ''" @click="viewMode = 'tree'"
|
||||
>树形模式
|
||||
</el-button>
|
||||
</template>
|
||||
<template #menu="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-setting"
|
||||
v-if="permission.data_scope_setting"
|
||||
plain
|
||||
style="border: 0; background-color: transparent !important"
|
||||
@click.stop="handleDataScope(row)"
|
||||
>权限配置
|
||||
</el-button>
|
||||
</template>
|
||||
<template #name="{ row }">
|
||||
<i :class="row.source" style="margin-right: 5px" />
|
||||
<span>{{ row.name }}</span>
|
||||
</template>
|
||||
<template #source="{ row }">
|
||||
<div style="text-align: center">
|
||||
<i :class="row.source" />
|
||||
</div>
|
||||
</template>
|
||||
</avue-crud>
|
||||
|
||||
<!-- ========== 数据权限配置抽屉(树形模式) ========== -->
|
||||
<el-drawer
|
||||
:title="`[${scopeMenuName}] 数据权限配置`"
|
||||
v-model="drawerVisible"
|
||||
:direction="direction"
|
||||
append-to-body
|
||||
:before-close="handleDrawerClose"
|
||||
size="1200px"
|
||||
>
|
||||
<basic-container>
|
||||
<avue-crud
|
||||
:option="optionScope"
|
||||
:data="dataScope"
|
||||
:page="pageScope"
|
||||
v-model="formScope"
|
||||
:table-loading="scopeLoading"
|
||||
ref="crudScope"
|
||||
@row-del="rowDelScope"
|
||||
@row-update="rowUpdateScope"
|
||||
@row-save="rowSaveScope"
|
||||
:before-open="beforeOpenScope"
|
||||
@search-change="searchChangeScope"
|
||||
@search-reset="searchResetScope"
|
||||
@selection-change="selectionChangeScope"
|
||||
@current-change="currentChangeScope"
|
||||
@size-change="sizeChangeScope"
|
||||
@on-load="onLoadScope"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button type="danger" icon="el-icon-delete" plain @click="handleDeleteScope"
|
||||
>删 除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #scopeType="{ row }">
|
||||
<el-tag>{{ row.scopeTypeName }}</el-tag>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</basic-container>
|
||||
</el-drawer>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getLazyMenuList } from '@/api/system/menu';
|
||||
import {
|
||||
addDataScope,
|
||||
removeDataScope,
|
||||
updateDataScope,
|
||||
getListDataScope,
|
||||
getMenuDataScope,
|
||||
} from '@/api/system/scope';
|
||||
import { mapGetters } from 'vuex';
|
||||
import func from '@/utils/func';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
viewMode: 'list',
|
||||
// ========== 列表模式 ==========
|
||||
formList: {},
|
||||
queryList: {},
|
||||
selectionListData: [],
|
||||
listLoading: true,
|
||||
dataList: [],
|
||||
pageList: {
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
optionList: {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
selection: true,
|
||||
labelWidth: 120,
|
||||
searchLabelWidth: 120,
|
||||
menuWidth: 250,
|
||||
dialogWidth: 900,
|
||||
dialogClickModal: false,
|
||||
column: [
|
||||
{
|
||||
label: '所属菜单',
|
||||
labelTip: '仅用于权限规则的分组归类,不参与实际鉴权与数据过滤逻辑',
|
||||
prop: 'menuId',
|
||||
type: 'tree',
|
||||
dicUrl: '/blade-system/menu/tree',
|
||||
props: {
|
||||
label: 'title',
|
||||
},
|
||||
search: true,
|
||||
span: 24,
|
||||
width: 200,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择所属菜单',
|
||||
trigger: 'click',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '权限名称',
|
||||
prop: 'scopeName',
|
||||
search: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入数据权限名称',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '权限编号',
|
||||
prop: 'resourceCode',
|
||||
search: true,
|
||||
width: 120,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入数据权限编号',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '权限字段',
|
||||
prop: 'scopeColumn',
|
||||
width: 130,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入权限字段',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '规则类型',
|
||||
type: 'select',
|
||||
dicUrl: '/blade-system/dict/dictionary?code=data_scope_type',
|
||||
props: {
|
||||
label: 'dictValue',
|
||||
value: 'dictKey',
|
||||
},
|
||||
dataType: 'number',
|
||||
slot: true,
|
||||
width: 140,
|
||||
prop: 'scopeType',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择规则类型',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '可见字段',
|
||||
prop: 'scopeField',
|
||||
span: 24,
|
||||
hide: true,
|
||||
value: '*',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入数据权限可见的字段',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '权限类名',
|
||||
prop: 'scopeClass',
|
||||
span: 24,
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入MybatisMapper对应方法的完整类名路径',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '规则值',
|
||||
prop: 'scopeValue',
|
||||
span: 24,
|
||||
minRows: 5,
|
||||
type: 'textarea',
|
||||
display: false,
|
||||
hide: true,
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
span: 24,
|
||||
hide: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
// ========== 树形模式 ==========
|
||||
dataTree: [],
|
||||
treeLoading: true,
|
||||
parentId: 0,
|
||||
queryTree: {},
|
||||
optionTree: {
|
||||
lazy: true,
|
||||
tip: false,
|
||||
simplePage: true,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
tree: true,
|
||||
border: true,
|
||||
index: true,
|
||||
selection: true,
|
||||
viewBtn: false,
|
||||
editBtn: false,
|
||||
addBtn: false,
|
||||
delBtn: false,
|
||||
menuWidth: 200,
|
||||
dialogClickModal: false,
|
||||
column: [
|
||||
{
|
||||
label: '菜单名称',
|
||||
prop: 'name',
|
||||
slot: true,
|
||||
width: 300,
|
||||
search: true,
|
||||
},
|
||||
{
|
||||
label: '菜单编号',
|
||||
prop: 'code',
|
||||
search: true,
|
||||
},
|
||||
{
|
||||
label: '路由地址',
|
||||
prop: 'path',
|
||||
},
|
||||
{
|
||||
label: '菜单图标',
|
||||
prop: 'source',
|
||||
slot: true,
|
||||
width: 85,
|
||||
},
|
||||
{
|
||||
label: '菜单别名',
|
||||
prop: 'alias',
|
||||
},
|
||||
{
|
||||
label: '菜单排序',
|
||||
prop: 'sort',
|
||||
width: 85,
|
||||
},
|
||||
],
|
||||
},
|
||||
// ========== 数据权限抽屉(树形模式) ==========
|
||||
drawerVisible: false,
|
||||
direction: 'rtl',
|
||||
scopeMenuId: 0,
|
||||
scopeMenuCode: '',
|
||||
scopeMenuName: '菜单',
|
||||
scopeLoading: false,
|
||||
watchMode: true,
|
||||
formScope: {},
|
||||
selectionListScope: [],
|
||||
pageScope: {
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
optionScope: {
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
selection: true,
|
||||
labelWidth: 120,
|
||||
menuWidth: 250,
|
||||
dialogWidth: 900,
|
||||
dialogClickModal: false,
|
||||
column: [
|
||||
{
|
||||
label: '所属菜单',
|
||||
labelTip: '仅用于权限规则的分组归类,不参与实际鉴权与数据过滤逻辑',
|
||||
prop: 'menuId',
|
||||
type: 'tree',
|
||||
dicUrl: '/blade-system/menu/tree',
|
||||
props: {
|
||||
label: 'title',
|
||||
},
|
||||
span: 24,
|
||||
disabled: true,
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择所属菜单',
|
||||
trigger: 'click',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '权限名称',
|
||||
prop: 'scopeName',
|
||||
search: true,
|
||||
value: '',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入数据权限名称',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '权限编号',
|
||||
prop: 'resourceCode',
|
||||
search: true,
|
||||
width: 100,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入数据权限编号',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '权限字段',
|
||||
prop: 'scopeColumn',
|
||||
width: 130,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入数据权限编号',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '规则类型',
|
||||
type: 'select',
|
||||
dicUrl: '/blade-system/dict/dictionary?code=data_scope_type',
|
||||
props: {
|
||||
label: 'dictValue',
|
||||
value: 'dictKey',
|
||||
},
|
||||
dataType: 'number',
|
||||
slot: true,
|
||||
width: 140,
|
||||
prop: 'scopeType',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入通知类型',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '可见字段',
|
||||
prop: 'scopeField',
|
||||
span: 24,
|
||||
hide: true,
|
||||
value: '*',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入数据权限可见的字段',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '权限类名',
|
||||
prop: 'scopeClass',
|
||||
span: 24,
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入MybatisMapper对应方法的完整类名路径',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '规则值',
|
||||
prop: 'scopeValue',
|
||||
span: 24,
|
||||
minRows: 5,
|
||||
type: 'textarea',
|
||||
display: true,
|
||||
hide: true,
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
span: 24,
|
||||
hide: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
dataScope: [],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
'formList.scopeType'() {
|
||||
this.initListScope();
|
||||
},
|
||||
'formScope.scopeType'() {
|
||||
this.initScope();
|
||||
},
|
||||
viewMode() {
|
||||
this.drawerVisible = false;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission']),
|
||||
permissionListScope() {
|
||||
return {
|
||||
addBtn: this.validData(this.permission.data_scope_setting, false),
|
||||
viewBtn: true,
|
||||
delBtn: this.validData(this.permission.data_scope_setting, false),
|
||||
editBtn: this.validData(this.permission.data_scope_setting, false),
|
||||
};
|
||||
},
|
||||
listIds() {
|
||||
let ids = [];
|
||||
this.selectionListData.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
scopeIds() {
|
||||
let ids = [];
|
||||
this.selectionListScope.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// ==================== 列表模式 ====================
|
||||
initListScope() {
|
||||
const scopeType = func.toInt(this.formList.scopeType);
|
||||
const column = this.$refs.crudList?.option?.column;
|
||||
if (column) {
|
||||
column.forEach(item => {
|
||||
if (item.prop === 'scopeValue') {
|
||||
item.display = scopeType === 5;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
rowSaveList(row, done, loading) {
|
||||
addDataScope(row).then(
|
||||
() => {
|
||||
this.onLoadList(this.pageList);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdateList(row, index, done, loading) {
|
||||
updateDataScope(row).then(
|
||||
() => {
|
||||
this.onLoadList(this.pageList);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowDelList(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
return removeDataScope(row.id);
|
||||
})
|
||||
.then(() => {
|
||||
this.onLoadList(this.pageList);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
});
|
||||
},
|
||||
handleDeleteList() {
|
||||
if (this.selectionListData.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
return removeDataScope(this.listIds);
|
||||
})
|
||||
.then(() => {
|
||||
this.onLoadList(this.pageList);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
this.$refs.crudList.toggleSelection();
|
||||
});
|
||||
},
|
||||
beforeOpenList(done, type) {
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getMenuDataScope(this.formList.id).then(res => {
|
||||
this.formList = res.data.data;
|
||||
});
|
||||
}
|
||||
done();
|
||||
},
|
||||
searchResetList() {
|
||||
this.queryList = {};
|
||||
this.pageList.currentPage = 1;
|
||||
this.onLoadList(this.pageList);
|
||||
},
|
||||
searchChangeList(params, done) {
|
||||
this.queryList = params;
|
||||
this.pageList.currentPage = 1;
|
||||
this.onLoadList(this.pageList, params);
|
||||
done();
|
||||
},
|
||||
selectionChangeList(list) {
|
||||
this.selectionListData = list;
|
||||
},
|
||||
currentChangeList(currentPage) {
|
||||
this.pageList.currentPage = currentPage;
|
||||
},
|
||||
sizeChangeList(pageSize) {
|
||||
this.pageList.pageSize = pageSize;
|
||||
},
|
||||
refreshChangeList() {
|
||||
this.onLoadList(this.pageList, this.queryList);
|
||||
},
|
||||
onLoadList(page, params = {}) {
|
||||
this.listLoading = true;
|
||||
getListDataScope(page.currentPage, page.pageSize, Object.assign(params, this.queryList)).then(
|
||||
res => {
|
||||
const data = res.data.data;
|
||||
this.pageList.total = data.total;
|
||||
this.dataList = data.records;
|
||||
this.selectionListData = [];
|
||||
this.listLoading = false;
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
// ==================== 树形模式 ====================
|
||||
onLoadTree(page, params = {}) {
|
||||
this.treeLoading = true;
|
||||
getLazyMenuList(this.parentId, Object.assign(params, this.queryTree)).then(res => {
|
||||
this.dataTree = res.data.data;
|
||||
this.treeLoading = false;
|
||||
});
|
||||
},
|
||||
treeLoad(tree, treeNode, resolve) {
|
||||
const parentId = tree.id;
|
||||
getLazyMenuList(parentId).then(res => {
|
||||
resolve(res.data.data);
|
||||
});
|
||||
},
|
||||
searchResetTree() {
|
||||
this.queryTree = {};
|
||||
this.parentId = 0;
|
||||
this.onLoadTree();
|
||||
},
|
||||
searchChangeTree(params, done) {
|
||||
this.queryTree = params;
|
||||
this.parentId = '';
|
||||
this.onLoadTree({}, params);
|
||||
done();
|
||||
},
|
||||
refreshChangeTree() {
|
||||
this.onLoadTree({}, this.queryTree);
|
||||
},
|
||||
handleDataScope(row) {
|
||||
this.drawerVisible = true;
|
||||
this.scopeMenuId = row.id;
|
||||
this.scopeMenuCode = row.code;
|
||||
this.scopeMenuName = row.name;
|
||||
this.onLoadScope(this.pageScope);
|
||||
},
|
||||
handleDrawerClose(hide) {
|
||||
hide();
|
||||
},
|
||||
|
||||
// ==================== 抽屉内权限 CRUD ====================
|
||||
initScope() {
|
||||
const scopeType = func.toInt(this.formScope.scopeType);
|
||||
const watchMode = this.watchMode;
|
||||
let column = '-',
|
||||
name = '暂无';
|
||||
if (scopeType === 1) {
|
||||
column = '-';
|
||||
name = '全部可见';
|
||||
} else if (scopeType === 2) {
|
||||
column = 'create_user';
|
||||
name = '本人可见';
|
||||
} else if (scopeType === 3) {
|
||||
column = 'create_dept';
|
||||
name = '所在机构可见';
|
||||
} else if (scopeType === 4) {
|
||||
column = 'create_dept';
|
||||
name = '所在机构可见及子级可见';
|
||||
} else if (scopeType === 5) {
|
||||
column = '';
|
||||
name = '自定义';
|
||||
}
|
||||
this.$refs.crudScope.option.column.filter(item => {
|
||||
if (watchMode) {
|
||||
if (item.prop === 'scopeName') {
|
||||
this.formScope.scopeName = `${this.scopeMenuName} [${name}]`;
|
||||
}
|
||||
if (item.prop === 'resourceCode') {
|
||||
this.formScope.resourceCode = this.scopeMenuCode;
|
||||
}
|
||||
if (item.prop === 'scopeColumn') {
|
||||
this.formScope.scopeColumn = column;
|
||||
}
|
||||
}
|
||||
if (item.prop === 'scopeValue') {
|
||||
item.display = scopeType === 5;
|
||||
}
|
||||
});
|
||||
},
|
||||
rowSaveScope(row, done, loading) {
|
||||
row = {
|
||||
...row,
|
||||
menuId: this.scopeMenuId,
|
||||
};
|
||||
addDataScope(row).then(
|
||||
() => {
|
||||
this.onLoadScope(this.pageScope);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdateScope(row, index, done, loading) {
|
||||
row = {
|
||||
...row,
|
||||
menuId: this.scopeMenuId,
|
||||
};
|
||||
updateDataScope(row).then(
|
||||
() => {
|
||||
this.onLoadScope(this.pageScope);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowDelScope(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
return removeDataScope(row.id);
|
||||
})
|
||||
.then(() => {
|
||||
this.onLoadScope(this.pageScope);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
});
|
||||
},
|
||||
handleDeleteScope() {
|
||||
if (this.selectionListScope.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
return removeDataScope(this.scopeIds);
|
||||
})
|
||||
.then(() => {
|
||||
this.onLoadScope(this.pageScope);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
this.$refs.crudScope.toggleSelection();
|
||||
});
|
||||
},
|
||||
beforeOpenScope(done, type) {
|
||||
if (['add'].includes(type)) {
|
||||
this.formScope.menuId = this.scopeMenuId;
|
||||
this.watchMode = true;
|
||||
this.initScope();
|
||||
}
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
this.watchMode = false;
|
||||
getMenuDataScope(this.formScope.id).then(res => {
|
||||
this.formScope = res.data.data;
|
||||
});
|
||||
}
|
||||
done();
|
||||
},
|
||||
searchResetScope() {
|
||||
this.onLoadScope(this.pageScope);
|
||||
},
|
||||
searchChangeScope(params, done) {
|
||||
this.onLoadScope(this.pageScope, params);
|
||||
done();
|
||||
},
|
||||
selectionChangeScope(list) {
|
||||
this.selectionListScope = list;
|
||||
},
|
||||
currentChangeScope(currentPage) {
|
||||
this.pageScope.currentPage = currentPage;
|
||||
},
|
||||
sizeChangeScope(pageSize) {
|
||||
this.pageScope.pageSize = pageSize;
|
||||
},
|
||||
onLoadScope(page, params = {}) {
|
||||
this.scopeLoading = true;
|
||||
const values = {
|
||||
...params,
|
||||
menuId: this.scopeMenuId,
|
||||
};
|
||||
getListDataScope(page.currentPage, page.pageSize, values).then(res => {
|
||||
const data = res.data.data;
|
||||
this.pageScope.total = data.total;
|
||||
this.dataScope = data.records;
|
||||
this.selectionListScope = [];
|
||||
this.scopeLoading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
639
src/views/authority/ratelimit.vue
Normal file
639
src/views/authority/ratelimit.vue
Normal file
@@ -0,0 +1,639 @@
|
||||
<template>
|
||||
<basic-container>
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
ref="crud"
|
||||
v-model="form"
|
||||
:before-open="beforeOpen"
|
||||
@row-del="rowDel"
|
||||
@row-update="rowUpdate"
|
||||
@row-save="rowSave"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="userInfo.authority.includes('administrator')"
|
||||
@click="handleDelete"
|
||||
>删 除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #limitPattern="{ row }">
|
||||
<el-tag
|
||||
v-for="(pattern, index) in parseLimitPattern(row.limitPattern)"
|
||||
:key="index"
|
||||
style="margin: 2px 4px 2px 0"
|
||||
>{{ pattern }}</el-tag
|
||||
>
|
||||
</template>
|
||||
<template #requestMethod="{ row }">
|
||||
<el-tag>{{ requestMethodLabel(row.requestMethod) }}</el-tag>
|
||||
</template>
|
||||
<template #limitDimension="{ row }">
|
||||
<el-tag>{{ limitDimensionLabel(row.limitDimension) }}</el-tag>
|
||||
</template>
|
||||
<template #rate="{ row }">
|
||||
{{ row.limitCount }} 次 / {{ row.limitPeriod }} {{ timeUnitLabel(row.timeUnit) }}
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'success' : 'danger'">
|
||||
{{ row.status === 1 ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #menu="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-circle-check"
|
||||
v-if="row.status !== 1 && userInfo.authority.includes('administrator')"
|
||||
@click.stop="handleEnable(row)"
|
||||
>启用
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-circle-close"
|
||||
v-if="row.status === 1 && userInfo.authority.includes('administrator')"
|
||||
@click.stop="handleDisable(row)"
|
||||
>禁用
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-notebook"
|
||||
v-if="userInfo.authority.includes('administrator')"
|
||||
@click.stop="handleRowLog(row)"
|
||||
>日志
|
||||
</el-button>
|
||||
</template>
|
||||
<!-- 匹配路径动态表单 -->
|
||||
<template #limitPattern-form="{ type }">
|
||||
<el-row
|
||||
v-for="(item, index) in limitPatternList"
|
||||
:key="index"
|
||||
:gutter="8"
|
||||
style="margin-bottom: 8px"
|
||||
>
|
||||
<el-col :span="22">
|
||||
<el-input
|
||||
v-model="limitPatternList[index]"
|
||||
placeholder="请输入URL匹配路径,如 /blade-system/user/**"
|
||||
:disabled="type === 'view'"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="2" v-if="type !== 'view'">
|
||||
<el-button
|
||||
type="danger"
|
||||
text
|
||||
icon="el-icon-delete"
|
||||
@click="removeLimitPattern(index)"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-if="type !== 'view'" :gutter="8">
|
||||
<el-col :span="8">
|
||||
<el-button type="primary" text icon="el-icon-circle-plus" @click="addLimitPattern"
|
||||
>添加路径</el-button
|
||||
>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row style="margin-top: 8px">
|
||||
<el-text type="info" size="small">
|
||||
<el-icon><WarningFilled /></el-icon>
|
||||
提示: 支持 Ant 风格路径匹配;多路径命中任一即生效,共享同一配额。
|
||||
</el-text>
|
||||
</el-row>
|
||||
</template>
|
||||
</avue-crud>
|
||||
|
||||
<!-- 单条规则触发日志 Drawer -->
|
||||
<el-drawer :title="`[${logLimitName}] 触发日志`" v-model="logDrawerVisible" append-to-body size="1200px">
|
||||
<basic-container>
|
||||
<avue-crud
|
||||
:option="logOption"
|
||||
:table-loading="logLoading"
|
||||
:data="logData"
|
||||
v-model:page="logPage"
|
||||
ref="logCrud"
|
||||
v-model="logForm"
|
||||
:before-open="beforeOpenLog"
|
||||
@search-change="logSearchChange"
|
||||
@search-reset="logSearchReset"
|
||||
@current-change="logCurrentChange"
|
||||
@size-change="logSizeChange"
|
||||
@refresh-change="logRefreshChange"
|
||||
@on-load="onLoadLog"
|
||||
>
|
||||
<template #requestUri="{ row }">
|
||||
<el-tag>{{ row.requestUri }}</el-tag>
|
||||
</template>
|
||||
<template #method="{ row }">
|
||||
<el-tag>{{ row.method }}</el-tag>
|
||||
</template>
|
||||
<template #limitDimension="{ row }">
|
||||
<el-tag>{{ limitDimensionLabel(row.limitDimension) }}</el-tag>
|
||||
</template>
|
||||
<template #rate="{ row }">
|
||||
<span v-if="row.limitCount"
|
||||
>{{ row.limitCount }} 次 / {{ row.limitPeriod }} {{ timeUnitLabel(row.timeUnit) }}</span
|
||||
>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template #rate-form>
|
||||
<span v-if="logForm.limitCount"
|
||||
>{{ logForm.limitCount }} 次 / {{ logForm.limitPeriod }} {{ timeUnitLabel(logForm.timeUnit) }}</span
|
||||
>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</basic-container>
|
||||
</el-drawer>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getList, getDetail, remove, add, update, enable, disable } from '@/api/system/ratelimit';
|
||||
import { getList as getLogList, getDetail as getLogDetail } from '@/api/system/ratelimitlog';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { WarningFilled } from '@element-plus/icons-vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
WarningFilled,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
query: {},
|
||||
loading: true,
|
||||
limitPatternList: [],
|
||||
page: {
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
selectionList: [],
|
||||
timeUnitDic: [
|
||||
{ label: '秒', value: 'SECONDS' },
|
||||
{ label: '分钟', value: 'MINUTES' },
|
||||
{ label: '小时', value: 'HOURS' },
|
||||
],
|
||||
requestMethodDic: [
|
||||
{ label: '全部', value: 'ALL' },
|
||||
{ label: 'GET', value: 'GET' },
|
||||
{ label: 'POST', value: 'POST' },
|
||||
{ label: 'PUT', value: 'PUT' },
|
||||
{ label: 'DELETE', value: 'DELETE' },
|
||||
],
|
||||
limitDimensionDic: [
|
||||
{ label: '全局', value: 'GLOBAL' },
|
||||
{ label: '租户', value: 'TENANT' },
|
||||
{ label: 'IP', value: 'IP' },
|
||||
{ label: '用户', value: 'USER' },
|
||||
],
|
||||
option: {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
dialogWidth: 700,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
labelWidth: 110,
|
||||
searchLabelWidth: 90,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
selection: true,
|
||||
menuWidth: 350,
|
||||
dialogClickModal: false,
|
||||
column: [
|
||||
{
|
||||
label: '规则名称',
|
||||
prop: 'limitName',
|
||||
search: true,
|
||||
span: 24,
|
||||
rules: [{ required: true, message: '请输入规则名称', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '匹配路径',
|
||||
prop: 'limitPattern',
|
||||
slot: true,
|
||||
formslot: true,
|
||||
span: 24,
|
||||
labelTip:
|
||||
'支持 Ant 风格路径匹配,可配置多个,如 /blade-system/user/** 匹配多层、/api/* 匹配单层;多个路径共享该规则的同一份配额',
|
||||
rules: [{ required: true, message: '请输入匹配路径', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: '请求方法',
|
||||
prop: 'requestMethod',
|
||||
type: 'select',
|
||||
slot: true,
|
||||
search: true,
|
||||
value: 'ALL',
|
||||
width: 100,
|
||||
labelTip: '指定规则生效的 HTTP 请求方法,选择「全部」时对所有方法生效,否则仅对所选方法限流',
|
||||
dicData: [
|
||||
{ label: '全部', value: 'ALL' },
|
||||
{ label: 'GET', value: 'GET' },
|
||||
{ label: 'POST', value: 'POST' },
|
||||
{ label: 'PUT', value: 'PUT' },
|
||||
{ label: 'DELETE', value: 'DELETE' },
|
||||
],
|
||||
rules: [{ required: true, message: '请选择请求方法', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: '限流维度',
|
||||
prop: 'limitDimension',
|
||||
type: 'select',
|
||||
slot: true,
|
||||
search: true,
|
||||
value: 'IP',
|
||||
width: 100,
|
||||
labelTip: '全局:所有请求共享配额;租户:按租户隔离;IP:按来源IP隔离;用户:按登录用户隔离',
|
||||
dicData: [
|
||||
{ label: '全局', value: 'GLOBAL' },
|
||||
{ label: '租户', value: 'TENANT' },
|
||||
{ label: 'IP', value: 'IP' },
|
||||
{ label: '用户', value: 'USER' },
|
||||
],
|
||||
rules: [{ required: true, message: '请选择限流维度', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: '请求阈值',
|
||||
prop: 'limitCount',
|
||||
type: 'number',
|
||||
minRows: 1,
|
||||
value: 100,
|
||||
hide: true,
|
||||
labelTip: '单个时间窗口内允许通过的最大请求数,超出后触发限流并返回 429',
|
||||
rules: [{ required: true, message: '请输入请求阈值', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '时间窗口',
|
||||
prop: 'limitPeriod',
|
||||
type: 'number',
|
||||
minRows: 1,
|
||||
value: 1,
|
||||
hide: true,
|
||||
labelTip: '限流统计周期的数值,与时间单位组合使用,如时间窗口 1 + 时间单位分钟表示每分钟一个周期',
|
||||
rules: [{ required: true, message: '请输入时间窗口', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '时间单位',
|
||||
prop: 'timeUnit',
|
||||
type: 'select',
|
||||
value: 'MINUTES',
|
||||
hide: true,
|
||||
labelTip: '限流统计周期的计量单位,与请求阈值、时间窗口共同构成限流速率,如 100 次 / 分钟',
|
||||
dicData: [
|
||||
{ label: '秒', value: 'SECONDS' },
|
||||
{ label: '分钟', value: 'MINUTES' },
|
||||
{ label: '小时', value: 'HOURS' },
|
||||
],
|
||||
rules: [{ required: true, message: '请选择时间单位', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: '限流速率',
|
||||
prop: 'rate',
|
||||
slot: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
label: '优先级',
|
||||
prop: 'priority',
|
||||
type: 'number',
|
||||
value: 1,
|
||||
hide: true,
|
||||
labelTip: '数值越小越优先匹配,同一路径命中多条规则时取优先级最高的一条',
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
slot: true,
|
||||
value: 1,
|
||||
display: false,
|
||||
width: 90,
|
||||
dicData: [
|
||||
{ label: '启用', value: 1 },
|
||||
{ label: '禁用', value: -1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
type: 'textarea',
|
||||
minRows: 2,
|
||||
span: 24,
|
||||
hide: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
data: [],
|
||||
// 单条规则触发日志
|
||||
logDrawerVisible: false,
|
||||
logLimitName: '',
|
||||
logLimitId: null,
|
||||
logForm: {},
|
||||
logQuery: {},
|
||||
logLoading: false,
|
||||
logData: [],
|
||||
logPage: { pageSize: 10, currentPage: 1, total: 0 },
|
||||
logOption: {
|
||||
height: 'auto',
|
||||
calcHeight: 70,
|
||||
tip: false,
|
||||
header: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
editBtn: false,
|
||||
addBtn: false,
|
||||
delBtn: false,
|
||||
menuWidth: 100,
|
||||
dialogType: 'drawer',
|
||||
dialogWidth: 800,
|
||||
column: [
|
||||
{ label: '请求接口', prop: 'requestUri', search: true, slot: true },
|
||||
{ label: '请求方法', prop: 'method', slot: true, width: 90 },
|
||||
{
|
||||
label: '限流维度',
|
||||
prop: 'limitDimension',
|
||||
type: 'select',
|
||||
slot: true,
|
||||
width: 100,
|
||||
dicData: [
|
||||
{ label: '全局', value: 'GLOBAL' },
|
||||
{ label: '租户', value: 'TENANT' },
|
||||
{ label: 'IP', value: 'IP' },
|
||||
{ label: '用户', value: 'USER' },
|
||||
],
|
||||
},
|
||||
{ label: '限流速率', prop: 'rate', slot: true, formslot: true, width: 150 },
|
||||
{ label: '租户ID', prop: 'tenantId', hide: true },
|
||||
{ label: '账号', prop: 'createBy', hide: true },
|
||||
{ label: '触发IP', prop: 'remoteIp', search: true, width: 140 },
|
||||
{ label: '服务ID', prop: 'serviceId', hide: true },
|
||||
{ label: '服务IP', prop: 'serverIp', hide: true, width: 160 },
|
||||
{ label: '环境', prop: 'env', hide: true, width: 85 },
|
||||
{ label: '用户代理', prop: 'userAgent', span: 24, hide: true },
|
||||
{ label: '触发时间', prop: 'createTime', span: 24, width: 170 },
|
||||
{ label: '请求数据', prop: 'params', type: 'textarea', span: 24, minRows: 2, hide: true },
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['userInfo']),
|
||||
ids() {
|
||||
let ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// 同步动态多行输入到表单字段,使匹配路径的必填校验生效
|
||||
limitPatternList: {
|
||||
handler() {
|
||||
this.form.limitPattern = this.buildLimitPattern();
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
timeUnitLabel(value) {
|
||||
const item = this.timeUnitDic.find(unit => unit.value === value);
|
||||
return item ? item.label : value;
|
||||
},
|
||||
requestMethodLabel(value) {
|
||||
const item = this.requestMethodDic.find(method => method.value === value);
|
||||
return item ? item.label : value;
|
||||
},
|
||||
limitDimensionLabel(value) {
|
||||
const item = this.limitDimensionDic.find(dimension => dimension.value === value);
|
||||
return item ? item.label : value;
|
||||
},
|
||||
addLimitPattern() {
|
||||
this.limitPatternList.push('');
|
||||
},
|
||||
removeLimitPattern(index) {
|
||||
this.limitPatternList.splice(index, 1);
|
||||
},
|
||||
parseLimitPattern(limitPattern) {
|
||||
if (!limitPattern) return [];
|
||||
return limitPattern
|
||||
.split(',')
|
||||
.map(pattern => pattern.trim())
|
||||
.filter(pattern => pattern.length > 0);
|
||||
},
|
||||
buildLimitPattern() {
|
||||
const patterns = this.limitPatternList
|
||||
.map(pattern => pattern && pattern.trim())
|
||||
.filter(pattern => pattern && pattern.length > 0);
|
||||
return patterns.length > 0 ? patterns.join(',') : '';
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
add(row).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdate(row, index, done, loading) {
|
||||
update(row).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
return remove(row.id);
|
||||
})
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
handleEnable(row) {
|
||||
this.$confirm('确定启用该限流规则?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
return enable(row.id);
|
||||
})
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '启用成功!' });
|
||||
});
|
||||
},
|
||||
handleDisable(row) {
|
||||
this.$confirm('确定禁用该限流规则?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
return disable(row.id);
|
||||
})
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '禁用成功!' });
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
return remove(this.ids);
|
||||
})
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
this.$refs.crud.toggleSelection();
|
||||
});
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
beforeOpen(done, type) {
|
||||
if (['add'].includes(type)) {
|
||||
this.limitPatternList = [''];
|
||||
}
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getDetail(this.form.id).then(res => {
|
||||
this.form = res.data.data;
|
||||
const patternList = this.parseLimitPattern(this.form.limitPattern);
|
||||
this.limitPatternList = patternList.length > 0 ? patternList : [''];
|
||||
});
|
||||
}
|
||||
done();
|
||||
},
|
||||
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, Object.assign(params, this.query)).then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.loading = false;
|
||||
this.selectionClear();
|
||||
});
|
||||
},
|
||||
// ==================== 单条规则触发日志 ====================
|
||||
handleRowLog(row) {
|
||||
this.logLimitName = row.limitName || '限流规则';
|
||||
this.logLimitId = row.id;
|
||||
this.logDrawerVisible = true;
|
||||
this.logPage.currentPage = 1;
|
||||
this.logQuery = {};
|
||||
this.onLoadLog(this.logPage);
|
||||
},
|
||||
beforeOpenLog(done, type) {
|
||||
if (['view'].includes(type)) {
|
||||
getLogDetail(this.logForm.id).then(res => {
|
||||
this.logForm = res.data.data;
|
||||
});
|
||||
}
|
||||
done();
|
||||
},
|
||||
logSearchChange(params, done) {
|
||||
this.logQuery = params;
|
||||
this.logPage.currentPage = 1;
|
||||
this.onLoadLog(this.logPage, params);
|
||||
done();
|
||||
},
|
||||
logSearchReset() {
|
||||
this.logQuery = {};
|
||||
this.onLoadLog(this.logPage);
|
||||
},
|
||||
logCurrentChange(currentPage) {
|
||||
this.logPage.currentPage = currentPage;
|
||||
},
|
||||
logSizeChange(pageSize) {
|
||||
this.logPage.pageSize = pageSize;
|
||||
},
|
||||
logRefreshChange() {
|
||||
this.onLoadLog(this.logPage, this.logQuery);
|
||||
},
|
||||
onLoadLog(page, params = {}) {
|
||||
this.logLoading = true;
|
||||
const query = Object.assign({ limitId: this.logLimitId }, params, this.logQuery);
|
||||
getLogList(page.currentPage, page.pageSize, query).then(res => {
|
||||
const data = res.data.data;
|
||||
this.logPage.total = data.total;
|
||||
this.logData = data.records;
|
||||
this.logLoading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
655
src/views/authority/role.vue
Normal file
655
src/views/authority/role.vue
Normal file
@@ -0,0 +1,655 @@
|
||||
<template>
|
||||
<basic-container>
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
ref="crud"
|
||||
v-model="form"
|
||||
:permission="permissionList"
|
||||
:before-open="beforeOpen"
|
||||
@row-del="rowDel"
|
||||
@row-update="rowUpdate"
|
||||
@row-save="rowSave"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
v-if="permission.role_delete"
|
||||
plain
|
||||
@click="handleDelete"
|
||||
>删 除
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="el-icon-setting"
|
||||
@click="handleRole"
|
||||
v-if="userInfo.authority.includes('admin')"
|
||||
plain
|
||||
>权限设置
|
||||
</el-button>
|
||||
</template>
|
||||
<template #menu="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-setting"
|
||||
v-if="userInfo.authority.includes('admin')"
|
||||
plain
|
||||
style="border: 0; background-color: transparent !important"
|
||||
@click.stop="handleRowRole(row)"
|
||||
>权限设置
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-crud>
|
||||
<el-dialog
|
||||
title="角色权限配置"
|
||||
append-to-body
|
||||
v-model="box"
|
||||
width="345px"
|
||||
@close="handleDialogClose"
|
||||
>
|
||||
<el-tabs type="border-card">
|
||||
<el-tab-pane label="菜单权限">
|
||||
<el-row
|
||||
justify="space-between"
|
||||
align="middle"
|
||||
style="margin-bottom: 12px; background: #f5f7fa; padding: 6px 10px; border-radius: 4px"
|
||||
>
|
||||
<span style="display: inline-flex; align-items: center">
|
||||
<el-switch
|
||||
v-model="menuLinked"
|
||||
active-text="节点联动"
|
||||
size="small"
|
||||
@change="handleLinkedChange('treeMenu')"
|
||||
/>
|
||||
<el-tooltip content="开启后勾选父节点会自动勾选所有子节点,关闭则可独立勾选任意节点" placement="top">
|
||||
<el-icon style="margin-left: 4px; color: #909399; cursor: pointer"><el-icon-question-filled /></el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-button-group>
|
||||
<el-button size="small" plain @click="handleSelectAll('treeMenu', menuGrantList)"
|
||||
>全选</el-button
|
||||
>
|
||||
<el-button
|
||||
size="small"
|
||||
plain
|
||||
@click="handleInvertSelect('treeMenu', menuGrantList, menuLinked)"
|
||||
>反选</el-button
|
||||
>
|
||||
</el-button-group>
|
||||
</el-row>
|
||||
<el-tree
|
||||
:data="menuGrantList"
|
||||
show-checkbox
|
||||
:check-strictly="!menuLinked"
|
||||
node-key="id"
|
||||
ref="treeMenu"
|
||||
:default-checked-keys="menuTreeObj"
|
||||
:props="props"
|
||||
>
|
||||
</el-tree>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="数据权限">
|
||||
<el-row
|
||||
justify="space-between"
|
||||
align="middle"
|
||||
style="margin-bottom: 12px; background: #f5f7fa; padding: 6px 10px; border-radius: 4px"
|
||||
>
|
||||
<span style="display: inline-flex; align-items: center">
|
||||
<el-switch
|
||||
v-model="dataScopeLinked"
|
||||
active-text="节点联动"
|
||||
size="small"
|
||||
@change="handleLinkedChange('treeDataScope')"
|
||||
/>
|
||||
<el-tooltip content="开启后勾选父节点会自动勾选所有子节点,关闭则可独立勾选任意节点" placement="top">
|
||||
<el-icon style="margin-left: 4px; color: #909399; cursor: pointer"><el-icon-question-filled /></el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-button-group>
|
||||
<el-button
|
||||
size="small"
|
||||
plain
|
||||
@click="handleSelectAll('treeDataScope', dataScopeGrantList)"
|
||||
>全选</el-button
|
||||
>
|
||||
<el-button
|
||||
size="small"
|
||||
plain
|
||||
@click="handleInvertSelect('treeDataScope', dataScopeGrantList, dataScopeLinked)"
|
||||
>反选</el-button
|
||||
>
|
||||
</el-button-group>
|
||||
</el-row>
|
||||
<el-tree
|
||||
:data="dataScopeGrantList"
|
||||
show-checkbox
|
||||
:check-strictly="!dataScopeLinked"
|
||||
node-key="id"
|
||||
ref="treeDataScope"
|
||||
:default-checked-keys="dataScopeTreeObj"
|
||||
:props="props"
|
||||
>
|
||||
</el-tree>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="接口权限">
|
||||
<el-row
|
||||
justify="space-between"
|
||||
align="middle"
|
||||
style="margin-bottom: 12px; background: #f5f7fa; padding: 6px 10px; border-radius: 4px"
|
||||
>
|
||||
<span style="display: inline-flex; align-items: center">
|
||||
<el-switch
|
||||
v-model="apiScopeLinked"
|
||||
active-text="节点联动"
|
||||
size="small"
|
||||
@change="handleLinkedChange('treeApiScope')"
|
||||
/>
|
||||
<el-tooltip content="开启后勾选父节点会自动勾选所有子节点,关闭则可独立勾选任意节点" placement="top">
|
||||
<el-icon style="margin-left: 4px; color: #909399; cursor: pointer"><el-icon-question-filled /></el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-button-group>
|
||||
<el-button
|
||||
size="small"
|
||||
plain
|
||||
@click="handleSelectAll('treeApiScope', apiScopeGrantList)"
|
||||
>全选</el-button
|
||||
>
|
||||
<el-button
|
||||
size="small"
|
||||
plain
|
||||
@click="handleInvertSelect('treeApiScope', apiScopeGrantList, apiScopeLinked)"
|
||||
>反选</el-button
|
||||
>
|
||||
</el-button-group>
|
||||
</el-row>
|
||||
<el-tree
|
||||
:data="apiScopeGrantList"
|
||||
show-checkbox
|
||||
:check-strictly="!apiScopeLinked"
|
||||
node-key="id"
|
||||
ref="treeApiScope"
|
||||
:default-checked-keys="apiScopeTreeObj"
|
||||
:props="props"
|
||||
>
|
||||
</el-tree>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="box = false">取 消</el-button>
|
||||
<el-button type="primary" @click="submit">确 定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
add,
|
||||
getList,
|
||||
getRole,
|
||||
getRoleAlias,
|
||||
getRoleTreeById,
|
||||
grant,
|
||||
grantTree,
|
||||
remove,
|
||||
update,
|
||||
} from '@/api/system/role';
|
||||
import { mapGetters } from 'vuex';
|
||||
import website from '@/config/website';
|
||||
import { validatenull } from '@/utils/validate';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
box: false,
|
||||
props: {
|
||||
label: 'title',
|
||||
value: 'key',
|
||||
},
|
||||
menuGrantList: [],
|
||||
dataScopeGrantList: [],
|
||||
apiScopeGrantList: [],
|
||||
apiGrantList: [],
|
||||
menuTreeObj: [],
|
||||
dataScopeTreeObj: [],
|
||||
apiScopeTreeObj: [],
|
||||
menuLinked: false,
|
||||
dataScopeLinked: false,
|
||||
apiScopeLinked: false,
|
||||
currentRoleIds: [],
|
||||
selectionList: [],
|
||||
query: {},
|
||||
loading: true,
|
||||
page: {
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
option: {
|
||||
tip: false,
|
||||
simplePage: true,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
tree: true,
|
||||
border: true,
|
||||
index: true,
|
||||
selection: true,
|
||||
viewBtn: true,
|
||||
labelWidth: 120,
|
||||
searchLabelWidth: 120,
|
||||
menuWidth: 350,
|
||||
dialogWidth: 600,
|
||||
dialogClickModal: false,
|
||||
column: [
|
||||
{
|
||||
label: '上级角色',
|
||||
prop: 'parentId',
|
||||
dicData: [],
|
||||
type: 'tree',
|
||||
hide: true,
|
||||
span: 24,
|
||||
props: {
|
||||
label: 'title',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: false,
|
||||
message: '请选择上级角色',
|
||||
trigger: 'click',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '角色名称',
|
||||
prop: 'roleName',
|
||||
search: true,
|
||||
span: 24,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入角色名称',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '所属租户',
|
||||
prop: 'tenantId',
|
||||
type: 'tree',
|
||||
dicUrl: '/blade-system/tenant/select',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: website.tenantMode,
|
||||
span: 24,
|
||||
props: {
|
||||
label: 'tenantName',
|
||||
value: 'tenantId',
|
||||
},
|
||||
hide: !website.tenantMode,
|
||||
search: website.tenantMode,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入所属租户',
|
||||
trigger: 'click',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '角色别名',
|
||||
labelTip: '用于权限校验与标注的属性,可手动录入也可点击现有别名快速录入',
|
||||
prop: 'roleAlias',
|
||||
search: true,
|
||||
span: 24,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入角色别名',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '现有别名',
|
||||
labelTip: '当前系统已存在的角色别名,点击可快速录入',
|
||||
prop: 'currentAlias',
|
||||
type: 'radio',
|
||||
dicData: [],
|
||||
props: {
|
||||
label: 'roleName',
|
||||
value: 'roleAlias',
|
||||
},
|
||||
hide: true,
|
||||
span: 24,
|
||||
},
|
||||
{
|
||||
label: '角色排序',
|
||||
prop: 'sort',
|
||||
type: 'number',
|
||||
span: 24,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入角色排序',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
data: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['userInfo', 'permission']),
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.validData(this.permission.role_add, false),
|
||||
viewBtn: this.validData(this.permission.role_view, false),
|
||||
delBtn: this.validData(this.permission.role_delete, false),
|
||||
editBtn: this.validData(this.permission.role_edit, false),
|
||||
};
|
||||
},
|
||||
ids() {
|
||||
let ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
idsArray() {
|
||||
let ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'form.currentAlias'() {
|
||||
const alias = this.form.currentAlias;
|
||||
if (!validatenull(alias)) {
|
||||
this.form.roleAlias = alias;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
initData(roleId) {
|
||||
getRoleTreeById(roleId).then(res => {
|
||||
const column = this.findColumn(this.option.column, 'parentId');
|
||||
column.dicData = res.data.data;
|
||||
});
|
||||
},
|
||||
getAllNodeKeys(nodes) {
|
||||
let keys = [];
|
||||
nodes.forEach(node => {
|
||||
keys.push(node.id);
|
||||
if (node.children && node.children.length > 0) {
|
||||
keys = keys.concat(this.getAllNodeKeys(node.children));
|
||||
}
|
||||
});
|
||||
return keys;
|
||||
},
|
||||
getLeafKeys(nodes) {
|
||||
let keys = [];
|
||||
nodes.forEach(node => {
|
||||
if (!node.children || node.children.length === 0) {
|
||||
keys.push(node.id);
|
||||
} else {
|
||||
keys = keys.concat(this.getLeafKeys(node.children));
|
||||
}
|
||||
});
|
||||
return keys;
|
||||
},
|
||||
handleSelectAll(treeRef, dataList) {
|
||||
const tree = this.$refs[treeRef];
|
||||
if (!tree) return;
|
||||
const allKeys = this.getAllNodeKeys(dataList);
|
||||
tree.setCheckedKeys(allKeys);
|
||||
},
|
||||
handleInvertSelect(treeRef, dataList, isLinked) {
|
||||
const tree = this.$refs[treeRef];
|
||||
if (!tree) return;
|
||||
const checkedKeys = new Set(tree.getCheckedKeys());
|
||||
if (isLinked) {
|
||||
const leafKeys = this.getLeafKeys(dataList);
|
||||
const invertedKeys = leafKeys.filter(key => !checkedKeys.has(key));
|
||||
tree.setCheckedKeys(invertedKeys);
|
||||
} else {
|
||||
const allKeys = this.getAllNodeKeys(dataList);
|
||||
const invertedKeys = allKeys.filter(key => !checkedKeys.has(key));
|
||||
tree.setCheckedKeys(invertedKeys);
|
||||
}
|
||||
},
|
||||
handleLinkedChange(treeRef) {
|
||||
const tree = this.$refs[treeRef];
|
||||
if (!tree) return;
|
||||
const checkedKeys = tree.getCheckedKeys();
|
||||
const halfCheckedKeys = tree.getHalfCheckedKeys();
|
||||
this.$nextTick(() => {
|
||||
tree.setCheckedKeys([...checkedKeys, ...halfCheckedKeys]);
|
||||
});
|
||||
},
|
||||
// el-tree 会把 setCheckedKeys 的入参记录为默认勾选集并在树数据重载时回放,
|
||||
// 复用的树实例需在打开配置时整体覆盖勾选状态,避免残留上一次配置的勾选
|
||||
applyCheckedKeys(treeRef, keys) {
|
||||
const tree = this.$refs[treeRef];
|
||||
if (!tree) return;
|
||||
tree.setCheckedKeys(keys);
|
||||
},
|
||||
submit() {
|
||||
const menuList = this.$refs.treeMenu.getCheckedKeys();
|
||||
const dataScopeList = this.$refs.treeDataScope.getCheckedKeys();
|
||||
const apiScopeList = this.$refs.treeApiScope.getCheckedKeys();
|
||||
grant(this.currentRoleIds, menuList, dataScopeList, apiScopeList).then(() => {
|
||||
this.box = false;
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
this.currentRoleIds = [];
|
||||
this.selectionList = [];
|
||||
this.onLoad(this.page);
|
||||
});
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
add(row).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdate(row, index, done, loading) {
|
||||
update(row).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
return remove(row.id);
|
||||
})
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
beforeOpen(done, type) {
|
||||
if (['add', 'edit'].includes(type)) {
|
||||
this.initData(this.form.id);
|
||||
}
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
if (this.form.parentId === '0') {
|
||||
this.form.parentId = '';
|
||||
}
|
||||
}
|
||||
getRoleAlias().then(res => {
|
||||
const column = this.findColumn(this.option.column, 'currentAlias');
|
||||
column.dicData = res.data.data;
|
||||
});
|
||||
done();
|
||||
},
|
||||
handleRole() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择一条数据');
|
||||
return;
|
||||
}
|
||||
if (this.selectionList.length > 1) {
|
||||
this.$message.warning('只能选择一条数据');
|
||||
return;
|
||||
}
|
||||
this.menuTreeObj = [];
|
||||
this.dataScopeTreeObj = [];
|
||||
this.apiScopeTreeObj = [];
|
||||
// 保存当前要配置权限的角色ID
|
||||
this.currentRoleIds = this.idsArray;
|
||||
grantTree().then(res => {
|
||||
this.menuGrantList = res.data.data.menu;
|
||||
this.dataScopeGrantList = res.data.data.dataScope;
|
||||
this.apiScopeGrantList = res.data.data.apiScope;
|
||||
getRole(this.ids).then(res => {
|
||||
this.menuTreeObj = res.data.data.menu;
|
||||
this.dataScopeTreeObj = res.data.data.dataScope;
|
||||
this.apiScopeTreeObj = res.data.data.apiScope;
|
||||
this.box = true;
|
||||
this.$nextTick(() => {
|
||||
this.applyCheckedKeys('treeMenu', this.menuTreeObj);
|
||||
this.applyCheckedKeys('treeDataScope', this.dataScopeTreeObj);
|
||||
this.applyCheckedKeys('treeApiScope', this.apiScopeTreeObj);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
handleRowRole(row) {
|
||||
this.menuTreeObj = [];
|
||||
this.dataScopeTreeObj = [];
|
||||
this.apiScopeTreeObj = [];
|
||||
// 保存当前要配置权限的角色ID(单个行操作)
|
||||
this.currentRoleIds = [row.id];
|
||||
grantTree().then(res => {
|
||||
this.menuGrantList = res.data.data.menu;
|
||||
this.dataScopeGrantList = res.data.data.dataScope;
|
||||
this.apiScopeGrantList = res.data.data.apiScope;
|
||||
getRole(row.id).then(res => {
|
||||
this.menuTreeObj = res.data.data.menu;
|
||||
this.dataScopeTreeObj = res.data.data.dataScope;
|
||||
this.apiScopeTreeObj = res.data.data.apiScope;
|
||||
this.box = true;
|
||||
this.$nextTick(() => {
|
||||
this.applyCheckedKeys('treeMenu', this.menuTreeObj);
|
||||
this.applyCheckedKeys('treeDataScope', this.dataScopeTreeObj);
|
||||
this.applyCheckedKeys('treeApiScope', this.apiScopeTreeObj);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
handleDialogClose() {
|
||||
// 对话框关闭时清空选择状态和权限树数据
|
||||
this.menuTreeObj = [];
|
||||
this.dataScopeTreeObj = [];
|
||||
this.apiScopeTreeObj = [];
|
||||
this.menuLinked = false;
|
||||
this.dataScopeLinked = false;
|
||||
this.apiScopeLinked = false;
|
||||
this.selectionClear();
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
return remove(this.ids);
|
||||
})
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
this.$refs.crud.toggleSelection();
|
||||
});
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, Object.assign(params, this.query)).then(res => {
|
||||
this.data = res.data.data;
|
||||
this.loading = false;
|
||||
this.selectionClear();
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user