结算审批

This commit is contained in:
bb_pan 2025-11-10 14:24:24 +08:00
parent 9726f4daf8
commit 72e3667524
8 changed files with 709 additions and 1 deletions

View File

@ -1,5 +1,5 @@
{
"tabWidth": 4,
"tabWidth": 2,
"singleQuote": true,
"semi": false,
"printWidth": 100,

View File

@ -0,0 +1,148 @@
<template>
<div>
<!-- 弹框确认组件 -->
<uni-popup ref="popupDialog" type="dialog" :mask-click="false">
<view class="popup-content">
<view class="popup-title">{{ title }}</view>
<uni-row :gutter="24" style="margin: 10px 0">
<uni-col :span="8">
<div>{{ titleTip }}</div>
</uni-col>
<uni-col :span="16">
<div>
<radio-group @change="radioChange">
<radio value="2" :checked="radioValue == 2" style="margin-right: 8px">通过</radio>
<radio value="3" :checked="radioValue == 3">不通过</radio>
</radio-group>
</div>
</uni-col>
</uni-row>
<uni-easyinput type="textarea" v-model="remark" placeholder="请输入原因" autoHeight />
<view class="popup-btns">
<view class="btn cancel" v-if="showClose" @click="closePopup">{{ leftBtn }}</view>
<view class="btn confirm" :class="{ disabled: countdown > 0 }" @click="confirm">
<span>{{ rightBtn }}</span>
{{ countdown > 0 ? '(' + countdown + ')' : '' }}
</view>
</view>
</view>
</uni-popup>
</div>
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps({
title: { type: String, default: '提示' },
titleTip: { type: String, default: '结算审批' },
content: { type: String, default: '是否确定提交?' },
showClose: { type: Boolean, default: true },
leftBtn: { type: String, default: '取 消' },
rightBtn: { type: String, default: '确 定' },
showRemark: { type: Boolean, default: false },
})
const emit = defineEmits(['confirm'])
const popupDialog = ref(null)
const remark = ref('')
const timer = ref(null)
const countdown = ref(0)
const radioValue = ref('2')
let resolvePromise = null // Promise resolve
// Promise
const openPopup = () => {
return new Promise((resolve) => {
resolvePromise = resolve // resolve
popupDialog.value.open()
remark.value = '' //
//
if (timer.value) clearInterval(timer.value)
countdown.value = 3
timer.value = setInterval(() => {
if (countdown.value > 0) {
countdown.value--
} else {
clearInterval(timer.value)
timer.value = null
countdown.value = 0
}
}, 1000)
})
}
const radioChange = (e) => {
console.log('🚀 ~ radioChange ~ e:', e.detail.value, radioValue.value)
radioValue.value = e.detail.value
}
//
const closePopup = () => {
popupDialog.value.close()
if (resolvePromise) resolvePromise({ data: false })
resolvePromise = null
}
//
const confirm = () => {
if (countdown.value > 0) return
popupDialog.value.close()
const params = {
data: true,
radioValue: radioValue.value,
remark: remark.value,
}
if (resolvePromise) resolvePromise(params)
resolvePromise = null
}
//
defineExpose({ openPopup })
</script>
<style lang="scss" scoped>
.popup-content {
padding: 20rpx;
background-color: #fff;
border-radius: 10rpx;
width: 80vw;
}
.popup-title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 20rpx;
}
.popup-message {
font-size: 36rpx;
font-weight: bold;
margin: 30px 0;
text-align: center;
}
.popup-btns {
display: flex;
border-top: 1px solid #eee;
height: 88rpx;
line-height: 88rpx;
text-align: center;
}
.btn {
margin-top: 3px;
flex: 1;
font-size: 34rpx;
}
.cancel {
color: #666;
border-right: 1px solid #eee;
}
.confirm {
color: #007aff;
font-weight: 500;
&.disabled {
color: #999;
}
}
</style>

View File

