审批管理

This commit is contained in:
WindowBird 2025-11-13 17:18:37 +08:00
parent 6da442b49e
commit d7d7661a33
6 changed files with 424 additions and 1 deletions

View File

@ -24,3 +24,6 @@ export * from './customer';
// 导入通用 API
export * from './common';
// 导入审批相关 API
export * from './verify';

18
api/verify.js Normal file
View File

@ -0,0 +1,18 @@
// 审批相关 API
export const getVerifyList = (params = {}) => {
return uni.$uv.http.get('/bst/verify/list', {
params: {
pageNum: 1,
pageSize: 20,
orderByColumn: 'createTime',
isAsc: 'descending',
bstType: 'UPDATE_TASK',
...params
},
custom: {
auth: true
}
});
};

View File

@ -0,0 +1,146 @@
<template>
<scroll-view class="workbench-scroll" scroll-y>
<view class="workbench-card">
<view class="header">
<text class="title">工作台</text>
<view class="search-box" @click="goToSearch">
<text class="search-icon">🔍</text>
<text class="placeholder">搜索</text>
</view>
</view>
<view class="grid">
<view
class="grid-item"
v-for="item in items"
:key="item.key"
@click="handleClick(item)"
>
<view class="icon-wrapper">
<text class="icon-text">{{ item.icon }}</text>
</view>
<text class="item-text">{{ item.text }}</text>
</view>
</view>
</view>
</scroll-view>
</template>
<script setup>
import { ref } from 'vue';
const items = ref([
{ key: 'verify', text: '审批管理', icon: '📝' },
{ key: 'customer', text: '客户管理', icon: '👤' },
{ key: 'project', text: '项目管理', icon: '📚' },
{ key: 'task', text: '任务管理', icon: '🗂️' },
{ key: 'schedule', text: '日程管理', icon: '🗓️' },
{ key: 'contact', text: '通讯录', icon: '📇' },
{ key: 'notice', text: '公告管理', icon: '📢' },
{ key: 'wechat', text: '工作微信', icon: '🤖' }
]);
const goToSearch = () => {
//
};
const handleClick = (item) => {
if (item.key === 'verify') {
//
uni.navigateTo({
url: '/pages/verify/list/index?bstType=UPDATE_TASK'
});
return;
}
if (item.key === 'task') {
uni.switchTab({ url: '/pages/index/index' });
return;
}
if (item.key === 'customer') {
uni.switchTab({ url: '/pages/index/index' });
return;
}
//
uni.showToast({ title: '开发中', icon: 'none' });
};
</script>
<style lang="scss" scoped>
.workbench-scroll {
width: 100%;
height: 100vh;
background: #f5f5f5;
}
.workbench-card {
margin: 16px;
padding: 16px;
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.title {
font-size: 16px;
font-weight: 600;
color: #333;
}
.search-box {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
background: #f5f6f7;
border-radius: 16px;
}
.search-icon {
font-size: 14px;
}
.placeholder {
font-size: 12px;
color: #aaa;
}
.grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px 10px;
margin-top: 8px;
}
.grid-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.icon-wrapper {
width: 54px;
height: 54px;
border-radius: 50%;
background: #2885ff;
display: flex;
align-items: center;
justify-content: center;
}
.icon-text {
font-size: 20px;
color: #fff;
}
.item-text {
font-size: 12px;
color: #333;
}
</style>

View File

