Browse Source
* [Feature][UI Next][V1.0.0-Alpha]: Refactor the user manage page under security. * [Feature][UI Next][V1.0.0-Alpha]: Add license into types file.3.0.0/version-upgrade
Amy0104
3 years ago
committed by
GitHub
26 changed files with 1277 additions and 1128 deletions
@ -0,0 +1,31 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
export function removeUselessChildren( |
||||
list: { children?: []; dirctory?: boolean; disabled?: boolean }[] |
||||
) { |
||||
if (!list.length) return |
||||
list.forEach((item) => { |
||||
if (item.dirctory) item.disabled = true |
||||
if (!item.children) return |
||||
if (item.children.length === 0) { |
||||
delete item.children |
||||
return |
||||
} |
||||
removeUselessChildren(item.children) |
||||
}) |
||||
} |
@ -0,0 +1,159 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import { defineComponent, PropType, toRefs, watch } from 'vue' |
||||
import { useI18n } from 'vue-i18n' |
||||
import { |
||||
NTransfer, |
||||
NSpace, |
||||
NRadioGroup, |
||||
NRadioButton, |
||||
NTreeSelect |
||||
} from 'naive-ui' |
||||
import { useAuthorize } from './use-authorize' |
||||
import Modal from '@/components/modal' |
||||
import styles from '../index.module.scss' |
||||
import type { TAuthType } from '../types' |
||||
|
||||
const props = { |
||||
show: { |
||||
type: Boolean as PropType<boolean>, |
||||
default: false |
||||
}, |
||||
userId: { |
||||
type: Number, |
||||
default: 0 |
||||
}, |
||||
type: { |
||||
type: String as PropType<TAuthType>, |
||||
default: 'auth_project' |
||||
} |
||||
} |
||||
|
||||
export const AuthorizeModal = defineComponent({ |
||||
name: 'authorize-project-modal', |
||||
props, |
||||
emits: ['cancel'], |
||||
setup(props, ctx) { |
||||
const { t } = useI18n() |
||||
const { state, onInit, onSave } = useAuthorize() |
||||
const onCancel = () => { |
||||
ctx.emit('cancel') |
||||
} |
||||
const onConfirm = async () => { |
||||
const result = await onSave(props.type, props.userId) |
||||
if (result) onCancel() |
||||
} |
||||
|
||||
watch( |
||||
() => props.show, |
||||
() => { |
||||
if (props.show) { |
||||
onInit(props.type, props.userId) |
||||
} |
||||
} |
||||
) |
||||
|
||||
return { |
||||
t, |
||||
...toRefs(state), |
||||
onCancel, |
||||
onConfirm |
||||
} |
||||
}, |
||||
render(props: { type: TAuthType }) { |
||||
const { t } = this |
||||
const { type } = props |
||||
return ( |
||||
<Modal |
||||
show={this.show} |
||||
title={t(`security.user.${type}`)} |
||||
onCancel={this.onCancel} |
||||
confirmLoading={this.loading} |
||||
onConfirm={this.onConfirm} |
||||
confirmClassName='btn-submit' |
||||
cancelClassName='btn-cancel' |
||||
> |
||||
{type === 'authorize_project' && ( |
||||
<NTransfer |
||||
virtualScroll |
||||
options={this.unauthorizedProjects} |
||||
filterable |
||||
v-model={[this.authorizedProjects, 'value']} |
||||
class={styles.transfer} |
||||
/> |
||||
)} |
||||
{type === 'authorize_datasource' && ( |
||||
<NTransfer |
||||
virtualScroll |
||||
options={this.unauthorizedDatasources} |
||||
filterable |
||||
v-model:value={this.authorizedDatasources} |
||||
class={styles.transfer} |
||||
/> |
||||
)} |
||||
{type === 'authorize_udf' && ( |
||||
<NTransfer |
||||
virtualScroll |
||||
options={this.unauthorizedUdfs} |
||||
filterable |
||||
v-model:value={this.authorizedUdfs} |
||||
class={styles.transfer} |
||||
/> |
||||
)} |
||||
{type === 'authorize_resource' && ( |
||||
<NSpace vertical> |
||||
<NRadioGroup v-model:value={this.resourceType}> |
||||
<NRadioButton key='file' value='file'> |
||||
{t('security.user.file_resource')} |
||||
</NRadioButton> |
||||
<NRadioButton key='udf' value='udf'> |
||||
{t('security.user.udf_resource')} |
||||
</NRadioButton> |
||||
</NRadioGroup> |
||||
<NTreeSelect |
||||
v-show={this.resourceType === 'file'} |
||||
filterable |
||||
multiple |
||||
cascade |
||||
checkable |
||||
checkStrategy='child' |
||||
key-field='id' |
||||
label-field='fullName' |
||||
options={this.fileResources} |
||||
v-model:value={this.authorizedFileResources} |
||||
/> |
||||
<NTreeSelect |
||||
v-show={this.resourceType === 'udf'} |
||||
filterable |
||||
multiple |
||||
cascade |
||||
checkable |
||||
checkStrategy='child' |
||||
key-field='id' |
||||
label-field='fullName' |
||||
options={this.udfResources} |
||||
v-model:value={this.authorizedUdfResources} |
||||
/> |
||||
</NSpace> |
||||
)} |
||||
</Modal> |
||||
) |
||||
} |
||||
}) |
||||
|
||||
export default AuthorizeModal |
@ -0,0 +1,196 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
import { reactive } from 'vue' |
||||
import { useI18n } from 'vue-i18n' |
||||
import { |
||||
queryAuthorizedProject, |
||||
queryUnauthorizedProject |
||||
} from '@/service/modules/projects' |
||||
import { |
||||
authedDatasource, |
||||
unAuthDatasource |
||||
} from '@/service/modules/data-source' |
||||
import { |
||||
authorizedFile, |
||||
authorizeResourceTree, |
||||
authUDFFunc, |
||||
unAuthUDFFunc |
||||
} from '@/service/modules/resources' |
||||
import { |
||||
grantProject, |
||||
grantResource, |
||||
grantDataSource, |
||||
grantUDFFunc |
||||
} from '@/service/modules/users' |
||||
import { removeUselessChildren } from '@/utils/tree-format' |
||||
import type { TAuthType, IResourceOption, IOption } from '../types' |
||||
|
||||
export function useAuthorize() { |
||||
const { t } = useI18n() |
||||
|
||||
const state = reactive({ |
||||
saving: false, |
||||
loading: false, |
||||
authorizedProjects: [] as number[], |
||||
unauthorizedProjects: [] as IOption[], |
||||
authorizedDatasources: [] as number[], |
||||
unauthorizedDatasources: [] as IOption[], |
||||
authorizedUdfs: [] as number[], |
||||
unauthorizedUdfs: [] as IOption[], |
||||
resourceType: 'file', |
||||
fileResources: [] as IResourceOption[], |
||||
udfResources: [] as IResourceOption[], |
||||
authorizedFileResources: [] as number[], |
||||
authorizedUdfResources: [] as number[] |
||||
}) |
||||
|
||||
const getProjects = async (userId: number) => { |
||||
if (state.loading) return |
||||
state.loading = true |
||||
const projects = await Promise.all([ |
||||
queryAuthorizedProject({ userId }), |
||||
queryUnauthorizedProject({ userId }) |
||||
]) |
||||
state.loading = false |
||||
state.authorizedProjects = projects[0].map( |
||||
(item: { name: string; id: number }) => item.id |
||||
) |
||||
state.unauthorizedProjects = [...projects[0], ...projects[1]].map( |
||||
(item: { name: string; id: number }) => ({ |
||||
label: item.name, |
||||
value: item.id |
||||
}) |
||||
) |
||||
} |
||||
|
||||
const getDatasources = async (userId: number) => { |
||||
if (state.loading) return |
||||
state.loading = true |
||||
const datasources = await Promise.all([ |
||||
authedDatasource({ userId }), |
||||
unAuthDatasource({ userId }) |
||||
]) |
||||
state.loading = false |
||||
state.authorizedDatasources = datasources[0].map( |
||||
(item: { name: string; id: number }) => item.id |
||||
) |
||||
state.unauthorizedDatasources = [...datasources[0], ...datasources[1]].map( |
||||
(item: { name: string; id: number }) => ({ |
||||
label: item.name, |
||||
value: item.id |
||||
}) |
||||
) |
||||
} |
||||
|
||||
const getUdfs = async (userId: number) => { |
||||
if (state.loading) return |
||||
state.loading = true |
||||
const udfs = await Promise.all([ |
||||
authUDFFunc({ userId }), |
||||
unAuthUDFFunc({ userId }) |
||||
]) |
||||
state.loading = false |
||||
state.authorizedUdfs = udfs[0].map( |
||||
(item: { name: string; id: number }) => item.id |
||||
) |
||||
state.unauthorizedUdfs = [...udfs[0], ...udfs[1]].map( |
||||
(item: { name: string; id: number }) => ({ |
||||
label: item.name, |
||||
value: item.id |
||||
}) |
||||
) |
||||
} |
||||
|
||||
const getResources = async (userId: number) => { |
||||
if (state.loading) return |
||||
state.loading = true |
||||
const resources = await Promise.all([ |
||||
authorizeResourceTree({ userId }), |
||||
authorizedFile({ userId }) |
||||
]) |
||||
state.loading = false |
||||
removeUselessChildren(resources[0]) |
||||
let udfResources = [] as IResourceOption[] |
||||
let fileResources = [] as IResourceOption[] |
||||
resources[0].forEach((item: IResourceOption) => { |
||||
item.type === 'FILE' ? fileResources.push(item) : udfResources.push(item) |
||||
}) |
||||
let udfTargets = [] as number[] |
||||
let fileTargets = [] as number[] |
||||
resources[1].forEach((item: { type: string; id: number }) => { |
||||
item.type === 'FILE' |
||||
? fileTargets.push(item.id) |
||||
: udfTargets.push(item.id) |
||||
}) |
||||
state.fileResources = fileResources |
||||
state.udfResources = udfResources |
||||
console.log(fileResources) |
||||
state.authorizedFileResources = fileTargets |
||||
state.authorizedUdfResources = fileTargets |
||||
} |
||||
|
||||
const onInit = (type: TAuthType, userId: number) => { |
||||
if (type === 'authorize_project') { |
||||
getProjects(userId) |
||||
} |
||||
if (type === 'authorize_datasource') { |
||||
getDatasources(userId) |
||||
} |
||||
if (type === 'authorize_udf') { |
||||
getUdfs(userId) |
||||
} |
||||
if (type === 'authorize_resource') { |
||||
getResources(userId) |
||||
} |
||||
} |
||||
|
||||
const onSave = async (type: TAuthType, userId: number) => { |
||||
if (state.saving) return false |
||||
state.saving = true |
||||
if (type === 'authorize_project') { |
||||
await grantProject({ |
||||
userId, |
||||
projectIds: state.authorizedProjects.join(',') |
||||
}) |
||||
} |
||||
if (type === 'authorize_datasource') { |
||||
await grantDataSource({ |
||||
userId, |
||||
datasourceIds: state.authorizedDatasources.join(',') |
||||
}) |
||||
} |
||||
if (type === 'authorize_udf') { |
||||
await grantUDFFunc({ |
||||
userId, |
||||
udfIds: state.authorizedUdfResources.join(',') |
||||
}) |
||||
} |
||||
if (type === 'authorize_resource') { |
||||
await grantResource({ |
||||
userId, |
||||
resourceIds: |
||||
state.resourceType === 'file' |
||||
? state.authorizedFileResources.join(',') |
||||
: state.authorizedUdfResources.join(',') |
||||
}) |
||||
} |
||||
state.saving = false |
||||
return true |
||||
} |
||||
|
||||
return { state, onInit, onSave } |
||||
} |
@ -1,496 +0,0 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import { ref, watch, computed, InjectionKey } from 'vue' |
||||
import { useI18n } from 'vue-i18n' |
||||
import { useMessage } from 'naive-ui' |
||||
import { queryTenantList } from '@/service/modules/tenants' |
||||
import { queryList } from '@/service/modules/queues' |
||||
import { |
||||
createUser, |
||||
updateUser, |
||||
delUserById, |
||||
verifyUserName, |
||||
grantProject, |
||||
grantResource, |
||||
grantDataSource, |
||||
grantUDFFunc |
||||
} from '@/service/modules/users' |
||||
import { |
||||
queryAuthorizedProject, |
||||
queryUnauthorizedProject |
||||
} from '@/service/modules/projects' |
||||
import { |
||||
authorizedFile, |
||||
authorizeResourceTree, |
||||
authUDFFunc, |
||||
unAuthUDFFunc |
||||
} from '@/service/modules/resources' |
||||
import { |
||||
authedDatasource, |
||||
unAuthDatasource |
||||
} from '@/service/modules/data-source' |
||||
import regexUtils from '@/utils/regex' |
||||
export type Mode = |
||||
| 'add' |
||||
| 'edit' |
||||
| 'delete' |
||||
| 'auth_project' |
||||
| 'auth_resource' |
||||
| 'auth_datasource' |
||||
| 'auth_udf' |
||||
|
||||
export type UserModalSharedStateType = ReturnType< |
||||
typeof useSharedUserModalState |
||||
> & { |
||||
onSuccess?: (mode: Mode) => void |
||||
} |
||||
|
||||
export const UserModalSharedStateKey: InjectionKey<UserModalSharedStateType> = |
||||
Symbol() |
||||
|
||||
export function useSharedUserModalState() { |
||||
return { |
||||
show: ref(false), |
||||
mode: ref<Mode>('add'), |
||||
user: ref() |
||||
} |
||||
} |
||||
|
||||
export function useModal({ |
||||
onSuccess, |
||||
show, |
||||
mode, |
||||
user |
||||
}: UserModalSharedStateType) { |
||||
const message = useMessage() |
||||
const { t } = useI18n() |
||||
const formRef = ref() |
||||
const formValues = ref({ |
||||
userName: '', |
||||
userPassword: '', |
||||
tenantId: 0, |
||||
email: '', |
||||
queue: '', |
||||
phone: '', |
||||
state: 1 |
||||
}) |
||||
const tenants = ref<any[]>([]) |
||||
const queues = ref<any[]>([]) |
||||
const authorizedProjects = ref<string[]>([]) |
||||
const projects = ref<any[]>([]) |
||||
const authorizedFiles = ref<string[]>([]) |
||||
const originResourceTree = ref<any[]>([]) |
||||
const resourceType = ref<'file' | 'udf'>() |
||||
const authorizedUDF = ref<string[]>([]) |
||||
const UDFs = ref<any[]>([]) |
||||
const authorizedDatasource = ref<string[]>([]) |
||||
const datasource = ref<any[]>([]) |
||||
const optionsLoading = ref(false) |
||||
const confirmLoading = ref(false) |
||||
|
||||
const formRules = computed(() => { |
||||
return { |
||||
userName: { |
||||
required: true, |
||||
message: t('security.user.username_rule_msg'), |
||||
trigger: 'blur' |
||||
}, |
||||
userPassword: { |
||||
required: mode.value === 'add', |
||||
validator(rule: any, value?: string) { |
||||
if (mode.value !== 'add' && !value) { |
||||
return true |
||||
} |
||||
const msg = t('security.user.user_password_rule_msg') |
||||
if (!value || !regexUtils.password.test(value)) { |
||||
return new Error(msg) |
||||
} |
||||
return true |
||||
}, |
||||
trigger: ['blur', 'input'] |
||||
}, |
||||
tenantId: { |
||||
required: true, |
||||
validator(rule: any, value?: number) { |
||||
const msg = t('security.user.tenant_id_rule_msg') |
||||
if (typeof value === 'number') { |
||||
return true |
||||
} |
||||
return new Error(msg) |
||||
}, |
||||
trigger: 'blur' |
||||
}, |
||||
email: { |
||||
required: true, |
||||
validator(rule: any, value?: string) { |
||||
const msg = t('security.user.email_rule_msg') |
||||
if (!value || !regexUtils.email.test(value)) { |
||||
return new Error(msg) |
||||
} |
||||
return true |
||||
}, |
||||
trigger: ['blur', 'input'] |
||||
}, |
||||
phone: { |
||||
validator(rule: any, value?: string) { |
||||
const msg = t('security.user.phone_rule_msg') |
||||
if (value && !regexUtils.phone.test(value)) { |
||||
return new Error(msg) |
||||
} |
||||
return true |
||||
}, |
||||
trigger: ['blur', 'input'] |
||||
} |
||||
} |
||||
}) |
||||
|
||||
const resourceTree = computed(() => { |
||||
const loopTree = (arr: any[]): any[] => |
||||
arr |
||||
.map((d) => { |
||||
if ( |
||||
(resourceType.value && |
||||
`${d.type}`.toLowerCase() === resourceType.value) || |
||||
!resourceType.value |
||||
) { |
||||
const obj = { key: `${d.pid}-${d.id}`, label: d.name } |
||||
const children = d.children |
||||
if (children instanceof Array && children.length > 0) { |
||||
return { |
||||
...obj, |
||||
children: loopTree(children) |
||||
} |
||||
} |
||||
return obj |
||||
} |
||||
return null |
||||
}) |
||||
.filter((f) => f) |
||||
const data = loopTree(originResourceTree.value) |
||||
return data |
||||
}) |
||||
|
||||
const titleMap = computed(() => { |
||||
const titles: Record<Mode, string> = { |
||||
add: t('security.user.create_user'), |
||||
edit: t('security.user.update_user'), |
||||
delete: t('security.user.delete_user'), |
||||
auth_project: t('security.user.authorize_project'), |
||||
auth_resource: t('security.user.authorize_resource'), |
||||
auth_datasource: t('security.user.authorize_datasource'), |
||||
auth_udf: t('security.user.authorize_udf') |
||||
} |
||||
return titles |
||||
}) |
||||
|
||||
const setFormValues = () => { |
||||
const defaultValues = { |
||||
userName: '', |
||||
userPassword: '', |
||||
tenantId: tenants.value[0]?.value, |
||||
email: '', |
||||
queue: queues.value[0]?.value, |
||||
phone: '', |
||||
state: 1 |
||||
} |
||||
if (!user.value) { |
||||
formValues.value = defaultValues |
||||
} else { |
||||
const v: any = {} |
||||
Object.keys(defaultValues).map((k) => { |
||||
v[k] = user.value[k] |
||||
}) |
||||
v.userPassword = '' |
||||
formValues.value = v |
||||
} |
||||
} |
||||
|
||||
const prepareOptions = async () => { |
||||
optionsLoading.value = true |
||||
Promise.all([queryTenantList(), queryList()]) |
||||
.then((res) => { |
||||
tenants.value = |
||||
res[0]?.map((d: any) => ({ |
||||
label: d.tenantCode, |
||||
value: d.id |
||||
})) || [] |
||||
queues.value = |
||||
res[1]?.map((d: any) => ({ |
||||
label: d.queueName, |
||||
value: d.queue |
||||
})) || [] |
||||
}) |
||||
.finally(() => { |
||||
optionsLoading.value = false |
||||
}) |
||||
} |
||||
|
||||
const fetchProjects = async () => { |
||||
optionsLoading.value = true |
||||
Promise.all([ |
||||
queryAuthorizedProject({ userId: user.value.id }), |
||||
queryUnauthorizedProject({ userId: user.value.id }) |
||||
]) |
||||
.then((res: any[]) => { |
||||
const ids: string[] = [] |
||||
res[0]?.forEach((d: any) => { |
||||
if (!ids.includes(d.id)) { |
||||
ids.push(d.id) |
||||
} |
||||
}) |
||||
authorizedProjects.value = ids |
||||
projects.value = |
||||
res?.flat().map((d: any) => ({ label: d.name, value: d.id })) || [] |
||||
}) |
||||
.finally(() => { |
||||
optionsLoading.value = false |
||||
}) |
||||
} |
||||
|
||||
const fetchResources = async () => { |
||||
optionsLoading.value = true |
||||
Promise.all([ |
||||
authorizedFile({ userId: user.value.id }), |
||||
authorizeResourceTree({ userId: user.value.id }) |
||||
]) |
||||
.then((res: any[]) => { |
||||
const ids: string[] = [] |
||||
const getIds = (arr: any[]) => { |
||||
arr.forEach((d) => { |
||||
const children = d.children |
||||
if (children instanceof Array && children.length > 0) { |
||||
getIds(children) |
||||
} else { |
||||
ids.push(`${d.pid}-${d.id}`) |
||||
} |
||||
}) |
||||
} |
||||
getIds(res[0] || []) |
||||
authorizedFiles.value = ids |
||||
originResourceTree.value = res[1] || [] |
||||
}) |
||||
.finally(() => { |
||||
optionsLoading.value = false |
||||
}) |
||||
} |
||||
|
||||
const fetchDatasource = async () => { |
||||
optionsLoading.value = true |
||||
Promise.all([ |
||||
authedDatasource({ userId: user.value.id }), |
||||
unAuthDatasource({ userId: user.value.id }) |
||||
]) |
||||
.then((res: any[]) => { |
||||
const ids: string[] = [] |
||||
res[0]?.forEach((d: any) => { |
||||
if (!ids.includes(d.id)) { |
||||
ids.push(d.id) |
||||
} |
||||
}) |
||||
authorizedDatasource.value = ids |
||||
datasource.value = |
||||
res?.flat().map((d: any) => ({ label: d.name, value: d.id })) || [] |
||||
}) |
||||
.finally(() => { |
||||
optionsLoading.value = false |
||||
}) |
||||
} |
||||
|
||||
const fetchUDFs = async () => { |
||||
optionsLoading.value = true |
||||
Promise.all([ |
||||
authUDFFunc({ userId: user.value.id }), |
||||
unAuthUDFFunc({ userId: user.value.id }) |
||||
]) |
||||
.then((res: any[]) => { |
||||
const ids: string[] = [] |
||||
res[0]?.forEach((d: any) => { |
||||
if (!ids.includes(d.id)) { |
||||
ids.push(d.id) |
||||
} |
||||
}) |
||||
authorizedUDF.value = ids |
||||
UDFs.value = |
||||
res?.flat().map((d: any) => ({ label: d.name, value: d.id })) || [] |
||||
}) |
||||
.finally(() => { |
||||
optionsLoading.value = false |
||||
}) |
||||
} |
||||
|
||||
const onModalCancel = () => { |
||||
show.value = false |
||||
} |
||||
|
||||
const onDelete = () => { |
||||
confirmLoading.value = true |
||||
delUserById({ id: user.value.id }) |
||||
.then( |
||||
() => { |
||||
onSuccess?.(mode.value) |
||||
onModalCancel() |
||||
}, |
||||
() => { |
||||
message.error(t('security.user.delete_error_msg')) |
||||
} |
||||
) |
||||
.finally(() => { |
||||
confirmLoading.value = false |
||||
}) |
||||
} |
||||
|
||||
const onCreateUser = () => { |
||||
confirmLoading.value = true |
||||
verifyUserName({ userName: formValues.value.userName }) |
||||
.then( |
||||
() => createUser(formValues.value), |
||||
(error) => { |
||||
if (`${error.message}`.includes('exists')) { |
||||
message.error(t('security.user.username_exists')) |
||||
} |
||||
return false |
||||
} |
||||
) |
||||
.then( |
||||
(res) => { |
||||
if (res) { |
||||
onSuccess?.(mode.value) |
||||
onModalCancel() |
||||
} |
||||
}, |
||||
() => { |
||||
message.error(t('security.user.save_error_msg')) |
||||
} |
||||
) |
||||
.finally(() => { |
||||
confirmLoading.value = false |
||||
}) |
||||
} |
||||
|
||||
const onUpdateUser = () => { |
||||
confirmLoading.value = true |
||||
updateUser({ id: user.value.id, ...formValues.value }) |
||||
.then( |
||||
() => { |
||||
onSuccess?.(mode.value) |
||||
onModalCancel() |
||||
}, |
||||
() => { |
||||
message.error(t('security.user.save_error_msg')) |
||||
} |
||||
) |
||||
.finally(() => { |
||||
confirmLoading.value = false |
||||
}) |
||||
} |
||||
|
||||
const onGrant = (grantReq: () => Promise<any>) => { |
||||
confirmLoading.value = true |
||||
grantReq() |
||||
.then( |
||||
() => { |
||||
onSuccess?.(mode.value) |
||||
onModalCancel() |
||||
message.success(t('security.user.auth_success_msg')) |
||||
}, |
||||
() => { |
||||
message.error(t('security.user.auth_error_msg')) |
||||
} |
||||
) |
||||
.finally(() => { |
||||
confirmLoading.value = false |
||||
}) |
||||
} |
||||
|
||||
const onConfirm = () => { |
||||
if (mode.value === 'add' || mode.value === 'edit') { |
||||
formRef.value.validate((errors: any) => { |
||||
if (!errors) { |
||||
user.value ? onUpdateUser() : onCreateUser() |
||||
} |
||||
}) |
||||
} else { |
||||
mode.value === 'delete' && onDelete() |
||||
mode.value === 'auth_project' && |
||||
onGrant(() => |
||||
grantProject({ |
||||
userId: user.value.id, |
||||
projectIds: authorizedProjects.value.join(',') |
||||
}) |
||||
) |
||||
mode.value === 'auth_resource' && |
||||
onGrant(() => |
||||
grantResource({ |
||||
userId: user.value.id, |
||||
resourceIds: authorizedFiles.value.join(',') |
||||
}) |
||||
) |
||||
mode.value === 'auth_datasource' && |
||||
onGrant(() => |
||||
grantDataSource({ |
||||
userId: user.value.id, |
||||
datasourceIds: authorizedDatasource.value.join(',') |
||||
}) |
||||
) |
||||
mode.value === 'auth_udf' && |
||||
onGrant(() => |
||||
grantUDFFunc({ |
||||
userId: user.value.id, |
||||
udfIds: authorizedUDF.value.join(',') |
||||
}) |
||||
) |
||||
} |
||||
} |
||||
|
||||
watch([show, mode], () => { |
||||
show.value && ['add', 'edit'].includes(mode.value) && prepareOptions() |
||||
show.value && mode.value === 'auth_project' && fetchProjects() |
||||
show.value && mode.value === 'auth_resource' && fetchResources() |
||||
show.value && mode.value === 'auth_datasource' && fetchDatasource() |
||||
show.value && mode.value === 'auth_udf' && fetchUDFs() |
||||
}) |
||||
|
||||
watch([queues, tenants, user], () => { |
||||
setFormValues() |
||||
}) |
||||
|
||||
return { |
||||
show, |
||||
mode, |
||||
user, |
||||
titleMap, |
||||
onModalCancel, |
||||
formRef, |
||||
formValues, |
||||
formRules, |
||||
tenants, |
||||
queues, |
||||
authorizedProjects, |
||||
projects, |
||||
authorizedDatasource, |
||||
datasource, |
||||
authorizedUDF, |
||||
UDFs, |
||||
authorizedFiles, |
||||
resourceTree, |
||||
resourceType, |
||||
optionsLoading, |
||||
onConfirm, |
||||
confirmLoading |
||||
} |
||||
} |
@ -0,0 +1,177 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
import { onMounted, reactive, ref } from 'vue' |
||||
import { useI18n } from 'vue-i18n' |
||||
import { pick } from 'lodash' |
||||
import { queryTenantList } from '@/service/modules/tenants' |
||||
import { queryList } from '@/service/modules/queues' |
||||
import { verifyUserName, createUser, updateUser } from '@/service/modules/users' |
||||
import { useUserStore } from '@/store/user/user' |
||||
import type { IRecord, UserReq, UserInfoRes } from '../types' |
||||
|
||||
export function useUserDetail() { |
||||
const { t } = useI18n() |
||||
const userStore = useUserStore() |
||||
const userInfo = userStore.getUserInfo as UserInfoRes |
||||
const IS_ADMIN = userInfo.userType === 'ADMIN_USER' |
||||
|
||||
const initialValues = { |
||||
userName: '', |
||||
userPassword: '', |
||||
tenantId: 0, |
||||
email: '', |
||||
queue: '', |
||||
phone: '', |
||||
state: 1 |
||||
} as UserReq |
||||
|
||||
let PREV_NAME: string |
||||
|
||||
const state = reactive({ |
||||
formRef: ref(), |
||||
formData: { ...initialValues }, |
||||
saving: false, |
||||
loading: false, |
||||
queues: [] as { label: string; value: string }[], |
||||
tenants: [] as { label: string; value: number }[] |
||||
}) |
||||
|
||||
const formRules = { |
||||
userName: { |
||||
trigger: ['input', 'blur'], |
||||
required: true, |
||||
validator(validator: any, value: string) { |
||||
if (!value.trim()) { |
||||
return new Error(t('security.user.username_tips')) |
||||
} |
||||
} |
||||
}, |
||||
userPassword: { |
||||
trigger: ['input', 'blur'], |
||||
required: true, |
||||
validator(validator: any, value: string) { |
||||
if ( |
||||
!value || |
||||
!/^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?![`~!@#$%^&*()_\-+=<>?:"{}|,./;'\\[\]·~!@#¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、]+$)[`~!@#$%^&*()_\-+=<>?:"{}|,./;'\\[\]·~!@#¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、0-9A-Za-z]{6,22}$/.test( |
||||
value |
||||
) |
||||
) { |
||||
return new Error(t('security.user.user_password_tips')) |
||||
} |
||||
} |
||||
}, |
||||
tenantId: { |
||||
trigger: ['input', 'blur'], |
||||
required: true, |
||||
validator(validator: any, value: string) { |
||||
if (IS_ADMIN && !value) { |
||||
return new Error(t('security.user.tenant_id_tips')) |
||||
} |
||||
} |
||||
}, |
||||
email: { |
||||
trigger: ['input', 'blur'], |
||||
required: true, |
||||
validator(validator: any, value: string) { |
||||
if (!value) { |
||||
return new Error(t('security.user.email_empty_tips')) |
||||
} |
||||
if ( |
||||
!/^([a-zA-Z0-9]+[_|\-|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\-|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,}$/.test( |
||||
value |
||||
) |
||||
) { |
||||
return new Error(t('security.user.emial_correct_tips')) |
||||
} |
||||
} |
||||
}, |
||||
phone: { |
||||
trigger: ['input', 'blur'], |
||||
validator(validator: any, value: string) { |
||||
if (value && !/^1(3|4|5|6|7|8)\d{9}$/.test(value)) { |
||||
return new Error(t('security.user.phone_correct_tips')) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
const getQueues = async () => { |
||||
const result = await queryList() |
||||
state.queues = result.map((queue: { queueName: string; id: string }) => ({ |
||||
label: queue.queueName, |
||||
value: queue.id |
||||
})) |
||||
if (state.queues.length) { |
||||
initialValues.queue = state.queues[0].value |
||||
state.formData.queue = state.queues[0].value |
||||
} |
||||
} |
||||
const getTenants = async () => { |
||||
const result = await queryTenantList() |
||||
state.tenants = result.map( |
||||
(tenant: { tenantCode: string; id: number }) => ({ |
||||
label: tenant.tenantCode, |
||||
value: tenant.id |
||||
}) |
||||
) |
||||
if (state.tenants.length) { |
||||
initialValues.tenantId = state.tenants[0].value |
||||
state.formData.tenantId = state.tenants[0].value |
||||
} |
||||
} |
||||
const onReset = () => { |
||||
state.formData = { ...initialValues } |
||||
} |
||||
const onSave = async (id?: number): Promise<boolean> => { |
||||
await state.formRef.validate() |
||||
if (state.saving) return false |
||||
state.saving = true |
||||
if (PREV_NAME !== state.formData.userName) { |
||||
await verifyUserName({ userName: state.formData.userName }) |
||||
} |
||||
|
||||
id |
||||
? await updateUser({ id, ...state.formData }) |
||||
: await createUser(state.formData) |
||||
|
||||
state.saving = false |
||||
return true |
||||
} |
||||
const onSetValues = (record: IRecord) => { |
||||
state.formData = { |
||||
...pick(record, [ |
||||
'userName', |
||||
'tenantId', |
||||
'email', |
||||
'queue', |
||||
'phone', |
||||
'state' |
||||
]), |
||||
userPassword: '' |
||||
} as UserReq |
||||
PREV_NAME = state.formData.userName |
||||
} |
||||
|
||||
onMounted(async () => { |
||||
if (IS_ADMIN) { |
||||
getQueues() |
||||
getTenants() |
||||
} |
||||
}) |
||||
|
||||
return { state, formRules, IS_ADMIN, onReset, onSave, onSetValues } |
||||
} |
@ -0,0 +1,177 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import { defineComponent, PropType, toRefs, watch } from 'vue' |
||||
import { useI18n } from 'vue-i18n' |
||||
import { |
||||
NInput, |
||||
NForm, |
||||
NFormItem, |
||||
NSelect, |
||||
NRadio, |
||||
NRadioGroup, |
||||
NSpace |
||||
} from 'naive-ui' |
||||
import { useUserDetail } from './use-user-detail' |
||||
import Modal from '@/components/modal' |
||||
import type { IRecord } from '../types' |
||||
|
||||
const props = { |
||||
show: { |
||||
type: Boolean as PropType<boolean>, |
||||
default: false |
||||
}, |
||||
currentRecord: { |
||||
type: Object as PropType<IRecord | null>, |
||||
default: {} |
||||
} |
||||
} |
||||
|
||||
export const UserModal = defineComponent({ |
||||
name: 'user-modal', |
||||
props, |
||||
emits: ['cancel', 'update'], |
||||
setup(props, ctx) { |
||||
const { t } = useI18n() |
||||
const { state, IS_ADMIN, formRules, onReset, onSave, onSetValues } = |
||||
useUserDetail() |
||||
const onCancel = () => { |
||||
onReset() |
||||
ctx.emit('cancel') |
||||
} |
||||
const onConfirm = async () => { |
||||
const result = await onSave(props.currentRecord?.id) |
||||
if (!result) return |
||||
onCancel() |
||||
ctx.emit('update') |
||||
} |
||||
|
||||
watch( |
||||
() => props.show, |
||||
() => { |
||||
if (props.show && props.currentRecord?.id) { |
||||
onSetValues(props.currentRecord) |
||||
} |
||||
} |
||||
) |
||||
|
||||
return { |
||||
t, |
||||
...toRefs(state), |
||||
IS_ADMIN, |
||||
formRules, |
||||
onCancel, |
||||
onConfirm |
||||
} |
||||
}, |
||||
render(props: { currentRecord: IRecord }) { |
||||
const { t } = this |
||||
const { currentRecord } = props |
||||
return ( |
||||
<Modal |
||||
show={this.show} |
||||
title={`${t( |
||||
currentRecord?.id |
||||
? 'security.user.update_user' |
||||
: 'security.user.create_user' |
||||
)}`}
|
||||
onCancel={this.onCancel} |
||||
confirmLoading={this.loading} |
||||
onConfirm={this.onConfirm} |
||||
confirmClassName='btn-submit' |
||||
cancelClassName='btn-cancel' |
||||
> |
||||
<NForm |
||||
ref='formRef' |
||||
model={this.formData} |
||||
rules={this.formRules} |
||||
labelPlacement='left' |
||||
labelAlign='left' |
||||
labelWidth={80} |
||||
> |
||||
<NFormItem label={t('security.user.username')} path='userName'> |
||||
<NInput |
||||
class='input-username' |
||||
v-model:value={this.formData.userName} |
||||
minlength={3} |
||||
maxlength={39} |
||||
placeholder={t('security.user.username_tips')} |
||||
/> |
||||
</NFormItem> |
||||
<NFormItem |
||||
label={t('security.user.user_password')} |
||||
path='userPassword' |
||||
> |
||||
<NInput |
||||
class='input-password' |
||||
type='password' |
||||
v-model:value={this.formData.userPassword} |
||||
placeholder={t('security.user.user_password_tips')} |
||||
/> |
||||
</NFormItem> |
||||
{this.IS_ADMIN && ( |
||||
<NFormItem label={t('security.user.tenant_code')} path='tenantId'> |
||||
<NSelect |
||||
class='select-tenant' |
||||
options={this.tenants} |
||||
v-model:value={this.formData.tenantId} |
||||
/> |
||||
</NFormItem> |
||||
)} |
||||
{this.IS_ADMIN && ( |
||||
<NFormItem label={t('security.user.queue')} path='queue'> |
||||
<NSelect |
||||
class='select-queue' |
||||
options={this.queues} |
||||
v-model:value={this.formData.queue} |
||||
placeholder={t('security.user.queue_tips')} |
||||
/> |
||||
</NFormItem> |
||||
)} |
||||
<NFormItem label={t('security.user.email')} path='email'> |
||||
<NInput |
||||
class='input-email' |
||||
v-model:value={this.formData.email} |
||||
placeholder={t('security.user.email_empty_tips')} |
||||
/> |
||||
</NFormItem> |
||||
<NFormItem label={t('security.user.phone')} path='phone'> |
||||
<NInput |
||||
class='input-phone' |
||||
v-model:value={this.formData.phone} |
||||
placeholder={t('security.user.phone_empty_tips')} |
||||
/> |
||||
</NFormItem> |
||||
<NFormItem label={t('security.user.state')} path='state'> |
||||
<NRadioGroup v-model:value={this.formData.state}> |
||||
<NSpace> |
||||
<NRadio value={1} class='radio-state-enable'> |
||||
{this.t('security.user.enable')} |
||||
</NRadio> |
||||
<NRadio value={0} class='radio-state-disable'> |
||||
{this.t('security.user.disable')} |
||||
</NRadio> |
||||
</NSpace> |
||||
</NRadioGroup> |
||||
</NFormItem> |
||||
</NForm> |
||||
</Modal> |
||||
) |
||||
} |
||||
}) |
||||
|
||||
export default UserModal |
@ -1,209 +0,0 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import { defineComponent, inject } from 'vue' |
||||
import { useI18n } from 'vue-i18n' |
||||
import { |
||||
NInput, |
||||
NForm, |
||||
NFormItem, |
||||
NSelect, |
||||
NRadio, |
||||
NRadioGroup, |
||||
NRadioButton, |
||||
NSpace, |
||||
NAlert, |
||||
NTransfer, |
||||
NTreeSelect |
||||
} from 'naive-ui' |
||||
|
||||
import Modal from '@/components/modal' |
||||
import { |
||||
useModal, |
||||
useSharedUserModalState, |
||||
UserModalSharedStateKey |
||||
} from './use-modal' |
||||
|
||||
export const UserModal = defineComponent({ |
||||
name: 'user-modal', |
||||
setup() { |
||||
const { t } = useI18n() |
||||
const sharedState = |
||||
inject(UserModalSharedStateKey) || useSharedUserModalState() |
||||
const modalState = useModal(sharedState) |
||||
|
||||
return { |
||||
t, |
||||
...modalState |
||||
} |
||||
}, |
||||
render() { |
||||
const { t } = this |
||||
return ( |
||||
<Modal |
||||
show={this.show} |
||||
title={this.titleMap?.[this.mode || 'add']} |
||||
onCancel={this.onModalCancel} |
||||
confirmDisabled={this.optionsLoading} |
||||
confirmLoading={this.confirmLoading} |
||||
onConfirm={this.onConfirm} |
||||
confirmClassName='btn-submit' |
||||
cancelClassName='btn-cancel' |
||||
> |
||||
{{ |
||||
default: () => { |
||||
if (this.mode === 'delete') { |
||||
return ( |
||||
<NAlert type='error' title={t('security.user.delete_confirm')}> |
||||
{t('security.user.delete_confirm_tip')} |
||||
</NAlert> |
||||
) |
||||
} |
||||
if (this.mode === 'auth_project') { |
||||
return ( |
||||
<NTransfer |
||||
virtualScroll |
||||
options={this.projects} |
||||
filterable |
||||
v-model:value={this.authorizedProjects} |
||||
style={{ margin: '0 auto' }} |
||||
/> |
||||
) |
||||
} |
||||
if (this.mode === 'auth_datasource') { |
||||
return ( |
||||
<NTransfer |
||||
virtualScroll |
||||
options={this.datasource} |
||||
filterable |
||||
v-model:value={this.authorizedDatasource} |
||||
style={{ margin: '0 auto' }} |
||||
/> |
||||
) |
||||
} |
||||
if (this.mode === 'auth_udf') { |
||||
return ( |
||||
<NTransfer |
||||
virtualScroll |
||||
options={this.UDFs} |
||||
filterable |
||||
v-model:value={this.authorizedUDF} |
||||
style={{ margin: '0 auto' }} |
||||
/> |
||||
) |
||||
} |
||||
if (this.mode === 'auth_resource') { |
||||
return ( |
||||
<NSpace vertical> |
||||
<NRadioGroup v-model:value={this.resourceType}> |
||||
<NRadioButton key='file' value='file'> |
||||
{t('security.user.file_resource')} |
||||
</NRadioButton> |
||||
<NRadioButton key='udf' value='udf'> |
||||
{t('security.user.udf_resource')} |
||||
</NRadioButton> |
||||
</NRadioGroup> |
||||
<NTreeSelect |
||||
multiple |
||||
cascade |
||||
checkable |
||||
checkStrategy='child' |
||||
defaultExpandAll |
||||
options={this.resourceTree} |
||||
v-model:value={this.authorizedFiles} |
||||
/> |
||||
</NSpace> |
||||
) |
||||
} |
||||
return ( |
||||
<NForm |
||||
ref='formRef' |
||||
model={this.formValues} |
||||
rules={this.formRules} |
||||
labelPlacement='left' |
||||
labelAlign='left' |
||||
labelWidth={80} |
||||
> |
||||
<NFormItem label={t('security.user.username')} path='userName'> |
||||
<NInput |
||||
class='input-username' |
||||
inputProps={{ autocomplete: 'off' }} |
||||
v-model:value={this.formValues.userName} |
||||
/> |
||||
</NFormItem> |
||||
<NFormItem |
||||
label={t('security.user.user_password')} |
||||
path='userPassword' |
||||
> |
||||
<NInput |
||||
class='input-password' |
||||
inputProps={{ autocomplete: 'off' }} |
||||
type='password' |
||||
v-model:value={this.formValues.userPassword} |
||||
/> |
||||
</NFormItem> |
||||
<NFormItem |
||||
label={t('security.user.tenant_code')} |
||||
path='tenantId' |
||||
> |
||||
<NSelect |
||||
class='select-tenant' |
||||
options={this.tenants} |
||||
v-model:value={this.formValues.tenantId} |
||||
/> |
||||
</NFormItem> |
||||
<NFormItem label={t('security.user.queue')} path='queue'> |
||||
<NSelect |
||||
class='select-queue' |
||||
options={this.queues} |
||||
v-model:value={this.formValues.queue} |
||||
/> |
||||
</NFormItem> |
||||
<NFormItem label={t('security.user.email')} path='email'> |
||||
<NInput |
||||
class='input-email' |
||||
v-model:value={this.formValues.email} |
||||
/> |
||||
</NFormItem> |
||||
<NFormItem label={t('security.user.phone')} path='phone'> |
||||
<NInput |
||||
class='input-phone' |
||||
v-model:value={this.formValues.phone} |
||||
/> |
||||
</NFormItem> |
||||
<NFormItem label={t('security.user.state')} path='state'> |
||||
<NRadioGroup v-model:value={this.formValues.state}> |
||||
<NSpace> |
||||
<NRadio value={1} class='radio-state-enable'> |
||||
启用 |
||||
</NRadio> |
||||
<NRadio value={0} class='radio-state-disable'> |
||||
停用 |
||||
</NRadio> |
||||
</NSpace> |
||||
</NRadioGroup> |
||||
</NFormItem> |
||||
</NForm> |
||||
) |
||||
} |
||||
}} |
||||
</Modal> |
||||
) |
||||
} |
||||
}) |
||||
|
||||
export default UserModal |
@ -0,0 +1,20 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
.transfer { |
||||
width: 100%; |
||||
} |
@ -0,0 +1,63 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
import type { |
||||
TableColumns, |
||||
InternalRowData |
||||
} from 'naive-ui/es/data-table/src/interface' |
||||
import { UserReq } from '@/service/modules/users/types' |
||||
export type { UserInfoRes } from '@/service/modules/users/types' |
||||
|
||||
type TUserType = 'GENERAL_USER' | '' |
||||
type TAuthType = |
||||
| 'authorize_project' |
||||
| 'authorize_resource' |
||||
| 'authorize_datasource' |
||||
| 'authorize_udf' |
||||
|
||||
interface IRecord { |
||||
id: number |
||||
userName: string |
||||
userType: TUserType |
||||
tenantCode: string |
||||
queueName: string |
||||
email: string |
||||
phone: string |
||||
state: 0 | 1 |
||||
createTime: string |
||||
updateTime: string |
||||
} |
||||
|
||||
interface IResourceOption { |
||||
id: number |
||||
fullName: string |
||||
type: string |
||||
} |
||||
|
||||
interface IOption { |
||||
value: number |
||||
label: string |
||||
} |
||||
|
||||
export { |
||||
IRecord, |
||||
IResourceOption, |
||||
IOption, |
||||
TAuthType, |
||||
UserReq, |
||||
TableColumns, |
||||
InternalRowData |
||||
} |
@ -0,0 +1,219 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import { h, ref, watch, onMounted, Ref } from 'vue' |
||||
import { useI18n } from 'vue-i18n' |
||||
import { |
||||
NSpace, |
||||
NTooltip, |
||||
NButton, |
||||
NIcon, |
||||
NTag, |
||||
NDropdown, |
||||
NPopconfirm |
||||
} from 'naive-ui' |
||||
import { EditOutlined, DeleteOutlined, UserOutlined } from '@vicons/antd' |
||||
import { TableColumns, InternalRowData } from './types' |
||||
|
||||
export function useColumns(onCallback: Function) { |
||||
const { t } = useI18n() |
||||
|
||||
const columnsRef = ref([]) as Ref<TableColumns> |
||||
|
||||
const createColumns = () => { |
||||
columnsRef.value = [ |
||||
{ |
||||
title: '#', |
||||
key: 'index', |
||||
render: (rowData: InternalRowData, rowIndex: number) => rowIndex + 1 |
||||
}, |
||||
{ |
||||
title: t('security.user.username'), |
||||
key: 'userName' |
||||
}, |
||||
{ |
||||
title: t('security.user.user_type'), |
||||
key: 'userType', |
||||
render: (rowData: InternalRowData) => |
||||
rowData.userType === 'GENERAL_USER' |
||||
? t('security.user.ordinary_user') |
||||
: t('security.user.administrator') |
||||
}, |
||||
{ |
||||
title: t('security.user.tenant_code'), |
||||
key: 'tenantCode' |
||||
}, |
||||
{ |
||||
title: t('security.user.queue'), |
||||
key: 'queue' |
||||
}, |
||||
{ |
||||
title: t('security.user.email'), |
||||
key: 'email' |
||||
}, |
||||
{ |
||||
title: t('security.user.phone'), |
||||
key: 'phone' |
||||
}, |
||||
{ |
||||
title: t('security.user.state'), |
||||
key: 'state', |
||||
render: (rowData: any, unused: number) => |
||||
h( |
||||
NTag, |
||||
{ type: rowData.state === 1 ? 'success' : 'error' }, |
||||
{ |
||||
default: () => |
||||
t( |
||||
`security.user.state_${ |
||||
rowData.state === 1 ? 'enabled' : 'disabled' |
||||
}` |
||||
) |
||||
} |
||||
) |
||||
}, |
||||
{ |
||||
title: t('security.user.create_time'), |
||||
key: 'createTime' |
||||
}, |
||||
{ |
||||
title: t('security.user.update_time'), |
||||
key: 'updateTime' |
||||
}, |
||||
{ |
||||
title: t('security.user.operation'), |
||||
key: 'operation', |
||||
render: (rowData: any, unused: number) => { |
||||
return h(NSpace, null, { |
||||
default: () => [ |
||||
h( |
||||
NDropdown, |
||||
{ |
||||
trigger: 'click', |
||||
options: [ |
||||
{ |
||||
label: t('security.user.project'), |
||||
key: 'authorize_project' |
||||
}, |
||||
{ |
||||
label: t('security.user.resource'), |
||||
key: 'authorize_resource' |
||||
}, |
||||
{ |
||||
label: t('security.user.datasource'), |
||||
key: 'authorize_datasource' |
||||
}, |
||||
{ label: t('security.user.udf'), key: 'authorize_udf' } |
||||
], |
||||
onSelect: (key) => |
||||
void onCallback({ rowData, key }, 'authorize') |
||||
}, |
||||
() => |
||||
h( |
||||
NTooltip, |
||||
{ |
||||
trigger: 'hover' |
||||
}, |
||||
{ |
||||
trigger: () => |
||||
h( |
||||
NButton, |
||||
{ |
||||
circle: true, |
||||
type: 'warning', |
||||
size: 'small', |
||||
class: 'authorize' |
||||
}, |
||||
{ |
||||
icon: () => h(NIcon, null, () => h(UserOutlined)) |
||||
} |
||||
), |
||||
default: () => t('security.user.authorize') |
||||
} |
||||
) |
||||
), |
||||
h( |
||||
NTooltip, |
||||
{ trigger: 'hover' }, |
||||
{ |
||||
trigger: () => |
||||
h( |
||||
NButton, |
||||
{ |
||||
circle: true, |
||||
type: 'info', |
||||
size: 'small', |
||||
onClick: () => void onCallback({ rowData }, 'edit') |
||||
}, |
||||
() => h(NIcon, null, () => h(EditOutlined)) |
||||
), |
||||
default: () => t('security.user.edit') |
||||
} |
||||
), |
||||
h( |
||||
NPopconfirm, |
||||
{ |
||||
onPositiveClick: () => void onCallback({ rowData }, 'delete') |
||||
}, |
||||
{ |
||||
trigger: () => |
||||
h( |
||||
NTooltip, |
||||
{}, |
||||
{ |
||||
trigger: () => |
||||
h( |
||||
NButton, |
||||
{ |
||||
circle: true, |
||||
type: 'error', |
||||
size: 'small', |
||||
class: 'delete' |
||||
}, |
||||
{ |
||||
icon: () => |
||||
h(NIcon, null, { |
||||
default: () => h(DeleteOutlined) |
||||
}) |
||||
} |
||||
), |
||||
default: () => t('security.user.delete') |
||||
} |
||||
), |
||||
default: () => t('security.user.delete_confirm') |
||||
} |
||||
) |
||||
] |
||||
}) |
||||
} |
||||
} |
||||
] |
||||
} |
||||
|
||||
onMounted(() => { |
||||
createColumns() |
||||
}) |
||||
|
||||
watch(useI18n().locale, () => { |
||||
createColumns() |
||||
}) |
||||
|
||||
return { |
||||
columnsRef, |
||||
createColumns |
||||
} |
||||
} |
@ -0,0 +1,112 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import { reactive, onMounted } from 'vue' |
||||
import { queryUserList, delUserById } from '@/service/modules/users' |
||||
import { format } from 'date-fns' |
||||
import { parseTime } from '@/utils/common' |
||||
import type { IRecord, TAuthType } from './types' |
||||
|
||||
export function useTable() { |
||||
const state = reactive({ |
||||
page: 1, |
||||
pageSize: 10, |
||||
itemCount: 0, |
||||
searchVal: '', |
||||
list: [], |
||||
loading: false, |
||||
currentRecord: {} as IRecord | null, |
||||
authorizeType: 'authorize_project' as TAuthType, |
||||
detailModalShow: false, |
||||
authorizeModalShow: false |
||||
}) |
||||
|
||||
const getList = async () => { |
||||
if (state.loading) return |
||||
state.loading = true |
||||
|
||||
const { totalList, total } = await queryUserList({ |
||||
pageNo: state.page, |
||||
pageSize: state.pageSize, |
||||
searchVal: state.searchVal |
||||
}) |
||||
state.loading = false |
||||
if (!totalList) throw Error() |
||||
state.list = totalList.map((record: IRecord) => { |
||||
record.createTime = record.createTime |
||||
? format(parseTime(record.createTime), 'yyyy-MM-dd HH:mm:ss') |
||||
: '' |
||||
record.updateTime = record.updateTime |
||||
? format(parseTime(record.updateTime), 'yyyy-MM-dd HH:mm:ss') |
||||
: '' |
||||
return record |
||||
}) |
||||
|
||||
state.itemCount = total |
||||
} |
||||
|
||||
const updateList = () => { |
||||
if (state.list.length === 1 && state.page > 1) { |
||||
--state.page |
||||
} |
||||
getList() |
||||
} |
||||
|
||||
const deleteUser = async (userId: number) => { |
||||
await delUserById({ id: userId }) |
||||
updateList() |
||||
} |
||||
|
||||
const onOperationClick = ( |
||||
data: { rowData: IRecord; key?: TAuthType }, |
||||
type: 'authorize' | 'edit' | 'delete' |
||||
) => { |
||||
state.currentRecord = data.rowData |
||||
if (type === 'edit') { |
||||
state.detailModalShow = true |
||||
} |
||||
if (type === 'authorize' && data.key) { |
||||
state.authorizeModalShow = true |
||||
state.authorizeType = data.key |
||||
} |
||||
if (type === 'delete') { |
||||
deleteUser(data.rowData.id) |
||||
} |
||||
} |
||||
|
||||
// const deleteRecord = async (id: number) => {
|
||||
// const ignored = await deleteAlertPluginInstance(id)
|
||||
// updateList()
|
||||
// }
|
||||
|
||||
const changePage = (page: number) => { |
||||
state.page = page |
||||
getList() |
||||
} |
||||
|
||||
const changePageSize = (pageSize: number) => { |
||||
state.page = 1 |
||||
state.pageSize = pageSize |
||||
getList() |
||||
} |
||||
|
||||
onMounted(() => { |
||||
getList() |
||||
}) |
||||
|
||||
return { state, changePage, changePageSize, updateList, onOperationClick } |
||||
} |
@ -1,255 +0,0 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import { ref, watch, onBeforeMount, computed } from 'vue' |
||||
import { NSpace, NTooltip, NButton, NIcon, NTag, NDropdown } from 'naive-ui' |
||||
import { EditOutlined, DeleteOutlined, UserOutlined } from '@vicons/antd' |
||||
import { queryUserList } from '@/service/modules/users' |
||||
import { useI18n } from 'vue-i18n' |
||||
import { Mode } from './components/use-modal' |
||||
|
||||
type UseTableProps = { |
||||
onEdit: (user: any, mode: Mode) => void |
||||
onDelete: (user: any) => void |
||||
} |
||||
|
||||
function useColumns({ onEdit, onDelete }: UseTableProps) { |
||||
const { t } = useI18n() |
||||
const columns = computed(() => |
||||
[ |
||||
{ |
||||
title: '#', |
||||
key: 'index', |
||||
width: 80, |
||||
render: (rowData: any, rowIndex: number) => rowIndex + 1 |
||||
}, |
||||
{ |
||||
title: t('security.user.username'), |
||||
key: 'userName', |
||||
className: 'name' |
||||
}, |
||||
{ |
||||
title: t('security.user.tenant_code'), |
||||
key: 'tenantCode' |
||||
}, |
||||
{ |
||||
title: t('security.user.queue'), |
||||
key: 'queue' |
||||
}, |
||||
{ |
||||
title: t('security.user.email'), |
||||
key: 'email' |
||||
}, |
||||
{ |
||||
title: t('security.user.phone'), |
||||
key: 'phone' |
||||
}, |
||||
{ |
||||
title: t('security.user.state'), |
||||
key: 'state', |
||||
render: (rowData: any, unused: number) => { |
||||
return rowData.state === 1 ? ( |
||||
<NTag type='success'>{t('security.user.state_enabled')}</NTag> |
||||
) : ( |
||||
<NTag type='error'>{t('security.user.state_disabled')}</NTag> |
||||
) |
||||
} |
||||
}, |
||||
{ |
||||
title: t('security.user.create_time'), |
||||
key: 'createTime', |
||||
width: 200 |
||||
}, |
||||
{ |
||||
title: t('security.user.update_time'), |
||||
key: 'updateTime', |
||||
width: 200 |
||||
}, |
||||
{ |
||||
title: t('security.user.operation'), |
||||
key: 'operation', |
||||
fixed: 'right', |
||||
width: 140, |
||||
render: (rowData: any, unused: number) => { |
||||
return ( |
||||
<NSpace> |
||||
<NDropdown |
||||
trigger='click' |
||||
options={[ |
||||
{ label: t('security.user.project'), key: 'auth_project' }, |
||||
{ label: t('security.user.resource'), key: 'auth_resource' }, |
||||
{ |
||||
label: t('security.user.datasource'), |
||||
key: 'auth_datasource' |
||||
}, |
||||
{ label: t('security.user.udf'), key: 'auth_udf' } |
||||
]} |
||||
onSelect={(key) => { |
||||
onEdit(rowData, key) |
||||
}} |
||||
> |
||||
<NTooltip trigger='hover'> |
||||
{{ |
||||
trigger: () => ( |
||||
<NButton |
||||
circle |
||||
type='warning' |
||||
size='small' |
||||
class='authorize' |
||||
> |
||||
{{ |
||||
icon: () => ( |
||||
<NIcon> |
||||
<UserOutlined /> |
||||
</NIcon> |
||||
) |
||||
}} |
||||
</NButton> |
||||
), |
||||
default: () => t('security.user.authorize') |
||||
}} |
||||
</NTooltip> |
||||
</NDropdown> |
||||
<NTooltip trigger='hover'> |
||||
{{ |
||||
trigger: () => ( |
||||
<NButton |
||||
circle |
||||
type='info' |
||||
size='small' |
||||
class='edit' |
||||
onClick={() => { |
||||
onEdit(rowData, 'edit') |
||||
}} |
||||
> |
||||
{{ |
||||
icon: () => ( |
||||
<NIcon> |
||||
<EditOutlined /> |
||||
</NIcon> |
||||
) |
||||
}} |
||||
</NButton> |
||||
), |
||||
default: () => t('security.user.edit') |
||||
}} |
||||
</NTooltip> |
||||
<NTooltip trigger='hover'> |
||||
{{ |
||||
trigger: () => ( |
||||
<NButton |
||||
circle |
||||
type='error' |
||||
size='small' |
||||
class='delete' |
||||
onClick={() => { |
||||
onDelete(rowData) |
||||
}} |
||||
> |
||||
{{ |
||||
icon: () => ( |
||||
<NIcon> |
||||
<DeleteOutlined /> |
||||
</NIcon> |
||||
) |
||||
}} |
||||
</NButton> |
||||
), |
||||
default: () => t('security.user.delete') |
||||
}} |
||||
</NTooltip> |
||||
</NSpace> |
||||
) |
||||
} |
||||
} |
||||
].map((d: any) => ({ ...d, width: d.width || 160 })) |
||||
) |
||||
|
||||
const scrollX = columns.value.reduce((p, c) => p + c.width, 0) |
||||
|
||||
return { |
||||
columns, |
||||
scrollX |
||||
} |
||||
} |
||||
|
||||
export function useTable(props: UseTableProps) { |
||||
const page = ref(1) |
||||
const pageCount = ref(0) |
||||
const pageSize = ref(10) |
||||
const searchInputVal = ref() |
||||
const searchVal = ref('') |
||||
const pageSizes = [10, 30, 50] |
||||
const userListLoading = ref(false) |
||||
const userList = ref([]) |
||||
const { columns, scrollX } = useColumns(props) |
||||
|
||||
const getUserList = () => { |
||||
userListLoading.value = true |
||||
queryUserList({ |
||||
pageNo: page.value, |
||||
pageSize: pageSize.value, |
||||
searchVal: searchVal.value |
||||
}) |
||||
.then((res: any) => { |
||||
userList.value = res?.totalList || [] |
||||
pageCount.value = res?.totalPage || 0 |
||||
}) |
||||
.finally(() => { |
||||
userListLoading.value = false |
||||
}) |
||||
} |
||||
|
||||
const resetPage = () => { |
||||
page.value = 1 |
||||
} |
||||
|
||||
const onSearchValOk = () => { |
||||
resetPage() |
||||
searchVal.value = searchInputVal.value |
||||
} |
||||
|
||||
const onSearchValClear = () => { |
||||
resetPage() |
||||
searchVal.value = '' |
||||
} |
||||
|
||||
onBeforeMount(() => { |
||||
getUserList() |
||||
}) |
||||
|
||||
watch([page, pageSize, searchVal], () => { |
||||
getUserList() |
||||
}) |
||||
|
||||
return { |
||||
userList, |
||||
userListLoading, |
||||
getUserList, |
||||
page, |
||||
pageCount, |
||||
pageSize, |
||||
searchVal, |
||||
searchInputVal, |
||||
pageSizes, |
||||
columns, |
||||
scrollX, |
||||
onSearchValOk, |
||||
onSearchValClear, |
||||
resetPage |
||||
} |
||||
} |
Loading…
Reference in new issue