@ -855,6 +855,18 @@
"navigationBarTitleText": "OCR盘点入库"
}
},
{
"path": "pages/settleAccounts/index",
"style": {
"navigationBarTitleText": "结算审批"
}
},
{
"path": "pages/settleAccounts/details",
"style": {
"navigationBarTitleText": "结算审批"
}
},
// start
//
{

View File

@ -0,0 +1,175 @@
<template>
<div>
<div class="card">
<uni-row :gutter="24">
<uni-col :span="16">施工机具有偿使用费</uni-col>
<uni-col :span="8">
<div class="cont"> {{ Number(agreementContent.leaseCost).toFixed(2) }}</div>
</uni-col>
</uni-row>
<uni-row :gutter="24">
<uni-col :span="16">施工机具维修费</uni-col>
<uni-col :span="8">
<div class="cont"> {{ Number(agreementContent.repairCost).toFixed(2) }}</div>
</uni-col>
</uni-row>
<uni-row :gutter="24">
<uni-col :span="16">施工机具丢失费</uni-col>
<uni-col :span="8">
<div class="cont"> {{ Number(agreementContent.loseCost).toFixed(2) }}</div>
</uni-col>
</uni-row>
<uni-row :gutter="24">
<uni-col :span="16">施工机具损坏赔偿费</uni-col>
<uni-col :span="8">
<div class="cont"> {{ Number(agreementContent.scrapCost).toFixed(2) }}</div>
</uni-col>
</uni-row>
<uni-row :gutter="24">
<uni-col :span="16">施工机具租赁减免费</uni-col>
<uni-col :span="8">
<div class="cont"> {{ Number(agreementContent.reductionCost).toFixed(2) }}</div>
</uni-col>
</uni-row>
<div class="line"></div>
<uni-row :gutter="24">
<uni-col :span="16">费用合计金额大写</uni-col>
</uni-row>
<uni-row :gutter="24">
<uni-col :span="16">
{{ Number(agreementContent.costAll) > 0 ? agreementContent.costAllUpper : '' }}
</uni-col>
<uni-col :span="8">
<div class="cont"> {{ Number(agreementContent.costAll).toFixed(2) }}</div>
</uni-col>
</uni-row>
</div>
<!-- 提交 -->
<div v-if="row.active == 1">
<button type="primary" @click="submit"> </button>
</div>
</div>
<PopupConfirm ref="popupConfirm" />
</template>
<script setup>
import { onLoad } from '@dcloudio/uni-app'
import { ref } from 'vue'
import { getSltInfoApi, costExamineApi } from '@/services/accounts/index'
import { toChineseAmount } from '@/utils/bnsBase'
import PopupConfirm from '@/components/PopupConfirm/approve'
const row = ref(null)
const agreementContent = ref({
agreementCode: '',
projectName: '',
unitName: '',
leaseCost: '', //
repairCost: '', //
loseCost: '', //
scrapCost: '', //
reductionCost: '', //
costAll: '', //
costAllUpper: '', //
})
const popupConfirm = ref()
onLoad((opts) => {
row.value = JSON.parse(opts.params)
console.log('🚀 ~ row.value:', row.value)
getInfo()
})
const getInfo = async () => {
try {
const res = await getSltInfoApi([row.value])
if (!res.data) return
agreementContent.value = res.data
agreementContent.value.costAll =
Number(res.data.leaseCost) +
Number(res.data.repairCost) +
Number(res.data.scrapCost) +
Number(res.data.loseCost) -
Number(res.data.reductionCost)
agreementContent.value.costAllUpper = toChineseAmount(agreementContent.value.costAll.toFixed(2))
} catch (error) {
console.log('🚀 ~ getInfo ~ error:', error)
}
}
const submit = async () => {
const res = await popupConfirm.value.openPopup()
if (!res.data) return
uni.showLoading({
title: '加载中...',
})
try {
const params = {
id: row.value.id,
status: res.radioValue,
remark: res.remark,
agreementId: row.value.agreementId,
}
console.log('🚀 ~ submit ~ params:', params)
await costExamineApi(params)
uni.showToast({
title: '审批成功!',
icon: 'none',
})
//
uni.navigateBack()
} catch (error) {
console.log('🚀 ~ submit ~ error:', error)
} finally {
uni.hideLoading()
}
}
</script>
<style lang="scss" scoped>
page {
background-color: #f7f8fa;
padding: 24rpx;
}
.card {
margin: 24rpx 0;
padding: 32rpx;
background-color: #fff;
min-height: 300rpx;
border-radius: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
transition: all 0.3s ease;
&:active {
transform: scale(0.985);
background-color: #fafbfc;
}
}
::v-deep .uni-row {
.uni-col-6 {
color: #8c8c8c;
font-size: 28rpx;
font-weight: 500;
}
.uni-col-8 {
color: #8c8c8c;
font-size: 28rpx;
font-weight: 500;
}
}
.cont {
display: flex;
justify-content: flex-end;
line-height: 1.8;
color: #262626;
font-size: 28rpx;
font-weight: 500;
}
.line {
border-bottom: 1px solid #ebebeb;
margin: 10px 0;
}
</style>

View File

@ -0,0 +1,302 @@
<template>
<div class="page-container">
<view class="complete-btn">
<view class="btn" @click="changeTab(1)">
<span>待审批</span>
<div v-if="active == 1" class="bt-line"></div>
</view>
<view class="btn" style="margin-left: 80rpx" @click="changeTab(2)">
<span>审批通过</span>
<div v-if="active == 2" class="bt-line"></div>
</view>
</view>
<!-- 滚动列表 -->
<scroll-view
scroll-y
:scroll-top="scrollTop"
@scroll="onScroll"
@scrolltolower="onScrollTolower"
class="scroll-container"
>
<view
class="table-list-item"
:key="index"
@click="handleItem(item)"
v-for="(item, index) in tableList"
>
<uni-swipe-action class="swipe-action">
<uni-swipe-action-item>
<div class="title">
<span class="code"
><span style="color: #333">{{ index + 1 }}. </span>{{ item.agreementCode }}</span
>
<div class="cont">
<uni-tag
:text="item.sltStatus == 1 ? '待审核' : '审批通过'"
:type="item.sltStatus == 1 ? 'warning' : 'success'"
/>
</div>
</div>
<div class="line"></div>
<uni-row :gutter="24">
<uni-col :span="6">结算单位</uni-col>
<uni-col :span="18">
<div class="cont">{{ item.unitName }}</div>
</uni-col>
</uni-row>
<uni-row :gutter="24">
<uni-col :span="6">结算工程</uni-col>
<uni-col :span="18">
<div class="cont">{{ item.projectName }}</div>
</uni-col>
</uni-row>
<uni-row :gutter="24">
<uni-col :span="8">合计费用()</uni-col>
<uni-col :span="16">
<div class="cont">{{ parseFloat(item.costs || 0).toFixed(2) }}</div>
</uni-col>
</uni-row>
<uni-row :gutter="24">
<uni-col :span="6">结算类型</uni-col>
<uni-col :span="18">
<div class="cont">
<uni-tag v-if="item.settlementType == 1" text="工器具" type="primary" />
<uni-tag v-else-if="item.settlementType == 2" text="安全工器具" type="warning" />
<uni-tag v-else-if="item.settlementType == 3" text="总费用" type="success" />
</div>
</uni-col>
</uni-row>
<uni-row :gutter="24" v-if="active == 2">
<uni-col :span="6">审批人</uni-col>
<uni-col :span="18">
<div class="cont">{{ item.auditor }}</div>
</uni-col>
</uni-row>
<uni-row :gutter="24" v-if="active == 2">
<uni-col :span="6">审批时间</uni-col>
<uni-col :span="18">
<div class="cont">{{ item.auditTime }}</div>
</uni-col>
</uni-row>
</uni-swipe-action-item>
</uni-swipe-action>
</view>
<view class="loading-text">
{{ tableList.length == total ? '没有更多数据了~' : '正在加载...' }}
</view>
</scroll-view>
</div>
</template>
<script setup>
import { onLoad, onShow } from '@dcloudio/uni-app'
import { ref, reactive } from 'vue'
import { getSltListApi } from '@/services/accounts/index'
const active = ref(1)
const tableList = ref([])
const total = ref(0)
const queryParams = ref({
pageNum: 1,
pageSize: 10,
sltStatus: 1, // 1 2
})
onShow(() => {
getList()
})
// tap
const changeTab = (index) => {
uni.setStorageSync('scrollTop', 0)
uni.setStorageSync('activeTab', index)
uni.removeStorageSync('queryParams')
uni.removeStorageSync('scrollTop')
active.value = index
queryParams.value.pageNum = 1
queryParams.value.pageSize = 20
tableList.value = []
if (index == 1) {
queryParams.value.sltStatus = 1
} else {
queryParams.value.sltStatus = 2
}
getList()
}
const getList = async () => {
uni.showLoading({
title: '加载中...',
})
try {
const params = {
...queryParams.value,
}
const res = await getSltListApi(params)
console.log('🚀 ~ getList ~ res:', res)
tableList.value = res.rows || []
total.value = res.total || 0
} catch (error) {
console.log('🚀 ~ getList ~ error:', error)
} finally {
uni.hideLoading()
}
}
//
const onScrollTolower = () => {
console.log('🚀 ~ onScrollTolower ~ onScrollTolower:')
if (total.value > tableList.value.length) {
queryParams.value.pageSize += 10
getList()
}
}
const handleItem = (item) => {
uni.navigateTo({
url: `/pages/settleAccounts/details?params=${JSON.stringify({
...item,
active: active.value,
})}`,
})
}
</script>
<style lang="scss" scoped>
.page-container {
display: flex;
height: 100vh;
flex-direction: column;
background-color: #f7f8fa;
padding: 24rpx;
}
.complete-btn {
display: flex;
justify-content: center;
.btn {
display: flex;
flex-direction: column;
align-items: center;
padding: 0 60rpx;
position: relative;
transition: all 0.3s ease;
span {
font-size: 32rpx;
color: #8c8c8c;
font-weight: 500;
&.active {
color: #3784fb;
font-weight: 600;
.second-active & {
color: #fa8c16;
}
}
}
.bt-line {
width: 32rpx;
height: 6rpx;
background: #3784fb;
margin-top: 12rpx;
border-radius: 6rpx;
transition: all 0.3s ease;
.second-active & {
background: #fa8c16;
}
}
}
}
.scroll-container {
padding: 0 2rpx;
.table-list-item {
margin: 24rpx 0;
padding: 32rpx;
background-color: #fff;
min-height: 300rpx;
border-radius: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
transition: all 0.3s ease;
&:active {
transform: scale(0.985);
background-color: #fafbfc;
}
.title {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
.code {
font-size: 32rpx;
font-weight: 600;
color: #3784fb;
letter-spacing: 1rpx;
}
span.status {
padding: 8rpx 28rpx;
border-radius: 8rpx;
font-size: 26rpx;
font-weight: 600;
&.completed {
background-color: rgba(82, 196, 26, 0.1);
color: #52c41a;
}
&.pending {
background-color: rgba(250, 140, 22, 0.1);
color: #fa8c16;
}
}
}
.line {
margin: 24rpx 0;
height: 2rpx;
background: linear-gradient(
90deg,
rgba(232, 232, 232, 0) 0%,
rgba(232, 232, 232, 1) 50%,
rgba(232, 232, 232, 0) 100%
);
}
:deep(.uni-row) {
.uni-col-6 {
color: #8c8c8c;
font-size: 28rpx;
font-weight: 500;
}
.uni-col-8 {
color: #8c8c8c;
font-size: 28rpx;
font-weight: 500;
}
}
.cont {
display: flex;
justify-content: flex-end;
line-height: 1.8;
color: #262626;
font-size: 28rpx;
font-weight: 500;
}
}
//
.loading-text {
text-align: center;
font-size: 26rpx;
color: #8c8c8c;
padding: 32rpx 0;
letter-spacing: 1rpx;
}
}
</style>

View File

@ -396,6 +396,11 @@ const inStorageList = ref([
url: '/pages/inStorage/index',
iconSrc: '../../static/workbench/minPai.png',
},
{
title: '结算管理',
url: '/pages/settleAccounts/index',
iconSrc: '../../static/workbench/minPai.png',
},
])
const inStorageListTwo = computed(() => {

View File

@ -0,0 +1,28 @@
import { http } from '@/utils/http'
// 结算审批-列表
export const getSltListApi = (data) => {
return http({
method: 'GET',
url: '/material/slt_agreement_info/getSltList',
data,
})
}
// 协议书
export const getSltInfoApi = (data) => {
return http({
method: 'POST',
url: '/material/slt_agreement_info/getSltInfo',
data,
})
}
// 结算审批
export const costExamineApi = (data) => {
return http({
method: 'POST',
url: '/material/slt_agreement_info/costExamine',
data,
})
}

View File

@ -59,3 +59,41 @@ export const formatDiff = (...nums) => {
return parseFloat(result.toFixed(3))
}
}
export function toChineseAmount(n) {
const fraction = ['角', '分']
const digit = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']
const unit = [
['元', '万', '亿'],
['', '拾', '佰', '仟']
]
let head = n < 0 ? '负' : ''
n = Math.abs(n)
let s = ''
// 处理小数部分
for (let i = 0; i < fraction.length; i++) {
s += (digit[Math.floor(n * 10 * Math.pow(10, i)) % 10] + fraction[i]).replace(/零./, '')
}
s = s || '整'
// 处理整数部分
n = Math.floor(n)
for (let i = 0; i < unit[0].length && n > 0; i++) {
let p = ''
for (let j = 0; j < unit[1].length && n > 0; j++) {
p = digit[n % 10] + unit[1][j] + p
n = Math.floor(n / 10)
}
s = p.replace(/(零.)*零$/, '').replace(/^$/, '零') + unit[0][i] + s
}
return (
head +
s
.replace(/(零.)*零元/, '元')
.replace(/(零.)+/g, '零')
.replace(/^整$/, '零元整')
)
}