2025-08-24 21:46:29 +08:00

594 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<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>
</view>
</view>
</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) }}
</text>
</view>
<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>
</view>
<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>
</view>
</view>
</template>
<!-- 新建公文按钮放在bottom插槽中 -->
<template #bottom>
<view class="flex-row items-center pb-10 pt-5">
<u-button
text="新建公文"
class="mx-15"
type="primary"
@click="createNewGw"
/>
</view>
</template>
</BasicListLayout>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from "vue";
import { onShow } from "@dcloudio/uni-app";
import { navigateTo } from "@/utils/uniapp";
import BasicSearch from "@/components/BasicSearch/Search.vue";
import BasicLayout from "@/components/BasicLayout/Layout.vue";
import BasicListLayout from "@/components/BasicListLayout/ListLayout.vue";
import { useLayout } from "@/components/BasicListLayout/hooks/useLayout";
import { gwFindPageApi, gwLogicDeleteApi } 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: "A", label: "已提交" },
{ key: "B", label: "草稿" },
];
const activeTab = ref("all");
const searchKeyword = ref("");
// 使用 BasicListLayout
const [register, { reload, setParam }] = useLayout({
api: gwFindPageApi,
componentProps: {
defaultPageSize: 20,
},
param: {
title: "",
gwStatus: "",
},
});
// 数据列表
const dataList = ref<GwListItem[]>([]);
// 筛选后的公文列表
const filteredGwList = computed(() => {
let list = dataList.value;
// 按状态筛选
if (activeTab.value !== "all") {
list = list.filter(item => item.gwStatus === activeTab.value);
}
// 按关键词搜索
if (searchKeyword.value) {
const keyword = searchKeyword.value.toLowerCase();
list = list.filter(item =>
item.title.toLowerCase().includes(keyword) ||
(item.gwNo && item.gwNo.toLowerCase().includes(keyword))
);
}
return list;
});
// 切换筛选标签
const switchTab = (tabKey: string) => {
activeTab.value = tabKey;
// 更新查询参数并重新加载
const gwStatus = tabKey === "all" ? "" : tabKey;
setParam({ gwStatus });
reload();
};
// 搜索处理
const handleSearch = (keyword: string) => {
searchKeyword.value = keyword;
// 更新查询参数并重新加载
setParam({ title: keyword });
reload();
};
// 跳转到详情页面
const goToDetail = (item: GwListItem) => {
navigateTo(`/pages/view/routine/gwlz/gwDetail?id=${item.id}`);
};
// 编辑公文
const editGw = (item: GwListItem) => {
const url = `/pages/view/routine/gwlz/gwAdd?id=${item.id}&mode=edit`;
// 先存储编辑参数到本地存储,确保页面跳转后能获取到
uni.setStorageSync('gwEditMode', 'edit');
uni.setStorageSync('gwEditId', item.id);
navigateTo(url);
};
// 删除公文
const deleteGw = (item: GwListItem) => {
uni.showModal({
title: "确认删除",
content: `确定要删除公文"${item.title}"吗?`,
success: async (res) => {
if (res.confirm) {
try {
// 调用软删除API - 需要传递 {ids: item.id} 格式的参数
await gwLogicDeleteApi({ ids: item.id });
// 删除成功后刷新列表
reload();
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: string) => {
const statusMap: Record<string, string> = {
'A': "status-submitted", // 已提交状态
'B': "status-draft", // 草稿状态
};
return statusMap[status] || "status-default";
};
// 获取状态文本
const getStatusText = (status: string) => {
const statusMap: Record<string, string> = {
'A': "已提交", // 已提交状态
'B': "草稿", // 草稿状态
};
return statusMap[status] || "未知";
};
// 获取紧急程度样式类
const getUrgencyClass = (urgency: string) => {
const urgencyMap: Record<string, string> = {
'low': "urgency-low",
'normal': "urgency-normal",
'high': "urgency-high",
'urgent': "urgency-urgent",
};
return urgencyMap[urgency] || "urgency-normal";
};
// 获取紧急程度文本
const getUrgencyText = (urgency: string) => {
const urgencyMap: Record<string, string> = {
'low': "普通",
'normal': "一般",
'high': "紧急",
'urgent': "特急",
};
return urgencyMap[urgency] || "一般";
};
// 获取审批进度
const getApproverProgress = (item: GwListItem) => {
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;
}
}
}
// 统计抄送人数量 - 处理逗号分隔的字符串或数组
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;
}
}
}
if (spCount === 0 && ccCount === 0) {
return "无审批人和抄送人";
}
let result = "";
if (spCount > 0) {
result += `${spCount}个审批人`;
}
if (ccCount > 0) {
if (result) result += "";
result += `${ccCount}个抄送人`;
}
return result;
};
// 格式化时间
const formatTime = (time: string | Date | undefined) => {
if (!time) return '暂无';
return dayjs(time).format("MM-DD HH:mm");
};
// 监听数据变化
watch(dataList, (val) => {
// 数据变化监听
});
// 页面显示时重新加载数据
onShow(() => {
reload();
});
// 页面加载时也加载一次数据
onMounted(() => {
reload();
});
</script>
<style lang="scss" scoped>
.gw-list-page {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f7fa;
}
.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;
}
}
.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 {
display: flex;
justify-content: space-between;
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 {
display: flex;
flex-direction: column;
gap: 8px;
}
.info-item {
display: flex;
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;
}
}
.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;
}
.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;
}
}
// 加载动画
.gw-card {
animation: fadeInUp 0.3s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>