Bladeren bron

Merge branch 'dev' of http://14.103.151.10:8888/product-group-fe/sfy-safety-group/sfy-safety into fix/production-safety

sunqijun 3 weken geleden
bovenliggende
commit
c57d3a19d0
20 gewijzigde bestanden met toevoegingen van 679 en 149 verwijderingen
  1. 10 0
      src/api/emergency-organization/teams.ts
  2. BIN
      src/assets/images/institute-safety/default-propaganda.jpeg
  3. 0 0
      src/assets/images/institute-safety/sfy.jpg
  4. BIN
      src/assets/images/institute-safety/workshop-list.png
  5. 0 0
      src/assets/images/production-safety/index.ts
  6. 1 1
      src/views/emergency/components/OrgChart.vue
  7. 116 14
      src/views/emergency/organization/PageOrganization.vue
  8. 213 0
      src/views/emergency/organization/components/TeamDetailList.vue
  9. 16 1
      src/views/emergency/organization/team-management/store/userTeam.ts
  10. 4 3
      src/views/emergency/organization/team-management/type.ts
  11. 2 2
      src/views/institute-safety/components/CardMapAndAlert.vue
  12. 13 4
      src/views/institute-safety/modules/safety-company-home/CompanyHome.vue
  13. 30 0
      src/views/institute-safety/modules/safety-company-home/apis/index.ts
  14. 5 0
      src/views/institute-safety/modules/safety-company-home/components/ControlTab.vue
  15. 150 0
      src/views/institute-safety/modules/safety-company-home/components/WorkshopList.vue
  16. 0 0
      src/views/institute-safety/modules/safety-company-home/constants.ts
  17. 1 2
      src/views/institute-safety/modules/safety-company-home/hooks/use-violation-notice-company.ts
  18. 118 0
      src/views/institute-safety/modules/safety-propaganda/SafetyPropaganda.vue
  19. 0 105
      src/views/institute-safety/modules/safety-workshop-list/WorkshopList.vue
  20. 0 17
      src/views/institute-safety/modules/safety-workshop-list/apis/index.ts

+ 10 - 0
src/api/emergency-organization/teams.ts

@@ -22,6 +22,16 @@ export function queryEmergencyTeamDetail(teamId: number) {
   });
 }
 
