增强办公,新增审批,缺少电子签章功能
This commit is contained in:
@@ -16,6 +16,13 @@ export function getSealReq(bizId) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getSealPdfPages(bizId) {
|
||||||
|
return request({
|
||||||
|
url: `/hrm/seal/${bizId}/pdfPages`,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function addSealReq(data) {
|
export function addSealReq(data) {
|
||||||
return request({
|
return request({
|
||||||
url: '/hrm/seal',
|
url: '/hrm/seal',
|
||||||
|
|||||||
@@ -48,6 +48,14 @@
|
|||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template v-else-if="field.type === 'sealPreview'">
|
||||||
|
<view class="seal-preview-box" v-if="getSealPreviewSrc(field)">
|
||||||
|
<image class="seal-preview-image" :src="getSealPreviewSrc(field)" mode="aspectFit"></image>
|
||||||
|
<view class="seal-preview-text">盖章示例(当前选择)</view>
|
||||||
|
</view>
|
||||||
|
<view class="input-like" v-else>请先选择用印类型</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template v-else-if="field.type === 'file'">
|
<template v-else-if="field.type === 'file'">
|
||||||
<view class="file-picker-wrap">
|
<view class="file-picker-wrap">
|
||||||
<view class="upload-box clickable" :class="{ disabled: uploadingFieldKey === field.key }" @tap="openFilePicker(field.key)">
|
<view class="upload-box clickable" :class="{ disabled: uploadingFieldKey === field.key }" @tap="openFilePicker(field.key)">
|
||||||
@@ -246,6 +254,16 @@ export default {
|
|||||||
normalizeInputType(inputType) {
|
normalizeInputType(inputType) {
|
||||||
return inputType || 'text'
|
return inputType || 'text'
|
||||||
},
|
},
|
||||||
|
getSealPreviewSrc(field) {
|
||||||
|
if (!field || field.key !== 'sealType') return ''
|
||||||
|
const value = this.form[field.key]
|
||||||
|
const map = {
|
||||||
|
'山东福安德信息科技有限公司采购部专用章FAD201400201.png': 'http://49.232.154.205:10900/fad-oa/files%2Fstamp%2F山东福安德信息科技有限公司采购部专用章FAD201400201.png',
|
||||||
|
'山东福安德信息科技有限公司采购部专用章FAD201400202.png': 'http://49.232.154.205:10900/fad-oa/files%2Fstamp%2F山东福安德信息科技有限公司采购部专用章FAD201400202.png',
|
||||||
|
'山东福安德信息科技有限公司采购部专用章FAD201400401.png': 'http://49.232.154.205:10900/fad-oa/files%2Fstamp%2F山东福安德信息科技有限公司采购部专用章FAD201400401.png'
|
||||||
|
}
|
||||||
|
return map[value] || ''
|
||||||
|
},
|
||||||
onInputFocus(fieldKey) {
|
onInputFocus(fieldKey) {
|
||||||
this.currentInputFieldKey = fieldKey
|
this.currentInputFieldKey = fieldKey
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- 出差申请详情容器,复用统一卡片样式 -->
|
<!-- 出差申请详情容器,复用统一卡片样式 -->
|
||||||
<view class="reimburse-detail-container">
|
<view class="reimburse-detail-container">
|
||||||
|
<view class="detail-item single-item status-item">
|
||||||
|
<text class="item-icon icon-status"></text>
|
||||||
|
<view class="item-label">出差状态</view>
|
||||||
|
<view class="item-value">{{ travelStatusText }}</view>
|
||||||
|
</view>
|
||||||
<!-- 出差类型 - 带分类图标 -->
|
<!-- 出差类型 - 带分类图标 -->
|
||||||
<view class="detail-item single-item">
|
<view class="detail-item single-item">
|
||||||
<text class="item-icon icon-travel-type"></text>
|
<text class="item-icon icon-travel-type"></text>
|
||||||
@@ -66,6 +71,11 @@
|
|||||||
<view class="item-label">申请时间</view>
|
<view class="item-label">申请时间</view>
|
||||||
<view class="item-value">{{ detail.createTime || '无' }}</view>
|
<view class="item-value">{{ detail.createTime || '无' }}</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="detail-item single-item" v-if="detail.actualEndTime">
|
||||||
|
<text class="item-icon icon-end-time"></text>
|
||||||
|
<view class="item-label">实际结束时间</view>
|
||||||
|
<view class="item-value">{{ detail.actualEndTime }}</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="detail-item single-item">
|
<view class="detail-item single-item">
|
||||||
<text class="item-icon icon-date"></text>
|
<text class="item-icon icon-date"></text>
|
||||||
@@ -75,6 +85,10 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<view class="detail-action-row" v-if="canEarlyEnd">
|
||||||
|
<button class="early-end-btn" @click="handleEarlyEnd">提前结束</button>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="detail-item single-item amount-item" @click="openPopup" v-if="detail.projectId">
|
<view class="detail-item single-item amount-item" @click="openPopup" v-if="detail.projectId">
|
||||||
<text class="item-icon icon-creator"></text>
|
<text class="item-icon icon-creator"></text>
|
||||||
<view class="item-label">项目名称</view>
|
<view class="item-label">项目名称</view>
|
||||||
@@ -147,6 +161,14 @@
|
|||||||
detail: {}
|
detail: {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
travelStatusText() {
|
||||||
|
return this.detail.actualEndTime ? '已结束' : '出差中';
|
||||||
|
},
|
||||||
|
canEarlyEnd() {
|
||||||
|
return !this.detail.actualEndTime;
|
||||||
|
}
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
bizId: {
|
bizId: {
|
||||||
handler(newVal) {
|
handler(newVal) {
|
||||||
@@ -175,6 +197,9 @@
|
|||||||
},
|
},
|
||||||
openPopup() {
|
openPopup() {
|
||||||
this.$refs.popup.open()
|
this.$refs.popup.open()
|
||||||
|
},
|
||||||
|
handleEarlyEnd() {
|
||||||
|
this.$emit('early-end', this.detail)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
<view class="sub-tabs sub-tabs--underline">
|
<view class="sub-tabs sub-tabs--underline">
|
||||||
<view class="sub-tab" :class="{ active: myApplyStatusTab === 'all' }" @click="switchMyApplyStatusTab('all')">全部</view>
|
<view class="sub-tab" :class="{ active: myApplyStatusTab === 'all' }" @click="switchMyApplyStatusTab('all')">全部</view>
|
||||||
<view class="sub-tab" :class="{ active: myApplyStatusTab === 'running' }" @click="switchMyApplyStatusTab('running')">审批中</view>
|
<view class="sub-tab" :class="{ active: myApplyStatusTab === 'running' }" @click="switchMyApplyStatusTab('running')">审批中</view>
|
||||||
<view class="sub-tab" :class="{ active: myApplyStatusTab === 'approved' }" @click="switchMyApplyStatusTab('approved')">已通过</view>
|
<view class="sub-tab" :class="{ active: myApplyStatusTab === 'approved' }" @click="switchMyApplyStatusTab('approved')">出差状态</view>
|
||||||
<view class="sub-tab" :class="{ active: myApplyStatusTab === 'rejected' }" @click="switchMyApplyStatusTab('rejected')">已驳回</view>
|
<view class="sub-tab" :class="{ active: myApplyStatusTab === 'rejected' }" @click="switchMyApplyStatusTab('rejected')">已驳回</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
<uni-icons type="empty" size="60" color="#909399"></uni-icons>
|
<uni-icons type="empty" size="60" color="#909399"></uni-icons>
|
||||||
<view class="empty-text">暂无我的申请</view>
|
<view class="empty-text">暂无我的申请</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-else class="list-item" v-for="(item, index) in myApplyList" :key="item.instId || item.id || index" @click="goDetail(item)">
|
<view v-else class="list-item my-apply-item" v-for="(item, index) in myApplyList" :key="item.instId || item.id || index" @click="goDetail(item)">
|
||||||
<view class="item-tag" :class="getBizTypeTagType(item.bizType)">{{ getBizTypeText(item.bizType) }}</view>
|
<view class="item-tag" :class="getBizTypeTagType(item.bizType)">{{ getBizTypeText(item.bizType) }}</view>
|
||||||
<view class="item-main">
|
<view class="item-main">
|
||||||
<view class="applicant">
|
<view class="applicant">
|
||||||
@@ -99,9 +99,9 @@
|
|||||||
<view class="request-info">{{ formatMyApplyInfo(item) }}</view>
|
<view class="request-info">{{ formatMyApplyInfo(item) }}</view>
|
||||||
<view class="time-info">{{ formatDate(item.createTime || item.applyTime || item.startTime) }}</view>
|
<view class="time-info">{{ formatDate(item.createTime || item.applyTime || item.startTime) }}</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="right-section">
|
<view class="my-apply-actions">
|
||||||
<view class="status-tag" :class="statusType(item.status)">{{ myApplyStatusText(item) }}</view>
|
<view v-if="canShowMyApplyEarlyEnd(item)" class="my-apply-early-end" @click.stop="goTravelEarlyEnd(item)">提前结束</view>
|
||||||
<view v-if="item.bizType === 'travel' && (item.status === 'approved' || item.status === 'finished') && !item.actualEndTime && !item.endTime && !item.realEndTime" class="early-end-btn" @click.stop="goTravelEarlyEnd(item)">提前结束</view>
|
<view class="status-tag" :class="myApplyStatusClass(item)">{{ myApplyStatusText(item) }}</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
@@ -126,6 +126,7 @@
|
|||||||
import { getUserProfile } from '@/api/oa/user.js';
|
import { getUserProfile } from '@/api/oa/user.js';
|
||||||
import { approveFlowTask, listMyFlowInstance, listTodoFlowTask, listDoneFlowTask, rejectFlowTask } from '@/api/hrm/flow';
|
import { approveFlowTask, listMyFlowInstance, listTodoFlowTask, listDoneFlowTask, rejectFlowTask } from '@/api/hrm/flow';
|
||||||
import { earlyEndTravelReq } from '@/api/hrm/travel';
|
import { earlyEndTravelReq } from '@/api/hrm/travel';
|
||||||
|
import { listCc } from '@/api/hrm/cc';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HrmApproval',
|
name: 'HrmApproval',
|
||||||
@@ -180,9 +181,9 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
currentList() {
|
currentList() {
|
||||||
if (this.activeTopTab === 'myApply') return this.myApplyList;
|
if (this.activeTopTab === 'myApply') return this.myApplyList;
|
||||||
if (this.activeApprovalTab === 'todo') return this.todoList;
|
if (this.activeApprovalTab === 'todo') return this.todoList.filter(item => item.bizType !== 'seal');
|
||||||
if (this.activeApprovalTab === 'done') return this.doneList;
|
if (this.activeApprovalTab === 'done') return this.doneList.filter(item => item.bizType !== 'seal');
|
||||||
return this.ccList;
|
return this.ccList.filter(item => item.bizType !== 'seal');
|
||||||
},
|
},
|
||||||
emptyText() {
|
emptyText() {
|
||||||
if (this.activeTopTab === 'myApply') return '暂无我的申请';
|
if (this.activeTopTab === 'myApply') return '暂无我的申请';
|
||||||
@@ -286,8 +287,8 @@ export default {
|
|||||||
async loadCcList() {
|
async loadCcList() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
try {
|
||||||
const res = await listMyFlowInstance({ pageNum: 1, pageSize: 200 });
|
const res = await listCc({ ccUserId: this.currentOaUserId, pageNum: 1, pageSize: 200 });
|
||||||
let list = (res.rows || res.data || []).filter(item => item.ccFlag === 1 || item.isCc === 1 || item.readFlag === 0);
|
let list = res.rows || res.data || [];
|
||||||
if (this.query.bizType) list = list.filter(item => item.bizType === this.query.bizType);
|
if (this.query.bizType) list = list.filter(item => item.bizType === this.query.bizType);
|
||||||
this.ccList = list;
|
this.ccList = list;
|
||||||
this.summary.cc = list.length;
|
this.summary.cc = list.length;
|
||||||
@@ -356,17 +357,22 @@ export default {
|
|||||||
if (this.myApplyStatusTab === 'rejected') return ['rejected', 'reject'].includes(s);
|
if (this.myApplyStatusTab === 'rejected') return ['rejected', 'reject'].includes(s);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
myApplyStatusText(item) {
|
||||||
|
if (item?.bizType === 'travel') {
|
||||||
|
return item?.actualEndTime ? '已结束' : '出差中';
|
||||||
|
}
|
||||||
|
return this.statusText(item?.status);
|
||||||
|
},
|
||||||
|
myApplyStatusClass(item) {
|
||||||
|
if (item?.bizType === 'travel') {
|
||||||
|
return item?.actualEndTime ? 'travel-ended' : 'travel-running';
|
||||||
|
}
|
||||||
|
return this.statusType(item?.status);
|
||||||
|
},
|
||||||
statusText(status) {
|
statusText(status) {
|
||||||
const map = { pending: '待审批', draft: '草稿', approved: '已通过', rejected: '已驳回', running: '审批中', finished: '已完成', revoked: '已撤销' };
|
const map = { pending: '待审批', draft: '草稿', approved: '已通过', rejected: '已驳回', running: '审批中', finished: '已完成', revoked: '已撤销' };
|
||||||
return map[status] || status || '-';
|
return map[status] || status || '-';
|
||||||
},
|
},
|
||||||
myApplyStatusText(item) {
|
|
||||||
if (item.bizType === 'travel' && (item.status === 'approved' || item.status === 'finished')) {
|
|
||||||
const endTime = item.actualEndTime || item.endTime || item.realEndTime;
|
|
||||||
return endTime ? '已结束' : '出差中';
|
|
||||||
}
|
|
||||||
return this.statusText(item.status);
|
|
||||||
},
|
|
||||||
statusType(status) {
|
statusType(status) {
|
||||||
const map = { pending: 'warning', running: 'warning', draft: 'info', approved: 'success', rejected: 'danger', finished: 'success', revoked: 'danger' };
|
const map = { pending: 'warning', running: 'warning', draft: 'info', approved: 'success', rejected: 'danger', finished: 'success', revoked: 'danger' };
|
||||||
return map[status] || 'info';
|
return map[status] || 'info';
|
||||||
@@ -408,6 +414,10 @@ export default {
|
|||||||
},
|
},
|
||||||
goDetail(task) {
|
goDetail(task) {
|
||||||
if (!task) return;
|
if (!task) return;
|
||||||
|
if (task.bizType === 'seal') {
|
||||||
|
uni.showToast({ title: '用印审批暂时关闭', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
const bizId = task.bizId || task.instId;
|
const bizId = task.bizId || task.instId;
|
||||||
const bizType = task.bizType;
|
const bizType = task.bizType;
|
||||||
if (!bizType || !bizId) {
|
if (!bizType || !bizId) {
|
||||||
@@ -426,10 +436,49 @@ export default {
|
|||||||
};
|
};
|
||||||
uni.navigateTo({ url: map[type] || '/pages/workbench/hrm/apply/apply' });
|
uni.navigateTo({ url: map[type] || '/pages/workbench/hrm/apply/apply' });
|
||||||
},
|
},
|
||||||
|
canShowMyApplyEarlyEnd(item) {
|
||||||
|
return item?.bizType === 'travel' && item?.actualEndTime == null;
|
||||||
|
},
|
||||||
|
goTravelEarlyEnd(task) {
|
||||||
|
if (!task || task.bizType !== 'travel') {
|
||||||
|
uni.showToast({ title: '仅出差申请支持提前结束', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const bizId = task.bizId || task.instId;
|
||||||
|
if (!bizId) {
|
||||||
|
uni.showToast({ title: '缺少出差单编号', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uni.showModal({
|
||||||
|
title: '提前结束出差',
|
||||||
|
content: '确定要提前结束该出差吗?',
|
||||||
|
success: (res) => {
|
||||||
|
if (!res.confirm) return;
|
||||||
|
earlyEndTravelReq(bizId)
|
||||||
|
.then(() => {
|
||||||
|
uni.showToast({ title: '提前结束成功', icon: 'none' });
|
||||||
|
this.refreshCurrentList();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('提前结束失败:', err);
|
||||||
|
uni.showToast({ title: '提前结束失败', icon: 'none' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
goTravelEarlyEnd() {
|
goTravelEarlyEnd() {
|
||||||
uni.showToast({ title: '请进入出差详情后发起提前结束', icon: 'none' });
|
uni.showToast({ title: '请进入出差详情后发起提前结束', icon: 'none' });
|
||||||
},
|
},
|
||||||
|
|
||||||
showActionMenu(task) {
|
showActionMenu(task) {
|
||||||
|
if (task?.bizType === 'seal') {
|
||||||
|
this.goDetail(task);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.activeApprovalTab === 'cc' || this.activeApprovalTab === 'done') {
|
||||||
|
this.goDetail(task);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this.activeApprovalTab !== 'todo') {
|
if (this.activeApprovalTab !== 'todo') {
|
||||||
this.goDetail(task);
|
this.goDetail(task);
|
||||||
return;
|
return;
|
||||||
@@ -706,11 +755,68 @@ export default {
|
|||||||
|
|
||||||
.item-tag,
|
.item-tag,
|
||||||
.status-tag {
|
.status-tag {
|
||||||
padding: 0;
|
padding: 4rpx 14rpx;
|
||||||
border-radius: 0;
|
border-radius: 999rpx;
|
||||||
font-size: 22rpx;
|
font-size: 22rpx;
|
||||||
|
line-height: 1.4;
|
||||||
|
background: #f3f4f6;
|
||||||
color: #6b7280;
|
color: #6b7280;
|
||||||
background: transparent !important;
|
}
|
||||||
|
|
||||||
|
.status-tag.primary {
|
||||||
|
background: rgba(22, 119, 255, 0.12);
|
||||||
|
color: #1677ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-tag.success {
|
||||||
|
background: rgba(34, 197, 94, 0.12);
|
||||||
|
color: #16a34a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-tag.warning,
|
||||||
|
.travel-running {
|
||||||
|
background: rgba(245, 158, 11, 0.12);
|
||||||
|
color: #d97706;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-tag.danger {
|
||||||
|
background: rgba(239, 68, 68, 0.12);
|
||||||
|
color: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-tag.info,
|
||||||
|
.travel-ended {
|
||||||
|
background: #f3f4f6;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.travel-running {
|
||||||
|
color: #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.travel-ended {
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-apply-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-apply-early-end {
|
||||||
|
padding: 10rpx 18rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background: #1677ff;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 22rpx;
|
||||||
|
line-height: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
box-shadow: 0 6rpx 14rpx rgba(22, 119, 255, 0.22);
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-main {
|
.item-main {
|
||||||
@@ -732,25 +838,6 @@ export default {
|
|||||||
.request-info { font-size: 26rpx; color: #6b7280; }
|
.request-info { font-size: 26rpx; color: #6b7280; }
|
||||||
.time-info { font-size: 24rpx; color: #9ca3af; }
|
.time-info { font-size: 24rpx; color: #9ca3af; }
|
||||||
|
|
||||||
.early-end-btn {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
padding: 6rpx 16rpx;
|
|
||||||
font-size: 22rpx;
|
|
||||||
color: #1677ff;
|
|
||||||
background: #eef4ff;
|
|
||||||
border-radius: 6rpx;
|
|
||||||
border: 1rpx solid #1677ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-section {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-end;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 8rpx;
|
|
||||||
padding-right: 8rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.apply-panel {
|
.apply-panel {
|
||||||
padding-top: 16rpx;
|
padding-top: 16rpx;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,9 +51,32 @@
|
|||||||
:is="currentDetailComponent"
|
:is="currentDetailComponent"
|
||||||
:bizId="bizId"
|
:bizId="bizId"
|
||||||
v-if="bizId && bizType"
|
v-if="bizId && bizType"
|
||||||
|
@early-end="handleTravelEarlyEnd"
|
||||||
></component>
|
></component>
|
||||||
|
|
||||||
<view class="approval-btn-bar" v-if="canApprove">
|
<view class="detail-action-bar" v-if="canTravelEarlyEnd">
|
||||||
|
<button class="btn early-end-btn" @click="handleTravelEarlyEnd">提前结束</button>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="seal-inline-card" v-if="canSealApprove">
|
||||||
|
<view class="seal-inline-head">
|
||||||
|
<text class="seal-inline-title">用印签章审批</text>
|
||||||
|
<text class="seal-inline-subtitle">当前申请已绑定印章:{{ sealTypeLabel }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="seal-form-row">
|
||||||
|
<text class="seal-form-label">签章页码</text>
|
||||||
|
<input class="seal-form-input" type="number" v-model="sealStampForm.pageNo" :placeholder="sealPageHint" />
|
||||||
|
</view>
|
||||||
|
<view class="seal-form-row seal-form-row--info">
|
||||||
|
<text class="seal-form-label">PDF页数</text>
|
||||||
|
<text class="seal-form-value">{{ sealPageTotal || '-' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="seal-form-actions">
|
||||||
|
<button class="btn approve-btn" @click="submitSealStamp">签章通过</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="approval-btn-bar" v-if="canApprove && bizType !== 'seal'">
|
||||||
<button class="btn reject-btn" @click="handleReject">驳回</button>
|
<button class="btn reject-btn" @click="handleReject">驳回</button>
|
||||||
<button class="btn approve-btn" @click="handleApprove">通过</button>
|
<button class="btn approve-btn" @click="handleApprove">通过</button>
|
||||||
</view>
|
</view>
|
||||||
@@ -71,6 +94,8 @@
|
|||||||
rejectFlowTask,
|
rejectFlowTask,
|
||||||
getFlowTaskDetailByBiz,
|
getFlowTaskDetailByBiz,
|
||||||
} from '@/api/hrm/flow';
|
} from '@/api/hrm/flow';
|
||||||
|
import { earlyEndTravelReq } from '@/api/hrm/travel';
|
||||||
|
import { stampSealJava, getSealPdfPages } from '@/api/hrm/seal';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -91,7 +116,17 @@
|
|||||||
seal: 'HRMSealDetail',
|
seal: 'HRMSealDetail',
|
||||||
travel: 'HRMTravelDetail',
|
travel: 'HRMTravelDetail',
|
||||||
appropriation: 'HRMAppropriationDetail'
|
appropriation: 'HRMAppropriationDetail'
|
||||||
}
|
},
|
||||||
|
sealStampPresets: [
|
||||||
|
{ key: 'left-bottom', label: '山东福安德信息科技有限公司采购部专用章FAD201400201.png', preview: 'http://49.232.154.205:10900/fad-oa/files%2Fstamp%2F山东福安德信息科技有限公司采购部专用章FAD201400201.png' },
|
||||||
|
{ key: 'center-bottom', label: '山东福安德信息科技有限公司采购部专用章FAD201400202.png', preview: 'http://49.232.154.205:10900/fad-oa/files%2Fstamp%2F山东福安德信息科技有限公司采购部专用章FAD201400202.png' },
|
||||||
|
{ key: 'right-bottom', label: '山东福安德信息科技有限公司采购部专用章FAD201400401.png', preview: 'http://49.232.154.205:10900/fad-oa/files%2Fstamp%2F山东福安德信息科技有限公司采购部专用章FAD201400401.png' }
|
||||||
|
],
|
||||||
|
sealStampForm: {
|
||||||
|
position: 'right-bottom',
|
||||||
|
pageNo: 1
|
||||||
|
},
|
||||||
|
sealPageTotal: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -106,6 +141,18 @@
|
|||||||
(this.currentTask?.assigneeUserName === this.$store.getters.storeOaName
|
(this.currentTask?.assigneeUserName === this.$store.getters.storeOaName
|
||||||
|| this.currentTask?.assigneeUserId === this.$store.getters.storeOaId)
|
|| this.currentTask?.assigneeUserId === this.$store.getters.storeOaId)
|
||||||
},
|
},
|
||||||
|
canSealApprove() {
|
||||||
|
return this.bizType === 'seal' && this.canApprove;
|
||||||
|
},
|
||||||
|
sealPageHint() {
|
||||||
|
return this.sealPageTotal > 0 ? `1-${this.sealPageTotal}` : '1-1';
|
||||||
|
},
|
||||||
|
sealTypeLabel() {
|
||||||
|
return this.detailData?.sealType || '-';
|
||||||
|
},
|
||||||
|
canTravelEarlyEnd() {
|
||||||
|
return this.bizType === 'travel' && this.detailData?.actualEndTime == null;
|
||||||
|
},
|
||||||
assigneeText() {
|
assigneeText() {
|
||||||
return this.currentTask?.assigneeNickName || this.currentTask?.assigneeUserName || this.currentTask?.assigneeUserId || '-';
|
return this.currentTask?.assigneeNickName || this.currentTask?.assigneeUserName || this.currentTask?.assigneeUserId || '-';
|
||||||
}
|
}
|
||||||
@@ -134,7 +181,8 @@
|
|||||||
rejected: '已驳回',
|
rejected: '已驳回',
|
||||||
reject: '已驳回',
|
reject: '已驳回',
|
||||||
withdraw: '已撤回',
|
withdraw: '已撤回',
|
||||||
withdrawn: '已撤回'
|
withdrawn: '已撤回',
|
||||||
|
finished: '已结束'
|
||||||
};
|
};
|
||||||
return map[status] || status || '-';
|
return map[status] || status || '-';
|
||||||
},
|
},
|
||||||
@@ -191,6 +239,56 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
async fetchSealPageTotal() {
|
||||||
|
try {
|
||||||
|
const res = await getSealPdfPages(this.bizId);
|
||||||
|
this.sealPageTotal = Number(res?.data || 0);
|
||||||
|
} catch (e) {
|
||||||
|
this.sealPageTotal = 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitSealStamp() {
|
||||||
|
const pageNo = Number(this.sealStampForm.pageNo) || 1;
|
||||||
|
const payload = {
|
||||||
|
pageNo,
|
||||||
|
xPx: 120,
|
||||||
|
yPx: 120,
|
||||||
|
viewportWidth: 750,
|
||||||
|
viewportHeight: 1334
|
||||||
|
};
|
||||||
|
stampSealJava(this.bizId, payload)
|
||||||
|
.then(() => {
|
||||||
|
uni.showToast({ title: '签章成功', icon: 'none' });
|
||||||
|
setTimeout(() => uni.navigateBack(), 1000);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('签章失败:', err);
|
||||||
|
uni.showToast({ title: '签章失败', icon: 'none' });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleTravelEarlyEnd() {
|
||||||
|
if (!this.canTravelEarlyEnd) {
|
||||||
|
uni.showToast({ title: '当前出差已结束', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const bizId = this.bizId;
|
||||||
|
uni.showModal({
|
||||||
|
title: '提前结束出差',
|
||||||
|
content: '确认提前结束该出差吗?',
|
||||||
|
success: (res) => {
|
||||||
|
if (!res.confirm) return;
|
||||||
|
earlyEndTravelReq(bizId)
|
||||||
|
.then(() => {
|
||||||
|
uni.showToast({ title: '提前结束成功', icon: 'none' });
|
||||||
|
setTimeout(() => uni.navigateBack(), 1200);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('提前结束失败:', err);
|
||||||
|
uni.showToast({ title: '提前结束失败', icon: 'none' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad(options) {
|
onLoad(options) {
|
||||||
@@ -199,6 +297,9 @@
|
|||||||
if (!this.bizId || !this.bizType) {
|
if (!this.bizId || !this.bizType) {
|
||||||
uni.showToast({ title: '参数缺失,无法加载详情', icon: 'none' });
|
uni.showToast({ title: '参数缺失,无法加载详情', icon: 'none' });
|
||||||
}
|
}
|
||||||
|
if (this.bizType === 'seal') {
|
||||||
|
this.fetchSealPageTotal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -256,8 +357,10 @@
|
|||||||
.timeline-header, .timeline-meta { display:flex; justify-content:space-between; gap: 16rpx; font-size:24rpx; color:#6b7280; }
|
.timeline-header, .timeline-meta { display:flex; justify-content:space-between; gap: 16rpx; font-size:24rpx; color:#6b7280; }
|
||||||
.timeline-name { color:#111827; font-weight:600; }
|
.timeline-name { color:#111827; font-weight:600; }
|
||||||
.timeline-remark { margin-top: 8rpx; color:#374151; font-size:26rpx; }
|
.timeline-remark { margin-top: 8rpx; color:#374151; font-size:26rpx; }
|
||||||
|
.detail-action-bar { display: flex; justify-content: center; margin: 24rpx 0; }
|
||||||
.approval-btn-bar { position: fixed; bottom: 0; left: 0; right: 0; display: flex; padding: 20rpx; background-color: #fff; border-top: 1px solid #eee; z-index: 99; }
|
.approval-btn-bar { position: fixed; bottom: 0; left: 0; right: 0; display: flex; padding: 20rpx; background-color: #fff; border-top: 1px solid #eee; z-index: 99; }
|
||||||
.btn { flex: 1; height: 88rpx; line-height: 88rpx; border-radius: 44rpx; font-size: 32rpx; border: none; margin: 0 10rpx; }
|
.btn { flex: 1; height: 88rpx; line-height: 88rpx; border-radius: 44rpx; font-size: 32rpx; border: none; margin: 0 10rpx; }
|
||||||
|
.early-end-btn { flex: none; min-width: 260rpx; background-color: #007aff; color: #fff; }
|
||||||
.reject-btn { background-color: #fff; color: #ff4757; border: 1px solid #ff4757; }
|
.reject-btn { background-color: #fff; color: #ff4757; border: 1px solid #ff4757; }
|
||||||
.approve-btn { background-color: #007aff; color: #fff; }
|
.approve-btn { background-color: #007aff; color: #fff; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -21,7 +21,11 @@ export default {
|
|||||||
initialForm: { sealType: '', applyFileIds: '', reason: '', remark: '' },
|
initialForm: { sealType: '', applyFileIds: '', reason: '', remark: '' },
|
||||||
sections: [
|
sections: [
|
||||||
{ key: 'basic', title: '基础信息', fields: [
|
{ key: 'basic', title: '基础信息', fields: [
|
||||||
{ key: 'sealType', label: '用印类型', type: 'select', required: true, placeholder: '请选择用印类型', options: ['合同用印', '公章', '财务章', '法人章', '其他'] },
|
{ key: 'sealType', label: '用印类型', type: 'select', required: true, placeholder: '请选择用印类型', options: [
|
||||||
|
'山东福安德信息科技有限公司采购部专用章FAD201400201.png',
|
||||||
|
'山东福安德信息科技有限公司采购部专用章FAD201400202.png',
|
||||||
|
'山东福安德信息科技有限公司采购部专用章FAD201400401.png'
|
||||||
|
] },
|
||||||
{ key: 'applyFileIds', label: '附件', type: 'file', required: true, placeholder: '上传盖章文件', accept: '.pdf' }
|
{ key: 'applyFileIds', label: '附件', type: 'file', required: true, placeholder: '上传盖章文件', accept: '.pdf' }
|
||||||
]},
|
]},
|
||||||
{ key: 'desc', title: '说明', fields: [
|
{ key: 'desc', title: '说明', fields: [
|
||||||
|
|||||||
Reference in New Issue
Block a user