603 lines
18 KiB
Vue
603 lines
18 KiB
Vue
<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" :style="contentStyle">
|
||
<!-- 标签栏 -->
|
||
<ReviewTabBar v-model="activeTab" :tabs="tabs" @change="handleTabChange" />
|
||
|
||
<!-- 搜索栏 -->
|
||
<ReviewSearchBar
|
||
@search="handleSearch"
|
||
@action="handleBatchReview"
|
||
v-model="queryParams.keyWord"
|
||
:action-button-text="showReviewActions ? '批量审核' : ''"
|
||
/>
|
||
|
||
<!-- 列表内容 -->
|
||
<scroll-view class="list-container" scroll-y>
|
||
<ReviewEmptyState v-if="list.length === 0" text="暂无数据" />
|
||
<view v-else class="card-list">
|
||
<view
|
||
:key="idx"
|
||
class="card"
|
||
v-for="(item, idx) in list"
|
||
@tap="handleCardClick(item)"
|
||
>
|
||
<view class="card-header">
|
||
<view class="left">
|
||
<view v-if="showReviewActions" class="checkbox" @tap.stop>
|
||
<up-checkbox-group>
|
||
<up-checkbox
|
||
shape="square"
|
||
activeColor="#07c160"
|
||
:checked="item.isChecked"
|
||
@change="handleCheckChange(item, $event)"
|
||
/>
|
||
</up-checkbox-group>
|
||
</view>
|
||
<view class="name-row">
|
||
<text class="name">{{ item.partB || '-' }}</text>
|
||
<text class="party-tag party-b">乙</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view
|
||
class="audit-status"
|
||
v-if="activeTab != 0"
|
||
:style="{
|
||
color: item.isAudit == 1 ? '#4caf50' : '#e34d59',
|
||
}"
|
||
>
|
||
{{ item.isAudit == 1 ? '已通过' : '未通过' }}
|
||
</view>
|
||
</view>
|
||
<view class="row">
|
||
<text class="label">身份证:</text>
|
||
<text class="value">{{ item.partBIdCard || '-' }}</text>
|
||
</view>
|
||
<view class="row">
|
||
<text class="label">工资核定方式:</text>
|
||
<text class="value">{{ item.verMethod || '-' }}</text>
|
||
</view>
|
||
<view class="row">
|
||
<text class="label">工资核定标准:</text>
|
||
<text class="value">{{ item.verStand || '-' }}</text>
|
||
</view>
|
||
<view class="row">
|
||
<text class="label">绩效核定标准:</text>
|
||
<text class="value">{{ item.achievementsVerification || '-' }}</text>
|
||
</view>
|
||
<view class="row">
|
||
<text class="label">绩效奖金区间:</text>
|
||
<text class="value">
|
||
{{ item.bonusA + ' - ' + item.bonusB || '-' }}
|
||
</text>
|
||
</view>
|
||
<view class="row">
|
||
<text class="label">工作地点:</text>
|
||
<text class="value">{{ item.workAdress || '-' }}</text>
|
||
</view>
|
||
<view class="row">
|
||
<text class="label">是否为小包干班组:</text>
|
||
<text class="value">{{ item.isXbg }}</text>
|
||
</view>
|
||
<view class="row">
|
||
<view class="party-row">
|
||
<text class="label">甲方:</text>
|
||
<text class="value">{{ item.partA || '-' }}</text>
|
||
<text class="party-tag party-a">甲</text>
|
||
</view>
|
||
</view>
|
||
<view class="row">
|
||
<text class="label">生效日期:</text>
|
||
<text class="value">{{ item.effectDate || '-' }}</text>
|
||
<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>
|
||
</view>
|
||
|
||
<!-- 全选按钮(仅待审核) -->
|
||
<ReviewSelectAll v-if="showReviewActions" @selectAll="handleSelectAll" />
|
||
|
||
<!-- 审核弹窗 -->
|
||
<up-popup :show="showReviewModal" mode="center" round="20" @close="handleCancelReview">
|
||
<view class="review-popup">
|
||
<view class="review-title">审核</view>
|
||
<scroll-view class="review-body" scroll-y>
|
||
<view class="review-block">
|
||
<text class="block-title">是否有效</text>
|
||
<view class="radio-group">
|
||
<up-radio-group v-model="auditStatus" placement="row">
|
||
<up-radio
|
||
:name="1"
|
||
label="通过"
|
||
activeColor="#4caf50"
|
||
:labelColor="auditStatus === 1 ? '#4caf50' : '#333'"
|
||
/>
|
||
<up-radio
|
||
:name="2"
|
||
label="未通过"
|
||
activeColor="#e34d59"
|
||
:labelColor="auditStatus === 2 ? '#e34d59' : '#333'"
|
||
/>
|
||
</up-radio-group>
|
||
</view>
|
||
</view>
|
||
<view class="review-block">
|
||
<text class="block-title">备注</text>
|
||
<up-textarea
|
||
count
|
||
autoHeight
|
||
maxlength="200"
|
||
border="surround"
|
||
v-model="reviewRemark"
|
||
placeholder="请输入审核意见"
|
||
/>
|
||
</view>
|
||
<view class="review-actions">
|
||
<up-button
|
||
text="取消"
|
||
type="default"
|
||
@tap="handleCancelReview"
|
||
:customStyle="cancelBtnStyle"
|
||
/>
|
||
<up-button
|
||
text="确认"
|
||
type="primary"
|
||
@tap="handleConfirmReview"
|
||
:customStyle="confirmBtnStyle"
|
||
/>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</up-popup>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
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'
|
||
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 { getContractReviewListAPI, auditContractAPI } from '@/services/realName/contractReview'
|
||
import { useMemberStore, useCommonStore } from '@/stores'
|
||
import dayjs from 'dayjs'
|
||
|
||
const memberStore = useMemberStore()
|
||
const commonStore = useCommonStore()
|
||
|
||
const queryParams = ref({
|
||
isNotAudit: 0, // 0待审核 1已审核
|
||
proId: commonStore.activeProjectId,
|
||
keyWord: '',
|
||
subId: memberStore.realNameUserInfo.subId,
|
||
})
|
||
|
||
const contentStyle = computed(() => {
|
||
return getContentStyle({
|
||
includeNavBar: true,
|
||
includeStatusBar: true,
|
||
includeBottomSafeArea: false, // 底部有全选按钮,不需要额外padding
|
||
})
|
||
})
|
||
|
||
// 标签配置
|
||
const tabs = [{ label: '待审核' }, { label: '已审核' }]
|
||
|
||
const activeTab = ref(0)
|
||
const list = ref([])
|
||
const showReviewActions = computed(() => activeTab.value === 0)
|
||
const showReviewModal = ref(false)
|
||
const reviewRemark = ref('')
|
||
const auditStatus = ref(1) // 1通过 2未通过,默认通过
|
||
const cancelBtnStyle = { flex: 1, marginRight: '16rpx' }
|
||
const confirmBtnStyle = { flex: 1 }
|
||
|
||
/**
|
||
* 处理标签切换
|
||
* @param {Number} index - 标签索引
|
||
*/
|
||
const handleTabChange = (index) => {
|
||
activeTab.value = index
|
||
queryParams.value.isNotAudit = index
|
||
loadList()
|
||
}
|
||
|
||
/**
|
||
* 处理搜索
|
||
* @param {String} keyword - 搜索关键词
|
||
*/
|
||
const handleSearch = (keyword) => {
|
||
queryParams.value.keyWord = keyword || ''
|
||
loadList()
|
||
}
|
||
|
||
/**
|
||
* 处理批量审核
|
||
*/
|
||
const handleBatchReview = () => {
|
||
if (!showReviewActions.value) return
|
||
const checkedList = list.value.filter((item) => item.isChecked)
|
||
if (list.value.length === 0) {
|
||
uni.$u.toast('当前没有待审核的合同')
|
||
return
|
||
}
|
||
if (checkedList.length === 0) {
|
||
uni.$u.toast('请选择要审核的合同')
|
||
return
|
||
}
|
||
reviewRemark.value = ''
|
||
auditStatus.value = 1 // 重置为默认值:通过
|
||
showReviewModal.value = true
|
||
}
|
||
|
||
/**
|
||
* 取消审核
|
||
*/
|
||
const handleCancelReview = () => {
|
||
showReviewModal.value = false
|
||
reviewRemark.value = ''
|
||
auditStatus.value = 1
|
||
}
|
||
|
||
/**
|
||
* 确认审核
|
||
*/
|
||
const handleConfirmReview = () => {
|
||
handleAudit(auditStatus.value)
|
||
}
|
||
|
||
/**
|
||
* 处理全选
|
||
*/
|
||
const handleSelectAll = () => {
|
||
if (!showReviewActions.value) return
|
||
const allChecked = list.value.every((item) => item.isChecked)
|
||
list.value = list.value.map((item) => ({ ...item, isChecked: !allChecked }))
|
||
}
|
||
|
||
/**
|
||
* 处理复选框变化
|
||
*/
|
||
const handleCheckChange = (item, val) => {
|
||
item.isChecked = val
|
||
}
|
||
|
||
/**
|
||
* 处理卡片点击
|
||
*/
|
||
const handleCardClick = (item) => {
|
||
// 如果是在待审核状态,不跳转(用于选择复选框)
|
||
// if (showReviewActions.value) {
|
||
// return
|
||
// }
|
||
// 已审核状态,跳转到详情页面
|
||
const params = encodeURIComponent(JSON.stringify(item))
|
||
uni.navigateTo({
|
||
url: `/pages/work/contract-review/detail?params=${params}`,
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 加载列表数据
|
||
*/
|
||
const loadList = async () => {
|
||
try {
|
||
const res = await getContractReviewListAPI(queryParams.value)
|
||
if (res.obj === 'is null' || !res.obj) {
|
||
list.value = []
|
||
return
|
||
}
|
||
list.value = (res?.obj || []).map((item) => ({
|
||
...item,
|
||
isChecked: false,
|
||
}))
|
||
} catch (err) {
|
||
uni.$u.toast('获取列表失败')
|
||
list.value = []
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 审核操作
|
||
*/
|
||
const handleAudit = async (auditStatus) => {
|
||
const checkedList = list.value.filter((item) => item.isChecked)
|
||
if (checkedList.length === 0) {
|
||
uni.$u.toast('请选择要审核的合同')
|
||
return
|
||
}
|
||
|
||
// 显示加载提示
|
||
uni.showLoading({
|
||
title: '审核中...',
|
||
mask: true,
|
||
})
|
||
|
||
try {
|
||
// 创建所有审核请求的 Promise 数组
|
||
const auditPromises = checkedList.map((item) => {
|
||
return auditContractAPI({
|
||
auditor: memberStore?.realNameUserInfo.id,
|
||
auditTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||
isAudit: auditStatus,
|
||
remarks: reviewRemark.value,
|
||
partBIdCard: item.partBIdCard,
|
||
id: item.id,
|
||
})
|
||
.then((res) => {
|
||
return {
|
||
name: item.partB || item.partBIdCard,
|
||
success: res.res == 1,
|
||
message: res.res == 1 ? '成功' : res.resMsg || '失败',
|
||
}
|
||
})
|
||
.catch((err) => {
|
||
return {
|
||
name: item.partB || item.partBIdCard,
|
||
success: false,
|
||
message: '失败',
|
||
}
|
||
})
|
||
})
|
||
|
||
// 使用 Promise.all 并发执行所有请求
|
||
const results = await Promise.all(auditPromises)
|
||
|
||
// 关闭加载提示
|
||
uni.hideLoading()
|
||
|
||
// 统计成功和失败数量
|
||
const successCount = results.filter((r) => r.success).length
|
||
const failCount = results.length - successCount
|
||
const hasSuccess = successCount > 0
|
||
|
||
// 根据结果决定是否刷新列表
|
||
if (hasSuccess) {
|
||
loadList()
|
||
showReviewModal.value = false
|
||
reviewRemark.value = ''
|
||
auditStatus.value = 1
|
||
}
|
||
|
||
// 统一提示结果
|
||
if (successCount === results.length) {
|
||
uni.$u.toast(`全部审核成功(${successCount}条)`)
|
||
} else if (failCount === results.length) {
|
||
uni.$u.toast(`全部审核失败(${failCount}条)`)
|
||
} else {
|
||
uni.$u.toast(`审核完成:成功${successCount}条,失败${failCount}条`)
|
||
}
|
||
} catch (err) {
|
||
uni.hideLoading()
|
||
uni.$u.toast('审核过程中发生错误')
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 返回上一页
|
||
*/
|
||
const handleBack = () => {
|
||
uni.navigateBack()
|
||
}
|
||
|
||
onLoad(() => {
|
||
loadList()
|
||
})
|
||
|
||
onMounted(() => {
|
||
uni.$on('refreshList', loadList)
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
uni.$off('refreshList', loadList)
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.page-container {
|
||
height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: #f5f7fa;
|
||
}
|
||
|
||
.content {
|
||
// flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
min-height: 0; /* 重要:确保flex子元素可以收缩 */
|
||
}
|
||
|
||
.list-container {
|
||
// flex: 1;
|
||
min-height: 0; /* 重要:确保scroll-view可以正确计算高度 */
|
||
padding: 24rpx;
|
||
box-sizing: border-box;
|
||
background: #f5f7fa;
|
||
}
|
||
|
||
.card-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.card {
|
||
background: #fff;
|
||
border-radius: 16rpx;
|
||
padding: 24rpx;
|
||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.card-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16rpx;
|
||
.left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
}
|
||
}
|
||
|
||
.name-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.name,
|
||
.audit-status {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
|
||
.party-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.party-tag {
|
||
display: inline-block;
|
||
padding: 4rpx 12rpx;
|
||
border-radius: 4rpx;
|
||
font-size: 22rpx;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.party-a {
|
||
background: #fff4e6;
|
||
color: #ff9800;
|
||
}
|
||
|
||
.party-b {
|
||
background: #e8f5e9;
|
||
color: #4caf50;
|
||
}
|
||
|
||
.row {
|
||
margin-top: 10rpx;
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.label {
|
||
color: #666;
|
||
margin-right: 8rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.value {
|
||
color: #333;
|
||
flex: 1;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.checkbox {
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.review-popup {
|
||
width: 640rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
max-height: 80vh;
|
||
background: #fff;
|
||
border-radius: 20rpx;
|
||
padding: 32rpx;
|
||
box-sizing: border-box;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.review-title {
|
||
font-size: 36rpx;
|
||
font-weight: 700;
|
||
color: #333;
|
||
margin-bottom: 32rpx;
|
||
padding-bottom: 20rpx;
|
||
border-bottom: 2rpx solid #f0f0f0;
|
||
}
|
||
|
||
.review-body {
|
||
max-height: 60vh;
|
||
}
|
||
|
||
.review-block {
|
||
margin-bottom: 32rpx;
|
||
}
|
||
|
||
.block-title {
|
||
display: block;
|
||
font-size: 30rpx;
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.radio-group {
|
||
padding: 20rpx;
|
||
background: #f8f8f8;
|
||
border-radius: 12rpx;
|
||
border: 1rpx solid #e5e5e5;
|
||
}
|
||
|
||
.review-actions {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
margin-top: 32rpx;
|
||
}
|
||
|
||
.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);
|
||
}
|
||
</style>
|