hn_platform_h5/src/pages/work/contract-review/index.vue

603 lines
18 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="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>