first commit

This commit is contained in:
2026-02-06 20:21:10 +08:00
commit f9d7f980bb
3994 changed files with 1146030 additions and 0 deletions
+460
View File
@@ -0,0 +1,460 @@
<template>
<view class="main-data-page">
<!-- 使用子头部组件 -->
<sub-header
title="业务审批"
@goBack="goBack"
@showMoreOptions="showMoreOptions"
/>
<view class="main-container">
<!-- 审批状态tabs -->
<view class="approval-tabs">
<view
v-for="tab in approvalTabs"
:key="tab.key"
class="tab-item"
:class="{ active: activeTab === tab.key }"
@click="switchTab(tab.key)"
>
{{ tab.label }}
<view v-if="(tab.key === 'pending' || tab.key === 'all') && tab.count > 0" class="tab-badge">{{ tab.count }}</view>
</view>
</view>
<!-- 审批列表 -->
<view class="approval-list">
<view
v-for="item in filteredApprovalList"
:key="item.id"
class="approval-item"
@click="viewDetail(item)"
>
<view class="item-header">
<view class="item-title">{{ item.title }}</view>
<view class="item-status" :class="item.status">
{{ getStatusText(item.status) }}
</view>
</view>
<view class="item-content">
<view class="item-info">
<text class="info-label">申请人</text>
<text class="info-value">{{ item.applicant }}</text>
</view>
<view class="item-info">
<text class="info-label">申请时间</text>
<text class="info-value">{{ item.applyTime }}</text>
</view>
<view class="item-info">
<text class="info-label">业务类型</text>
<text class="info-value">{{ item.businessType }}</text>
</view>
</view>
<view class="item-footer">
<view class="priority" :class="item.priority">
{{ getPriorityText(item.priority) }}
</view>
<view class="item-amount" v-if="item.amount">
金额{{ item.amount }}
</view>
</view>
</view>
<!-- 空状态 -->
<view v-if="filteredApprovalList.length === 0" class="empty-state">
<view class="empty-icon">📋</view>
<view class="empty-text">暂无{{ getCurrentTabLabel() }}审批</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from "vue";
import SubHeader from "../components/subHeader.vue";
// 响应式数据
const activeTab = ref('all'); // 当前激活的tab
// 审批状态tabs配置
const approvalTabs = ref([
{ key: 'all', label: '全部', count: 0 },
{ key: 'pending', label: '待审批', count: 0 },
{ key: 'approved', label: '已通过', count: 0 },
{ key: 'rejected', label: '已拒绝', count: 0 }
]);
// 审批列表数据
const approvalList = ref([
{
id: 1,
title: '港口作业申请',
status: 'pending',
applicant: '张三',
applyTime: '2024-01-15 09:30',
businessType: '港口作业',
priority: 'high',
amount: '¥50,000'
},
{
id: 2,
title: '设备维修申请',
status: 'approved',
applicant: '李四',
applyTime: '2024-01-14 14:20',
businessType: '设备维护',
priority: 'medium',
amount: '¥15,000'
},
{
id: 3,
title: '人员调动申请',
status: 'rejected',
applicant: '王五',
applyTime: '2024-01-13 16:45',
businessType: '人事管理',
priority: 'low',
amount: null
},
{
id: 4,
title: '采购申请',
status: 'pending',
applicant: '赵六',
applyTime: '2024-01-12 11:15',
businessType: '采购管理',
priority: 'high',
amount: '¥80,000'
},
{
id: 5,
title: '安全培训申请',
status: 'approved',
applicant: '钱七',
applyTime: '2024-01-11 08:30',
businessType: '培训管理',
priority: 'medium',
amount: '¥5,000'
}
]);
// 计算属性:根据当前tab过滤列表
const filteredApprovalList = computed(() => {
let filteredList = [];
if (activeTab.value === 'all') {
filteredList = approvalList.value;
} else {
filteredList = approvalList.value.filter(item => item.status === activeTab.value);
}
// 按照状态排序:待审批、已拒绝、已通过
const statusOrder = ['pending', 'rejected', 'approved'];
return filteredList.sort((a, b) => {
const aIndex = statusOrder.indexOf(a.status);
const bIndex = statusOrder.indexOf(b.status);
return aIndex - bIndex;
});
});
// 更新tabs计数
const updateTabCounts = () => {
approvalTabs.value.forEach(tab => {
if (tab.key === 'all') {
tab.count = approvalList.value.length;
} else {
tab.count = approvalList.value.filter(item => item.status === tab.key).length;
}
});
};
// 切换tab
const switchTab = (tabKey) => {
activeTab.value = tabKey;
};
// 获取状态文本
const getStatusText = (status) => {
const statusMap = {
pending: '待审批',
approved: '已通过',
rejected: '已拒绝'
};
return statusMap[status] || status;
};
// 获取优先级文本
const getPriorityText = (priority) => {
const priorityMap = {
high: '高',
medium: '中',
low: '低'
};
return priorityMap[priority] || priority;
};
// 获取当前tab标签
const getCurrentTabLabel = () => {
const currentTab = approvalTabs.value.find(tab => tab.key === activeTab.value);
return currentTab ? currentTab.label : '';
};
// 查看详情
const viewDetail = (item) => {
uni.navigateTo({
url: `/pages/tasks/approvalDetail?id=${item.id}&title=${encodeURIComponent(item.title)}&status=${item.status}&applicant=${encodeURIComponent(item.applicant)}&applyTime=${encodeURIComponent(item.applyTime)}&businessType=${encodeURIComponent(item.businessType)}&priority=${item.priority}&amount=${item.amount ? encodeURIComponent(item.amount) : ''}`
});
};
// 头部相关方法
const goBack = () => {
uni.navigateBack();
};
const showMoreOptions = () => {
uni.showActionSheet({
itemList: ["刷新", "返回首页"],
success: (res) => {
switch (res.tapIndex) {
case 0:
// 刷新数据
updateTabCounts();
uni.showToast({ title: "数据已刷新", icon: "success" });
break;
case 1:
uni.reLaunch({
url: "/pages/index/index",
});
uni.showToast({ title: "已返回工作台", icon: "success" });
break;
}
},
});
};
// 页面加载时处理URL参数
onMounted(() => {
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const options = currentPage.options;
// 如果URL中有activeTab参数,则设置为对应的tab
if (options.activeTab) {
activeTab.value = options.activeTab;
}
// 初始化计数
updateTabCounts();
});
</script>
<style lang="scss" scoped>
// 变量定义
$border-radius: 16rpx;
$border-radius-small: 12rpx;
$border-radius-badge: 20rpx;
$spacing-small: 12rpx;
$spacing-medium: 20rpx;
$spacing-large: 30rpx;
$transition: all 0.3s ease;
.main-data-page {
background: var(--background);
min-height: 100vh;
padding-top: calc(var(--status-bar-height) + 88rpx);
padding-bottom: 40rpx;
.main-container {
padding: 24rpx;
}
}
// 审批状态tabs
.approval-tabs {
display: flex;
background: var(--surface);
border-radius: $border-radius;
margin-bottom: $spacing-large;
box-shadow: var(--shadow-md);
border: 1rpx solid var(--border-light);
overflow: hidden;
.tab-item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 24rpx 16rpx;
font-size: 28rpx;
color: var(--text-secondary);
background: var(--surface);
transition: $transition;
position: relative;
&.active {
background: var(--primary-color);
color: var(--white);
font-weight: 600;
.tab-badge {
background: rgba(255, 255, 255, 0.3);
}
}
.tab-badge {
background: var(--error);
color: var(--white);
font-size: 20rpx;
padding: 4rpx 8rpx;
border-radius: $border-radius-badge;
margin-left: 8rpx;
min-width: 32rpx;
text-align: center;
line-height: 1;
}
}
}
// 审批列表
.approval-list {
.approval-item {
background: var(--surface);
border-radius: $border-radius;
margin-bottom: $spacing-medium;
padding: $spacing-large;
box-shadow: var(--shadow);
border: 1rpx solid var(--border-light);
transition: $transition;
&:active {
transform: scale(0.98);
box-shadow: var(--shadow-md);
background: var(--surface-hover);
}
.item-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: $spacing-medium;
.item-title {
font-size: 32rpx;
font-weight: 600;
color: var(--title-color);
flex: 1;
margin-right: $spacing-medium;
}
.item-status {
padding: 8rpx 16rpx;
border-radius: $border-radius-badge;
font-size: 24rpx;
font-weight: 500;
white-space: nowrap;
&.pending {
background: var(--warning-light);
color: var(--warning);
}
&.approved {
background: var(--success-light);
color: var(--success);
}
&.rejected {
background: var(--error-light);
color: var(--error);
}
}
}
.item-content {
margin-bottom: $spacing-medium;
.item-info {
display: flex;
margin-bottom: $spacing-small;
&:last-child {
margin-bottom: 0;
}
.info-label {
font-size: 26rpx;
color: var(--text-muted);
width: 140rpx;
flex-shrink: 0;
}
.info-value {
font-size: 26rpx;
color: var(--text-color);
flex: 1;
}
}
}
.item-footer {
display: flex;
justify-content: space-between;
align-items: center;
.priority {
padding: 6rpx 12rpx;
border-radius: $border-radius-small;
font-size: 22rpx;
font-weight: 500;
&.high {
background: var(--error-light);
color: var(--error);
}
&.medium {
background: var(--warning-light);
color: var(--warning);
}
&.low {
background: var(--success-light);
color: var(--success);
}
}
.item-amount {
font-size: 26rpx;
color: var(--orange);
font-weight: 600;
}
}
}
}
// 空状态
.empty-state {
text-align: center;
padding: 120rpx 60rpx;
.empty-icon {
font-size: 120rpx;
margin-bottom: $spacing-large;
}
.empty-text {
font-size: 28rpx;
color: var(--text-muted);
}
}
// 支持安全区域的设备
@supports (padding: max(0px)) {
.main-data-page {
padding-top: calc(
var(--status-bar-height) + 88rpx + env(safe-area-inset-top)
);
}
}
</style>
+484
View File
@@ -0,0 +1,484 @@
<template>
<view class="main-data-page">
<!-- 使用子头部组件 -->
<sub-header
title="审批详情"
@goBack="goBack"
@showMoreOptions="showMoreOptions"
/>
<view class="main-container">
<!-- 审批详情卡片 -->
<view class="detail-card">
<!-- 标题和状态 -->
<view class="detail-header">
<view class="detail-title">{{ approvalDetail.title }}</view>
<view class="detail-status" :class="approvalDetail.status">
{{ getStatusText(approvalDetail.status) }}
</view>
</view>
<!-- 基本信息 -->
<view class="detail-section">
<view class="section-title">基本信息</view>
<view class="info-list">
<view class="info-item">
<text class="info-label">申请人</text>
<text class="info-value">{{ approvalDetail.applicant }}</text>
</view>
<view class="info-item">
<text class="info-label">申请时间</text>
<text class="info-value">{{ approvalDetail.applyTime }}</text>
</view>
<view class="info-item">
<text class="info-label">业务类型</text>
<text class="info-value">{{ approvalDetail.businessType }}</text>
</view>
<view class="info-item">
<text class="info-label">优先级</text>
<view class="priority-tag" :class="approvalDetail.priority">
{{ getPriorityText(approvalDetail.priority) }}
</view>
</view>
<view class="info-item" v-if="approvalDetail.amount">
<text class="info-label">申请金额</text>
<text class="info-value amount">{{ approvalDetail.amount }}</text>
</view>
</view>
</view>
<!-- 审批信息 -->
<view class="detail-section">
<view class="section-title">审批信息</view>
<view class="info-list">
<view class="info-item">
<text class="info-label">审批人</text>
<text class="info-value">{{ approvalDetail.approver || '待分配' }}</text>
</view>
<view class="info-item">
<text class="info-label">审批时间</text>
<text class="info-value">{{ approvalDetail.approveTime || '待审批' }}</text>
</view>
<view class="info-item" v-if="approvalDetail.remark">
<text class="info-label">审批意见</text>
<text class="info-value">{{ approvalDetail.remark }}</text>
</view>
</view>
</view>
<!-- 操作按钮 -->
<view class="action-buttons" v-if="approvalDetail.status === 'pending'">
<button class="action-btn reject-btn" @click="handleApproval('rejected')">
拒绝
</button>
<button class="action-btn approve-btn" @click="handleApproval('approved')">
通过
</button>
</view>
</view>
<!-- 理由输入弹窗 -->
<view v-if="showReasonDialog" class="reason-mask" @click.self="closeReasonDialog">
<view class="reason-modal">
<view class="reason-title">{{ actionType === 'approved' ? '通过理由' : '拒绝理由' }}</view>
<textarea class="reason-textarea" v-model="reasonText" placeholder="请输入审批理由(必填)" auto-height :maxlength="200" />
<view class="reason-tip">最多200字需填写后才能提交</view>
<view class="reason-actions">
<button class="reason-btn cancel" @click="closeReasonDialog">取消</button>
<button class="reason-btn confirm" @click="submitApproval">提交</button>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from "vue";
import SubHeader from "../components/subHeader.vue";
// 审批详情数据
const approvalDetail = ref({
id: '',
title: '',
status: '',
applicant: '',
applyTime: '',
businessType: '',
priority: '',
amount: '',
approver: '',
approveTime: '',
remark: ''
});
// 获取状态文本
const getStatusText = (status) => {
const statusMap = {
pending: '待审批',
approved: '已通过',
rejected: '已拒绝'
};
return statusMap[status] || status;
};
// 获取优先级文本
const getPriorityText = (priority) => {
const priorityMap = {
high: '高',
medium: '中',
low: '低'
};
return priorityMap[priority] || priority;
};
// 理由弹框状态
const showReasonDialog = ref(false);
const actionType = ref('approved'); // 'approved' | 'rejected'
const reasonText = ref('');
// 处理审批操作(先弹出理由框)
const handleApproval = (action) => {
actionType.value = action;
reasonText.value = '';
showReasonDialog.value = true;
};
const closeReasonDialog = () => {
showReasonDialog.value = false;
};
const submitApproval = () => {
const text = reasonText.value.trim();
if (!text) {
uni.showToast({ title: '请先填写理由', icon: 'none' });
return;
}
const action = actionType.value;
const actionText = action === 'approved' ? '通过' : '拒绝';
// 这里可以调用API提交备注与状态
approvalDetail.value.status = action;
approvalDetail.value.approveTime = new Date().toLocaleString('zh-CN');
approvalDetail.value.approver = '当前用户'; // 实际应为当前登录用户
approvalDetail.value.remark = text;
showReasonDialog.value = false;
uni.showToast({ title: `${actionText}`, icon: 'success' });
setTimeout(() => {
uni.navigateBack();
}, 1200);
};
// 头部相关方法
const goBack = () => {
uni.navigateBack();
};
const showMoreOptions = () => {
uni.showActionSheet({
itemList: ["刷新", "返回首页"],
success: (res) => {
switch (res.tapIndex) {
case 0:
// 刷新数据
uni.showToast({ title: "数据已刷新", icon: "success" });
break;
case 1:
uni.reLaunch({
url: "/pages/index/index",
});
uni.showToast({ title: "已返回工作台", icon: "success" });
break;
}
},
});
};
// 页面加载时获取参数
onMounted(() => {
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const options = currentPage.options;
// 从URL参数中获取数据
approvalDetail.value = {
id: options.id || '',
title: decodeURIComponent(options.title || ''),
status: options.status || '',
applicant: decodeURIComponent(options.applicant || ''),
applyTime: decodeURIComponent(options.applyTime || ''),
businessType: decodeURIComponent(options.businessType || ''),
priority: options.priority || '',
amount: options.amount ? decodeURIComponent(options.amount) : '',
approver: '待分配',
approveTime: '',
remark: ''
};
});
</script>
<style lang="scss" scoped>
@import "../../static/css/style.scss";
// 变量定义
$border-radius: 16rpx;
$border-radius-small: 12rpx;
$spacing-small: 12rpx;
$spacing-medium: 20rpx;
$spacing-large: 30rpx;
$transition: all 0.3s ease;
.main-data-page {
background: var(--background);
min-height: 100vh;
padding-top: calc(var(--status-bar-height) + 88rpx);
padding-bottom: 40rpx;
.main-container {
padding: 24rpx;
}
}
// 详情卡片
.detail-card {
background: var(--surface);
border-radius: $border-radius;
box-shadow: var(--shadow);
border: 1rpx solid var(--border-light);
overflow: hidden;
}
// 详情头部
.detail-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: $spacing-large;
border-bottom: 1rpx solid var(--border-light);
.detail-title {
font-size: 32rpx;
font-weight: 600;
color: var(--title-color);
flex: 1;
margin-right: $spacing-medium;
}
.detail-status {
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-size: 24rpx;
font-weight: 500;
white-space: nowrap;
&.pending {
background: var(--warning-light);
color: var(--warning);
}
&.approved {
background: var(--success-light);
color: var(--success);
}
&.rejected {
background: var(--error-light);
color: var(--error);
}
}
}
// 详情区域
.detail-section {
padding: $spacing-large;
border-bottom: 1rpx solid var(--border-light);
&:last-child {
border-bottom: none;
}
.section-title {
font-size: 28rpx;
font-weight: 600;
color: var(--title-color);
margin-bottom: $spacing-medium;
}
}
// 信息列表
.info-list {
.info-item {
display: flex;
align-items: center;
margin-bottom: $spacing-small;
&:last-child {
margin-bottom: 0;
}
.info-label {
font-size: 26rpx;
color: var(--text-muted);
width: 140rpx;
flex-shrink: 0;
}
.info-value {
font-size: 26rpx;
color: var(--text-color);
flex: 1;
&.amount {
color: var(--orange);
font-weight: 600;
}
}
.priority-tag {
padding: 6rpx 12rpx;
border-radius: $border-radius-small;
font-size: 22rpx;
font-weight: 500;
&.high {
background: var(--error-light);
color: var(--error);
}
&.medium {
background: var(--warning-light);
color: var(--warning);
}
&.low {
background: var(--success-light);
color: var(--success);
}
}
}
}
// 操作按钮
.action-buttons {
display: flex;
gap: $spacing-medium;
padding: $spacing-large;
.action-btn {
flex: 1;
height: 80rpx;
border-radius: $border-radius;
font-size: 28rpx;
font-weight: 600;
border: none;
transition: $transition;
&.reject-btn {
background: var(--error-light);
color: var(--error);
border: 1rpx solid var(--error);
&:active {
background: var(--error);
color: var(--white);
}
}
&.approve-btn {
background: var(--success-light);
color: var(--success);
border: 1rpx solid var(--success);
&:active {
background: var(--success);
color: var(--white);
}
}
}
}
// 支持安全区域的设备
@supports (padding: max(0px)) {
.main-data-page {
padding-top: calc(
var(--status-bar-height) + 88rpx + env(safe-area-inset-top)
);
}
}
// 理由弹窗样式
.reason-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.45);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
}
.reason-modal {
width: 86%;
background: var(--surface);
border-radius: $border-radius;
box-shadow: var(--shadow-lg);
border: 1rpx solid var(--border-light);
padding: $spacing-large;
}
.reason-title {
font-size: 30rpx;
font-weight: 600;
color: var(--title-color);
margin-bottom: $spacing-medium;
}
.reason-textarea {
width: 100%;
min-height: 160rpx;
background: var(--surface-hover);
border: 1rpx solid var(--border-light);
border-radius: $border-radius-small;
padding: $spacing-medium;
font-size: 26rpx;
color: var(--text-color);
box-sizing: border-box;
}
.reason-tip {
margin-top: $spacing-small;
font-size: 22rpx;
color: var(--text-muted);
}
.reason-actions {
margin-top: $spacing-large;
display: flex;
gap: $spacing-medium;
}
.reason-btn {
flex: 1;
height: 76rpx;
border-radius: $border-radius;
font-size: 28rpx;
font-weight: 600;
border: none;
}
.reason-btn.cancel {
background: var(--button-secondary);
color: var(--text-color);
}
.reason-btn.confirm {
background: var(--primary-color);
color: var(--white);
}
</style>
File diff suppressed because it is too large Load Diff