| src/views/menuPage.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/work/menuIndex/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/work/qualityInfo/outsourcingCooperate/outsourcedProcessFlow/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | 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" >