window.error “Script error”问题跟进

本文探讨了跨域脚本错误(Script Error)的问题,尤其是在使用window.onerror监听时遇到的'Script error'信息。分析了这一现象在不同浏览器中的表现及背后的安全考量,并提供了相应的解决策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://www.webryan.net/2012/12/something-about-window-onerror-script-error/


话题背景:

为了提高web开发质量,我们通常会在web前端页面里注册window.onerror事件,然后将统一的脚本错误信息发送到服务器汇总和统计,每天计算出错误排行榜,以方便我们跟踪和解决问题。

window.onerror = function(msg,url,line,row){

$.badjs(msg,url,line,row,上报ID); //创建图片请求,并加上一些前台需要搜集的信息

}

但是最近统计发现有大量的Script error信息,排 行第一且没有给出具体的错误内容。

error

 

出现“Script error”的场景和条件:

1、通过window.onerror注册监听脚本错误事件

2、浏览器:Firefox、Chrome、Safari、Opera等浏览器历史版本

3、页面内使用了script标签引入,非同域的JS或者HTML;且出现脚本错误。

Eg: http://zc.qq.com/index.html 里 <script src=”http://1.url.cn/b/js/10001/simple.js“></script>

 

出现“Script error”的原因(只列举webkit;ff同理):

 

code

 

不难看出,在出发脚本错误提示的时候,通过secureityOrigin()里的方法判断了是否是同源可用。不是的话,就生成massage为“Script error”,错误行号为0的信息。

 

 浏览器限制同源脚本错误背景分析:

本质上是出于安全考虑。因为script引入文件内容的时候是忽略文件本身的MIME声明,且是允许跨域请求的。这里如果不屏蔽掉跨域情况下的错误信息,很可能会给黑客提供一个攻击通道。

比如:

1、qq.com有个页面pay.html(在有登录态的情况下)会有两种显示内容:“您当前的余额为0元”或“您当前的余额为100元”

2、hacker.com/index.html页面里通过script引入了这个http://qq.com/pay.html的话

那么黑客就可以在hacker.com上获取到错误:“Uncaught ReferenceError: 你的余额为100 is not defined|http://hacker.com/index.html|1”。此时黑客已经间接的获取到了他想要的信息,那么黑客可以通过遍历等方式将信息最大化。

 

应对办法:

目前Firefox 13+,Chrome 2012.12.9号之后版本均已经支持:通过设置同源策略来显示错误信息。

a、设置 script 标签的 crossorigin

Eg:    <script src=”http://somremotesite.example/script.js” crossorigin></script>

b、设置javascript文件的HTTP头

Access-Control-Allow-Origin: http://qq.com

对于使用CEF(Chromium Embedded Framework)的项目可以直接更新代码或手动修改代码:

Source/WebCore/dom/ScriptExecutionContext.cpp ,301-309,

 

301

    if (securityOrigin()->canRequest(targetUrl)) {

302

        message = errorMessage;

303

        line = lineNumber;

304

        sourceName = sourceURL;

305

    } else {

306

        message = ”Script error.”;

307

        sourceName = String();

308

        line = 0;

309

    }

310

 

 

 

301

    message = errorMessage;

302

    line = lineNumber;

