Files
threeonecheck_web/pages/index/index.vue

1215 lines
34 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="content">
<!-- 顶部背景区域 -->
<view class="header-wrapper">
<!-- 渐变图片叠加在蓝色底色上 -->
<image class="header-bg-image" src="/static/home_icon/jianbianbeijing.png" mode="aspectFill"></image>
<!-- 自定义导航栏 -->
<u-navbar
title="湘西州“三个一”安全管理平台"
:placeholder="false"
:fixed="false"
:safeAreaInsetTop="true"
bgColor="transparent"
titleColor="#fff"
:border="false"
leftIcon=""
>
</u-navbar>
<!-- 用户信息卡片 -->
<view class="user-card">
<view class="user-avatar">
<image class="avatar-image" :src="getImageUrl(userInfo.avatar) || defaultAvatar" mode="aspectFill"></image>
</view>
<view class="user-info">
<view class="user-dept text-bold">{{ userInfo.deptName || '未知部门' }}</view>
<view class="user-phone">手机号{{ userInfo.phone || '未绑定' }}</view>
<view v-if="displayIdentityName" class="user-identity">当前身份{{ displayIdentityName }}</view>
</view>
<view class="switch-btn" @click="goSwitchIdentity">
<text>切换</text>
<u-icon name="list" color="#285CE9" size="14"></u-icon>
</view>
</view>
</view>
<view class="padding page-content">
<!-- 功能菜单 -->
<view v-if="infoList.length > 0" class="menu-card">
<view class="menu-grid">
<view class="menu-item" v-for="(item, index) in infoList" :key="index" @click="handleMenuClick(item)">
<image class="menu-icon" :src="item.src"></image>
<text class="menu-text">{{ item.name }}</text>
</view>
</view>
</view>
<!-- 我的检查计划 -->
<view v-if="showCheckPlanSection" class="bg-white margin-top radius" style="padding: 40rpx; margin-left: -30rpx; margin-right: -30rpx;">
<view class="flex margin-bottom align-center justify-between">
<view class="text-bold margin-left-xs" style="font-size: 32rpx;">我的检查计划</view>
<!-- <button class="cu-btn round sm line-blue inspect-list-btn" @click="goInspectList">检查列表</button> -->
</view>
<view class="danger-tab-list plan-tab-list">
<view
class="danger-tab-item"
:class="{ 'danger-tab-active': activePlanTab === index }"
v-for="(tab, index) in planTabs"
:key="tab.value"
@click="switchPlanTab(index)"
>{{ tab.label }}</view>
</view>
<!-- 无数据提示 -->
<view v-if="checkPlanData.length === 0" class="text-center text-gray padding">
暂无检查计划
</view>
<!-- 列表渲染 -->
<view class="plan-card margin-bottom" v-for="item in checkPlanData" :key="item.id">
<!-- 蓝色标题栏 -->
<view class="plan-header" @click="togglePlanExpand(item.id)">
<text class="plan-header-title">{{ item.name }}</text>
<view class="plan-toggle-btn" @click.stop="togglePlanExpand(item.id)">
<u-icon
:name="isPlanExpanded(item.id) ? 'arrow-down' : 'arrow-up'"
color="#ffffff"
size="18"
></u-icon>
</view>
</view>
<!-- 内容区域收起时保留标签与计划时间 -->
<view class="plan-body" :class="{ 'plan-body--collapsed': !isPlanExpanded(item.id) }">
<view class="plan-body-summary">
<view class="flex">
<view class="border-border margin-right-xs">{{ item.runModeName }}完成</view>
<view class="border-border">{{ item.cycle }}</view>
</view>
<view class="flex text-gray margin-top">
<view>计划时间</view>
<view style="color: #333333;">{{ formatDate(item.planStartTime) }}{{ formatDate(item.planEndTime) }}</view>
</view>
</view>
<view v-show="isPlanExpanded(item.id)" class="plan-body-detail">
<view class="flex margin-top align-center">
<view style="color: #B5B5B5;">完成进度</view>
<view class="flex align-center margin-left-sm">
<view class="cu-progress round">
<view class="bg-green" :style="{ width: formatProgress(item.progress) + '%' }"></view>
</view>
<text class="margin-left-sm">{{ formatProgress(item.progress) }}%</text>
</view>
</view>
<view class="plan-stats margin-top">
<view class="plan-stat-item">
<view class="plan-stat-num text-orange">{{ item.pendingCount ?? 0 }}</view>
<view class="plan-stat-label">未完成</view>
</view>
<view class="plan-stat-item">
<view class="plan-stat-num text-yellow">{{ item.waitCheckNum ?? 0 }}</view>
<view class="plan-stat-label">待排查</view>
</view>
<view class="plan-stat-item">
<view class="plan-stat-num text-red">{{ item.unusualNum ?? 0 }}</view>
<view class="plan-stat-label">异常数</view>
</view>
<view class="plan-stat-item">
<view class="plan-stat-num text-blue">{{ item.finishedCount ?? 0 }}</view>
<view class="plan-stat-label">已完成</view>
</view>
</view>
<view class="margin-top margin-bottom flex justify-end">
<button class="cu-btn round lg light bg-blue margin-right" @click.stop="goTodayInspect(item)">今日检查</button>
<button class="cu-btn round lg bg-blue" @click.stop="goPlanCheckList(item)">检查清单</button>
</view>
</view>
</view>
</view>
<view
v-if="hasMoreCheckPlans"
class="plan-load-more"
@click="loadMoreCheckPlans"
>
加载更多
</view>
</view>
<!-- 我的隐患排查 -->
<view v-if="!isApprovalRole" class="bg-white margin-top radius" style="padding: 40rpx; margin-left: -40rpx; margin-right: -40rpx;">
<view class="flex margin-bottom">
<!-- <view class="border-tite"></view> -->
<view class="text-bold margin-left-xs" style="font-size: 32rpx;">我的隐患排查</view>
</view>
<!-- Tab 筛选栏 -->
<scroll-view scroll-x class="danger-tab-scroll">
<view class="danger-tab-list">
<view
class="danger-tab-item"
:class="{ 'danger-tab-active': activeDangerTab === index }"
v-for="(tab, index) in dangerTabs"
:key="index"
@click="switchDangerTab(index)"
>{{ tab.label }}</view>
</view>
</scroll-view>
<!-- 无数据提示 -->
<view v-if="hiddenDangerData.length === 0 && !hiddenDangerLoading" class="text-center text-gray padding">
暂无隐患数据
</view>
<!-- 隐患卡片列表 -->
<view class="danger-card margin-top" v-for="(item, index) in hiddenDangerData" :key="item.hazardId">
<!-- 标题行图标+标题 + 等级标签 -->
<view class="flex justify-between align-center">
<view class="flex align-center" style="flex: 1; min-width: 0; margin-right: 24rpx;">
<text class="cuIcon-infofill text-blue" style="font-size: 36rpx; margin-right: 12rpx; flex-shrink: 0;"></text>
<view class="text-bold" style="font-size: 30rpx; word-break: break-all; flex: 1;">{{ item.title }}</view>
</view>
<view class="level-tag" :class="{
'level-minor': item.levelName === '轻微隐患',
'level-normal': item.levelName === '一般隐患',
'level-major': item.levelName === '重大隐患'
}">{{ item.levelName }}</view>
</view>
<!-- 地址 -->
<view class="text-gray margin-top-sm" style="font-size: 26rpx; padding-left: 48rpx;">{{ item.address }}</view>
<!-- 分隔线 -->
<view style="height: 1rpx; background: #EEEEEE; margin: 20rpx 0;"></view>
<!-- 信息行 -->
<view class="danger-info-row">
<text class="text-gray">隐患来源</text>
<text style="color: #333333;">{{ item.source }}</text>
</view>
<view class="danger-info-row">
<text class="text-gray">隐患状态</text>
<text style="color: #333333;">{{ item.statusName }}</text>
</view>
<view class="danger-info-row">
<text class="text-gray">发现时间</text>
<text style="color: #333333;">{{ item.createdAt }}</text>
</view>
<!-- 操作按钮 -->
<view class="flex justify-end danger-card-actions">
<button class="round cu-btn light bg-blue" @click.stop="viewHazardDetail(item)">查看详情</button>
<button v-if="item.statusName === '待整改' && item.canEdit"
class="round cu-btn light bg-blue" @click.stop="assignHazard(item)">隐患交办</button>
<button v-if="item.statusName === '待整改' && item.canEdit"
class="round cu-btn bg-blue" @click.stop="goRectification(item)">立即整改</button>
<button v-if="item.statusName === '待验收' && item.canEdit"
class="round cu-btn light bg-blue" @click.stop="editRectification(item)">编辑整改信息</button>
<button v-if="canShowAcceptanceButton(item)"
class="round cu-btn bg-blue" @click.stop="goAcceptance(item)">立即验收</button>
<button v-if="item.statusName === '待交办'"
class="round cu-btn bg-blue" @click.stop="assignHazard(item)">隐患交办</button>
<button v-if="canShowWriteoffApplyButton(item)"
class="round cu-btn bg-blue" @click.stop="goWriteoffApply(item)">销号申请</button>
<button v-if="canShowWriteoffApprovalButton(item, currentRoleKey)"
class="round cu-btn bg-blue" @click.stop="goWriteoffApproval(item)">销号审批</button>
</view>
</view>
<u-loadmore
v-if="hiddenDangerData.length > 0"
:status="hiddenDangerLoadStatus"
style="margin-top: 20rpx; margin-bottom: 20rpx;"
/>
</view>
<!-- 我的工作台approval 角色专属 -->
<view v-if="isApprovalRole" class="bg-white margin-top radius" style="padding: 40rpx; margin-left: -40rpx; margin-right: -40rpx;">
<view class="flex margin-bottom">
<view class="text-bold margin-left-xs" style="font-size: 32rpx;">我的工作台</view>
</view>
<view class="danger-tab-list workbench-tab-list">
<view
class="danger-tab-item"
:class="{ 'danger-tab-active': activeWorkbenchTab === index }"
v-for="(tab, index) in workbenchTabs"
:key="tab.value"
@click="switchWorkbenchTab(index)"
>{{ tab.label }}</view>
</view>
<view v-if="workbenchLoading" class="text-center text-gray padding">加载中...</view>
<view v-else-if="workbenchList.length === 0" class="text-center text-gray padding">
暂无数据
</view>
<view class="danger-card margin-top" v-for="item in workbenchList" :key="getWorkbenchItemKey(item)">
<view class="flex justify-between align-center">
<view class="flex align-center" style="flex: 1; min-width: 0; margin-right: 24rpx;">
<text class="cuIcon-infofill text-blue" style="font-size: 36rpx; margin-right: 12rpx; flex-shrink: 0;"></text>
<view class="text-bold" style="font-size: 30rpx; word-break: break-all; flex: 1;">{{ item.title }}</view>
</view>
<view class="level-tag" :class="{
'level-minor': item.levelName === '轻微隐患',
'level-normal': item.levelName === '一般隐患',
'level-major': item.levelName === '重大隐患'
}">{{ item.levelName }}</view>
</view>
<view class="text-gray margin-top-sm" style="font-size: 26rpx; padding-left: 48rpx;">{{ item.address }}</view>
<view style="height: 1rpx; background: #EEEEEE; margin: 20rpx 0;"></view>
<view class="danger-info-row">
<text class="text-gray">隐患来源</text>
<text style="color: #333333;">{{ item.source }}</text>
</view>
<view class="danger-info-row">
<text class="text-gray">隐患状态</text>
<text style="color: #333333;">{{ item.statusName }}</text>
</view>
<view class="danger-info-row">
<text class="text-gray">发现时间</text>
<text style="color: #333333;">{{ item.createdAt }}</text>
</view>
<view class="flex justify-end danger-card-actions">
<button class="round cu-btn light bg-blue" @click.stop="viewHazardDetail(item)">查看详情</button>
<button
v-if="activeWorkbenchTab === 0"
class="round cu-btn bg-blue"
@click.stop="goLeaderApproval(item)"
>领导批示</button>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, reactive, computed } from 'vue';
// import { onLoad } from '@dcloudio/uni-app';
import {getCheckPlanList, getHiddenDangerList, getFlowTodoList, getFlowDoneList} from '@/request/api.js'
import { getProfileDetail } from '@/request/three_one_api/info.js';
import { onLoad, onShow, onReachBottom } from '@dcloudio/uni-app';
import { toImageUrl } from '@/request/request.js';
import { applyProfileToUserInfo, applyStoredUserInfo, resolveIdentityName, resolveUserIdentityRoleKey, isGovernmentEntityAdmin } from '@/utils/userInfo.js';
import {
buildRectificationUrl,
buildEditRectificationUrl,
buildAssignmentUrl,
buildAcceptanceUrl,
buildAcceptanceApprovalUrl,
buildWriteoffApplyUrl,
buildWriteoffApprovalUrl,
buildLeaderWriteoffApprovalUrl,
canShowAcceptanceButton,
canShowWriteoffApplyButton,
canShowWriteoffApprovalButton
} from '@/utils/hazardNav.js';
const loading = ref(true);
const defaultAvatar = '/static/my/default_avater.png';
// 用户信息
const userInfo = reactive({
userId: '',
username: '',
nickName: '',
deptId: '',
deptName: '',
role: '',
avatar: '',
phone: '',
identityName: '',
userIdentity: null
});
// 当前身份角色(取 storage 中 userInfo.userIdentity.roleKey
const currentRoleKey = computed(() => resolveUserIdentityRoleKey(userInfo));
const isApprovalRole = computed(() => currentRoleKey.value === 'approval');
const showCheckPlanSection = computed(() => !isApprovalRole.value && currentRoleKey.value !== 'manage');
// 展示用身份名称(兼容接口字段与本地缓存)
const displayIdentityName = computed(() => {
return resolveIdentityName(userInfo, userInfo.userIdentity);
});
const loadUserInfoFromStorage = () => {
try {
const storedUserInfo = uni.getStorageSync('userInfo');
if (storedUserInfo) {
applyStoredUserInfo(userInfo, JSON.parse(storedUserInfo));
}
} catch (storageError) {
console.error('从本地存储获取用户信息失败:', storageError);
}
};
// 获取图片完整URL用于显示
const getImageUrl = (path) => {
if (!path) return '';
return toImageUrl(path);
};
// 获取用户信息(从接口获取)
const getUserInfo = async () => {
try {
const res = await getProfileDetail();
if (res.code === 0 && res.data) {
applyProfileToUserInfo(userInfo, res.data);
}
} catch (e) {
console.error('获取用户信息失败:', e);
loadUserInfoFromStorage();
}
};
// 全部菜单项
const allMenuList = [
{
name: '成员管理',
src: '/static/home_icon/chengyuangaunli.png'
},
{
name: '信息填报',
src: '/static/home_icon/xinxitianbao.png'
},
{
name: '区域设置',
src: '/static/home_icon/quyushezhi.png'
},
{
name: '检查清单',
src: '/static/home_icon/jiachaqingdan.png'
},
// {
// name: '检查记录',
// src: '/static/home_icon/jianchajilu.png'
// },
{
name: '证件管理',
src: '/static/home_icon/zhengjianguanli.png'
},
{
name: '隐患排查',
src: '/static/home_icon/yinhuanpaicha.png'
},
{
name: '隐患销号',
src: '/static/home_icon/yinhuanxiaohao.png'
}
];
// common角色只能看到的菜单名称
const commonMenuNames = ['隐患排查', '隐患销号'];
// 政府类型管理员organizationEntityType=0/1 且 admin/manage隐藏的菜单
const govEntityAdminHiddenMenuNames = ['成员管理', '信息填报', '区域设置', '证件管理'];
// 根据角色与组织实体类型动态展示菜单
const infoList = computed(() => {
let list = allMenuList;
if (currentRoleKey.value === 'approval') {
list = list.filter(item => item.name === '隐患排查');
} else if (currentRoleKey.value === 'common') {
list = list.filter(item => commonMenuNames.includes(item.name));
}
if (isGovernmentEntityAdmin(userInfo)) {
list = list.filter(item => !govEntityAdminHiddenMenuNames.includes(item.name));
}
return list;
});
const goInspectList = () => {
uni.navigateTo({
url: '/pages/Inspectionresult/list'
})
}
const formatProgress = (progress) => {
const num = Number(progress);
if (Number.isNaN(num)) return 0;
return Math.min(100, Math.max(0, num));
}
const getTodayDateStr = () => {
const date = new Date();
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
return `${y}-${m}-${d}`;
};
const goTodayInspect = (item) => {
const oneTableId = item.id;
const taskDate = getTodayDateStr();
const tableId = item.tableId ?? item.id;
if (!oneTableId || !taskDate) {
uni.showToast({ title: '缺少任务参数', icon: 'none' });
return;
}
const name = item.name || '';
const tableIdQuery = tableId ? `&tableId=${tableId}` : '';
uni.navigateTo({
url: `/pages/Inspectionresult/detail?oneTableId=${oneTableId}&taskDate=${encodeURIComponent(taskDate)}&name=${encodeURIComponent(name)}${tableIdQuery}`
});
}
const goPlanCheckList = (item) => {
const tableId = item.tableId ?? item.id;
if (!tableId) {
uni.showToast({ title: '缺少检查表ID', icon: 'none' });
return;
}
uni.navigateTo({
url: `/pages/Inspectionresult/list?tableId=${tableId}&name=${encodeURIComponent(item.name || '')}`
});
}
// 菜单点击跳转
const handleMenuClick = (item) => {
const menuRoutes = {
'成员管理': '/pages/membermanagemen/membermanagemen',
'信息填报': '/pages/corporateInformation/corporateInformation',
'区域设置':'/pages/area/management',
'检查清单' :'/pages/checklist/checklist',
'检查记录': '/pages/Inspectionlog/Inspectionlog',
'证件管理': '/pages/Idphotomanagement/Idphotomanagement',
'隐患排查':'/pages/hiddendanger/Inspection',
'隐患销号':'/pages/closeout/application',
'设备登记':'/pages/equipmentregistration/equipmentregistration',
};
const url = menuRoutes[item.name];
if (url) {
uni.navigateTo({ url });
} else {
uni.showToast({ title: '功能开发中', icon: 'none' });
}
}
//我的检查计划
const PLAN_INITIAL_SIZE = 4;
const planTabs = [
{ label: '全部', value: 0 },
{ label: '未完成', value: 4 },
{ label: '已完成', value: 3 }
];
const activePlanTab = ref(0);
const checkPlanParams = ref({
pageNum: 1,
pageSize: PLAN_INITIAL_SIZE,
name: ''
});
const checkPlanData = ref([]);
const checkPlanTotal = ref(0);
const planShowAll = ref(false);
const expandedPlanMap = ref({});
const buildCheckPlanListParams = (pageSize) => ({
pageNum: 1,
pageSize,
name: checkPlanParams.value.name,
planStatus: planTabs[activePlanTab.value].value
});
const switchPlanTab = (index) => {
if (activePlanTab.value === index) return;
activePlanTab.value = index;
getCheckPlanLists();
};
const hasMoreCheckPlans = computed(() => {
return !planShowAll.value && checkPlanTotal.value > PLAN_INITIAL_SIZE;
});
const initPlanExpandedState = (records = [], { reset = false, expandFirst = false } = {}) => {
const map = reset ? {} : { ...expandedPlanMap.value };
records.forEach((item, index) => {
if (reset) {
map[item.id] = expandFirst && index === 0;
} else if (!(item.id in map)) {
map[item.id] = false;
}
});
expandedPlanMap.value = map;
};
const isPlanExpanded = (id) => !!expandedPlanMap.value[id];
const togglePlanExpand = (id) => {
expandedPlanMap.value = {
...expandedPlanMap.value,
[id]: !expandedPlanMap.value[id]
};
};
const getCheckPlanLists = async () => {
planShowAll.value = false;
try {
const res = await getCheckPlanList(buildCheckPlanListParams(PLAN_INITIAL_SIZE));
if (res.code === 0) {
const records = res.data?.records || [];
checkPlanTotal.value = Number(res.data?.total ?? records.length);
checkPlanData.value = records;
initPlanExpandedState(records, { reset: true, expandFirst: true });
}
} catch (error) {
console.error(error);
} finally {
loading.value = false;
}
};
const loadMoreCheckPlans = async () => {
if (!hasMoreCheckPlans.value) return;
try {
const res = await getCheckPlanList(buildCheckPlanListParams(checkPlanTotal.value));
if (res.code === 0) {
const records = res.data?.records || [];
initPlanExpandedState(records);
checkPlanData.value = records;
planShowAll.value = true;
} else {
uni.showToast({ title: res.msg || '加载失败', icon: 'none' });
}
} catch (error) {
console.error(error);
uni.showToast({ title: '加载失败', icon: 'none' });
}
};
// 格式化日期 (2025-12-18 00:00:00 -> 2025-12-18)
const formatDate = (dateStr) => {
if (!dateStr) return '';
return dateStr.split(' ')[0];
};
// 页面加载时调用接口
// onLoad(() => {
// getUserInfo();
// getCheckPlanLists();
// });
// 页面每次显示时都会加载数据
onShow(() => {
loadUserInfoFromStorage();
getUserInfo().then(() => {
if (isApprovalRole.value) {
fetchWorkbenchList();
} else {
if (showCheckPlanSection.value) {
getCheckPlanLists();
}
resetHiddenDangerList();
fetchHiddenDangerList();
}
});
});
//我的隐患排查
const HAZARD_PAGE_SIZE = 10;
const hiddenDangerPageNum = ref(1);
const hiddenDangerLoading = ref(false);
const hiddenDangerLoadStatus = ref('loadmore');
const hiddenDangerData = ref([]);
// 隐患排查 Tab 筛选
const dangerTabs = ref([
{ label: '全部', value: null },
{ label: '待交办', value: 1 },
{ label: '待整改', value: 2 },
{ label: '待验收', value: 3 },
{ label: '待销号', value: 4 },
{ label: '已完成', value: 5 }
]);
const activeDangerTab = ref(0);
const buildHiddenDangerListParams = () => {
const params = {
pageNum: hiddenDangerPageNum.value,
pageSize: HAZARD_PAGE_SIZE
};
const activeTab = dangerTabs.value[activeDangerTab.value];
if (activeTab?.value != null) {
params.status = activeTab.value;
}
return params;
};
const resetHiddenDangerList = () => {
hiddenDangerPageNum.value = 1;
hiddenDangerData.value = [];
hiddenDangerLoadStatus.value = 'loadmore';
};
// 切换 Tab带 status 重新请求接口
const switchDangerTab = (index) => {
if (activeDangerTab.value === index) return;
activeDangerTab.value = index;
resetHiddenDangerList();
fetchHiddenDangerList();
};
const fetchHiddenDangerList = async () => {
if (hiddenDangerLoading.value) return;
if (hiddenDangerPageNum.value > 1 && hiddenDangerLoadStatus.value === 'nomore') return;
hiddenDangerLoading.value = true;
if (hiddenDangerPageNum.value > 1) {
hiddenDangerLoadStatus.value = 'loading';
}
try {
const res = await getHiddenDangerList(buildHiddenDangerListParams());
if (res.code === 0) {
const records = res.data?.records || [];
const total = Number(res.data?.total ?? 0);
if (hiddenDangerPageNum.value === 1) {
hiddenDangerData.value = records;
} else {
hiddenDangerData.value = [...hiddenDangerData.value, ...records];
}
if (hiddenDangerData.value.length >= total || records.length < HAZARD_PAGE_SIZE) {
hiddenDangerLoadStatus.value = 'nomore';
} else {
hiddenDangerLoadStatus.value = 'loadmore';
}
} else {
if (hiddenDangerPageNum.value === 1) {
hiddenDangerData.value = [];
}
hiddenDangerLoadStatus.value = 'nomore';
}
} catch (error) {
console.error(error);
if (hiddenDangerPageNum.value > 1) {
hiddenDangerPageNum.value--;
}
hiddenDangerLoadStatus.value = 'loadmore';
} finally {
hiddenDangerLoading.value = false;
loading.value = false;
}
};
const loadMoreHiddenDangerList = () => {
if (hiddenDangerLoadStatus.value !== 'loadmore' || hiddenDangerLoading.value) return;
hiddenDangerPageNum.value++;
fetchHiddenDangerList();
};
// 页面加载时调用接口
onLoad(() => {
if (!isApprovalRole.value) {
resetHiddenDangerList();
fetchHiddenDangerList();
}
});
onReachBottom(() => {
if (isApprovalRole.value) return;
loadMoreHiddenDangerList();
});
// ========== 我的工作台approval 角色) ==========
const workbenchTabs = [
{ label: '待办', value: 'todo' },
{ label: '已办', value: 'done' }
];
const activeWorkbenchTab = ref(0);
const workbenchList = ref([]);
const workbenchLoading = ref(false);
const normalizeWorkbenchItem = (item) => {
if (!item) return {};
const hazard = item.hazard || item.hazardInfo || {};
return {
...item,
hazardId: item.hazardId ?? hazard.hazardId ?? '',
assignId: item.assignId ?? hazard.assignId ?? '',
rectifyId: item.rectifyId ?? hazard.rectifyId ?? '',
taskId: item.taskId ?? item.flowTaskId ?? item.currentTaskId ?? '',
taskKey: item.taskKey ?? item.flowTaskKey ?? item.currentTaskKey ?? '',
title: item.title ?? hazard.title ?? item.hazardTitle ?? '',
address: item.address ?? hazard.address ?? '',
source: item.source ?? hazard.source ?? item.hazardSourceName ?? '',
statusName: item.statusName ?? hazard.statusName ?? item.hazardStatusName ?? hazard.hazardStatusName ?? item.taskName ?? '',
hazardStatus: item.hazardStatus ?? hazard.hazardStatus ?? item.status ?? hazard.status ?? '',
createdAt: item.discoveredAt ?? hazard.discoveredAt ?? '',
levelName: item.levelName ?? hazard.levelName ?? ''
};
};
const resolveWorkbenchRecords = (data) => {
if (!data) return [];
if (Array.isArray(data)) return data;
if (Array.isArray(data.records)) return data.records;
if (Array.isArray(data.list)) return data.list;
return [];
};
const getWorkbenchItemKey = (item) => {
const taskId = item.taskId || item.flowTaskId || '';
const hazardId = item.hazardId || '';
if (taskId && hazardId) return `${taskId}-${hazardId}`;
return taskId || hazardId || item.rectifyId || JSON.stringify(item);
};
const switchWorkbenchTab = (index) => {
if (activeWorkbenchTab.value === index) return;
activeWorkbenchTab.value = index;
fetchWorkbenchList();
};
const fetchWorkbenchList = async () => {
workbenchLoading.value = true;
try {
const isTodo = activeWorkbenchTab.value === 0;
const res = isTodo ? await getFlowTodoList() : await getFlowDoneList();
if (res.code === 0) {
workbenchList.value = resolveWorkbenchRecords(res.data).map(normalizeWorkbenchItem);
} else {
workbenchList.value = [];
uni.showToast({ title: res.msg || '获取工作台数据失败', icon: 'none' });
}
} catch (error) {
console.error('获取工作台数据失败:', error);
workbenchList.value = [];
uni.showToast({ title: '获取工作台数据失败', icon: 'none' });
} finally {
workbenchLoading.value = false;
loading.value = false;
}
};
const goLeaderApproval = (item) => {
const hazardStatus = Number(item.hazardStatus);
const statusName = item.statusName || item.hazardStatusName || '';
if (hazardStatus === 4 || statusName === '待销号') {
uni.navigateTo({ url: buildLeaderWriteoffApprovalUrl(item) });
return;
}
if (hazardStatus === 3 || statusName === '待验收') {
uni.navigateTo({ url: buildAcceptanceApprovalUrl(item) });
return;
}
uni.showToast({ title: '当前状态不支持领导审批', icon: 'none' });
};
// ========== 隐患排查相关跳转函数 ==========
// 查看隐患详情(流程链)
const viewHazardDetail = (item) => {
uni.navigateTo({
url: `/pages/hiddendanger/process-chain?hazardId=${item.hazardId}`
})
}
// 立即整改(待整改状态)
const goRectification = (item) => {
uni.navigateTo({ url: buildRectificationUrl(item) });
}
// 编辑整改信息(待验收状态)
const editRectification = (item) => {
uni.navigateTo({ url: buildEditRectificationUrl(item) });
}
// 立即验收
const goAcceptance = (item) => {
uni.navigateTo({ url: buildAcceptanceUrl(item) });
}
// 隐患交办
const assignHazard = (item) => {
uni.navigateTo({ url: buildAssignmentUrl(item) });
}
// 销号申请
const goWriteoffApply = (item) => {
uni.navigateTo({ url: buildWriteoffApplyUrl(item) });
};
// 销号审批
const goWriteoffApproval = (item) => {
uni.navigateTo({ url: buildWriteoffApprovalUrl(item) });
};
// 跳转身份切换页
const goSwitchIdentity = () => {
uni.navigateTo({
url: '/pages/personalcenter/identity'
});
};
</script>
<style lang="scss" scoped>
// ========== 顶部背景区域样式 ==========
.header-wrapper {
position: relative;
padding-bottom: 40rpx;
overflow: hidden;
// 蓝色底色
background-color: #2472EA;
}
// 渐变图片叠加在蓝色底色上
.header-bg-image {
position: absolute;
top: 2rpx; // 往下移动
left: 0;
width: 100%;
height: 100%;
z-index: 0;
opacity: 1.6;
}
// 用户信息卡片
.user-card {
position: relative;
z-index: 10;
display: flex;
align-items: center;
padding: 42rpx 30rpx 54rpx 30rpx; // 上42 右30 下54 左30
.user-avatar {
flex-shrink: 0;
width: 144rpx;
height: 144rpx;
border-radius: 50%;
overflow: hidden;
background: #fff;
}
.user-info {
flex: 1;
margin-left: 24rpx;
overflow: hidden;
max-width: 576rpx;
.user-dept {
font-size: 34rpx;
color: #fff;
font-weight: bold;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.user-phone {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.9);
margin-top: 10rpx;
}
.user-identity {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.85);
margin-top: 8rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.switch-btn {
display: flex;
align-items: center;
background: #fff;
padding: 8rpx 24rpx;
border-radius: 30rpx;
color: #285CE9;
font-size: 26rpx;
flex-shrink: 0;
margin-left: 20rpx;
text {
margin-right: 8rpx;
}
}
}
.page-content {
background: #F4F7FB;
border-radius: 40rpx 40rpx 0rpx 0rpx;
margin-top: -40rpx;
padding: 30rpx;
padding-bottom: 50rpx;
position: relative;
z-index: 10;
min-height: calc(100vh - 400rpx);
}
// ========== 功能菜单区域 ==========
.menu-card {
background: #fff;
border-radius: 40rpx 40rpx 0 0rpx;
padding: 44rpx 30rpx 30rpx 30rpx;
margin-left: -40rpx;
margin-right: -40rpx;
margin-top: -30rpx;
}
.menu-grid {
display: flex;
flex-wrap: wrap;
}
.menu-item {
width: 20%;
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 38rpx;
.menu-icon {
width: 72rpx;
height: 80rpx;
}
.menu-text {
font-size: 24rpx;
color: #333;
margin-top: 14rpx;
text-align: center;
white-space: nowrap;
}
}
// 头像图片样式
.avatar-image {
width: 100%;
height: 100%;
border-radius: 50%;
}
.list-list {
background: #FFFFFF;
box-shadow: 0rpx 2rpx 6rpx 2rpx rgba(0, 0, 0, 0.08);
border-left: 5px solid #2667E9;
border-radius: 20rpx;
padding: 20rpx;
}
// ========== 检查计划卡片 ==========
.inspect-list-btn {
margin: 0;
padding: 0 24rpx;
height: 56rpx;
line-height: 56rpx;
font-size: 24rpx;
flex-shrink: 0;
}
.plan-card {
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0rpx 2rpx 6rpx 2rpx rgba(0, 0, 0, 0.06);
}
.plan-header {
background: linear-gradient(135deg, #4A90E2 0%, #2667E9 100%);
padding: 24rpx 30rpx;
display: flex;
align-items: center;
justify-content: space-between;
.plan-header-icon {
width: 36rpx;
height: 36rpx;
margin-right: 12rpx;
}
.plan-header-title {
flex: 1;
min-width: 0;
padding-right: 16rpx;
color: #fff;
font-size: 30rpx;
font-weight: bold;
}
}
.plan-toggle-btn {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: 48rpx;
height: 48rpx;
}
.plan-body {
padding: 24rpx 30rpx 10rpx 30rpx;
background: #fff;
&--collapsed {
padding-bottom: 24rpx;
}
}
.plan-load-more {
text-align: center;
padding: 24rpx 0 8rpx;
font-size: 28rpx;
color: #2667E9;
}
.plan-stats {
display: flex;
background: #F5F7FA;
border-radius: 12rpx;
border: 1rpx solid #E8ECF0;
overflow: hidden;
.plan-stat-item {
flex: 1;
text-align: center;
padding: 20rpx 0;
border-right: 1rpx solid #E8ECF0;
&:last-child {
border-right: none;
}
}
.plan-stat-num {
font-size: 36rpx;
font-weight: bold;
}
.plan-stat-label {
font-size: 24rpx;
color: #666;
margin-top: 8rpx;
}
}
.border-tite {
width: 10rpx;
height: 32rpx;
background: #2667E9;
border-radius: 10rpx 10rpx 10rpx 10rpx;
}
.cu-progress {
width: 300rpx;
height: 20rpx;
background: #ebeef5;
border-radius: 100rpx;
overflow: hidden;
view {
height: 100%;
border-radius: 100rpx;
transition: width 0.3s ease;
}
}
.bg-green {
background: #2667E9;
}
.border-border {
padding: 10rpx;
background: #EEF3FF;
border-radius: 4rpx 4rpx 4rpx 4rpx;
border: 2rpx solid #AAC5FC;
text-align: center;
justify-content: center;
align-items: center;
display: flex;
font-size: 28rpx;
color: #2667E9;
}
// 隐患等级标签样式
.level-tag {
padding: 4rpx 16rpx;
border-radius: 8rpx;
white-space: nowrap;
flex-shrink: 0;
}
// 轻微隐患
.level-minor {
background: #F6FFED;
border: 2rpx solid #B7EB8F;
color: #52C41A;
}
// 一般隐患
.level-normal {
background: #FFF7E6;
border: 2rpx solid #FFD591;
color: #FA8C16;
}
// 重大隐患
.level-major {
background: #FFF1F0;
border: 2rpx solid #FFA39E;
color: #F5222D;
}
// ========== 隐患排查 Tab 筛选栏 ==========
.danger-tab-scroll {
white-space: nowrap;
margin-bottom: 20rpx;
}
.danger-tab-list {
display: inline-flex;
gap: 42rpx;
margin-left: 10rpx;
}
.workbench-tab-list,
.plan-tab-list {
margin-bottom: 20rpx;
}
.danger-tab-item {
font-size: 28rpx;
color: #666;
padding-bottom: 12rpx;
position: relative;
flex-shrink: 0;
}
.danger-tab-active {
font-weight: bold;
color: #2667E9 !important;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 40rpx;
height: 6rpx;
background: #2667E9;
border-radius: 3rpx;
}
}
// ========== 隐患卡片 ==========
.danger-card {
padding: 30rpx;
margin-bottom: 20rpx;
background: #FFFFFF;
box-shadow: 0rpx 6rpx 12rpx 2rpx #F1F5FE;
border-radius: 16rpx 16rpx 16rpx 16rpx;
border: 2rpx solid #EAECEF;
}
.danger-info-row {
font-size: 28rpx;
margin-top: 16rpx;
display: flex;
.text-gray {
white-space: nowrap;
flex-shrink: 0;
}
}
.danger-card-actions {
margin-top: 20rpx;
gap: 10rpx;
flex-wrap: wrap;
.cu-btn {
padding: 0 24rpx !important;
font-size: 26rpx !important;
height: 60rpx !important;
line-height: 60rpx !important;
white-space: nowrap !important;
flex-shrink: 0 !important;
width: auto !important;
margin: 0 !important;
}
}
</style>