+/**
+ * 查询所有队伍详细信息
+ */
+export function queryAllEmergencyTeamDetail() {
+  return http.request<TeamAndPersonInfoType[]>({
+    url: '/emergencySystem/queryAllEmergencyTeamDetail',
+    method: 'get',
+  });
+}
+
 export type SetTeamInfoType = {
   id: number;
   memberCount?: number;

BIN
src/assets/images/institute-safety/default-propaganda.jpeg


src/assets/images/sfy.jpg → src/assets/images/institute-safety/sfy.jpg


BIN
src/assets/images/institute-safety/workshop-list.png


+ 0 - 0
src/assets/images/production-safety/index.ts


+ 1 - 1
src/views/emergency/components/OrgChart.vue

@@ -102,7 +102,7 @@
         animation: true, // 启用布局动画
       },
       autoFit: {
-        type: 'center', // 自适应类型:'view' 或 'center'
+        type: 'view', // 自适应类型:'view' 或 'center'
         // options: {
         //   // 仅适用于 'view' 类型
         //   when: 'always', // 何时适配:'overflow'(仅当内容溢出时) 或 'always'(总是适配)

+ 116 - 14
src/views/emergency/organization/PageOrganization.vue

@@ -3,25 +3,47 @@
     <div class="safety-platform-container__header">
       <div class="breadcrumb-title"> 应急架构体系 </div>
     </div>
-    <div class="safety-platform-container__main">
-      <OrgChart :treeData="treeData" @node-click="handleNodeClick" v-if="treeData.id !== '-1'" />
-      <div v-else class="no-data">暂无队伍</div>
+    <div class="safety-platform-container__main" :class="{ 'zoom-mode': isChartZoomed }">
+      <div class="chart-container" ref="chartContainerRef" v-loading="loadingTeams">
+        <OrgChart
+          :treeData="treeData"
+          @node-click="handleNodeClick"
+          @canvas-click="handleCanvasClick"
+          v-if="treeData.id !== '-1'"
+        />
+        <div class="no-data" v-else>暂无队伍</div>
+        <div class="chart-actions">
+          <el-button v-if="!isChartFullscreen" size="small" @click="toggleChartZoom">
+            {{ isChartZoomed ? '恢复布局' : '放大模式' }}
+          </el-button>
+          <el-button size="small" @click="toggleChartFullscreen">
+            {{ isChartFullscreen ? '退出全屏' : '全屏查看' }}
+          </el-button>
+        </div>
+      </div>
+      <div class="detail-container" v-loading="loadingTeamsDetail">
+        <TeamDetailList :selected-team-id="selectedTeamId" class="team-detail" />
+      </div>
     </div>
     <div class="safety-platform-container__footer" v-if="showOperationBar">
       <el-button @click="router.push('team-management')"> 编辑 </el-button>
     </div>
   </div>
-  <TeamDetailDrawer ref="teamDetailDrawerRef" :selected-team-id="selectedTeamId" />
 </template>
 
 <script setup lang="ts">
-  import { ref, onMounted } from 'vue';
+  import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
   import { useRouter } from 'vue-router';
-  import OrgChart from '../components/OrgChart.vue';
-  import TeamDetailDrawer from './components/TeamDetailDrawer.vue';
+  import { storeToRefs } from 'pinia';
   import { useUserInfoHook } from '@/views/disaster/hooks';
   import useTeamStore from './team-management/store/userTeam';
   import { EMERGENCY_PERMISSIONS } from '@/views/emergency/constant';
+  import OrgChart from '../components/OrgChart.vue';
+  import TeamDetailList from './components/TeamDetailList.vue';
+
+  const { permissions } = useUserInfoHook();
+  const { loadingTeams, loadingTeamsDetail } = storeToRefs(useTeamStore());
+  const { getLeaderTeams } = useTeamStore();
 
   type OrganizationTreeType = {
     id: string;
@@ -51,20 +73,49 @@
   const router = useRouter();
 
   const showOperationBar = ref(false);
-
-  const { permissions } = useUserInfoHook();
-  const { getLeaderTeams } = useTeamStore();
-
-  const teamDetailDrawerRef = ref<InstanceType<typeof TeamDetailDrawer>>();
+  const chartContainerRef = ref<HTMLElement | null>(null);
+  const isChartFullscreen = ref(false);
+  const isChartZoomed = ref(false);
 
   const selectedTeamId = ref<number | null>(null);
 
   const handleNodeClick = (nodeData: any) => {
     selectedTeamId.value = Number(nodeData.id);
+  };
 
-    teamDetailDrawerRef.value?.drawerShow();
+  const handleCanvasClick = () => {
+    selectedTeamId.value = null;
   };
 
+  async function toggleChartFullscreen() {
+    const el = chartContainerRef.value;
+    if (!el) return;
+
+    if (document.fullscreenElement === el) {
+      await document.exitFullscreen();
+      await triggerChartResize();
+      return;
+    }
+
+    await el.requestFullscreen();
+    await triggerChartResize();
+  }
+
+  async function toggleChartZoom() {
+    isChartZoomed.value = !isChartZoomed.value;
+    await triggerChartResize();
+  }
+
+  function syncFullscreenState() {
+    isChartFullscreen.value = document.fullscreenElement === chartContainerRef.value;
+  }
+
+  async function triggerChartResize() {
+    await nextTick();
+    // G6 组件内部监听 window resize,这里主动触发一次确保尺寸立即更新
+    window.dispatchEvent(new Event('resize'));
+  }
+
   function convertData(leaderTeams): OrganizationTreeType {
     return {
       id: leaderTeams.teamId.toString(),
@@ -76,19 +127,70 @@
   }
 
   onMounted(async () => {
+    document.addEventListener('fullscreenchange', syncFullscreenState);
+
     showOperationBar.value = Boolean(
       permissions.find((item: { code: string }) => item.code === EMERGENCY_PERMISSIONS.ORGANIZATION_MANAGEMENT),
     );
 
     const res = await getLeaderTeams();
-
     treeData.value = convertData(res);
   });
+
+  onBeforeUnmount(() => {
+    document.removeEventListener('fullscreenchange', syncFullscreenState);
+  });
 </script>
 
 <style lang="scss" scoped>
   @use '@/styles/page-details-layout.scss' as *;
 
+  .chart-container {
+    position: relative;
+    width: 100%;
+    height: 40%;
+    box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
+    border-radius: 8px;
+  }
+
+  .chart-container:fullscreen {
+    height: 100%;
+    padding: 12px;
+    background: #ffffff;
+    border-radius: 0;
+  }
+
+  .chart-actions {
+    position: absolute;
+    display: flex;
+    gap: 8px;
+    right: 12px;
+    bottom: 12px;
+    z-index: 2;
+  }
+
+  .safety-platform-container__main.zoom-mode {
+    .chart-container {
+      height: 75%;
+    }
+
+    .detail-container {
+      height: 25%;
+    }
+  }
+
+  .detail-container {
+    width: 100%;
+    height: 60%;
+    padding-top: 20px;
+    overflow: auto;
+
+    .team-detail {
+      width: 60%;
+      margin: 0 auto;
+    }
+  }
+
   .no-data {
     width: 100%;
     height: 100%;

+ 213 - 0
src/views/emergency/organization/components/TeamDetailList.vue

@@ -0,0 +1,213 @@
+<template>
+  <div class="team-detail-list">
+    <template v-if="displayTeams.length">
+      <section v-for="team in displayTeams" :key="team.teamId" class="team-detail-list__section">
+        <header class="team-detail-list__header">
+          <h2 class="team-detail-list__title">{{ team.teamName }}</h2>
+          <span class="team-detail-list__count">共{{ team.memberCount ?? 0 }}人</span>
+        </header>
+
+        <div
+          v-for="row in roleRowsForTeam(team.personnelList)"
+          :key="`${team.teamId}-${row.positionLevel}`"
+          class="team-detail-list__role-row"
+        >
+          <span class="team-detail-list__role-pill">{{ row.title }}</span>
+          <div class="team-detail-list__names">
+            <el-tooltip v-for="person in row.members" :key="person.id" placement="top" effect="light" :show-after="200">
+              <template #content>
+                <div class="team-detail-list__tooltip">
+                  <div>部门:{{ person.department || '—' }}</div>
+                  <div>工号:{{ person.staffNo || '—' }}</div>
+                  <div>手机:{{ person.mobile || '—' }}</div>
+                </div>
+              </template>
+              <span class="team-detail-list__name">{{ person.realname }}</span>
+            </el-tooltip>
+          </div>
+        </div>
+
+        <div v-if="team.description?.trim()" class="team-detail-list__duty">
+          <h3 class="team-detail-list__duty-title">队伍职责</h3>
+          <div class="team-detail-list__duty-content">{{ team.description }}</div>
+        </div>
+      </section>
+    </template>
+    <div v-else class="team-detail-list__empty">暂无队伍详情</div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { computed, ref, onMounted } from 'vue';
+  import useTeamStore from '../team-management/store/userTeam';
+  import type { TeamAndPersonInfoType, TeamPersonnelInfoType } from '../team-management/type';
+
+  const props = defineProps<{
+    selectedTeamId: number | null;
+  }>();
+
+  type RoleRow = {
+    positionLevel: number;
+    title: string;
+    members: TeamPersonnelInfoType[];
+  };
+
+  const teamStore = useTeamStore();
+  const teams = ref<TeamAndPersonInfoType[]>([]);
+  const displayTeams = computed(() => {
+    if (props.selectedTeamId === null) return teams.value;
+    return teams.value.filter((team) => team.teamId === props.selectedTeamId);
+  });
+
+  function roleRowsForTeam(personnelList: TeamPersonnelInfoType[] | undefined): RoleRow[] {
+    if (!personnelList?.length) return [];
+    const levelMap = new Map<number, RoleRow>();
+    for (const person of personnelList) {
+      const level = person.positionLevel;
+      if (!levelMap.has(level)) {
+        levelMap.set(level, {
+          positionLevel: level,
+          title: person.title || '成员',
+          members: [],
+        });
+      }
+      levelMap.get(level)!.members.push(person);
+    }
+    return [...levelMap.values()].sort((a, b) => a.positionLevel - b.positionLevel);
+  }
+
+  onMounted(async () => {
+    teams.value = await teamStore.getAllEmergencyTeamDetail();
+  });
+</script>
+
+<style scoped lang="scss">
+  $primary: #1777ff;
+  $text: #333333;
+  $text-muted: #666666;
+
+  .team-detail-list {
+    padding: 0 10px;
+  }
+
+  .team-detail-list__section {
+    padding: 18px 20px 20px;
+    background: #ffffff;
+    border: 1px solid #e8eef8;
+    border-left: 4px solid $primary;
+    border-radius: 10px;
+    box-shadow: 0 4px 14px rgba(23, 119, 255, 0.06);
+
+    & + & {
+      margin-top: 18px;
+    }
+  }
+
+  .team-detail-list__header {
+    display: flex;
+    align-items: baseline;
+    justify-content: space-between;
+    gap: 16px;
+    margin: -18px -20px 16px;
+    padding: 12px 20px;
+    background: linear-gradient(90deg, rgba(23, 119, 255, 0.08), rgba(23, 119, 255, 0.02));
+    border-bottom: 1px solid #edf2fb;
+    border-radius: 10px 10px 0 0;
+
+    .team-detail-list__title {
+      margin: 0;
+      font-size: 18px;
+      font-weight: 600;
+      color: $text;
+    }
+
+    .team-detail-list__count {
+      flex-shrink: 0;
+      font-size: 14px;
+      color: $primary;
+    }
+  }
+
+  .team-detail-list__role-row {
+    display: flex;
+    align-items: flex-start;
+    gap: 16px;
+    margin-bottom: 14px;
+  }
+
+  .team-detail-list__role-pill {
+    flex-shrink: 0;
+    min-width: 56px;
+    padding: 4px 14px;
+    font-size: 13px;
+    color: $primary;
+    text-align: center;
+    background: #ffffff;
+    border: 1px solid $primary;
+    border-radius: 999px;
+  }
+
+  .team-detail-list__names {
+    display: flex;
+    flex-wrap: wrap;
+    align-items: center;
+    gap: 12px 24px;
+    padding-top: 2px;
+    line-height: 1.5;
+  }
+
+  .team-detail-list__name {
+    cursor: default;
+    font-size: 14px;
+    color: $text-muted;
+    border-bottom: 1px dashed transparent;
+
+    &:hover {
+      color: $primary;
+      border-bottom-color: rgba($primary, 0.35);
+    }
+  }
+
+  .team-detail-list__tooltip {
+    padding: 2px 0;
+    font-size: 13px;
+    line-height: 1.6;
+    color: $text-muted;
+
+    div + div {
+      margin-top: 4px;
+    }
+  }
+
+  :deep(.el-tooltip__trigger) {
+    display: inline-block;
+  }
+
+  .team-detail-list__duty {
+    margin-top: 22px;
+    padding-top: 14px;
+    border-top: 1px dashed #dbe7fb;
+  }
+
+  .team-detail-list__duty-title {
+    margin: 0 0 10px;
+    font-size: 15px;
+    font-weight: 600;
+    color: $text;
+  }
+
+  .team-detail-list__duty-content {
+    margin: 0;
+    font-size: 14px;
+    line-height: 1.7;
+    color: $text-muted;
+    white-space: pre-wrap;
+  }
+
+  .team-detail-list__empty {
+    padding: 48px 0;
+    text-align: center;
+    font-size: 14px;
+    color: #999999;
+  }
+</style>

+ 16 - 1
src/views/emergency/organization/team-management/store/userTeam.ts

@@ -1,7 +1,11 @@
 import { ref } from 'vue';
 import { defineStore } from 'pinia';
 import { LeaderTeamType, TeamAndPersonInfoType } from '../type';
-import { queryLeaderTeam, queryEmergencyTeamDetail } from '@/api/emergency-organization/teams';
+import {
+  queryLeaderTeam,
+  queryEmergencyTeamDetail,
+  queryAllEmergencyTeamDetail,
+} from '@/api/emergency-organization/teams';
 import { PositionType, saveTeamPosition } from '@/api/emergency-organization/teams';
 import {
   SetTeamInfoType,
@@ -23,9 +27,18 @@ const useTeamStore = defineStore('useTeam', () => {
   const curTeam = ref<LeaderTeamType>();
   const teamAndPersonInfo = ref<TeamAndPersonInfoType>(); // 右侧的队伍信息和人员信息表格数据
 
+  const loadingTeamsDetail = ref(false);
   const loadingTeams = ref(false);
   const loadingTeamInfo = ref(false);
 
+  // 获取所有队伍详细信息
+  async function getAllEmergencyTeamDetail() {
+    loadingTeamsDetail.value = true;
+    const res = await queryAllEmergencyTeamDetail();
+    loadingTeamsDetail.value = false;
+    return res ?? [];
+  }
+
   async function getLeaderTeams() {
     loadingTeams.value = true;
 
@@ -152,8 +165,10 @@ const useTeamStore = defineStore('useTeam', () => {
     leaderTeamTree,
     curTeam,
     teamAndPersonInfo,
+    loadingTeamsDetail,
     loadingTeams,
     loadingTeamInfo,
+    getAllEmergencyTeamDetail,
     getLeaderTeams,
     createFirstLevelTeam,
     createTeam,

+ 4 - 3
src/views/emergency/organization/team-management/type.ts

@@ -18,14 +18,15 @@ export type TeamInfoType = {
 };
 
 export type TeamPersonnelInfoType = {
-  teamId: number;
+  id: number;
   userId: number;
   realname: string;
+  teamId: number;
   positionId: number; //	职位ID
-  positionLevel: number; //	职位等级
   jobTitle: string; //职务
-  title: string; //  岗位职责
   staffNo: string;
   department: string;
   mobile: string;
+  title: string; // 职位名称
+  positionLevel: number; //	职位等级
 };

+ 2 - 2
src/views/institute-safety/components/CardMapAndAlert.vue

@@ -1,13 +1,13 @@
 <template>
   <div class="map-alert">
     <CompanyHome />
-    <WorkshopList />
+    <SafetyPropaganda />
   </div>
 </template>
 
 <script setup lang="ts">
   import CompanyHome from '@/views/institute-safety/modules/safety-company-home/CompanyHome.vue';
-  import WorkshopList from '@/views/institute-safety/modules/safety-workshop-list/WorkshopList.vue';
+  import SafetyPropaganda from '@/views/institute-safety/modules/safety-propaganda/SafetyPropaganda.vue';
 </script>
 
 <style scoped>

+ 13 - 4
src/views/institute-safety/modules/safety-company-home/CompanyHome.vue

@@ -1,10 +1,15 @@
 <template>
   <div class="company-home" id="company-home">
     <RealtimeSurveillance v-if="curCamera?.code" />
-    <img v-else style="width: 100%" src="@/assets/images/sfy.jpg" alt="" />
+    <img v-else style="width: 100%" src="@/assets/images/institute-safety/sfy.jpg" alt="" />
     <CompanyRating v-if="!curCamera?.code" />
-    <ControlTab @open-surveillance-list="showSurveillanceList = true" @open-question-list="handleOpenQuestionList" />
+    <ControlTab
+      @open-surveillance-list="showSurveillanceList = true"
+      @open-workshop-list="showWorkshopList = true"
+      @open-question-list="handleOpenQuestionList"
+    />
     <SurveillanceList v-if="showSurveillanceList" @close="showSurveillanceList = false" />
+    <WorkshopList v-if="showWorkshopList" @close="showWorkshopList = false" />
 
     <QuestionList v-if="showQuestionList" @close="handleQuestionListClose" />
   </div>
@@ -13,8 +18,10 @@
 <script setup lang="ts">
   import CompanyRating from './components/CompanyRating.vue';
   import ControlTab from './components/ControlTab.vue';
-  import SurveillanceList from './components/SurveillanceList.vue';
   import RealtimeSurveillance from './components/RealtimeSurveillance.vue';
+  import SurveillanceList from './components/SurveillanceList.vue';
+  import WorkshopList from './components/WorkshopList.vue';
+
   import QuestionList from '../safety-question-list/QuestionList.vue';
 
   import useQuestionListStore from '@/views/institute-safety/modules/safety-company-home/stores/use-question-list';
@@ -23,9 +30,10 @@
   import useCameraStore from './stores/use-camera-store';
   import { nextTick, onUnmounted, ref } from 'vue';
   import { storeToRefs } from 'pinia';
-  import { WORKSHOP_INFOS } from '../safety-workshop-list/constants';
+  import { WORKSHOP_INFOS } from './constants';
 
   const showSurveillanceList = ref(false);
+  const showWorkshopList = ref(false);
 
   useViolationNoticeCompany();
 
@@ -51,6 +59,7 @@
       questionListStore.openList();
     });
   }
+
   function handleQuestionListClose() {
     questionListStore.closeList();
   }

+ 30 - 0
src/views/institute-safety/modules/safety-company-home/apis/index.ts

@@ -43,3 +43,33 @@ export function updateReadIssueId(issueId: number) {
     method: 'post',
   });
 }
+
+// 查询车间今日是否有异常状态
+export function getWorkshopTodayExceptionStatus(list: string[]) {
+  return http.request<
+    {
+      workshopCode: string;
+      exceptionStatus: boolean;
+    }[]
+  >({
+    url: '/issue/queryWorkshopTodayExceptionStatus',
+    method: 'post',
+    data: {
+      workshopCodeList: list,
+    },
+  });
+}
+
+export function getPaprogandaData() {
+  return http.request<
+    {
+      id: number;
+      imageUrls: string | null;
+      materialName: string;
+      createdAt: string;
+    }[]
+  >({
+    url: '/safetypublicitybulletinboard/field_external',
+    method: 'get',
+  });
+}

+ 5 - 0
src/views/institute-safety/modules/safety-company-home/components/ControlTab.vue

@@ -17,6 +17,10 @@
         <img src="@/assets/images/institute-safety/alert-white.png" alt="" />
         <div class="tab-text">今日异常告警</div>
       </div>
+      <div class="tab-button" @click="emit('open-workshop-list')">
+        <img src="@/assets/images/institute-safety/workshop-list.png" alt="" />
+        <div class="tab-text">监控区域列表</div>
+      </div>
     </div>
   </div>
 </template>
@@ -26,6 +30,7 @@
 
   const emit = defineEmits<{
     (e: 'open-surveillance-list'): void;
+    (e: 'open-workshop-list'): void;
     (e: 'open-question-list'): void;
   }>();
 

+ 150 - 0
src/views/institute-safety/modules/safety-company-home/components/WorkshopList.vue

@@ -0,0 +1,150 @@
+<template>
+  <div class="workshop-list">
+    <header class="list-header">
+      <div style="width: 16px"></div>
+      <span> 监控区域列表 </span>
+      <img class="close-btn" src="@/assets/images/institute-safety/close.png" alt="" @click="emits('close')" />
+    </header>
+    <main class="workshop-list-main">
+      <div class="workshop-item" v-for="item in staticWorkshopList" :key="item.id">
+        <span class="item-id">{{ item.id }}</span>
+
+        <el-tooltip placement="top" :content="item.name" :hide-after="0">
+          <span class="item-name"> {{ item.name }} </span>
+        </el-tooltip>
+        <img
+          class="item-icon"
+          v-if="item.status"
+          src="@/assets/images/institute-safety/alert.png"
+          alt=""
+          @click="handleOpenQuestionList(item.workshopCode)"
+        />
+      </div>
+    </main>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { nextTick, onMounted, ref } from 'vue';
+  import { ElTooltip } from 'element-plus';
+  import { getWorkshopTodayExceptionStatus } from '../apis';
+  import { WORKSHOP_INFOS } from '../constants';
+
+  import useQuestionListStore from '@/views/institute-safety/modules/safety-company-home/stores/use-question-list';
+
+  const emits = defineEmits<{
+    (e: 'close'): void;
+  }>();
+
+  const questionListStore = useQuestionListStore();
+
+  const staticWorkshopList = ref(WORKSHOP_INFOS);
+
+  function handleOpenQuestionList(code: string) {
+    questionListStore.closeList();
+    questionListStore.clearStore();
+    nextTick(() => {
+      questionListStore.setState({
+        type: 'workshop',
+        workshopCodes: [code],
+      });
+      questionListStore.openList();
+    });
+  }
+
+  onMounted(() => {
+    getWorkshopTodayExceptionStatus(staticWorkshopList.value.map((item) => item.workshopCode)).then((res) => {
+      staticWorkshopList.value.forEach((item) => {
+        item.status = res.find((x) => x.workshopCode === item.workshopCode)!.exceptionStatus;
+      });
+    });
+  });
+</script>
+
+<style scoped>
+  .workshop-list {
+    width: 280px;
+    height: 675px;
+
+    position: absolute;
+    top: 0px;
+    right: 0px;
+
+    background: rgba(0, 0, 0, 0.5);
+    border-radius: 12px 4px 4px 12px;
+    backdrop-filter: blur(10px);
+  }
+
+  .list-header {
+    margin: 12px 0;
+    padding: 0 16px;
+
+    font-weight: 500;
+    font-size: 14px;
+    color: #ffffff;
+
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+  .config-btn,
+  .close-btn {
+    cursor: pointer;
+  }
+  .workshop-list-main {
+    padding: 0 16px;
+    height: 623px;
+    overflow: auto;
+  }
+
+  .workshop-item {
+    display: flex;
+    align-items: center;
+    justify-content: flex-start;
+    margin-top: 20px;
+  }
+  .item-id {
+    font-family: DIN, DIN;
+    font-weight: bold;
+    font-size: 13px;
+    color: #ffffff;
+    width: 20px;
+    height: 20px;
+    background: rgba(0, 0, 0, 0.4);
+    border-radius: 50%;
+    text-align: center;
+  }
+
+  .item-name {
+    margin-left: 8px;
+    max-width: 183px;
+    height: 28px;
+    line-height: 28px;
+    border-radius: 4px;
+
+    padding: 0 10px;
+
+    white-space: nowrap; /* 防止文本换行 */
+    overflow: hidden; /* 隐藏溢出的文本 */
+    text-overflow: ellipsis; /* 显示省略号 */
+
+    font-weight: 400;
+    font-size: 14px;
+    color: #ffffff;
+  }
+
+  .item-icon {
+    margin-right: 8px;
+    margin-left: auto;
+    cursor: pointer;
+    width: 20px;
+    height: 20px;
+  }
+
+  .camera-name-text {
+    max-width: 200px;
+    white-space: nowrap; /* 防止文本换行 */
+    overflow: hidden; /* 隐藏溢出的文本 */
+    text-overflow: ellipsis; /* 显示省略号 */
+  }
+</style>

src/views/institute-safety/modules/safety-workshop-list/constants.ts → src/views/institute-safety/modules/safety-company-home/constants.ts


+ 1 - 2
src/views/institute-safety/modules/safety-company-home/hooks/use-violation-notice-company.ts

@@ -1,12 +1,11 @@
 // 报警消息实时提醒
-
 import { getNewIssueList, updateReadIssueId } from '../apis';
 import useViolationNoticeStore, { getPlace, emitter } from '../stores/use-violation-notice-store';
 import { onUnmounted, onMounted, nextTick } from 'vue';
 import dayjs from 'dayjs';
 import { push } from 'notivue';
 import useQuestionListStore from '@/views/institute-safety/modules/safety-company-home/stores/use-question-list';
-import { WORKSHOP_INFOS } from '../../safety-workshop-list/constants';
+import { WORKSHOP_INFOS } from '../constants';
 
 const questionListStore = useQuestionListStore();
 const useViolationRealtimeCompany = () => {

+ 118 - 0
src/views/institute-safety/modules/safety-propaganda/SafetyPropaganda.vue

@@ -0,0 +1,118 @@
+<template>
+  <div class="safety-propaganda-container">
+    <div class="title">
+      <span class="line"></span>
+      <span class="text">安全宣传栏</span>
+    </div>
+    <div class="safety-propaganda">
+      <div
+        class="item"
+        v-for="item in data"
+        :key="item.id"
+        @click="
+          router.push({
+            name: 'safetyPublicityBoardManagementItem',
+            query: { id: item.id, operate: 'safety-publicity-board-view' },
+          })
+        "
+      >
+        <img v-if="item.imageUrls[0]?.url" class="propaganda-img" :src="item.imageUrls[0].url" alt="" />
+        <img v-else class="propaganda-img" src="@/assets/images/institute-safety/default-propaganda.jpeg" alt="" />
+        <div class="category-name">{{ item.materialName }}</div>
+        <div class="propaganda-time">{{ item.createdAt }}</div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, onMounted } from 'vue';
+  import { useRouter } from 'vue-router';
+  import { getPaprogandaData } from '../safety-company-home/apis';
+
+  const router = useRouter();
+  const data = ref();
+
+  onMounted(() => {
+    getPaprogandaData().then((res) => {
+      data.value = res || [];
+      data.value.forEach((item) => {
+        item.imageUrls = JSON.parse(item.imageUrls || '[]');
+      });
+    });
+  });
+</script>
+
+<style scoped>
+  .safety-propaganda-container {
+    font-family: PingFangSC, PingFang SC;
+
+    margin-top: 20px;
+  }
+
+  .safety-propaganda {
+    display: grid;
+    grid-template-columns: repeat(5, 1fr);
+    gap: 12px;
+  }
+  .item {
+    /* width: 228px; */
+    height: 224px;
+    background: #f0f2f6;
+    border-radius: 8px;
+    cursor: pointer;
+  }
+  .propaganda-img {
+    width: 100%;
+    height: 140px;
+    object-fit: cover;
+    border-radius: 8px 8px 0 0;
+  }
+  .category-name {
+    margin-left: 12px;
+    margin-top: 10px;
+    font-weight: 500;
+    font-size: 14px;
+    color: #333333;
+
+    /* 设置最大宽度(你可以随意改) */
+    max-width: 204px;
+    height: 40px;
+
+    /* 核心:多行文本溢出省略 */
+    display: -webkit-box;
+    -webkit-line-clamp: 2; /* 最多显示 2 行 */
+    -webkit-box-orient: vertical;
+    overflow: hidden;
+    text-overflow: ellipsis;
+
+    /* 可选:文字换行 */
+    word-break: break-all;
+  }
+  .propaganda-time {
+    margin-left: 12px;
+    margin-top: 5px;
+    font-weight: 400;
+    font-size: 12px;
+    color: #999999;
+  }
+
+  .title {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    margin-bottom: 16px;
+  }
+  .line {
+    width: 3px;
+    height: 16px;
+    background: #1777ff;
+  }
+
+  .text {
+    font-weight: 500;
+    font-size: 16px;
+    color: #000000;
+    line-height: 22px;
+  }
+</style>

+ 0 - 105
src/views/institute-safety/modules/safety-workshop-list/WorkshopList.vue

@@ -1,105 +0,0 @@
-<template>
-  <div class="workshop-list">
-    <div class="workshop-list-item" v-for="item in staticWorkshopList" :key="item.id">
-      <div class="workshop-list-item-id">{{ item.id }}</div>
-      <el-tooltip placement="top" :content="item.name">
-        <div class="workshop-list-item-name">{{ item.name }}</div>
-      </el-tooltip>
-      <img
-        class="workshop-list-item-icon"
-        v-if="item.status"
-        src="@/assets/images/institute-safety/alert.png"
-        alt=""
-        @click="handleOpenQuestionList(item.workshopCode)"
-      />
-    </div>
-  </div>
-</template>
-
-<script setup lang="ts">
-  import { nextTick, onMounted, ref } from 'vue';
-  import { ElTooltip } from 'element-plus';
-  import { getWorkshopTodayExceptionStatus } from './apis';
-  import { WORKSHOP_INFOS } from './constants';
-
-  import useQuestionListStore from '@/views/institute-safety/modules/safety-company-home/stores/use-question-list';
-  // import { storeToRefs } from 'pinia';
-
-  const questionListStore = useQuestionListStore();
-  // const { showQuestionList } = storeToRefs(questionListStore);
-
-  const staticWorkshopList = ref(WORKSHOP_INFOS);
-
-  function handleOpenQuestionList(code: string) {
-    questionListStore.closeList();
-    questionListStore.clearStore();
-    nextTick(() => {
-      questionListStore.setState({
-        type: 'workshop',
-        workshopCodes: [code],
-      });
-      questionListStore.openList();
-    });
-  }
-
-  onMounted(() => {
-    getWorkshopTodayExceptionStatus(staticWorkshopList.value.map((item) => item.workshopCode)).then((res) => {
-      staticWorkshopList.value.forEach((item) => {
-        item.status = res.find((x) => x.workshopCode === item.workshopCode)!.exceptionStatus;
-      });
-    });
-  });
-</script>
-
-<style scoped>
-  .workshop-list {
-    margin-top: 8px;
-    display: grid;
-    grid-template-columns: repeat(auto-fill, 200px);
-  }
-  .workshop-list-item {
-    display: flex;
-    flex-direction: row;
-    align-items: center;
-    justify-content: flex-start;
-    width: 200px;
-    height: 40px;
-    border: 1px solid #e8e8e8;
-  }
-  .workshop-list-item:nth-child(n + 12) {
-    border: 1px solid rgba(232, 232, 232, 0.6);
-  }
-  .workshop-list-item:nth-child(n + 30) {
-    border: 1px solid rgba(232, 232, 232, 0.3);
-  }
-
-  .workshop-list-item-id {
-    margin: 0 10px;
-    width: 20px;
-    height: 20px;
-    border-radius: 50%;
-    background: #e8e8e8;
-    font-size: 13px;
-    color: #666666;
-    font-weight: bold;
-    font-family: DINAlternate;
-    text-align: center;
-    line-height: 20px;
-  }
-
-  .workshop-list-item-name {
-    width: 126px;
-    font-weight: 400;
-    font-size: 14px;
-    color: #666666;
-    white-space: nowrap;
-    overflow: hidden;
-    text-overflow: ellipsis;
-  }
-  .workshop-list-item-icon {
-    cursor: pointer;
-    margin: 0 8px 0 10px;
-    width: 20px;
-    height: 20px;
-  }
-</style>

+ 0 - 17
src/views/institute-safety/modules/safety-workshop-list/apis/index.ts

@@ -1,17 +0,0 @@
-import { http } from '@/utils/http/axios';
-
-// 查询车间今日是否有异常状态
-export function getWorkshopTodayExceptionStatus(list: string[]) {
-  return http.request<
-    {
-      workshopCode: string;
-      exceptionStatus: boolean;
-    }[]
-  >({
-    url: '/issue/queryWorkshopTodayExceptionStatus',
-    method: 'post',
-    data: {
-      workshopCodeList: list,
-    },
-  });
-}