factory_mp/pages/wf/ticketd.vue

383 lines
8.7 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>
<view v-if="ticket_" class="ticket-status-wrap">
<!-- 审批状态摘要 -->
<view class="ticket-status-row">
<view class="ticket-status-left">
<text class="ticket-label">审批状态</text>
<uni-tag :text="actStateEnum[ticket_?.act_state]?.text" :circle="true" size="small"
:type="actStateEnum[ticket_?.act_state]?.type" :inverted="true" style="font-weight: 500;"></uni-tag>
<text class="ticket-state-name">{{ticket_?.state_.name}}</text>
</view>
<view class="ticket-detail-btn" @click="handleDetail">
<text class="ticket-detail-text">详情</text>
<text class="ticket-detail-arrow"></text>
</view>
</view>
<!-- 撤回操作 -->
<view v-if="ticket_?.state_.enable_retreat && isOwn" class="ticket-action-row">
<button class="ticket-retreat-btn" @click="handleRetreat">撤回申请</button>
</view>
<!-- 可处理人 -->
<view v-if="ticket_.participant_" class="ticket-participant-row">
<text class="ticket-label">处理人</text>
<view class="ticket-participant-list" v-if="ticket_.participant_type == 2 || ticket_.participant_type == 1">
<text v-for="item in ticket_.participant_" :key="item.id" class="ticket-participant-tag">{{ item.name }}</text>
</view>
<text v-else class="ticket-participant-none">无</text>
</view>
<!-- 审批详情弹窗 -->
<uni-popup ref="popup" background-color="#fff">
<view class="ticket-popup">
<view class="ticket-popup-header">
<text class="ticket-popup-title">审批流程</text>
<view class="ticket-popup-status">
<uni-tag :text="actStateEnum[ticket_f?.act_state]?.text" :circle="true" size="small"
:type="actStateEnum[ticket_f?.act_state]?.type" :inverted="true" style="font-weight: 500;"></uni-tag>
<text class="ticket-state-name">{{ticket_f?.state_.name}}</text>
<button v-if="ticket_f?.state_.enable_retreat && isOwn" class="ticket-retreat-btn-sm" @click="handleRetreat">撤回</button>
</view>
</view>
<!-- 可处理人 -->
<view v-if="ticket_f.participant_" class="ticket-participant-row" style="margin-bottom: 24rpx;">
<text class="ticket-label">当前处理人</text>
<view class="ticket-participant-list" v-if="ticket_f.participant_type == 2 || ticket_f.participant_type == 1">
<text v-for="item in ticket_f.participant_" :key="item.id" class="ticket-participant-tag">{{ item.name }}</text>
</view>
<text v-else class="ticket-participant-none">无</text>
</view>
<!-- 时间线 -->
<scroll-view scroll-y="true" class="ticket-timeline-scroll">
<view class="ticket-timeline">
<view v-for="(item, idx) in flowsteps" :key="item.id" class="timeline-item">
<view class="timeline-dot-wrap">
<view class="timeline-dot" :class="{'timeline-dot-success': item.transition_?.attribute_type === 1, 'timeline-dot-reject': item.transition_?.attribute_type === 2}"></view>
<view v-if="idx < flowsteps.length - 1" class="timeline-line"></view>
</view>
<view class="timeline-content">
<view class="timeline-header">
<text class="timeline-state">{{ item.state_?.name || "未知状态" }}</text>
<text v-if="item.transition_" class="timeline-transition"
:class="{'text-success': item.transition_.attribute_type === 1, 'text-danger': item.transition_.attribute_type !== 1}">
{{ item.transition_.name }}
</text>
</view>
<view class="timeline-meta">
<text class="timeline-time">{{item.create_time}}</text>
<text v-if="item.participant_" class="timeline-person">{{ item.participant_.name }}</text>
<uni-tag v-if="item.intervene_type != 0"
:type="interveneTypeEnum[item.intervene_type]?.type" :inverted="true" :circle="true"
:text="interveneTypeEnum[item.intervene_type]?.text" size="small" style="font-weight: 500;">
</uni-tag>
</view>
<view v-if="item.suggestion" class="timeline-suggestion">
<text>{{ item.suggestion.length > 40 ? item.suggestion.slice(0, 40) + '...' : item.suggestion }}</text>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import {
ref,
reactive,
onMounted,
defineEmits,
watch
} from 'vue'
import API from '@/utils/api';
import {
actStateEnum, interveneTypeEnum
} from "@/utils/enum.js"
const props = defineProps({
ticket_: {
type: Object,
default: null,
required: false
}
})
const ticket_f = ref({});
const isOwn = ref(false);
onMounted(() => {
watch(
() => props.ticket_,
async (newVal) => {
if (newVal && Object.keys(newVal).length > 0) {
if(props.ticket_.create_by === uni.getStorageSync("userInfo").id){
isOwn.value = true;
}
}
},
{ immediate: false, deep: true }
)
})
const popup = ref(null);
const flowsteps = ref([]);
const handleDetail = async () => {
ticket_f.value = await API.getTicketItem(props.ticket_.id)
popup.value.open("top");
API.getTicketFlowLogs(props.ticket_.id).then(res=>{
flowsteps.value = res
})
}
const handleRetreat = () => {
uni.showModal({
title: "撤回原因",
editable: true,
success(res) {
if(res.confirm){
API.ticketRetreat(props.ticket_.id, {"suggestion": res.content}).then(res=>{
uni.navigateBack()
})
}
}
})
}
</script>
<style lang="scss" scoped>
.ticket-status-wrap {
padding: 4rpx 0;
}
.ticket-status-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16rpx 0;
}
.ticket-status-left {
display: flex;
align-items: center;
gap: 12rpx;
}
.ticket-label {
font-size: 26rpx;
color: #9CA3AF;
margin-right: 8rpx;
}
.ticket-state-name {
font-size: 26rpx;
color: #4B5563;
margin-left: 8rpx;
}
.ticket-detail-btn {
display: flex;
align-items: center;
padding: 8rpx 16rpx;
background: #E0F5EA;
border-radius: 24rpx;
}
.ticket-detail-text {
font-size: 24rpx;
color: #2BA471;
}
.ticket-detail-arrow {
font-size: 28rpx;
color: #2BA471;
margin-left: 4rpx;
}
.ticket-action-row {
padding: 8rpx 0;
}
.ticket-retreat-btn {
display: inline-block;
font-size: 24rpx;
color: #EF4444;
background: #FEF2F2;
border: 1rpx solid #FECACA;
border-radius: 12rpx;
padding: 8rpx 24rpx;
line-height: 1.5;
}
.ticket-retreat-btn-sm {
display: inline-block;
font-size: 22rpx;
color: #EF4444;
background: #FEF2F2;
border: 1rpx solid #FECACA;
border-radius: 10rpx;
padding: 4rpx 16rpx;
margin-left: 12rpx;
line-height: 1.5;
}
.ticket-participant-row {
display: flex;
align-items: center;
padding: 12rpx 0;
flex-wrap: wrap;
gap: 8rpx;
}
.ticket-participant-list {
display: flex;
flex-wrap: wrap;
gap: 8rpx;
}
.ticket-participant-tag {
font-size: 24rpx;
color: #2BA471;
background: #C4EACF;
padding: 4rpx 16rpx;
border-radius: 16rpx;
border: 1rpx solid #6CC294;
}
.ticket-participant-none {
font-size: 24rpx;
color: #9CA3AF;
}
// ========== 弹窗样式 ==========
.ticket-popup {
padding: 32rpx 28rpx;
max-height: 70vh;
}
.ticket-popup-header {
margin-bottom: 24rpx;
padding-bottom: 20rpx;
border-bottom: 1rpx solid #F3F4F6;
}
.ticket-popup-title {
font-size: 34rpx;
font-weight: 600;
color: #1F2937;
display: block;
margin-bottom: 16rpx;
}
.ticket-popup-status {
display: flex;
align-items: center;
gap: 8rpx;
}
// ========== 时间线 ==========
.ticket-timeline-scroll {
max-height: 480rpx;
}
.ticket-timeline {
padding-left: 8rpx;
}
.timeline-item {
display: flex;
min-height: 100rpx;
}
.timeline-dot-wrap {
display: flex;
flex-direction: column;
align-items: center;
width: 40rpx;
flex-shrink: 0;
}
.timeline-dot {
width: 20rpx;
height: 20rpx;
border-radius: 50%;
background: #D1D5DB;
border: 3rpx solid #E5E7EB;
flex-shrink: 0;
}
.timeline-dot-success {
background: #10B981;
border-color: #A7F3D0;
}
.timeline-dot-reject {
background: #EF4444;
border-color: #FECACA;
}
.timeline-line {
width: 2rpx;
flex: 1;
background: #E5E7EB;
margin: 4rpx 0;
}
.timeline-content {
flex: 1;
padding: 0 0 24rpx 16rpx;
}
.timeline-header {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 6rpx;
}
.timeline-state {
font-size: 28rpx;
font-weight: 600;
color: #1F2937;
}
.timeline-transition {
font-size: 24rpx;
font-weight: 500;
}
.text-success { color: #10B981; }
.text-danger { color: #EF4444; }
.timeline-meta {
display: flex;
align-items: center;
gap: 12rpx;
flex-wrap: wrap;
}
.timeline-time {
font-size: 22rpx;
color: #9CA3AF;
}
.timeline-person {
font-size: 22rpx;
color: #6B7280;
background: #F3F4F6;
padding: 2rpx 12rpx;
border-radius: 8rpx;
}
.timeline-suggestion {
margin-top: 8rpx;
padding: 12rpx 16rpx;
background: #F9FAFB;
border-radius: 10rpx;
border-left: 4rpx solid #D1D5DB;
font-size: 24rpx;
color: #6B7280;
}
</style>