| | |
| | | <template> |
| | | <div class="system-select-container"> |
| | | <!-- 顶部用户信息栏 --> |
| | | <div class="user-info-bar"> |
| | | <div class="user-left"></div> |
| | | <h3 class="user-details">欢迎访问多体系建设信息化系统</h3> |
| | |
| | | </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> |
| | |
| | | </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 class="systems-grid"> |
| | | <div |
| | | 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.id)" |
| | | :style="getCardStyle(system.id)" |
| | | > |
| | | <div class="card-content"> |
| | | <div class="system-icon"> |
| | | <img :src="system.icon"/> |
| | | </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> |
| | | <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 { getToken, removeToken } from "@/utils/auth"; |
| | | 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 {ElMessage, ElMessageBox} from "element-plus"; |
| | | 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 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, |
| | |
| | | }) |
| | | }) |
| | | |
| | | 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] = { |
| | |
| | | } |
| | | } |
| | | |
| | | // 鼠标移动 |
| | | const handleCardMove = (event, id) => { |
| | | if (!cardStates.value[id]?.hover) return |
| | | |
| | |
| | | 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 |
| | |
| | | } |
| | | } |
| | | |
| | | |
| | | // 系统列表 |
| | | 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": |
| | |
| | | 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: '确定', |
| | |
| | | }).then(() => { |
| | | removeToken() |
| | | location.href = '/homePage'; |
| | | |
| | | }).catch(() => { }); |
| | | } |
| | | |
| | | |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | } |
| | | |
| | | |
| | | .systems-container { |
| | | flex: 1; |
| | | padding: 20px; |
| | | overflow-y: auto; |
| | | width: 100%; |
| | | margin-top: 60px; |
| | | } |
| | | |
| | | .layout-container { |
| | | display: flex; |
| | | gap: 20px; |
| | | width: 100%; |
| | | height: 100%; |
| | | max-width: none; /* 移除最大宽度限制 */ |
| | | margin: 0; /* 移除居中margin */ |
| | | .systems-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); |
| | | gap: 30px; |
| | | max-width: 1500px; |
| | | margin: 0 auto; |
| | | perspective: 1000px; |
| | | } |
| | | |
| | | // 左侧列 - 宽度占比2 |
| | | .left-column { |
| | | flex: 2; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | min-width: 0; /* 防止内容溢出 */ |
| | | } |
| | | .system-card { |
| | | position: relative; |
| | | height: 280px; |
| | | 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; |
| | | |
| | | // 右侧列 - 宽度占比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; |
| | | &:hover{ |
| | | border-radius: 16px; |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | } |
| | | &::after { |
| | | content: ''; |
| | | position: absolute; |
| | | 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; /* 确保在内容之上 */ |
| | | } |
| | | |
| | | .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; |
| | | &::before { |
| | | content: ''; |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | 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%); |
| | | } |
| | | 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; |
| | | } |
| | | |
| | | .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; |
| | | &:hover::after { |
| | | border-color: rgba(37,99,235,1); /* 使用蓝色描边 */ |
| | | border-radius: 12px; |
| | | box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.5); /* 可选:添加内发光效果 */ |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | } |
| | | } |
| | | &:hover::before { |
| | | border-radius: 16px; |
| | | background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(20,20,20,0.05) 100%); |
| | | } |
| | | } |
| | | |
| | | // 响应式设计 |
| | | @media (max-width: 1200px) { |
| | | .layout-container { |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .left-column, |
| | | .right-column { |
| | | flex: 1; |
| | | min-width: auto; |
| | | } |
| | | .system-card:hover { |
| | | //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%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 25px; |
| | | z-index: 2; |
| | | transform-style: preserve-3d; |
| | | } |
| | | |
| | | // 超大屏幕优化 |
| | | @media (min-width: 1920px) { |
| | | .systems-container { |
| | | padding: 30px 40px; |
| | | } |
| | | .system-icon img { |
| | | width: 80px; |
| | | height: 80px; |
| | | margin-bottom: 30px; |
| | | transition: transform 0.5s; |
| | | } |
| | | |
| | | .layout-container { |
| | | gap: 30px; |
| | | } |
| | | .system-card:hover .system-icon img { |
| | | transform: scale(1.4); |
| | | } |
| | | |
| | | .module-card { |
| | | padding: 25px; |
| | | } |
| | | .system-card h3 { |
| | | margin: 0 0 12px; |
| | | color: #333; |
| | | font-size: 24px; |
| | | text-align: center; |
| | | transition: transform 0.3s; |
| | | } |
| | | |
| | | // 在超大屏幕上可以显示3列的系统卡片 |
| | | .quick-access-module .systems-grid { |
| | | grid-template-columns: repeat(3, 1fr); |
| | | } |
| | | .system-card p { |
| | | margin: 0; |
| | | color: #999; |
| | | font-size: 15px; |
| | | text-align: center; |
| | | transition: transform 0.3s; |
| | | } |
| | | |
| | | .system-card:hover h3, |
| | | .system-card:hover p { |
| | | transform: translateZ(20px); |
| | | } |
| | | </style> |