合同签署 合同审核 接口完善 页面优化
This commit is contained in:
parent
cf7f5eb115
commit
cd3d71586b
|
|
@ -1,6 +1,11 @@
|
|||
<template>
|
||||
<view class="select-all-container" :style="containerStyle">
|
||||
<up-button text="全选" type="primary" :customStyle="buttonStyle" @tap="handleSelectAll" />
|
||||
<up-button
|
||||
type="primary"
|
||||
@tap="handleSelectAll"
|
||||
:customStyle="buttonStyle"
|
||||
:text="isAllChecked ? '取消全选' : '全选'"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
|
@ -8,15 +13,13 @@
|
|||
import { computed } from 'vue'
|
||||
import { getSafeAreaInfo } from '@/utils/safeArea'
|
||||
|
||||
/**
|
||||
* 审核页面全选按钮组件
|
||||
* 业务背景:用于批量操作场景,提供全选功能
|
||||
* 设计决策:
|
||||
* 1. 固定在底部右侧,方便用户操作
|
||||
* 2. 适配安全区,确保在底部有安全区的设备上正常显示
|
||||
* 3. 使用国网绿主题色,保持视觉一致性
|
||||
*/
|
||||
const emit = defineEmits(['selectAll'])
|
||||
const props = defineProps({
|
||||
isAllChecked: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 容器样式,适配底部安全区
|
||||
const containerStyle = computed(() => {
|
||||
|
|
|
|||
|
|
@ -250,6 +250,14 @@
|
|||
"navigationBarBackgroundColor": "#07c160"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/work/contract-sign/detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "电子合同信息",
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarBackgroundColor": "#07c160"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/work/wage-view/index",
|
||||
"style": {
|
||||
|
|
|
|||
|
|
@ -103,6 +103,16 @@
|
|||
<text class="label" style="margin-left: 20rpx">签订日期:</text>
|
||||
<text class="value">{{ item.signingDate }}</text>
|
||||
</view>
|
||||
|
||||
<view class="row" v-if="activeTab != 0">
|
||||
<text class="label">甲方签署状态:</text>
|
||||
<text
|
||||
class="value"
|
||||
:style="{ color: item.isSign == 1 ? '#4caf50' : '#ed7b2f' }"
|
||||
>
|
||||
{{ item.isSign == 1 ? '已签署' : '待签署' || '-' }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,737 @@
|
|||
<template>
|
||||
<view class="page-container">
|
||||
<!-- 导航栏 -->
|
||||
<NavBarModal navBarTitle="电子合同信息">
|
||||
<template #left>
|
||||
<view class="back-btn" @tap="handleBack">
|
||||
<up-icon name="arrow-left" size="20" color="#fff" />
|
||||
</view>
|
||||
</template>
|
||||
</NavBarModal>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<view class="content-wrapper" :style="contentStyle">
|
||||
<!-- 下拉选择器 -->
|
||||
<view v-if="documentOptions.length > 0" class="picker-section">
|
||||
<CommonPicker
|
||||
placeholder="请选择文档"
|
||||
:options="documentOptions"
|
||||
v-model="currentDocumentValue"
|
||||
@change="handleDocumentChange"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view v-if="error" class="error-wrapper">
|
||||
<up-icon name="close-circle" size="80" color="#e34d59" />
|
||||
<text class="error-text">{{ error }}</text>
|
||||
<up-button
|
||||
text="重试"
|
||||
type="primary"
|
||||
size="small"
|
||||
:customStyle="{ marginTop: '32rpx' }"
|
||||
@tap="loadDocument"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view v-if="pdfUrl" class="document-wrapper">
|
||||
<DocumentPreview
|
||||
:file-url="pdfUrl"
|
||||
file-type="pdf"
|
||||
preview-service="pdfjs"
|
||||
:auto-load="true"
|
||||
:show-retry="true"
|
||||
:show-download="true"
|
||||
:container-style="{ width: '100%', height: '100%' }"
|
||||
@load="handleDocumentLoad"
|
||||
@error="handleDocumentError"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-else class="empty-wrapper">
|
||||
<up-icon name="file-text" size="80" color="#ccc" />
|
||||
<text class="empty-text">暂无文档</text>
|
||||
</view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<view class="button-section">
|
||||
<up-button
|
||||
text="返回"
|
||||
type="default"
|
||||
:customStyle="backButtonStyle"
|
||||
@tap="handleBack"
|
||||
/>
|
||||
<template v-if="!isSigned">
|
||||
<up-button
|
||||
text="查看视频"
|
||||
type="default"
|
||||
:customStyle="videoButtonStyle"
|
||||
@tap="handleViewVideo"
|
||||
/>
|
||||
<up-button
|
||||
text="签署"
|
||||
type="primary"
|
||||
:customStyle="signButtonStyle"
|
||||
@tap="handleSign"
|
||||
/>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 签署弹窗 -->
|
||||
<up-popup :show="showSignModal" mode="center" round="20" @close="handleCancelSign">
|
||||
<view class="sign-popup">
|
||||
<view class="sign-title">签署</view>
|
||||
<scroll-view class="sign-body" scroll-y>
|
||||
<view class="sign-block">
|
||||
<text class="block-title">确认信息</text>
|
||||
<view class="info-text">
|
||||
<text>您确定要签署这份合同吗?</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stamp-section">
|
||||
<up-button
|
||||
text="获取公章"
|
||||
type="primary"
|
||||
@tap="handleGetStamp"
|
||||
:customStyle="getStampButtonStyle"
|
||||
:loading="stampLoading"
|
||||
/>
|
||||
<view class="stamp-label">电子公章</view>
|
||||
<view class="stamp-preview" v-if="stampImageUrl">
|
||||
<image :src="stampImageUrl" mode="aspectFit" class="stamp-image" />
|
||||
</view>
|
||||
<view v-else class="stamp-placeholder">
|
||||
<text class="placeholder-text">暂无公章</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="sign-actions">
|
||||
<up-button
|
||||
text="取消"
|
||||
type="default"
|
||||
@tap="handleCancelSign"
|
||||
:customStyle="cancelBtnStyle"
|
||||
/>
|
||||
<up-button
|
||||
text="确认"
|
||||
type="primary"
|
||||
@tap="handleConfirmSign"
|
||||
:customStyle="confirmBtnStyle"
|
||||
/>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</up-popup>
|
||||
|
||||
<!-- 视频全屏弹窗 -->
|
||||
<up-popup :show="showVideoModal" mode="center" round="20" @close="showVideoModal = false">
|
||||
<view class="video-popup">
|
||||
<view class="video-header">
|
||||
<text class="video-title">查看视频</text>
|
||||
<up-icon name="close" size="24" color="#333" @tap="showVideoModal = false" />
|
||||
</view>
|
||||
<view class="video-container">
|
||||
<video
|
||||
v-if="videoUrl"
|
||||
:src="videoUrl"
|
||||
controls
|
||||
class="video-player"
|
||||
:enable-play-gesture="true"
|
||||
:show-fullscreen-btn="true"
|
||||
:show-center-play-btn="true"
|
||||
:enable-progress-gesture="true"
|
||||
object-fit="contain"
|
||||
></video>
|
||||
<view v-else class="video-empty">
|
||||
<text>暂无视频</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</up-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import NavBarModal from '@/components/NavBarModal/index.vue'
|
||||
import DocumentPreview from '@/components/DocumentPreview/index.vue'
|
||||
import CommonPicker from '@/components/CommonPicker/index.vue'
|
||||
import { getContentStyle } from '@/utils/safeArea'
|
||||
import { signContractAPI, getUserElectronicStampAPI } from '@/services/realName/contractSign'
|
||||
import { useMemberStore, useCommonStore } from '@/stores'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const memberStore = useMemberStore()
|
||||
const commonStore = useCommonStore()
|
||||
|
||||
const contentStyle = computed(() => {
|
||||
return getContentStyle({
|
||||
includeNavBar: true,
|
||||
includeStatusBar: true,
|
||||
includeBottomSafeArea: false,
|
||||
})
|
||||
})
|
||||
|
||||
// 合同数据
|
||||
const contractData = ref({})
|
||||
// PDF文档URL
|
||||
const pdfUrl = ref('')
|
||||
// 视频URL
|
||||
const videoUrl = ref('')
|
||||
// 是否已签署
|
||||
const isSigned = ref(false)
|
||||
// 签署弹窗
|
||||
const showSignModal = ref(false)
|
||||
const cancelBtnStyle = { flex: 1, marginRight: '16rpx' }
|
||||
const confirmBtnStyle = { flex: 1 }
|
||||
// 视频弹窗
|
||||
const showVideoModal = ref(false)
|
||||
// 加载状态
|
||||
const loading = ref(false)
|
||||
// 错误信息
|
||||
const error = ref('')
|
||||
// 公章相关
|
||||
const stampImageUrl = ref('')
|
||||
const stampImagePath = ref('')
|
||||
const stampLoading = ref(false)
|
||||
const getStampButtonStyle = {
|
||||
width: '100%',
|
||||
height: '88rpx',
|
||||
marginBottom: '24rpx',
|
||||
}
|
||||
|
||||
// 文档选项
|
||||
const documentOptions = ref([])
|
||||
const currentDocumentValue = ref('contract')
|
||||
|
||||
// 按钮样式
|
||||
const backButtonStyle = computed(() => {
|
||||
return {
|
||||
flex: 1,
|
||||
marginRight: '16rpx',
|
||||
backgroundColor: '#fff',
|
||||
border: '1rpx solid #07c160',
|
||||
color: '#07c160',
|
||||
}
|
||||
})
|
||||
|
||||
const videoButtonStyle = computed(() => {
|
||||
return {
|
||||
flex: 1,
|
||||
marginRight: '16rpx',
|
||||
backgroundColor: '#fff',
|
||||
border: '1rpx solid #07c160',
|
||||
color: '#07c160',
|
||||
}
|
||||
})
|
||||
|
||||
const signButtonStyle = computed(() => {
|
||||
return {
|
||||
flex: 1,
|
||||
backgroundColor: '#07c160',
|
||||
borderColor: '#07c160',
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 初始化文档选项
|
||||
*/
|
||||
const initDocumentOptions = () => {
|
||||
const options = []
|
||||
if (contractData.value.personPdfUrl) {
|
||||
options.push({ label: '电子合同', value: 'contract' })
|
||||
}
|
||||
if (contractData.value.aqxysPath) {
|
||||
options.push({ label: '安全协议书', value: 'safety' })
|
||||
}
|
||||
documentOptions.value = options
|
||||
if (options.length > 0) {
|
||||
currentDocumentValue.value = options[0].value
|
||||
handleDocumentChange({ value: options[0].value })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理文档切换
|
||||
*/
|
||||
const handleDocumentChange = (option) => {
|
||||
const value = option.value || option
|
||||
if (value === 'contract') {
|
||||
loadDocument(contractData.value.personPdfUrl)
|
||||
} else if (value === 'safety') {
|
||||
loadDocument(contractData.value.aqxysPath)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回上一页
|
||||
*/
|
||||
const handleBack = () => {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看视频
|
||||
*/
|
||||
const handleViewVideo = () => {
|
||||
if (!videoUrl.value) {
|
||||
uni.$u.toast('暂无视频')
|
||||
return
|
||||
}
|
||||
showVideoModal.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* PDF加载完成
|
||||
*/
|
||||
const handleDocumentLoad = (data) => {
|
||||
console.log('PDF加载完成:', data)
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* PDF加载错误
|
||||
*/
|
||||
const handleDocumentError = (data) => {
|
||||
console.error('PDF加载失败:', data)
|
||||
error.value = data.error || 'PDF加载失败'
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 签署
|
||||
*/
|
||||
const handleSign = () => {
|
||||
showSignModal.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消签署
|
||||
*/
|
||||
const handleCancelSign = () => {
|
||||
showSignModal.value = false
|
||||
stampImageUrl.value = '' // 关闭时清空公章图片
|
||||
stampImagePath.value = ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公章
|
||||
*/
|
||||
const handleGetStamp = async () => {
|
||||
const phone = memberStore.realNameUserInfo.phone
|
||||
if (!phone) {
|
||||
uni.$u.toast('手机号不存在')
|
||||
return
|
||||
}
|
||||
|
||||
stampLoading.value = true
|
||||
try {
|
||||
const res = await getUserElectronicStampAPI({ phone })
|
||||
if (res.res === 1 && res.obj) {
|
||||
const imageUrl = res?.obj?.sealUrl
|
||||
stampImagePath.value = imageUrl
|
||||
if (imageUrl) {
|
||||
// 如果是相对路径,需要拼接完整URL
|
||||
if (imageUrl.startsWith('http')) {
|
||||
stampImageUrl.value = imageUrl
|
||||
} else {
|
||||
stampImageUrl.value = import.meta.env.VITE_API_FILE_ASE_URL + imageUrl
|
||||
}
|
||||
uni.$u.toast('获取公章成功')
|
||||
} else {
|
||||
uni.$u.toast('未找到公章信息')
|
||||
}
|
||||
} else {
|
||||
uni.$u.toast(res.resMsg || '获取公章失败')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('获取公章失败:', err)
|
||||
uni.$u.toast('获取公章失败,请稍后重试')
|
||||
} finally {
|
||||
stampLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认签署
|
||||
*/
|
||||
const handleConfirmSign = () => {
|
||||
handleSignContract()
|
||||
}
|
||||
|
||||
/**
|
||||
* 签署操作
|
||||
*/
|
||||
const handleSignContract = async () => {
|
||||
if (!contractData.value.id) {
|
||||
uni.$u.toast('合同信息不完整')
|
||||
return
|
||||
}
|
||||
|
||||
if (!stampImagePath.value) {
|
||||
uni.$u.toast('请先获取公章')
|
||||
return
|
||||
}
|
||||
|
||||
uni.showLoading({
|
||||
title: '签署中...',
|
||||
mask: true,
|
||||
})
|
||||
|
||||
try {
|
||||
const res = await signContractAPI({
|
||||
companySeal: stampImagePath.value, // 公司公章
|
||||
subPdfUrl: contractData.value.personPdfUrl,
|
||||
legalSeal: null,
|
||||
aqxysPath: contractData.value.aqxysPath,
|
||||
partBIdCard: contractData.value.partBIdCard,
|
||||
id: contractData.value.id,
|
||||
isSign: 1,
|
||||
})
|
||||
|
||||
uni.hideLoading()
|
||||
|
||||
if (res.res == 1) {
|
||||
uni.$u.toast('签署成功')
|
||||
showSignModal.value = false
|
||||
// 返回上一页并刷新
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
// 通知父组件刷新列表
|
||||
uni.$emit('refreshList')
|
||||
}, 500)
|
||||
} else {
|
||||
uni.$u.toast(res.resMsg || '签署失败')
|
||||
}
|
||||
} catch (err) {
|
||||
uni.hideLoading()
|
||||
uni.$u.toast('签署失败')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载文档
|
||||
*/
|
||||
const loadDocument = (url) => {
|
||||
if (!url) {
|
||||
error.value = '暂无文档'
|
||||
pdfUrl.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
|
||||
// 如果是相对路径,需要拼接完整URL
|
||||
if (url.startsWith('http')) {
|
||||
pdfUrl.value = url
|
||||
} else {
|
||||
pdfUrl.value = import.meta.env.VITE_API_FILE_ASE_URL + url
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载文档失败:', err)
|
||||
error.value = '加载失败,请重试'
|
||||
pdfUrl.value = ''
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载视频
|
||||
*/
|
||||
const loadVideo = () => {
|
||||
try {
|
||||
if (contractData.value.videoUrl) {
|
||||
const url = contractData.value.videoUrl
|
||||
if (url.startsWith('http')) {
|
||||
videoUrl.value = url
|
||||
} else {
|
||||
videoUrl.value = import.meta.env.VITE_API_FILE_ASE_URL + url
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载视频失败:', err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面加载
|
||||
*/
|
||||
onLoad((options) => {
|
||||
if (options.params) {
|
||||
try {
|
||||
contractData.value = JSON.parse(decodeURIComponent(options.params))
|
||||
|
||||
// 判断是否已签署
|
||||
isSigned.value = contractData.value.isSign == 1
|
||||
|
||||
// 初始化文档选项
|
||||
initDocumentOptions()
|
||||
|
||||
// 加载视频
|
||||
loadVideo()
|
||||
} catch (err) {
|
||||
console.error('解析合同数据失败:', err)
|
||||
uni.$u.toast('合同数据加载失败')
|
||||
}
|
||||
} else {
|
||||
uni.$u.toast('缺少合同数据')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.picker-section {
|
||||
padding: 24rpx 32rpx;
|
||||
background: #fff;
|
||||
border-bottom: 1rpx solid #e5e5e5;
|
||||
}
|
||||
|
||||
.loading-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 80rpx 32rpx;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-top: 24rpx;
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.error-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 80rpx 32rpx;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
margin-top: 24rpx;
|
||||
font-size: 28rpx;
|
||||
color: #e34d59;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.document-wrapper {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
position: relative;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.empty-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 80rpx 32rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
margin-top: 24rpx;
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.button-section {
|
||||
display: flex;
|
||||
padding: 32rpx;
|
||||
gap: 16rpx;
|
||||
background: #fff;
|
||||
border-top: 1rpx solid #e5e5e5;
|
||||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.back-btn:active {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.sign-popup {
|
||||
width: 640rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 80vh;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 32rpx;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sign-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
margin-bottom: 32rpx;
|
||||
padding-bottom: 20rpx;
|
||||
border-bottom: 2rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.sign-body {
|
||||
max-height: 60vh;
|
||||
}
|
||||
|
||||
.sign-block {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.block-title {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.info-text {
|
||||
padding: 20rpx;
|
||||
background: #f8f8f8;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #e5e5e5;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.stamp-section {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.stamp-label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 16rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stamp-preview {
|
||||
width: 100%;
|
||||
min-height: 200rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f8f8f8;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #e5e5e5;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.stamp-image {
|
||||
max-width: 100%;
|
||||
max-height: 300rpx;
|
||||
}
|
||||
|
||||
.stamp-placeholder {
|
||||
width: 100%;
|
||||
min-height: 200rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f8f8f8;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #e5e5e5;
|
||||
}
|
||||
|
||||
.placeholder-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.sign-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
.video-popup {
|
||||
width: 90vw;
|
||||
max-width: 750rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #000;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.video-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx 32rpx;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.video-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
width: 100%;
|
||||
height: 60vh;
|
||||
min-height: 400rpx;
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #000;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.video-player {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.video-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
<ReviewSearchBar
|
||||
@search="handleSearch"
|
||||
@action="handleBatchSign"
|
||||
action-button-text="批量签署"
|
||||
:action-button-text="showSignActions ? '批量签署' : ''"
|
||||
v-model="queryParams.keyWord"
|
||||
/>
|
||||
|
||||
|
|
@ -52,6 +52,7 @@
|
|||
|
||||
<view
|
||||
class="sign-status"
|
||||
v-if="activeTab != 0"
|
||||
:style="{ color: item.isSign == 1 ? '#4caf50' : '#ed7b2f' }"
|
||||
>
|
||||
{{ item.isSign == 1 ? '已签署' : '待签署' }}
|
||||
|
|
@ -106,12 +107,16 @@
|
|||
</view>
|
||||
|
||||
<!-- 全选按钮(仅待签署) -->
|
||||
<ReviewSelectAll v-if="showSignActions" @selectAll="handleSelectAll" />
|
||||
<ReviewSelectAll
|
||||
v-if="showSignActions"
|
||||
@selectAll="handleSelectAll"
|
||||
:isAllChecked="isAllChecked"
|
||||
/>
|
||||
|
||||
<!-- 签署确认弹窗 -->
|
||||
<up-popup :show="showSignModal" mode="center" round="20" @close="handleCancelSign">
|
||||
<view class="sign-popup">
|
||||
<view class="sign-title">确认签署</view>
|
||||
<view class="sign-title">签署</view>
|
||||
<scroll-view class="sign-body" scroll-y>
|
||||
<view class="sign-block">
|
||||
<text class="block-title">确认信息</text>
|
||||
|
|
@ -119,6 +124,22 @@
|
|||
<text>您确定要签署选中的 {{ checkedCount }} 份合同吗?</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stamp-section">
|
||||
<up-button
|
||||
text="获取公章"
|
||||
type="primary"
|
||||
@tap="handleGetStamp"
|
||||
:customStyle="getStampButtonStyle"
|
||||
:loading="stampLoading"
|
||||
/>
|
||||
<view class="stamp-label">电子公章</view>
|
||||
<view class="stamp-preview" v-if="stampImageUrl">
|
||||
<image :src="stampImageUrl" mode="aspectFit" class="stamp-image" />
|
||||
</view>
|
||||
<view v-else class="stamp-placeholder">
|
||||
<text class="placeholder-text">暂无公章</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="sign-actions">
|
||||
<up-button
|
||||
text="取消"
|
||||
|
|
@ -127,7 +148,7 @@
|
|||
:customStyle="cancelBtnStyle"
|
||||
/>
|
||||
<up-button
|
||||
text="确认签署"
|
||||
text="确认"
|
||||
type="primary"
|
||||
@tap="handleConfirmSign"
|
||||
:customStyle="confirmBtnStyle"
|
||||
|
|
@ -140,7 +161,7 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import NavBarModal from '@/components/NavBarModal/index.vue'
|
||||
import ReviewTabBar from '@/components/ReviewTabBar/index.vue'
|
||||
|
|
@ -148,13 +169,17 @@ import ReviewSearchBar from '@/components/ReviewSearchBar/index.vue'
|
|||
import ReviewEmptyState from '@/components/ReviewEmptyState/index.vue'
|
||||
import ReviewSelectAll from '@/components/ReviewSelectAll/index.vue'
|
||||
import { getContentStyle } from '@/utils/safeArea'
|
||||
import { getContractSignListAPI, signContractAPI } from '@/services/realName/contractSign'
|
||||
import {
|
||||
signContractAPI,
|
||||
getContractSignListAPI,
|
||||
getUserElectronicStampAPI,
|
||||
} from '@/services/realName/contractSign'
|
||||
import { useMemberStore, useCommonStore } from '@/stores'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const memberStore = useMemberStore()
|
||||
const commonStore = useCommonStore()
|
||||
|
||||
const isAllChecked = ref(false)
|
||||
const queryParams = ref({
|
||||
isSign: 0, // 0待签署 1已签署
|
||||
proId: commonStore.activeProjectId,
|
||||
|
|
@ -179,6 +204,14 @@ const showSignActions = computed(() => activeTab.value === 0)
|
|||
const showSignModal = ref(false)
|
||||
const cancelBtnStyle = { flex: 1, marginRight: '16rpx' }
|
||||
const confirmBtnStyle = { flex: 1 }
|
||||
const stampImageUrl = ref('')
|
||||
const stampImagePath = ref('')
|
||||
const stampLoading = ref(false)
|
||||
const getStampButtonStyle = {
|
||||
width: '100%',
|
||||
height: '88rpx',
|
||||
marginBottom: '24rpx',
|
||||
}
|
||||
|
||||
// 已选中的数量
|
||||
const checkedCount = computed(() => {
|
||||
|
|
@ -209,6 +242,11 @@ const handleSearch = (keyword) => {
|
|||
*/
|
||||
const handleBatchSign = () => {
|
||||
if (!showSignActions.value) return
|
||||
|
||||
if (list.value.length === 0) {
|
||||
uni.$u.toast('暂无可签署合同')
|
||||
return
|
||||
}
|
||||
const checkedList = list.value.filter((item) => item.isChecked)
|
||||
if (checkedList.length === 0) {
|
||||
uni.$u.toast('请选择要签署的合同')
|
||||
|
|
@ -222,6 +260,46 @@ const handleBatchSign = () => {
|
|||
*/
|
||||
const handleCancelSign = () => {
|
||||
showSignModal.value = false
|
||||
stampImageUrl.value = '' // 关闭时清空公章图片
|
||||
stampImagePath.value = ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公章
|
||||
*/
|
||||
const handleGetStamp = async () => {
|
||||
const phone = memberStore.realNameUserInfo.phone
|
||||
if (!phone) {
|
||||
uni.$u.toast('手机号不存在')
|
||||
return
|
||||
}
|
||||
|
||||
stampLoading.value = true
|
||||
try {
|
||||
const res = await getUserElectronicStampAPI({ phone })
|
||||
if (res.res === 1 && res.obj) {
|
||||
const imageUrl = res?.obj?.sealUrl
|
||||
stampImagePath.value = imageUrl
|
||||
if (imageUrl) {
|
||||
// 如果是相对路径,需要拼接完整URL
|
||||
if (imageUrl.startsWith('http')) {
|
||||
stampImageUrl.value = imageUrl
|
||||
} else {
|
||||
stampImageUrl.value = import.meta.env.VITE_API_FILE_ASE_URL + imageUrl
|
||||
}
|
||||
uni.$u.toast('获取公章成功')
|
||||
} else {
|
||||
uni.$u.toast('未找到公章信息')
|
||||
}
|
||||
} else {
|
||||
uni.$u.toast(res.resMsg || '获取公章失败')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('获取公章失败:', err)
|
||||
uni.$u.toast('获取公章失败,请稍后重试')
|
||||
} finally {
|
||||
stampLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -237,6 +315,7 @@ const handleConfirmSign = () => {
|
|||
const handleSelectAll = () => {
|
||||
if (!showSignActions.value) return
|
||||
const allChecked = list.value.every((item) => item.isChecked)
|
||||
isAllChecked.value = !allChecked
|
||||
list.value = list.value.map((item) => ({ ...item, isChecked: !allChecked }))
|
||||
}
|
||||
|
||||
|
|
@ -255,7 +334,7 @@ const handleCardClick = (item) => {
|
|||
// 待签署状态,点击可以选择复选框
|
||||
const params = encodeURIComponent(JSON.stringify(item))
|
||||
uni.navigateTo({
|
||||
url: `/pages/work/contract-review/detail?params=${params}`,
|
||||
url: `/pages/work/contract-sign/detail?params=${params}`,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -289,6 +368,11 @@ const handleSign = async () => {
|
|||
return
|
||||
}
|
||||
|
||||
if (!stampImagePath.value) {
|
||||
uni.$u.toast('请先获取公章')
|
||||
return
|
||||
}
|
||||
|
||||
// 显示加载提示
|
||||
uni.showLoading({
|
||||
title: '签署中...',
|
||||
|
|
@ -299,11 +383,13 @@ const handleSign = async () => {
|
|||
// 创建所有签署请求的 Promise 数组
|
||||
const signPromises = checkedList.map((item) => {
|
||||
return signContractAPI({
|
||||
signer: memberStore?.realNameUserInfo.id,
|
||||
signTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||
isSign: 1, // 1已签署
|
||||
companySeal: stampImagePath.value, // 公司公章
|
||||
subPdfUrl: item.personPdfUrl,
|
||||
legalSeal: null,
|
||||
aqxysPath: item.aqxysPath,
|
||||
partBIdCard: item.partBIdCard,
|
||||
id: item.id,
|
||||
isSign: 1,
|
||||
})
|
||||
.then((res) => {
|
||||
return {
|
||||
|
|
@ -362,6 +448,14 @@ const handleBack = () => {
|
|||
onLoad(() => {
|
||||
loadList()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
uni.$on('refreshList', loadList)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
uni.$off('refreshList', loadList)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
@ -523,6 +617,51 @@ onLoad(() => {
|
|||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.stamp-section {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.stamp-label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 16rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stamp-preview {
|
||||
width: 100%;
|
||||
min-height: 200rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f8f8f8;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #e5e5e5;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.stamp-image {
|
||||
max-width: 100%;
|
||||
max-height: 300rpx;
|
||||
}
|
||||
|
||||
.stamp-placeholder {
|
||||
width: 100%;
|
||||
min-height: 200rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f8f8f8;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #e5e5e5;
|
||||
}
|
||||
|
||||
.placeholder-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.sign-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
|
|
|||
|
|
@ -13,10 +13,18 @@ export const getContractSignListAPI = (data) => {
|
|||
})
|
||||
}
|
||||
|
||||
// 签署接口
|
||||
// 合同签署
|
||||
export const signContractAPI = (data) => {
|
||||
return realNameHttp({
|
||||
url: `/workPerson/updateSign?${initParams(data)}`,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
// 根据手机号码获取用户的电子公章
|
||||
export const getUserElectronicStampAPI = (data) => {
|
||||
return realNameHttp({
|
||||
url: `/personIdentify/selectSubMsgByPhone?${initParams(data)}`,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue