祖安之光
2025-11-17 971ed2f7636afc60802bc06f7fe160b61210aa72
修改新增
已添加1个文件
已修改2个文件
2590 ■■■■■ 文件已修改
src/views/menuPage.vue 1196 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/work/menuIndex/index.vue 1388 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/work/qualityInfo/outsourcingCooperate/outsourcedProcessFlow/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/menuPage.vue
@@ -1,5 +1,6 @@
<template>
  <div class="system-select-container">
    <!-- 顶部用户信息栏 -->
    <div class="user-info-bar">
      <div class="user-left"></div>
      <h3 class="user-details">欢迎访问多体系建设信息化系统</h3>
@@ -12,6 +13,9 @@
          </div>
          <template #dropdown>
            <el-dropdown-menu>
<!--              <el-dropdown-item command="info">-->
<!--                <span>基本信息</span>-->
<!--              </el-dropdown-item>-->
              <el-dropdown-item command="password">
                <span>修改密码</span>
              </el-dropdown-item>
@@ -24,201 +28,47 @@
      </div>
    </div>
    <!-- 重新布局的系统选择区域 -->
    <!-- 系统选择区域 -->
    <div class="systems-container">
      <div class="layout-container">
        <!-- 左侧列 -->
        <div class="left-column">
          <!-- 通知公告 -->
          <div class="module-card notice-module">
            <div class="module-header">
              <h3>通知公告</h3>
              <span class="more-link" @click="toNoticeMng">更多 ></span>
            </div>
            <div class="notice-list">
              <div class="notice-item" v-for="item in noticeList" :key="item">
                <span class="notice-title" @click="openNoticeFile(item.filePath)">{{item.content}}</span>
                <span class="notice-date">{{item.publishDate}}</span>
              </div>
            </div>
          </div>
          <!-- 流程中心 -->
          <div class="module-card process-module">
            <div class="module-header">
              <h3>流程中心</h3>
<!--              <span class="more-link">更多 ></span>-->
            </div>
            <div class="process-list" v-if="flowList && flowList.length>0">
              <div class="process-item" v-for="process in flowList" :key="process.id">
                <div class="process-info" @click="openDetail(process)">
                  <span class="process-status" :class="{processing: process.type == 1,pending: process.type == 2,success: process.type == 3,normal: process.type == 4,seal: process.type == 5}">
                    {{process.type == 1? '内审实施计划':process.type == 2? '培训计划':process.type == 3? '项目评审':process.type == 4?'年度检定计划':process.type == 5?'用章审批(申请)': '用章审批(待审批)'}}
                  </span>
                  <span class="process-name">{{process.title}}</span>
                </div>
              </div>
            </div>
            <div class="process-list" v-else>
              <span style="color: #999;font-size: 16px">暂无流程</span>
            </div>
          </div>
          <!-- 备忘录 -->
          <div class="module-card memo-module">
            <div class="module-header">
              <h3>备忘录</h3>
              <el-button @click="addMemo" type="primary">保存</el-button>
            </div>
            <div class="memo-content">
              <div class="memo-input">
                <el-input
                    v-model="memo.content"
                    placeholder="添加新的备忘录..."
                    type="textarea"
                    :autosize="{ minRows: 6, maxRows: 10}"
                >
                </el-input>
                <div class="memo-time" v-if="memo.updateTime">{{memo.updateTime}}</div>
              </div>
            </div>
          </div>
        </div>
        <!-- 右侧列 -->
        <div class="right-column">
          <!-- 快捷入口 -->
          <div class="module-card quick-access-module">
            <div class="module-header">
              <h3>快捷入口</h3>
              <div class="pagination-controls" v-if="platformList.length > 9">
                <el-button
                    :icon="ArrowLeft"
                    @click="prevPage"
                    size="small"
                    :disabled="currentPage === 0"
                    circle
                />
                <span class="page-info">{{ currentPage + 1 }}/{{ totalPages }}</span>
                <el-button
                    :icon="ArrowRight"
                    @click="nextPage"
                    size="small"
                    :disabled="currentPage === totalPages - 1"
                    circle
                />
              </div>
            </div>
            <div class="systems-grid-container">
              <div class="systems-grid">
                <div
                    v-for="(system, index) in displayedSystems"
            v-for="system in systems"
                    :key="system.id"
                    class="system-card"
                    @mouseenter="handleCardEnter($event, system.id)"
                    @mousemove="handleCardMove($event, system.id)"
                    @mouseleave="handleCardLeave(system.id)"
                    @click="enterSystem(system.platformAddress, getActualIndex(index))"
            @click="enterSystem(system.id)"
                    :style="getCardStyle(system.id)"
                >
                  <div class="card-content">
                    <div class="system-icon">
                      <el-image v-if="getActualIndex(index) == 0" :src="system.platformPic"/>
                      <el-image v-else :src="picUrl + system.platformPic"/>
            <img :src="system.icon"/>
                    </div>
                    <h3>{{ system.platformName }}</h3>
                  </div>
                </div>
                <!-- 填充空白格子 -->
                <div
                    v-if="showEmptyCard && displayedSystems.length < 9"
                    class="system-card empty-card"
                    @click="openAdd('add',{})"
                >
                  <div class="card-content">
                    <div class="system-icon">
                      <el-icon><Plus /></el-icon>
                    </div>
                    <h3>新增平台</h3>
                  </div>
                </div>
              </div>
            </div>
          </div>
          <!-- 日历 -->
          <div class="module-card calendar-module">
            <div class="module-header">
              <h3>日历</h3>
            </div>
            <div class="calendar-header">
              <div class="lunar-info">
                <span class="lunar-date-full">{{ currentLunarDate }}</span>
                <span class="lunar-year">{{ currentLunarYear }}</span>
              </div>
              <div class="current-date-info">
                <div class="solar-date-large">{{ currentSolarDate }}</div>
                <div class="week-day">{{ currentWeekDay }}</div>
              </div>
              <div class="calendar-actions">
                <el-button-group>
                  <el-button :icon="ArrowLeft" @click="prevMonth" size="small" />
                  <el-button @click="goToday" size="small">今天</el-button>
                  <el-button :icon="ArrowRight" @click="nextMonth" size="small" />
                </el-button-group>
              </div>
            </div>
            <div class="calendar-content">
              <el-calendar v-model="currentDate" ref="calendarRef">
                <template #header>
                  <!-- 隐藏默认header -->
                  <div style="display: none;"></div>
                </template>
                <template #date-cell="{ data }">
                  <div class="calendar-date" :class="{ 'is-today': isToday(data.day), 'is-current-month': isCurrentMonth(data.day) }">
                    <div class="solar-date">{{ getSolarDate(data.day) }}</div>
                    <div class="lunar-date">{{ getLunarDate(data.day) }}</div>
                    <div v-if="hasEvent(data.day)" class="calendar-event-dot"></div>
                  </div>
                </template>
              </el-calendar>
            </div>
          <h3>{{ system.name }}</h3>
          <p>{{ system.description }}</p>
          </div>
        </div>
      </div>
    </div>
    <user-dialog ref="reviewRef"></user-dialog>
    <edit-dialog ref="dialogRef" @getList=getPlatformList></edit-dialog>
    <sealDialog ref="noticeRef"></sealDialog>
  </div>
