feat: 修改主机额外配置.

This commit is contained in:
lijiahang
2023-12-25 19:03:24 +08:00
parent b2a697f6ba
commit f7cbd7b507
34 changed files with 321 additions and 59 deletions

View File

@@ -8,7 +8,7 @@
:cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk"
@cancel="handleClose">
<a-spin :loading="loading">
<a-spin class="full" :loading="loading">
<a-form :model="formModel"
ref="formRef"
label-align="right"

View File

@@ -12,7 +12,7 @@
:cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk"
@close="handleClose">
<a-spin :loading="loading">
<a-spin class="full" :loading="loading">
<a-form :model="formModel"
ref="formRef"
label-align="right"

View File

@@ -58,7 +58,7 @@ public class HostExtraController {
return hostExtraService.getHostExtraList(request);
}
@GetMapping("/update")
@PutMapping("/update")
@Operation(summary = "修改主机拓展信息")
public Integer updateHostExtra(@Validated @RequestBody HostExtraUpdateRequest request) {
return hostExtraService.updateHostExtra(request);

View File

@@ -15,14 +15,14 @@ public enum HostExtraSshAuthTypeEnum {
DEFAULT,
/**
* 秘钥验证
* 自定义秘钥验证
*/
KEY,
CUSTOM_KEY,
/**
* 身份验证
* 自定义身份验证
*/
IDENTITY,
CUSTOM_IDENTITY,
;

View File

@@ -55,9 +55,9 @@ public class HostSshExtraStrategy implements MapDataStrategy<HostSshExtraModel>
Long keyId = model.getKeyId();
Long identityId = model.getIdentityId();
// 必填验证
if (HostExtraSshAuthTypeEnum.KEY.equals(authType)) {
if (HostExtraSshAuthTypeEnum.CUSTOM_KEY.equals(authType)) {
Valid.notNull(keyId);
} else if (HostExtraSshAuthTypeEnum.IDENTITY.equals(authType)) {
} else if (HostExtraSshAuthTypeEnum.CUSTOM_IDENTITY.equals(authType)) {
Valid.notNull(identityId);
}
// 验证主机秘钥是否存在

View File

@@ -243,7 +243,6 @@ public class AssetAuthorizedDataServiceImpl implements AssetAuthorizedDataServic
List<Long> authorizedGroupIdList) {
// 查询主机列表
List<HostVO> hosts = hostService.getHostListByCache();
// 全部数据直接返回
if (allData) {
return hosts;
@@ -258,6 +257,7 @@ public class AssetAuthorizedDataServiceImpl implements AssetAuthorizedDataServic
.map(dataGroupRel::get)
.filter(Lists::isNoneEmpty)
.flatMap(Collection::stream)
.distinct()
.map(hostMap::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());

View File

@@ -149,14 +149,17 @@ public class DataPermissionServiceImpl implements DataPermissionService {
List<Long> list = RedisLists.range(cacheKey, Long::valueOf);
if (list.isEmpty()) {
LambdaQueryWrapper<DataPermissionDO> wrapper = dataPermissionDAO.lambda()
.eq(DataPermissionDO::getType, type)
.eq(DataPermissionDO::getUserId, userId);
// 查询用户角色
.eq(DataPermissionDO::getType, type);
if (dataType.isToRole()) {
// 查询用户角色
List<Long> roleIdList = systemUserRoleDAO.selectRoleIdByUserId(userId);
if (!roleIdList.isEmpty()) {
wrapper.or().in(DataPermissionDO::getRoleId, roleIdList);
}
wrapper.and(s -> s.eq(DataPermissionDO::getUserId, userId)
.or()
.in(!roleIdList.isEmpty(), DataPermissionDO::getRoleId, roleIdList)
);
} else {
// 单用户
wrapper.eq(DataPermissionDO::getUserId, userId);
}
// 查询数据库
list = dataPermissionDAO.of()

View File

@@ -26,7 +26,11 @@
UPDATE data_extra
SET value = JSON_REPLACE(value,
"$.keyId", NULL,
"$.authType", IF(JSON_EXTRACT(value, "$.authType") = 'KEY', 'DEFAULT', JSON_EXTRACT(value, "$.authType")))
"$.authType", IF(
JSON_EXTRACT(value, "$.authType") = 'CUSTOM_KEY',
'DEFAULT',
JSON_EXTRACT(value, "$.authType")
))
WHERE deleted = 0
AND type = 'HOST'
AND item = 'ssh'
@@ -37,7 +41,11 @@
UPDATE data_extra
SET value = JSON_REPLACE(value,
"$.identityId", NULL,
"$.authType", IF(JSON_EXTRACT(value, "$.authType") = 'IDENTITY', 'DEFAULT', JSON_EXTRACT(value, "$.authType")))
"$.authType", IF(
JSON_EXTRACT(value, "$.authType") = 'CUSTOM_IDENTITY',
'DEFAULT',
JSON_EXTRACT(value, "$.authType")
))
WHERE deleted = 0
AND type = 'HOST'
AND item = 'ssh'

View File

@@ -24,12 +24,12 @@ export function getCurrentAuthorizedHost() {
* 查询当前用户已授权的主机秘钥
*/
export function getCurrentAuthorizedHostKey() {
return axios.get<HostKeyQueryResponse>('/asset/authorized-data/current-host-key');
return axios.get<Array<HostKeyQueryResponse>>('/asset/authorized-data/current-host-key');
}
/**
* 查询当前用户已授权的主机身份
*/
export function getCurrentAuthorizedHostIdentity() {
return axios.get<HostIdentityQueryResponse>('/asset/authorized-data/current-host-identity');
return axios.get<Array<HostIdentityQueryResponse>>('/asset/authorized-data/current-host-identity');
}

View File

@@ -8,9 +8,48 @@ export interface HostAliasUpdateRequest {
name?: string;
}
/**
* 主机拓展信息查询请求
*/
export interface HostExtraQueryRequest {
hostId?: number;
item: string;
items?: Array<string>;
}
/**
* 主机拓展信息更新请求
*/
export interface HostExtraUpdateRequest {
hostId?: number;
item: string;
extra: string;
}
/**
* 修改主机别名
*/
export function updateHostAlias(request: HostAliasUpdateRequest) {
return axios.put('/asset/host-extra/update-alias', request);
}
/**
* 获取主机拓展信息
*/
export function getHostExtraItem<T>(params: HostExtraQueryRequest) {
return axios.get<T>('/asset/host-extra/get', { params });
}
/**
* 获取多个主机拓展信息
*/
export function getHostExtraItemList(request: HostExtraQueryRequest) {
return axios.post<Record<string, Record<string, any>>>('/asset/host-extra/list', request);
}
/**
* 修改主机拓展信息
*/
export function updateHostExtra(request: HostExtraUpdateRequest) {
return axios.put('/asset/host-extra/update', request);
}

View File

@@ -94,6 +94,11 @@ body {
border-color: rgb(var(--gray-2));
}
.full {
width: 100%;
height: 100%;
}
.disabled {
pointer-events: none;
}

View File

@@ -256,10 +256,10 @@
const localeRef = ref();
// 打开应用设置
const openAppSetting = inject(openAppSettingKey) as () => void;
const openAppSetting = inject<() => void>(openAppSettingKey);
// 注入收缩菜单
const toggleDrawerMenu = inject(toggleDrawerMenuKey) as () => void;
const toggleDrawerMenu = inject<() => void>(toggleDrawerMenuKey);
// 切换主题
const handleToggleTheme = () => {

View File

@@ -20,6 +20,7 @@
const props = defineProps({
modelValue: Number,
authorized: Boolean
});
const emits = defineEmits(['update:modelValue']);
@@ -45,7 +46,9 @@
onBeforeMount(async () => {
setLoading(true);
try {
const hostIdentities = await cacheStore.loadHostIdentities();
const hostIdentities = props.authorized
? await cacheStore.loadAuthorizedHostIdentities()
: await cacheStore.loadHostIdentities();
optionData.value = hostIdentities.map(s => {
return {
label: `${s.name} (${s.username})`,

View File

@@ -20,6 +20,7 @@
const props = defineProps({
modelValue: Number,
authorized: Boolean
});
const emits = defineEmits(['update:modelValue']);
@@ -45,7 +46,9 @@
onBeforeMount(async () => {
setLoading(true);
try {
const hostKeys = await cacheStore.loadHostKeys();
const hostKeys = props.authorized
? await cacheStore.loadAuthorizedHostKeys()
: await cacheStore.loadHostKeys();
optionData.value = hostKeys.map(s => {
return {
label: s.name,

View File

@@ -12,7 +12,7 @@
:cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk"
@close="handleClose">
<a-spin :loading="loading">
<a-spin class="full" :loading="loading">
<a-form :model="formModel"
ref="formRef"
label-align="right"

View File

@@ -11,10 +11,13 @@ import { getHostIdentityList } from '@/api/asset/host-identity';
import { getHostList } from '@/api/asset/host';
import { getHostGroupTree } from '@/api/asset/host-group';
import { getMenuList } from '@/api/system/menu';
import { getCurrentAuthorizedHostIdentity, getCurrentAuthorizedHostKey } from '@/api/asset/asset-authorized-data';
export type CacheType = 'users' | 'menus' | 'roles'
| 'host' | 'hostGroups' | 'hostKeys' | 'hostIdentities'
| 'dictKeys' | string
| 'dictKeys'
| 'authorizedHostKeys' | 'authorizedHostIdentities'
| string
export default defineStore('cache', {
state: (): CacheState => ({}),
@@ -98,5 +101,15 @@ export default defineStore('cache', {
return await this.load(`${type}_Tags`, () => getTagList(type), force);
},
// 获取已授权的主机秘钥列表
async loadAuthorizedHostKeys(force = false) {
return await this.load('authorizedHostKeys', getCurrentAuthorizedHostKey, force);
},
// 获取已授权的主机身份列表
async loadAuthorizedHostIdentities(force = false) {
return await this.load('authorizedHostIdentities', getCurrentAuthorizedHostIdentity, force);
},
}
});

View File

@@ -16,6 +16,8 @@ export interface CacheState {
hostKeys?: HostKeyQueryResponse[];
hostIdentities?: HostIdentityQueryResponse[];
dictKeys?: DictKeyQueryResponse[];
authorizedHostKeys?: HostKeyQueryResponse[];
authorizedHostIdentities?: HostIdentityQueryResponse[];
[key: string]: unknown;
}

View File

@@ -12,7 +12,7 @@
:cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk"
@close="handleClose">
<a-spin :loading="loading">
<a-spin class="full" :loading="loading">
<a-form :model="formModel"
ref="formRef"
label-align="right"

View File

@@ -26,9 +26,9 @@
label="用户名"
:rules="usernameRules"
label-col-flex="60px"
:help="AuthType.IDENTITY === formModel.authType ? '将使用主机身份的用户名' : undefined">
:help="SshAuthType.IDENTITY === formModel.authType ? '将使用主机身份的用户名' : undefined">
<a-input v-model="formModel.username"
:disabled="AuthType.IDENTITY === formModel.authType"
:disabled="SshAuthType.IDENTITY === formModel.authType"
placeholder="请输入用户名" />
</a-form-item>
<!-- SSH 端口 -->
@@ -48,10 +48,10 @@
<a-radio-group type="button"
class="auth-type-group usn"
v-model="formModel.authType"
:options="toOptions(authTypeKey)" />
:options="toOptions(sshAuthTypeKey)" />
</a-form-item>
<!-- 主机密码 -->
<a-form-item v-if="AuthType.PASSWORD === formModel.authType"
<a-form-item v-if="SshAuthType.PASSWORD === formModel.authType"
field="password"
label="主机密码"
:rules="passwordRules"
@@ -68,7 +68,7 @@
unchecked-text="使用原密码" />
</a-form-item>
<!-- 主机秘钥 -->
<a-form-item v-if="AuthType.KEY === formModel.authType"
<a-form-item v-if="SshAuthType.KEY === formModel.authType"
field="keyId"
label="主机秘钥"
:hide-asterisk="true"
@@ -76,7 +76,7 @@
<host-key-selector v-model="formModel.keyId" />
</a-form-item>
<!-- 主机身份 -->
<a-form-item v-if="AuthType.IDENTITY === formModel.authType"
<a-form-item v-if="SshAuthType.IDENTITY === formModel.authType"
field="identityId"
label="主机身份"
:hide-asterisk="true"
@@ -140,7 +140,7 @@
import type { HostSshConfig } from './types/const';
import { reactive, ref, watch } from 'vue';
import { updateHostConfigStatus, updateHostConfig } from '@/api/asset/host-config';
import { authTypeKey, AuthType } from './types/const';
import { sshAuthTypeKey, SshAuthType } from './types/const';
import rules from './types/form.rules';
import { Message } from '@arco-design/web-vue';
import useLoading from '@/hooks/loading';
@@ -170,7 +170,7 @@
username: undefined,
port: undefined,
password: undefined,
authType: AuthType.PASSWORD,
authType: SshAuthType.PASSWORD,
keyId: undefined,
identityId: undefined,
connectTimeout: undefined,
@@ -195,7 +195,7 @@
cb('用户名长度不能大于128位');
return;
}
if (formModel.value.authType !== AuthType.IDENTITY && !value) {
if (formModel.value.authType !== SshAuthType.IDENTITY && !value) {
cb('请输入用户名');
return;
}

View File

@@ -14,8 +14,8 @@ export interface HostSshConfig {
hasPassword?: boolean;
}
// 主机验证方式
export const AuthType = {
// 主机 ssh 验证方式
export const SshAuthType = {
// 密码验证
PASSWORD: 'PASSWORD',
// 秘钥验证
@@ -25,7 +25,7 @@ export const AuthType = {
};
// 主机验证方式 字典项
export const authTypeKey = 'hostAuthTypeType';
export const sshAuthTypeKey = 'hostSshAuthType';
// 加载的字典值
export const dictKeys = [authTypeKey];
export const dictKeys = [sshAuthTypeKey];

View File

@@ -12,7 +12,7 @@
:cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk"
@close="handleClose">
<a-spin :loading="loading">
<a-spin class="full" :loading="loading">
<a-form :model="formModel"
ref="formRef"
label-align="right"

View File

@@ -60,7 +60,7 @@
:max-length="32"
:disabled="item.loading"
size="mini"
placeholder="主机别名"
:placeholder="`${item.name} (${item.code})`"
@blur="saveAlias(item)"
@pressEnter="saveAlias(item)"
@change="saveAlias(item)"
@@ -164,11 +164,12 @@
<script lang="ts" setup>
import type { HostQueryResponse } from '@/api/asset/host';
import { ref, nextTick, inject } from 'vue';
import useFavorite from '@/hooks/favorite';
import { dataColor } from '@/utils';
import { tagColor } from '@/views/asset/host-list/types/const';
import { ref, nextTick } from 'vue';
import { updateHostAlias } from '@/api/asset/host-extra';
import { sshModalKey } from '../../types/terminal.const';
const props = defineProps<{
hostList: Array<HostQueryResponse>,
@@ -183,7 +184,7 @@
const clickEditAlias = (item: HostQueryResponse) => {
item.editable = true;
if (!item.alias) {
item.alias = `${item.name} (${item.code})`;
item.alias = '';
}
nextTick(() => {
aliasNameInput.value?.focus();
@@ -217,9 +218,7 @@
};
// 打开配置
const openSetting = (item: HostQueryResponse) => {
console.log('set', item);
};
const openSetting = inject<(record: HostQueryResponse) => void>(sshModalKey);
// 设置收藏
const setFavorite = async (item: HostQueryResponse) => {

View File

@@ -21,6 +21,8 @@
class="list-view-container"
:hostList="hostList"
empty-value="暂无连接记录, 快去体验吧!" />
<!-- 修改主机设置模态框 -->
<ssh-extra-modal ref="sshModal" />
</div>
</template>
@@ -31,12 +33,13 @@
</script>
<script lang="ts" setup>
import { onMounted, ref, watch } from 'vue';
import { NewConnectionType } from '../../types/terminal.const';
import { onMounted, provide, ref, watch } from 'vue';
import { NewConnectionType, sshModalKey } from '../../types/terminal.const';
import { AuthorizedHostQueryResponse } from '@/api/asset/asset-authorized-data';
import { HostQueryResponse } from '@/api/asset/host';
import HostGroupView from './host-group-view.vue';
import HostListView from './host-list-view.vue';
import SshExtraModal from './ssh-extra-modal.vue';
const props = defineProps<{
hosts: AuthorizedHostQueryResponse,
@@ -51,6 +54,12 @@
? props.hosts.groupTree[0].key
: 0
);
const sshModal = ref();
// 暴露打开 ssh 配置模态框
provide(sshModalKey, (record: any) => {
sshModal.value?.open(record);
});
// 主机数据处理
const shuffleHosts = () => {

View File

@@ -0,0 +1,146 @@
<template>
<a-modal v-model:visible="visible"
body-class="modal-form"
title-align="start"
:title="title"
:top="80"
:align-center="false"
:draggable="true"
:mask-closable="false"
:unmount-on-close="true"
ok-text="保存"
:ok-button-props="{ disabled: loading }"
:cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk"
@close="handleClose">
<a-spin class="full" :loading="loading">
<a-form :model="formModel"
ref="formRef"
label-align="right"
:style="{ width: '460px' }"
:label-col-props="{ span: 6 }"
:wrapper-col-props="{ span: 18 }"
:rules="{}">
<!-- 验证方式 -->
<a-form-item field="authType" label="验证方式">
<a-radio-group type="button"
v-model="formModel.authType"
:options="toOptions(extraSshAuthTypeKey)" />
</a-form-item>
<!-- 用户名 -->
<a-form-item v-if="formModel.authType === ExtraSshAuthType.CUSTOM_KEY"
field="username"
label="用户名">
<a-input v-model="formModel.username" placeholder="请输入用户名" />
</a-form-item>
<!-- 主机秘钥 -->
<a-form-item v-if="formModel.authType === ExtraSshAuthType.CUSTOM_KEY"
field="keyId"
label="主机秘钥"
:rules="{ required: true, message: '请选择主机秘钥' }">
<host-key-selector v-model="formModel.keyId"
:authorized="true" />
</a-form-item>
<!-- 主机身份 -->
<a-form-item v-if="formModel.authType === ExtraSshAuthType.CUSTOM_IDENTITY"
field="identityId"
label="主机身份"
:rules="{ required: true, message: '请选择主机身份' }">
<host-identity-selector v-model="formModel.identityId"
:authorized="true" />
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script lang="ts">
export default {
name: 'ssh-extra-modal'
};
</script>
<script lang="ts" setup>
import type { HostQueryResponse } from '@/api/asset/host';
import type { SshExtraModel } from '../../types/terminal.const';
import { ref } from 'vue';
import useLoading from '@/hooks/loading';
import useVisible from '@/hooks/visible';
import { Message } from '@arco-design/web-vue';
import { getHostExtraItem, updateHostExtra } from '@/api/asset/host-extra';
import { ExtraSshAuthType, extraSshAuthTypeKey } from '../../types/terminal.const';
import { useDictStore } from '@/store';
import HostIdentitySelector from '@/components/asset/host-identity/host-identity-selector.vue';
import HostKeySelector from '@/components/asset/host-key/host-key-selector.vue';
const { toOptions } = useDictStore();
const { visible, setVisible } = useVisible();
const { loading, setLoading } = useLoading();
const title = ref<string>();
const hostId = ref<number>();
const formRef = ref();
const formModel = ref<SshExtraModel>({});
// 打开配置
const open = (record: HostQueryResponse) => {
hostId.value = record.id;
title.value = record.alias || `${record.name} (${record.code})`;
renderForm();
setVisible(true);
};
defineExpose({ open });
// 渲染表单
const renderForm = async () => {
try {
setLoading(true);
const { data } = await getHostExtraItem<SshExtraModel>({ hostId: hostId.value, item: 'ssh' });
formModel.value = data;
} catch (e) {
} finally {
setLoading(false);
}
};
// 确定
const handlerOk = async () => {
setLoading(true);
try {
// 验证参数
const error = await formRef.value.validate();
if (error) {
return false;
}
// 修改
await updateHostExtra({
hostId: hostId.value,
item: 'ssh',
extra: JSON.stringify(formModel.value)
});
Message.success('保存成功');
// 清空
handlerClear();
} catch (e) {
return false;
} finally {
setLoading(false);
}
};
// 关闭
const handleClose = () => {
handlerClear();
};
// 清空
const handlerClear = () => {
setLoading(false);
};
</script>
<style lang="less" scoped>
</style>

View File

@@ -34,9 +34,9 @@
<script lang="ts" setup>
import type { TabItem } from './types/terminal.const';
import { ref, onBeforeMount } from 'vue';
import { ref, onBeforeMount, onUnmounted } from 'vue';
import { TabType, InnerTabs, dictKeys } from './types/terminal.const';
import { useDictStore, useTerminalStore } from '@/store';
import { useCacheStore, useDictStore, useTerminalStore } from '@/store';
import TerminalHeader from './components/layout/terminal-header.vue';
import TerminalLeftSidebar from './components/layout/terminal-left-sidebar.vue';
import TerminalRightSidebar from './components/layout/terminal-right-sidebar.vue';
@@ -46,6 +46,7 @@
const terminalStore = useTerminalStore();
const dictStore = useDictStore();
const cacheStore = useCacheStore();
const render = ref(false);
const activeKey = ref(InnerTabs.NEW_CONNECTION.key);
@@ -93,6 +94,11 @@
await dictStore.loadKeys([...dictKeys]);
});
// 卸载时清除 cache
onUnmounted(() => {
cacheStore.reset('authorizedHostKeys', 'authorizedHostIdentities');
});
</script>
<style lang="less" scoped>

View File

@@ -51,6 +51,27 @@ export const NewConnectionType = {
LATEST: 'latest'
};
// ssh 额外配置
export interface SshExtraModel {
authType?: string;
username?: string;
keyId?: number;
identityId?: number;
}
// 主机额外配置 ssh 认证方式
export const ExtraSshAuthType = {
// 使用默认认证方式
DEFAULT: 'DEFAULT',
// 自定义秘钥
CUSTOM_KEY: 'CUSTOM_KEY',
// 自定义身份
CUSTOM_IDENTITY: 'CUSTOM_IDENTITY',
};
// 打开 sshModal key
export const sshModalKey = Symbol();
// 字体后缀 兜底
export const fontFamilySuffix = ',courier-new, courier, monospace';
@@ -72,9 +93,13 @@ export const cursorStyleKey = 'terminalCursorStyle';
// 终端新建连接类型
export const NewConnectionTypeKey = 'terminalNewConnectionType';
// 终端新建连接类型
export const extraSshAuthTypeKey = 'hostExtraSshAuthType';
// 加载的字典值
export const dictKeys = [
darkThemeKey, fontFamilyKey,
fontSizeKey, fontWeightKey,
cursorStyleKey, NewConnectionTypeKey
cursorStyleKey, NewConnectionTypeKey,
extraSshAuthTypeKey
];

View File

@@ -13,7 +13,7 @@
:cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk"
@close="handleClose">
<a-spin :loading="loading">
<a-spin class="full" :loading="loading">
<a-form :model="formModel"
ref="formRef"
label-align="right"

View File

@@ -12,7 +12,7 @@
:cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk"
@close="handleClose">
<a-spin :loading="loading">
<a-spin class="full" :loading="loading">
<a-form :model="formModel"
ref="formRef"
label-align="right"

View File

@@ -12,7 +12,7 @@
:cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk"
@close="handleClose">
<a-spin :loading="loading">
<a-spin class="full" :loading="loading">
<a-form :model="formModel"
ref="formRef"
label-align="right"

View File

@@ -12,7 +12,7 @@
:cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk"
@close="handleClose">
<a-spin :loading="loading">
<a-spin class="full" :loading="loading">
<a-form :model="formModel"
ref="formRef"
label-align="right"

View File

@@ -12,7 +12,7 @@
:cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk"
@close="handleClose">
<a-spin :loading="loading">
<a-spin class="full" :loading="loading">
<a-form :model="formModel"
ref="formRef"
label-align="right"

View File

@@ -12,7 +12,7 @@
:cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk"
@close="handleClose">
<a-spin :loading="loading">
<a-spin class="full" :loading="loading">
<a-form :model="formModel"
ref="formRef"
label-align="right"

View File

@@ -12,7 +12,7 @@
:cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk"
@close="handleClose">
<a-spin :loading="loading">
<a-spin class="full" :loading="loading">
<a-form :model="formModel"
ref="formRef"
label-align="right"

View File

@@ -127,6 +127,7 @@
<!-- 分配角色 -->
<a-button type="text"
size="mini"
:disabled="record.id === userStore.id"
v-permission="['infra:system-user:grant-role']"
@click="emits('openGrantRole', record)">
分配角色