Bläddra i källkod

feat:优化 user-pciker、dept-picker、user-picker 的封装

YunaiV 4 månader sedan
förälder
incheckning
817098a04d

+ 1 - 1
src/pages-system/dept/detail/index.vue

@@ -67,7 +67,7 @@ const userList = ref<User[]>([]) // 用户列表
 
 /** 返回上一页 */
 function handleBack() {
-  navigateBackPlus()
+  navigateBackPlus('/pages-system/dept/index')
 }
 
 /** 获取上级部门名称 */

+ 55 - 20
src/pages-system/user/form/components/dept-picker.vue

@@ -1,8 +1,7 @@
-<!-- TODO @芋艿:【优化】看看后续要不要抽成组件! -->
 <template>
   <wd-col-picker
     v-model="selectedValue"
-    label="归属部门"
+    :label="label"
     label-width="180rpx"
     :columns="deptColumns"
     :column-change="handleColumnChange"
@@ -12,13 +11,18 @@
 </template>
 
 <script lang="ts" setup>
-import type { Dept } from '@/api/system/dept'
-import { onMounted, ref, watch } from 'vue'
-import { getSimpleDeptList } from '@/api/system/dept'
+import type {Dept} from '@/api/system/dept'
+import {getSimpleDeptList} from '@/api/system/dept'
+import {onMounted, ref, watch} from 'vue'
 
