Files
gxtyzx-uniapp/src/packageApproval/pages/detail.vue
T

442 lines
10 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 class="detail-page">
<view v-if="detail" class="content">
<uni-card :border="false" :padding="'16px'">
<view class="head">
<text class="title">{{ detail.title || detail.name || '单据详情' }}</text>
<text class="status" :class="'status-' + detail.status">
{{ detail.statusName }}
</text>
</view>
<view class="rows">
<view class="row">
<text class="label">单号</text>
<text class="value">{{ detail.bizNo || '—' }}</text>
</view>
<view class="row">
<text class="label">单据类型</text>
<text class="value">{{ detail.docTypeName }}</text>
</view>
<view class="row">
<text class="label">报送人</text>
<text class="value">{{ detail.reporterName || '—' }}</text>
</view>
<view class="row">
<text class="label">报送部门</text>
<text class="value">{{ detail.reportOrgName || '—' }}</text>
</view>
<view class="row">
<text class="label">当前节点</text>
<text class="value">{{ detail.currentNodeName || '—' }}</text>
</view>
<view class="row">
<text class="label">提交时间</text>
<text class="value">{{ formatTime(detail.submitTime) }}</text>
</view>
</view>
</uni-card>
<uni-card :border="false" :padding="'16px'" title="单据信息">
<view v-if="detail.docType === 'completion'" class="rows">
<view class="row">
<text class="label">名称</text>
<text class="value">{{ detail.name }}</text>
</view>
<view class="row">
<text class="label">实施单位</text>
<text class="value">{{ detail.implementOrg }}</text>
</view>
<view class="row">
<text class="label">费用(元)</text>
<text class="value">{{ formatFee(detail.fee) }}</text>
</view>
<view class="row">
<text class="label">服务内容</text>
<text class="value multiline">{{ detail.serviceContent }}</text>
</view>
</view>
<view v-else class="rows">
<view class="row">
<text class="label">受理部门</text>
<text class="value">{{ detail.acceptOrgName || '—' }}</text>
</view>
<view class="row">
<text class="label">标题</text>
<text class="value">{{ detail.title }}</text>
</view>
<view class="row">
<text class="label">内容</text>
<text class="value multiline">{{ detail.content }}</text>
</view>
<view class="row">
<text class="label">备注</text>
<text class="value multiline">{{ detail.remark || '—' }}</text>
</view>
</view>
</uni-card>
<uni-card :border="false" :padding="'16px'" title="附件">
<view
v-for="item in detail.attachments"
:key="item.id"
class="attachment"
@click="openAttachment(item.url)"
>
<view class="attachment-main">
<text class="attachment-name">{{ item.name }}</text>
<text class="attachment-meta">
{{ item.categoryName }} · {{ formatSize(item.length) }}
</text>
</view>
<text class="link">查看</text>
</view>
<view v-if="detail.attachments.length === 0" class="empty">暂无附件</view>
</uni-card>
<uni-card :border="false" :padding="'16px'" title="处理记录">
<view v-for="item in detail.records" :key="item.id" class="timeline-item">
<view class="timeline-dot"></view>
<view class="timeline-body">
<view class="timeline-head">
<text class="timeline-operator">{{ item.operatorName || '—' }}</text>
<text class="timeline-action">{{ item.actionName }}</text>
</view>
<text class="timeline-node">{{ item.nodeName || '提交' }}</text>
<text v-if="item.opinion" class="timeline-opinion">意见:{{ item.opinion }}</text>
<text class="timeline-time">{{ formatTime(item.createTime) }}</text>
</view>
</view>
<view v-if="detail.records.length === 0" class="empty">暂无记录</view>
</uni-card>
<view v-if="detail.permissions.canEdit || detail.permissions.canSubmit" class="actions">
<button
v-if="detail.permissions.canEdit"
class="action-button ghost"
@click="editDraft"
>
编辑
</button>
<button
v-if="detail.permissions.canSubmit"
class="action-button primary"
:loading="submitting"
@click="submitDraft"
>
提交
</button>
</view>
</view>
<view v-else class="loading">
<text>{{ loading ? '加载中...' : '单据不存在' }}</text>
</view>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { getInstance, submitInstance } from '@/api/approval/instance';
import type { ApprovalInstance } from '@/api/approval/model';
import { showError } from '@/utils/app';
const detail = ref<ApprovalInstance | null>(null);
const loading = ref(false);
const submitting = ref(false);
const instanceId = ref(0);
onLoad(options => {
instanceId.value = Number(options?.id) || 0;
load();
});
async function load() {
if (!instanceId.value) {
return;
}
loading.value = true;
try {
detail.value = await getInstance(instanceId.value);
} catch (error: any) {
showError(error?.message || '加载失败', null);
} finally {
loading.value = false;
}
}
function editDraft() {
uni.navigateTo({
url:
'/packageApproval/pages/form-completion?mode=edit&id=' + instanceId.value
});
}
function submitDraft() {
uni.showModal({
title: '提交确认',
content: '提交后字段与附件将锁定,确认提交?',
success: async result => {
if (!result.confirm) {
return;
}
submitting.value = true;
try {
const response = await submitInstance(instanceId.value);
uni.showToast({
title: response.message || '提交成功',
icon: 'none'
});
await load();
} catch (error: any) {
showError(error?.message || '提交失败', null);
} finally {
submitting.value = false;
}
}
});
}
function openAttachment(url?: string) {
if (!url) {
return;
}
// #ifdef H5
window.open(url, '_blank');
// #endif
// #ifndef H5
uni.downloadFile({
url,
success: result => {
if (result.statusCode === 200) {
uni.openDocument({
filePath: result.tempFilePath,
showMenu: true
});
}
},
fail: () => uni.showToast({ title: '附件打开失败', icon: 'none' })
});
// #endif
}
function formatTime(value?: string) {
if (!value) {
return '—';
}
return value.replace('T', ' ').substring(0, 19);
}
function formatFee(value?: number) {
if (value === undefined || value === null) {
return '—';
}
return Number(value).toFixed(2);
}
function formatSize(value?: number) {
if (!value) {
return '0 B';
}
if (value < 1024) {
return value + ' B';
}
if (value < 1024 * 1024) {
return (value / 1024).toFixed(1) + ' KB';
}
return (value / 1024 / 1024).toFixed(1) + ' MB';
}
</script>
<style lang="scss">
.detail-page {
min-height: 100vh;
background-color: #f5f6f8;
padding-bottom: 180rpx;
}
.head {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 16rpx;
}
.title {
flex: 1;
font-size: 32rpx;
font-weight: 600;
color: #303133;
}
.status {
font-size: 24rpx;
padding: 4rpx 14rpx;
border-radius: 999rpx;
background: #f2f3f5;
color: #606266;
}
.status-PENDING {
background: #fff7e6;
color: #d46b08;
}
.status-REJECTED {
background: #fff1f0;
color: #cf1322;
}
.status-APPROVED {
background: #f6ffed;
color: #389e0d;
}
.status-ARCHIVED {
background: #e6f4ff;
color: #0958d9;
}
.rows {
display: flex;
flex-direction: column;
}
.row {
display: flex;
align-items: flex-start;
margin-top: 12rpx;
font-size: 26rpx;
}
.label {
width: 160rpx;
color: #909399;
flex-shrink: 0;
}
.value {
flex: 1;
color: #303133;
word-break: break-all;
}
.multiline {
white-space: pre-wrap;
line-height: 1.6;
}
.attachment {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.attachment-main {
display: flex;
flex-direction: column;
flex: 1;
}
.attachment-name {
font-size: 26rpx;
color: #303133;
word-break: break-all;
}
.attachment-meta {
margin-top: 6rpx;
font-size: 24rpx;
color: #909399;
}
.link {
font-size: 26rpx;
color: #d81e06;
margin-left: 16rpx;
}
.timeline-item {
display: flex;
padding: 12rpx 0;
}
.timeline-dot {
width: 16rpx;
height: 16rpx;
margin: 10rpx 16rpx 0 0;
border-radius: 50%;
background: #d81e06;
flex-shrink: 0;
}
.timeline-body {
flex: 1;
display: flex;
flex-direction: column;
}
.timeline-head {
display: flex;
align-items: center;
gap: 12rpx;
}
.timeline-operator {
font-size: 28rpx;
color: #303133;
font-weight: 600;
}
.timeline-action {
font-size: 24rpx;
color: #d81e06;
}
.timeline-node,
.timeline-opinion,
.timeline-time {
margin-top: 6rpx;
font-size: 24rpx;
color: #909399;
}
.empty {
padding: 24rpx 0;
text-align: center;
font-size: 26rpx;
color: #909399;
}
.actions {
display: flex;
gap: 24rpx;
padding: 24rpx 32rpx 48rpx;
}
.action-button {
flex: 1;
font-size: 30rpx;
border-radius: 12rpx;
}
.action-button.primary {
background: #d81e06;
color: #ffffff;
}
.action-button.ghost {
background: #ffffff;
color: #303133;
border: 1rpx solid #dcdfe6;
}
.loading {
padding: 160rpx 0;
text-align: center;
color: #909399;
font-size: 28rpx;
}
</style>