576 lines
14 KiB
Vue
Raw Normal View History

2025-08-17 22:04:29 +08:00
<template>
<BasicLayout>
<view class="px-15 pb-15">
<!-- 搜索和筛选 -->
<view class="search-section">
<BasicSearch
placeholder="搜索公文标题或编号"
@search="handleSearch"
/>
</view>
<!-- 筛选标签 -->
<view class="filter-section">
<view class="filter-tabs">
<view
v-for="tab in filterTabs"
:key="tab.key"
class="filter-tab"
:class="{ active: activeTab === tab.key }"
@click="switchTab(tab.key)"
>
{{ tab.label }}
</view>
</view>
</view>
<!-- 公文列表 -->
<view class="gw-list">
<view
v-for="item in filteredGwList"
:key="item.id"
class="gw-item"
@click="goToDetail(item)"
>
<view class="gw-header">
<view class="gw-title">{{ item.title }}</view>
<view class="gw-status" :class="getStatusClass(item.status)">
{{ getStatusText(item.status) }}
</view>
</view>
<view class="gw-info">
<view class="info-row">
<text class="label">编号</text>
<text class="value">{{ item.gwNo }}</text>
</view>
<view class="info-row">
<text class="label">类型</text>
<text class="value">{{ item.gwType }}</text>
</view>
<view class="info-row">
<text class="label">紧急程度</text>
<text class="value urgency-tag" :class="getUrgencyClass(item.urgencyLevel)">
{{ getUrgencyText(item.urgencyLevel) }}
</text>
</view>
</view>
<view class="gw-footer">
<view class="approver-info">
<text class="label">审批进度</text>
<text class="value">{{ getApproverProgress(item) }}</text>
</view>
<view class="time-info">
<text class="label">创建时间</text>
<text class="value">{{ formatTime(item.createdTime) }}</text>
</view>
</view>
<view class="gw-actions">
<u-button
text="查看详情"
size="mini"
type="primary"
@click.stop="goToDetail(item)"
/>
<u-button
v-if="item.status === GwStatus.DRAFT"
text="编辑"
size="mini"
@click.stop="editGw(item)"
/>
<u-button
v-if="item.status === GwStatus.DRAFT"
text="删除"
size="mini"
type="error"
@click.stop="deleteGw(item)"
/>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-if="filteredGwList.length === 0" class="empty-state">
<view class="empty-icon">📄</view>
<view class="empty-text">暂无公文数据</view>
</view>
<!-- 加载更多 -->
<view v-if="hasMore && filteredGwList.length > 0" class="load-more">
<u-button
text="加载更多"
@click="loadMore"
:loading="loading"
/>
</view>
</view>
<!-- 底部操作按钮 -->
<template #bottom>
<view class="flex-row items-center pb-10 pt-5">
<u-button
text="新建公文"
class="mx-15"
type="primary"
@click="createNewGw"
/>
</view>
</template>
</BasicLayout>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from "vue";
import { navigateTo } from "@/utils/uniapp";
import BasicSearch from "@/components/BasicSearch/Search.vue";
import BasicLayout from "@/components/BasicLayout/Layout.vue";
import { getGwListApi, deleteGwApi } from "@/api/routine/gw";
import { GwStatus, UrgencyLevel, ApproverStatus } from "@/types/gw";
import type { GwInfo, GwListItem } from "@/types/gw";
import dayjs from "dayjs";
// 筛选标签
const filterTabs = [
{ key: "all", label: "全部" },
{ key: "draft", label: "草稿" },
{ key: "pending", label: "待审批" },
{ key: "approved", label: "已通过" },
{ key: "rejected", label: "已驳回" },
];
const activeTab = ref("all");
const searchKeyword = ref("");
const gwList = ref<GwListItem[]>([]);
const loading = ref(false);
const hasMore = ref(true);
const page = ref(1);
const pageSize = 20;
// 筛选后的公文列表
const filteredGwList = computed(() => {
let list = gwList.value;
// 按状态筛选
if (activeTab.value !== "all") {
const statusMap: Record<string, GwStatus> = {
draft: GwStatus.DRAFT,
pending: GwStatus.PENDING,
approved: GwStatus.APPROVED,
rejected: GwStatus.REJECTED,
};
const targetStatus = statusMap[activeTab.value];
if (targetStatus) {
list = list.filter(item => item.status === targetStatus);
}
}
// 按关键词搜索
if (searchKeyword.value) {
const keyword = searchKeyword.value.toLowerCase();
list = list.filter(item =>
item.title.toLowerCase().includes(keyword) ||
item.gwNo.toLowerCase().includes(keyword)
);
}
return list;
});
// 切换筛选标签
const switchTab = (tabKey: string) => {
activeTab.value = tabKey;
};
// 搜索处理
const handleSearch = (keyword: string) => {
searchKeyword.value = keyword;
};
// 获取公文列表
const getGwList = async (isLoadMore = false) => {
try {
loading.value = true;
if (!isLoadMore) {
page.value = 1;
hasMore.value = true;
}
// 模拟API调用实际开发时替换为真实API
const statusMap: Record<string, GwStatus> = {
draft: GwStatus.DRAFT,
pending: GwStatus.PENDING,
approved: GwStatus.APPROVED,
rejected: GwStatus.REJECTED,
};
const targetStatus = activeTab.value === "all" ? undefined : statusMap[activeTab.value];
// 模拟筛选逻辑
let filteredData = mockData;
if (targetStatus) {
filteredData = mockData.filter(item => item.status === targetStatus);
}
if (searchKeyword.value) {
const keyword = searchKeyword.value.toLowerCase();
filteredData = filteredData.filter(item =>
item.title.toLowerCase().includes(keyword) ||
item.gwNo.toLowerCase().includes(keyword)
);
}
// 模拟分页
const startIndex = (page.value - 1) * pageSize;
const endIndex = startIndex + pageSize;
const result = {
list: filteredData.slice(startIndex, endIndex),
hasMore: endIndex < filteredData.length,
};
if (isLoadMore) {
gwList.value.push(...result.list);
} else {
gwList.value = result.list;
}
hasMore.value = result.hasMore;
page.value++;
} catch (error) {
console.error("获取公文列表失败:", error);
uni.showToast({
title: "获取列表失败",
icon: "error",
});
} finally {
loading.value = false;
}
};
// 加载更多
const loadMore = () => {
if (!loading.value && hasMore.value) {
getGwList(true);
}
};
// 跳转到详情页面
const goToDetail = (item: GwListItem) => {
navigateTo(`/pages/view/routine/gwlz/gwFlow?id=${item.id}`);
};
// 编辑公文
const editGw = (item: GwListItem) => {
navigateTo(`/pages/view/routine/gwlz?id=${item.id}&mode=edit`);
};
// 删除公文
const deleteGw = (item: GwListItem) => {
uni.showModal({
title: "确认删除",
content: `确定要删除公文"${item.title}"吗?`,
success: async (res) => {
if (res.confirm) {
try {
// 模拟删除API调用
await deleteGwApi(item.id);
// 从本地列表中移除
const index = gwList.value.findIndex(gw => gw.id === item.id);
if (index > -1) {
gwList.value.splice(index, 1);
}
uni.showToast({
title: "删除成功",
icon: "success",
});
} catch (error) {
console.error("删除公文失败:", error);
uni.showToast({
title: "删除失败",
icon: "error",
});
}
}
},
});
};
// 新建公文
const createNewGw = () => {
navigateTo("/pages/view/routine/gwlz/gwAdd");
};
// 获取状态样式类
const getStatusClass = (status: GwStatus) => {
const statusMap: Record<GwStatus, string> = {
[GwStatus.DRAFT]: "status-draft",
[GwStatus.PENDING]: "status-pending",
[GwStatus.APPROVED]: "status-approved",
[GwStatus.REJECTED]: "status-rejected",
};
return statusMap[status] || "status-default";
};
// 获取状态文本
const getStatusText = (status: GwStatus) => {
const statusMap: Record<GwStatus, string> = {
[GwStatus.DRAFT]: "草稿",
[GwStatus.PENDING]: "待审批",
[GwStatus.APPROVED]: "已通过",
[GwStatus.REJECTED]: "已驳回",
};
return statusMap[status] || "未知";
};
// 获取紧急程度样式类
const getUrgencyClass = (urgency: UrgencyLevel) => {
const urgencyMap: Record<UrgencyLevel, string> = {
[UrgencyLevel.LOW]: "urgency-low",
[UrgencyLevel.NORMAL]: "urgency-normal",
[UrgencyLevel.HIGH]: "urgency-high",
[UrgencyLevel.URGENT]: "urgency-urgent",
};
return urgencyMap[urgency] || "urgency-normal";
};
// 获取紧急程度文本
const getUrgencyText = (urgency: UrgencyLevel) => {
const urgencyMap: Record<UrgencyLevel, string> = {
[UrgencyLevel.LOW]: "普通",
[UrgencyLevel.NORMAL]: "一般",
[UrgencyLevel.HIGH]: "紧急",
[UrgencyLevel.URGENT]: "特急",
};
return urgencyMap[urgency] || "一般";
};
// 获取审批进度
const getApproverProgress = (item: GwListItem) => {
if (!item.approvers || item.approvers.length === 0) {
return "无审批人";
}
const total = item.approvers.length;
const approved = item.approvers.filter((a: any) => a.status === ApproverStatus.APPROVED).length;
const rejected = item.approvers.filter((a: any) => a.status === ApproverStatus.REJECTED).length;
if (rejected > 0) {
return `已驳回 (${rejected}/${total})`;
}
return `${approved}/${total} 已审批`;
};
// 格式化时间
const formatTime = (time: string | Date) => {
return dayjs(time).format("MM-DD HH:mm");
};
// 模拟数据用于开发测试
const mockData: GwListItem[] = [
{
id: "1",
title: "关于2024年教学工作计划的通知",
gwNo: "GW2024001",
gwType: "通知",
urgencyLevel: UrgencyLevel.NORMAL,
status: GwStatus.PENDING,
createdBy: "admin",
createdTime: new Date(),
files: [],
approvers: [
{ id: "1", userId: "user1", userName: "审批人1", deptId: "dept1", deptName: "部门1", order: 1, status: ApproverStatus.APPROVED },
{ id: "2", userId: "user2", userName: "审批人2", deptId: "dept2", deptName: "部门2", order: 2, status: ApproverStatus.PENDING },
],
ccUsers: [],
},
{
id: "2",
title: "2024年春季学期课程安排",
gwNo: "GW2024002",
gwType: "安排",
urgencyLevel: UrgencyLevel.HIGH,
status: GwStatus.APPROVED,
createdBy: "admin",
createdTime: new Date(Date.now() - 86400000),
files: [],
approvers: [
{ id: "3", userId: "user1", userName: "审批人1", deptId: "dept1", deptName: "部门1", order: 1, status: ApproverStatus.APPROVED },
{ id: "4", userId: "user2", userName: "审批人2", deptId: "dept2", deptName: "部门2", order: 2, status: ApproverStatus.APPROVED },
],
ccUsers: [],
},
{
id: "3",
title: "学生宿舍管理规定修订稿",
gwNo: "GW2024003",
gwType: "规定",
urgencyLevel: UrgencyLevel.LOW,
status: GwStatus.DRAFT,
createdBy: "admin",
createdTime: new Date(Date.now() - 172800000),
files: [],
approvers: [],
ccUsers: [],
},
];
onMounted(() => {
// 使用模拟数据初始化列表
gwList.value = mockData;
});
</script>
<style lang="scss" scoped>
.search-section {
margin-bottom: 15px;
}
.filter-section {
margin-bottom: 20px;
.filter-tabs {
display: flex;
background: #f5f5f5;
border-radius: 8px;
padding: 4px;
.filter-tab {
flex: 1;
text-align: center;
padding: 8px 12px;
border-radius: 6px;
font-size: 14px;
color: #666;
transition: all 0.3s;
&.active {
background: #007aff;
color: white;
}
}
}
}
.gw-list {
.gw-item {
background: white;
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.gw-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
.gw-title {
flex: 1;
font-size: 16px;
font-weight: 500;
color: #333;
line-height: 1.4;
margin-right: 10px;
}
.gw-status {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
&.status-draft { background: #f0f0f0; color: #666; }
&.status-pending { background: #fff7e6; color: #fa8c16; }
&.status-approved { background: #f6ffed; color: #52c41a; }
&.status-rejected { background: #fff2f0; color: #ff4d4f; }
}
}
.gw-info {
margin-bottom: 12px;
.info-row {
display: flex;
margin-bottom: 6px;
.label {
width: 70px;
color: #666;
font-size: 14px;
}
.value {
flex: 1;
color: #333;
font-size: 14px;
}
.urgency-tag {
padding: 2px 6px;
border-radius: 4px;
font-size: 12px;
&.urgency-low { background: #f6ffed; color: #52c41a; }
&.urgency-normal { background: #f0f0f0; color: #666; }
&.urgency-high { background: #fff7e6; color: #fa8c16; }
&.urgency-urgent { background: #fff2f0; color: #ff4d4f; }
}
}
}
.gw-footer {
display: flex;
justify-content: space-between;
margin-bottom: 15px;
font-size: 12px;
.approver-info,
.time-info {
.label {
color: #666;
margin-right: 5px;
}
.value {
color: #333;
}
}
}
.gw-actions {
display: flex;
gap: 8px;
justify-content: flex-end;
}
}
}
.empty-state {
text-align: center;
padding: 60px 20px;
.empty-icon {
font-size: 48px;
margin-bottom: 15px;
opacity: 0.5;
}
.empty-text {
color: #999;
font-size: 14px;
}
}
.load-more {
text-align: center;
margin-top: 20px;
}
</style>