594 lines
14 KiB
Vue
Raw Normal View History

2025-08-17 22:04:29 +08:00
<template>
2025-08-24 21:46:29 +08:00
<view class="gw-list-page">
<!-- 列表组件 -->
<view class="list-component">
<BasicListLayout @register="register" v-model="dataList">
<!-- 搜索和筛选组件放在top插槽中 -->
<template #top>
<view class="query-component">
<view class="search-card">
<!-- 搜索框 -->
<view class="search-item">
<BasicSearch
placeholder="搜索公文标题或编号"
@search="handleSearch"
class="search-input"
/>
</view>
<!-- 筛选标签 -->
<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>
2025-08-17 22:04:29 +08:00
</view>
</view>
2025-08-24 21:46:29 +08:00
</template>
<template v-slot="{ data }">
<view class="gw-card">
<view class="card-header">
<text class="gw-title">{{ data.title }}</text>
<text class="gw-status" :class="getStatusClass(data.gwStatus)">
{{ getStatusText(data.gwStatus) }}
2025-08-17 22:04:29 +08:00
</text>
</view>
2025-08-24 21:46:29 +08:00
<view class="card-body">
<view class="gw-info">
<view class="info-item">
<text class="info-label">类型</text>
<text class="info-value">{{ data.docType }}</text>
</view>
<view class="info-item">
<text class="info-label">紧急程度</text>
<text class="info-value urgency-tag" :class="getUrgencyClass(data.urgencyLevel)">
{{ getUrgencyText(data.urgencyLevel) }}
</text>
</view>
<view class="info-item">
<text class="info-label">审批进度</text>
<text class="info-value">{{ getApproverProgress(data) }}</text>
</view>
<view class="info-item">
<text class="info-label">提交人</text>
<text class="info-value">{{ data.tjrxm || '未知' }}</text>
</view>
<view class="info-item">
<text class="info-label">提交时间</text>
<text class="info-value">{{ formatTime(data.tjrtime || data.createdTime) }}</text>
</view>
</view>
2025-08-17 22:04:29 +08:00
</view>
2025-08-24 21:46:29 +08:00
<view class="card-footer">
<view class="footer-actions">
<u-button
text="详情"
size="mini"
type="primary"
@click="goToDetail(data)"
/>
<u-button
v-if="data.gwStatus === 'B'"
text="编辑"
size="mini"
@click="editGw(data)"
/>
<u-button
v-if="data.gwStatus === 'B'"
text="删除"
size="mini"
type="error"
@click="deleteGw(data)"
/>
</view>
2025-08-17 22:04:29 +08:00
</view>
</view>
2025-08-24 21:46:29 +08:00
</template>
<!-- 新建公文按钮放在bottom插槽中 -->
<template #bottom>
<view class="flex-row items-center pb-10 pt-5">
<u-button
text="新建公文"
class="mx-15"
type="primary"
@click="createNewGw"
2025-08-17 22:04:29 +08:00
/>
</view>
2025-08-24 21:46:29 +08:00
</template>
</BasicListLayout>
2025-08-17 22:04:29 +08:00
</view>
2025-08-24 21:46:29 +08:00
</view>
2025-08-17 22:04:29 +08:00
</template>
<script setup lang="ts">
2025-08-24 21:46:29 +08:00
import { ref, computed, watch, onMounted } from "vue";
import { onShow } from "@dcloudio/uni-app";
2025-08-17 22:04:29 +08:00
import { navigateTo } from "@/utils/uniapp";
import BasicSearch from "@/components/BasicSearch/Search.vue";
import BasicLayout from "@/components/BasicLayout/Layout.vue";
2025-08-24 21:46:29 +08:00
import BasicListLayout from "@/components/BasicListLayout/ListLayout.vue";
import { useLayout } from "@/components/BasicListLayout/hooks/useLayout";
import { gwFindPageApi, gwLogicDeleteApi } from "@/api/routine/gw";
2025-08-17 22:04:29 +08:00
import { GwStatus, UrgencyLevel, ApproverStatus } from "@/types/gw";
import type { GwInfo, GwListItem } from "@/types/gw";
import dayjs from "dayjs";
// 筛选标签
const filterTabs = [
{ key: "all", label: "全部" },
2025-08-24 21:46:29 +08:00
{ key: "A", label: "已提交" },
{ key: "B", label: "草稿" },
2025-08-17 22:04:29 +08:00
];
const activeTab = ref("all");
const searchKeyword = ref("");
2025-08-24 21:46:29 +08:00
// 使用 BasicListLayout
const [register, { reload, setParam }] = useLayout({
api: gwFindPageApi,
componentProps: {
defaultPageSize: 20,
},
param: {
title: "",
gwStatus: "",
},
});
// 数据列表
const dataList = ref<GwListItem[]>([]);
2025-08-17 22:04:29 +08:00
// 筛选后的公文列表
const filteredGwList = computed(() => {
2025-08-24 21:46:29 +08:00
let list = dataList.value;
2025-08-17 22:04:29 +08:00
// 按状态筛选
if (activeTab.value !== "all") {
2025-08-24 21:46:29 +08:00
list = list.filter(item => item.gwStatus === activeTab.value);
2025-08-17 22:04:29 +08:00
}
// 按关键词搜索
if (searchKeyword.value) {
const keyword = searchKeyword.value.toLowerCase();
list = list.filter(item =>
item.title.toLowerCase().includes(keyword) ||
2025-08-24 21:46:29 +08:00
(item.gwNo && item.gwNo.toLowerCase().includes(keyword))
2025-08-17 22:04:29 +08:00
);
}
return list;
});
// 切换筛选标签
const switchTab = (tabKey: string) => {
activeTab.value = tabKey;
2025-08-24 21:46:29 +08:00
// 更新查询参数并重新加载
const gwStatus = tabKey === "all" ? "" : tabKey;
setParam({ gwStatus });
reload();
2025-08-17 22:04:29 +08:00
};
// 搜索处理
const handleSearch = (keyword: string) => {
searchKeyword.value = keyword;
2025-08-24 21:46:29 +08:00
// 更新查询参数并重新加载
setParam({ title: keyword });
reload();
2025-08-17 22:04:29 +08:00
};
// 跳转到详情页面
const goToDetail = (item: GwListItem) => {
2025-08-24 21:46:29 +08:00
navigateTo(`/pages/view/routine/gwlz/gwDetail?id=${item.id}`);
2025-08-17 22:04:29 +08:00
};
// 编辑公文
const editGw = (item: GwListItem) => {
2025-08-24 21:46:29 +08:00
const url = `/pages/view/routine/gwlz/gwAdd?id=${item.id}&mode=edit`;
// 先存储编辑参数到本地存储,确保页面跳转后能获取到
uni.setStorageSync('gwEditMode', 'edit');
uni.setStorageSync('gwEditId', item.id);
navigateTo(url);
2025-08-17 22:04:29 +08:00
};
// 删除公文
const deleteGw = (item: GwListItem) => {
uni.showModal({
title: "确认删除",
content: `确定要删除公文"${item.title}"吗?`,
success: async (res) => {
if (res.confirm) {
try {
2025-08-24 21:46:29 +08:00
// 调用软删除API - 需要传递 {ids: item.id} 格式的参数
await gwLogicDeleteApi({ ids: item.id });
2025-08-17 22:04:29 +08:00
2025-08-24 21:46:29 +08:00
// 删除成功后刷新列表
reload();
2025-08-17 22:04:29 +08:00
2025-08-24 21:46:29 +08:00
uni.showToast({
2025-08-17 22:04:29 +08:00
title: "删除成功",
2025-08-24 21:46:29 +08:00
icon: "success",
});
2025-08-17 22:04:29 +08:00
2025-08-24 21:46:29 +08:00
} catch (error) {
2025-08-17 22:04:29 +08:00
console.error("删除公文失败:", error);
2025-08-24 21:46:29 +08:00
uni.showToast({
2025-08-17 22:04:29 +08:00
title: "删除失败",
2025-08-24 21:46:29 +08:00
icon: "error",
});
}
2025-08-17 22:04:29 +08:00
}
},
});
};
// 新建公文
const createNewGw = () => {
navigateTo("/pages/view/routine/gwlz/gwAdd");
};
// 获取状态样式类
2025-08-24 21:46:29 +08:00
const getStatusClass = (status: string) => {
const statusMap: Record<string, string> = {
'A': "status-submitted", // 已提交状态
'B': "status-draft", // 草稿状态
2025-08-17 22:04:29 +08:00
};
return statusMap[status] || "status-default";
};
// 获取状态文本
2025-08-24 21:46:29 +08:00
const getStatusText = (status: string) => {
const statusMap: Record<string, string> = {
'A': "已提交", // 已提交状态
'B': "草稿", // 草稿状态
2025-08-17 22:04:29 +08:00
};
return statusMap[status] || "未知";
};
// 获取紧急程度样式类
2025-08-24 21:46:29 +08:00
const getUrgencyClass = (urgency: string) => {
const urgencyMap: Record<string, string> = {
'low': "urgency-low",
'normal': "urgency-normal",
'high': "urgency-high",
'urgent': "urgency-urgent",
2025-08-17 22:04:29 +08:00
};
return urgencyMap[urgency] || "urgency-normal";
};
// 获取紧急程度文本
2025-08-24 21:46:29 +08:00
const getUrgencyText = (urgency: string) => {
const urgencyMap: Record<string, string> = {
'low': "普通",
'normal': "一般",
'high': "紧急",
'urgent': "特急",
2025-08-17 22:04:29 +08:00
};
return urgencyMap[urgency] || "一般";
};
// 获取审批进度
const getApproverProgress = (item: GwListItem) => {
2025-08-24 21:46:29 +08:00
let spCount = 0;
let ccCount = 0;
// 统计审批人数量 - 处理逗号分隔的字符串或数组
if (item.spId) {
if (Array.isArray(item.spId)) {
spCount = item.spId.length;
} else {
// 如果是逗号分隔的字符串,按逗号分割并统计
const spIdStr = String(item.spId);
if (spIdStr.trim()) {
spCount = spIdStr.split(',').filter((id: string) => id.trim()).length;
}
}
2025-08-17 22:04:29 +08:00
}
2025-08-24 21:46:29 +08:00
// 统计抄送人数量 - 处理逗号分隔的字符串或数组
if (item.ccId) {
if (Array.isArray(item.ccId)) {
ccCount = item.ccId.length;
} else {
// 如果是逗号分隔的字符串,按逗号分割并统计
const ccIdStr = String(item.ccId);
if (ccIdStr.trim()) {
ccCount = ccIdStr.split(',').filter((id: string) => id.trim()).length;
}
}
}
2025-08-17 22:04:29 +08:00
2025-08-24 21:46:29 +08:00
if (spCount === 0 && ccCount === 0) {
return "无审批人和抄送人";
2025-08-17 22:04:29 +08:00
}
2025-08-24 21:46:29 +08:00
let result = "";
if (spCount > 0) {
result += `${spCount}个审批人`;
}
if (ccCount > 0) {
if (result) result += "";
result += `${ccCount}个抄送人`;
}
return result;
2025-08-17 22:04:29 +08:00
};
// 格式化时间
2025-08-24 21:46:29 +08:00
const formatTime = (time: string | Date | undefined) => {
if (!time) return '暂无';
2025-08-17 22:04:29 +08:00
return dayjs(time).format("MM-DD HH:mm");
};
2025-08-24 21:46:29 +08:00
// 监听数据变化
watch(dataList, (val) => {
// 数据变化监听
});
// 页面显示时重新加载数据
onShow(() => {
reload();
});
2025-08-17 22:04:29 +08:00
2025-08-24 21:46:29 +08:00
// 页面加载时也加载一次数据
2025-08-17 22:04:29 +08:00
onMounted(() => {
2025-08-24 21:46:29 +08:00
reload();
2025-08-17 22:04:29 +08:00
});
</script>
<style lang="scss" scoped>
2025-08-24 21:46:29 +08:00
.gw-list-page {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f7fa;
2025-08-17 22:04:29 +08:00
}
2025-08-24 21:46:29 +08:00
.list-component {
flex: 1;
overflow: hidden;
}
.query-component {
padding: 15px;
background-color: white;
border-bottom: 1px solid #eee;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
}
.search-card {
background-color: #ffffff;
border-radius: 12px;
padding: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
border: 1px solid #f0f0f0;
}
.search-item {
margin-bottom: 12px;
}
.search-input {
width: 100%;
height: 40px;
background-color: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 0 15px;
font-size: 14px;
color: #333;
}
.filter-tabs {
display: flex;
gap: 8px;
}
.filter-tab {
flex: 1;
text-align: center;
padding: 8px 12px;
border-radius: 6px;
font-size: 14px;
color: #666;
transition: all 0.3s;
background-color: #f0f0f0;
border: 1px solid #e9ecef;
&.active {
background-color: #007aff;
color: white;
border-color: #007aff;
2025-08-17 22:04:29 +08:00
}
}
2025-08-24 21:46:29 +08:00
.gw-card {
background-color: white;
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
&:active {
transform: translateY(1px);
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.12);
}
}
.card-header {
2025-08-17 22:04:29 +08:00
display: flex;
justify-content: space-between;
2025-08-24 21:46:29 +08:00
align-items: flex-start;
margin-bottom: 12px;
gap: 12px;
}
.gw-title {
font-size: 16px;
font-weight: 600;
color: #2c3e50;
flex: 1;
line-height: 1.4;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
word-break: break-word;
}
.gw-status {
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
white-space: nowrap;
flex-shrink: 0;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
&.status-draft {
background: linear-gradient(135deg, #ffa726 0%, #ff9800 100%);
color: white;
}
&.status-submitted {
background: linear-gradient(135deg, #66bb6a 0%, #4caf50 100%);
color: white;
}
&.status-approved {
background: linear-gradient(135deg, #66bb6a 0%, #4caf50 100%);
color: white;
}
&.status-rejected {
background: linear-gradient(135deg, #ef5350 0%, #e53935 100%);
color: white;
}
}
.card-body {
margin-bottom: 15px;
}
.gw-info {
2025-08-17 22:04:29 +08:00
display: flex;
2025-08-24 21:46:29 +08:00
flex-direction: column;
gap: 8px;
}
.info-item {
2025-08-17 22:04:29 +08:00
display: flex;
2025-08-24 21:46:29 +08:00
align-items: center;
}
.info-label {
width: 80px;
color: #666;
font-size: 14px;
margin-right: 8px;
flex-shrink: 0;
}
.info-value {
color: #333;
font-size: 14px;
flex: 1;
}
.urgency-tag {
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
&.urgency-low {
background: linear-gradient(135deg, #66bb6a 0%, #4caf50 100%);
color: white;
}
&.urgency-normal {
background: linear-gradient(135deg, #90a4ae 0%, #78909c 100%);
color: white;
}
&.urgency-high {
background: linear-gradient(135deg, #ffa726 0%, #ff9800 100%);
color: white;
}
&.urgency-urgent {
background: linear-gradient(135deg, #ef5350 0%, #e53935 100%);
color: white;
2025-08-17 22:04:29 +08:00
}
}
2025-08-24 21:46:29 +08:00
.card-footer {
display: flex;
justify-content: flex-end;
align-items: center;
padding-top: 12px;
border-top: 1px solid #f0f0f0;
}
.footer-actions {
display: flex;
gap: 8px;
}
// 响应式优化
@media (max-width: 375px) {
.query-component {
padding: 12px;
2025-08-17 22:04:29 +08:00
}
2025-08-24 21:46:29 +08:00
.search-card {
padding: 12px;
}
.gw-card {
padding: 12px;
margin-bottom: 8px;
}
.card-header .gw-title {
font-size: 15px;
}
.filter-tabs {
gap: 6px;
}
.filter-tab {
padding: 6px 10px;
font-size: 13px;
2025-08-17 22:04:29 +08:00
}
}
2025-08-24 21:46:29 +08:00
// 加载动画
.gw-card {
animation: fadeInUp 0.3s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
2025-08-17 22:04:29 +08:00
}
</style>