lilyzhou
3 years ago
committed by
GitHub
9 changed files with 837 additions and 7 deletions
@ -0,0 +1,277 @@
|
||||
/* |
||||
* 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 |
||||
} from '@/service/modules/users' |
||||
import regexUtils from '@/utils/regex' |
||||
export type Mode = 'add' | 'edit' | 'delete' |
||||
|
||||
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 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 titleMap: Record<Mode, string> = { |
||||
add: t('security.user.create_user'), |
||||
edit: t('security.user.update_user'), |
||||
delete: t('security.user.delete_user') |
||||
} |
||||
|
||||
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 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 onConfirm = () => { |
||||
if (mode.value === 'delete') { |
||||
onDelete() |
||||
} else { |
||||
formRef.value.validate((errors: any) => { |
||||
if (!errors) { |
||||
user.value ? onUpdateUser() : onCreateUser() |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
const onModalCancel = () => { |
||||
show.value = false |
||||
} |
||||
|
||||
watch([show, mode], () => { |
||||
show.value && mode.value !== 'delete' && prepareOptions() |
||||
}) |
||||
|
||||
watch([queues, tenants, user], () => { |
||||
setFormValues() |
||||
}) |
||||
|
||||
return { |
||||
show, |
||||
mode, |
||||
user, |
||||
titleMap, |
||||
onModalCancel, |
||||
formRef, |
||||
formValues, |
||||
formRules, |
||||
tenants, |
||||
queues, |
||||
optionsLoading, |
||||
onConfirm, |
||||
confirmLoading |
||||
} |
||||
} |
@ -0,0 +1,133 @@
|
||||
/* |
||||
* 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, |
||||
NSpace, |
||||
NAlert |
||||
} 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} |
||||
confirmLoading={this.confirmLoading} |
||||
onConfirm={this.onConfirm} |
||||
> |
||||
{{ |
||||
default: () => { |
||||
if (this.mode === 'delete') { |
||||
return ( |
||||
<NAlert type='error' title={t('security.user.delete_confirm')}> |
||||
{t('security.user.delete_confirm_tip')} |
||||
</NAlert> |
||||
) |
||||
} |
||||
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 |
||||
inputProps={{ autocomplete: 'off' }} |
||||
v-model:value={this.formValues.userName} |
||||
/> |
||||
</NFormItem> |
||||
<NFormItem |
||||
label={t('security.user.user_password')} |
||||
path='userPassword' |
||||
> |
||||
<NInput |
||||
inputProps={{ autocomplete: 'off' }} |
||||
type='password' |
||||
v-model:value={this.formValues.userPassword} |
||||
/> |
||||
</NFormItem> |
||||
<NFormItem |
||||
label={t('security.user.tenant_code')} |
||||
path='tenantId' |
||||
> |
||||
<NSelect |
||||
options={this.tenants} |
||||
v-model:value={this.formValues.tenantId} |
||||
/> |
||||
</NFormItem> |
||||
<NFormItem label={t('security.user.queue')} path='queue'> |
||||
<NSelect |
||||
options={this.queues} |
||||
v-model:value={this.formValues.queue} |
||||
/> |
||||
</NFormItem> |
||||
<NFormItem label={t('security.user.email')} path='email'> |
||||
<NInput v-model:value={this.formValues.email} /> |
||||
</NFormItem> |
||||
<NFormItem label={t('security.user.phone')} path='phone'> |
||||
<NInput 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}>启用</NRadio> |
||||
<NRadio value={0}>停用</NRadio> |
||||
</NSpace> |
||||
</NRadioGroup> |
||||
</NFormItem> |
||||
</NForm> |
||||
) |
||||
} |
||||
}} |
||||
</Modal> |
||||
) |
||||
} |
||||
}) |
||||
|
||||
export default UserModal |
@ -0,0 +1,144 @@
|
||||
/* |
||||
* 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, provide } from 'vue' |
||||
import { |
||||
NCard, |
||||
NButton, |
||||
NInputGroup, |
||||
NInput, |
||||
NIcon, |
||||
NSpace, |
||||
NGrid, |
||||
NGridItem, |
||||
NDataTable, |
||||
NPagination, |
||||
NSkeleton |
||||
} from 'naive-ui' |
||||
import { useI18n } from 'vue-i18n' |
||||
import { SearchOutlined } from '@vicons/antd' |
||||
import { useTable } from './use-table' |
||||
import UserModal from './components/user-modal' |
||||
import { |
||||
useSharedUserModalState, |
||||
UserModalSharedStateKey, |
||||
Mode |
||||
} from './components/use-modal' |
||||
|
||||
const UsersManage = defineComponent({ |
||||
name: 'user-manage', |
||||
setup() { |
||||
const { t } = useI18n() |
||||
const { show, mode, user } = useSharedUserModalState() |
||||
const tableState = useTable({ |
||||
onEdit: (u) => { |
||||
show.value = true |
||||
mode.value = 'edit' |
||||
user.value = u |
||||
}, |
||||
onDelete: (u) => { |
||||
show.value = true |
||||
mode.value = 'delete' |
||||
user.value = u |
||||
} |
||||
}) |
||||
|
||||
const onSuccess = (mode: Mode) => { |
||||
if (mode === 'add') { |
||||
tableState.resetPage() |
||||
} |
||||
tableState.getUserList() |
||||
} |
||||
|
||||
const onAddUser = () => { |
||||
show.value = true |
||||
mode.value = 'add' |
||||
user.value = undefined |
||||
} |
||||
|
||||
provide(UserModalSharedStateKey, { show, mode, user, onSuccess }) |
||||
|
||||
return { |
||||
t, |
||||
onAddUser, |
||||
...tableState |
||||
} |
||||
}, |
||||
render() { |
||||
const { t, onSearchValOk, onSearchValClear, userListLoading } = this |
||||
return ( |
||||
<> |
||||
<NGrid cols={1} yGap={16}> |
||||
<NGridItem> |
||||
<NCard> |
||||
<NSpace justify='space-between'> |
||||
<NButton onClick={this.onAddUser} type='primary'> |
||||
{t('security.user.create_user')} |
||||
</NButton> |
||||
<NInputGroup> |
||||
<NInput |
||||
v-model:value={this.searchInputVal} |
||||
clearable |
||||
onClear={onSearchValClear} |
||||
onKeyup={(e) => { |
||||
if (e.key === 'Enter') { |
||||
onSearchValOk() |
||||
} |
||||
}} |
||||
/> |
||||
<NButton type='primary' onClick={onSearchValOk}> |
||||
<NIcon> |
||||
<SearchOutlined /> |
||||
</NIcon> |
||||
</NButton> |
||||
</NInputGroup> |
||||
</NSpace> |
||||
</NCard> |
||||
</NGridItem> |
||||
<NGridItem> |
||||
<NCard> |
||||
{userListLoading ? ( |
||||
<NSkeleton text repeat={6}></NSkeleton> |
||||
) : ( |
||||
<NSpace v-show={!userListLoading} vertical size={20}> |
||||
<NDataTable |
||||
columns={this.columns} |
||||
data={this.userList} |
||||
scrollX={this.scrollX} |
||||
bordered={false} |
||||
/> |
||||
<NSpace justify='center'> |
||||
<NPagination |
||||
v-model:page={this.page} |
||||
v-model:page-size={this.pageSize} |
||||
pageCount={this.pageCount} |
||||
pageSizes={this.pageSizes} |
||||
showSizePicker |
||||
/> |
||||
</NSpace> |
||||
</NSpace> |
||||
)} |
||||
</NCard> |
||||
</NGridItem> |
||||
</NGrid> |
||||
<UserModal /> |
||||
</> |
||||
) |
||||
} |
||||
}) |
||||
|
||||
export default UsersManage |
@ -0,0 +1,212 @@
|
||||
/* |
||||
* 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 } from 'vue' |
||||
import { NSpace, NTooltip, NButton, NIcon, NTag } from 'naive-ui' |
||||
import { EditOutlined, DeleteOutlined } from '@vicons/antd' |
||||
import { queryUserList } from '@/service/modules/users' |
||||
import { useI18n } from 'vue-i18n' |
||||
|
||||
type UseTableProps = { |
||||
onEdit: (user: any) => void |
||||
onDelete: (user: any) => void |
||||
} |
||||
|
||||
function useColumns({ onEdit, onDelete }: UseTableProps) { |
||||
const { t } = useI18n() |
||||
const columns: any[] = [ |
||||
{ |
||||
title: t('security.user.index'), |
||||
key: 'index', |
||||
width: 80, |
||||
render: (rowData: any, rowIndex: number) => rowIndex + 1 |
||||
}, |
||||
{ |
||||
title: t('security.user.username'), |
||||
key: 'userName' |
||||
}, |
||||
{ |
||||
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, rowIndex: number) => { |
||||
return rowData.state === 1 ? ( |
||||
<NTag type='success'>启用</NTag> |
||||
) : ( |
||||
<NTag type='error'>停用</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: 120, |
||||
render: (rowData: any, rowIndex: number) => { |
||||
return ( |
||||
<NSpace> |
||||
<NTooltip trigger='hover'> |
||||
{{ |
||||
trigger: () => ( |
||||
<NButton |
||||
circle |
||||
type='info' |
||||
size='small' |
||||
onClick={() => { |
||||
onEdit(rowData) |
||||
}} |
||||
> |
||||
{{ |
||||
icon: () => ( |
||||
<NIcon> |
||||
<EditOutlined /> |
||||
</NIcon> |
||||
) |
||||
}} |
||||
</NButton> |
||||
), |
||||
default: () => t('security.user.edit') |
||||
}} |
||||
</NTooltip> |
||||
<NTooltip trigger='hover'> |
||||
{{ |
||||
trigger: () => ( |
||||
<NButton |
||||
circle |
||||
type='error' |
||||
size='small' |
||||
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.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