Files
shanxia2.0/miniprogram/pages/bx/advance/list.vue
T

345 lines
10 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="bx-page">
<view class="topbar" :style="safeAreaStyle">
<view class="topbar-left">
<text class="topbar-back" @click="goBack">←</text>
<text class="topbar-title">备用金申请</text>
</view>
<view class="topbar-right" />
</view>
<view class="fab-add" @click="goApply()">
<view class="fab-add__bar fab-add__bar--h" />
<view class="fab-add__bar fab-add__bar--v" />
</view>
<view class="search-wrap">
<view class="search-box">
<input
class="search-input"
v-model="keyword"
placeholder="搜索备用金申请"
confirm-type="search"
@confirm="onRefresh"
/>
<view v-if="keyword.trim()" class="search-box__clear" @click="clearKeyword">×</view>
</view>
</view>
<view class="search-wrap" style="padding-top: 0">
<scroll-view scroll-x class="search-filter-scroll" :show-scrollbar="false">
<view class="search-filter">
<view
v-for="tab in tabs"
:key="tab.value === '' ? '__all' : tab.value"
class="filter-tag"
:class="{ active: activeTab === tab.value }"
@click="onTabChange(tab.value)"
>
{{ tab.label }}
</view>
</view>
</scroll-view>
</view>
<scroll-view
class="page-padded list-scroll"
scroll-y
:style="{ height: listHeight }"
refresher-enabled
:refresher-triggered="refreshing"
@refresherrefresh="onPullRefresh"
@scrolltolower="onLoadMore"
>
<view v-if="!list.length && !loading" class="empty-state empty-state--fill">
<text>暂无备用金记录</text>
</view>
<view
v-for="item in list"
:key="item.id"
class="expense-card"
@click="goDetail(item.id)"
>
<view class="ec-top">
<view class="ec-type">
<text class="advance-list-card__title">{{ item.reason || '—' }}</text>
</view>
<text class="status-tag" :class="workflowListStatusTagClass(item.status)">
{{ statusLabel(item.status) }}
</text>
</view>
<view class="ec-amount-label">申请金额</view>
<view class="ec-amount">{{ formatAmount(item.amount) }}</view>
<view class="advance-card-subs">
<view class="advance-card-sub-row">
<text class="k">实际发放金额</text>
<text class="v">¥ {{ actualIssuedDisplay(item) }}</text>
</view>
<view class="advance-card-sub-row">
<text class="k">实际冲抵</text>
<text class="v">¥ {{ formatAmount(offsetTotal(item)) }}</text>
</view>
</view>
<view class="list-card-kv">
<view class="list-card-kv__row">
<text class="list-card-kv__k">申请单号</text>
<text class="list-card-kv__v">{{ item.applyCode || '—' }}</text>
</view>
<view class="list-card-kv__row">
<text class="list-card-kv__k">申请人</text>
<text class="list-card-kv__v">{{ item.applicantName || '-' }}</text>
</view>
<view v-if="item.currentNode" class="list-card-kv__row">
<text class="list-card-kv__k">当前环节</text>
<text class="list-card-kv__v">{{ item.currentNode }}</text>
</view>
<view v-if="item.remark" class="list-card-kv__row">
<text class="list-card-kv__k">备注</text>
<text class="list-card-kv__v">{{ item.remark }}</text>
</view>
</view>
<view class="ec-meta">
<text>{{ formatApplyDate(item) }}</text>
</view>
<view class="ec-bottom" @click.stop>
<view v-if="item.status === 'draft' || item.status === 'rejected'" class="op-actions">
<template v-if="item.status === 'draft'">
<button class="btn btn-sm btn-danger" @click="handleDelete(item)">删除</button>
<button class="btn btn-sm btn-primary" @click="goApply(item.id)">编辑</button>
<button class="btn btn-sm btn-primary" @click="handleSubmit(item)">提交</button>
</template>
<template v-else>
<button class="btn btn-sm btn-primary" @click="goDetail(item.id)">查看</button>
<button class="btn btn-sm btn-primary" @click="goApply(item.id)">编辑</button>
<button class="btn btn-sm btn-primary" @click="handleSubmit(item)">提交</button>
</template>
</view>
<view v-else class="op-actions">
<button class="btn btn-sm btn-primary" @click="goDetail(item.id)">查看</button>
<button
v-if="canWithdraw(item)"
class="btn btn-sm btn-ghost"
:disabled="withdrawingId === item.id"
@click="handleWithdraw(item)"
>
撤回
</button>
</view>
</view>
</view>
<view v-if="list.length" class="list-footer-tip">
{{ finished ? '没有更多了' : loading ? '加载中...' : '上拉加载更多' }}
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { PAGE_PATHS } from '@/config/api'
import { advanceApi } from '@/api/bx'
import {
statusLabel,
workflowListStatusTagClass,
formatAmount,
formatApplyDate
} from '@/utils/bxFormat'
import { getSafeAreaStyle } from '@/utils/safeArea'
import { useUserStore } from '@/stores/user'
const safeAreaStyle = getSafeAreaStyle()
const userStore = useUserStore()
const withdrawingId = ref('')
function currentUserId() {
const u = userStore.userInfo || {}
return String(u.id || u.userId || '')
}
function canWithdraw(item) {
if (!item) return false
return item.status === 'pending' && String(item.applicantId || '') === currentUserId()
}
const tabs = [
{ label: '全部', value: '' },
{ label: '待提交', value: 'draft' },
{ label: '审批中', value: 'pending' },
{ label: '已通过', value: 'approved' },
{ label: '已驳回', value: 'rejected' },
{ label: '已发放', value: 'issued' }
]
const keyword = ref('')
const activeTab = ref('')
const list = ref([])
const loading = ref(false)
const finished = ref(false)
const refreshing = ref(false)
const currentPage = ref(1)
const pageSize = 10
const listHeight = ref('60vh')
function computeListHeight() {
try {
const sys = uni.getSystemInfoSync()
const h = (sys.windowHeight || 600) - 210
listHeight.value = `${Math.max(h, 320)}px`
} catch (e) {
listHeight.value = '60vh'
}
}
function actualIssuedDisplay(item) {
const raw = item.actualAmount ?? item.actual_amount
if (raw == null || raw === '') return '—'
const n = typeof raw === 'string' ? parseFloat(raw) : Number(raw)
if (Number.isNaN(n)) return '—'
return formatAmount(n)
}
function offsetTotal(item) {
const raw = item.offsetAmount ?? item.offset_amount ?? 0
const n = typeof raw === 'string' ? parseFloat(raw) : Number(raw)
return Number.isNaN(n) ? 0 : n
}
async function fetchPage() {
if (loading.value && !refreshing.value) return
loading.value = true
try {
const params = {
page: currentPage.value,
pageSize
}
if (activeTab.value) params.status = activeTab.value
if (keyword.value.trim()) params.keyword = keyword.value.trim()
const res = await advanceApi.my(params)
const rows = res.data?.rows || []
const total = res.data?.total || 0
if (currentPage.value === 1) {
list.value = rows
} else {
list.value = list.value.concat(rows)
}
currentPage.value += 1
finished.value = rows.length < pageSize || list.value.length >= total
} catch (e) {
finished.value = true
} finally {
loading.value = false
refreshing.value = false
}
}
function onRefresh() {
currentPage.value = 1
finished.value = false
loading.value = false
list.value = []
fetchPage()
}
function onPullRefresh() {
refreshing.value = true
currentPage.value = 1
finished.value = false
fetchPage()
}
function onLoadMore() {
if (finished.value || loading.value) return
fetchPage()
}
function onTabChange(value) {
activeTab.value = value
onRefresh()
}
function clearKeyword() {
keyword.value = ''
onRefresh()
}
function goBack() {
uni.navigateBack({ fail: () => uni.reLaunch({ url: PAGE_PATHS.workbench }) })
}
function goApply(id) {
const q = id ? `?id=${id}` : ''
uni.navigateTo({ url: `${PAGE_PATHS.bxAdvanceApplyForm}${q}` })
}
function goDetail(id) {
uni.navigateTo({ url: `${PAGE_PATHS.bxAdvanceDetail}?id=${id}` })
}
async function handleSubmit(item) {
try {
await advanceApi.submit(item.id)
uni.showToast({ title: '提交成功', icon: 'success' })
onRefresh()
} catch (e) {
// bxRequest already toasted
}
}
function handleDelete(item) {
uni.showModal({
title: '删除确认',
content: '确定要删除该申请单吗?删除后不可恢复。',
confirmText: '确定删除',
confirmColor: '#ef4444',
success: async (res) => {
if (!res.confirm) return
try {
await advanceApi.delete(item.id)
uni.showToast({ title: '删除成功', icon: 'success' })
onRefresh()
} catch (e) {
// toasted
}
}
})
}
function handleWithdraw(item) {
if (!item?.id || !canWithdraw(item)) return
uni.showModal({
title: '确认撤回',
content: '确定撤回该备用金申请?撤回后可修改再提交。',
confirmText: '确定撤回',
success: async (res) => {
if (!res.confirm) return
withdrawingId.value = String(item.id)
try {
await advanceApi.withdraw(item.id)
uni.showToast({ title: '已撤回', icon: 'success' })
} catch (e) {
// toasted by bxRequest
} finally {
withdrawingId.value = ''
onRefresh()
}
}
})
}
onShow(() => {
computeListHeight()
onRefresh()
})
</script>
<style lang="scss">
@import '@/styles/bx-theme.scss';
.list-scroll {
box-sizing: border-box;
}
</style>