mp-approver-picker.vue 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. <script setup>
  2. import { ref, computed, watch } from 'vue'
  3. import { fetchUserOptions } from '@/api/admin'
  4. const props = defineProps({
  5. modelValue: { type: Array, default: () => [] },
  6. visible: { type: Boolean, default: false },
  7. title: { type: String, default: '选择审批人' },
  8. multiple: { type: Boolean, default: true }
  9. })
  10. const emit = defineEmits(['update:modelValue', 'update:visible', 'confirm'])
  11. const keyword = ref('')
  12. const loading = ref(false)
  13. const users = ref([])
  14. const draftIds = ref([])
  15. const filtered = computed(() => {
  16. const kw = String(keyword.value || '')
  17. .trim()
  18. .toLowerCase()
  19. if (!kw) return users.value
  20. return users.value.filter((u) => {
  21. const blob = `${u.nickName} ${u.userNo} ${u.userId}`.toLowerCase()
  22. return blob.includes(kw)
  23. })
  24. })
  25. const isChecked = (userId) => draftIds.value.includes(String(userId))
  26. const loadUsers = async () => {
  27. loading.value = true
  28. try {
  29. users.value = await fetchUserOptions()
  30. } catch (e) {
  31. uni.showToast({ title: e.message || '加载用户失败', icon: 'none' })
  32. users.value = []
  33. } finally {
  34. loading.value = false
  35. }
  36. }
  37. watch(
  38. () => props.visible,
  39. (v) => {
  40. if (!v) return
  41. draftIds.value = (props.modelValue || []).map(String)
  42. keyword.value = ''
  43. if (!users.value.length) loadUsers()
  44. }
  45. )
  46. const close = () => emit('update:visible', false)
  47. const toggle = (userId) => {
  48. const id = String(userId)
  49. if (!props.multiple) {
  50. draftIds.value = [id]
  51. return
  52. }
  53. const i = draftIds.value.indexOf(id)
  54. if (i >= 0) draftIds.value.splice(i, 1)
  55. else draftIds.value.push(id)
  56. }
  57. const confirm = () => {
  58. if (!draftIds.value.length) {
  59. uni.showToast({ title: '请至少选择一位审批人', icon: 'none' })
  60. return
  61. }
  62. emit('update:modelValue', [...draftIds.value])
  63. emit('confirm', [...draftIds.value])
  64. close()
  65. }
  66. /** 根据已选 ID 解析展示名(父级摘要用) */
  67. function resolveLabels(ids) {
  68. const set = new Set((ids || []).map(String))
  69. return users.value
  70. .filter((u) => set.has(u.userId))
  71. .map((u) => u.nickName || u.userNo || u.userId)
  72. }
  73. defineExpose({ loadUsers, resolveLabels, users })
  74. </script>
  75. <template>
  76. <view v-if="visible" class="mp-sheet-mask" @click="close">
  77. <view class="mp-sheet" @click.stop>
  78. <view class="mp-sheet__head">
  79. <text class="mp-sheet__cancel" @click="close">取消</text>
  80. <text class="mp-sheet__title">{{ title }}</text>
  81. <text class="mp-sheet__ok" @click="confirm">确定</text>
  82. </view>
  83. <view class="mp-sheet__search">
  84. <input
  85. v-model="keyword"
  86. class="mp-sheet__search-input"
  87. placeholder="搜索姓名 / 账号"
  88. />
  89. </view>
  90. <scroll-view scroll-y class="mp-sheet__body">
  91. <view v-if="loading" class="mp-sheet__hint">加载中…</view>
  92. <view v-else-if="!filtered.length" class="mp-sheet__hint">暂无用户</view>
  93. <view
  94. v-for="u in filtered"
  95. :key="u.userId"
  96. class="mp-sheet__row"
  97. @click="toggle(u.userId)"
  98. >
  99. <view class="mp-sheet__check" :class="{ 'mp-sheet__check--on': isChecked(u.userId) }" />
  100. <view class="mp-sheet__meta">
  101. <text class="mp-sheet__name">{{ u.nickName || u.userNo || u.userId }}</text>
  102. <text class="mp-sheet__sub">{{ u.userNo || u.userId }}</text>
  103. </view>
  104. </view>
  105. </scroll-view>
  106. <view class="mp-sheet__foot">已选 {{ draftIds.length }} 人</view>
  107. </view>
  108. </view>
  109. </template>