@ -15,6 +15,12 @@
}
},
{
"path": "pages/verify/list/index",
"style": {
"navigationBarTitleText": "审批管理"
}
},
{
"path": "pages/login/index",
"style": {

View File

@ -14,6 +14,10 @@
<!-- 月度考核排行榜 -->
<RankingBoard v-else-if="admin_bst && value === rankingIndex" />
<!-- 工作台 -->
<Workbench v-else-if="value === workIndex" />
<!-- 其他内容底部导航栏未选中客户管理时显示 -->
<template v-else>
<!-- 日程编辑 -->
@ -32,6 +36,7 @@
<!-- 消息内容 -->
<MessageContent v-if="topTabValue === 3" />
</template>
</view>
@ -69,6 +74,7 @@ import MessageContent from '@/components/index/MessageContent.vue';
import CustomerManagement from '@/components/customer/CustomerManagement.vue';
import My from '@/components/my/My.vue';
import RankingBoard from '@/components/index/RankingBoard.vue';
import Workbench from '@/components/index/Workbench.vue';
// tabs
@ -77,6 +83,7 @@ const topTabs = [
{ name: '内容看板', value: 1 },
{ name: '待办事项', value: 2 },
{ name: '消息内容', value: 3 }
];
//
const topTabValue = ref(1);
@ -150,10 +157,11 @@ const admin_bst=ref(false);
const rankingIndex = computed(() => 3);
const customerManagementIndex = computed(() => 4);
const myIndex = computed(() => 2);
const workIndex = computed(() => 1);
// Tabs
const shouldShowTopTabs = computed(() => {
if (value.value === customerManagementIndex.value || value.value === myIndex.value) {
if (value.value === customerManagementIndex.value || value.value === myIndex.value||value.value === workIndex.value) {
return false;
}
if (admin_bst.value && value.value === rankingIndex.value) {

242
pages/verify/list/index.vue Normal file
View File

@ -0,0 +1,242 @@
<template>
<view class="verify-page">
<view class="tabs-wrapper">
<uv-tabs :list="tabs" :current="currentTab" @click="onTabClick"></uv-tabs>
</view>
<scroll-view class="list-scroll" scroll-y @scrolltolower="loadMore">
<view class="card" v-for="item in displayList" :key="item.id" @click="goDetail(item)">
<view class="card-header">
<view class="left">
<text class="badge">申请延期</text>
<text class="sub">所属项目 · {{ item.projectName || '—' }}</text>
</view>
<view class="right">
<uv-tags :text="statusText(item.status)" :type="statusType(item.status)" size="mini"></uv-tags>
</view>
</view>
<view class="card-body">
<text class="remark" v-if="item.createRemark">{{ item.createRemark }}</text>
<view class="row">
<text class="label">截止时间</text>
<text class="value">{{ item.expireTime || '—' }}</text>
</view>
<view class="row">
<text class="label">申请人</text>
<text class="value">{{ item.createName || '—' }}</text>
</view>
</view>
<view class="card-footer">
<uv-button
v-if="item.status === '3'"
type="primary"
size="small"
text="去处理"
@click.stop="goHandle(item)"
/>
<uv-button
v-else
type="info"
size="small"
:text="statusText(item.status)"
:plain="true"
:disabled="true"
/>
</view>
</view>
<view class="empty" v-if="!loading && displayList.length === 0">
<text>暂无数据</text>
</view>
<view class="loading" v-if="loading">
<text>加载中...</text>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { getVerifyList } from '@/api';
const tabs = [
{ name: '待我处理', value: 'pending' },
{ name: '我已处理', value: 'done' }
];
const currentTab = ref(0);
const pageNum = ref(1);
const pageSize = ref(20);
const total = ref(0);
const rawList = ref([]);
const loading = ref(false);
const bstType = ref('UPDATE_TASK');
function statusText(s) {
if (s === '1') return '已通过';
if (s === '2') return '已驳回';
return '待处理';
}
function statusType(s) {
if (s === '1') return 'success';
if (s === '2') return 'error';
return 'warning';
}
const displayList = computed(() => {
if (currentTab.value === 0) {
return rawList.value.filter(i => String(i.status) === '3');
}
return rawList.value.filter(i => String(i.status) !== '3');
});
const safeParseExpire = (dataField) => {
if (!dataField) return '';
try {
if (typeof dataField === 'string') {
return JSON.parse(dataField)?.expireTime || '';
}
return dataField.expireTime || '';
} catch (e) {
return '';
}
};
const fetchList = async () => {
if (loading.value) return;
loading.value = true;
try {
const res = await getVerifyList({
pageNum: pageNum.value,
pageSize: pageSize.value,
orderByColumn: 'createTime',
isAsc: 'descending',
bstType: bstType.value
});
const rows = res?.rows || [];
total.value = res?.total || 0;
const mapped = rows.map(r => ({
...r,
expireTime: safeParseExpire(r.data)
}));
if (pageNum.value === 1) {
rawList.value = mapped;
} else {
rawList.value = rawList.value.concat(mapped);
}
} finally {
loading.value = false;
}
};
const loadMore = () => {
if (rawList.value.length >= total.value) return;
pageNum.value += 1;
fetchList();
};
const onTabClick = (tab) => {
currentTab.value = tab.index;
};
const goHandle = (item) => {
//
uni.showToast({ title: '去处理', icon: 'none' });
};
const goDetail = (item) => {
//
};
onMounted(() => {
//
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const options = currentPage?.options || {};
if (options.bstType) {
bstType.value = options.bstType;
}
fetchList();
});
</script>
<style lang="scss" scoped>
.verify-page {
width: 100%;
background: #f5f5f5;
display: flex;
flex-direction: column;
}
.tabs-wrapper {
background: #fff;
}
.list-scroll {
flex: 1;
}
.card {
background: #fff;
margin: 12px 12px 0 12px;
border-radius: 12px;
padding: 12px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6px;
}
.left {
display: flex;
align-items: center;
gap: 8px;
}
.badge {
font-size: 14px;
font-weight: 600;
color: #333;
}
.sub {
font-size: 12px;
color: #999;
}
.card-body {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 4px;
}
.remark {
font-size: 13px;
color: #333;
line-height: 1.6;
}
.row {
display: flex;
font-size: 12px;
color: #666;
}
.label {
width: 70px;
flex-shrink: 0;
}
.value {
color: #333;
}
.card-footer {
margin-top: 10px;
display: flex;
justify-content: flex-start;
}
.empty, .loading {
text-align: center;
color: #999;
font-size: 12px;
padding: 16px 0;
}
</style>