-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: 新增用户选择器 通知公告可以指定通知范围 Created-by: kiki1373639299 Author-id: 86659 MR-id: 188386 Commit-by: KAI Merged-by: Charles_7c E2E-issues: Description: <!-- 非常感谢您的 PR!在提交之前,请务必确保您 PR 的代码经过了完整测试,并且通过了代码规范检查。 --> <!-- 在 [] 中输入 x 来勾选) --> ## PR 类型 <!-- 您的 PR 引入了哪种类型的变更? --> <!-- 只支持选择一种类型,如果有多种类型,可以在更新日志中增加 “类型” 列。 --> - [X] 新 feature - [ ] Bug 修复 - [ ] 功能增强 - [ ] 文档变更 - [ ] 代码样式变更 - [ ] 重构 - [ ] 性能改进 - [ ] 单元测试 - [ ] CI/CD - [ ] 其他 ## PR 目的 <!-- 描述一下您的 PR 解决了什么问题。如果可以,请链接到相关 issues。 --> ## 解决方案 <!-- 详细描述您是如何解决的问题 --> ## PR 测试 <!-- 如果可以,请为您的 PR 添加或更新单元测试。 --> <!-- 请描述一下您是如何测试 PR 的。例如:创建/更新单元测试或添加相关的截图。 --> ## Changelog | 模块 | Changelog | Related issues | |-----|-----------| -------------- | | src/api/system | 新增查询用户列表接口 以及通知公告字段类型变更 | | | src/component/UserSelect | 新增用户选择器 | | | src/view/system/notice/add | 适配用户选择器 以及新增通知范围 | | <!-- 如果有多种类型的变更,可以在变更日志表中增加 “类型” 列,该列的值与上方 “PR 类型” 相同。 --> <!-- Related issues 格式为 Closes #<issue号>,或者 Fixes #<issue号>,或者 Resolves #<issue号>。 --> ## 其他信息 <!-- 请描述一下还有哪些注意事项。例如:如果引入了一个不向下兼容的变更,请描述其影响。 --> ## 提交前确认 - [X] PR 代码经过了完整测试,并且通过了代码规范检查 - [ ] 已经完整填写 Changelog,并链接到了相关 issues - [X] PR 代码将要提交到 dev 分支 See merge request: continew/continew-admin-ui!1
- Loading branch information
Showing
7 changed files
with
418 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
238 changes: 238 additions & 0 deletions
238
src/components/UserSelect/component/UserSelectContent.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,238 @@ | ||
<template> | ||
<div class="container"> | ||
<a-row :gutter="16"> | ||
<a-col :span="24" :md="5" class="section"> | ||
<a-input v-model="searchKey" placeholder="请输入部门名称" allow-clear> | ||
<template #prefix> | ||
<icon-search /> | ||
</template> | ||
</a-input> | ||
<a-tree | ||
ref="treeRef" | ||
:data="treeData" | ||
block-node | ||
@select="handleDeptSelect" | ||
/> | ||
</a-col> | ||
|
||
<a-col :span="24" :md="14" class="section"> | ||
<GiTable | ||
v-model:selectedKeys="selectedKeys" | ||
style="min-height: 600px;" | ||
row-key="id" | ||
:data="dataList" | ||
:columns="tableColumns" | ||
:loading="loading" | ||
:scroll="{ x: '100%', y: '100%' }" | ||
:pagination="pagination" | ||
:disabled-tools="['size', 'fullscreen', 'setting', 'refresh']" | ||
:row-selection="{ type: props.multiple ? 'checkbox' : 'radio', showCheckedAll: true }" | ||
@select="onRowSelect" | ||
@select-all="onTableSelectAll" | ||
@refresh="search" | ||
> | ||
<template #top> | ||
<div> | ||
<a-space class="mt-5"> | ||
<a-input v-model="queryForm.description" placeholder="用户名/昵称/描述" /> | ||
<a-button @click="search"> | ||
<template #icon> | ||
<icon-search /> | ||
</template> | ||
</a-button> | ||
<a-button @click="onRefresh"> | ||
<template #icon> | ||
<icon-refresh /> | ||
</template> | ||
</a-button> | ||
</a-space> | ||
</div> | ||
<a-alert class="mt-5"> | ||
<template v-if="selectedKeys.length > 0"> | ||
已选中{{ selectedKeys.length }}条记录(可跨页) | ||
</template> | ||
<template v-else> | ||
未选中任何项目 | ||
</template> | ||
<template v-if="selectedKeys.length > 0" #action> | ||
<a-link @click="onClearSelected">清空</a-link> | ||
</template> | ||
</a-alert> | ||
</template> | ||
|
||
<template #status="{ record }"> | ||
<GiCellStatus :status="record.status" /> | ||
</template> | ||
</GiTable> | ||
</a-col> | ||
|
||
<a-col :span="24" :md="5" class="section"> | ||
<a-card title="已选用户"> | ||
<a-table :columns="rightColumn" :data="selectedData"> | ||
<template #action="{ record }"> | ||
<a-button @click="handleDeleteSelectUser(record)"> | ||
<icon-delete /> | ||
</a-button> | ||
</template> | ||
</a-table> | ||
</a-card> | ||
</a-col> | ||
</a-row> | ||
</div> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import type { TreeNodeData } from '@arco-design/web-vue' | ||
import { useDept } from '@/hooks/app' | ||
import { useTable } from '@/hooks' | ||
import { listAllUser, listUser } from '@/apis' | ||
import type { UserItem, UserSelectPropType } from '@/components/UserSelect/type' | ||
const props = withDefaults(defineProps<UserSelectPropType & { selectedUsers: string | string[] }>(), { | ||
multiple: false, | ||
selectedUsers: () => [] | ||
}) | ||
const emit = defineEmits(['update:selectedUsers']) | ||
// 查询表单引用 | ||
const queryForm = ref({ description: '' }) | ||
// 部门树引用 | ||
const treeRef = ref() | ||
const selectedKeys = ref<string[]>([]) | ||
const selectedDeptId = ref<string>('') | ||
const selectedData = ref<any[]>([]) | ||
const { tableData: dataList, loading, pagination, search } = useTable( | ||
(page) => listUser({ ...queryForm.value, deptId: selectedDeptId.value, sort: [], ...page }), | ||
{ immediate: false, formatResult: (data) => data.map((i) => ({ ...i, disabled: false })) } | ||
) | ||
// 刷新表单 | ||
const onRefresh = () => { | ||
queryForm.value.description = '' | ||
search() | ||
} | ||
// 使用 useDept 钩子获取部门列表数据 | ||
const { deptList, getDeptList } = useDept({ | ||
onSuccess: () => { | ||
nextTick(() => treeRef.value?.expandAll(true)) | ||
} | ||
}) | ||
// 部门树过滤函数 | ||
const deptTreeSearch = (keyword: string, data: TreeNodeData[]): TreeNodeData[] => { | ||
return data | ||
.map((item) => ({ | ||
...item, | ||
children: item.children ? deptTreeSearch(keyword, item.children) : [] | ||
})) | ||
.filter( | ||
(item) => | ||
item.title?.toLowerCase().includes(keyword.toLowerCase()) || item.children?.length | ||
) | ||
} | ||
// 过滤树数据 | ||
const searchKey = ref('') | ||
const treeData = computed(() => { | ||
return searchKey.value ? deptTreeSearch(searchKey.value, deptList.value) : deptList.value | ||
}) | ||
// 表格列定义 | ||
const tableColumns = [ | ||
{ title: '昵称', dataIndex: 'nickname' }, | ||
{ title: '部门', dataIndex: 'deptName' }, | ||
{ title: '角色', dataIndex: 'roleNames' }, | ||
{ title: '手机号', dataIndex: 'phone' }, | ||
{ title: '邮箱', dataIndex: 'email' }, | ||
{ title: '状态', dataIndex: 'status', slotName: 'status' } | ||
] | ||
// 右侧已选用户列定义 | ||
const rightColumn = [ | ||
{ title: '昵称', dataIndex: 'nickname' }, | ||
{ title: '操作', dataIndex: 'action', slotName: 'action' } | ||
] | ||
// 处理部门选择 | ||
const handleDeptSelect = (keys: Array<any>) => { | ||
selectedDeptId.value = keys[0] || '' | ||
search() | ||
} | ||
const emitSelectedUsers = () => { | ||
emit('update:selectedUsers', selectedKeys.value) | ||
} | ||
// 从选中列表中移除用户 | ||
const handleDeleteSelectUser = (user: UserItem) => { | ||
selectedData.value = selectedData.value.filter((item) => item.id !== user.id) | ||
selectedKeys.value = selectedData.value.map((item) => item.id) | ||
emitSelectedUsers() | ||
} | ||
// 行选择事件 | ||
const onRowSelect = (rowKeys: string[], rowKey: string, record: UserItem) => { | ||
selectedData.value = props.multiple | ||
? rowKeys.includes(rowKey) | ||
? [...selectedData.value, record] | ||
: selectedData.value.filter((item) => item.id !== rowKey) | ||
: [record] | ||
selectedKeys.value = selectedData.value.map((item) => item.id) | ||
emitSelectedUsers() | ||
} | ||
// 全选事件 | ||
const onTableSelectAll = (checked: boolean) => { | ||
selectedData.value = checked | ||
? [...selectedData.value, ...dataList.value.filter((item) => !selectedKeys.value.includes(item.id))] | ||
: [] | ||
selectedKeys.value = selectedData.value.map((item) => item.id) | ||
emitSelectedUsers() | ||
} | ||
// 清空所有选中数据 | ||
const onClearSelected = () => { | ||
selectedData.value = [] | ||
selectedKeys.value = [] | ||
emitSelectedUsers() | ||
} | ||
// 初始化函数 | ||
const init = (selectUsers: string[]) => { | ||
getDeptList() | ||
search() | ||
if (selectUsers && selectUsers.length > 0) { | ||
// admin的id是number 不是string 类型 所以处理一下 | ||
listAllUser({ userIds: selectUsers }).then((dataList) => { | ||
selectedData.value = dataList.data.map((data) => { | ||
return { ...data, id: `${data.id}` } | ||
}) | ||
}) | ||
} | ||
} | ||
watch(() => props.selectedUsers, (newValue) => { | ||
const newSelectedKeys = Array.isArray(newValue) ? newValue : [newValue] | ||
selectedKeys.value = newSelectedKeys.filter(Boolean) | ||
selectedData.value = dataList.value.filter((item) => selectedKeys.value.includes(item.id)) | ||
}, { immediate: true }) | ||
defineExpose({ init, onClearSelected }) | ||
</script> | ||
|
||
<style scoped> | ||
.container { | ||
padding: 20px; | ||
} | ||
.section { | ||
margin-bottom: 20px; | ||
} | ||
.mt-5 { | ||
margin-top: 5px; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
<template> | ||
<div> | ||
<div style="display: flex;"> | ||
<a-select | ||
v-model="selectedUsers" | ||
:allow-clear="true" | ||
:multiple="props.multiple" | ||
:max-tag-count="4" | ||
:field-names="{ value: 'id', label: 'nickname' }" | ||
:options="options" | ||
@change="handleSelectChange" | ||
/> | ||
<a-tooltip content="选择用户"> | ||
<a-button @click="onOpen"> | ||
<template #icon> | ||
<icon-plus /> | ||
</template> | ||
</a-button> | ||
</a-tooltip> | ||
</div> | ||
<a-modal | ||
v-model:visible="visible" | ||
title="用户选择" | ||
:width="width >= 1350 ? 1350 : '100%'" | ||
:esc-to-close="true" | ||
@ok="handleModalOk" | ||
> | ||
<UserSelectContent | ||
ref="userSelectContentRef" | ||
:value="selectedUsers" | ||
:multiple="props.multiple" | ||
:selected-users="selectedUsers" | ||
@update:selected-users="updateSelectedUsers" | ||
/> | ||
</a-modal> | ||
</div> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import { useWindowSize } from '@vueuse/core' | ||
import UserSelectContent from './component/UserSelectContent.vue' | ||
import { type UserResp, listAllUser } from '@/apis' | ||
import type { UserSelectPropType } from '@/components/UserSelect/type' | ||
const props = withDefaults(defineProps<UserSelectPropType>(), { | ||
multiple: false, // 是否支持多选 | ||
value: '' | ||
}) | ||
const emit = defineEmits(['update:value']) | ||
const visible = ref<boolean>(false) // 控制弹窗显示的状态 | ||
const { width } = useWindowSize() // 获取窗口的宽度,用于设置弹窗宽度 | ||
const options = ref<UserResp[]>([]) // 保存用户选项列表 | ||
const userSelectContentRef = ref() // 引用 UserSelectContent 组件实例 | ||
const selectedUsers = ref<string[]>([]) // 保存已选择的用户 | ||
// 打开用户选择弹窗 | ||
const onOpen = () => { | ||
visible.value = true | ||
userSelectContentRef.value.init(selectedUsers.value) // 调用子组件的初始化方法 | ||
} | ||
// 发出数据更新事件 | ||
const emitDataChange = () => { | ||
emit('update:value', selectedUsers.value.filter(Boolean)) // 发出更新事件 | ||
} | ||
// 处理用户选择变更事件 | ||
const handleSelectChange = (value: any) => { | ||
selectedUsers.value = props.multiple ? value : [...value] | ||
emitDataChange() // 每次选择变化时发出更新事件 | ||
} | ||
// 更新已选择的用户列表 | ||
const updateSelectedUsers = (users: string[]) => { | ||
selectedUsers.value = users | ||
emitDataChange() // 每次选择变化时发出更新事件 | ||
} | ||
// 弹窗确认按钮点击事件 | ||
const handleModalOk = () => { | ||
emitDataChange() // 确认时发出数据更新事件 | ||
visible.value = false // 关闭弹窗 | ||
} | ||
// 组件挂载后初始化用户列表 | ||
onMounted(async () => { | ||
const { data } = await listAllUser({}) // 获取所有用户 | ||
options.value = data.map((user) => { | ||
user.id = String(user.id) | ||
user.disabled = false // 初始化时设置用户未被禁用 | ||
return user | ||
}) | ||
// 初始化选择的用户 | ||
selectedUsers.value = Array.isArray(props.value) ? props.value : props.value.split(',') | ||
}) | ||
</script> | ||
|
||
<style scoped> | ||
:deep(.arco-input-append) { | ||
padding: 0; | ||
.arco-btn { | ||
border-top-left-radius: 0; | ||
border-bottom-left-radius: 0; | ||
border: 1px solid transparent; | ||
} | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
export interface UserSelectPropType { | ||
multiple: boolean | ||
value: string | string[] | ||
} | ||
export interface UserItem { | ||
id: string | ||
nickname: string | ||
deptName: string | ||
roleNames: string | ||
phone: string | ||
email: string | ||
status: number | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.