-const props = defineProps<{
+const props = withDefaults(defineProps<{
   modelValue?: number
-}>()
+  label?: string
+  showRoot?: boolean // 是否显示顶级部门节点
+}>(), {
+  label: '上级部门',
+  showRoot: false,
+})
 
 const emit = defineEmits<{
   (e: 'update:modelValue', value: number | undefined): void
@@ -32,26 +36,54 @@ const selectedValue = ref<number[]>([])
 watch(
   () => props.modelValue,
   (val) => {
-    if (val && deptList.value.length > 0) {
+    // 0 或 undefined 都视为顶级部门(如果允许显示顶级)
+    if (val && val !== 0 && deptList.value.length > 0) {
       const path = findDeptPath(val)
       selectedValue.value = path
       // 构建列数据以支持回显
       buildColumnsForPath(path)
-    }
-    else {
-      selectedValue.value = []
+    } else {
+      if (props.showRoot) {
+        // 顶级部门或未选择,重置
+        selectedValue.value = [0]
+      } else {
+         selectedValue.value = []
+      }
+      // 重新构建第一列,确保正确
+      if (deptList.value.length > 0) {
+         initFirstColumn()
+      }
     }
   },
+  { immediate: true }
 )
 
+/** 初始化第一列 */
+function initFirstColumn() {
+  const topDepts = deptList.value.filter(item => item.parentId === 0)
+  if (props.showRoot) {
+    deptColumns.value = [
+      [
+        { label: '顶级部门', value: 0 },
+        ...topDepts.map(item => ({ value: item.id, label: item.name }))
+      ]
+    ]
+  } else {
+    deptColumns.value = [
+      topDepts.map(item => ({ value: item.id, label: item.name }))
+    ]
+  }
+}
+
 /** 加载部门列表 */
 async function loadDeptList() {
   deptList.value = await getSimpleDeptList()
-  // 构建第一列数据(顶级部门)
-  const topDepts = deptList.value.filter(item => item.parentId === 0)
-  deptColumns.value = [topDepts.map(item => ({ value: item.id, label: item.name }))]
+
+  // 初始化第一列
+  initFirstColumn()
+
   // 如果有初始值,回显
-  if (props.modelValue) {
+  if (props.modelValue && props.modelValue !== 0) {
     const path = findDeptPath(props.modelValue)
     selectedValue.value = path
     buildColumnsForPath(path)
@@ -98,11 +130,14 @@ function buildColumnsForPath(path: number[]) {
 
 /** 列变化 */
 function handleColumnChange({ selectedItem, resolve, finish }: any) {
+  if (selectedItem.value === 0) {
+    finish()
+    return
+  }
   const children = deptList.value.filter(item => item.parentId === selectedItem.value)
   if (children.length > 0) {
     resolve(children.map(item => ({ value: item.id, label: item.name })))
-  }
-  else {
+  } else {
     finish()
   }
 }
@@ -116,9 +151,9 @@ function displayFormat(selectedItems: any[]) {
 function handleConfirm({ value }: { value: number[] }) {
   if (value && value.length > 0) {
     emit('update:modelValue', value[value.length - 1])
-  }
-  else {
-    emit('update:modelValue', undefined)
+  } else {
+    // 如果允许 root,默认顶级 0;否则 undefined
+    emit('update:modelValue', props.showRoot ? 0 : undefined)
   }
 }
 

+ 12 - 95
src/pages-system/dept/form/index.vue

@@ -11,13 +11,10 @@
     <view>
       <wd-form ref="formRef" :model="formData" :rules="formRules">
         <wd-cell-group border>
-          <wd-cell
-            title="上级部门"
-            title-width="180rpx"
-            prop="parentId"
-            is-link
-            :value="getParentName()"
-            @click="showDeptPicker = true"
+          <DeptPicker
+            v-model="formData.parentId"
+            label="上级部门"
+            :show-root="true"
           />
           <wd-input
             v-model="formData.name"
@@ -33,13 +30,9 @@
               :min="0"
             />
           </wd-cell>
-          <wd-cell
-            title="负责人"
-            title-width="180rpx"
-            prop="leaderUserId"
-            is-link
-            :value="getLeaderName()"
-            @click="showUserPicker = true"
+          <UserPicker
+            v-model="formData.leaderUserId"
+            type="radio"
           />
           <wd-input
             v-model="formData.phone"
@@ -79,35 +72,18 @@
         保存
       </wd-button>
     </view>
-
-    <!-- 上级部门选择器 -->
-    <wd-picker
-      :model-value="showDeptPicker"
-      :columns="deptPickerColumns"
-      title="选择上级部门"
-      @confirm="handleDeptConfirm"
-      @close="showDeptPicker = false"
-    />
-
-    <!-- 负责人选择器 -->
-    <wd-picker
-      :model-value="showUserPicker"
-      :columns="userPickerColumns"
-      title="选择负责人"
-      @confirm="handleUserConfirm"
-      @close="showUserPicker = false"
-    />
   </view>
 </template>
 
 <script lang="ts" setup>
 import type { Dept } from '@/api/system/dept'
-import type { User } from '@/api/system/user'
 import { computed, onMounted, ref } from 'vue'
 import { useToast } from 'wot-design-uni'
-import { createDept, getDept, getSimpleDeptList, updateDept } from '@/api/system/dept'
-import { getSimpleUserList } from '@/api/system/user'
+import { createDept, getDept, updateDept } from '@/api/system/dept'
 import { CommonStatusEnum } from '@/utils/constants'
+import DeptPicker from './components/dept-picker.vue'
+import UserPicker from '@/pages-system/user/form/components/user-picker.vue'
+import {navigateBackPlus} from '@/utils';
 
 const props = defineProps<{
   id?: number | any
@@ -142,54 +118,9 @@ const formRules = {
 }
 const formRef = ref()
 
-const deptList = ref<Dept[]>([]) // 部门列表
-const userList = ref<User[]>([]) // 用户列表
-const showDeptPicker = ref(false) // 部门选择器
-const showUserPicker = ref(false) // 负责人选择器
-
-/** 部门选择器列 */
-const deptPickerColumns = computed(() => {
-  const items = [{ label: '顶级部门', value: 0 }]
-  deptList.value.forEach((dept) => {
-    // 编辑时排除自己和子部门
-    if (props.id && dept.id === props.id) {
-      return
-    }
-    items.push({ label: dept.name, value: dept.id! })
-  })
-  return items
-})
-
-/** 用户选择器列 */
-const userPickerColumns = computed(() => {
-  const items = [{ label: '不设置', value: 0 }]
-  userList.value.forEach((user) => {
-    items.push({ label: user.nickname, value: user.id! })
-  })
-  return items
-})
-
-/** 获取上级部门名称 */
-function getParentName(): string {
-  if (!formData.value.parentId || formData.value.parentId === 0) {
-    return '顶级部门'
-  }
-  const parent = deptList.value.find(d => d.id === formData.value.parentId)
-  return parent?.name || '请选择'
-}
-
-/** 获取负责人名称 */
-function getLeaderName(): string {
-  if (!formData.value.leaderUserId) {
-    return '请选择'
-  }
-  const user = userList.value.find(u => u.id === formData.value.leaderUserId)
-  return user?.nickname || '请选择'
-}
-
 /** 返回上一页 */
 function handleBack() {
-  uni.navigateBack()
+  navigateBackPlus('/pages-system/dept/index')
 }
 
 /** 加载部门详情 */
@@ -200,16 +131,6 @@ async function getDetail() {
   formData.value = await getDept(props.id)
 }
 
-/** 部门选择确认 */
-function handleDeptConfirm({ value }: { value: number }) {
-  formData.value.parentId = value
-}
-
-/** 负责人选择确认 */
-function handleUserConfirm({ value }: { value: number }) {
-  formData.value.leaderUserId = value || undefined
-}
-
 /** 提交表单 */
 async function handleSubmit() {
   const { valid } = await formRef.value.validate()
@@ -236,10 +157,6 @@ async function handleSubmit() {
 
 /** 初始化 */
 onMounted(async () => {
-  // 获取部门列表
-  deptList.value = await getSimpleDeptList()
-  // 获取用户列表
-  userList.value = await getSimpleUserList()
   // 获取详情
   await getDetail()
 })

+ 56 - 0
src/pages-system/post/form/components/post-picker.vue

@@ -0,0 +1,56 @@
+<template>
+  <wd-select-picker
+    v-model="selectedIds"
+    label="岗位"
+    label-width="180rpx"
+    :columns="columns"
+    type="checkbox"
+    filterable
+    @confirm="handleConfirm"
+  />
+</template>
+
+<script lang="ts" setup>
+import type { Post } from '@/api/system/post'
+import { computed, onMounted, ref, watch } from 'vue'
+import { getSimplePostList } from '@/api/system/post'
+
+const props = defineProps<{
+  modelValue?: number[]
+}>()
+
+const emit = defineEmits<{
+  (e: 'update:modelValue', value: number[]): void
+}>()
+
+const postList = ref<Post[]>([])
+const selectedIds = ref<number[]>([])
+
+/** 构建 columns 数据 */
+const columns = computed(() => {
+  return postList.value.map(item => ({
+    label: item.name,
+    value: item.id
+  }))
+})
+
+watch(
+  () => props.modelValue,
+  (val) => {
+    selectedIds.value = val || []
+  },
+  { immediate: true }
+)
+
+async function loadPostList() {
+  postList.value = await getSimplePostList()
+}
+
+function handleConfirm({ value }: { value: number[] }) {
+  emit('update:modelValue', value)
+}
+
+onMounted(() => {
+  loadPostList()
+})
+</script>

+ 0 - 82
src/pages-system/user/form/components/post-picker.vue

@@ -1,82 +0,0 @@
-<!-- TODO @芋艿:【优化】看看后续要不要抽成组件! -->
-<template>
-  <!-- 岗位选择单元格 -->
-  <wd-cell title="岗位" title-width="180rpx" is-link @click="popupVisible = true">
-    <view class="text-left">
-      {{ displayText }}
-    </view>
-  </wd-cell>
-
-  <!-- 岗位选择弹窗 -->
-  <wd-popup v-model="popupVisible" position="bottom" custom-style="border-radius: 24rpx 24rpx 0 0;">
-    <view class="p-32rpx">
-      <view class="mb-24rpx flex items-center justify-between">
-        <text class="text-32rpx text-[#333] font-semibold">选择岗位</text>
-        <wd-icon name="close" size="20px" @click="popupVisible = false" />
-      </view>
-      <wd-checkbox-group v-model="selectedIds" cell shape="button">
-        <wd-checkbox v-for="item in postList" :key="item.id" :model-value="item.id">
-          {{ item.name }}
-        </wd-checkbox>
-      </wd-checkbox-group>
-      <view class="mt-32rpx">
-        <wd-button type="primary" block @click="handleConfirm">
-          确定
-        </wd-button>
-      </view>
-    </view>
-  </wd-popup>
-</template>
-
-<script lang="ts" setup>
-import type { Post } from '@/api/system/post'
-import { computed, onMounted, ref, watch } from 'vue'
-import { getSimplePostList } from '@/api/system/post'
-
-const props = defineProps<{
-  modelValue?: number[]
-}>()
-
-const emit = defineEmits<{
-  (e: 'update:modelValue', value: number[]): void
-}>()
-
-const popupVisible = ref(false)
-const postList = ref<Post[]>([])
-const selectedIds = ref<number[]>([])
-
-const displayText = computed(() => {
-  if (!selectedIds.value || selectedIds.value.length === 0) {
-    return ''
-  }
-  return postList.value
-    .filter(item => selectedIds.value.includes(item.id))
-    .map(item => item.name)
-    .join('、')
-}) // 显示文本
-
-/** 监听外部值变化 */
-watch(
-  () => props.modelValue,
-  (val) => {
-    selectedIds.value = val || []
-  },
-  { immediate: true },
-)
-
-/** 加载岗位列表 */
-async function loadPostList() {
-  postList.value = await getSimplePostList()
-}
-
-/** 确认选择 */
-function handleConfirm() {
-  emit('update:modelValue', selectedIds.value)
-  popupVisible.value = false
-}
-
-/** 初始化 */
-onMounted(() => {
-  loadPostList()
-})
-</script>

+ 67 - 0
src/pages-system/user/form/components/user-picker.vue

@@ -0,0 +1,67 @@
+<template>
+  <wd-select-picker
+    v-model="selectedId"
+    :label="label"
+    label-width="180rpx"
+    :columns="columns"
+    :type="type"
+    filterable
+    @confirm="handleConfirm"
+  />
+</template>
+
+<script lang="ts" setup>
+import type { User } from '@/api/system/user'
+import { computed, onMounted, ref, watch } from 'vue'
+import { getSimpleUserList } from '@/api/system/user'
+
+const props = withDefaults(defineProps<{
+  modelValue?: number | number[]
+  type?: 'radio' | 'checkbox'
+  label?: string
+}>(), {
+  type: 'checkbox',
+  label: '负责人'
+})
+
+const emit = defineEmits<{
+  (e: 'update:modelValue', value: number | number[] | undefined): void
+}>()
+
+const userList = ref<User[]>([])
+const selectedId = ref<number | string | number[]>([])
+
+// 构建 columns 数据
+const columns = computed(() => {
+  return userList.value.map(user => ({
+    label: user.nickname,
+    value: user.id
+  }))
+})
+
+watch(
+  () => props.modelValue,
+  (val) => {
+    if (props.type === 'radio') {
+      // 单选时,如果值为 undefined,使用空字符串避免警告
+      selectedId.value = val !== undefined ? val : ''
+    } else {
+      // 多选时,确保是数组
+      selectedId.value = Array.isArray(val) ? val : []
+    }
+  },
+  { immediate: true }
+)
+
+async function loadUserList() {
+  userList.value = await getSimpleUserList()
+}
+
+function handleConfirm({ value }: { value: any }) {
+  emit('update:modelValue', value)
+}
+
+onMounted(() => {
+  loadUserList()
+})
+</script>

+ 6 - 3
src/pages-system/user/form/index.vue

@@ -37,7 +37,10 @@
             clearable
             placeholder="请输入用户昵称"
           />
-          <DeptPicker v-model="formData.deptId" />
+          <DeptPicker
+            v-model="formData.deptId"
+            label="归属部门"
+          />
           <PostPicker v-model="formData.postIds" />
           <wd-input
             v-model="formData.email"
@@ -104,8 +107,8 @@ import { createUser, getUser, updateUser } from '@/api/system/user'
 import { getIntDictOptions } from '@/hooks/useDict'
 import { CommonStatusEnum, DICT_TYPE } from '@/utils/constants'
 import { isEmail, isMobile } from '@/utils/validator'
-import DeptPicker from './components/dept-picker.vue'
-import PostPicker from './components/post-picker.vue'
+import DeptPicker from '@/pages-system/dept/form/components/dept-picker.vue'
+import PostPicker from '@/pages-system/post/form/components/post-picker.vue'
 import { navigateBackPlus } from '@/utils';
 
 const props = defineProps<{