mirror of
				https://github.com/continew-org/continew-admin-ui.git
				synced 2025-10-31 22:57:15 +08:00 
			
		
		
		
	merge dev into dev
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
This commit is contained in:
		| @@ -40,6 +40,7 @@ export interface UserQuery { | |||||||
|   createTime?: Array<string> |   createTime?: Array<string> | ||||||
|   deptId?: string |   deptId?: string | ||||||
|   sort: Array<string> |   sort: Array<string> | ||||||
|  |   userIds?: Array<string> | ||||||
| } | } | ||||||
|  |  | ||||||
| export interface UserPageQuery extends UserQuery, PageQuery { | export interface UserPageQuery extends UserQuery, PageQuery { | ||||||
| @@ -190,6 +191,8 @@ export interface NoticeResp { | |||||||
|   type: string |   type: string | ||||||
|   effectiveTime: string |   effectiveTime: string | ||||||
|   terminateTime: string |   terminateTime: string | ||||||
|  |   noticeScope: number | ||||||
|  |   noticeUsers: Array<string> | ||||||
|   createUserString: string |   createUserString: string | ||||||
|   createTime: string |   createTime: string | ||||||
|   updateUserString: string |   updateUserString: string | ||||||
|   | |||||||
| @@ -9,6 +9,9 @@ const BASE_URL = '/system/user' | |||||||
| export function listUser(query: T.UserPageQuery) { | export function listUser(query: T.UserPageQuery) { | ||||||
|   return http.get<PageRes<T.UserResp[]>>(`${BASE_URL}`, query) |   return http.get<PageRes<T.UserResp[]>>(`${BASE_URL}`, query) | ||||||
| } | } | ||||||
|  | export function listAllUser(query: Partial<T.UserPageQuery>) { | ||||||
|  |   return http.get<T.UserResp[]>(`${BASE_URL}/list`, query) | ||||||
|  | } | ||||||
|  |  | ||||||
| /** @desc 查询用户详情 */ | /** @desc 查询用户详情 */ | ||||||
| export function getUser(id: string) { | export function getUser(id: string) { | ||||||
|   | |||||||
							
								
								
									
										238
									
								
								src/components/UserSelect/component/UserSelectContent.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										238
									
								
								src/components/UserSelect/component/UserSelectContent.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -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> | ||||||
							
								
								
									
										110
									
								
								src/components/UserSelect/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								src/components/UserSelect/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -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> | ||||||
							
								
								
									
										13
									
								
								src/components/UserSelect/type.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/components/UserSelect/type.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
|  | } | ||||||
							
								
								
									
										2
									
								
								src/types/components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/types/components.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -48,6 +48,8 @@ declare module 'vue' { | |||||||
|     RouterView: typeof import('vue-router')['RouterView'] |     RouterView: typeof import('vue-router')['RouterView'] | ||||||
|     SecondForm: typeof import('./../components/GenCron/CronForm/component/second-form.vue')['default'] |     SecondForm: typeof import('./../components/GenCron/CronForm/component/second-form.vue')['default'] | ||||||
|     TextCopy: typeof import('./../components/TextCopy/index.vue')['default'] |     TextCopy: typeof import('./../components/TextCopy/index.vue')['default'] | ||||||
|  |     UserSelect: typeof import('./../components/UserSelect/index.vue')['default'] | ||||||
|  |     UserSelectContent: typeof import('./../components/UserSelect/component/UserSelectContent.vue')['default'] | ||||||
|     Verify: typeof import('./../components/Verify/index.vue')['default'] |     Verify: typeof import('./../components/Verify/index.vue')['default'] | ||||||
|     VerifyPoints: typeof import('./../components/Verify/Verify/VerifyPoints.vue')['default'] |     VerifyPoints: typeof import('./../components/Verify/Verify/VerifyPoints.vue')['default'] | ||||||
|     VerifySlide: typeof import('./../components/Verify/Verify/VerifySlide.vue')['default'] |     VerifySlide: typeof import('./../components/Verify/Verify/VerifySlide.vue')['default'] | ||||||
|   | |||||||
| @@ -1,29 +1,33 @@ | |||||||
| <template> | <template> | ||||||
|     <div ref="containerRef" class="detail"> |   <div ref="containerRef" class="detail"> | ||||||
|         <div class="detail_header"> |     <div class="detail_header"> | ||||||
|             <a-affix :target="(containerRef as HTMLElement)"> |       <a-affix :target="(containerRef as HTMLElement)"> | ||||||
|                 <a-page-header title="通知公告" :subtitle="type === 'edit' ? '修改' : '新增'" @back="onBack"> |         <a-page-header title="通知公告" :subtitle="type === 'edit' ? '修改' : '新增'" @back="onBack"> | ||||||
|                     <template #extra> |           <template #extra> | ||||||
|                         <a-button type="primary" @click="onReleased"> |             <a-button type="primary" @click="onReleased"> | ||||||
|                           <template #icon> |               <template #icon> | ||||||
|                             <icon-save v-if="type === 'edit'" /> |                 <icon-save v-if="type === 'edit'" /> | ||||||
|                             <icon-send v-else /> |                 <icon-send v-else /> | ||||||
|                           </template> |               </template> | ||||||
|                           <template #default> |               <template #default> | ||||||
|                             {{ type === 'edit' ? '保存' : '发布' }} |                 {{ type === 'edit' ? '保存' : '发布' }} | ||||||
|                           </template> |               </template> | ||||||
|                         </a-button> |             </a-button> | ||||||
|                     </template> |           </template> | ||||||
|                 </a-page-header> |         </a-page-header> | ||||||
|             </a-affix> |       </a-affix> | ||||||
|         </div> |  | ||||||
|         <div class="detail_content" style="display: flex; flex-direction: column;"> |  | ||||||
|             <GiForm ref="formRef" v-model="form" :options="options" :columns="columns" /> |  | ||||||
|             <div style="flex: 1;"> |  | ||||||
|                 <AiEditor v-model="form.content" /> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|     </div> |     </div> | ||||||
|  |     <div class="detail_content" style="display: flex; flex-direction: column;"> | ||||||
|  |       <GiForm ref="formRef" v-model="form" :options="options" :columns="columns"> | ||||||
|  |         <template #noticeUsers> | ||||||
|  |           <UserSelect v-model:value="form.noticeUsers" :multiple="true" class="w-full" /> | ||||||
|  |         </template> | ||||||
|  |       </GiForm> | ||||||
|  |       <div style="flex: 1;"> | ||||||
|  |         <AiEditor v-model="form.content" /> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script setup lang="tsx"> | <script setup lang="tsx"> | ||||||
| @@ -46,7 +50,8 @@ const { form, resetForm } = useForm({ | |||||||
|   type: '', |   type: '', | ||||||
|   effectiveTime: '', |   effectiveTime: '', | ||||||
|   terminateTime: '', |   terminateTime: '', | ||||||
|   content: '' |   content: '', | ||||||
|  |   noticeScope: 1 | ||||||
| }) | }) | ||||||
| const options: Options = { | const options: Options = { | ||||||
|   form: { size: 'large' }, |   form: { size: 'large' }, | ||||||
| @@ -88,7 +93,24 @@ const columns: Columns = reactive([ | |||||||
|     props: { |     props: { | ||||||
|       showTime: true |       showTime: true | ||||||
|     } |     } | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     label: '通知范围', | ||||||
|  |     field: 'noticeScope', | ||||||
|  |     type: 'radio-group', | ||||||
|  |     options: [{ label: '所有人', value: 1 }, { label: '指定用户', value: 2 }], | ||||||
|  |     rules: [{ required: true, message: '请选择通知范围' }] | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     label: '指定用户', | ||||||
|  |     field: 'noticeUsers', | ||||||
|  |     type: 'input', | ||||||
|  |     hide: () => { | ||||||
|  |       return form.noticeScope === 1 | ||||||
|  |     }, | ||||||
|  |     rules: [{ required: true, message: '请选择指定用户' }] | ||||||
|   } |   } | ||||||
|  |  | ||||||
| ]) | ]) | ||||||
| // 修改 | // 修改 | ||||||
| const onUpdate = async (id: string) => { | const onUpdate = async (id: string) => { | ||||||
| @@ -103,6 +125,8 @@ const onReleased = async () => { | |||||||
|   const isInvalid = await formRef.value?.formRef?.validate() |   const isInvalid = await formRef.value?.formRef?.validate() | ||||||
|   if (isInvalid) return false |   if (isInvalid) return false | ||||||
|   try { |   try { | ||||||
|  |     // 通知范围 所有人 去除指定用户 | ||||||
|  |     form.noticeUsers = form.noticeScope === 1 ? null : form.noticeUsers | ||||||
|     if (type === 'edit') { |     if (type === 'edit') { | ||||||
|       await updateNotice(form, id as string) |       await updateNotice(form, id as string) | ||||||
|       Message.success('修改成功') |       Message.success('修改成功') | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user