Commit 332ff3dd by liangjianmin

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

- 新增“我发起”标签页,支持查看我发起的审批
- 优化搜索框和标签样式,提升用户体验
- 重构数据获取和分页逻辑,支持多标签页数据管理
parent a224d3e1
.approve-page { .approve-page {
min-height: 100vh; height: 100vh;
background: #f5f7fa; background: #f3f6fb;
padding-bottom: 158rpx; display: flex;
flex-direction: column;
overflow: hidden;
.approve-fixed {
flex-shrink: 0;
background: #ffffff;
border-bottom: 1rpx solid #e9eef6;
position: relative;
z-index: 2;
}
.search-bar { .search-bar {
padding: 20rpx 28rpx 0 28rpx; padding: 18rpx 28rpx 10rpx;
background: #ffffff;
} }
.tabs-wrap { .tabs-wrap {
background: #ffffff; padding: 0 28rpx;
margin-bottom: 20rpx;
position: relative;
.custom-tabs { .custom-tabs {
display: flex; display: flex;
width: 100%; width: 100%;
background: #f5f7fa;
border-radius: 12rpx;
padding: 4rpx;
box-sizing: border-box;
} }
.tab-item { .tab-item {
flex: 1; flex: 1;
padding: 24rpx 20rpx; min-width: 0;
min-height: 56rpx;
padding: 0 10rpx;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
position: relative; position: relative;
cursor: pointer; border: 1rpx solid transparent;
transition: all 0.25s ease; border-radius: 10rpx;
min-height: 80rpx; transition: background 0.2s ease, color 0.2s ease;
.tab-text { .tab-text {
font-size: 28rpx; font-size: 26rpx;
font-weight: 400; font-weight: 500;
color: #909399; color: #59697c;
transition: all 0.25s ease;
line-height: 1.2; line-height: 1.2;
transition: all 0.2s ease;
} }
.count-badge { .count-badge {
position: relative; margin-left: 8rpx;
margin-left: 10rpx; min-width: 26rpx;
min-width: 32rpx; height: 26rpx;
height: 32rpx; padding: 0 7rpx;
padding: 0 8rpx;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%); background: #ff5470;
border-radius: 16rpx; border-radius: 999rpx;
box-shadow: 0 2rpx 8rpx rgba(255, 107, 107, 0.25); box-sizing: border-box;
transition: all 0.3s ease;
.count-text { .count-text {
font-size: 20rpx; font-size: 19rpx;
font-weight: 600; font-weight: 600;
color: #ffffff; color: #ffffff;
line-height: 1; line-height: 1;
transform: scale(0.95);
}
.badge-pulse {
display: none;
} }
} }
&.active { &.active {
background: #ffffff;
border-color: #e3e8ef;
.tab-text { .tab-text {
color: #3b82f6; color: #1f2d3d;
font-weight: 600; font-weight: 700;
font-size: 32rpx;
} }
} }
} }
.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 { .type-tabs-wrap {
padding: 20rpx 28rpx; display: flex;
background: #ffffff; gap: 12rpx;
margin-bottom: 20rpx; padding: 16rpx 28rpx 18rpx;
overflow-x: auto;
.filter-btn { white-space: nowrap;
padding: 12rpx 24rpx;
background: #f5f7fa; .type-tab {
border-radius: 32rpx; 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 { &.active {
font-size: 26rpx; border-color: #2f73ff;
color: #606266; background: #2f73ff;
margin-right: 8rpx; color: #ffffff;
font-weight: 600;
} }
} }
} }
.list-scroll {
flex: 1;
height: 0;
}
.list-wrap { .list-wrap {
padding: 0 28rpx; padding: 22rpx 28rpx 158rpx;
box-sizing: border-box;
.skeleton-card { .skeleton-card {
background: #ffffff; background: #ffffff;
border-radius: 16rpx; border-radius: 14rpx;
margin-bottom: 24rpx; margin-bottom: 22rpx;
padding: 28rpx; 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 { .approve-card {
background: #ffffff; background: #ffffff;
border-radius: 16rpx; border-radius: 14rpx;
margin-bottom: 24rpx; margin-bottom: 22rpx;
overflow: hidden; 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 { .card-header {
padding: 24rpx 28rpx 12rpx; padding: 26rpx 28rpx 12rpx;
} }
.card-title { .card-title {
font-size: 30rpx; font-size: 30rpx;
font-weight: 600; font-weight: 700;
color: #1e293b; color: #172033;
} }
.status-badge { .status-badge {
font-size: 22rpx; font-size: 22rpx;
font-weight: 500; font-weight: 600;
padding: 8rpx 16rpx; padding: 8rpx 16rpx;
border-radius: 20rpx; border-radius: 999rpx;
&.approved { &.approved {
color: #10b981; color: #0f9f6e;
background: rgba(16, 185, 129, 0.1); background: rgba(15, 159, 110, 0.1);
} }
&.rejected { &.rejected {
color: #f56c6c; color: #e5484d;
background: rgba(245, 108, 108, 0.1); background: rgba(229, 72, 77, 0.1);
} }
} }
.card-body { .card-body {
padding: 0 28rpx 20rpx; padding: 0 28rpx 22rpx;
.card-row { .card-row {
margin-bottom: 10rpx; margin-bottom: 10rpx;
.card-label { .card-label {
font-size: 26rpx; font-size: 26rpx;
color: #909399; color: #8a97a8;
width: 160rpx; width: 160rpx;
flex-shrink: 0; flex-shrink: 0;
} }
.card-value { .card-value {
font-size: 26rpx; font-size: 26rpx;
color: #303133; color: #263447;
flex: 1; flex: 1;
word-break: break-all; word-break: break-all;
} }
.card-time { .card-time {
font-size: 24rpx; font-size: 24rpx;
color: #909399; color: #98a4b3;
margin-left: 8rpx; margin-left: 8rpx;
} }
.card-tag { .card-tag {
font-size: 24rpx; font-size: 24rpx;
color: #e6a23c; color: #d8891d;
font-weight: 500; font-weight: 600;
} }
} }
} }
.card-footer { .card-footer {
border-top: 1rpx solid #f0f1f5; border-top: 1rpx solid #edf1f6;
padding: 20rpx 28rpx; padding: 20rpx 28rpx;
.action-btn { .action-btn {
flex: 1; flex: 1;
text-align: center; text-align: center;
font-size: 28rpx; font-size: 28rpx;
font-weight: 500; font-weight: 600;
padding: 16rpx 0; padding: 16rpx 0;
border-radius: 8rpx; border-radius: 10rpx;
transition: all 0.2s ease; transition: all 0.2s ease;
&.approve { &.approve {
color: #ffffff; color: #ffffff;
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); background: #2f73ff;
&:active { &:active {
opacity: 0.8; opacity: 0.82;
} }
} }
&.detail { &.detail {
color: #3b82f6; color: #2563eb;
background: rgba(59, 130, 246, 0.1); background: #edf4ff;
border: 1rpx solid rgba(59, 130, 246, 0.2); border: 1rpx solid #cfe0ff;
&:active { &:active {
background: rgba(59, 130, 246, 0.15); background: #e2edff;
} }
} }
} }
......
<template> <template>
<view class="approve-page"> <view class="approve-page">
<view class="search-bar"> <view class="approve-fixed">
<u-search v-model="order_sn" placeholder="订单号" shape="round" :showAction="false" bgColor="#f5f7fa" :inputStyle="{ fontSize: '24rpx' }" height="60" :searchIconSize="34" @change="onSearch"></u-search> <view class="search-bar">
</view> <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"> <view class="tabs-wrap">
<view class="custom-tabs row"> <view class="custom-tabs row">
<view v-for="(tab, index) in tabList" :key="index" class="tab-item row verCenter rowCenter" :class="{ active: currentTab === index }" @click="onTabChange({ index })"> <view v-for="(tab, index) in tabList" :key="index" class="tab-item row verCenter rowCenter" :class="{ active: currentTab === index }" @click="onTabChange({ index })">
<text class="tab-text">{{ tab.baseName || tab.name }}</text> <text class="tab-text">{{ tab.baseName || tab.name }}</text>
<view v-if="tab.count > 0" class="count-badge"> <view v-if="tab.count > 0" class="count-badge">
<text class="count-text">{{ tab.count > 99 ? '99+' : tab.count }}</text> <text class="count-text">{{ tab.count > 99 ? '99+' : tab.count }}</text>
</view>
</view> </view>
</view> </view>
</view> </view>
<view class="tab-line" :style="{ left: currentTab === 0 ? '25%' : '75%', transform: 'translateX(-50%)' }"></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> </view>
<view class="list-wrap"> <scroll-view class="list-scroll" scroll-y @scrolltolower="loadMore">
<view class="list-wrap">
<!-- 骨架屏:首次加载时展示 --> <!-- 骨架屏:首次加载时展示 -->
<view v-if="skeletonLoading"> <view v-if="skeletonLoading">
<view class="skeleton-card" v-for="i in 3" :key="i"> <view class="skeleton-card" v-for="i in 3" :key="i">
...@@ -28,8 +36,8 @@ ...@@ -28,8 +36,8 @@
<template v-else> <template v-else>
<view class="approve-card" v-for="item in currentList" :key="item.approve_id"> <view class="approve-card" v-for="item in currentList" :key="item.approve_id">
<view class="card-header row bothSide verCenter"> <view class="card-header row bothSide verCenter">
<text class="card-title">费用减免申请</text> <text class="card-title">{{ getApproveTitle(item) }}</text>
<view v-if="currentTab === 1" :class="['status-badge', item.status === 1 ? 'approved' : 'rejected']"> <view v-if="currentTab !== 0" :class="['status-badge', item.status === 1 ? 'approved' : 'rejected']">
{{ item.status_cn }} {{ item.status_cn }}
</view> </view>
</view> </view>
...@@ -64,11 +72,12 @@ ...@@ -64,11 +72,12 @@
</view> </view>
</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> <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> </template>
</view> </view>
</scroll-view>
</view> </view>
</template> </template>
...@@ -82,16 +91,26 @@ ...@@ -82,16 +91,26 @@
currentTab: 0, currentTab: 0,
tabList: [ tabList: [
{ name: '待处理', baseName: '待处理', count: 0 }, { 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: [], pendingList: [],
doneList: [], doneList: [],
// 分页参数(两个 tab 各自独立) mineList: [],
// 分页参数(三个 tab 各自独立)
pendingPage: 1, pendingPage: 1,
donePage: 1, donePage: 1,
minePage: 1,
limit: 10, limit: 10,
pendingTotal: 0, pendingTotal: 0,
doneTotal: 0, doneTotal: 0,
mineTotal: 0,
// 骨架屏:首次进入 tab 且无缓存数据时显示 // 骨架屏:首次进入 tab 且无缓存数据时显示
skeletonLoading: false, skeletonLoading: false,
// loadMore 状态 // loadMore 状态
...@@ -99,14 +118,37 @@ ...@@ -99,14 +118,37 @@
}; };
}, },
computed: { computed: {
/**
* 当前 tab 展示列表
* @return {Array} 列表数据
*/
currentList() { 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() { 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() { 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() { onShow() {
...@@ -121,49 +163,55 @@ ...@@ -121,49 +163,55 @@
* @return {void} * @return {void}
*/ */
resetAndLoad() { resetAndLoad() {
if (this.currentTab === 0) { this.resetCurrentList();
this.pendingPage = 1;
this.pendingList = [];
} else {
this.donePage = 1;
this.doneList = [];
}
this.skeletonLoading = true; this.skeletonLoading = true;
this.loadStatus = 'loadmore'; this.loadStatus = 'loadmore';
this.getData(); 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 = [];
},
/**
* 重置已处理列表
* @return {void}
*/
resetDoneList() {
this.donePage = 1;
this.doneList = [];
},
/**
* 重置我发起列表
* @return {void}
*/
resetMineList() {
this.minePage = 1;
this.mineList = [];
},
/**
* 获取审批列表,支持分页追加 * 获取审批列表,支持分页追加
* @return {void} * @return {void}
*/ */
getData() { getData() {
var page = this.currentTab === 0 ? this.pendingPage : this.donePage; var page = this.currentPage;
var statusVal = this.currentTab === 0 ? '0,1' : '2,-1'; var params = this.getRequestParams(page);
var params = {
approval_status: statusVal,
page,
limit: this.limit
};
if (this.order_sn) {
params.order_sn = this.order_sn;
}
this.request(API.feeApproveList, 'GET', params, !this.skeletonLoading).then(res => { this.request(API.feeApproveList, 'GET', params, !this.skeletonLoading).then(res => {
this.skeletonLoading = false; this.skeletonLoading = false;
if (res.code === 0) { if (res.code === 0) {
var { list = [], total = 0 } = res.data || {}; var { list = [], total = 0 } = res.data || {};
if (this.currentTab === 0) { this.setCurrentList(list, total, page);
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.updateLoadStatus(); this.updateLoadStatus();
} }
}).catch(() => { }).catch(() => {
...@@ -172,12 +220,88 @@ ...@@ -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} * @return {void}
*/ */
updateLoadStatus() { updateLoadStatus() {
var listLen = this.currentTab === 0 ? this.pendingList.length : this.doneList.length; var listLen = this.currentList.length;
var total = this.currentTab === 0 ? this.pendingTotal : this.doneTotal; var total = this.currentTotal;
this.loadStatus = listLen >= total ? 'nomore' : 'loadmore'; this.loadStatus = listLen >= total ? 'nomore' : 'loadmore';
}, },
/** /**
...@@ -187,14 +311,39 @@ ...@@ -187,14 +311,39 @@
loadMore() { loadMore() {
if (this.loadStatus !== 'loadmore') return; if (this.loadStatus !== 'loadmore') return;
this.loadStatus = 'loading'; this.loadStatus = 'loading';
if (this.currentTab === 0) { this.addCurrentPage();
this.pendingPage++;
} else {
this.donePage++;
}
this.getData(); this.getData();
}, },
/** /**
* 当前 tab 页码加一
* @return {void}
*/
addCurrentPage() {
var pageMap = [this.addPendingPage, this.addDonePage, this.addMinePage];
pageMap[this.currentTab]();
},
/**
* 待处理页码加一
* @return {void}
*/
addPendingPage() {
this.pendingPage++;
},
/**
* 已处理页码加一
* @return {void}
*/
addDonePage() {
this.donePage++;
},
/**
* 我发起页码加一
* @return {void}
*/
addMinePage() {
this.minePage++;
},
/**
* 搜索时重置分页 * 搜索时重置分页
* @return {void} * @return {void}
*/ */
...@@ -208,14 +357,35 @@ ...@@ -208,14 +357,35 @@
*/ */
onTabChange(item) { onTabChange(item) {
this.currentTab = item.index; this.currentTab = item.index;
var targetList = this.currentTab === 0 ? this.pendingList : this.doneList; if (!this.currentList.length) {
if (!targetList.length) {
this.resetAndLoad(); this.resetAndLoad();
} else { } else {
this.updateLoadStatus(); 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 - 审批项 * @param {Object} item - 审批项
* @return {void} * @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