</template>
<script setup>
import {ref, onMounted, computed, reactive, toRefs, watch} from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { getToken, removeToken } from "@/utils/auth";
import Cookies from "js-cookie";
import { ElMessage, ElMessageBox } from "element-plus";
import { Plus, ArrowLeft, ArrowRight } from '@element-plus/icons-vue'
import useUserStore from '@/store/modules/user'
import userDialog from '@/views/build/conpanyFunctionConsult/staffManage/staffRegister/components/staffDialog.vue'
import editDialog from '@/views/build/conpanyFunctionConsult/infoPlatform/components/editDialog.vue'
import sealDialog from "@/views/work/sealManagement/apply/components/editDialog"
import menu1 from '@/assets/icons/menu1.png'
import menu2 from '@/assets/icons/menu2.png'
import menu3 from '@/assets/icons/menu3.png'
import menu4 from '@/assets/icons/menu4.png'
import menu5 from '@/assets/icons/menu5.png'
import menu6 from '@/assets/icons/menu6.png'
// 引入农历库
import * as lunarCalendar from 'lunar-calendar'
import {getIndexTitle, getMemoList, listNotice, updateMemo} from "@/api/system/notice";
import {renderAsync} from "docx-preview";
import {getInfoPlatforms} from "@/api/staffManage/staff";
import {getCompany} from "@/api/onlineEducation/company";
import {getSealApply} from "@/api/sealManage/apply";
const router = useRouter()
const route = useRoute();
@@ -227,53 +77,17 @@
const userName = ref('')
const userTypeName = ref('')
const cardStates = ref({})
const currentDate = ref(new Date())
const newMemo = ref('')
const calendarRef = ref()
const dialogRef = ref()
const noticeRef = ref();
const userStore = useUserStore()
const state = reactive({
  noticeParams: {
    pageNum: 1,
    pageSize: 6,
    companyId: null
  },
  platformParams: {
    pageNum: 1,
    pageSize: 99,
    companyId: null
  },
  noticeList: [],
  platformList: [],
  picUrl: import.meta.env.VITE_APP_BASE_API + '/',
  isAdmin: false,
  companyList: [],
  flowList: [],
  memo: {}
})
const { noticeParams,platformParams, noticeList,platformList,picUrl,isAdmin,companyList,flowList,memo } = toRefs(state)
// 组件挂载时获取用户信息和系统列表
onMounted(async () => {
onMounted(() => {
  if(getToken()){
    userInfo.value = JSON.parse(Cookies.get('userInfo'))
    userName.value = userInfo.value.username
    userTypeName.value = userInfo.value.userType == 0 ? '系统管理员' : (userInfo.value.userType == 1 ||  userInfo.value.userType == 2 || userInfo.value.userType == 3) ? '企业用户' :userInfo.value.userType == 6 ? '企业管理员' :userInfo.value.userType == 4 ? '其他' : '学员'
  }
  if(userStore.roles.includes('admin')){
    state.noticeParams.companyId = null
    state.platformParams.companyId = null
    state.isAdmin = true
  }else{
    state.noticeParams.companyId = userStore.companyId
    state.platformParams.companyId = userStore.companyId
    state.isAdmin = false
  }
  await getNoticeList()
  await getPlatformList()
  await getFlowList()
  await getMemo()
  state.platformList.forEach(system => {
  const userStore = useUserStore()
  userStore.roles = []
  systems.value.forEach(system => {
    cardStates.value[system.id] = {
      mouseX: 0,
      mouseY: 0,
@@ -284,335 +98,8 @@
  })
})
const getSealDetail = async (type,sealId) => {
  let param = {}
  if(type == 5){
    param = {
      pageNum: 1,
      pageSize: 999,
      companyId: state.noticeParams.companyId,
      applyUserId: userStore.id
    }
  }else{
    param = {
      pageNum: 1,
      pageSize: 999,
      companyId: state.noticeParams.companyId,
      nextCheck: userStore.id
    }
  }
  const res = await getSealApply(param);
  if(res.code === 200){
    return res.data.list.find(i=>i.id == sealId)
  }else{
    ElMessage.warning(res.message)
  }
}
const toNoticeMng = ()=>{
  router.push({ path: "/work/noticeMng" });
}
const openDetail = async (value) => {
  if(value.type == '5' || value.type == '6'){
    const data = await getSealDetail(Number(value.type),Number(value.dataId))
    noticeRef.value.openDialog('review', data,state.companyList)
  }
}
function getNoticeList() {
  listNotice(state.noticeParams).then(res => {
    state.noticeList = res.data.list
  })
}
const getCompanyList = async ()=>{
  const queryParams = {
    pageNum: 1,
    pageSize: 999
  }
  const res = await getCompany(queryParams)
  if (res.code == 200) {
    state.companyList = res.data.list?res.data.list:[]
  } else {
    ElMessage.warning(res.message)
  }
}
const getPlatformList = async () => {
  const res = await getInfoPlatforms(state.platformParams)
  if(res.code == 200){
    const originPlatform = {
      id: 0,
      platformName: '国军标9001C质量管理体系',
      platformPic: menu1
    }
    state.platformList = [originPlatform, ...(Array.isArray(res.data) ? res.data : [])]
  }else{
    ElMessage.warning(res.message)
  }
}
const openAdd = async (type, value) => {
  await getCompanyList()
  dialogRef.value.openDialog(type, value, state.platformParams.companyId, state.isAdmin, state.companyList );
}
const getFlowList = async () => {
  const res = await getIndexTitle({pageNum: 1,pageSize: 99})
  if(res.code == 200){
    state.flowList = Array.isArray(res.data.list) ? res.data.list : []
  }else{
    ElMessage.warning(res.message)
  }
}
const getMemo = async () => {
  const res = await getMemoList()
  if(res.code == 200){
    state.memo = res.data ? res.data : {}
  }else{
    ElMessage.warning(res.message)
  }
}
// 分页相关
const currentPage = ref(0)
const pageSize = 9 // 九宫格,每页9个
// 计算总页数
const totalPages = computed(() => {
  return Math.ceil(state.platformList.length / pageSize)
})
// 获取当前页显示的系统列表
const displayedSystems = computed(() => {
  const start = currentPage.value * pageSize
  const end = start + pageSize
  return state.platformList.slice(start, end)
})
// 是否显示敬请期待的卡片(只在最后一页且系统总数不是9的倍数时显示)
const showEmptyCard = computed(() => {
  // 如果是最后一页,并且系统总数不是9的倍数,且当前页显示的系统数量小于9
  const isLastPage = currentPage.value === totalPages.value - 1
  const totalCount = state.platformList.length
  const currentCount = displayedSystems.value.length
  return isLastPage && (totalCount % pageSize !== 0) && currentCount < 9
})
// 获取实际在原始数组中的索引
const getActualIndex = (displayIndex) => {
  return currentPage.value * pageSize + displayIndex
}
// 分页方法
const prevPage = () => {
  if (currentPage.value > 0) {
    currentPage.value--
  }
}
const nextPage = () => {
  if (currentPage.value < totalPages.value - 1) {
    currentPage.value++
  }
}
// 重置分页(当系统列表变化时)
watch(state.platformList, () => {
  currentPage.value = 0
})
const openNoticeFile = async(path)=>{
  const ext = path.split('.').pop().toLowerCase();
  if (ext === 'doc' || ext === 'xls' || ext === 'xlsx') {
    ElMessageBox.confirm('暂不支持线上预览文件,是否下载查看?', '提示', { confirmButtonText: '确认', cancelButtonText: '取消', type: 'warning' }).then(() => {
      window.open(`${import.meta.env.VITE_APP_BASE_API}/${path}`, '_blank');
    }).catch(() => {
      console.log('取消预览')
    });
    return
  }
  if(ext === 'pdf'){
    window.open(`${import.meta.env.VITE_APP_BASE_API}/${path}`, '_blank')
    return
  }
  try {
    // 1. 获取文件
    const response = await fetch(import.meta.env.VITE_APP_BASE_API + '/' + path);
    const arrayBuffer = await response.arrayBuffer();
    // 2. 创建新窗口
    const win = window.open('', '_blank')
    win.document.write(`
      <!DOCTYPE html>
      <html>
        <head>
          <title>预览</title>
          <style>
            body { margin: 20px; font-family: Arial; }
            .docx-container { width: 100%; height: 100%; }
          </style>
        </head>
        <body>
          <div id="container" class="docx-container"></div>
        </body>
      </html>
    `);
    // 3. 渲染 DOCX
    await renderAsync(arrayBuffer, win.document.getElementById('container'));
  } catch (error) {
    console.error('预览失败:', error);
    alert(`预览失败: ${error.message}`);
  }
}
// 十二生肖数组
const zodiacAnimals = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪']
// 获取生肖年份
const getZodiacYear = (lunarYear) => {
  // 农历年份计算生肖:年份减去4后除以12取余数
  // 因为鼠年对应4,牛年对应5,以此类推
  const index = (lunarYear - 4) % 12
  return index >= 0 ? zodiacAnimals[index] : zodiacAnimals[index + 12]
}
// 计算当前日期信息
const currentWeekDay = computed(() => {
  const days = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
  return days[currentDate.value.getDay()]
})
const currentSolarDate = computed(() => {
  const date = currentDate.value
  return `${date.getMonth() + 1}月${date.getDate()}日`
})
const currentLunarDate = computed(() => {
  try {
    const date = currentDate.value
    const year = date.getFullYear()
    const month = date.getMonth() + 1
    const day = date.getDate()
    const lunar = lunarCalendar.solarToLunar(year, month, day)
    if (lunar && lunar.lunarMonthName && lunar.lunarDayName) {
      return `${lunar.lunarMonthName}${lunar.lunarDayName}`
    }
    return ''
  } catch (error) {
    console.error('获取农历日期错误:', error)
    return ''
  }
})
const currentLunarYear = computed(() => {
  try {
    const date = currentDate.value
    const year = date.getFullYear()
    const month = date.getMonth() + 1
    const day = date.getDate()
    const lunar = lunarCalendar.solarToLunar(year, month, day)
    if (lunar && lunar.GanZhiYear) {
      const zodiac = getZodiacYear(year)
      return `${lunar.GanZhiYear}年【${zodiac}年】`
    }
    return ''
  } catch (error) {
    console.error('获取农历年份错误:', error)
    return ''
  }
})
// 月份导航方法
const prevMonth = () => {
  const date = new Date(currentDate.value)
  date.setMonth(date.getMonth() - 1)
  currentDate.value = date
}
const nextMonth = () => {
  const date = new Date(currentDate.value)
  date.setMonth(date.getMonth() + 1)
  currentDate.value = date
}
const goToday = () => {
  currentDate.value = new Date()
}
// 添加备忘录
const addMemo = async () => {
  if (state.memo.content) {
    const data = {
      id: state.memo.id || null,
      companyId: state.noticeParams.companyId,
      content: state.memo.content,
      createById: userStore.id
    }
    const res = await updateMemo(data)
    if(res.code == 200){
      ElMessage.success('保存成功')
      await getMemo()
    }else{
      ElMessage.warning(res.message)
    }
    newMemo.value = ''
  }
}
// 获取公历日期(去掉前导零)
const getSolarDate = (dateString) => {
  const date = new Date(dateString)
  return date.getDate()
}
// 获取农历日期
const getLunarDate = (dateString) => {
  try {
    const date = new Date(dateString)
    const year = date.getFullYear()
    const month = date.getMonth() + 1
    const day = date.getDate()
    // 使用农历库获取农历信息
    const lunar = lunarCalendar.solarToLunar(year, month, day)
    if (lunar && lunar.lunarDayName) {
      // 如果是初一,显示月份,否则显示日期
      if (lunar.lunarDay === 1) {
        return lunar.lunarMonthName + '月'
      } else {
        // 简化显示,只显示数字日期
        const lunarDay = lunar.lunarDayName.replace('初', '').replace('十', '')
        return lunarDay
      }
    }
    return ''
  } catch (error) {
    console.error('获取农历日期错误:', error)
    return ''
  }
}
// 检查日期是否有事件
const hasEvent = (date) => {
  const eventDates = []
  return eventDates.includes(date)
}
// 检查是否是今天
const isToday = (dateString) => {
  const today = new Date().toISOString().split('T')[0]
  return dateString === today
}
// 检查是否是当前月份
const isCurrentMonth = (dateString) => {
  const date = new Date(dateString)
  const current = new Date(currentDate.value)
  return date.getMonth() === current.getMonth() && date.getFullYear() === current.getFullYear()
}
// 鼠标进入卡片
const handleCardEnter = (event, id) => {
  const card = event.currentTarget
  cardStates.value[id] = {
@@ -623,6 +110,7 @@
  }
}
// 鼠标移动
const handleCardMove = (event, id) => {
  if (!cardStates.value[id]?.hover) return
@@ -633,18 +121,21 @@
  cardStates.value[id].mouseY = event.clientY - rect.top - cardStates.value[id].height / 2
}
// 鼠标离开
const handleCardLeave = (id) => {
  cardStates.value[id].hover = false;
  // 立即开始归位动画,不使用setTimeout延迟
  cardStates.value[id].mouseX = 0;
  cardStates.value[id].mouseY = 0;
}
// 获取卡片样式
const getCardStyle = (id) => {
  const state = cardStates.value[id] || {}
  const mousePX = state.mouseX / (state.width || 1)
  const mousePY = state.mouseY / (state.height || 1)
  const rX = mousePX * 20
  const rX = mousePX * 20 // 减小旋转角度,使效果更柔和
  const rY = mousePY * -20
  const tX = mousePX * -20
@@ -656,6 +147,46 @@
  }
}
// 系统列表
const systems = ref([
  {
    id: 1,
    name: '国军标9001C质量管理体系',
    description: '确保产品和服务质量符合国际标准',
    icon: menu1
  },
  {
    id: 2,
    name: 'ISO 27001 信息安全体系',
    description: '保护企业信息资产安全与机密性',
    icon: menu2
  },
  {
    id: 3,
    name: 'ISO 45001 安全体系',
    description: '实现企业安全的持续改进',
    icon: menu3
  },
  {
    id: 4,
    name: '项目管理控制',
    description: '标准化项目管理流程与方法',
    icon: menu4
  },
  {
    id: 5,
    name: '承制评价体系',
    description: '供应商与承包商能力评估标准',
    icon: menu5
  },
  {
    id: 6,
    name: '新体系评价',
    description: '新体系评价',
    icon: menu6
  }
])
function handleCommand(command) {
  switch (command) {
    case "info":
@@ -671,24 +202,21 @@
      break;
  }
}
const enterSystem = (address,index) => {
  if(index == 0){
    router.push({ path: "/learn/standardSysTemp/sysStandardModule"})
// 进入系统
const enterSystem = (systemId) => {
  if(systemId == 1){
    router.push({ path: "/"});
  }else{
    window.open(address)
    // ElMessage.warning('系统正在开发中...')
    ElMessage.warning('系统正在开发中...')
  }
}
function getInfo() {
  reviewRef.value.openDialog('view',userInfo.value)
}
function editPsd() {
  reviewRef.value.openDialog('pwd',userInfo.value)
}
// 退出登录
function logout() {
  ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
    confirmButtonText: '确定',
@@ -697,8 +225,11 @@
  }).then(() => {
    removeToken()
    location.href = '/homePage';
  }).catch(() => { });
}
</script>
<style scoped lang="scss">
@@ -755,259 +286,29 @@
      }
    }
  }
}
.systems-container {
  flex: 1;
  padding: 20px;
  overflow-y: auto;
  width: 100%;
}
.layout-container {
  display: flex;
  gap: 20px;
  width: 100%;
  height: 100%;
  max-width: none; /* 移除最大宽度限制 */
  margin: 0; /* 移除居中margin */
}
// 左侧列 - 宽度占比2
.left-column {
  flex: 2;
  display: flex;
  flex-direction: column;
  gap: 20px;
  min-width: 0; /* 防止内容溢出 */
}
// 右侧列 - 宽度占比1
.right-column {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 20px;
  min-width: 0; /* 防止内容溢出 */
  min-width: 400px; /* 设置最小宽度避免过窄 */
}
// 通用模块卡片样式
.module-card {
  background: #ffffff;
  border-radius: 8px;
  padding: 20px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  transition: box-shadow 0.3s ease;
  height: 100%;
  display: flex;
  flex-direction: column;
  &:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  }
}
.module-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid #f0f0f0;
  flex-shrink: 0; /* 防止header被压缩 */
  h3 {
    margin: 0;
    color: #333;
    font-size: 18px;
    font-weight: 600;
  }
  .more-link {
    color: #409eff;
    font-size: 14px;
    cursor: pointer;
    &:hover {
      color: #337ecc;
    }
  }
}
// 通知公告模块
.notice-module {
  flex: 1;
  .notice-list {
    flex: 1;
    overflow: hidden;
    overflow-y: auto;
    .notice-item {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 12px 0;
      border-bottom: 1px solid #f5f5f5;
      &:last-child {
        border-bottom: none;
      }
      .notice-title {
        color: #333;
        font-size: 16px;
        cursor: pointer;
        flex: 1;
        margin-right: 12px;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        &:hover {
          color: #409eff;
        }
      }
      .notice-date {
        color: #999;
        font-size: 14px;
        flex-shrink: 0;
      }
    }
  }
}
// 流程中心模块
.process-module {
  flex: 1;
  overflow-y: auto;
  .process-list {
    flex: 1;
    overflow-y: auto;
    .process-item {
      margin-bottom: 8px;
      .process-info {
        display: flex;
        align-items: center;
        padding-bottom: 8px;
        margin-bottom: 8px;
        box-sizing: border-box;
        border-bottom: 1px dashed #f0f0f0;
        cursor: pointer;
        .process-name {
          color: #333;
          font-size: 16px;
          flex: 1;
          &:hover {
            color: #409eff;
          }
        }
        .process-status {
          font-size: 12px;
          padding: 2px 8px;
          border-radius: 4px;
          flex-shrink: 0;
          margin-right: 10px;
          &.processing {
            background: #e6f7ff;
            color: #1890ff;
          }
          &.pending {
            background: #fff7e6;
            color: #fa8c16;
          }
          &.success {
            background: #edffdb;
            color: #52c41a;
          }
          &.normal {
            background: #ffebca;
            color: #ff6518;
          }
          &.seal {
            background: #ffe6e6;
            color: #ff1818;
          }
        }
      }
    }
  }
}
// 备忘录模块
.memo-module {
  flex: 1;
  .memo-content {
    flex: 1;
    display: flex;
    flex-direction: column;
    .memo-input {
      flex: 1;
      .memo-time{
        color: #999;
        font-size: 12px;
        text-align: right;
        margin-top: 10px;
      }
    }
  }
}
// 快捷入口模块
.quick-access-module {
  .module-header {
    .pagination-controls {
      display: flex;
      align-items: center;
      gap: 8px;
      .page-info {
        font-size: 12px;
        color: #666;
        min-width: 40px;
        text-align: center;
      }
      .el-button {
        width: 28px;
        height: 28px;
        display: flex;
        align-items: center;
        justify-content: center;
      }
    }
  }
  .systems-grid-container {
    flex: 1;
    overflow: hidden;
  margin-top: 60px;
  }
  .systems-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(3, 1fr);
    gap: 16px;
    height: 100%;
    min-height: 400px; // 确保有足够的高度显示九宫格
  grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
  gap: 30px;
  max-width: 1500px;
  margin: 0 auto;
    perspective: 1000px;
  }
  .system-card {
    position: relative;
  height: 280px;
    background-color: #ffffff;
    border-radius: 4px;
    transition: transform 1s cubic-bezier(0.23, 1, 0.32, 1),
@@ -1015,50 +316,23 @@
    transform-style: preserve-3d;
    cursor: pointer;
    overflow: hidden;
    min-height: 100px; // 确保卡片有最小高度
    // 空白卡片样式
    &.empty-card {
      opacity: 0.6;
      .system-icon {
        color: #ccc;
      }
      h3 {
        color: #999;
      }
      &:hover {
        transform: none;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
        &::after {
          border-color: transparent;
        }
        &::before {
          background: #fff;
        }
      }
    }
    &:hover{
      border-radius: 8px;
    border-radius: 16px;
    }
    &::after {
      content: '';
      position: absolute;
      top: 4px;
      left: 4px;
      right: 4px;
      bottom: 4px;
      border-radius: 4px;
    top: 8px;
    left: 8px;
    right: 8px;
    bottom: 8px;
    border-radius: 4px; /* 比卡片小1px */
      border: 1px solid transparent;
      transition: border-color 0.3s ease;
      pointer-events: none;
      z-index: 3;
    pointer-events: none; /* 确保不影响鼠标事件 */
    z-index: 3; /* 确保在内容之上 */
    }
    &::before {
@@ -1074,24 +348,22 @@
      transition: transform 0.5s cubic-bezier(0.23, 1, 0.32, 1);
      z-index: 1;
    }
    &:hover::after {
      border-color: rgba(37,99,235,1);
      border-radius: 6px;
      box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.5);
    border-color: rgba(37,99,235,1); /* 使用蓝色描边 */
    border-radius: 12px;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.5); /* 可选:添加内发光效果 */
    }
    &:hover::before {
      border-radius: 8px;
    border-radius: 16px;
      background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(20,20,20,0.05) 100%);
    }
  }
  .system-card:hover {
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15),
  //transform: translateY(-5px);
  box-shadow: 0 15px 30px rgba(0, 0, 0, 0.15),
    0 0 0 1px rgba(255, 255, 255, 0.5) inset;
  }
  .card-content {
    position: relative;
    height: 100%;
@@ -1099,290 +371,40 @@
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 12px;
  padding: 25px;
    z-index: 2;
    transform-style: preserve-3d;
  }
  .system-icon {
    margin-bottom: 8px;
    .el-image {
      width: 40px;
      height: 40px;
.system-icon img {
  width: 80px;
  height: 80px;
  margin-bottom: 30px;
      transition: transform 0.5s;
    }
    .el-icon {
      font-size: 40px;
      color: #ccc;
    }
  }
  .system-card:hover .system-icon img {
    transform: scale(1.2);
  transform: scale(1.4);
  }
  .system-card h3 {
    margin: 0;
  margin: 0 0 12px;
    color: #333;
    font-size: 14px;
  font-size: 24px;
    text-align: center;
    transition: transform 0.3s;
    line-height: 1.2;
    font-weight: 500;
  }
  .system-card:hover h3 {
    transform: translateZ(10px);
  }
}
// 日历模块
.calendar-module {
  .calendar-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding-bottom: 16px;
    .lunar-info {
      width: 33.33%;
      display: flex;
      flex-direction: column;
      font-size: 16px;
      .lunar-date-full {
        color: #e6a23c;
        font-weight: 500;
      }
      .lunar-year {
.system-card p {
  margin: 0;
        color: #999;
      }
    }
    .current-date-info {
      width: 33.33%;
      font-size: 16px;
      display: flex;
      flex-direction: column;
      align-items: center;
      .solar-date-large {
        color: #333;
        font-weight: 600;
      }
      .week-day {
        color: #666;
        margin-bottom: 4px;
        font-weight: 500;
      }
    }
    .calendar-actions {
      display: flex;
      justify-content: right;
      width: 33.33%;
      .el-button-group {
        display: flex;
        gap: 1px;
        .el-button {
          padding: 6px 12px;
          border-radius: 4px;
          &:first-child {
            border-top-right-radius: 0;
            border-bottom-right-radius: 0;
          }
          &:last-child {
            border-top-left-radius: 0;
            border-bottom-left-radius: 0;
          }
          &:not(:first-child):not(:last-child) {
            border-radius: 0;
          }
        }
      }
    }
  }
  .calendar-content {
    flex: 1;
    display: flex;
    flex-direction: column;
    :deep(.el-calendar) {
      border: none;
      flex: 1;
      display: flex;
      flex-direction: column;
      .el-calendar__header {
        padding: 0;
        flex-shrink: 0;
      }
      .el-calendar__body {
        flex: 1;
      }
      .el-calendar-table {
        height: 100%;
        .el-calendar-day {
          height: 60px;
          padding: 2px;
  font-size: 15px;
          text-align: center;
  transition: transform 0.3s;
        }
        td.is-selected {
          background-color: transparent;
        }
        .el-calendar-day:hover {
          background-color: #f5f7fa;
        }
      }
    }
    .calendar-date {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: flex-start;
      height: 100%;
      position: relative;
      padding-top: 4px;
      .solar-date {
        font-size: 14px;
        font-weight: 500;
        color: #333;
        margin-bottom: 2px;
      }
      .lunar-date {
        font-size: 10px;
        color: #999;
        margin-bottom: 4px;
      }
      .calendar-event-dot {
        width: 4px;
        height: 4px;
        background: #409eff;
        border-radius: 50%;
        margin-bottom: 2px;
      }
      .today-indicator {
        position: absolute;
        top: 2px;
        right: 2px;
        width: 16px;
        height: 16px;
        background: #409eff;
        color: white;
        border-radius: 50%;
        font-size: 10px;
        display: flex;
        align-items: center;
        justify-content: center;
        line-height: 1;
      }
    }
    // 今天日期的样式
    .calendar-date.is-today {
      background-color: #ecf5ff;
      .solar-date {
        color: #409eff;
        font-weight: 600;
      }
      &::before {
        content: '';
        position: absolute;
        top: 2px;
        left: 50%;
        transform: translateX(-50%);
        width: 24px;
        height: 24px;
        background: #409eff;
        border-radius: 50%;
        z-index: -1;
        opacity: 0.1;
      }
    }
    .calendar-legend {
      display: flex;
      justify-content: center;
      gap: 16px;
      margin-top: 12px;
      padding-top: 12px;
      border-top: 1px solid #f0f0f0;
      flex-shrink: 0;
      .legend-item {
        display: flex;
        align-items: center;
        gap: 6px;
        .legend-dot {
          width: 8px;
          height: 8px;
          border-radius: 50%;
        }
        .event-dot {
          background: #409eff;
        }
        .today-dot {
          background: #409eff;
        }
        span {
          font-size: 12px;
          color: #666;
        }
      }
    }
  }
}
// 响应式设计
@media (max-width: 1200px) {
  .layout-container {
    flex-direction: column;
  }
  .left-column,
  .right-column {
    flex: 1;
    min-width: auto;
  }
}
// 超大屏幕优化
@media (min-width: 1920px) {
  .systems-container {
    padding: 30px 40px;
  }
  .layout-container {
    gap: 30px;
  }
  .module-card {
    padding: 25px;
  }
  // 在超大屏幕上可以显示3列的系统卡片
  .quick-access-module .systems-grid {
    grid-template-columns: repeat(3, 1fr);
  }
.system-card:hover h3,
.system-card:hover p {
  transform: translateZ(20px);
}
</style>
src/views/work/menuIndex/index.vue
对比新文件
@@ -0,0 +1,1388 @@
<template>
  <div class="system-select-container">
    <div class="user-info-bar">
      <div class="user-left"></div>
      <h3 class="user-details">欢迎访问多体系建设信息化系统</h3>
      <div class="avatar-container">
        <el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
          <div class="avatar-wrapper" style="display: flex;align-items: center">
            <img src="../assets/images/avator.png" class="user-avatar"  />
            <span style="font-size: 16px">{{userName}}({{userTypeName}})</span>
            <el-icon><caret-bottom /></el-icon>
          </div>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item command="password">
                <span>修改密码</span>
              </el-dropdown-item>
              <el-dropdown-item divided command="logout">
                <span>退出登录</span>
              </el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </div>
    </div>
    <!-- 重新布局的系统选择区域 -->
    <div class="systems-container">
      <div class="layout-container">
        <!-- 左侧列 -->
        <div class="left-column">
          <!-- 通知公告 -->
          <div class="module-card notice-module">
            <div class="module-header">
              <h3>通知公告</h3>
              <span class="more-link" @click="toNoticeMng">更多 ></span>
            </div>
            <div class="notice-list">
              <div class="notice-item" v-for="item in noticeList" :key="item">
                <span class="notice-title" @click="openNoticeFile(item.filePath)">{{item.content}}</span>
                <span class="notice-date">{{item.publishDate}}</span>
              </div>
            </div>
          </div>
          <!-- 流程中心 -->
          <div class="module-card process-module">
            <div class="module-header">
              <h3>流程中心</h3>
<!--              <span class="more-link">更多 ></span>-->
            </div>
            <div class="process-list" v-if="flowList && flowList.length>0">
              <div class="process-item" v-for="process in flowList" :key="process.id">
                <div class="process-info" @click="openDetail(process)">
                  <span class="process-status" :class="{processing: process.type == 1,pending: process.type == 2,success: process.type == 3,normal: process.type == 4,seal: process.type == 5}">
                    {{process.type == 1? '内审实施计划':process.type == 2? '培训计划':process.type == 3? '项目评审':process.type == 4?'年度检定计划':process.type == 5?'用章审批(申请)': '用章审批(待审批)'}}
                  </span>
                  <span class="process-name">{{process.title}}</span>
                </div>
              </div>
            </div>
            <div class="process-list" v-else>
              <span style="color: #999;font-size: 16px">暂无流程</span>
            </div>
          </div>
          <!-- 备忘录 -->
          <div class="module-card memo-module">
            <div class="module-header">
              <h3>备忘录</h3>
              <el-button @click="addMemo" type="primary">保存</el-button>
            </div>
            <div class="memo-content">
              <div class="memo-input">
                <el-input
                    v-model="memo.content"
                    placeholder="添加新的备忘录..."
                    type="textarea"
                    :autosize="{ minRows: 6, maxRows: 10}"
                >
                </el-input>
                <div class="memo-time" v-if="memo.updateTime">{{memo.updateTime}}</div>
              </div>
            </div>
          </div>
        </div>
        <!-- 右侧列 -->
        <div class="right-column">
          <!-- 快捷入口 -->
          <div class="module-card quick-access-module">
            <div class="module-header">
              <h3>快捷入口</h3>
              <div class="pagination-controls" v-if="platformList.length > 9">
                <el-button
                    :icon="ArrowLeft"
                    @click="prevPage"
                    size="small"
                    :disabled="currentPage === 0"
                    circle
                />
                <span class="page-info">{{ currentPage + 1 }}/{{ totalPages }}</span>
                <el-button
                    :icon="ArrowRight"
                    @click="nextPage"
                    size="small"
                    :disabled="currentPage === totalPages - 1"
                    circle
                />
              </div>
            </div>
            <div class="systems-grid-container">
              <div class="systems-grid">
                <div
                    v-for="(system, index) in displayedSystems"
                    :key="system.id"
                    class="system-card"
                    @mouseenter="handleCardEnter($event, system.id)"
                    @mousemove="handleCardMove($event, system.id)"
                    @mouseleave="handleCardLeave(system.id)"
                    @click="enterSystem(system.platformAddress, getActualIndex(index))"
                    :style="getCardStyle(system.id)"
                >
                  <div class="card-content">
                    <div class="system-icon">
                      <el-image v-if="getActualIndex(index) == 0" :src="system.platformPic"/>
                      <el-image v-else :src="picUrl + system.platformPic"/>
                    </div>
                    <h3>{{ system.platformName }}</h3>
                  </div>
                </div>
                <!-- 填充空白格子 -->
                <div
                    v-if="showEmptyCard && displayedSystems.length < 9"
                    class="system-card empty-card"
                    @click="openAdd('add',{})"
                >
                  <div class="card-content">
                    <div class="system-icon">
                      <el-icon><Plus /></el-icon>
                    </div>
                    <h3>新增平台</h3>
                  </div>
                </div>
              </div>
            </div>
          </div>
          <!-- 日历 -->
          <div class="module-card calendar-module">
            <div class="module-header">
              <h3>日历</h3>
            </div>
            <div class="calendar-header">
              <div class="lunar-info">
                <span class="lunar-date-full">{{ currentLunarDate }}</span>
                <span class="lunar-year">{{ currentLunarYear }}</span>
              </div>
              <div class="current-date-info">
                <div class="solar-date-large">{{ currentSolarDate }}</div>
                <div class="week-day">{{ currentWeekDay }}</div>
              </div>
              <div class="calendar-actions">
                <el-button-group>
                  <el-button :icon="ArrowLeft" @click="prevMonth" size="small" />
                  <el-button @click="goToday" size="small">今天</el-button>
                  <el-button :icon="ArrowRight" @click="nextMonth" size="small" />
                </el-button-group>
              </div>
            </div>
            <div class="calendar-content">
              <el-calendar v-model="currentDate" ref="calendarRef">
                <template #header>
                  <!-- 隐藏默认header -->
                  <div style="display: none;"></div>
                </template>
                <template #date-cell="{ data }">
                  <div class="calendar-date" :class="{ 'is-today': isToday(data.day), 'is-current-month': isCurrentMonth(data.day) }">
                    <div class="solar-date">{{ getSolarDate(data.day) }}</div>
                    <div class="lunar-date">{{ getLunarDate(data.day) }}</div>
                    <div v-if="hasEvent(data.day)" class="calendar-event-dot"></div>
                  </div>
                </template>
              </el-calendar>
            </div>
          </div>
        </div>
      </div>
    </div>
    <user-dialog ref="reviewRef"></user-dialog>
    <edit-dialog ref="dialogRef" @getList=getPlatformList></edit-dialog>
    <sealDialog ref="noticeRef"></sealDialog>
  </div>
</template>
<script setup>
import {ref, onMounted, computed, reactive, toRefs, watch} from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { getToken, removeToken } from "@/utils/auth";
import Cookies from "js-cookie";
import { ElMessage, ElMessageBox } from "element-plus";
import { Plus, ArrowLeft, ArrowRight } from '@element-plus/icons-vue'
import useUserStore from '@/store/modules/user'
import userDialog from '@/views/build/conpanyFunctionConsult/staffManage/staffRegister/components/staffDialog.vue'
import editDialog from '@/views/build/conpanyFunctionConsult/infoPlatform/components/editDialog.vue'
import sealDialog from "@/views/work/sealManagement/apply/components/editDialog"
import menu1 from '@/assets/icons/menu1.png'
import menu2 from '@/assets/icons/menu2.png'
import menu3 from '@/assets/icons/menu3.png'
import menu4 from '@/assets/icons/menu4.png'
import menu5 from '@/assets/icons/menu5.png'
import menu6 from '@/assets/icons/menu6.png'
// 引入农历库
import * as lunarCalendar from 'lunar-calendar'
import {getIndexTitle, getMemoList, listNotice, updateMemo} from "@/api/system/notice";
import {renderAsync} from "docx-preview";
import {getInfoPlatforms} from "@/api/staffManage/staff";
import {getCompany} from "@/api/onlineEducation/company";
import {getSealApply} from "@/api/sealManage/apply";
const router = useRouter()
const route = useRoute();
const reviewRef = ref();
const userInfo = ref();
const userName = ref('')
const userTypeName = ref('')
const cardStates = ref({})
const currentDate = ref(new Date())
const newMemo = ref('')
const calendarRef = ref()
const dialogRef = ref()
const noticeRef = ref();
const userStore = useUserStore()
const state = reactive({
  noticeParams: {
    pageNum: 1,
    pageSize: 6,
    companyId: null
  },
  platformParams: {
    pageNum: 1,
    pageSize: 99,
    companyId: null
  },
  noticeList: [],
  platformList: [],
  picUrl: import.meta.env.VITE_APP_BASE_API + '/',
  isAdmin: false,
  companyList: [],
  flowList: [],
  memo: {}
})
const { noticeParams,platformParams, noticeList,platformList,picUrl,isAdmin,companyList,flowList,memo } = toRefs(state)
// 组件挂载时获取用户信息和系统列表
onMounted(async () => {
  if(getToken()){
    userInfo.value = JSON.parse(Cookies.get('userInfo'))
    userName.value = userInfo.value.username
    userTypeName.value = userInfo.value.userType == 0 ? '系统管理员' : (userInfo.value.userType == 1 ||  userInfo.value.userType == 2 || userInfo.value.userType == 3) ? '企业用户' :userInfo.value.userType == 6 ? '企业管理员' :userInfo.value.userType == 4 ? '其他' : '学员'
  }
  if(userStore.roles.includes('admin')){
    state.noticeParams.companyId = null
    state.platformParams.companyId = null
    state.isAdmin = true
  }else{
    state.noticeParams.companyId = userStore.companyId
    state.platformParams.companyId = userStore.companyId
    state.isAdmin = false
  }
  await getNoticeList()
  await getPlatformList()
  await getFlowList()
  await getMemo()
  state.platformList.forEach(system => {
    cardStates.value[system.id] = {
      mouseX: 0,
      mouseY: 0,
      width: 0,
      height: 0,
      hover: false
    }
  })
})
const getSealDetail = async (type,sealId) => {
  let param = {}
  if(type == 5){
    param = {
      pageNum: 1,
      pageSize: 999,
      companyId: state.noticeParams.companyId,
      applyUserId: userStore.id
    }
  }else{
    param = {
      pageNum: 1,
      pageSize: 999,
      companyId: state.noticeParams.companyId,
      nextCheck: userStore.id
    }
  }
  const res = await getSealApply(param);
  if(res.code === 200){
    return res.data.list.find(i=>i.id == sealId)
  }else{
    ElMessage.warning(res.message)
  }
}
const toNoticeMng = ()=>{
  router.push({ path: "/work/noticeMng" });
}
const openDetail = async (value) => {
  if(value.type == '5' || value.type == '6'){
    const data = await getSealDetail(Number(value.type),Number(value.dataId))
    noticeRef.value.openDialog('review', data,state.companyList)
  }
}
function getNoticeList() {
  listNotice(state.noticeParams).then(res => {
    state.noticeList = res.data.list
  })
}
const getCompanyList = async ()=>{
  const queryParams = {
    pageNum: 1,
    pageSize: 999
  }
  const res = await getCompany(queryParams)
  if (res.code == 200) {
    state.companyList = res.data.list?res.data.list:[]
  } else {
    ElMessage.warning(res.message)
  }
}
const getPlatformList = async () => {
  const res = await getInfoPlatforms(state.platformParams)
  if(res.code == 200){
    const originPlatform = {
      id: 0,
      platformName: '国军标9001C质量管理体系',
      platformPic: menu1
    }
    state.platformList = [originPlatform, ...(Array.isArray(res.data) ? res.data : [])]
  }else{
    ElMessage.warning(res.message)
  }
}
const openAdd = async (type, value) => {
  await getCompanyList()
  dialogRef.value.openDialog(type, value, state.platformParams.companyId, state.isAdmin, state.companyList );
}
const getFlowList = async () => {
  const res = await getIndexTitle({pageNum: 1,pageSize: 99})
  if(res.code == 200){
    state.flowList = Array.isArray(res.data.list) ? res.data.list : []
  }else{
    ElMessage.warning(res.message)
  }
}
const getMemo = async () => {
  const res = await getMemoList()
  if(res.code == 200){
    state.memo = res.data ? res.data : {}
  }else{
    ElMessage.warning(res.message)
  }
}
// 分页相关
const currentPage = ref(0)
const pageSize = 9 // 九宫格,每页9个
// 计算总页数
const totalPages = computed(() => {
  return Math.ceil(state.platformList.length / pageSize)
})
// 获取当前页显示的系统列表
const displayedSystems = computed(() => {
  const start = currentPage.value * pageSize
  const end = start + pageSize
  return state.platformList.slice(start, end)
})
// 是否显示敬请期待的卡片(只在最后一页且系统总数不是9的倍数时显示)
const showEmptyCard = computed(() => {
  // 如果是最后一页,并且系统总数不是9的倍数,且当前页显示的系统数量小于9
  const isLastPage = currentPage.value === totalPages.value - 1
  const totalCount = state.platformList.length
  const currentCount = displayedSystems.value.length
  return isLastPage && (totalCount % pageSize !== 0) && currentCount < 9
})
// 获取实际在原始数组中的索引
const getActualIndex = (displayIndex) => {
  return currentPage.value * pageSize + displayIndex
}
// 分页方法
const prevPage = () => {
  if (currentPage.value > 0) {
    currentPage.value--
  }
}
const nextPage = () => {
  if (currentPage.value < totalPages.value - 1) {
    currentPage.value++
  }
}
// 重置分页(当系统列表变化时)
watch(state.platformList, () => {
  currentPage.value = 0
})
const openNoticeFile = async(path)=>{
  const ext = path.split('.').pop().toLowerCase();
  if (ext === 'doc' || ext === 'xls' || ext === 'xlsx') {
    ElMessageBox.confirm('暂不支持线上预览文件,是否下载查看?', '提示', { confirmButtonText: '确认', cancelButtonText: '取消', type: 'warning' }).then(() => {
      window.open(`${import.meta.env.VITE_APP_BASE_API}/${path}`, '_blank');
    }).catch(() => {
      console.log('取消预览')
    });
    return
  }
  if(ext === 'pdf'){
    window.open(`${import.meta.env.VITE_APP_BASE_API}/${path}`, '_blank')
    return
  }
  try {
    // 1. 获取文件
    const response = await fetch(import.meta.env.VITE_APP_BASE_API + '/' + path);
    const arrayBuffer = await response.arrayBuffer();
    // 2. 创建新窗口
    const win = window.open('', '_blank')
    win.document.write(`
      <!DOCTYPE html>
      <html>
        <head>
          <title>预览</title>
          <style>
            body { margin: 20px; font-family: Arial; }
            .docx-container { width: 100%; height: 100%; }
          </style>
        </head>
        <body>
          <div id="container" class="docx-container"></div>
        </body>
      </html>
    `);
    // 3. 渲染 DOCX
    await renderAsync(arrayBuffer, win.document.getElementById('container'));
  } catch (error) {
    console.error('预览失败:', error);
    alert(`预览失败: ${error.message}`);
  }
}
// 十二生肖数组
const zodiacAnimals = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪']
// 获取生肖年份
const getZodiacYear = (lunarYear) => {
  // 农历年份计算生肖:年份减去4后除以12取余数
  // 因为鼠年对应4,牛年对应5,以此类推
  const index = (lunarYear - 4) % 12
  return index >= 0 ? zodiacAnimals[index] : zodiacAnimals[index + 12]
}
// 计算当前日期信息
const currentWeekDay = computed(() => {
  const days = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
  return days[currentDate.value.getDay()]
})
const currentSolarDate = computed(() => {
  const date = currentDate.value
  return `${date.getMonth() + 1}月${date.getDate()}日`
})
const currentLunarDate = computed(() => {
  try {
    const date = currentDate.value
    const year = date.getFullYear()
    const month = date.getMonth() + 1
    const day = date.getDate()
    const lunar = lunarCalendar.solarToLunar(year, month, day)
    if (lunar && lunar.lunarMonthName && lunar.lunarDayName) {
      return `${lunar.lunarMonthName}${lunar.lunarDayName}`
    }
    return ''
  } catch (error) {
    console.error('获取农历日期错误:', error)
    return ''
  }
})
const currentLunarYear = computed(() => {
  try {
    const date = currentDate.value
    const year = date.getFullYear()
    const month = date.getMonth() + 1
    const day = date.getDate()
    const lunar = lunarCalendar.solarToLunar(year, month, day)
    if (lunar && lunar.GanZhiYear) {
      const zodiac = getZodiacYear(year)
      return `${lunar.GanZhiYear}年【${zodiac}年】`
    }
    return ''
  } catch (error) {
    console.error('获取农历年份错误:', error)
    return ''
  }
})
// 月份导航方法
const prevMonth = () => {
  const date = new Date(currentDate.value)
  date.setMonth(date.getMonth() - 1)
  currentDate.value = date
}
const nextMonth = () => {
  const date = new Date(currentDate.value)
  date.setMonth(date.getMonth() + 1)
  currentDate.value = date
}
const goToday = () => {
  currentDate.value = new Date()
}
// 添加备忘录
const addMemo = async () => {
  if (state.memo.content) {
    const data = {
      id: state.memo.id || null,
      companyId: state.noticeParams.companyId,
      content: state.memo.content,
      createById: userStore.id
    }
    const res = await updateMemo(data)
    if(res.code == 200){
      ElMessage.success('保存成功')
      await getMemo()
    }else{
      ElMessage.warning(res.message)
    }
    newMemo.value = ''
  }
}
// 获取公历日期(去掉前导零)
const getSolarDate = (dateString) => {
  const date = new Date(dateString)
  return date.getDate()
}
// 获取农历日期
const getLunarDate = (dateString) => {
  try {
    const date = new Date(dateString)
    const year = date.getFullYear()
    const month = date.getMonth() + 1
    const day = date.getDate()
    // 使用农历库获取农历信息
    const lunar = lunarCalendar.solarToLunar(year, month, day)
    if (lunar && lunar.lunarDayName) {
      // 如果是初一,显示月份,否则显示日期
      if (lunar.lunarDay === 1) {
        return lunar.lunarMonthName + '月'
      } else {
        // 简化显示,只显示数字日期
        const lunarDay = lunar.lunarDayName.replace('初', '').replace('十', '')
        return lunarDay
      }
    }
    return ''
  } catch (error) {
    console.error('获取农历日期错误:', error)
    return ''
  }
}
// 检查日期是否有事件
const hasEvent = (date) => {
  const eventDates = []
  return eventDates.includes(date)
}
// 检查是否是今天
const isToday = (dateString) => {
  const today = new Date().toISOString().split('T')[0]
  return dateString === today
}
// 检查是否是当前月份
const isCurrentMonth = (dateString) => {
  const date = new Date(dateString)
  const current = new Date(currentDate.value)
  return date.getMonth() === current.getMonth() && date.getFullYear() === current.getFullYear()
}
const handleCardEnter = (event, id) => {
  const card = event.currentTarget
  cardStates.value[id] = {
    ...cardStates.value[id],
    width: card.offsetWidth,
    height: card.offsetHeight,
    hover: true
  }
}
const handleCardMove = (event, id) => {
  if (!cardStates.value[id]?.hover) return
  const card = event.currentTarget
  const rect = card.getBoundingClientRect()
  cardStates.value[id].mouseX = event.clientX - rect.left - cardStates.value[id].width / 2
  cardStates.value[id].mouseY = event.clientY - rect.top - cardStates.value[id].height / 2
}
const handleCardLeave = (id) => {
  cardStates.value[id].hover = false;
  cardStates.value[id].mouseX = 0;
  cardStates.value[id].mouseY = 0;
}
const getCardStyle = (id) => {
  const state = cardStates.value[id] || {}
  const mousePX = state.mouseX / (state.width || 1)
  const mousePY = state.mouseY / (state.height || 1)
  const rX = mousePX * 20
  const rY = mousePY * -20
  const tX = mousePX * -20
  const tY = mousePY * -20
  return {
    transform: `rotateY(${rX}deg) rotateX(${rY}deg)`,
    '--bg-transform': `translateX(${tX}px) translateY(${tY}px)`
  }
}
function handleCommand(command) {
  switch (command) {
    case "info":
      getInfo();
      break;
    case "logout":
      logout();
      break;
    case "password":
      editPsd();
      break;
    default:
      break;
  }
}
const enterSystem = (address,index) => {
  if(index == 0){
    router.push({ path: "/learn/standardSysTemp/sysStandardModule"})
  }else{
    window.open(address)
    // ElMessage.warning('系统正在开发中...')
  }
}
function getInfo() {
  reviewRef.value.openDialog('view',userInfo.value)
}
function editPsd() {
  reviewRef.value.openDialog('pwd',userInfo.value)
}
function logout() {
  ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    removeToken()
    location.href = '/homePage';
  }).catch(() => { });
}
</script>
<style scoped lang="scss">
.system-select-container {
  height: 100vh;
  display: flex;
  flex-direction: column;
  background-color: #f5f7fa;
}
.user-info-bar {
  display: flex;
  height: 100px;
  align-items: center;
  justify-content: space-between;
  padding: 10px 20px;
  background-color: #ffffff;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  .user-left{
    width: 20%;
  }
  .user-details{
    width: 60%;
    margin: 0;
    color: #333;
    text-align: center;
    font-size: 28px;
    letter-spacing: 2px;
  }
  .avatar-container {
    width: 20%;
    display: flex;
    justify-content: center;
    .avatar-wrapper {
      position: relative;
      .user-avatar {
        cursor: pointer;
        width: 40px;
        height: 40px;
        border-radius: 10px;
        margin-right: 15px;
      }
      i {
        cursor: pointer;
        position: absolute;
        right: -20px;
        font-size: 12px;
      }
    }
  }
}
.systems-container {
  flex: 1;
  padding: 20px;
  overflow-y: auto;
  width: 100%;
}
.layout-container {
  display: flex;
  gap: 20px;
  width: 100%;
  height: 100%;
  max-width: none; /* 移除最大宽度限制 */
  margin: 0; /* 移除居中margin */
}
// 左侧列 - 宽度占比2
.left-column {
  flex: 2;
  display: flex;
  flex-direction: column;
  gap: 20px;
  min-width: 0; /* 防止内容溢出 */
}
// 右侧列 - 宽度占比1
.right-column {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 20px;
  min-width: 0; /* 防止内容溢出 */
  min-width: 400px; /* 设置最小宽度避免过窄 */
}
// 通用模块卡片样式
.module-card {
  background: #ffffff;
  border-radius: 8px;
  padding: 20px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  transition: box-shadow 0.3s ease;
  height: 100%;
  display: flex;
  flex-direction: column;
  &:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  }
}
.module-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid #f0f0f0;
  flex-shrink: 0; /* 防止header被压缩 */
  h3 {
    margin: 0;
    color: #333;
    font-size: 18px;
    font-weight: 600;
  }
  .more-link {
    color: #409eff;
    font-size: 14px;
    cursor: pointer;
    &:hover {
      color: #337ecc;
    }
  }
}
// 通知公告模块
.notice-module {
  flex: 1;
  .notice-list {
    flex: 1;
    overflow: hidden;
    overflow-y: auto;
    .notice-item {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 12px 0;
      border-bottom: 1px solid #f5f5f5;
      &:last-child {
        border-bottom: none;
      }
      .notice-title {
        color: #333;
        font-size: 16px;
        cursor: pointer;
        flex: 1;
        margin-right: 12px;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        &:hover {
          color: #409eff;
        }
      }
      .notice-date {
        color: #999;
        font-size: 14px;
        flex-shrink: 0;
      }
    }
  }
}
// 流程中心模块
.process-module {
  flex: 1;
  overflow-y: auto;
  .process-list {
    flex: 1;
    overflow-y: auto;
    .process-item {
      margin-bottom: 8px;
      .process-info {
        display: flex;
        align-items: center;
        padding-bottom: 8px;
        margin-bottom: 8px;
        box-sizing: border-box;
        border-bottom: 1px dashed #f0f0f0;
        cursor: pointer;
        .process-name {
          color: #333;
          font-size: 16px;
          flex: 1;
          &:hover {
            color: #409eff;
          }
        }
        .process-status {
          font-size: 12px;
          padding: 2px 8px;
          border-radius: 4px;
          flex-shrink: 0;
          margin-right: 10px;
          &.processing {
            background: #e6f7ff;
            color: #1890ff;
          }
          &.pending {
            background: #fff7e6;
            color: #fa8c16;
          }
          &.success {
            background: #edffdb;
            color: #52c41a;
          }
          &.normal {
            background: #ffebca;
            color: #ff6518;
          }
          &.seal {
            background: #ffe6e6;
            color: #ff1818;
          }
        }
      }
    }
  }
}
// 备忘录模块
.memo-module {
  flex: 1;
  .memo-content {
    flex: 1;
    display: flex;
    flex-direction: column;
    .memo-input {
      flex: 1;
      .memo-time{
        color: #999;
        font-size: 12px;
        text-align: right;
        margin-top: 10px;
      }
    }
  }
}
// 快捷入口模块
.quick-access-module {
  .module-header {
    .pagination-controls {
      display: flex;
      align-items: center;
      gap: 8px;
      .page-info {
        font-size: 12px;
        color: #666;
        min-width: 40px;
        text-align: center;
      }
      .el-button {
        width: 28px;
        height: 28px;
        display: flex;
        align-items: center;
        justify-content: center;
      }
    }
  }
  .systems-grid-container {
    flex: 1;
    overflow: hidden;
  }
  .systems-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(3, 1fr);
    gap: 16px;
    height: 100%;
    min-height: 400px; // 确保有足够的高度显示九宫格
    perspective: 1000px;
  }
  .system-card {
    position: relative;
    background-color: #ffffff;
    border-radius: 4px;
    transition: transform 1s cubic-bezier(0.23, 1, 0.32, 1),
    box-shadow 0.5s cubic-bezier(0.23, 1, 0.32, 1);
    transform-style: preserve-3d;
    cursor: pointer;
    overflow: hidden;
    min-height: 100px; // 确保卡片有最小高度
    // 空白卡片样式
    &.empty-card {
      opacity: 0.6;
      .system-icon {
        color: #ccc;
      }
      h3 {
        color: #999;
      }
      &:hover {
        transform: none;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
        &::after {
          border-color: transparent;
        }
        &::before {
          background: #fff;
        }
      }
    }
    &:hover{
      border-radius: 8px;
    }
    &::after {
      content: '';
      position: absolute;
      top: 4px;
      left: 4px;
      right: 4px;
      bottom: 4px;
      border-radius: 4px;
      border: 1px solid transparent;
      transition: border-color 0.3s ease;
      pointer-events: none;
      z-index: 3;
    }
    &::before {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      border-radius: 4px;
      background: #fff;
      transform: var(--bg-transform, translateX(0) translateY(0));
      transition: transform 0.5s cubic-bezier(0.23, 1, 0.32, 1);
      z-index: 1;
    }
    &:hover::after {
      border-color: rgba(37,99,235,1);
      border-radius: 6px;
      box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.5);
    }
    &:hover::before {
      border-radius: 8px;
      background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(20,20,20,0.05) 100%);
    }
  }
  .system-card:hover {
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15),
    0 0 0 1px rgba(255, 255, 255, 0.5) inset;
  }
  .card-content {
    position: relative;
    height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 12px;
    z-index: 2;
    transform-style: preserve-3d;
  }
  .system-icon {
    margin-bottom: 8px;
    .el-image {
      width: 40px;
      height: 40px;
      transition: transform 0.5s;
    }
    .el-icon {
      font-size: 40px;
      color: #ccc;
    }
  }
  .system-card:hover .system-icon img {
    transform: scale(1.2);
  }
  .system-card h3 {
    margin: 0;
    color: #333;
    font-size: 14px;
    text-align: center;
    transition: transform 0.3s;
    line-height: 1.2;
    font-weight: 500;
  }
  .system-card:hover h3 {
    transform: translateZ(10px);
  }
}
// 日历模块
.calendar-module {
  .calendar-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding-bottom: 16px;
    .lunar-info {
      width: 33.33%;
      display: flex;
      flex-direction: column;
      font-size: 16px;
      .lunar-date-full {
        color: #e6a23c;
        font-weight: 500;
      }
      .lunar-year {
        color: #999;
      }
    }
    .current-date-info {
      width: 33.33%;
      font-size: 16px;
      display: flex;
      flex-direction: column;
      align-items: center;
      .solar-date-large {
        color: #333;
        font-weight: 600;
      }
      .week-day {
        color: #666;
        margin-bottom: 4px;
        font-weight: 500;
      }
    }
    .calendar-actions {
      display: flex;
      justify-content: right;
      width: 33.33%;
      .el-button-group {
        display: flex;
        gap: 1px;
        .el-button {
          padding: 6px 12px;
          border-radius: 4px;
          &:first-child {
            border-top-right-radius: 0;
            border-bottom-right-radius: 0;
          }
          &:last-child {
            border-top-left-radius: 0;
            border-bottom-left-radius: 0;
          }
          &:not(:first-child):not(:last-child) {
            border-radius: 0;
          }
        }
      }
    }
  }
  .calendar-content {
    flex: 1;
    display: flex;
    flex-direction: column;
    :deep(.el-calendar) {
      border: none;
      flex: 1;
      display: flex;
      flex-direction: column;
      .el-calendar__header {
        padding: 0;
        flex-shrink: 0;
      }
      .el-calendar__body {
        flex: 1;
      }
      .el-calendar-table {
        height: 100%;
        .el-calendar-day {
          height: 60px;
          padding: 2px;
          text-align: center;
        }
        td.is-selected {
          background-color: transparent;
        }
        .el-calendar-day:hover {
          background-color: #f5f7fa;
        }
      }
    }
    .calendar-date {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: flex-start;
      height: 100%;
      position: relative;
      padding-top: 4px;
      .solar-date {
        font-size: 14px;
        font-weight: 500;
        color: #333;
        margin-bottom: 2px;
      }
      .lunar-date {
        font-size: 10px;
        color: #999;
        margin-bottom: 4px;
      }
      .calendar-event-dot {
        width: 4px;
        height: 4px;
        background: #409eff;
        border-radius: 50%;
        margin-bottom: 2px;
      }
      .today-indicator {
        position: absolute;
        top: 2px;
        right: 2px;
        width: 16px;
        height: 16px;
        background: #409eff;
        color: white;
        border-radius: 50%;
        font-size: 10px;
        display: flex;
        align-items: center;
        justify-content: center;
        line-height: 1;
      }
    }
    // 今天日期的样式
    .calendar-date.is-today {
      background-color: #ecf5ff;
      .solar-date {
        color: #409eff;
        font-weight: 600;
      }
      &::before {
        content: '';
        position: absolute;
        top: 2px;
        left: 50%;
        transform: translateX(-50%);
        width: 24px;
        height: 24px;
        background: #409eff;
        border-radius: 50%;
        z-index: -1;
        opacity: 0.1;
      }
    }
    .calendar-legend {
      display: flex;
      justify-content: center;
      gap: 16px;
      margin-top: 12px;
      padding-top: 12px;
      border-top: 1px solid #f0f0f0;
      flex-shrink: 0;
      .legend-item {
        display: flex;
        align-items: center;
        gap: 6px;
        .legend-dot {
          width: 8px;
          height: 8px;
          border-radius: 50%;
        }
        .event-dot {
          background: #409eff;
        }
        .today-dot {
          background: #409eff;
        }
        span {
          font-size: 12px;
          color: #666;
        }
      }
    }
  }
}
// 响应式设计
@media (max-width: 1200px) {
  .layout-container {
    flex-direction: column;
  }
  .left-column,
  .right-column {
    flex: 1;
    min-width: auto;
  }
}
// 超大屏幕优化
@media (min-width: 1920px) {
  .systems-container {
    padding: 30px 40px;
  }
  .layout-container {
    gap: 30px;
  }
  .module-card {
    padding: 25px;
  }
  // 在超大屏幕上可以显示3列的系统卡片
  .quick-access-module .systems-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}
</style>
src/views/work/qualityInfo/outsourcingCooperate/outsourcedProcessFlow/index.vue
@@ -30,10 +30,10 @@
    <!-- 表格数据 -->
    <el-table v-loading="loading" :data="dataList" :border="true">
      <el-table-column label="序号" type="index" align="center" width="80"/>
      <el-table-column label="供应商" prop="supplierName" align="center"/>
      <el-table-column label="工艺流程图" align="center">
      <el-table-column label="文件名称" prop="fileName" align="center"/>
      <el-table-column label="文件" align="center">
        <template #default="scope">
          <el-link type="primary" @click="openFile(scope.row.filePath)">{{scope.row.fileName !== '' ?scope.row.fileName + scope.row.format : '工艺流程图'}}</el-link>
          <el-link type="primary" @click="openFile(scope.row.filePath)">{{scope.row.fileName !== '' ?scope.row.fileName + scope.row.format : '文件'}}</el-link>
        </template>
      </el-table-column>
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" >