<script setup lang="tsx">
|
import noDataImg from '@/assets/element-img/no-data.png'
|
import type { DataTableColumns } from 'naive-ui'
|
import { useBoolean } from '@/hooks'
|
import { computed, ref, watch } from 'vue'
|
import { NButton, NCard, NDataTable, NInput, NModal, NSelect } from 'naive-ui'
|
import Pagination from '@/components/common/Pagination.vue'
|
import { getTaskOrderReassignOperationsUserList, reassignTaskOrder } from '@/service/api/attendance'
|
import { fetchRoleList } from '@/service/api/fleet'
|
import { getMultiSelectRowClassName, shouldIgnoreTableRowClick, toggleMultiSelectRowKey } from '@/utils/table'
|
|
interface StaffItem {
|
id: number
|
name: string
|
phone: string
|
roleId: string
|
fleet: string
|
distance: string
|
[key: string]: any
|
}
|
|
const props = withDefaults(defineProps<{
|
taskId?: number | string | null
|
}>(), {
|
taskId: null,
|
})
|
|
const emit = defineEmits<{
|
confirm: [selectedStaff: StaffItem[]]
|
}>()
|
const visible = defineModel<boolean>('visible', { default: false })
|
const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
|
|
// 搜索条件
|
const searchModel = ref({
|
nickName: '',
|
fleet: '',
|
roleId: null,
|
})
|
|
const isPositionOpen = ref(false)
|
|
// 选中的行
|
const checkedRowKeys = ref<number[]>([])
|
|
const listData = ref<StaffItem[]>([])
|
|
// 缓存所有加载过的数据
|
const allLoadedData = ref<Map<number, StaffItem>>(new Map())
|
|
const count = ref(0)
|
const currentPage = ref(1)
|
const pageSize = ref(10)
|
|
// 表格列定义
|
const columns = computed<DataTableColumns<StaffItem>>(() => [
|
{
|
type: 'selection',
|
width: 50,
|
resizable: true,
|
},
|
{
|
title: '序号',
|
key: 'index',
|
resizable: true,
|
width: 80,
|
render: (row, index) => <span>{(currentPage.value - 1) * pageSize.value + index + 1}</span>,
|
},
|
{
|
title: '姓名',
|
key: 'nickName',
|
ellipsis: true,
|
resizable: true,
|
},
|
{
|
title: '联系电话',
|
key: 'phoneNumber',
|
ellipsis: true,
|
resizable: true,
|
},
|
{
|
title: '角色',
|
key: 'roleName',
|
ellipsis: true,
|
resizable: true,
|
},
|
{
|
title: '关联车队',
|
key: 'fleetName',
|
ellipsis: true,
|
resizable: true,
|
},
|
{
|
title: '距离事故点距离',
|
key: 'dis',
|
ellipsis: true,
|
render: row => `${row.dis}km`,
|
},
|
])
|
|
// 获取可改派人员列表(真实接口)
|
async function getList() {
|
if (!props.taskId) {
|
listData.value = []
|
count.value = 0
|
return
|
}
|
startLoading()
|
try {
|
const params = {
|
taskId: props.taskId,
|
pageNum: currentPage.value,
|
pageSize: pageSize.value,
|
nickName: searchModel.value.nickName || undefined,
|
fleet: searchModel.value.fleet || undefined,
|
roleId: searchModel.value.roleId || undefined,
|
}
|
const res = await getTaskOrderReassignOperationsUserList(params)
|
if (res.code === 200) {
|
const records = res.data?.records || []
|
listData.value = records
|
count.value = res.data?.total || records.length || 0
|
}
|
else {
|
listData.value = []
|
count.value = 0
|
}
|
|
// 缓存已加载数据,支持跨页保留选择项
|
listData.value.forEach((item) => {
|
allLoadedData.value.set(item.id, item)
|
})
|
}
|
catch (error) {
|
console.error('获取可改派人员列表失败', error)
|
listData.value = []
|
count.value = 0
|
}
|
finally {
|
endLoading()
|
}
|
}
|
|
// 搜索
|
function handleSearch() {
|
currentPage.value = 1
|
getList()
|
}
|
|
function resetSearchModel() {
|
searchModel.value = {
|
nickName: '',
|
fleet: '',
|
roleId: null,
|
}
|
}
|
|
// 重置
|
function handleReset() {
|
resetSearchModel()
|
currentPage.value = 1
|
pageSize.value = 10
|
getList()
|
}
|
|
// 取消
|
function handleCancel() {
|
visible.value = false
|
}
|
|
// 确认
|
function handleConfirm() {
|
if (checkedRowKeys.value.length === 0) {
|
window.$message?.warning('请至少选择一条记录')
|
return
|
}
|
// 从缓存中获取选中的完整对象
|
const selectedStaff: StaffItem[] = []
|
checkedRowKeys.value.forEach((id) => {
|
const item = allLoadedData.value.get(id)
|
if (item) {
|
selectedStaff.push(item)
|
}
|
})
|
|
if (selectedStaff.length === 0) {
|
window.$message?.warning('未找到选中人员,请重试')
|
return
|
}
|
if (selectedStaff.length > 1) {
|
window.$message?.warning('当前仅支持选择 1 名改派人员')
|
return
|
}
|
|
const selectedUser = selectedStaff[0]
|
const handlerUserId = selectedUser?.id
|
if (!handlerUserId) {
|
window.$message?.warning('改派人员ID缺失,请重试')
|
return
|
}
|
|
reassignTaskOrder({
|
taskId: props.taskId,
|
newOperationsUserId: handlerUserId,
|
}).then((res) => {
|
if (res.code === 200 || res.code === 0) {
|
emit('confirm', selectedStaff)
|
visible.value = false
|
return
|
}
|
window.$message?.error(res.msg || '改派失败,请稍后重试')
|
}).catch((error) => {
|
console.error('改派失败:', error)
|
window.$message?.error('改派失败,请稍后重试')
|
})
|
}
|
|
function handleRowClick(row: StaffItem) {
|
checkedRowKeys.value = toggleMultiSelectRowKey(checkedRowKeys.value, row.id)
|
}
|
|
function getRowClassName(row: StaffItem) {
|
return getMultiSelectRowClassName(row.id, checkedRowKeys.value)
|
}
|
|
function getRowProps(row: StaffItem) {
|
return {
|
style: 'cursor: pointer;',
|
onClick: (event: MouseEvent) => {
|
if (shouldIgnoreTableRowClick(event.target))
|
return
|
handleRowClick(row)
|
},
|
}
|
}
|
|
// 分页改变
|
function changePage(page: number, size: number) {
|
const isPageSizeChanged = size !== pageSize.value
|
pageSize.value = size
|
currentPage.value = isPageSizeChanged ? 1 : page
|
getList()
|
}
|
|
// 角色选项
|
const roleOptions = ref<{ label: string, value: number }[]>([])
|
|
async function getRoleList() {
|
const res = await fetchRoleList()
|
if (res.code === 200) {
|
roleOptions.value = res.data.map((item: any) => ({
|
label: item.roleName,
|
value: item.id,
|
}))
|
}
|
}
|
|
// 监听弹窗打开和taskId变化
|
watch(
|
[visible, () => props.taskId],
|
([newVisible, newTaskId], [oldVisible, oldTaskId]) => {
|
if (newVisible) {
|
if (!newTaskId) {
|
if (!oldVisible) {
|
window.$message?.warning('缺少任务ID,无法获取可改派人员')
|
}
|
return
|
}
|
|
// 如果是刚打开弹窗,初始化状态
|
if (!oldVisible) {
|
resetSearchModel()
|
getRoleList()
|
currentPage.value = 1
|
pageSize.value = 10
|
checkedRowKeys.value = []
|
allLoadedData.value.clear()
|
getList()
|
}
|
// 如果弹窗已经打开,但 taskId 发生了变化,重新获取数据
|
else if (newTaskId !== oldTaskId) {
|
checkedRowKeys.value = []
|
allLoadedData.value.clear()
|
currentPage.value = 1
|
getList()
|
}
|
}
|
else if (oldVisible) {
|
// 关闭时清空
|
checkedRowKeys.value = []
|
allLoadedData.value.clear()
|
}
|
},
|
)
|
</script>
|
|
<template>
|
<NModal v-model:show="visible">
|
<NCard
|
style="
|
width: 1000px;
|
height: 80vh;
|
max-height: 800px;
|
border-radius: 8px;
|
display: flex;
|
flex-direction: column;
|
"
|
:bordered="false"
|
:segmented="{ content: true }"
|
content-style="padding: 16px 24px; flex: 1; display: flex; flex-direction: column; overflow: hidden;"
|
footer-style="padding: 16px 24px; border-top: 1px solid rgba(46, 61, 77, 0.1); flex-shrink: 0;"
|
>
|
<template #header>
|
<span style="font-size: 16px; font-weight: bold; color: #1F1F1F;">任务改派</span>
|
</template>
|
<template #header-extra>
|
<div text class="cursor-pointer modal-close-trigger" @click="handleCancel">
|
<span class="modal-close-text" style="color: #999;">×</span>
|
</div>
|
</template>
|
|
<div class="flex flex-col h-full overflow-hidden">
|
<!-- 搜索区域 -->
|
<div class="mb-3 flex items-end gap-16px" style="flex-shrink: 0; flex-wrap: wrap;">
|
<div class="w-240px">
|
<div class="mb-2 text-sm text-[#1F1F1F]">
|
运维人员
|
</div>
|
<NInput
|
v-model:value="searchModel.nickName"
|
placeholder="输入人员名称"
|
clearable
|
/>
|
</div>
|
<div class="w-240px">
|
<div class="mb-2 text-sm text-[#1F1F1F]">
|
车队
|
</div>
|
<NInput
|
v-model:value="searchModel.fleet"
|
placeholder="请输入"
|
clearable
|
/>
|
</div>
|
<div class="w-240px">
|
<div class="mb-2 text-sm text-[#1F1F1F]">
|
角色
|
</div>
|
<NSelect
|
v-model:value="searchModel.roleId"
|
:options="roleOptions"
|
placeholder="请选择"
|
clearable
|
@update:show="(val: any) => isPositionOpen = val"
|
>
|
<template #arrow>
|
<img
|
src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e120f81c-9f15-4e1a-9938-22252898edf9.png"
|
class="arrow-icon" :class="{ 'arrow-icon--open': isPositionOpen }"
|
:style="{ width: '16px', height: '16px' }" alt="arrow"
|
>
|
</template>
|
<template #empty>
|
<div class="flex-center">
|
<img
|
:src="noDataImg"
|
style="width: 50%;"
|
@error="(e: any) => { e.target.src = 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png' }"
|
>
|
</div>
|
</template>
|
</NSelect>
|
</div>
|
<div class="flex gap-8px">
|
<NButton style="width: 84px; height: 32px" @click="handleReset">
|
重置
|
</NButton>
|
<NButton
|
type="primary"
|
style="width: 84px; height: 32px"
|
@click="handleSearch"
|
>
|
查询
|
</NButton>
|
</div>
|
</div>
|
|
<!-- 表格区域 -->
|
<div class="flex-1 overflow-hidden" style="min-height: 0">
|
<NDataTable
|
v-model:checked-row-keys="checkedRowKeys"
|
style="height: 100%"
|
flex-height
|
:columns="columns"
|
:data="listData"
|
:loading="loading"
|
:bordered="false"
|
:row-key="(row: StaffItem) => row.id"
|
:row-class-name="getRowClassName"
|
:row-props="getRowProps"
|
:pagination="false"
|
>
|
<template #empty>
|
<div class="flex-center">
|
<img
|
:src="noDataImg"
|
style="width: 123px; height: 125px"
|
>
|
</div>
|
</template>
|
</NDataTable>
|
</div>
|
<Pagination
|
:count="count"
|
:page="currentPage"
|
:page-size="pageSize"
|
style="margin-top: 12px; flex-shrink: 0"
|
@change="changePage"
|
/>
|
</div>
|
|
<template #footer>
|
<div class="flex justify-end gap-3">
|
<NButton class="footer-btn" @click="handleCancel">
|
取消
|
</NButton>
|
<NButton type="primary" class="footer-btn" @click="handleConfirm">
|
保存
|
</NButton>
|
</div>
|
</template>
|
</NCard>
|
</NModal>
|
</template>
|
|
<style scoped lang="scss">
|
.mb-4 {
|
margin-bottom: 1rem;
|
}
|
.flex {
|
display: flex;
|
}
|
.items-end {
|
align-items: flex-end;
|
}
|
.gap-4 {
|
gap: 1rem;
|
}
|
.gap-2 {
|
gap: 0.5rem;
|
}
|
.gap-3 {
|
gap: 0.75rem;
|
}
|
.flex-1 {
|
flex: 1 1 0%;
|
}
|
.justify-end {
|
justify-content: flex-end;
|
}
|
.mb-2 {
|
margin-bottom: 0.5rem;
|
}
|
.text-sm {
|
font-size: 0.875rem;
|
line-height: 1.25rem;
|
}
|
|
.footer-btn {
|
padding: 7px 29px;
|
height: auto;
|
font-size: 13px;
|
line-height: 18px;
|
}
|
|
:deep(.robotaxi-table-row--selected td) {
|
background-color: #f0f7ff !important;
|
}
|
</style>
|