Files
threeonecheck_web/components/flow/FlowAssigneePickerPopup.vue
2026-07-27 09:27:22 +08:00

229 lines
5.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<u-popup :show="show" mode="bottom" round="20" @close="handleCancel">
<view class="user-popup">
<view class="popup-header">
<view class="popup-title text-bold">选择下一步处理人</view>
<view class="popup-close" @click="handleCancel">×</view>
</view>
<view v-if="pickerIdentityId" class="selected-summary">
<text class="summary-label">已选</text>
<text class="summary-text">{{ pickerDisplayName }}</text>
</view>
<scroll-view class="user-list-scroll" scroll-y>
<view v-if="loading" class="empty-tip">加载中...</view>
<view v-else-if="!taskId" class="empty-tip">缺少任务ID无法加载处理人</view>
<view v-else-if="deptTree.length === 0" class="empty-tip">暂无人员数据</view>
<FlowAssigneeTree
v-else
:depts="deptTree"
:selected-identity-id="pickerIdentityId"
@select="handleUserSelect"
/>
</scroll-view>
<view class="popup-footer">
<button class="btn-cancel" @click="handleCancel">取消</button>
<button class="btn-confirm bg-blue" @click="handleConfirm">确定</button>
</view>
</view>
</u-popup>
</template>
<script setup>
import { computed, ref, watch } from 'vue';
import { getFlowApproverCandidates } from '@/request/api.js';
import FlowAssigneeTree from './FlowAssigneeTree.vue';
import {
findAssigneeUserInDeptTree,
formatAssigneeDisplayName,
normalizeApproverDeptTree,
resolveAssigneeIdentityId
} from './flowAssigneeUtils.js';
const props = defineProps({
show: {
type: Boolean,
default: false
},
taskId: {
type: String,
default: ''
},
pickerIdentityId: {
type: String,
default: ''
},
pickerName: {
type: String,
default: ''
}
});
const emit = defineEmits(['update:pickerIdentityId', 'update:pickerName', 'cancel', 'confirm']);
const loading = ref(false);
const deptTree = ref([]);
const localPickerIdentityId = ref('');
const localPickerName = ref('');
const pickerDisplayName = computed(() => {
const user = findAssigneeUserInDeptTree(deptTree.value, localPickerIdentityId.value);
return user ? formatAssigneeDisplayName(user) : localPickerName.value;
});
const syncLocalPicker = () => {
localPickerIdentityId.value = props.pickerIdentityId || '';
localPickerName.value = props.pickerName || '';
};
const fetchAssigneeTree = async () => {
if (!props.taskId) {
deptTree.value = [];
return;
}
loading.value = true;
try {
const res = await getFlowApproverCandidates(props.taskId);
if (res.code === 0) {
deptTree.value = normalizeApproverDeptTree(res.data);
} else {
deptTree.value = [];
uni.showToast({ title: res.msg || '获取处理人失败', icon: 'none' });
}
} catch (error) {
console.error('获取处理人失败:', error);
deptTree.value = [];
uni.showToast({ title: '获取处理人失败', icon: 'none' });
} finally {
loading.value = false;
}
};
watch(
() => props.show,
async (visible) => {
if (!visible) return;
syncLocalPicker();
await fetchAssigneeTree();
}
);
const handleUserSelect = (user) => {
const identityId = resolveAssigneeIdentityId(user);
if (!identityId) return;
localPickerIdentityId.value = identityId;
localPickerName.value = formatAssigneeDisplayName(user);
emit('update:pickerIdentityId', identityId);
emit('update:pickerName', localPickerName.value);
};
const handleCancel = () => {
emit('cancel');
};
const handleConfirm = () => {
if (!localPickerIdentityId.value) {
uni.showToast({ title: '请选择下一步处理人', icon: 'none' });
return;
}
const user = findAssigneeUserInDeptTree(deptTree.value, localPickerIdentityId.value);
if (!user) {
uni.showToast({ title: '所选身份无效,请重新选择', icon: 'none' });
return;
}
const name = formatAssigneeDisplayName(user);
emit('update:pickerIdentityId', localPickerIdentityId.value);
emit('update:pickerName', name);
emit('confirm', {
identityId: localPickerIdentityId.value,
name,
user
});
};
</script>
<style lang="scss" scoped>
.user-popup {
background: #fff;
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #eee;
.popup-title {
font-size: 32rpx;
color: #333;
}
.popup-close {
font-size: 40rpx;
color: #999;
line-height: 1;
}
}
.selected-summary {
padding: 16rpx 30rpx;
background: #f5f7fa;
border-bottom: 1rpx solid #eee;
font-size: 24rpx;
line-height: 1.5;
.summary-label {
color: #909399;
}
.summary-text {
color: #333;
}
}
.user-list-scroll {
max-height: 600rpx;
box-sizing: border-box;
}
.empty-tip {
padding: 80rpx 20rpx;
text-align: center;
color: #909399;
font-size: 26rpx;
}
.popup-footer {
display: flex;
gap: 24rpx;
padding: 24rpx 30rpx;
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
background: #fff;
button {
flex: 1;
height: 80rpx;
line-height: 80rpx;
border-radius: 40rpx;
font-size: 30rpx;
margin: 0;
padding: 0;
&::after {
border: none;
}
}
.btn-cancel {
background: #fff;
color: #2667E9;
border: 2rpx solid #2667E9;
}
.btn-confirm {
color: #fff;
border: none;
}
}
}
</style>