<input type = file/>上传图片限制大小、类型判断、像素判断

本文介绍如何使用JavaScript验证用户上传的图片文件,包括类型验证、大小限制及尺寸检查的方法。

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

在项目中经常用到input标签来上传文件,而这些文件通常是图片文件。图片有很多格式我们只需要其中的几种,就需要对用户上传的文件进行验证,在HTML5中有一个新的属性:accept文件类型限制。但是通常我们会用javascript或jQuery编写方法进行验证图片的大小限制、类型判断、像素判断。

<input type="file" name="files" id="file" onchange="verificationPicFile(this)">
//图片类型验证
function verificationPicFile(file) {
    var fileTypes = [".jpg", ".png"];
    var filePath = file.value;
    //当括号里面的值为0、空字符、false 、null 、undefined的时候就相当于false
    if(filePath){
        var isNext = false;
        var fileEnd = filePath.substring(filePath.indexOf("."));
        for (var i = 0; i < fileTypes.length; i++) {
            if (fileTypes[i] == fileEnd) {
                isNext = true;
                break;
            }
        }
        if (!isNext){
            alert('不接受此文件类型');
            file.value = "";
            return false;
        }
    }else {
        return false;
    }
}
//图片大小验证
function verificationPicFile(file) {
    var fileSize = 0;
    var fileMaxSize = 1024;//1M
    var filePath = file.value;
    if(filePath){
        fileSize =file.files[0].size;
        var size = fileSize / 1024;
        if (size > fileMaxSize) {
            alert("文件大小不能大于1M!");
            file.value = "";
            return false;
        }else if (size <= 0) {
            alert("文件大小不能为0M!");
            file.value = "";
            return false;
        }
    }else{
        return false;
    }
}
//图片尺寸验证
function verificationPicFile(file) {
    var filePath = file.value;
    if(filePath){
        //读取图片数据
        var filePic = file.files[0];
        var reader = new FileReader();
        reader.onload = function (e) {
            var data = e.target.result;
            //加载图片获取图片真实宽度和高度
            var image = new Image();
            image.onload=function(){
                var width = image.width;
                var height = image.height;
                if (width == 720 | height == 1280){
                    alert("文件尺寸符合!");
                }else {
                    alert("文件尺寸应为:720*1280!");
                    file.value = "";
                    return false;
                }
            };
            image.src= data;
        };
        reader.readAsDataURL(filePic);
    }else{
        return false;
    }
}




<html><head><meta http-equiv="Content-Type" content="text/html; charset= =iso-8859-1"> <title>Trioptics Certificate</title> <link type="text/css" rel="stylesheet" href="certificate.css"></link> <base href="file:///C:/Program%20Files%20(x86)/TRIOPTICS%20GmbH/MTF-LAB%2= 05/Certificates/"></base></head> <body id="IBodyCert" class="CBodyCert"> <!-- open certificate table --> <table align="left" id="ITableCert" class="CTableCert"> <!-- Header --> <tbody><tr id="ITRCompany" class="CTRHeader"> <td id="ITDCompany" class="CTDHeader"> <div id="IDivCompany" class="CDivHeader">ImageMaster - Certificate</div> </td> <td id="ITDLogo" class="CTDHeader"> <img src="./img/Trioptics-Logo-250x65.png" id="ITDLogo" class="CTDHea= der"> </td> </tr> <tr id="ITDSlogan" class="CTRHeader"> <td colspan="2" id="ITDSlogan" class="CTDHeader"> </td> </tr> <tr> <td colspan="2"> <pre>Company : ****** Operator : ****** Time/Date : 13:30:33 June 12, 2025 Sample ID : ****** Measure Program : MTF vs. Field Temperature : 20°C Measured with : <a href="http://www.trioptics.com/">TRIOPTICS</a> - MT= F-LAB - Vers. 5.16.1 Instrument S/N : 09-113-0519 Comments : SN215U0540 </pre> </td> </tr> <tr> <td colspan="2"> </td> </tr> <tr> <td colspan="2"> <hr> <b>Measurement Parameter: MTF vs. Image Height</b> <pre>Setup Type : Object Infinite / Image Finite EFL (Collimator): 50 mm Wavelength : 940 nm (NIR) EFL (Sample) : 1.5550 mm F-Number : 2.0000 Object Angle : -0.0198 ° Focus Position : 85.8471 Sample Azimuth : 0.0 ° </pre></td> </tr> <tr> <td colspan="2"> <hr> <b>Measurement Graph: MTF vs. Image Height</b> <br><br> <right> <img src="Chart12.png"> </right> </td> </tr> <tr> <td colspan="2"> <hr> <b>Measurement Table: MTF vs. Image Height</b> <br><br> <!-- open measurement table --> <table cellspacing="0" align="left" id="ITableDataItems" class="CTa= bleDataItems"> <!-- begin table caption --> <tbody><tr id="ITRDataCaption" class="CTRDataCaption"> <td id="ITDCaptionBlank" class="CTDCaptionBlank"></td> <td align="center" colspan="5" id="ITDDataCaption" class="CTDDataCa= ption">Image Height (mm)</td> </tr> <tr id="ITRDataCaption" class="CTRDataCaption"> <td id="ITDDataCaption" class="CTDDataCaption">MTF</td> <td id="ITDDataCaption" class="CTDDataCaption">0.42800</td> <td id="ITDDataCaption" class="CTDDataCaption">0.29960</td> <td id="ITDDataCaption" class="CTDDataCaption">-0.00114</td> <td id="ITDDataCaption" class="CTDDataCaption">-0.29960</td> <td id="ITDDataCaption" class="CTDDataCaption">-0.42800</td> <td id="ITDDataCaption" class="CTDDataCaption">Legend</td> </tr> <!-- end table caption --> <!-- begin measurement data --> <tr id="ITRDataItem" class="CTRDataItem"> <td id="ITDEven" class="CTDDataItem">Tan 100(lp/mm)</td> <td id="ITDEven" class="CTDDataItem">0.599</td> <td id="ITDEven" class="CTDDataItem">0.677</td> <td id="ITDEven" class="CTDDataItem">0.668</td> <td id="ITDEven" class="CTDDataItem">0.703</td> <td id="ITDEven" class="CTDDataItem">0.645</td> <td colspan="1" id="ITDLegend" class="CTDLegend"><font color="ff000= 0">— — —</font></td> </tr> <tr id="ITRDataItem" class="CTRDataItem"> <td id="ITDEven" class="CTDDataItem">Sag 100(lp/mm)</td> <td id="ITDEven" class="CTDDataItem">0.558</td> <td id="ITDEven" class="CTDDataItem">0.659</td> <td id="ITDEven" class="CTDDataItem">0.673</td> <td id="ITDEven" class="CTDDataItem">0.688</td> <td id="ITDEven" class="CTDDataItem">0.642</td> <td colspan="1" id="ITDLegend" class="CTDLegend"><font color="ff000= 0">————</font></td> </tr> <!-- end measurement data --> <!-- close measurement table --> </tbody></table><br clear="all"> </td> </tr> <tr> <td colspan="2"> <hr> <b>Measurement Parameter: MTF vs. Object Angle</b> <pre>Setup Type : Object Infinite / Image Finite EFL (Collimator): 50 mm Wavelength : 940 nm (NIR) EFL (Sample) : 1.5550 mm F-Number : 2.0000 Object Angle : -0.0198 ° Focus Position : 85.8471 Sample Azimuth : 0.0 ° </pre></td> </tr> <tr> <td colspan="2"> <hr> <b>Measurement Graph: MTF vs. Object Angle</b> <br><br> <right> <img src="Chart11.png"> </right> </td> </tr> <tr> <td colspan="2"> <hr> <b>Measurement Table: MTF vs. Object Angle</b> <br><br> <!-- open measurement table --> <table cellspacing="0" align="left" id="ITableDataItems" class="CTa= bleDataItems"> <!-- begin table caption --> <tbody><tr id="ITRDataCaption" class="CTRDataCaption"> <td id="ITDCaptionBlank" class="CTDCaptionBlank"></td> <td align="center" colspan="5" id="ITDDataCaption" class="CTDDataCa= ption">Object Angle (?</td> </tr> <tr id="ITRDataCaption" class="CTRDataCaption"> <td id="ITDDataCaption" class="CTDDataCaption">MTF</td> <td id="ITDDataCaption" class="CTDDataCaption">-15.33590</td> <td id="ITDDataCaption" class="CTDDataCaption">-10.72937</td> <td id="ITDDataCaption" class="CTDDataCaption">0.00072</td> <td id="ITDDataCaption" class="CTDDataCaption">10.77662</td> <td id="ITDDataCaption" class="CTDDataCaption">15.40352</td> <td id="ITDDataCaption" class="CTDDataCaption">Legend</td> </tr> <!-- end table caption --> <!-- begin measurement data --> <tr id="ITRDataItem" class="CTRDataItem"> <td id="ITDEven" class="CTDDataItem">Tan 100(lp/mm)</td> <td id="ITDEven" class="CTDDataItem">0.599</td> <td id="ITDEven" class="CTDDataItem">0.677</td> <td id="ITDEven" class="CTDDataItem">0.668</td> <td id="ITDEven" class="CTDDataItem">0.703</td> <td id="ITDEven" class="CTDDataItem">0.645</td> <td colspan="1" id="ITDLegend" class="CTDLegend"><font color="ff000= 0">— — —</font></td> </tr> <tr id="ITRDataItem" class="CTRDataItem"> <td id="ITDEven" class="CTDDataItem">Sag 100(lp/mm)</td> <td id="ITDEven" class="CTDDataItem">0.558</td> <td id="ITDEven" class="CTDDataItem">0.659</td> <td id="ITDEven" class="CTDDataItem">0.673</td> <td id="ITDEven" class="CTDDataItem">0.688</td> <td id="ITDEven" class="CTDDataItem">0.642</td> <td colspan="1" id="ITDLegend" class="CTDLegend"><font color="ff000= 0">————</font></td> </tr> <!-- end measurement data --> <!-- close measurement table --> </tbody></table><br clear="all"> </td> </tr> <!-- close certificate table --> </tbody></table> </body></html> 解析这个
06-26
<template> <div class="container"> <div class="search-wrapper"> <el-form class="search-from" :inline="true" label-width="70px" @submit.prevent="initData"> <el-form-item label="名称"> <el-input v-model="queryParams.name" placeholder="请输入名称" clearable/> </el-form-item> <el-form-item label="责任人"> <el-input v-model="queryParams.respPerson" placeholder="请输入责任人" clearable/> </el-form-item> <el-form-item> <el-button type="primary" @click="initData"> <i class="el-icon-search"></i> 搜索 </el-button> <el-button @click="resetQuery"> <i class="el-icon-refresh"></i> 重置 </el-button> <el-button type="primary" style="margin-left: 10px;" @click="toggleGantt" > {{ showGantt ? '收起甘特图' : '展开甘特图' }} </el-button> </el-form-item> </el-form> </div> <div ref="gantt" class="gantt-container"></div> <!-- 添加或修改治理计划对话框 --> <Dialog title="查看治理计划" :visible.sync="open" width="850px" height="600px" append-to-body> <el-form ref="form" :model="form" label-width="100px" disabled> <el-row> <el-col :span="12"> <el-form-item label="编号" prop="code"> <el-input v-model="form.code" placeholder="请输入编号"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="名称" prop="text"> <el-input v-model="form.text" placeholder="请输入名称"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="上级计划" prop="parent"> <treeselect v-model="form.parent" :options="planOptions" :normalizer="normalizer" placeholder="选择上级计划" disabled /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="完成度(%)" prop="progress"> <el-input-number v-model="form.progress" placeholder="请输入完成百分比"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="责任人" prop="respPerson"> <span slot="label"> <span class="content-font">责任人</span> </span> <!-- 用户向导 --> <p style="margin-top:0px; float: left;font-size: 12px;"> {{ form.respPerson }} <el-button size="mini" type="primary" icon="el-icon-user" class="btn-wizard-trigger" style="margin-left: 10px" >选择用户 </el-button> </p> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="责任部门" prop="respDept"> <el-input v-model="form.respDept" placeholder="请输入责任部门"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="计划开始日期" prop="planStartDate"> <el-date-picker clearable v-model="form.planStartDate" type="date" value-format="yyyy-MM-dd" placeholder="请选择计划开始日期"> </el-date-picker> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="实际开始日期" prop="realStartDate"> <el-date-picker clearable v-model="form.realStartDate" type="date" value-format="yyyy-MM-dd" placeholder="请选择实际开始日期"> </el-date-picker> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="计划结束时间" prop="planEndDate"> <el-date-picker clearable v-model="form.planEndDate" type="date" value-format="yyyy-MM-dd" placeholder="请选择计划结束时间"> </el-date-picker> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="实际结束日期" prop="realEndDate"> <el-date-picker clearable v-model="form.realEndDate" type="date" value-format="yyyy-MM-dd" placeholder="请选择实际结束日期"> </el-date-picker> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="计划工期(天)" prop="planDuration"> <el-input v-model="form.planDuration" placeholder="请选择计划日期"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="实际工期(天)" prop="realDuration"> <el-input v-model="form.realDuration" placeholder="请选择实际日期"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="24"> <el-form-item label="备注" prop="remarks"> <el-input v-model="form.remarks" type="textarea" placeholder="请输入备注"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="24"> <el-form-item label="反馈内容" prop="feedback"> <el-input v-model="form.feedback" type="textarea" placeholder="请输入内容"/> </el-form-item> </el-col> </el-row> </el-form> <div slot="buttons" class="dialog-footer"> <el-button @click="open = false">关 闭</el-button> </div> </Dialog> </div> </template> <script> import Dialog from '@/components/Dialog' import Treeselect from '@riophae/vue-treeselect' import '@riophae/vue-treeselect/dist/vue-treeselect.css' import {gantt} from "dhtmlx-gantt"; import "dhtmlx-gantt/codebase/dhtmlxgantt.css"; import {listPlan} from "@/api/dw/plan/planview"; export default { components: {Dialog, Treeselect}, name: "gantt", data() { return { tasks: { data: [], }, queryParams: { name: null, respPerson: null }, showGantt: true, // 状态控制甘特图显示 planOptions: [], open: false, // 控制详情弹窗显示 form: {} // 当前查看的任务 }; }, // 把携带的参数放到queryParams查询参数里 created() { // console.log(this.$route.params.id); }, methods: { // 查看任务详情 handleView(taskId) { // 根据任务ID查找任务详情 const task = this.tasks.data.find(item => item.id == taskId); if (task) { this.getTreeselect(); this.form = task; this.open = true; } }, toggleGantt() { this.showGantt = !this.showGantt; this.initData(); // 重新初始化甘特图 }, // 上级节点 getTreeselect() { listPlan().then(response => { const data = {uid: 0, name: '顶级节点', children: []}; data.children = this.handleTree(response.data, 'uid', 'parentUid') this.planOptions.push(data) }) }, normalizer(node) { if (node.children && !node.children.length) { delete node.children } return { id: node.uid, label: node.name, children: node.children } }, //开始时间-结束时间参数 DateDifference: function (strDateStart, strDateEnd) { var begintime_ms = Date.parse(new Date(strDateStart.replace(/-/g, "/"))); //begintime 为开始时间 var endtime_ms = Date.parse(new Date(strDateEnd.replace(/-/g, "/"))); // endtime 为结束时间 var date3 = endtime_ms - begintime_ms; //时间差的毫秒数 var days = Math.floor(date3 / (24 * 3600 * 1000)); return days; }, // 重置查询 resetQuery() { this.queryParams = { name: null, respPerson: null }; this.initData(); }, initData: function () { gantt.clearAll(); listPlan(this.queryParams).then((res) => { // 重新初始化甘特图配置 gantt.config.autosize = true; gantt.config.readonly = true; gantt.config.show_grid = true; this.tasks.data = res.data.map((item) => { let statusColor; //存在status字段 说明非一级菜单,判断阶段的具体类型 设置不同颜色 if (item.status == '1') { //冒烟 statusColor = "#84bd54" } else if (item.status == '2') { //单元 statusColor = "#fcca02" } else if (item.status == '3') { //回归 statusColor = "#dc1626" } else { statusColor = "#999999" } return { id: item.uid, parent: item.parent, text: item.name, start_date: item.planStartDate, duration: item.planDuration, open: true, //默认打开, toolTipsTxt: item.name, progress: item.schedule, status: item.status, code: item.code, respPerson: item.respPerson, respDept: item.respDept, planStartDate: item.planStartDate, planEndDate: item.planEndDate, realStartDate: item.realStartDate, realEndDate: item.realEndDate, planDuration: item.planDuration, realDuration: item.realDuration, remarks: item.remarks, feedback: item.feedback, color: statusColor, } }); //自适应甘特图的尺寸大小, 使得在不出现滚动条的情况下, 显示全部任务 gantt.config.autosize = true; //只读模式 gantt.config.readonly = true; //是否显示左侧树表格 gantt.config.show_grid = true; //表格列设置 gantt.config.columns = [ { name: "code", label: "编号", tree: true, width: "160", onrender: function (task, node) { node.setAttribute( "class", "gantt_cell gantt_last_cell gantt_cell_tree " + task.status ); }, }, { name: "status", label: "状态", align: "center", width: "80", template: function (task) { // 自定义状态列显示为状态灯 return `<div class="status-light" style="background-color: ${task.color}"></div>`; } }, {name: "text", label: "名称", align: "center", width: "180", hide: true}, {name: "progress", label: "完成度(%)", align: "center", width: "90", hide: true}, {name: "respPerson", label: "责任人", align: "center", width: "120", hide: true}, {name: "respDept", label: "责任部门", align: "center", width: "140", hide: true}, {name: "planStartDate", label: "计划开始日期", align: "center", width: "130", hide: true}, {name: "planEndDate", label: "计划结束时间", align: "center", width: "130", hide: true}, {name: "realStartDate", label: "实际开始日期", align: "center", width: "130", hide: true}, {name: "realEndDate", label: "实际结束日期", align: "center", width: "130", hide: true}, {name: "planDuration", label: "计划工期", align: "center", width: "90", hide: true}, {name: "realDuration", label: "实际工期", align: "center", width: "90", hide: true}, {name: "remarks", label: "备注", align: "center", width: "220", hide: true}, // { // name: "operate", // label: "操作", // align: "center", // width: "80", // template: function (task) { // return '<el-button size="mini" type="text" onclick=" window.vueInstance.handleView(\'' + task.id + '\')">查看</el-button>'; // } // } ]; var weekScaleTemplate = function (date) { var dateToStr = gantt.date.date_to_str("%m %d"); var endDate = gantt.date.add( gantt.date.add(date, 1, "week"), -1, "day" ); var weekNum = gantt.date.date_to_str("第 %W 周"); return weekNum(date); }; var daysStyle = function (date) { var dateToStr = gantt.date.date_to_str("%D"); if (dateToStr(date) == "六" || dateToStr(date) == "日") return "weekend"; return ""; }; gantt.config.subscales = [ { unit: "week", step: 1, template: weekScaleTemplate, }, { unit: "day", step: 1, format: "%d", }, ]; gantt.plugins({ tooltip: true, }); //设置鼠标放置显示事件 gantt.attachEvent("onGanttReady", function() { var tooltips = gantt.ext.tooltips; gantt.templates.tooltip_text = function(start, end, task) { return "编号:" + task.code + "<br/>" + "名称:" + task.text + "<br/>" + "计划开始:" + gantt.templates.tooltip_date_format(start) + "<br/>" + "工期:" + task.duration }; }); //设置任务条进度内容 gantt.templates.progress_text = function (start, end, task) { return ( "<div style='text-align:left;color:#fff;padding-left:20px'>" + task.progress + "% </div>" ); }; //任务条显示内容 gantt.templates.task_text = function (start, end, task) { // return task.text + '(' + task.duration + '天)'; return ( "<div style='text-align:center;color:#fff'>" + task.text + "(" + task.duration + "天)" + "</div>" ); }; //任务条上的文字大小 以及取消border自带样式 gantt.templates.task_class = function (start, end, item) { return item.$level == 0 ? "firstLevelTask" : "secondLevelTask"; }; gantt.config.layout = { css: "gantt_container", cols: [ { width: this.showGantt ? 800 : "100%", // 如果收起甘特图,左侧表格宽度占满 min_width: 300, //表格左侧最小宽度 rows: [ { view: "grid", scrollX: "gridScroll", scrollable: true, scrollY: "scrollVer", }, { view: "scrollbar", id: "gridScroll", group: "horizontal", }, ], }, ...(this.showGantt ? [{ resizer: true, width: 1, }, { rows: [ { view: "timeline", scrollX: "scrollHor", scrollY: "scrollVer", }, { view: "scrollbar", id: "scrollHor", group: "horizontal", }, ] }] : []) ], }; //时间轴图表中,任务条形图的高度 // gantt.config.task_height = 28 //时间轴图表中,甘特图的高度 // gantt.config.row_height = 36 //时间轴图表中,如果不设置,只有行边框,区分上下的任务,设置之后带有列的边框,整个时间轴变成格子状。 gantt.config.show_task_cells = true; //当task的长度改变时,自动调整图表坐标轴区间用于适配task的长度 gantt.config.fit_tasks = true; gantt.config.min_column_width = 50; gantt.config.auto_types = true; gantt.config.xml_date = "%Y-%m-%d"; gantt.config.scale_unit = "month"; gantt.config.step = 1; gantt.config.date_scale = "%Y年%M"; gantt.config.start_on_monday = true; gantt.config.scale_height = 160; gantt.config.autoscroll = true; gantt.config.calendar_property = "start_date"; gantt.config.calendar_property = "end_date"; gantt.config.readonly = true; gantt.i18n.setLocale("cn"); // 初始化 gantt.init(this.$refs.gantt); // 数据解析 gantt.parse(this.tasks); // 添加双击行事件监听器 gantt.attachEvent("onTaskDblClick", function(id, e) { // 调用查看详情方法 window.vueInstance.handleView(id); return true; }); }); }, }, mounted() { // 将当前Vue实例赋值给window.vueInstance,供甘特图中调用 window.vueInstance = this; this.initData(); }, }; </script> <style lang="scss" scoped> .firstLevelTask { border: none; .gantt_task_content { font-size: 13px; } } .secondLevelTask { border: none; } .thirdLevelTask { border: 2px solid #da645d; color: #da645d; background: #da645d; } .milestone-default { border: none; background: rgba(0, 0, 0, 0.45); } .milestone-unfinished { border: none; background: #5692f0; } .milestone-finished { border: none; background: #84bd54; } .milestone-canceled { border: none; background: #da645d; } html, body { margin: 0; padding: 0; height: 100%; overflow: hidden; } .container { height: 100%; width: 100%; position: relative; padding: 10px; .gantt_grid_head_cell { padding-left: 20px; text-align: left !important; font-size: 14px; color: #333; } .left-container { height: 100%; } .green, .yellow, .pink, .popular { .gantt_tree_icon.gantt_file { background: none; position: relative; &::before { content: ""; width: 10px; height: 10px; border-radius: 50%; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); } } } .green { .gantt_tree_icon.gantt_file { &::before { background: #84bd54; } } } .yellow { .gantt_tree_icon.gantt_file { &::before { background: #fcca02; } } } .pink { .gantt_tree_icon.gantt_file { &::before { background: #da645d; } } } .popular { .gantt_tree_icon.gantt_file { &::before { background: #d1a6ff; } } } } .left-container { height: 100%; } .gantt_task_content { text-align: left; padding-left: 10px; } .gantt-container { height: calc(100% - 65px) !important; } // 状态灯样式 ::v-deep .gantt_grid_data .gantt_cell div.status-light { width: 12px; height: 12px; border-radius: 50%; display: inline-block; margin: 0 auto; } // 表格表头居中样式 ::v-deep .gantt_grid_head_cell { text-align: center !important; } .search-wrapper { text-align: left; padding: 10px 0; } .search-from { .el-form-item--mini.el-form-item, .el-form-item--small.el-form-item { margin-bottom: 3px; } } </style> 实现表格和甘特图的左右拖拽
07-29
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>工资条生成工具</title> <!-- 引入SheetJS库用于处理Excel文件 --> <script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script> <!-- 关键修复:使用支持样式的xlsx-style库替换基础版xlsx库 --> <script src="https://unpkg.com/xlsx-style@0.15.6/dist/xlsx.full.min.js"></script> <style> * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: "Microsoft YaHei", "SimHei", Arial, sans-serif; width: 100%; min-height: 100vh; padding: 20px; font-size: 14px; color: #333; line-height: 1.5; } h1 { text-align: center; margin-bottom: 25px; font-size: 24px; color: #2c3e50; } h2 { font-size: 18px; margin: 15px 0; color: #3498db; } .file-upload { border: 2px dashed #95a5a6; padding: 30px 20px; text-align: center; margin-bottom: 30px; cursor: pointer; border-radius: 6px; transition: all 0.3s ease; background-color: #f8f9fa; } .file-upload:hover, .file-upload.active { border-color: #3498db; background-color: #f1f7fc; } .file-upload p { font-size: 16px; color: #7f8c8d; } #fileInput { display: none; } .controls { display: flex; justify-content: center; margin: 20px 0; } .btn { background-color: #3498db; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 14px; transition: background-color 0.3s ease; display: flex; align-items: center; gap: 8px; } .btn:hover { background-color: #2980b9; } .btn:disabled { background-color: #bdc3c7; cursor: not-allowed; } #dataContainer { width: 100%; overflow-x: auto; margin: 0 auto; } table { width: 100%; min-width: 900px; border-collapse: collapse; table-layout: fixed; } th, td { border: 1px solid #ddd; padding: 12px 10px; text-align: left; word-wrap: break-word; word-break: break-all; height: 45px; } /* 数值列右对齐,方便查看 */ td.numeric { text-align: right; } /* 工资条表头样式 */ .salary-slip-header th { background-color: #ecf0f1; color: #2c3e50; font-weight: bold; position: sticky; top: 0; z-index: 2; } /* 合计行样式 */ .total-row td { background-color: #f8f9fa; font-weight: bold; } /* 签字行样式 */ .signature-row td { background-color: #f9f9f9; } tr:hover { background-color: #f1f7fc; } .message { color: #7f8c8d; text-align: center; padding: 50px 0; font-size: 16px; } .error { color: #e74c3c; } /* 无效数值样式 */ .invalid-value { background-color: #fff3cd; color: #856404; } .date-info { color: #2c3e50; font-size: 15px; margin: 15px 0; padding: 15px 12px; background-color: #f1f9f7; border-left: 3px solid #27ae60; border-radius: 4px; line-height: 1.8; min-height: 45px; } /* 响应式调整 */ @media (max-width: 1200px) { body { padding: 15px; font-size: 13px; } th, td { padding: 10px 8px; height: 40px; } .date-info { padding: 12px 10px; min-height: 40px; } } @media (max-width: 768px) { body { padding: 10px; font-size: 12px; } h1 { font-size: 20px; margin-bottom: 15px; } h2 { font-size: 16px; } .file-upload { padding: 20px 10px; margin-bottom: 20px; } th, td { padding: 8px 6px; height: 36px; } .date-info { padding: 10px 8px; min-height: 36px; } } </style> </head> <body> <h1>工资条生成工具</h1> <!-- 文件上传区域 --> <div class="file-upload" id="dropArea"> <p>点击或拖放工资表Excel文件到这里(支持多次上传更新)</p> <input type="file" id="fileInput" accept=".xlsx, .xls"> </div> <!-- 提取的信息 --> <div id="infoContainer" style="display: none;"> <div class="date-info"> <p><strong>公司名称:</strong><span id="companyName"></span></p> <p><strong>工资月份:</strong><span id="salaryMonth"></span></p> <p><strong>上次更新:</strong><span id="lastUpdated"></span></p> </div> </div> <!-- 控制按钮区域 --> <div class="controls"> <button id="exportBtn" class="btn" disabled> 导出工资条 </button> </div> <!-- 数据展示区域 --> <div id="dataContainer" style="display: none;"> <h2>工资条数据</h2> <table id="dataTable"> <tbody id="dataTableBody"> <!-- 提取的数据将在这里显示,每条数据带独立表头 --> </tbody> </table> </div> <!-- 消息区域 --> <div id="message" class="message">请上传符合格式要求的工资表Excel文件(.xlsx, .xls)</div> <script> // 获取DOM元素 const dropArea = document.getElementById('dropArea'); const fileInput = document.getElementById('fileInput'); const dataContainer = document.getElementById('dataContainer'); const dataTable = document.getElementById('dataTable'); const dataTableBody = document.getElementById('dataTableBody'); const message = document.getElementById('message'); const infoContainer = document.getElementById('infoContainer'); const companyName = document.getElementById('companyName'); const salaryMonth = document.getElementById('salaryMonth'); const lastUpdated = document.getElementById('lastUpdated'); const exportBtn = document.getElementById('exportBtn'); // 存储处理后的数据用于导出 let processedDataForExport = null; let currentDate = ''; // 定义预期的表头结构 const expectedHeaders = [ '序号', '部门', '姓名', '出勤', '基本工资', '岗位工资', '绩效', '浮动津贴', '扣除', '应发工资', '基本养老(个人)', '失业(个人)', '基本医疗(个人)', '大病(个人)', '公积金(个人)', '个税', '实发工资', '签字' ]; // 定义哪些列是数值类型(索引) const numericColumns = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; // 点击上传区域触发文件选择 dropArea.addEventListener('click', () => fileInput.click()); // 文件选择变化时处理 fileInput.addEventListener('change', (e) => { if (e.target.files.length > 0) { handleFile(e.target.files[0]); // 重置input值,允许重复选择同一文件 fileInput.value = ''; } }); // 拖放功能增强 dropArea.addEventListener('dragover', (e) => { e.preventDefault(); dropArea.classList.add('active'); }); dropArea.addEventListener('dragleave', () => { dropArea.classList.remove('active'); }); dropArea.addEventListener('drop', (e) => { e.preventDefault(); dropArea.classList.remove('active'); if (e.dataTransfer.files.length > 0) { handleFile(e.dataTransfer.files[0]); } }); // 导出按钮点击事件 exportBtn.addEventListener('click', exportToExcel); // 处理Excel文件 - 支持多次上传更新 function handleFile(file) { // 检查文件类型 if (!file.name.match(/\.(xlsx|xls)$/)) { showMessage('请上传Excel文件(.xlsx, .xls)', true); return; } showMessage(`正在解析文件: ${file.name}...`); const reader = new FileReader(); reader.onload = function(e) { try { // 读取并解析Excel文件 const data = new Uint8Array(e.target.result); const workbook = XLSX.read(data, { type: 'array' }); // 获取第一个工作表 const firstSheetName = workbook.SheetNames[1]; const worksheet = workbook.Sheets[firstSheetName]; // 转换为JSON const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 }); if (jsonData.length < 2) { showMessage('文件内容不符合要求,至少需要包含标题行和表头行', true); return; } // 处理数据并更新表格 processData(jsonData); // 更新最后上传时间 updateLastModifiedTime(); } catch (error) { showMessage('解析文件失败: ' + error.message, true); console.error(error); } }; reader.readAsArrayBuffer(file); } // 更新最后修改时间 function updateLastModifiedTime() { const now = new Date(); const formattedTime = now.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); lastUpdated.textContent = formattedTime; infoContainer.style.display = 'block'; } // 处理数据,提取公司名称和日期并规范化表头 function processData(rawData) { // 提取标题行信息(第一行数据) let title = rawData[0][0] || ''; // 提取公司名称 const companyMatch = title.match(/^(.*?)\s*\d{4}/); const company = companyMatch ? companyMatch[1] : '未知公司'; // 提取日期 const datePattern = /(\d{4})\D*(\d{1,2})\D*月/; const dateMatch = title.match(datePattern); currentDate = dateMatch ? dateMatch[0] : ''; // 更新公司名称和工资月份信息 companyName.textContent = company; salaryMonth.textContent = currentDate; // 提取特定行列数据 const extractedData = extractSpecificData(rawData); // 处理数值数据 const processedData = processNumericData(extractedData); // 保存处理后的数据用于导出 processedDataForExport = processedData; // 启用导出按钮 exportBtn.disabled = false; // 显示处理后的数据(每条数据带独立表头) displayData(processedData, currentDate); } // 提取特定行列数据 function extractSpecificData(rawData) { // 转换Excel列字母为索引(A=0, B=1, C=2...) const columnMap = { 'A': 0, 'B': 1, 'C': 2, 'O': 14, 'Q': 16, 'R': 17, 'S': 18, 'T': 19, 'W': 22, 'X': 23, 'Y': 24, 'Z': 25, 'AA': 26, 'AB': 27, 'AC': 28, 'AE': 30, 'AG': 32, 'AH': 33 }; // 需要提取的列索引 const columnsToExtract = Object.values(columnMap); // 找到最后一行有数据的行索引 let lastDataRow = -1; for (let i = rawData.length - 1; i >= 0; i--) { const row = rawData[i]; if (row && row.some(cell => cell !== undefined && cell !== null && cell !== '')) { lastDataRow = i; break; } } // 计算行范围 const startRow = 5; // 第6行(0-based索引) const endRow = lastDataRow !== -1 ? lastDataRow - 6 : -1; // 验证行范围有效性 if (lastDataRow === -1) { console.warn('未找到有数据的行'); return []; } if (startRow >= endRow || endRow < 0) { console.warn(`数据行范围无效,开始行(${startRow+1}) >= 结束行(${endRow+1})`); showMessage(`数据行范围无效,开始行(${startRow+1}) >= 结束行(${endRow+1})`, true); return []; } // 提取数据到二维数组 const result = []; for (let i = startRow; i < endRow; i++) { const rowData = rawData[i] || []; const extractedRow = []; columnsToExtract.forEach(colIndex => { extractedRow.push(rowData[colIndex] !== undefined ? rowData[colIndex] : ''); }); result.push(extractedRow); } return result; } // 处理数值数据:校验并保留2位小数(四舍五入),空值不标红 function processNumericData(data) { return data.map(row => { return row.map((cell, index) => { // 检查当前列是否为数值列 if (numericColumns.includes(index)) { // 处理空值情况 if (cell === '' || cell === null || cell === undefined) { return { value: '', valid: true, // 空值视为有效 rawValue: 0 // 用于计算合计的原始数值 }; } // 尝试转换为数值 const num = parseFloat(cell); // 校验数值有效性 if (isNaN(num)) { return { value: cell, valid: false, rawValue: 0 }; } else { const roundedValue = Math.round(num * 100) / 100; return { value: roundedValue, valid: true, rawValue: roundedValue // 存储用于计算合计的数值 }; } } // 非数值列,直接返回原始值 return { value: cell, valid: true, rawValue: 0 }; }); }); } // 计算各列的总和 function calculateTotals(processedData) { const totals = new Array(expectedHeaders.length).fill(0); processedData.forEach(row => { row.forEach((cell, index) => { // 只对数值列进行求和 if (numericColumns.includes(index)) { totals[index] += cell.rawValue; } }); }); // 保留两位小数 return totals.map(total => Math.round(total * 100) / 100); } // 显示解析后的数据 - 每条数据前都添加表头,无间隔,最后添加合计行和签字行 function displayData(processedData, date) { // 清空表格 dataTableBody.innerHTML = ''; // 如果没有提取到数据,显示提示 if (processedData.length === 0) { const emptyRow = document.createElement('tr'); const emptyCell = document.createElement('td'); emptyCell.colSpan = expectedHeaders.length; emptyCell.textContent = '没有提取到符合条件的数据'; emptyRow.appendChild(emptyCell); dataTableBody.appendChild(emptyRow); } else { // 为每条数据添加表头(无间隔) processedData.forEach((row) => { // 添加表头行(每条数据前都添加) const headerRow = document.createElement('tr'); headerRow.className = 'salary-slip-header'; expectedHeaders.forEach((header) => { if (header === '应发工资' || header === '实发工资') { header += `(${date})`; } const th = document.createElement('th'); th.textContent = header; headerRow.appendChild(th); }); dataTableBody.appendChild(headerRow); // 添加数据行 const dataRow = document.createElement('tr'); row.forEach((cell, cellIndex) => { const td = document.createElement('td'); td.textContent = cell.value; // 数值列右对齐 if (numericColumns.includes(cellIndex)) { td.classList.add('numeric'); } // 标记无效数值 if (!cell.valid) { td.classList.add('invalid-value'); } dataRow.appendChild(td); }); dataTableBody.appendChild(dataRow); }); // 计算各列总和 const totals = calculateTotals(processedData); // 添加合计行(倒数第二行) const totalRow = document.createElement('tr'); totalRow.className = 'total-row'; // 前3个单元格合并,内容为"合计" const mergedCell = document.createElement('td'); mergedCell.colSpan = 3; mergedCell.textContent = '合计'; mergedCell.style.textAlign = 'center'; totalRow.appendChild(mergedCell); // 第4个单元格为空 const emptyCell = document.createElement('td'); totalRow.appendChild(emptyCell); // 5-17单元格为各列总和(索引4到16) for (let i = 4; i < expectedHeaders.length; i++) { const td = document.createElement('td'); td.textContent = totals[i]; if (i == expectedHeaders.length - 1) { td.textContent = ''; } if (numericColumns.includes(i)) { td.classList.add('numeric'); } totalRow.appendChild(td); } dataTableBody.appendChild(totalRow); // 添加签字行(最后一行) const signatureRow = document.createElement('tr'); signatureRow.className = 'signature-row'; // 制表 const makerCell = document.createElement('td'); makerCell.colSpan = 4; makerCell.textContent = '制表:'; signatureRow.appendChild(makerCell); // 财务 const financeCell = document.createElement('td'); financeCell.colSpan = 4; financeCell.textContent = '财务:'; signatureRow.appendChild(financeCell); // 审核 const auditCell = document.createElement('td'); auditCell.colSpan = 5; auditCell.textContent = '审核:'; signatureRow.appendChild(auditCell); // 审批 const approveCell = document.createElement('td'); approveCell.colSpan = 5; approveCell.textContent = '审批:'; signatureRow.appendChild(approveCell); dataTableBody.appendChild(signatureRow); } // 显示数据区域,隐藏消息 dataContainer.style.display = 'block'; message.style.display = 'none'; } // 导出数据到Excel function exportToExcel() { if (!processedDataForExport || processedDataForExport.length === 0) { showMessage('没有可导出的数据', true); return; } // 准备导出数据 const exportData = []; // 添加每条记录的表头和数据 processedDataForExport.forEach(row => { // 添加表头行 const headerRow = expectedHeaders.map(header => { if (header === '应发工资' || header === '实发工资') { return `${header}(${currentDate})`; } return header; }); exportData.push(headerRow); // 添加数据行 const dataRow = row.map(cell => cell.value); exportData.push(dataRow); }); // 计算合计 const totals = calculateTotals(processedDataForExport); // 准备合计行 const totalRow = []; // 前3个单元格合并为"合计" totalRow.push("合计"); totalRow.push(null); // 第二个单元格(合并后不需要) totalRow.push(null); // 第三个单元格(合并后不需要) totalRow.push(""); // 第四个单元格为空 // 添加5-17列的合计值 for (let i = 4; i < expectedHeaders.length - 1; i++) { totalRow.push(totals[i]); } exportData.push(totalRow); // 准备签字行 - 只在第一个单元格设置值,其他为空 const signatureRow = new Array(expectedHeaders.length).fill(null); signatureRow[0] = "制表: 财务: 审核: 审批:"; exportData.push(signatureRow); // 创建工作簿和工作表 const ws = XLSX.utils.aoa_to_sheet(exportData); // 设置单元格合并 if (exportData.length > 0) { const totalRowIndex = exportData.length - 2; // 合计行索引 const signatureRowIndex = exportData.length - 1; // 签字行索引 ws['!merges'] = [ // 合计行前3列合并 { s: { r: totalRowIndex, c: 0 }, e: { r: totalRowIndex, c: 2 } }, // 签字行A-R列合并为一个单元格 { s: { r: signatureRowIndex, c: 0 }, e: { r: signatureRowIndex, c: 17 } // 合并到第18列(R列) } ]; } // 设置固定行高 const range = XLSX.utils.decode_range(ws['!ref']); ws['!rows'] = []; // 初始化行配置数组 for (let R = 0; R <= range.e.r; R++) { // 为每一行设置固定高度20 ws['!rows'][R] = { hpt: 30 }; // hpt单位是1/20点,所以20像素需要乘以20 } // 使用自定义列宽,从第一列开始依次应用 const customWidths = [3.92, 4.8, 5, 4.05, 7.43, 7.68, 3.55, 4.93, 4.18, 12.05, 6.93, 6.93, 7.3, 6.93, 7.55, 5.68, 12.18, 9.43 ]; // 为每个表头应用对应的自定义宽度 ws['!cols'] = customWidths.map(width => ({ // 保留一位小数并转换为数字,确保宽度值正确 wch: parseFloat(width.toFixed(2)) })); // 定义包含自动换行的单元格样式 const cellStyle = { font: { sz: 10 // 小一号字体 }, alignment: { wrapText: true // 核心配置:启用自动换行 } }; // 应用样式到所有单元格 for (const cellAddress in ws) { // 跳过非单元格属性(如!cols、!rows等) if (cellAddress.startsWith('!')) continue; if (ws[cellAddress]) { // 合并已有样式与自动换行样式 ws[cellAddress].s = { ...ws[cellAddress].s, ...cellStyle }; } else { // 为空白单元格设置样式 ws[cellAddress] = { s: cellStyle }; } } // 创建工作簿并添加工作表 const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "工资条"); // 生成文件名(包含公司名称和日期) const company = companyName.textContent || "未知公司"; const fileName = `${company}_${currentDate || new Date().toLocaleDateString()}_工资条.xlsx`; // 导出文件 XLSX.writeFile(wb, fileName); } // 显示消息 function showMessage(text, isError = false) { message.textContent = text; message.className = isError ? 'message error' : 'message'; message.style.display = 'block'; dataContainer.style.display = 'none'; infoContainer.style.display = 'none'; } </script> </body> </html> 希望导出后有内容部分加边框,字体为宋体10号字,自动换行,对其方式上下左右居中,奇数行加粗
08-07
<template> <div class="container"> <TablePane ref="tablePane" :buttons="this.operations" :search-fields="searchFields" :query-params.sync="this.queryParams" :get-list="getListFun" :table-data="tableData" v-if="showGantt" > </TablePane> <div v-if="!showGantt"> <div class="search-wrapper"> <el-form class="search-from" :inline="true" label-width="70px" @submit.prevent="initGantt"> <el-form-item label="名称"> <el-input v-model="queryParams.name" placeholder="请输入名称" clearable/> </el-form-item> <el-form-item label="责任人"> <el-input v-model="queryParams.respPerson" placeholder="请输入责任人" clearable/> </el-form-item> <el-form-item> <el-button type="primary" @click="initGantt"> <i class="el-icon-search"></i> 搜索 </el-button> <el-button @click="resetQuery"> <i class="el-icon-refresh"></i> 重置 </el-button> <el-button type="primary" style="margin-left: 10px;" @click="retractGantt" > {{ '收起甘特图' }} </el-button> </el-form-item> </el-form> </div> <div ref="gantt" class="gantt-container"></div> </div> <!-- 添加或修改治理计划对话框 --> <Dialog title="查看治理计划" :visible.sync="open" width="850px" height="600px" append-to-body> <el-form ref="form" :model="form" label-width="100px" disabled> <el-row> <el-col :span="12"> <el-form-item label="编号" prop="code"> <el-input v-model="form.code" placeholder="请输入编号"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="名称" prop="text"> <el-input v-model="form.text" placeholder="请输入名称"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="上级计划" prop="parent"> <treeselect v-model="form.parent" :options="planOptions" :normalizer="normalizer" placeholder="选择上级计划" disabled /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="完成度(%)" prop="progress"> <el-input-number v-model="form.progress" placeholder="请输入完成百分比"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="责任人" prop="respPerson"> <span slot="label"> <span class="content-font">责任人</span> </span> <!-- 用户向导 --> <p style="margin-top:0px; float: left;font-size: 12px;"> {{ form.respPerson }} <el-button size="mini" type="primary" icon="el-icon-user" class="btn-wizard-trigger" style="margin-left: 10px" >选择用户 </el-button> </p> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="责任部门" prop="respDept"> <el-input v-model="form.respDept" placeholder="请输入责任部门"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="计划开始日期" prop="planStartDate"> <el-date-picker clearable v-model="form.planStartDate" type="date" value-format="yyyy-MM-dd" placeholder="请选择计划开始日期"> </el-date-picker> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="实际开始日期" prop="realStartDate"> <el-date-picker clearable v-model="form.realStartDate" type="date" value-format="yyyy-MM-dd" placeholder="请选择实际开始日期"> </el-date-picker> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="计划结束时间" prop="planEndDate"> <el-date-picker clearable v-model="form.planEndDate" type="date" value-format="yyyy-MM-dd" placeholder="请选择计划结束时间"> </el-date-picker> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="实际结束日期" prop="realEndDate"> <el-date-picker clearable v-model="form.realEndDate" type="date" value-format="yyyy-MM-dd" placeholder="请选择实际结束日期"> </el-date-picker> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="计划工期(天)" prop="planDuration"> <el-input v-model="form.planDuration" placeholder="请选择计划日期"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="实际工期(天)" prop="realDuration"> <el-input v-model="form.realDuration" placeholder="请选择实际日期"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="24"> <el-form-item label="备注" prop="remarks"> <el-input v-model="form.remarks" type="textarea" placeholder="请输入备注"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="24"> <el-form-item label="反馈内容" prop="feedback"> <el-input v-model="form.feedback" type="textarea" placeholder="请输入内容"/> </el-form-item> </el-col> </el-row> </el-form> <div slot="buttons" class="dialog-footer"> <el-button @click="open = false">关 闭</el-button> </div> </Dialog> </div> </template> <script> import TablePane from '@/components/TablePane' import Dialog from '@/components/Dialog' import Treeselect from '@riophae/vue-treeselect' import '@riophae/vue-treeselect/dist/vue-treeselect.css' import {gantt} from "dhtmlx-gantt"; import "dhtmlx-gantt/codebase/dhtmlxgantt.css"; import {listPlan} from "@/api/dw/plan/planview"; export default { components: {TablePane, Dialog, Treeselect}, name: "gantt", data() { return { getListFun: listPlan, searchFields: [ {'field': 'name', 'label': '名称', 'type': 'input'}, {'field': 'respPerson', 'label': '责任人', 'type': 'input'}, ], operations: [ { 'label': '展开甘特图', 'click': this.expandGantt, 'hasPermi': ['dw:planview:list'] }], tableData: { 'idField': 'uid', //加载数据后进行转换用于特殊处理 'loadDataHook': this.loadDataHook, 'attrs': { 'row-key': 'uid', '@row-click': this.rowClick, 'tree-props': { children: 'children', hasChildren: 'hasChildren' }, 'default-expand-all': true }, 'column': [ {'label': '编号', 'prop': 'code', 'attrs': { 'sortable': false,'align': 'left' } }, {'label': '状态', 'prop': 'status1', 'attrs': { 'sortable': false } ,'width': 80,'columnHook': this.light}, {'label': '名称', 'prop': 'name', 'attrs': { 'sortable': false } }, {'label': '完成百分比', 'prop': 'schedule', 'attrs': { 'sortable': false } }, {'label': '责任人', 'prop': 'respPerson', 'attrs': { 'sortable': false } }, {'label': '责任部门', 'prop': 'respDept' , 'attrs': { 'sortable': false }}, {'label': '计划开始日期', 'prop': 'planStartDate' ,'type':"dateTime" , 'format': (date) => { return this.parseTime(date, '{y}-{m}-{d}')}}, {'label': '计划结束时间', 'prop': 'planEndDate' ,'type':"dateTime" , 'format': (date) => { return this.parseTime(date, '{y}-{m}-{d}')}}, {'label': '实际开始日期', 'prop': 'realStartDate' ,'type':"dateTime" , 'format': (date) => { return this.parseTime(date, '{y}-{m}-{d}')}}, {'label': '实际结束日期', 'prop': 'realEndDate' ,'type':"dateTime" , 'format': (date) => { return this.parseTime(date, '{y}-{m}-{d}')}}, {'label': '计划工期', 'prop': 'planDuration' }, {'label': '实际工期', 'prop': 'realDuration' }, {'label': '备注', 'prop': 'remarks' }, { 'width': 100, 'label': '操作', 'type': 'button', 'attrs': { 'sortable': false, 'fixed': 'right' }, 'buttons': [ { 'icon': 'el-icon-view', 'label': '查看', 'hasPermi': ['dw:planview:query'], 'click': this.handleView }, ] } ] }, /***************************************************甘特图 start****************************************************************/ tasks: { data: [], }, queryParams: { name: null, respPerson: null }, showGantt: true, // 状态控制甘特图显示 planOptions: [], open: false, // 控制详情弹窗显示 form: {} // 当前查看的任务 /***************************************************甘特图 end****************************************************************/ }; }, // 把携带的参数放到queryParams查询参数里 created() { // console.log(this.$route.params.id); }, methods: { /** 查询治理计划列表 */ getList() { this.$refs.tablePane.loadData() }, loadDataHook(response) { response.data = this.handleTree(response.data, 'uid','parentUid') return response }, light(column, row){ const currentDate = this.getCurrentDate(); if( row.realStartDate != null){//有实际开始时间 // 状态灯:绿色:已完成; // 红色:计划开始时间小于当前日期并且没有实际开始日期; // 黄色:有实际开始日期,但是没有实际结束日期,并且计划结束日期小宇当前日期。 // 其他状态都没有颜色(数据体系里面的计划状态灯昨天没写,也按这个规则) var res = this.compareDate(new Date(row.planEndDate),new Date(currentDate)) if( row.realEndDate == null){ if(res== -1){ column.class='circle-light-yellow' } }else{ column.class='circle-light-green' } }else{//没用实际开始时间 红色:没有实际开始,但是当前日期大于计划开始日期; if(row.planStartDate != null){//有计划开始时间 var res = this.compareDate(new Date(row.planStartDate),new Date(currentDate)) if(res == -1){ column.class='circle-light-red' } } } }, getCurrentDate(){ const dateObj = new Date(); const year = dateObj.getFullYear(); // 获取当前年份 const month = ("0" + (dateObj.getMonth() + 1)).slice(-2); // 获取当前月份,其中需要将月份加1,因为月份是从0开始计数的 const day = ("0" + dateObj.getDate()).slice(-2); // 获取当前日期 const formattedDate = `${year}-${month}-${day}`; // 格式化日期 return formattedDate; // 输出当前时间的年月日 }, compareDate(date1,date2){//date1 > date2 返回1;date1 < date2 返回-1 相等返回0 if (date1.getTime() > date2.getTime()) { return 1; } else if (date1.getTime() < date2.getTime()) { return -1; } else { return 0; } }, // 上级节点 getTreeselect() { listPlan().then(response => { const data = {uid: 0, name: '顶级节点', children: []}; data.children = this.handleTree(response.data, 'uid', 'parentUid') this.planOptions.push(data) }) }, normalizer(node) { if (node.children && !node.children.length) { delete node.children } return { id: node.uid, label: node.name, children: node.children } }, rowClick(row, column, event) { // 行点击事件 }, /***************************************************甘特图 start****************************************************************/ // 查看任务详情 handleView(taskId) { // 根据任务ID查找任务详情 const task = this.tasks.data.find(item => item.id == taskId); if (task) { this.getTreeselect(); this.form = task; this.open = true; } }, expandGantt() { this.showGantt = false this.$nextTick(() => { this.initGantt(); // 确保DOM更新后再初始化甘特图 }); }, retractGantt() { this.showGantt = true; this.destroyGantt(); // 添加销毁甘特图方法 }, //开始时间-结束时间参数 DateDifference: function (strDateStart, strDateEnd) { var begintime_ms = Date.parse(new Date(strDateStart.replace(/-/g, "/"))); //begintime 为开始时间 var endtime_ms = Date.parse(new Date(strDateEnd.replace(/-/g, "/"))); // endtime 为结束时间 var date3 = endtime_ms - begintime_ms; //时间差的毫秒数 var days = Math.floor(date3 / (24 * 3600 * 1000)); return days; }, // 重置查询 resetQuery() { this.queryParams = { name: null, respPerson: null }; this.initGantt(); }, initGantt: function () { this.destroyGantt(); // 先销毁旧实例 // 确保容器存在 if (!this.$refs.gantt) return; // 设置容器尺寸 this.$refs.gantt.style.height = `${window.innerHeight - 150}px`; // 在初始化前配置所有设置 //自适应甘特图的尺寸大小, 使得在不出现滚动条的情况下, 显示全部任务 gantt.config.autosize = true; //只读模式 gantt.config.readonly = true; //是否显示左侧树表格 gantt.config.show_grid = true; //时间轴配置 gantt.config.show_task_cells = true; //当task的长度改变时,自动调整图表坐标轴区间用于适配task的长度 gantt.config.fit_tasks = true; gantt.config.min_column_width = 50; gantt.config.auto_types = true; gantt.config.xml_date = "%Y-%m-%d"; gantt.config.scale_unit = "month"; gantt.config.step = 1; gantt.config.date_scale = "%Y年%M"; gantt.config.start_on_monday = true; gantt.config.scale_height = 160; gantt.config.autoscroll = true; gantt.config.calendar_property = "start_date"; gantt.config.calendar_property = "end_date"; gantt.config.readonly = true; //时间刻度配置 var weekScaleTemplate = function (date) { var dateToStr = gantt.date.date_to_str("%m %d"); var endDate = gantt.date.add( gantt.date.add(date, 1, "week"), -1, "day" ); var weekNum = gantt.date.date_to_str("第 %W 周"); return weekNum(date); }; gantt.config.subscales = [ { unit: "week", step: 1, template: weekScaleTemplate, }, { unit: "day", step: 1, format: "%d", }, ]; //表格列设置 gantt.config.columns = [ { name: "code", label: "编号", tree: true, width: "160", onrender: function (task, node) { node.setAttribute( "class", "gantt_cell gantt_last_cell gantt_cell_tree " + task.status ); }, }, { name: "status", label: "状态", align: "center", width: "80", template: function (task) { // 自定义状态列显示为状态灯 return `<div class="status-light" style="background-color: ${task.color}"></div>`; } }, {name: "text", label: "名称", align: "center", width: "180", hide: true}, {name: "progress", label: "完成度(%)", align: "center", width: "90", hide: true}, {name: "respPerson", label: "责任人", align: "center", width: "120", hide: true}, {name: "respDept", label: "责任部门", align: "center", width: "140", hide: true}, {name: "planStartDate", label: "计划开始日期", align: "center", width: "130", hide: true}, {name: "planEndDate", label: "计划结束时间", align: "center", width: "130", hide: true}, {name: "realStartDate", label: "实际开始日期", align: "center", width: "130", hide: true}, {name: "realEndDate", label: "实际结束日期", align: "center", width: "130", hide: true}, {name: "planDuration", label: "计划工期", align: "center", width: "90", hide: true}, {name: "realDuration", label: "实际工期", align: "center", width: "90", hide: true}, {name: "remarks", label: "备注", align: "center", width: "220", hide: true}, ]; // 初始化 gantt.init(this.$refs.gantt); gantt.plugins({ tooltip: true, }); //设置鼠标放置显示事件 gantt.attachEvent("onGanttReady", function() { var tooltips = gantt.ext.tooltips; gantt.templates.tooltip_text = function(start, end, task) { return "编号:" + task.code + "<br/>" + "名称:" + task.text + "<br/>" + "计划开始:" + gantt.templates.tooltip_date_format(start) + "<br/>" + "工期:" + task.duration }; }); //设置任务条进度内容 gantt.templates.progress_text = function (start, end, task) { return ( "<div style='text-align:left;color:#fff;padding-left:20px'>" + task.progress + "% </div>" ); }; //任务条显示内容 gantt.templates.task_text = function (start, end, task) { return ( "<div style='text-align:center;color:#fff'>" + task.text + "(" + task.duration + "天)" + "</div>" ); }; //任务条上的文字大小 以及取消border自带样式 gantt.templates.task_class = function (start, end, item) { return item.$level == 0 ? "firstLevelTask" : "secondLevelTask"; }; // 设置布局 gantt.config.layout = { css: "gantt_container", cols: [ { width: "800", // 左侧表格固定宽度 min_width: 300, rows: [ { view: "grid", scrollX: "gridScroll", scrollable: true, scrollY: "scrollVer", }, { view: "scrollbar", id: "gridScroll", group: "horizontal", }, ], }, { resizer: true, width: 1, }, { rows: [ { view: "timeline", scrollX: "scrollHor", scrollY: "scrollVer", }, { view: "scrollbar", id: "scrollHor", group: "horizontal", }, ] } ], }; gantt.i18n.setLocale("cn"); listPlan(this.queryParams).then((res) => { this.tasks.data = res.data.map((item) => { let statusColor; //存在status字段 说明非一级菜单,判断阶段的具体类型 设置不同颜色 if (item.status == '1') { //冒烟 statusColor = "#84bd54" } else if (item.status == '2') { //单元 statusColor = "#fcca02" } else if (item.status == '3') { //回归 statusColor = "#dc1626" } else { statusColor = "#999999" } return { id: item.uid, parent: item.parent, text: item.name, start_date: item.planStartDate, duration: item.planDuration, open: true, //默认打开, toolTipsTxt: item.name, progress: item.schedule, status: item.status, code: item.code, respPerson: item.respPerson, respDept: item.respDept, planStartDate: item.planStartDate, planEndDate: item.planEndDate, realStartDate: item.realStartDate, realEndDate: item.realEndDate, planDuration: item.planDuration, realDuration: item.realDuration, remarks: item.remarks, feedback: item.feedback, color: statusColor, } }); // 数据解析 gantt.parse(this.tasks); }); // 添加双击行事件监听器 gantt.attachEvent("onTaskDblClick", function(id, e) { // 调用查看详情方法 window.vueInstance.handleView(id); return true; }); }, destroyGantt() { // 推荐方式:检查容器是否有内容 if (this.$refs.gantt && this.$refs.gantt.children.length > 0) { gantt.destroy(); // 安全销毁 } this.$refs.gantt.innerHTML = ""; // 清空容器 }, }, mounted() { window.vueInstance = this; if (!this.showGantt) { this.$nextTick(this.initGantt); } else { this.getList(); } }, }; </script> <style lang="scss" scoped> .firstLevelTask { border: none; .gantt_task_content { font-size: 13px; } } .secondLevelTask { border: none; } .thirdLevelTask { border: 2px solid #da645d; color: #da645d; background: #da645d; } .milestone-default { border: none; background: rgba(0, 0, 0, 0.45); } .milestone-unfinished { border: none; background: #5692f0; } .milestone-finished { border: none; background: #84bd54; } .milestone-canceled { border: none; background: #da645d; } html, body { margin: 0; padding: 0; height: 100%; overflow: hidden; } .container { height: 100%; width: 100%; position: relative; padding: 10px; .gantt_grid_head_cell { padding-left: 20px; text-align: left !important; font-size: 14px; color: #333; } .left-container { height: 100%; } .green, .yellow, .pink, .popular { .gantt_tree_icon.gantt_file { background: none; position: relative; &::before { content: ""; width: 10px; height: 10px; border-radius: 50%; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); } } } .green { .gantt_tree_icon.gantt_file { &::before { background: #84bd54; } } } .yellow { .gantt_tree_icon.gantt_file { &::before { background: #fcca02; } } } .pink { .gantt_tree_icon.gantt_file { &::before { background: #da645d; } } } .popular { .gantt_tree_icon.gantt_file { &::before { background: #d1a6ff; } } } } .left-container { height: 100%; } .gantt_task_content { text-align: left; padding-left: 10px; } .gantt-container { height: calc(100vh - 150px) !important; width: 100%; } // 状态灯样式 ::v-deep .gantt_grid_data .gantt_cell div.status-light { width: 12px; height: 12px; border-radius: 50%; display: inline-block; margin: 0 auto; } // 表格表头居中样式 ::v-deep .gantt_grid_head_cell { text-align: center !important; } .search-wrapper { text-align: left; padding: 10px 0; } .search-from { .el-form-item--mini.el-form-item, .el-form-item--small.el-form-item { margin-bottom: 3px; } } </style> <style lang="less"> .circle-light-yellow > span::before { content: ''; display: inline-block; width: 16px; height: 16px; background-color: #e6a23c; border-radius: 50%; margin-right: 10px; } .circle-light-green > span::before { content: ""; display: inline-block; width: 16px; height: 16px; background-color: #67c23a; border-radius: 50%; margin-right: 10px; } .circle-light-red > span::before { content: ""; display: inline-block; width: 16px; height: 16px; background-color: #f56c6c; border-radius: 50%; margin-right: 10px; } </style> 第一次展开甘特图时表格铺满了,请修复
最新发布
08-08
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <strings.h> #include <sys/mman.h> #include <linux/input.h> // bmp 图片显示函数原型 int show(const char* path); int main() { int x, y, k = 0; char photo[][30] = {"yy.bmp", "gy.bmp", "yql.bmp", "wrq.bmp"}; struct input_event myevent; // 定义数据缓冲区 // 打开触摸屏设备文件 int ts_fd = open("/dev/input/event0", O_RDONLY); if (ts_fd < 0) { perror("open /dev/input/event0 failed"); return -1; } while (1) { // 读取数据并输出 if (read(ts_fd, &myevent, sizeof(myevent)) != sizeof(myevent)) { perror("read event failed"); continue; } if (myevent.type == EV_ABS) { if (myevent.code == ABS_X) { x = myevent.value; } else if (myevent.code == ABS_Y) { y = myevent.value; } } if (myevent.type == EV_KEY && myevent.code == BTN_TOUCH && myevent.value == 0) { if (x < 400) { k++; if (k == 4) { k = 0; } } else { k--; if (k < 0) { k = 3; } } show(photo[k]); } } // 关闭触摸屏(实际不会执行到,可在退出时处理) close(ts_fd); return 0; } // bmp 图片显示函数实现 int show(const char* path) { int lcd_fd, bmp_fd; char bmp_buf[800 * 480 * 3] = {0}; int lcd_buf[800 * 480] = {0}; int show_buf[800 * 480] = {0}; // 打开 LCD 设备文件 lcd_fd = open("/dev/fb0", O_RDWR); if (lcd_fd < 0) { perror("open /dev/fb0 failed"); return -1; } // 打开图片 bmp_fd = open(path, O_RDONLY); if (bmp_fd < 0) { perror("open bmp file failed"); close(lcd_fd); return -1; } // 定位到像素数据位置 if (lseek(bmp_fd, 54, SEEK_SET) < 0) { perror("lseek bmp file failed"); close(bmp_fd); close(lcd_fd); return -1; } // 读取图片像素数据 if (read(bmp_fd, bmp_buf, sizeof(bmp_buf)) != sizeof(bmp_buf)) { perror("read bmp file failed"); close(bmp_fd); close(lcd_fd); return -1; } // 转换像素格式 for (int i = 0; i < 800 * 480; i++) { lcd_buf[i] = (bmp_buf[i * 3] << 0) | (bmp_buf[i * 3 + 1] << 8) | (bmp_buf[i * 3 + 2] << 16); } // 解决图片颠倒问题 for (int y = 0; y < 480; y++) { for (int x = 0; x < 800; x++) { show_buf[(479 - y) * 800 + x] = lcd_buf[800 * y + x]; } } // 内存映射 int *fd_mmap = mmap(NULL, 800 * 480 * 4, PROT_READ | PROT_WRITE, MAP_SHARED, lcd_fd, 0); if (fd_mmap == MAP_FAILED) { perror("mmap failed"); close(bmp_fd); close(lcd_fd); return -1; } // 显示图片 for (int i = 0; i < 800 * 480; i++) { *(fd_mmap + i) = show_buf[i]; } // 解除内存映射 munmap(fd_mmap, 800 * 480 * 4); // 关闭文件 close(bmp_fd); close(lcd_fd); return 0; } 再这份代码基础上,使用上一张,下一张,退出字库在开发板上控制
07-17
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值