303

    sourceName = sourceURL;

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>关爱展厅</title> <script src="https://cdn.tailwindcss.com"></script> <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"> <style> body { background: #0f172a; min-height: 100vh; display: flex; justify-content: center; align-items: center; font-family: 'Inter', sans-serif; overflow: hidden; } .gallery-container { perspective: 1000px; width: 100%; height: 100vh; display: flex; justify-content: center; align-items: center; } .carousel { position: relative; width: 300px; height: 400px; transform-style: preserve-3d; transition: transform 0.5s ease; } .carousel-item { position: absolute; width: 100%; height: 100%; backface-visibility: hidden; border-radius: 12px; overflow: hidden; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); transition: transform 0.5s ease, box-shadow 0.5s ease; cursor: pointer; z-index: 5; } .carousel-item:hover { box-shadow: 0 15px 40px rgba(0, 0, 0, 0.5); transform: translateZ(calc(400px + 10px)); /* 悬停时向前移动 */ } .carousel-item:active { transform: translateZ(calc(400px)) scale(0.98); } .carousel-item img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.5s ease; } .carousel-item:hover img { transform: scale(1.05); } .carousel-info { position: absolute; bottom: 0; left: 0; right: 0; padding: 20px; background: linear-gradient(to top, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0) 100%); transform: translateY(20px); opacity: 0; transition: transform 0.3s ease, opacity 0.3s ease; z-index: 10; } .carousel-item:hover .carousel-info { transform: translateY(0); opacity: 1; } .carousel-title { font-size: 1.5rem; font-weight: bold; margin-bottom: 5px; color: white; } .carousel-desc { font-size: 0.9rem; color: rgba(255, 255, 255, 0.8); } .controls { position: fixed; bottom: 50px; left: 50%; transform: translateX(-50%); display: flex; gap: 20px; z-index: 20; } .control-btn { background: rgba(255, 255, 255, 0.1); color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; transition: background 0.3s ease; } .control-btn:hover { background: rgba(255, 255, 255, 0.2); } #activityBtn { position: fixed; top: 20px; right: 20px; background: rgba(255, 255, 255, 0.1); color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; transition: background 0.3s ease; z-index: 20; } #activityBtn:hover { background: rgba(255, 255, 255, 0.2); } #activityModal { display: none; position: fixed; z-index: 100; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0, 0, 0, 0.7); } .modal-content { background-color: #1e293b; margin: 10% auto; padding: 20px; border-radius: 10px; width: 80%; max-width: 600px; color: white; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); } .close { color: #aaa; float: right; font-size: 28px; font-weight: bold; } .close:hover, .close:focus { color: white; text-decoration: none; cursor: pointer; } .activity-list { margin-top: 20px; } .activity-item { padding: 15px 0; border-bottom: 1px solid rgba(255, 255, 255, 0.1); } .activity-item:last-child { border-bottom: none; } .activity-date { font-weight: bold; color: #94a3b8; } @media (max-width: 600px) { .carousel { width: 250px; height: 350px; } .modal-content { width: 90%; margin: 5% auto; } #activityBtn { top: 10px; right: 10px; padding: 8px 15px; font-size: 0.9rem; } } </style> </head> <body> <div class="gallery-container"> <div id="carousel" class="carousel"> <!-- 图片将通过JS动态添加 --> </div> </div> <div class="controls"> <button id="prevBtn" class="control-btn"> <i class="fa fa-arrow-left mr-2"></i>上一张 </button> <button id="nextBtn" class="control-btn"> 下一张<i class="fa fa-arrow-right ml-2"></i> </button> </div> <button id="activityBtn" class="control-btn"> <i class="fa fa-calendar mr-2"></i>活动清单 </button> <div id="activityModal" class="modal"> <div class="modal-content"> <span class="close">×</span> <h2 class="text-xl font-bold mb-4">7月活动清单</h2> <div class="activity-list"> <!-- 活动列表将通过JS动态生成 --> </div> </div> </div> <script> document.addEventListener('DOMContentLoaded', () => { const carousel = document.getElementById('carousel'); const prevBtn = document.getElementById('prevBtn'); const nextBtn = document.getElementById('nextBtn'); const activityBtn = document.getElementById('activityBtn'); const activityModal = document.getElementById('activityModal'); const closeBtn = document.querySelector('.close'); const itemsCount = 5; const radius = 400; let currentAngle = 0; const angleStep = 360 / itemsCount; // 优化后的主题数据,确保每个主题都有合适的图片和描述 const communicationRecords = [ { id: 1001, title: "员工生病探望", participants: ["人力资源部: 王经理", "部门主管: 张主管"], content: "前往医院探望生病住院的员工,带去公司关怀和慰问品,了解康复情况并协调工作安排。", time: "2023-06-15", location: "市中心医院", followUp: "人力资源部持续跟进员工康复进度,部门安排同事临时接手相关工作。", topic: "生病看望", imageId: 1005 // 医疗相关图片 }, { id: 1002, title: "新员工入职培训", participants: ["人力资源部: 李专员", "新员工: 陈新", "导师: 赵工"], content: "为新员工安排入职培训,介绍公司文化、规章制度和工作流程,导师制定个性化培养计划。", time: "2023-06-20", location: "公司培训室", followUp: "导师将在3个月内指导新员工,人力资源部1个月后进行培训效果评估。", topic: "新员工融入", imageId: 1006 // 教育相关图片 }, { id: 1003, title: "6月员工生日会", participants: ["行政部: 刘主管", "6月寿星: 全体成员"], content: "举办6月员工生日会,庆祝员工生日,分享蛋糕和礼物,增强团队凝聚力。", time: "2023-06-25", location: "公司会议室", followUp: "行政部收集员工反馈,持续优化生日关怀活动形式。", topic: "生日关怀", imageId: 1003 // 庆祝相关图片 }, { id: 1004, title: "端午节福利发放", participants: ["行政部: 全体成员", "各部门代表"], content: "为全体员工发放端午节福利,包括粽子、咸鸭蛋等传统节日礼品,表达节日问候。", time: "2023-06-22", location: "公司大厅", followUp: "行政部持续关注员工福利需求,准备其他节日福利方案。", topic: "节日福利", imageId: 1004 // 食物相关图片 }, { id: 1005, title: "Q3重点项目分工", participants: ["项目经理: 周经理", "开发团队: 王工", "测试团队: 孙工"], content: "分配第三季度重点项目任务,明确各成员职责和时间节点,确保项目顺利推进。", time: "2023-07-01", location: "公司会议室", followUp: "各负责人制定详细计划,每周进行项目进度汇报,及时解决遇到的问题。", topic: "任务分派", imageId: 1002 // 商务相关图片 } ]; const currentMonthActivities = [ { date: "2023-07-05", title: "下半年战略规划会议", type: "会议", location: "公司大会议室", participants: "全体部门经理" }, { date: "2023-07-10", title: "员工户外拓展训练", type: "团队活动", location: "城市郊外拓展基地", participants: "全体员工" }, { date: "2023-07-15", title: "客户满意度调研分析", type: "分析会", location: "公司会议室A", participants: "市场部、客服部" }, { date: "2023-07-20", title: "季度绩效考核评估", type: "评估", location: "各部门会议室", participants: "各部门主管及员工" }, { date: "2023-07-25", title: "产品创新头脑风暴", type: "研讨会", location: "公司创意空间", participants: "产品部、研发部、设计部" } ]; function createCarouselItems() { for (let i = 0; i < itemsCount; i++) { const angle = (i * angleStep) * (Math.PI / 180); const item = document.createElement('div'); item.className = 'carousel-item'; item.style.transform = `rotateY(${i * angleStep}deg) translateZ(${radius}px)`; item.dataset.index = i; item.dataset.topic = communicationRecords[i].topic; const img = document.createElement('img'); // 使用优化后的图片ID,确保每张图片都有意义 img.src = `https://picsum.photos/id/${communicationRecords[i].imageId}/600/800`; img.alt = communicationRecords[i].title; const info = document.createElement('div'); info.className = 'carousel-info'; const title = document.createElement('h3'); title.className = 'carousel-title'; title.textContent = communicationRecords[i].title; const desc = document.createElement('p'); desc.className = 'carousel-desc'; desc.textContent = communicationRecords[i].participants.join('、'); info.appendChild(title); info.appendChild(desc); item.appendChild(img); item.appendChild(info); carousel.appendChild(item); item.addEventListener('click', () => { const topic = encodeURIComponent(communicationRecords[i].topic); console.log(`跳转到主题: ${topic}`); item.style.transform = `rotateY(${i * angleStep}deg) translateZ(${radius - 10}px) scale(0.98)`; setTimeout(() => { item.style.transform = `rotateY(${i * angleStep}deg) translateZ(${radius}px)`; // 注意:这里使用了相对路径,实际使用时请根据你的项目结构调整 window.location.href = `./photo-wall.html?topic=${topic}`; }, 150); }); } } function createActivityList() { const activityList = document.querySelector('.activity-list'); activityList.innerHTML = ''; currentMonthActivities.forEach(activity => { const activityItem = document.createElement('div'); activityItem.className = 'activity-item'; const dateElement = document.createElement('div'); dateElement.className = 'activity-date'; dateElement.textContent = activity.date; const titleElement = document.createElement('h3'); titleElement.className = 'text-lg font-semibold mt-1'; titleElement.textContent = activity.title; const typeElement = document.createElement('div'); typeElement.className = 'text-sm text-blue-400 mt-1'; typeElement.textContent = activity.type; const locationElement = document.createElement('div'); locationElement.className = 'text-sm text-gray-300 mt-1'; locationElement.innerHTML = `<i class="fa fa-map-marker mr-1"></i> ${activity.location}`; const participantsElement = document.createElement('div'); participantsElement.className = 'text-sm text-gray-300 mt-1'; participantsElement.innerHTML = `<i class="fa fa-users mr-1"></i> ${activity.participants}`; activityItem.appendChild(dateElement); activityItem.appendChild(titleElement); activityItem.appendChild(typeElement); activityItem.appendChild(locationElement); activityItem.appendChild(participantsElement); activityList.appendChild(activityItem); }); } function rotateCarousel(angle) { carousel.style.transform = `rotateY(${angle}deg)`; } prevBtn.addEventListener('click', () => { currentAngle += angleStep; rotateCarousel(currentAngle); }); nextBtn.addEventListener('click', () => { currentAngle -= angleStep; rotateCarousel(currentAngle); }); let isDragging = false; let startX, startRotation; document.addEventListener('mousedown', (e) => { if (!e.target.closest('.carousel-item')) { isDragging = true; startX = e.clientX; startRotation = currentAngle; document.body.style.cursor = 'grabbing'; } }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; const diffX = e.clientX - startX; currentAngle = startRotation - diffX * 0.5; rotateCarousel(currentAngle); }); document.addEventListener('mouseup', () => { isDragging = false; document.body.style.cursor = 'default'; }); activityBtn.addEventListener('click', () => { createActivityList(); activityModal.style.display = 'block'; }); closeBtn.addEventListener('click', () => { activityModal.style.display = 'none'; }); window.addEventListener('click', (e) => { if (e.target === activityModal) { activityModal.style.display = 'none'; } }); createCarouselItems(); rotateCarousel(currentAngle); }); </script> </body> </html> 检查代码中存在的问题
07-11
Error: Please import the top-level fullcalendar lib before attempting to import a plugin.下面是我的代码,并且使用的版本一致<template> <!-- 办公-我的日程页面 --> <div class="divBox"> <el-card body-style="padding:0 0 0 0; " class="normal-page"> <div class="header"> <span class="title">我的日程</span> <el-button type="primary" size="small" :icon="Plus" @click="addSchedule"> {{ $t('calendar.newschedule') }} </el-button> </div> <el-row :gutter="14"> <el-col :span="7"> <calendar-bar ref="calendarBar" @handle-date="handleDate" /> </el-col> <el-col :span="17" class="line" v-loading="loading" element-loading-text="数据正在加载中"> <div class="plan-tabs-content"></div> <!-- 我的日历 --> <div class="needToBeDealt"> <div class="add-btn"> <div class="content-right"></div> <div class="content-button"> <el-radio-group v-model="time" size="small" @change="selectChange"> <el-radio-button v-for="(itemn, indexn) in fromList" :key="indexn" :label="itemn.val" > {{ itemn.text }} </el-radio-button> </el-radio-group> </div> </div> <FullCalendar ref="calendar" id="calendar" style="height: calc(100vh - 150px)" :options="calendarOptions"> <template v-slot:slotLabelContent="arg"> <div class="calendar-timeline"> <!-- <span v-if="moment(arg.date).format('HH:mm') !== '00:00'">--> <!-- {{ moment(arg.date).format('HH:mm') }}--> <!-- </span>--> </div> </template> <template v-slot:dayHeaderContent="arg"> <div class="header-box"> <div class="day-header" :class="period == 1 ? 'ml30' : ''"> <!-- <span class="week">{{ getWeek(arg.date) }}</span>--> <!-- <span class="date" v-if="period !== 3">{{ moment(arg.date).format('D') }}</span>--> </div> </div> </template> <template v-slot:eventContent="arg"> <div class="item" :style="{ background: period == 3 ? getColorFn(arg.event.textColor, '0.1') : arg.event.color }" > <div class="flex over-title" @click="handleEventClick(arg)"> <img v-if="period !== 2" :src="arg.event.extendedProps.avatar" class="img" alt="" /> <div class="over-title" :style="{ color: period == 3 ? arg.event.textColor : arg.event.textColor }" > {{ arg.event.title }} </div> </div> <template v-if="arg.event.extendedProps.show > -1"> <span v-if="arg.event.extendedProps.finish == 2" class="el-icon-error" :style="{ color: arg.event.textColor }" ></span> <span v-else-if="arg.event.extendedProps.finish == 3" @click="putStatus('取消完成', 1, arg.event)" class="el-icon-success" :style="{ color: arg.event.textColor }" ></span> <span v-else class="yuan" :style="{ 'border-color': arg.event.textColor }" @click="putStatus('完成', 3, arg.event, arg)" ></span> </template> </div> </template> </FullCalendar> </div> </el-col> </el-row> </el-card> <!-- 通用弹窗表单 --> <add-todo ref="addTodo" @get-list="getList" :left-time="leftTime"></add-todo> <calendar-details ref="calendarDetails" @delete-fn="getList" @edit-fn="editFn" :date-info="dateInfo" ></calendar-details> <contract-dialog ref="contractDialog" :config="configContract" @is-ok="getList"></contract-dialog> <!-- 跟进弹窗 --> <el-dialog title="添加跟进记录" class="record" v-model="dialogVisible" width="40%"> <record-upload :form-info="formInfo" @change="recordChange"></record-upload> </el-dialog> <edit-examine ref="editExamine" :parameter-data="parameterData" :ids="formInfo.data.id" @schedule-record="scheduleRecord" ></edit-examine> </div> </template> <script> import FullCalendar from '@fullcalendar/vue3' // 核心库必须第一个导入 import dayGridPlugin from '@fullcalendar/daygrid' import timeGridPlugin from '@fullcalendar/timegrid' import interactionPlugin from '@fullcalendar/interaction' import listPlugin from '@fullcalendar/list' import { defineComponent, defineAsyncComponent, ref, reactive, onMounted, computed, toRefs, nextTick } from 'vue'; import { Plus } from '@element-plus/icons-vue'; //import { ElMessage } from 'element-plus'; import moment from 'moment'; // 正确顺序:先核心库再插件 // 样式导入(必须在插件之后) // import '@fullcalendar/common/main.css' // import '@fullcalendar/daygrid/main.css' // import '@fullcalendar/timegrid/main.css' // import '@fullcalendar/list/main.css' console.log(window.FullCalendar); // 检查全局对象 console.log(window.FullCalendarCore); // 如果你手动挂载了核心库 // 根据实际需要取消注释这些API导入 // import { // clientRemindDetailApi, // scheduleListApi, // scheduleStatusApi // } from '@/api/enterprise'; // import { configRuleApproveApi } from '@/api/config'; // import { toGetWeek, getColor } from '@/utils/format'; export default defineComponent({ name: 'WorkDealt', components: { CalendarBar: defineAsyncComponent(() => import('./components/calendarBar.vue')), AddTodo: defineAsyncComponent(() => import('./components/addTodo')), FullCalendar, CalendarDetails: defineAsyncComponent(() => import('./components/calendarDetails')) // ContractDialog: defineAsyncComponent(() => import('@/views/customer/contract/components/contractDialog')), // RecordUpload: defineAsyncComponent(() => import('@/views/customer/list/components/recordUpload')), // EditExamine: defineAsyncComponent(() => import('@/views/user/examine/components/editExamine')) }, setup() { const state = reactive({ loading: false, calendarApi: null, title: '', time: '1', cid: null, dialogVisible: false, configContract: {}, formInfo: { avatar: '', type: 'add', show: 1, data: {}, follow_id: 0 }, fromList: computed(() => [ {text: '日', val: '1'}, {text: '周', val: '2'}, {text: '月', val: '3'} ]), start_time: '', end_time: '', dateInfo: {}, type: [], period: 1, calendarOptions: { height: 'calc(100vh - 232px)', eventColor: '', initialDate: moment().format('YYYY-MM-DD HH:mm:ss'), plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin, listPlugin], handleWindowResize: true, displayEventTime: false, slotDuration: '00:60:00', scrollTime: '05:30:00', slotMinTime: '00:00:00', slotMaxTime: '24:00:00', headerToolbar: { left: '', center: 'prev title next', right: '' }, buttonText: { month: '月', week: '周', day: '天' }, allDaySlot: true, dayMaxEventRows: 6, allDayText: '', moreLinkContent: state => `还有${state.num}个日程`, weekends: true, nowIndicator: true, weekNumbers: false, slotLabelFormat: { hour: '2-digit', minute: '2-digit', meridiem: false, hour12: false }, selectable: true, displayEventEnd: false, initialView: 'timeGridDay', dateClick: (e) => handleDateClick(e), customButtons: { next: { text: 'PREV', click: () => next() }, prev: { text: 'PREV', click: () => prev() } }, events: [], slotEventOverlap: false, eventOverlap: false, locale: 'zh-cn', weekNumberCalculation: 'ISO' }, detailedData: {}, parameterData: { contract_id: '', customer_id: '', invoice_id: '', bill_id: '' }, leftTime: '', buildData: [], item: {}, status: 0 }) const calendarBar = ref(null); const addTodo = ref(null); const calendarDetails = ref(null); const contractDialog = ref(null); const editExamine = ref(null); const userId = ref(null); // 日期更新函数 const updateCalendarDates = () => { if (!state.calendarApi) return; state.title = moment(state.calendarApi.view.currentStart).format('YYYY-MM-DD'); state.start_time = state.title + ' 00:00:00'; state.end_time = state.title + ' 23:59:59'; }; onMounted(() => { const userInfo = JSON.parse(localStorage.getItem('userInfo')); if (userInfo) { userId.value = userInfo.userId; state.formInfo.avatar = userInfo.avatar; } // 使用 nextTick 确保在 DOM 更新后执行 nextTick(() => { if (calendarBar.value && calendarBar.value.getApi) { state.calendarApi = calendarBar.value.getApi(); dayRender(); updateCalendarDates(); } }); }); // 原始方法开始(保持所有方法逻辑不变) // const handleEventClick = e => { calendarDetails.value.open(e.event.extendedProps); } const recordChange = () => { state.dialogVisible = false; getList(); } const getConfigApprove = async () => { // 实际项目中取消注释 // const result = await configRuleApproveApi(0); // state.buildData = result.data; } const putStatus = async (text, status, item) => { const {extendedProps} = item; state.item = extendedProps; const bill_id = extendedProps.bill_id; switch (extendedProps.cid_value) { case 3: case 4: const id = bill_id.bill_id !== 0 ? bill_id.bill_id : bill_id.remind_id; if (status == 3) { // 实际项目中取消注释 // clientRemindDetailApi(id).then((res) => { // res.data.id = bill_id.remind_id; // state.parameterData.customer_id = res.data.eid; // state.parameterData.contract_id = res.data.cid; // // const switchType = res.data.types === 0 ? 'contract_refund_switch' : 'contract_renew_switch'; // const data = {id: state.buildData[switchType]}; // editExamine.value.open(data, res.data.cid, switchType, item, status); // }) } break; case 2: if (extendedProps.finish === 3) return false; state.formInfo.follow_id = bill_id ? bill_id.follow_id : 0; state.formInfo.data.eid = extendedProps.link_id; state.dialogVisible = true; break; default: scheduleRecord(extendedProps, status); } } const scheduleRecord = async (data = state.item, status = state.status) => { const {start_time: start, end_time: end, itemId: id} = data; const info = {status, start, end}; // 实际项目中取消注释 // await scheduleStatusApi(id, info); await getList(); } const handleDateClick = e => { state.detailedData = { startDate: moment(e.date).format('YYYY-MM-DD'), startTime: moment(e.date).format('HH:mm:ss'), endDate: moment(e.date).format('YYYY-MM-DD'), time: moment(e.date).format('YYYY-MM-DD HH:mm:ss') } addTodo.value.open(state.detailedData); } const dayRender = () => { document.documentElement.style.setProperty(`--fc-today-bg-color`, '#fff'); let dayTime = document.querySelectorAll('.fc-timegrid-slot-label-cushion'); dayTime[0]?.classList.add('fcTime'); document.querySelectorAll('.fc-button-primary').forEach(li => li.setAttribute('title', '')); } const day = () => { state.period = 1; state.calendarApi.changeView('timeGridDay'); getList(); removeTitle(); dayRender(); } const month = () => { state.period = 3; state.calendarApi.changeView('dayGridMonth'); getList(); removeTitle(); } const week = () => { state.period = 2; state.calendarApi.changeView('timeGridWeek'); getList(); removeTitle(); dayRender(); } const prev = () => { state.calendarApi.prev(); updateCalendarDates(); getList(); if (state.period === 2) { const date = calendarBar.value.value; calendarBar.value.value = moment(date).subtract(7, 'd').format('YYYY-MM-DD'); } else { calendarBar.value.value = moment(state.calendarApi.view.currentStart).format('YYYY-MM-DD'); } } const next = () => { state.calendarApi.next(); updateCalendarDates(); getList(); if (state.period == 2) { let date = calendarBar.value.value; calendarBar.value.value = moment(date).add(7, 'd').format('YYYY-MM-DD'); } else { calendarBar.value.value = moment(state.calendarApi.view.currentStart).format('YYYY-MM-DD'); } } const getList = () => { // 实际项目中取消注释 // scheduleListApi().then(res => { // let newArr = [] // res.data.forEach(item => { // // ...处理逻辑... // }) // state.calendarOptions.events = newArr // }) calendarBar.value?.getList(); } const findItem = (arr, key, val) => { for (var i = 0; i < arr.length; i++) { if (arr[i].id === val || arr[i].id == userId.value) { return 2; } } return -1; } // const getWeek = date => toGetWeek(date); const editFn = (id, type, date) => { let data = { id, type, edit: true, date } addTodo.value.openBox(data); } const handleDate = (data, val) => { state.cid = data.type; state.leftTime = data.time; if (val) { state.calendarApi.gotoDate(data.time); } setTimeout(() => { state.calendarApi = calendarBar.value.getApi(); state.time = 1; day(); getList(); }, 300); } const selectChange = e => { state.time = e; if (state.time == 1) { day(); } else if (state.time == 2) { week(); } else { month(); } } const addSchedule = () => addTodo.value.open(); // 临时 getColorFn 实现 - 实际项目中应该使用真实实现 const getColorFn = (thisColor, thisOpacity) => { // 如果是透明背景 if (state.period === 3) { // RGBA 格式 const r = parseInt(thisColor.slice(1, 3), 16); const g = parseInt(thisColor.slice(3, 5), 16); const b = parseInt(thisColor.slice(5, 7), 16); return `rgba(${r}, ${g}, ${b}, ${thisOpacity})`; } return thisColor; } const removeTitle = () => { setTimeout(() => { document.querySelectorAll('.fc-daygrid-day-bottom a').forEach(li => li.title = ''); }, 300); } return { ...toRefs(state), Plus, calendarBar, addTodo, calendarDetails, contractDialog, editExamine, handleEventClick, recordChange, putStatus, scheduleRecord, handleDateClick, dayRender, day, month, week, prev, next, getList, findItem, // getWeek, editFn, handleDate, selectChange, addSchedule, getColorFn, removeTitle } } }) </script> <style lang="scss" scoped> .divBox { :deep(.fc-header-toolbar) { position: absolute; top: 0; left: 0; } .header { padding: 16px 20px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #dcdfe6; .title { font-weight: 500; font-size: 18px; color: #303133; } } .day-header { display: flex; flex-direction: column; justify-content: center; height: 52px; font-family: PingFang SC-Regular, PingFang SC; line-height: 20px; .week { font-size: 13px; color: #909399; } .date { font-size: 18px; color: #606266; font-weight: 800; } } .ml30 { margin-left: -30px; } :deep(.fc-timegrid-slot-label-cushion) { color: #909399; font-size: 12px; } .line { border-left: 1px solid #f0f2f5; } .item { width: 100%; display: flex; font-size: 14px; font-family: PingFang SC-Regular, PingFang SC; justify-content: space-between; align-items: center; padding-right: 4px; border-radius: 13px; .img { flex-shrink: 0; width: 18px; height: 18px; border-radius: 50%; margin-right: 5px; object-fit: cover; } .yuan { width: 12px; height: 12px; border-radius: 50px; border: 1px solid #fff; } } .over-title { width: 98% !important; overflow: hidden !important; white-space: nowrap; text-overflow: ellipsis !important; } } </style>
06-07
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值