| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520 |
- <template>
- <view class="detail-container">
- <!-- 头部卡片:标题 + 基本信息 -->
- <view class="header-card">
- <view class="title">{{ rowData.instTitle || '未命名流程' }}</view>
- </view>
- <!-- 标签页:单据信息 / 审批记录 / 审批意见(审批模式) -->
- <view class="tabs-container">
- <uv-tabs
- :list="tabList"
- :current="currentTab"
- @click="handleTabClick"
- lineColor="#1c9bfd"
- activeColor="#1c9bfd"
- inactiveColor="#666"
- :scrollable="false"
- ></uv-tabs>
- </view>
- <!-- 内容区域 -->
- <scroll-view class="content-area" scroll-y>
- <view class="component-wrapper">
- <!-- 单据信息 -->
- <view v-if="currentTab === 0">
- <!-- 详细单据内容 (由 defCode 和 taskCode 驱动) -->
- <DocumentInfoDisplay
- v-if="defCode && taskCode"
- :defCode="defCode"
- :taskCode="taskCode"
- />
- </view>
- <!-- 审批记录 -->
- <view v-else-if="currentTab === 1">
- <ApprovalFlowTable
- v-if="instanceId"
- :id="instanceId"
- />
- </view>
- <!-- 审批意见(仅审批模式显示) -->
- <view v-else-if="currentTab === 2 && canApprove">
- <view class="section-card">
- <view class="section-title">审批处理</view>
- <view class="form-item">
- <view class="form-label"><text class="required" style="margin-right: 4rpx; margin-left: 0;">*</text>审批意见</view>
- <textarea
- v-model="approveForm.approveOpinion"
- class="form-textarea"
- placeholder="请输入审批意见"
- maxlength="500"
- :show-confirm-bar="false"
- />
- </view>
- <!-- 结算金额(仅报销单据且有权限时显示) -->
- <view class="form-item" v-if="showSettleAmount">
- <view class="form-label">结算金额(元)</view>
- <uv-input
- v-model="approveForm.settleAmount"
- type="number"
- placeholder="请输入结算金额"
- :customStyle="{ backgroundColor: '#f5f7fa' }"
- ></uv-input>
- </view>
- <!-- 附件上传(如果流程需要附件) -->
- <view class="form-item" v-if="rowData.isFile">
- <view class="form-label">附件 <text class="tip" style="font-size: 24rpx; color: #999; font-weight: normal; margin-left: 10rpx;">(仅“通过”时必传)</text></view>
- <view class="upload-area">
- <uv-upload
- :fileList="fileList"
- :maxCount="1"
- :action="uploadUrl"
- :header="uploadHeaders"
- @afterRead="afterRead"
- @delete="deleteFile"
- >
- <view class="upload-btn">
- <uv-icon name="plus" size="40"></uv-icon>
- <text class="upload-text">点击上传</text>
- </view>
- </uv-upload>
- </view>
- </view>
- <!-- 操作按钮移入此处 -->
- <view class="approval-actions">
- <uv-button
- type="error"
- text="拒绝"
- plain
- @click="submitApprove('rejection')"
- ></uv-button>
- <uv-button
- type="primary"
- text="通过"
- @click="submitApprove('pass')"
- ></uv-button>
- </view>
- </view>
- </view>
- </view>
- </scroll-view>
- </view>
- </template>
- <script setup lang="ts">
- import { onLoad } from '@dcloudio/uni-app';
- import { ref, computed } from 'vue';
- import { useExecutionApi } from '@/api/execution';
- import { formatDate } from '@/utils/date';
- import ApprovalFlowTable from '@/pages/todo/components/ApprovalFlowTable.vue';
- import DocumentInfoDisplay from '@/pages/todo/components/DocumentInfoDisplay.vue';
- import to from 'await-to-js';
- const executionApi = useExecutionApi();
- // 动态生成标签页列表
- const tabList = computed(() => {
- const tabs = [
- { name: '单据信息', index: 0 },
- { name: '审批记录', index: 1 }
- ];
- // 如果是审批模式,添加"审批意见"标签
- if (canApprove.value) {
- tabs.push({ name: '审批意见', index: 2 });
- }
- return tabs;
- });
- const currentTab = ref(0);
- const handleTabClick = (item: any) => {
- currentTab.value = item.index;
- };
- // 列表行数据(从待办页透传)
- const rowData = ref<any>({});
- // 流程实例 & 流程信息
- const instanceId = ref<number | null>(null);
- const taskCode = ref('');
- const defCode = ref('');
- // 审批相关
- const taskId = ref<number | null>(null);
- const mode = ref<'view' | 'approval'>('view');
- // 审批表单
- const approveForm = ref({
- approveOpinion: '',
- settleAmount: null as number | null,
- fileUrl: ''
- });
- // 文件列表
- const fileList = ref<any[]>([]);
- // 上传地址和请求头
- const uploadUrl = ref(import.meta.env.VITE_UPLOAD || '');
- const uploadHeaders = ref({
- Authorization: uni.getStorageSync('ACCESS_TOKEN') || ''
- });
- // 是否可以审批
- const canApprove = computed(() => {
- return (
- mode.value === 'approval' &&
- !!taskId.value &&
- rowData.value &&
- rowData.value.isFinish === '20'
- );
- });
- // 是否显示结算金额(报销单据且有权限)
- const showSettleAmount = computed(() => {
- // 这里可以根据权限 and 单据类型判断
- // 对应PC端的 auth('sci_fund_expense_settleAmount') && defCode === 'sci_fund_expense'
- return defCode.value === 'sci_fund_expense';
- });
- onLoad(async (options: any) => {
- // 从参数中提取基础信息
- taskCode.value = options.taskCode || '';
- defCode.value = options.defCode || '';
- instanceId.value = options.id ? Number(options.id) : null;
- taskId.value = options.taskId ? Number(options.taskId) : null;
- mode.value = (options.mode === 'approval' ? 'approval' : 'view');
- // 构建默认展示用的 rowData
- rowData.value = {
- businessCode: taskCode.value,
- taskId: taskId.value,
- defCode: defCode.value
- };
- // 如果有实例 ID,则去后台拉取最新的流程实例详情并回填头部元数据
- if (instanceId.value) {
- try {
- const res: any = await executionApi.getInstanceById({ id: instanceId.value });
- if (res?.data) {
- const d = res.data;
- rowData.value = {
- ...rowData.value,
- instTitle: d.instTitle || '',
- defName: d.defName || '',
- startUserName: d.startUserName || '',
- createdTime: d.createdTime || '',
- isFinish: d.isFinish || '',
- isFile: d.isFile || '' // 是否需要附件
- };
- }
- } catch (e) {
- console.error('Failed to fetch instance detail:', e);
- }
- }
- });
- // 文件上传后的回调
- const afterRead = (event: any) => {
- const { file } = event;
- // 这里可以处理上传逻辑
- uni.uploadFile({
- url: uploadUrl.value,
- filePath: file.url,
- name: 'file',
- header: uploadHeaders.value,
- success: (res) => {
- const data = JSON.parse(res.data);
- if (data.Data) {
- approveForm.value.fileUrl = data.Data;
- fileList.value = [{
- url: data.Data,
- name: file.name
- }];
- uni.showToast({ title: '上传成功', icon: 'success' });
- }
- },
- fail: () => {
- uni.showToast({ title: '上传失败', icon: 'none' });
- }
- });
- };
- // 删除文件
- const deleteFile = () => {
- approveForm.value.fileUrl = '';
- fileList.value = [];
- };
- // 提交审批
- const submitApprove = async (result: 'pass' | 'rejection') => {
- if (!taskId.value) {
- uni.showToast({ title: '缺少任务ID', icon: 'none' });
- return;
- }
- if (!approveForm.value.approveOpinion || !approveForm.value.approveOpinion.trim()) {
- uni.showToast({ title: '请输入审批意见', icon: 'none' });
- return;
- }
- // 如果需要附件且通过,检查是否上传
- if (rowData.value.isFile && result === 'pass' && !approveForm.value.fileUrl) {
- uni.showToast({ title: '请上传必要的附件', icon: 'none' });
- return;
- }
- uni.showModal({
- title: '提示',
- content: `确定要 ${result === 'pass' ? '通过' : '拒绝'} 该申请吗?`,
- success: async (res) => {
- if (res.confirm) {
- uni.showLoading({ title: '提交中...', mask: true });
-
- // 构建提交参数
- const params: any = {
- taskId: taskId.value,
- result: result,
- opinion: approveForm.value.approveOpinion || (result === 'pass' ? '同意' : '不同意')
- };
- // 如果有结算金额
- if (showSettleAmount.value && approveForm.value.settleAmount !== null) {
- params.settleAmount = approveForm.value.settleAmount;
- }
- // 如果有附件
- if (approveForm.value.fileUrl) {
- params.fileUrl = approveForm.value.fileUrl;
- }
- const [err]: any = await to(executionApi.approve(params));
- uni.hideLoading();
- if (err) {
- uni.showToast({
- title: err?.message || '提交失败',
- icon: 'none'
- });
- return;
- }
- uni.showToast({
- title: '提交成功',
- icon: 'success'
- });
-
- setTimeout(() => {
- uni.navigateBack();
- }, 800);
- }
- }
- });
- };
- </script>
- <style lang="scss" scoped>
- .detail-container {
- height: 100vh;
- display: flex;
- flex-direction: column;
- background-color: #f5f7fa;
- box-sizing: border-box;
- }
- .header-card {
- flex-shrink: 0;
- background: linear-gradient(135deg, #1c9bfd 0%, #15a982 100%);
- padding: 40rpx 30rpx 60rpx;
- color: #fff;
- border-bottom-left-radius: 40rpx;
- border-bottom-right-radius: 40rpx;
- box-shadow: 0 10rpx 20rpx rgba(28, 155, 253, 0.2);
- .title {
- font-size: 36rpx;
- font-weight: bold;
- margin-bottom: 20rpx;
- line-height: 1.4;
- }
- .meta-row {
- display: flex;
- justify-content: space-between;
- align-items: center;
- .left {
- .meta-text {
- font-size: 26rpx;
- opacity: 0.9;
- line-height: 1.6;
- }
- }
- .right {
- .status-tag {
- font-size: 24rpx;
- padding: 6rpx 18rpx;
- border-radius: 30rpx;
- border: 2rpx solid rgba(255, 255, 255, 0.7);
- &.status-done {
- background-color: rgba(82, 196, 26, 0.2);
- }
- &.status-undo {
- background-color: rgba(250, 140, 22, 0.2);
- }
- }
- }
- }
- }
- .tabs-container {
- flex-shrink: 0;
- margin: -30rpx 30rpx 20rpx;
- background-color: #fff;
- border-radius: 16rpx;
- padding: 10rpx 0;
- box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
- position: relative;
- z-index: 10;
- }
- .content-area {
- flex: 1;
- height: 0;
- }
- .component-wrapper {
- margin: 0 30rpx;
- }
- .section-card {
- background-color: #fff;
- border-radius: 16rpx;
- padding: 30rpx;
- box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.03);
- margin-bottom: 24rpx;
- .section-title {
- font-size: 32rpx;
- font-weight: 600;
- color: #343A3F;
- margin-bottom: 24rpx;
- padding-left: 20rpx;
- position: relative;
- &::before {
- content: '';
- position: absolute;
- left: 0;
- top: 50%;
- transform: translateY(-50%);
- width: 8rpx;
- height: 32rpx;
- background-color: #1c9bfd;
- border-radius: 4rpx;
- }
- }
- .info-row {
- display: flex;
- padding: 16rpx 0;
- border-bottom: 2rpx dashed #ececec;
- font-size: 26rpx;
- &:last-child {
- border-bottom: none;
- }
- .label {
- color: #343A3F;
- width: 180rpx;
- flex-shrink: 0;
- }
- .value {
- color: #585858;
- flex: 1;
- text-align: right;
- word-break: break-all;
- }
- }
- // 表单项样式
- .form-item {
- margin-bottom: 32rpx;
- &:last-child {
- margin-bottom: 0;
- }
- .form-label {
- font-size: 28rpx;
- color: #343A3F;
- margin-bottom: 16rpx;
- font-weight: 500;
- .required {
- color: #f56c6c;
- margin-left: 4rpx;
- }
- }
- .form-textarea {
- width: 100%;
- min-height: 200rpx;
- padding: 20rpx;
- border-radius: 12rpx;
- background-color: #f5f7fa;
- font-size: 26rpx;
- box-sizing: border-box;
- border: 2rpx solid #e4e7ed;
- color: #585858;
- &:focus {
- border-color: #1c9bfd;
- background-color: #fff;
- }
- }
- .upload-area {
- .upload-btn {
- width: 160rpx;
- height: 160rpx;
- border: 2rpx dashed #dcdfe6;
- border-radius: 12rpx;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- background-color: #fafafa;
- .upload-text {
- font-size: 24rpx;
- color: #999;
- margin-top: 8rpx;
- }
- }
- }
- }
- }
- .mt20 {
- margin-top: 20rpx;
- }
- /* Bottom Actions - Integrated into Form */
- .approval-actions {
- display: flex;
- gap: 24rpx;
- margin-top: 60rpx;
- padding-top: 30rpx;
- border-top: 1rpx solid #f1f5f9;
- }
- </style>
|