Commit 332ff3dd by liangjianmin

feat(approve): 添加我发起审批功能并优化界面样式

- 新增“我发起”标签页,支持查看我发起的审批
- 优化搜索框和标签样式,提升用户体验
- 重构数据获取和分页逻辑,支持多标签页数据管理
parent a224d3e1
.approve-page {
min-height: 100vh;
background: #f5f7fa;
padding-bottom: 158rpx;
height: 100vh;
background: #f3f6fb;
display: flex;
flex-direction: column;
overflow: hidden;
.search-bar {
padding: 20rpx 28rpx 0 28rpx;
.approve-fixed {
flex-shrink: 0;
background: #ffffff;
border-bottom: 1rpx solid #e9eef6;
position: relative;
z-index: 2;
}
.search-bar {
padding: 18rpx 28rpx 10rpx;
}
.tabs-wrap {
background: #ffffff;
margin-bottom: 20rpx;
position: relative;
padding: 0 28rpx;
.custom-tabs {
display: flex;
width: 100%;
background: #f5f7fa;
border-radius: 12rpx;
padding: 4rpx;
box-sizing: border-box;
}
.tab-item {
flex: 1;
padding: 24rpx 20rpx;
min-width: 0;
min-height: 56rpx;
padding: 0 10rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
cursor: pointer;
transition: all 0.25s ease;
min-height: 80rpx;
border: 1rpx solid transparent;
border-radius: 10rpx;
transition: background 0.2s ease, color 0.2s ease;
.tab-text {
font-size: 28rpx;
font-weight: 400;
color: #909399;
transition: all 0.25s ease;
font-size: 26rpx;
font-weight: 500;
color: #59697c;
line-height: 1.2;
transition: all 0.2s ease;
}
.count-badge {
position: relative;
margin-left: 10rpx;
min-width: 32rpx;
height: 32rpx;
padding: 0 8rpx;
margin-left: 8rpx;
min-width: 26rpx;
height: 26rpx;
padding: 0 7rpx;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%);
border-radius: 16rpx;
box-shadow: 0 2rpx 8rpx rgba(255, 107, 107, 0.25);
transition: all 0.3s ease;
background: #ff5470;
border-radius: 999rpx;
box-sizing: border-box;
.count-text {
font-size: 20rpx;
font-size: 19rpx;
font-weight: 600;
color: #ffffff;
line-height: 1;
transform: scale(0.95);
}
.badge-pulse {
display: none;
}
}
&.active {
background: #ffffff;
border-color: #e3e8ef;
.tab-text {
color: #3b82f6;
font-weight: 600;
font-size: 32rpx;
color: #1f2d3d;
font-weight: 700;
}
}
}
.tab-line {
position: absolute;
bottom: 0;
left: 25%;
width: 20%;
height: 4rpx;
background: #3b82f6;
border-radius: 2rpx;
transition: all 0.3s ease;
transform-origin: center;
}
}
.filter-bar {
padding: 20rpx 28rpx;
background: #ffffff;
margin-bottom: 20rpx;
.type-tabs-wrap {
display: flex;
gap: 12rpx;
padding: 16rpx 28rpx 18rpx;
overflow-x: auto;
white-space: nowrap;
.filter-btn {
padding: 12rpx 24rpx;
background: #f5f7fa;
border-radius: 32rpx;
.type-tab {
flex-shrink: 0;
min-height: 46rpx;
padding: 0 20rpx;
display: flex;
align-items: center;
justify-content: center;
border: 1rpx solid #d7e2ef;
border-radius: 999rpx;
background: #ffffff;
color: #59697c;
font-size: 24rpx;
font-weight: 500;
box-sizing: border-box;
transition: all 0.2s ease;
.filter-text {
font-size: 26rpx;
color: #606266;
margin-right: 8rpx;
&.active {
border-color: #2f73ff;
background: #2f73ff;
color: #ffffff;
font-weight: 600;
}
}
}
.list-scroll {
flex: 1;
height: 0;
}
.list-wrap {
padding: 0 28rpx;
padding: 22rpx 28rpx 158rpx;
box-sizing: border-box;
.skeleton-card {
background: #ffffff;
border-radius: 16rpx;
margin-bottom: 24rpx;
border-radius: 14rpx;
margin-bottom: 22rpx;
padding: 28rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05);
box-shadow: 0 8rpx 22rpx rgba(30, 41, 59, 0.06);
}
}
.approve-card {
background: #ffffff;
border-radius: 16rpx;
margin-bottom: 24rpx;
border-radius: 14rpx;
margin-bottom: 22rpx;
overflow: hidden;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05);
box-shadow: 0 8rpx 24rpx rgba(30, 41, 59, 0.07);
.card-header {
padding: 24rpx 28rpx 12rpx;
padding: 26rpx 28rpx 12rpx;
}
.card-title {
font-size: 30rpx;
font-weight: 600;
color: #1e293b;
font-weight: 700;
color: #172033;
}
.status-badge {
font-size: 22rpx;
font-weight: 500;
font-weight: 600;
padding: 8rpx 16rpx;
border-radius: 20rpx;
border-radius: 999rpx;
&.approved {
color: #10b981;
background: rgba(16, 185, 129, 0.1);
color: #0f9f6e;
background: rgba(15, 159, 110, 0.1);
}
&.rejected {
color: #f56c6c;
background: rgba(245, 108, 108, 0.1);
color: #e5484d;
background: rgba(229, 72, 77, 0.1);
}
}
.card-body {
padding: 0 28rpx 20rpx;
padding: 0 28rpx 22rpx;
.card-row {
margin-bottom: 10rpx;
.card-label {
font-size: 26rpx;
color: #909399;
color: #8a97a8;
width: 160rpx;
flex-shrink: 0;
}
.card-value {
font-size: 26rpx;
color: #303133;
color: #263447;
flex: 1;
word-break: break-all;
}
.card-time {
font-size: 24rpx;
color: #909399;
color: #98a4b3;
margin-left: 8rpx;
}
.card-tag {
font-size: 24rpx;
color: #e6a23c;
font-weight: 500;
color: #d8891d;
font-weight: 600;
}
}
}
.card-footer {
border-top: 1rpx solid #f0f1f5;
border-top: 1rpx solid #edf1f6;
padding: 20rpx 28rpx;
.action-btn {
flex: 1;
text-align: center;
font-size: 28rpx;
font-weight: 500;
font-weight: 600;
padding: 16rpx 0;
border-radius: 8rpx;
border-radius: 10rpx;
transition: all 0.2s ease;
&.approve {
color: #ffffff;
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
background: #2f73ff;
&:active {
opacity: 0.8;
opacity: 0.82;
}
}
&.detail {
color: #3b82f6;
background: rgba(59, 130, 246, 0.1);
border: 1rpx solid rgba(59, 130, 246, 0.2);
color: #2563eb;
background: #edf4ff;
border: 1rpx solid #cfe0ff;
&:active {
background: rgba(59, 130, 246, 0.15);
background: #e2edff;
}
}
}
......
<template>
<view class="approve-page">
<view class="approve-fixed">
<view class="search-bar">
<u-search v-model="order_sn" placeholder="订单号" shape="round" :showAction="false" bgColor="#f5f7fa" :inputStyle="{ fontSize: '24rpx' }" height="60" :searchIconSize="34" @change="onSearch"></u-search>
<u-search v-model="order_sn" placeholder="订单号" shape="round" :showAction="false" bgColor="#f4f7fb" :inputStyle="{ fontSize: '24rpx' }" height="64" :searchIconSize="34" @change="onSearch"></u-search>
</view>
<view class="tabs-wrap">
......@@ -13,9 +14,16 @@
</view>
</view>
</view>
<view class="tab-line" :style="{ left: currentTab === 0 ? '25%' : '75%', transform: 'translateX(-50%)' }"></view>
</view>
<view class="type-tabs-wrap">
<view v-for="(type, index) in typeList" :key="type.value" class="type-tab" :class="{ active: currentType === index }" @click="onTypeChange(index)">
<text>{{ type.name }}</text>
</view>
</view>
</view>
<scroll-view class="list-scroll" scroll-y @scrolltolower="loadMore">
<view class="list-wrap">
<!-- 骨架屏:首次加载时展示 -->
<view v-if="skeletonLoading">
......@@ -28,8 +36,8 @@
<template v-else>
<view class="approve-card" v-for="item in currentList" :key="item.approve_id">
<view class="card-header row bothSide verCenter">
<text class="card-title">费用减免申请</text>
<view v-if="currentTab === 1" :class="['status-badge', item.status === 1 ? 'approved' : 'rejected']">
<text class="card-title">{{ getApproveTitle(item) }}</text>
<view v-if="currentTab !== 0" :class="['status-badge', item.status === 1 ? 'approved' : 'rejected']">
{{ item.status_cn }}
</view>
</view>
......@@ -64,11 +72,12 @@
</view>
</view>
<u-empty v-if="!currentList.length" mode="data" :text="currentTab === 0 ? '暂无待处理审批' : '暂无已处理审批'" iconSize="140" textSize="24"></u-empty>
<u-empty v-if="!currentList.length" mode="data" :text="emptyText" iconSize="140" textSize="24"></u-empty>
<u-loadmore v-if="currentList.length >= 10" :status="loadStatus" @loadmore="loadMore" marginTop="10" marginBottom="30" fontSize="28" iconSize="25" :line="true" lineColor="#e4e7ed"></u-loadmore>
</template>
</view>
</scroll-view>
</view>
</template>
......@@ -82,16 +91,26 @@
currentTab: 0,
tabList: [
{ name: '待处理', baseName: '待处理', count: 0 },
{ name: '已处理', baseName: '已处理', count: 0 }
{ name: '已处理', baseName: '已处理', count: 0 },
{ name: '我发起', baseName: '我发起', count: 0 }
],
currentType: 0,
typeList: [
{ name: '全部', value: '' },
{ name: '业务费用减免', value: 12 },
{ name: '物流车辆申请', value: 13 }
],
pendingList: [],
doneList: [],
// 分页参数(两个 tab 各自独立)
mineList: [],
// 分页参数(三个 tab 各自独立)
pendingPage: 1,
donePage: 1,
minePage: 1,
limit: 10,
pendingTotal: 0,
doneTotal: 0,
mineTotal: 0,
// 骨架屏:首次进入 tab 且无缓存数据时显示
skeletonLoading: false,
// loadMore 状态
......@@ -99,14 +118,37 @@
};
},
computed: {
/**
* 当前 tab 展示列表
* @return {Array} 列表数据
*/
currentList() {
return this.currentTab === 0 ? this.pendingList : this.doneList;
var listMap = [this.pendingList, this.doneList, this.mineList];
return listMap[this.currentTab] || [];
},
/**
* 当前 tab 页码
* @return {number} 页码
*/
currentPage() {
return this.currentTab === 0 ? this.pendingPage : this.donePage;
var pageMap = [this.pendingPage, this.donePage, this.minePage];
return pageMap[this.currentTab] || 1;
},
/**
* 当前 tab 总数
* @return {number} 总数
*/
currentTotal() {
return this.currentTab === 0 ? this.pendingTotal : this.doneTotal;
var totalMap = [this.pendingTotal, this.doneTotal, this.mineTotal];
return totalMap[this.currentTab] || 0;
},
/**
* 空状态文案
* @return {string} 文案
*/
emptyText() {
var textMap = ['暂无待处理审批', '暂无已处理审批', '暂无我发起的审批'];
return textMap[this.currentTab] || '暂无审批数据';
}
},
onShow() {
......@@ -121,49 +163,55 @@
* @return {void}
*/
resetAndLoad() {
if (this.currentTab === 0) {
this.resetCurrentList();
this.skeletonLoading = true;
this.loadStatus = 'loadmore';
this.getData();
},
/**
* 重置当前 tab 的列表与分页
* @return {void}
*/
resetCurrentList() {
var resetMap = [this.resetPendingList, this.resetDoneList, this.resetMineList];
resetMap[this.currentTab]();
},
/**
* 重置待处理列表
* @return {void}
*/
resetPendingList() {
this.pendingPage = 1;
this.pendingList = [];
} else {
},
/**
* 重置已处理列表
* @return {void}
*/
resetDoneList() {
this.donePage = 1;
this.doneList = [];
}
this.skeletonLoading = true;
this.loadStatus = 'loadmore';
this.getData();
},
/**
* 重置我发起列表
* @return {void}
*/
resetMineList() {
this.minePage = 1;
this.mineList = [];
},
/**
* 获取审批列表,支持分页追加
* @return {void}
*/
getData() {
var page = this.currentTab === 0 ? this.pendingPage : this.donePage;
var statusVal = this.currentTab === 0 ? '0,1' : '2,-1';
var params = {
approval_status: statusVal,
page,
limit: this.limit
};
if (this.order_sn) {
params.order_sn = this.order_sn;
}
var page = this.currentPage;
var params = this.getRequestParams(page);
this.request(API.feeApproveList, 'GET', params, !this.skeletonLoading).then(res => {
this.skeletonLoading = false;
if (res.code === 0) {
var { list = [], total = 0 } = res.data || {};
if (this.currentTab === 0) {
this.pendingList = page === 1 ? list : [...this.pendingList, ...list];
this.pendingTotal = total;
// 更新tab中的数字badge
this.$set(this.tabList, 0, {
name: '待处理',
baseName: '待处理',
count: total
});
} else {
this.doneList = page === 1 ? list : [...this.doneList, ...list];
this.doneTotal = total;
}
this.setCurrentList(list, total, page);
this.updateLoadStatus();
}
}).catch(() => {
......@@ -172,12 +220,88 @@
});
},
/**
* 组装列表请求参数
* @param {number} page - 当前页码
* @return {Object} 请求参数
*/
getRequestParams(page) {
var statusMap = ['0,1', '2,-1', ''];
var params = {
page,
limit: this.limit
};
if (statusMap[this.currentTab]) params.approval_status = statusMap[this.currentTab];
if (this.currentTab === 2) params.is_apply = 1;
if (this.order_sn) params.order_sn = this.order_sn;
if (this.typeList[this.currentType].value) params.apply_type = this.typeList[this.currentType].value;
return params;
},
/**
* 写入当前 tab 的列表数据
* @param {Array} list - 接口返回列表
* @param {number} total - 接口返回总数
* @param {number} page - 当前页码
* @return {void}
*/
setCurrentList(list, total, page) {
var setterMap = [this.setPendingList, this.setDoneList, this.setMineList];
setterMap[this.currentTab](list, total, page);
},
/**
* 写入待处理列表
* @param {Array} list - 接口返回列表
* @param {number} total - 接口返回总数
* @param {number} page - 当前页码
* @return {void}
*/
setPendingList(list, total, page) {
this.pendingList = page === 1 ? list : [...this.pendingList, ...list];
this.pendingTotal = total;
this.updateTabCount(0, total);
},
/**
* 写入已处理列表
* @param {Array} list - 接口返回列表
* @param {number} total - 接口返回总数
* @param {number} page - 当前页码
* @return {void}
*/
setDoneList(list, total, page) {
this.doneList = page === 1 ? list : [...this.doneList, ...list];
this.doneTotal = total;
},
/**
* 写入我发起列表
* @param {Array} list - 接口返回列表
* @param {number} total - 接口返回总数
* @param {number} page - 当前页码
* @return {void}
*/
setMineList(list, total, page) {
this.mineList = page === 1 ? list : [...this.mineList, ...list];
this.mineTotal = total;
},
/**
* 更新主 tab 数量
* @param {number} index - tab 索引
* @param {number} total - 总数
* @return {void}
*/
updateTabCount(index, total) {
var tab = this.tabList[index];
this.$set(this.tabList, index, {
name: tab.name,
baseName: tab.baseName,
count: total
});
},
/**
* 根据当前数据量与总数更新底部加载状态
* @return {void}
*/
updateLoadStatus() {
var listLen = this.currentTab === 0 ? this.pendingList.length : this.doneList.length;
var total = this.currentTab === 0 ? this.pendingTotal : this.doneTotal;
var listLen = this.currentList.length;
var total = this.currentTotal;
this.loadStatus = listLen >= total ? 'nomore' : 'loadmore';
},
/**
......@@ -187,12 +311,37 @@
loadMore() {
if (this.loadStatus !== 'loadmore') return;
this.loadStatus = 'loading';
if (this.currentTab === 0) {
this.addCurrentPage();
this.getData();
},
/**
* 当前 tab 页码加一
* @return {void}
*/
addCurrentPage() {
var pageMap = [this.addPendingPage, this.addDonePage, this.addMinePage];
pageMap[this.currentTab]();
},
/**
* 待处理页码加一
* @return {void}
*/
addPendingPage() {
this.pendingPage++;
} else {
},
/**
* 已处理页码加一
* @return {void}
*/
addDonePage() {
this.donePage++;
}
this.getData();
},
/**
* 我发起页码加一
* @return {void}
*/
addMinePage() {
this.minePage++;
},
/**
* 搜索时重置分页
......@@ -208,14 +357,35 @@
*/
onTabChange(item) {
this.currentTab = item.index;
var targetList = this.currentTab === 0 ? this.pendingList : this.doneList;
if (!targetList.length) {
if (!this.currentList.length) {
this.resetAndLoad();
} else {
this.updateLoadStatus();
}
},
/**
* 类型 tab 切换
* @param {number} index - 类型索引
* @return {void}
*/
onTypeChange(index) {
if (this.currentType === index) return;
this.currentType = index;
this.resetAndLoad();
},
/**
* 获取审批标题
* @param {Object} item - 审批项
* @return {string} 审批标题
*/
getApproveTitle(item) {
var titleMap = {
12: '业务费用减免',
13: '物流车辆申请'
};
return item.apply_type_name || titleMap[item.apply_type] || titleMap[this.typeList[this.currentType].value] || '审批申请';
},
/**
* 跳转审批详情
* @param {Object} item - 审批项
* @return {void}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment