<script setup lang="ts">
|
import { fetchApprovalDetail, submitGeneralApproval, submitHandlerApproval } from '@/api/appointment'
|
import { fetchPassAccessAreaTree, searchLocalUnits, searchVisitedUsersByExactName } from '@/api/lookup'
|
import type { ApprovalFlowNodeItem, MiniApprovalDetailItem } from '@/types/appointment'
|
import type { MiniLocalUnitItem, MiniPassAccessAreaTreeItem, MiniVisitedUserItem } from '@/types/lookup'
|
|
definePage({
|
name: 'reviewDetail',
|
layout: 'default',
|
style: {
|
navigationStyle: 'custom',
|
},
|
})
|
|
const router = useRouter()
|
const sysInfo = uni.getSystemInfoSync()
|
const statusBarHeight = sysInfo.statusBarHeight || 0
|
const navBarHeight = 44
|
|
const detail = ref<MiniApprovalDetailItem | null>(null)
|
const loading = ref(false)
|
const submitting = ref(false)
|
const comment = ref('')
|
const selectedAreaIds = ref<number[]>([])
|
const involveOtherLocalUnit = ref(false)
|
const selectedLocalUnits = ref<MiniLocalUnitItem[]>([])
|
const areaTree = ref<MiniPassAccessAreaTreeItem[]>([])
|
const showAreaPicker = ref(false)
|
const showUnitPicker = ref(false)
|
const showNoticePicker = ref(false)
|
const selectedParentId = ref<number | null>(null)
|
const unitKeyword = ref('')
|
const unitResults = ref<MiniLocalUnitItem[]>([])
|
const noticeKeyword = ref('')
|
const noticeResults = ref<MiniVisitedUserItem[]>([])
|
const selectedNoticeUsers = ref<MiniVisitedUserItem[]>([])
|
|
const query = reactive<{
|
taskId?: number
|
taskUuid?: string
|
orderId?: number
|
orderUuid?: string
|
}>({})
|
|
const appointmentIcons = {
|
male: '/static/appointment/man.png',
|
female: '/static/appointment/woman.png',
|
company: '/static/appointment/company.png',
|
plate: '/static/appointment/plate.png',
|
idCard: '/static/appointment/idCard.png',
|
}
|
|
const isHandlerMode = computed(() => detail.value?.pageMode === 'HANDLER_PENDING')
|
const isLevel3LocalDeptLeader = computed(() => detail.value?.nodeCode === 'LEVEL3_LOCAL_DEPT_LEADER')
|
const canApprove = computed(() => Boolean(detail.value?.canApprove))
|
const activeAreaChildren = computed(() => {
|
const parent = areaTree.value.find(item => item.id === selectedParentId.value)
|
if (!parent) {
|
return []
|
}
|
return parent.children && parent.children.length > 0 ? parent.children : [parent]
|
})
|
const selectedAreaNamesText = computed(() => {
|
const names: string[] = []
|
const walk = (nodes: MiniPassAccessAreaTreeItem[]) => {
|
nodes.forEach((node) => {
|
if (selectedAreaIds.value.includes(node.id)) {
|
names.push(node.areaName)
|
}
|
if (node.children?.length) {
|
walk(node.children)
|
}
|
})
|
}
|
walk(areaTree.value)
|
return names.join('、')
|
})
|
const statusStamp = computed(() => {
|
const status = detail.value?.approvalStatusText || ''
|
if (status.includes('拒') || status.includes('驳回')) {
|
return '/static/review/refueReview.png'
|
}
|
if (status.includes('通过') || status.includes('同意')) {
|
return '/static/review/passReview.png'
|
}
|
if (status.includes('取消')) {
|
return '/static/review/cancelReview.png'
|
}
|
return '/static/review/underReview.png'
|
})
|
|
function handleBack() {
|
router.back()
|
}
|
|
function visitorGenderIcon(gender?: string | null) {
|
return gender === 'FEMALE' ? appointmentIcons.female : appointmentIcons.male
|
}
|
|
function flowStatusText(status?: string | null) {
|
switch (status) {
|
case 'APPROVED':
|
return '已通过'
|
case 'REJECTED':
|
return '已驳回'
|
case 'CANCELLED':
|
return '已取消'
|
case 'SKIPPED':
|
return '已跳过'
|
case 'PENDING':
|
return '审批中'
|
default:
|
return '待处理'
|
}
|
}
|
|
function flowStatusClass(status?: string | null) {
|
switch (status) {
|
case 'APPROVED':
|
return 'approved'
|
case 'REJECTED':
|
return 'rejected'
|
case 'CANCELLED':
|
return 'cancelled'
|
case 'PENDING':
|
return 'pending'
|
case 'SKIPPED':
|
return 'skipped'
|
default:
|
return 'waiting'
|
}
|
}
|
|
function flowUsers(node: ApprovalFlowNodeItem) {
|
return node.groups.flatMap(group => group.users.map(user => ({
|
id: user.taskId,
|
name: user.userName || group.groupName || '审批人',
|
status: user.taskStatus,
|
actionTime: user.actionTime,
|
groupName: group.groupName,
|
noticeUsers: user.noticeUsers || [],
|
})))
|
}
|
|
async function loadAreaTree() {
|
if (!isHandlerMode.value) {
|
return
|
}
|
areaTree.value = await fetchPassAccessAreaTree()
|
selectedParentId.value = areaTree.value[0]?.id ?? null
|
}
|
|
async function loadDetail() {
|
loading.value = true
|
try {
|
const response = await fetchApprovalDetail(query)
|
detail.value = response
|
comment.value = ''
|
selectedAreaIds.value = [...(response.passAccessAreaIds || [])]
|
involveOtherLocalUnit.value = Boolean(response.involveOtherLocalUnit)
|
selectedLocalUnits.value = [...(response.localUnits || [])]
|
selectedNoticeUsers.value = [...(response.noticeUsers || [])]
|
noticeKeyword.value = ''
|
noticeResults.value = []
|
await loadAreaTree()
|
}
|
catch (error) {
|
uni.showToast({
|
title: error instanceof Error ? error.message : '加载审批详情失败',
|
icon: 'none',
|
})
|
}
|
finally {
|
loading.value = false
|
}
|
}
|
|
async function searchNoticeUsersByKeyword() {
|
if (!noticeKeyword.value.trim()) {
|
noticeResults.value = []
|
return
|
}
|
try {
|
noticeResults.value = await searchVisitedUsersByExactName(noticeKeyword.value.trim())
|
}
|
catch (error) {
|
uni.showToast({
|
title: error instanceof Error ? error.message : '搜索抄送人失败',
|
icon: 'none',
|
})
|
}
|
}
|
|
async function searchUnitsByKeyword() {
|
if (!unitKeyword.value.trim()) {
|
unitResults.value = []
|
return
|
}
|
try {
|
unitResults.value = await searchLocalUnits(unitKeyword.value.trim())
|
}
|
catch (error) {
|
uni.showToast({
|
title: error instanceof Error ? error.message : '搜索属地单位失败',
|
icon: 'none',
|
})
|
}
|
}
|
|
function toggleArea(id: number) {
|
if (!canApprove.value) {
|
return
|
}
|
if (selectedAreaIds.value.includes(id)) {
|
selectedAreaIds.value = selectedAreaIds.value.filter(item => item !== id)
|
return
|
}
|
selectedAreaIds.value = [...selectedAreaIds.value, id]
|
}
|
|
function addLocalUnit(item: MiniLocalUnitItem) {
|
if (selectedLocalUnits.value.some(unit => unit.id === item.id)) {
|
return
|
}
|
selectedLocalUnits.value = [...selectedLocalUnits.value, item]
|
}
|
|
function removeLocalUnit(id: number) {
|
selectedLocalUnits.value = selectedLocalUnits.value.filter(item => item.id !== id)
|
}
|
|
function addNoticeUser(item: MiniVisitedUserItem) {
|
if (selectedNoticeUsers.value.some(user => user.id === item.id)) {
|
noticeKeyword.value = ''
|
noticeResults.value = []
|
return
|
}
|
selectedNoticeUsers.value = [...selectedNoticeUsers.value, item]
|
noticeKeyword.value = ''
|
noticeResults.value = []
|
}
|
|
function removeNoticeUser(id: number) {
|
selectedNoticeUsers.value = selectedNoticeUsers.value.filter(item => item.id !== id)
|
}
|
|
async function submitApproval(approve: boolean) {
|
if (!detail.value?.taskId || !detail.value.taskUuid || submitting.value) {
|
return
|
}
|
if (!comment.value.trim()) {
|
uni.showToast({ title: '请输入审批意见', icon: 'none' })
|
return
|
}
|
if (approve && isHandlerMode.value && selectedAreaIds.value.length === 0) {
|
uni.showToast({ title: '请选择通行区域', icon: 'none' })
|
return
|
}
|
if (approve && isHandlerMode.value && involveOtherLocalUnit.value && selectedLocalUnits.value.length === 0) {
|
uni.showToast({ title: '请选择属地单位', icon: 'none' })
|
return
|
}
|
|
submitting.value = true
|
try {
|
if (detail.value.pageMode === 'HANDLER_PENDING') {
|
await submitHandlerApproval({
|
taskId: detail.value.taskId,
|
taskUuid: detail.value.taskUuid,
|
approve,
|
comment: comment.value.trim(),
|
passAccessAreaIds: approve ? selectedAreaIds.value : undefined,
|
involveOtherLocalUnit: approve ? involveOtherLocalUnit.value : false,
|
passLocalUnitIds: approve && involveOtherLocalUnit.value ? selectedLocalUnits.value.map(item => item.id) : [],
|
})
|
}
|
else {
|
await submitGeneralApproval({
|
taskId: detail.value.taskId,
|
taskUuid: detail.value.taskUuid,
|
approve,
|
comment: comment.value.trim(),
|
noticeIds: isLevel3LocalDeptLeader.value ? selectedNoticeUsers.value.map(item => item.id) : undefined,
|
})
|
}
|
|
uni.showToast({
|
title: approve ? '审批通过成功' : '驳回成功',
|
icon: 'success',
|
})
|
setTimeout(() => {
|
router.back()
|
}, 800)
|
}
|
catch (error) {
|
uni.showToast({
|
title: error instanceof Error ? error.message : '审批提交失败',
|
icon: 'none',
|
})
|
}
|
finally {
|
submitting.value = false
|
}
|
}
|
|
onLoad((options) => {
|
query.taskId = options?.taskId ? Number(options.taskId) : undefined
|
query.taskUuid = options?.taskUuid || undefined
|
query.orderId = options?.orderId ? Number(options.orderId) : undefined
|
query.orderUuid = options?.orderUuid || undefined
|
void loadDetail()
|
})
|
</script>
|
|
<template>
|
<view class="min-h-screen w-full relative box-border flex flex-col pb-[160rpx]">
|
<image src="/static/viewCode/bg@2x.png" class="fixed left-0 top-0 z-0 h-full w-full pointer-events-none" mode="scaleToFill" />
|
|
<view class="fixed top-0 left-0 w-full z-50 flex flex-col overflow-hidden">
|
<image src="/static/viewCode/bg@2x.png" class="absolute left-0 top-0 w-full z-0 pointer-events-none" mode="widthFix" />
|
|
<view class="relative w-full shrink-0" :style="{ height: (statusBarHeight + navBarHeight) + 'px' }">
|
<view class="flex items-center justify-between w-full absolute left-0 z-10" :style="{ top: statusBarHeight + 'px', height: navBarHeight + 'px' }">
|
<view class="flex h-full w-[88rpx] items-center justify-center relative z-10" @click="handleBack">
|
<wd-icon name="arrow-left" size="40rpx" color="#ffffff" />
|
</view>
|
<view class="absolute left-0 w-full text-center text-[34rpx] font-medium text-white z-0">
|
预约
|
</view>
|
<view class="w-[88rpx]" />
|
</view>
|
</view>
|
</view>
|
|
<view :style="{ height: (statusBarHeight + navBarHeight) + 'px' }" class="shrink-0" />
|
|
<view class="absolute right-[24rpx] z-20 pointer-events-none" :style="{ top: (statusBarHeight + navBarHeight) - 5 + 'px' }">
|
<image :src="statusStamp" class="w-[156rpx] h-[156rpx]" mode="aspectFit" />
|
</view>
|
|
<view class="relative z-10 px-[24rpx] flex-1 mt-[32rpx]">
|
<view v-if="loading" class="text-center text-white text-[28rpx] py-[120rpx]">
|
加载中...
|
</view>
|
|
<template v-else-if="detail">
|
<view class="flex items-end mb-[26rpx] px-[8rpx]">
|
<text class="text-[32rpx] text-[#000000] font-bold">拜访信息</text>
|
<text class="text-[24rpx] text-[#676767] ml-[12rpx] mb-[4rpx]">被访问的人</text>
|
</view>
|
<view class="bg-white rounded-[8rpx] shadow-[0_4rpx_8rpx_rgba(6,47,108,0.1608)] w-full mb-[52rpx] px-[40rpx] py-[20rpx] box-border">
|
<view class="flex justify-between items-center h-[96rpx] border-b1">
|
<text class="text-[#000000] text-[28rpx]">被访人</text>
|
<text class="text-[#000000] text-[28rpx] text-right ml-[24rpx]">{{ detail.visitedUserName }}{{ detail.visitedDeptName ? `(${detail.visitedDeptName})` : '' }}</text>
|
</view>
|
<view class="flex justify-between items-center h-[96rpx]">
|
<text class="text-[#000000] text-[28rpx]">可访问区域</text>
|
<text class="text-[#000000] text-[28rpx] text-right ml-[24rpx]">{{ detail.accessAreaText || '-' }}</text>
|
</view>
|
</view>
|
|
<view class="flex items-end mb-[24rpx] px-[8rpx]">
|
<text class="text-[32rpx] text-[#000000] font-bold">来访信息</text>
|
<text class="text-[24rpx] text-[#676767] ml-[12rpx] mb-[4rpx]">访客信息</text>
|
</view>
|
<view class="bg-white rounded-[8rpx] shadow-[0_4rpx_8rpx_rgba(6,47,108,0.1608)] w-full mb-[32rpx] px-[40rpx] py-[20rpx] box-border">
|
<view class="flex justify-between items-center h-[96rpx] border-b1">
|
<text class="text-[#000000] text-[28rpx] shrink-0">来访类型</text>
|
<text class="text-[#000000] text-[28rpx] text-right truncate">{{ detail.visitTypeText }}</text>
|
</view>
|
<view class="flex justify-between items-center h-[96rpx] border-b1">
|
<text class="text-[#000000] text-[28rpx] shrink-0">来访事由</text>
|
<text class="text-[#000000] text-[28rpx] text-right truncate">{{ detail.visitReasonText }}</text>
|
</view>
|
<view class="flex justify-between items-center h-[96rpx] border-b1">
|
<text class="text-[#000000] text-[28rpx] shrink-0">来访时间</text>
|
<text class="text-[#000000] text-[24rpx] text-right truncate">{{ detail.visitTimeText }}</text>
|
</view>
|
<view class="flex justify-between items-center h-[96rpx]">
|
<text class="text-[#000000] text-[28rpx] shrink-0">来访人</text>
|
<text class="text-[#000000] text-[28rpx] text-right">合计{{ detail.visitors.length }}人</text>
|
</view>
|
|
<view class="pb-[24rpx]">
|
<view class="bg-[#F7F7F7] w-[608rpx] ml-[-16rpx] rounded-[8rpx] px-[24rpx] py-[20rpx]">
|
<view v-for="visitor in detail.visitors" :key="visitor.visitorId" class="mb-[12rpx] bg-white border-[2rpx] border-solid border-[#F5F5F5] rounded-[8rpx] p-[24rpx] shadow-[0_2rpx_8rpx_rgba(0,0,0,0.02)]">
|
<view class="flex items-center mb-[16rpx] border-b1 pb-[16rpx]">
|
<text class="text-[#000000] text-[28rpx] font-bold mr-[16rpx]">{{ visitor.name }}</text>
|
<image :src="visitorGenderIcon(visitor.gender)" class="w-[32rpx] h-[32rpx] mr-[16rpx]" mode="aspectFit" />
|
<text class="text-[#B3B3B3] text-[24rpx]">{{ visitor.mobileMasked || '-' }}</text>
|
</view>
|
<view class="flex flex-col gap-[12rpx]">
|
<view class="flex items-center">
|
<image :src="appointmentIcons.idCard" class="w-[32rpx] h-[32rpx] mr-[12rpx] shrink-0" mode="aspectFit" />
|
<text class="text-[#B3B3B3] text-[24rpx]">{{ visitor.idCardMasked || '-' }}</text>
|
</view>
|
<view class="flex items-center">
|
<image :src="appointmentIcons.company" class="w-[32rpx] h-[32rpx] mr-[12rpx] shrink-0" mode="aspectFit" />
|
<text class="text-[#B3B3B3] text-[24rpx]">{{ visitor.companyName || '-' }}</text>
|
</view>
|
<view v-if="visitor.plateNo" class="flex items-center">
|
<image :src="appointmentIcons.plate" class="w-[32rpx] h-[32rpx] mr-[12rpx] shrink-0" mode="aspectFit" />
|
<text class="text-[#B3B3B3] text-[24rpx]">{{ visitor.plateNo }}</text>
|
</view>
|
</view>
|
</view>
|
</view>
|
</view>
|
</view>
|
|
<view class="flex items-end mb-[26rpx] px-[8rpx]">
|
<text class="text-[32rpx] text-[#000000] font-bold">审批信息</text>
|
</view>
|
<view class="bg-white rounded-[8rpx] shadow-[0_4rpx_8rpx_rgba(6,47,108,0.1608)] w-full mb-[32rpx] px-[32rpx] py-[8rpx] box-border">
|
<view class="flex justify-between items-center h-[96rpx] border-b1">
|
<text class="text-[#000000] text-[28rpx]">审批状态</text>
|
<text class="text-[28rpx] text-[#F7A820]">{{ detail.approvalStatusText }}</text>
|
</view>
|
<view class="flex justify-between items-center min-h-[96rpx]" :class="isHandlerMode ? 'border-b1' : ''">
|
<text class="text-[#000000] text-[28rpx] shrink-0 w-[160rpx]">审批意见</text>
|
<textarea
|
v-model="comment"
|
:disabled="!canApprove"
|
placeholder="请输入审批意见"
|
placeholder-class="text-[#CCCCCC] text-[28rpx]"
|
class="flex-1 text-right text-[28rpx] text-[#000000] py-[20rpx] h-[120rpx]"
|
/>
|
</view>
|
|
<template v-if="canApprove && isLevel3LocalDeptLeader">
|
<view class="flex justify-between items-center min-h-[96rpx]" @click="showNoticePicker = true">
|
<text class="text-[#000000] text-[28rpx]">抄送<text class="text-[#999999] text-[24rpx]">(非必填)</text></text>
|
<view class="flex items-center max-w-[420rpx]">
|
<text class="text-[28rpx] mr-[8rpx] text-right" :class="selectedNoticeUsers.length ? 'text-[#000000]' : 'text-[#CCCCCC]'">
|
{{ selectedNoticeUsers.length ? `已选${selectedNoticeUsers.length}人` : '请选择抄送人' }}
|
</text>
|
<wd-icon name="arrow-right" size="32rpx" color="#000000" />
|
</view>
|
</view>
|
|
<view v-if="selectedNoticeUsers.length > 0" class="pb-[24rpx] flex flex-wrap gap-[16rpx]">
|
<view v-for="user in selectedNoticeUsers" :key="user.id" class="flex items-center px-[20rpx] h-[56rpx] rounded-[28rpx] bg-[#F4F8FF]">
|
<text class="text-[#0068FF] text-[24rpx]">
|
{{ user.realName }}{{ user.deptName ? `(${user.deptName})` : '' }}
|
</text>
|
<text class="text-[#0068FF] text-[24rpx] ml-[12rpx]" @click.stop="removeNoticeUser(user.id)">x</text>
|
</view>
|
</view>
|
</template>
|
|
<template v-if="isHandlerMode">
|
<view class="flex justify-between items-center min-h-[96rpx] border-b1" @click="canApprove && (showAreaPicker = true)">
|
<text class="text-[#000000] text-[28rpx]">通行区域<text class="text-[#FF4D4F]">*</text></text>
|
<view class="flex items-center max-w-[420rpx]">
|
<text class="text-[28rpx] mr-[8rpx] text-right" :class="selectedAreaNamesText ? 'text-[#000000]' : 'text-[#CCCCCC]'">{{ selectedAreaNamesText || '请选择区域' }}</text>
|
<wd-icon name="arrow-right" size="32rpx" color="#000000" />
|
</view>
|
</view>
|
|
<view class="flex justify-between items-center h-[96rpx]" :class="involveOtherLocalUnit ? 'border-b1' : ''">
|
<text class="text-[#000000] text-[28rpx]">是否涉及其他属地单位</text>
|
<view class="flex items-center gap-[40rpx]">
|
<view class="flex items-center" @click="canApprove && (involveOtherLocalUnit = true)">
|
<view class="w-[32rpx] h-[32rpx] rounded-full border-[2rpx] border-solid flex items-center justify-center mr-[12rpx] box-border" :class="involveOtherLocalUnit ? 'border-[#0068FF]' : 'border-[#CCCCCC]'">
|
<view v-if="involveOtherLocalUnit" class="w-[16rpx] h-[16rpx] rounded-full bg-[#0068FF]" />
|
</view>
|
<text class="text-[#333333] text-[28rpx]">是</text>
|
</view>
|
<view class="flex items-center" @click="canApprove && (involveOtherLocalUnit = false)">
|
<view class="w-[32rpx] h-[32rpx] rounded-full border-[2rpx] border-solid flex items-center justify-center mr-[12rpx] box-border" :class="!involveOtherLocalUnit ? 'border-[#0068FF]' : 'border-[#CCCCCC]'">
|
<view v-if="!involveOtherLocalUnit" class="w-[16rpx] h-[16rpx] rounded-full bg-[#0068FF]" />
|
</view>
|
<text class="text-[#333333] text-[28rpx]">否</text>
|
</view>
|
</view>
|
</view>
|
|
<view v-if="involveOtherLocalUnit" class="flex justify-between items-center min-h-[96rpx]" @click="canApprove && (showUnitPicker = true)">
|
<text class="text-[#000000] text-[28rpx]">其他属地单位</text>
|
<view class="flex items-center max-w-[420rpx]">
|
<text class="text-[28rpx] mr-[8rpx] text-right" :class="selectedLocalUnits.length ? 'text-[#000000]' : 'text-[#CCCCCC]'">
|
{{ selectedLocalUnits.length ? `已选${selectedLocalUnits.length}项` : '请选择单位' }}
|
</text>
|
<wd-icon name="arrow-right" size="32rpx" color="#000000" />
|
</view>
|
</view>
|
|
<view v-if="selectedLocalUnits.length > 0" class="pb-[24rpx] flex flex-wrap gap-[16rpx]">
|
<view v-for="unit in selectedLocalUnits" :key="unit.id" class="flex items-center px-[20rpx] h-[56rpx] rounded-[28rpx] bg-[#F4F8FF]">
|
<text class="text-[#0068FF] text-[24rpx]">{{ unit.unitName }}</text>
|
<text v-if="canApprove" class="text-[#0068FF] text-[24rpx] ml-[12rpx]" @click.stop="removeLocalUnit(unit.id)">x</text>
|
</view>
|
</view>
|
</template>
|
</view>
|
|
<view class="flex items-end mb-[26rpx] px-[8rpx]">
|
<text class="text-[32rpx] text-[#000000] font-bold">审批情况</text>
|
</view>
|
<view class="bg-white rounded-[8rpx] shadow-[0_4rpx_8rpx_rgba(6,47,108,0.1608)] w-full mb-[32rpx] px-[40rpx] py-[32rpx] box-border relative">
|
<view v-for="node in detail.flowTree" :key="node.nodeCode" class="pb-[28rpx]" :class="node !== detail.flowTree[detail.flowTree.length - 1] ? 'border-b1 mb-[28rpx]' : ''">
|
<view class="flex justify-between items-center mb-[16rpx]">
|
<text class="text-[28rpx] font-medium text-[#333333]">{{ node.nodeName }}</text>
|
<text class="text-[24rpx]" :class="flowStatusClass(node.nodeStatus)">{{ flowStatusText(node.nodeStatus) }}</text>
|
</view>
|
<view v-for="user in flowUsers(node)" :key="user.id" class="flex items-center justify-between py-[8rpx]">
|
<view class="flex flex-col">
|
<text class="text-[26rpx] text-[#333333]">{{ user.name }}</text>
|
<text v-if="user.groupName" class="text-[22rpx] text-[#999999]">{{ user.groupName }}</text>
|
<text
|
v-for="noticeUser in user.noticeUsers"
|
:key="noticeUser.id"
|
class="text-[22rpx] text-[#0068FF] mt-[4rpx]"
|
>
|
抄送:{{ noticeUser.realName }}{{ noticeUser.deptName ? `(${noticeUser.deptName})` : '' }}
|
</text>
|
</view>
|
<text class="text-[22rpx] text-[#999999]">{{ user.actionTime ? user.actionTime.replace('T', ' ').slice(0, 16) : flowStatusText(user.status) }}</text>
|
</view>
|
</view>
|
</view>
|
</template>
|
</view>
|
|
<view v-if="canApprove" class="fixed bottom-0 left-0 w-[750rpx] h-[108rpx] bg-[#FFFFFF] rounded-t-[20rpx] box-content pb-[env(safe-area-inset-bottom)] z-[100] shadow-[inset_0_2rpx_0_0_#E1E1E1] flex justify-end items-center">
|
<view class="w-[240rpx] h-[76rpx] rounded-full flex items-center justify-center bg-[#0068FF] shadow-[0_8rpx_24rpx_rgba(0,104,255,0.2)] active:opacity-80 transition-opacity" @click="submitApproval(true)">
|
<text class="text-white text-[30rpx] font-medium">{{ submitting ? '提交中...' : '通过' }}</text>
|
</view>
|
<view class="w-[240rpx] h-[76rpx] ml-[32rpx] rounded-full mr-[40rpx] flex items-center justify-center bg-white border-[2rpx] border-solid border-[#FF4D4F] box-border active:opacity-80 transition-opacity" @click="submitApproval(false)">
|
<text class="text-[#FF4D4F] text-[30rpx] font-medium">驳回</text>
|
</view>
|
</view>
|
|
<wd-popup v-model="showAreaPicker" position="bottom" custom-style="border-radius: 24rpx 24rpx 0 0; z-index: 9999;">
|
<view class="w-full bg-white rounded-t-[24rpx] flex flex-col h-[730rpx]">
|
<view class="flex items-center justify-between h-[108rpx] px-[32rpx] border-b border-solid border-[#F0F0F0] shrink-0">
|
<view class="w-[48rpx] h-[48rpx] flex items-center justify-center" @click="showAreaPicker = false">
|
<image src="/static/review/close@2x.png" class="w-[48rpx] h-[48rpx]" mode="aspectFit" />
|
</view>
|
<text class="text-[#333333] text-[32rpx] font-medium flex-1 text-center pr-[48rpx]">选择通行区域</text>
|
</view>
|
|
<view class="flex-1 flex overflow-hidden">
|
<scroll-view scroll-y class="w-[220rpx] bg-[#F7F9FC] h-full">
|
<view
|
v-for="parent in areaTree"
|
:key="parent.id"
|
class="px-[24rpx] py-[28rpx] text-[26rpx]"
|
:class="selectedParentId === parent.id ? 'bg-white text-[#0068FF] font-medium' : 'text-[#666666]'"
|
@click="selectedParentId = parent.id"
|
>
|
{{ parent.areaName }}
|
</view>
|
</scroll-view>
|
|
<scroll-view scroll-y class="flex-1 h-full px-[24rpx] py-[20rpx]">
|
<view
|
v-for="item in activeAreaChildren"
|
:key="item.id"
|
class="flex items-center justify-between h-[88rpx] border-b1"
|
@click="toggleArea(item.id)"
|
>
|
<text class="text-[28rpx] text-[#333333]">{{ item.areaName }}</text>
|
<view class="w-[36rpx] h-[36rpx] rounded-full border-[2rpx] border-solid flex items-center justify-center" :class="selectedAreaIds.includes(item.id) ? 'border-[#0068FF]' : 'border-[#CCCCCC]'">
|
<view v-if="selectedAreaIds.includes(item.id)" class="w-[20rpx] h-[20rpx] rounded-full bg-[#0068FF]" />
|
</view>
|
</view>
|
</scroll-view>
|
</view>
|
|
<view class="px-[32rpx] pb-[68rpx] shrink-0">
|
<view class="w-full h-[88rpx] rounded-[44rpx] bg-[#0068FF] text-white text-[32rpx] font-medium flex items-center justify-center shadow-[0_8rpx_24rpx_rgba(0,104,255,0.2)] active:opacity-80 transition-opacity" @click="showAreaPicker = false">确认</view>
|
</view>
|
</view>
|
</wd-popup>
|
|
<wd-popup v-model="showUnitPicker" position="bottom" custom-style="border-radius: 24rpx 24rpx 0 0; z-index: 9999;">
|
<view class="w-full bg-white rounded-t-[24rpx] flex flex-col h-[730rpx]">
|
<view class="flex items-center justify-between h-[108rpx] px-[32rpx] border-b border-solid border-[#F0F0F0] shrink-0">
|
<view class="w-[48rpx] h-[48rpx] flex items-center justify-center" @click="showUnitPicker = false">
|
<image src="/static/review/close@2x.png" class="w-[48rpx] h-[48rpx]" mode="aspectFit" />
|
</view>
|
<text class="text-[#333333] text-[32rpx] font-medium flex-1 text-center pr-[48rpx]">选择属地单位</text>
|
</view>
|
|
<view class="flex-1 flex flex-col px-[32rpx] overflow-hidden">
|
<view class="flex items-center justify-between h-[108rpx] border-b1 shrink-0 mb-[24rpx]">
|
<text class="text-[#333333] text-[28rpx] font-medium">单位</text>
|
<input v-model="unitKeyword" type="text" placeholder="请输入单位名称精确搜索" placeholder-class="text-[#CCCCCC] text-[28rpx]" class="flex-1 text-[28rpx] text-[#333333] ml-[32rpx]" @confirm="searchUnitsByKeyword" />
|
<view class="flex items-center h-full pl-[24rpx] active:opacity-80" @click="searchUnitsByKeyword">
|
<text class="text-[#0068FF] text-[28rpx]">搜索</text>
|
</view>
|
</view>
|
|
<scroll-view scroll-y class="flex-1 min-h-0">
|
<view v-for="unit in unitResults" :key="unit.id" class="flex items-center justify-between h-[88rpx] border-b1" @click="addLocalUnit(unit)">
|
<text class="text-[#333333] text-[28rpx]">{{ unit.unitName }}</text>
|
<text class="text-[#0068FF] text-[26rpx]">添加</text>
|
</view>
|
<view v-if="unitResults.length === 0" class="text-center text-[#999999] text-[26rpx] py-[48rpx]">
|
暂无结果
|
</view>
|
</scroll-view>
|
</view>
|
|
<view class="px-[32rpx] pb-[68rpx] shrink-0">
|
<view class="w-full h-[88rpx] rounded-[44rpx] bg-[#0068FF] text-white text-[32rpx] font-medium flex items-center justify-center shadow-[0_8rpx_24rpx_rgba(0,104,255,0.2)] active:opacity-80 transition-opacity" @click="showUnitPicker = false">确认</view>
|
</view>
|
</view>
|
</wd-popup>
|
|
<wd-popup v-model="showNoticePicker" position="bottom" custom-style="border-radius: 24rpx 24rpx 0 0; z-index: 9999;">
|
<view class="w-full bg-white rounded-t-[24rpx] flex flex-col h-[730rpx]">
|
<view class="flex items-center justify-between h-[108rpx] px-[32rpx] border-b border-solid border-[#F0F0F0] shrink-0">
|
<view class="w-[48rpx] h-[48rpx] flex items-center justify-center" @click="showNoticePicker = false">
|
<image src="/static/review/close@2x.png" class="w-[48rpx] h-[48rpx]" mode="aspectFit" />
|
</view>
|
<text class="text-[#333333] text-[32rpx] font-medium flex-1 text-center pr-[48rpx]">选择抄送人</text>
|
</view>
|
|
<view class="flex-1 flex flex-col px-[32rpx] overflow-hidden">
|
<view class="flex items-center justify-between h-[108rpx] border-b1 shrink-0 mb-[24rpx]">
|
<text class="text-[#333333] text-[28rpx] font-medium">抄送人</text>
|
<input v-model="noticeKeyword" type="text" placeholder="请输入被访人姓名精确搜索" placeholder-class="text-[#CCCCCC] text-[28rpx]" class="flex-1 text-[28rpx] text-[#333333] ml-[32rpx]" @confirm="searchNoticeUsersByKeyword" />
|
<view class="flex items-center h-full pl-[24rpx] active:opacity-80" @click="searchNoticeUsersByKeyword">
|
<text class="text-[#0068FF] text-[28rpx]">搜索</text>
|
</view>
|
</view>
|
|
<scroll-view scroll-y class="flex-1 min-h-0">
|
<view v-for="user in noticeResults" :key="user.id" class="flex items-center justify-between h-[88rpx] border-b1" @click="addNoticeUser(user)">
|
<view class="flex flex-col">
|
<text class="text-[#333333] text-[28rpx]">{{ user.realName }}</text>
|
<text v-if="user.deptName" class="text-[#999999] text-[22rpx] mt-[4rpx]">{{ user.deptName }}</text>
|
</view>
|
<text class="text-[#0068FF] text-[26rpx]">{{ selectedNoticeUsers.some(item => item.id === user.id) ? '已添加' : '添加' }}</text>
|
</view>
|
<view v-if="noticeResults.length === 0" class="text-center text-[#999999] text-[26rpx] py-[48rpx]">
|
暂无结果
|
</view>
|
</scroll-view>
|
</view>
|
|
<view class="px-[32rpx] pb-[68rpx] shrink-0">
|
<view class="w-full h-[88rpx] rounded-[44rpx] bg-[#0068FF] text-white text-[32rpx] font-medium flex items-center justify-center shadow-[0_8rpx_24rpx_rgba(0,104,255,0.2)] active:opacity-80 transition-opacity" @click="showNoticePicker = false">确认</view>
|
</view>
|
</view>
|
</wd-popup>
|
</view>
|
</template>
|
|
<style scoped lang="scss">
|
.border-b1 {
|
border-bottom: 2rpx solid #F5F5F5;
|
}
|
</style>
|
|
<style>
|
.pending {
|
color: #0068FF;
|
}
|
|
.approved {
|
color: #52C41A;
|
}
|
|
.rejected {
|
color: #FF4D4F;
|
}
|
|
.cancelled,
|
.skipped,
|
.waiting {
|
color: #999999;
|
}
|
</style>
|