解决html2canvas在element-ui el-dialog中因滚动条截图不全问题

本文记录了如何在使用html2canvas截取带有滚动条元素的el-dialog内容时,遇到截图不全的问题,分享了通过调整滚动位置临时解决的技巧,并寻求大佬们的更优解决方案。

关于html2canvas截图不全的问题,网上也有很多解决方法,我这里只是记录一下

大佬提了一个给el-dialog里面的内容生成截图的需求,很简单嘛!用html2canvas几行代码就搞定了,然后测试反馈说偶尔会出现截图不全的问题,尝试着复现了一下发现只要出现了滚动条就会出现截图不全,好了,直接上代码吧

     // 先把滚动前的位置记录一下 
     const scrollTop = document.documentElement.scrollTop
       // 加上这几行代码就好了
      window.pageYoffset = 0
      document.documentElement.scrollTop = 0
      document.body.scrollTop = 0
      html2canvas(this.$refs.qrImgUrl, {
        backgroundColor: '#FFFFFF',
        useCORS: true,
        width: 300,
        height: 450
      }).then(canvas => {
        var imgData = canvas.toDataURL('image/png')
        this.imgData = imgData
        var a = document.createElement('a')
        var event = new MouseEvent('click')
        a.href = this.imgData
        a.download = this.name ? this.name : '二维码'
        a.dispatchEvent(event)
        // 截完图后再把位置滚动到之前的位置
        document.documentElement.scrollTop = scrollTop
      })

大佬如果有更好的解决办法,可以留下你评论,万分感激!

<template> <div class="cnnp-main"> <el-button v-if="model === '3'" class="export" type="danger" style="min-width: 88px" @click="handleExport" > 导出 </el-button> <div class="table-container" ref="tableRef"> <table v-loading="loading"> <!-- 第一行有5列 --> <colgroup> <col v-for="i in 24" :width="100 / 24 + '%'" /> </colgroup> <tr> <th colspan="24">{{ work.name + '-' + work.description }}</th> </tr> <tr> <th colspan="3">测点</th> <th colspan="6">测点描述</th> <th colspan="15">任务</th> </tr> <template v-for="(site, siteIndex) in work.sites"> <template v-if="site.tasks && site.tasks.length"> <tr v-for="(task, taskIndex) in site.tasks" :key="task.id"> <td colspan="3" v-if="!taskIndex" :rowspan="Math.max(1, site.tasks.length)"> {{ site.name }} </td> <td colspan="6" v-if="!taskIndex" :rowspan="Math.max(1, site.tasks.length)"> {{ site.description }} <span class="red-start" v-if="site.method === '1' && site.digitalIdentification === '0'" > * </span> </td> <td colspan="6">{{ task.description }}</td> <td colspan="5">{{ task.taskTypeConfig && task.taskTypeConfig.name }}</td> <td :colspan="model === '1' ? 3 : 4"> {{ task.taskValue ? task.taskValue + (task.taskTypeConfig && task.taskTypeConfig.suffix ? task.taskTypeConfig.suffix : '') : '' }} <el-button type="text" v-if="model === '2'" @click="handleEdit(task)"> 修改 </el-button> </td> <td colspan="1" v-if="model === '1'"> <el-button size="large" type="text" @click="deleteTask(work.id, task.id)"> 删除 </el-button> </td> </tr> </template> <template v-else> <tr> <td colspan="3">{{ site.name }}</td> <td colspan="6">{{ site.description }}</td> <td colspan="15"> <el-button v-if="model === '1'" size="large" type="text" @click="deleteSite(work.id, site.id)" > 删除站点 </el-button> </td> </tr> </template> </template> <tr> <th colspan="3" rowspan="3">仪表</th> <td colspan="6">γ剂量率仪表类型+编码:</td> <td colspan="15">{{ work.meter1 }}</td> </tr> <tr> <td colspan="6">n剂量率仪表类型+编码:</td> <td colspan="15">{{ work.meter2 }}</td> </tr> <tr> <td colspan="6">表面污染仪类型+编码:</td> <td colspan="15">{{ work.meter3 }}</td> </tr> <tr style="height: 250px"> <th colspan="3">问题描述与处理措施</th> <td colspan="21">{{ work.solution }}</td> </tr> <tr> <th colspan="3">巡测人</th> <th colspan="5">{{ work.measurerName }}</th> <th colspan="3">记录人</th> <th colspan="5">{{ work.recorderName }}</th> <th colspan="3">审核人</th> <th colspan="5" v-if="model === '1' || work.approverId"> {{ model === '1' ? '' : work.approverName }} </th> <th colspan="5" v-if="model === '2' && !work.approverId"> <el-button @click="dialogVisible = true" type="text">审核通过</el-button> </th> </tr> </table> <!-- 审核意见展示 --> <div class="approve-remark" v-if="model === '3' && work.approveRemark"> <h3>审核意见:</h3> <el-input v-model="work.approveRemark" style="width: 100%" :rows="3" type="textarea" readonly /> </div> </div> <div class="dialog-footer"> <el-button @click="handleBack">返回</el-button> </div> <!-- 审核 --> <el-dialog v-model="dialogVisible" title="审核意见" width="500" :before-close="handleClose"> <el-input v-model="approveRemark" style="width: 100%" :rows="3" type="textarea" placeholder="请输入审核意见" :maxlength="200" /> <template #footer> <div class="dialog-footer"> <el-button @click="((dialogVisible = false), (approveRemark = ''))">取消</el-button> <el-button type="primary" @click="onApproveWork(work.id)">提交</el-button> </div> </template> </el-dialog> <!-- 修改 --> <el-dialog v-if="showEdit" v-model="showEdit" :title="'修改' + editData.taskTypeConfig.name" width="500" :before-close="handleClose" > <el-input v-model="editValue" style="width: 100%" placeholder="请输入" :maxlength="200" /> <template #footer> <div class="dialog-footer"> <el-button @click="onCancelEdit">取消</el-button> <el-button type="primary" @click="onConfirmEdit">提交</el-button> </div> </template> </el-dialog> </div> </template> <script> import { getWork, deleteTask, deleteSite, approveWork, updateTask, exportWork, } from '@/api/duty/work' import { http2 } from '@/axios/http' import html2canvas from 'html2canvas' export default { name: 'WorkDetails', data() { return { loading: false, model: '1', //1 计划详情,2 审核 ,3 历史 work: {}, dialogVisible: false, approveRemark: '', editValue: '', showEdit: false, editData: { taskTypeConfig: null }, } }, created() { this.model = this.$route.params.model this.getWork() }, methods: { onApproveWork(workId) { this.loading = true approveWork({ workId, approveRemark: this.approveRemark }).then((res) => { this.loading = false this.dialogVisible = false this.$message.success(`审核通过!`) this.approveRemark = '' this.getWork() }) }, getWork() { this.loading = true getWork(this.$route.params.id).then((res) => { console.log(res) this.work = res.data this.loading = false }) }, deleteTask(workId, taskId) { ElMessageBox.confirm('是否确认删除任务?', '提示', { type: 'warning', }).then(() => { deleteTask(workId, taskId).then(() => { this.getWork() this.$message.success(`删除成功!`) }) }) }, deleteSite(workId, siteId) { ElMessageBox.confirm('是否确认删除站点?', '提示', { type: 'warning', }).then(() => { deleteSite(workId, siteId).then(() => { this.getWork() this.$message.success(`删除成功!`) }) }) }, handleBack() { this.$router.back() }, async handleExport() { const PAGE_SIZE = 6 // 每页站点数,可按需调 const sites = this.work.sites || [] const total = sites.length if (!total) return this.$message.warning('暂无测点数据') this.loading = true const imagePages = [] // 收集每页 base64 /* 离线截图容器 */ const box = document.createElement('div') box.style.cssText = ` position:absolute; top:0; left:0; width:1400px; height:auto; background:#fff; z-index:-9999; /* 藏在最底层 */ opacity:0; /* 透明 */ pointer-events:none; /* 干扰点击 */ ` document.body.appendChild(box) try { /* ===== 1. 克隆骨架 table(带 tbody) ===== */ const srcTable = this.$refs.tableRef const table = srcTable.cloneNode(false) // false = 克隆子节点 ;['thead', 'tfoot'].forEach((tag) => { const el = srcTable.querySelector(tag) if (el) table.appendChild(el.cloneNode(true)) }) /* ===== 2. 按页循环 ===== */ for (let start = 0; start < total; start += PAGE_SIZE) { const end = Math.min(start + PAGE_SIZE, total) const pageSites = sites.slice(start, end) /* ---- 生成当前页 tbody ---- */ const tbody = document.createElement('tbody') /* 站点行 */ pageSites.forEach((site) => { if (site.tasks?.length) { site.tasks.forEach((task, idx) => { const tr = document.createElement('tr') if (idx === 0) { // 首行加 rowspan tr.innerHTML = ` <td colspan="3" rowspan="${site.tasks.length}">${site.name}</td> <td colspan="6" rowspan="${site.tasks.length}"> ${site.description} ${site.method === '1' && site.digitalIdentification === '0' ? '<span class="red-start">*</span>' : ''} </td>` } tr.innerHTML += ` <td colspan="6">${task.description}</td> <td colspan="5">${task.taskTypeConfig?.name || ''}</td> <td colspan="${this.model === '1' ? 3 : 4}"> ${task.taskValue || ''}${task.taskTypeConfig?.suffix || ''} </td> ${this.model === '1' ? '<td colspan="1"></td>' : ''} ` tbody.appendChild(tr) }) } else { // 无任务 const tr = document.createElement('tr') tr.innerHTML = ` <td colspan="3">${site.name}</td> <td colspan="6">${site.description}</td> <td colspan="15"></td> ` tbody.appendChild(tr) } }) /* ---- 插 tbody → 截图 ---- */ box.appendChild(table) await this.$nextTick() await new Promise((r) => setTimeout(r, 100)) // 100 ms 给浏览器渲染 const canvas = await html2canvas(box, { useCORS: true, backgroundColor: '#fff', scale: 2, width: box.scrollWidth, height: box.scrollHeight, }) /* 3. 立刻移除 */ document.body.removeChild(box) imagePages.push(canvas.toDataURL('image/png')) tbody.remove() // 清掉,复用骨架 } console.log(imagePages) /* ===== 3. 一次性发后端 ===== */ await http2.exportFile( { url: '/system/work/export', method: 'post', data: { imageBase64Work: imagePages }, }, `${this.work.name}_值班历史数据.pdf`, ) } catch (e) { console.error('导出失败:', e) this.$message.error('导出失败,请重试') } finally { document.body.removeChild(box) this.loading = false } }, handleEdit(task) { console.log(task) this.editData = task this.editValue = task.taskValue this.showEdit = true }, onConfirmEdit() { if (!this.editValue) return this.$message.warning(`请输入内容!`) this.loading = true updateTask({ id: this.editData.id, taskValue: this.editValue }).then((res) => { this.loading = false this.dialogVisible = false this.$message.success(`修改成功!`) this.onCancelEdit() this.getWork() }) }, onCancelEdit() { this.showEdit = false this.editData = { taskTypeConfig: null } this.editValue = '' }, }, } </script> <style scoped> .cnnp-main { display: flex; flex-direction: column; align-items: center; justify-content: center; } .export { margin-left: auto; margin-right: 15px; margin-top: 10px; } table { border-collapse: collapse; /* 合并边框 */ width: 95%; /* 可选的宽度设置 */ height: 100%; overflow: hidden; margin: 0 auto; } table, th, td { text-align: center; height: 50px; border: 1px solid black; /* 设置1px的实线边框 */ color: #333; } th { font-weight: bold; } .dialog-footer { margin: 20px; } .table-container { height: 660px; /* 设置一个固定高度 */ overflow-y: auto; /* 垂直方向自动滚动 */ width: 100%; /* 保持表格宽度 */ margin: auto; /* 居中显示 */ } .approve-remark { margin: 30px; h3 { font-size: 16px; font-weight: bold; } } .red-start { color: red; font-size: 20px; } </style> 修复 handleExport方法
10-25
<template> <div class="page-container"> <el-row :gutter="20"> <!-- 查询区域 --> <el-col :span="24"> <div class="search-wrapper"> <el-form :inline="true" label-width="100px" @submit.prevent="getList"> <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="getList">查询</el-button> <el-button @click="resetQuery">重置</el-button> <el-button type="primary" @click="toggleGantt" style="margin-left: 10px;" > {{ showGantt ? '收起甘特图' : '展开甘特图' }} </el-button> </el-form-item> </el-form> </div> </el-col> <!-- 左侧列表 --> <el-col :span="showGantt ? 12 : 24"> <div class="table-container"> <el-table ref="table" :data="listData" row-key="uid" border :row-style="{ height: '30px' }" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" @row-click="handleRowClick" @expand-change="handleExpandChange" highlight-current-row > <el-table-column prop="code" label="编号" width="120" /> <el-table-column prop="name" label="名称" min-width="180" /> <el-table-column prop="respPerson" label="责任人" width="120" /> <el-table-column prop="schedule" label="完成百分比" width="120"> <template slot-scope="{row}"> <el-progress :percentage="row.schedule" :show-text="row.schedule > 10" :stroke-width="18" :color="getProgressColor(row.schedule)" /> </template> </el-table-column> <el-table-column prop="planStartDate" label="计划开始日期" width="150" /> <el-table-column prop="planEndDate" label="计划结束日期" width="150" /> <el-table-column label="操作" width="100"> <template slot-scope="scope"> <el-button size="mini" icon="el-icon-view" @click.stop="handleUpdate(scope.row)">查看</el-button> </template> </el-table-column> </el-table> </div> </el-col> <!-- 右侧甘特图容器 --> <el-col v-if="showGantt" :span="12"> <div ref="ganttContainer" class="gantt-container" style="width: 100%; height: 600px;"></div> </el-col> </el-row> <!-- 查看弹窗 --> <el-dialog :title="title" :visible.sync="open" width="850px" append-to-body> <el-form ref="form" :model="form" :rules="rules" label-width="100px" :disabled="disable"> <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="name"> <el-input v-model="form.name" 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="请输入备注" rows="3" /> </el-form-item> </el-col> </el-row> <div class="dialog-footer"> <el-button @click="cancel">取 消</el-button> </div> </el-form> </el-dialog> </div> </template> <script> import gantt from 'dhtmlx-gantt'; import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'; import { getPlan, listPlan } from '@/api/dw/plan/planview'; export default { name: 'Planview', data() { return { expandedKeys: new Set(), // 存储所有展开节点的UID listData: [], total: 0, queryParams: { pageNum: 1, pageSize: 1000, // 树形结构适合分页,增加单页大小 name: null, respPerson: null }, open: false, title: '', form: {}, rules: { name: [{ required: true, message: '名称能为空', trigger: 'blur' }], schedule: [ { required: true, message: '完成百分比能为空', trigger: 'blur' }, { type: 'number', message: '输入内容是有效的数字', trigger: 'blur' } ] }, disable: true, showGantt: true, // 控制甘特图显示 flatData: [], // 扁平化数据 ganttInitialized: false, // 甘特图初始化标志 currentSelectedTask: null, // 当前选中的任务ID ganttExpandState: new Map() // 存储甘特图的展开状态 }; }, mounted() { this.getList(); }, methods: { // 获取进度条颜色 getProgressColor(percentage) { if (percentage < 30) return '#F56C6C'; if (percentage < 70) return '#E6A23C'; return '#67C23A'; }, // 初始化甘特图 initGantt() { if (!this.$refs.ganttContainer) return; try { // 清除之前的实例(如果存在) if (gantt.$container) { gantt.destructor(); } gantt.config.date_format = '%Y-%m-%d'; gantt.config.scale_unit = 'month'; gantt.config.step = 1; gantt.config.subscales = [ { unit: 'day', step: 1, date: '%j, %D' } ]; gantt.config.columns = [ { name: 'text', label: '任务名称', tree: true, width: 200 }, { name: 'start_date', label: '开始时间', align: 'center', width: 100 }, { name: 'end_date', label: '结束时间', align: 'center', width: 100 }, { name: 'progress', label: '进度', align: 'center', width: 80, template: (task) => `${task.progress * 100}%` } ]; gantt.config.row_height = 30; gantt.config.grid_width = 500; gantt.templates.task_text = (start, end, task) => task.text; gantt.init(this.$refs.ganttContainer); // 绑定事件 gantt.attachEvent('onTaskSelected', (id) => { this.currentSelectedTask = id; this.scrollToTableRow(id); }); // 绑定展开/折叠事件 gantt.attachEvent('onAfterTaskOpen', (id) => { this.ganttExpandState.set(id, true); this.syncGanttExpandToTable(id, true); }); gantt.attachEvent('onAfterTaskClose', (id) => { this.ganttExpandState.set(id, false); this.syncGanttExpandToTable(id, false); }); this.ganttInitialized = true; console.log('甘特图初始化成功'); } catch (e) { console.error('甘特图初始化失败:', e); } }, // 将甘特图的展开状态同步到表格 syncGanttExpandToTable(taskId, expanded) { const row = this.flatData.find(item => item.uid === taskId); if (!row) return; // 更新展开状态 if (expanded) { this.expandedKeys.add(row.uid); } else { this.expandedKeys.delete(row.uid); } // 更新表格UI this.$nextTick(() => { const tableRow = this.$refs.table.$el.querySelector(`[data-id="${row.uid}"]`); if (tableRow) { const expandIcon = tableRow.querySelector('.el-table__expand-icon'); if (expandIcon) { const isExpanded = expandIcon.classList.contains('el-table__expand-icon--expanded'); if (isExpanded !== expanded) { this.$refs.table.toggleRowExpansion(row, expanded); } } } }); }, // 获取数据 async getList() { try { const res = await listPlan(this.queryParams); this.listData = this.handleTree(res.data, 'uid', 'parentUid'); this.flatData = this.flattenTree(this.listData); // 初始展开所有节点 this.expandedKeys = new Set(this.flatData.map(item => item.uid)); this.$nextTick(() => { // 初始化甘特图 if (this.showGantt) { this.initGantt(); this.updateGantt(); } // 展开所有节点 this.expandAllNodes(); }); } catch (error) { console.error('获取数据失败:', error); } }, // 递归展开所有节点 expandAllNodes() { if (!this.$refs.table || !this.listData.length) return; const expandNode = (node) => { this.$refs.table.toggleRowExpansion(node, true); if (node.children && node.children.length) { node.children.forEach(child => expandNode(child)); } }; this.listData.forEach(root => expandNode(root)); }, // 更新甘特图数据 updateGantt() { if (!this.ganttInitialized) return; const tasks = this.getVisibleTasks(); console.log('更新甘特图任务数量:', tasks.length); try { // 保存当前甘特图的展开状态 this.saveGanttExpandState(); gantt.clearAll(); gantt.parse({ data: tasks, links: [] }); // 恢复甘特图的展开状态 this.restoreGanttExpandState(); this.adjustGanttView(tasks); } catch (e) { console.error('更新甘特图失败:', e); } }, // 保存甘特图的展开状态 saveGanttExpandState() { if (!this.flatData.length) return; // 遍历所有任务,保存展开状态 this.flatData.forEach(item => { if (gantt.isTaskExists(item.uid)) { this.ganttExpandState.set(item.uid, gantt.isTaskOpen(item.uid)); } }); }, // 恢复甘特图的展开状态 restoreGanttExpandState() { this.ganttExpandState.forEach((isOpen, taskId) => { if (gantt.isTaskExists(taskId)) { gantt.openTask(taskId, isOpen); } }); }, // 获取当前可见的任务(根据展开状态) getVisibleTasks() { const visibleTasks = []; const collectVisible = (nodes) => { nodes.forEach(node => { visibleTasks.push({ id: node.uid, text: node.name, start_date: node.planStartDate, duration: node.planDuration || 1, progress: (node.schedule || 0) / 100, parent: node.parentUid || 0, open: this.expandedKeys.has(node.uid) // 设置初始展开状态 }); // 如果节点是展开的,递归收集子节点 if (this.expandedKeys.has(node.uid) && node.children) { collectVisible(node.children); } }); }; collectVisible(this.listData); return visibleTasks; }, // 自动调整甘特图视图 adjustGanttView(tasks) { if (!tasks.length) return; // 计算时间范围 const dates = tasks .filter(t => t.start_date) .map(t => new Date(t.start_date)); if (!dates.length) return; const minDate = new Date(Math.min(...dates.map(d => d.getTime()))); const maxDate = new Date(Math.max(...dates.map(t => { const endDate = new Date(t.start_date); endDate.setDate(endDate.getDate() + (t.duration || 0)); return endDate.getTime(); }))); // 设置时间范围 gantt.setWorkTime({ start_date: minDate, end_date: maxDate }); // 根据时间跨度调整缩放级别 const timeDiffInDays = Math.ceil((maxDate - minDate) / (1000 * 60 * 60 * 24)); if (timeDiffInDays <= 7) { gantt.config.scale_unit = 'day'; gantt.config.step = 1; } else if (timeDiffInDays <= 31) { gantt.config.scale_unit = 'week'; gantt.config.step = 1; } else if (timeDiffInDays <= 365) { gantt.config.scale_unit = 'month'; gantt.config.step = 1; } else { gantt.config.scale_unit = 'year'; gantt.config.step = 1; } gantt.render(); }, // 树形结构转扁平结构 flattenTree(data) { const result = []; const stack = [...data]; while (stack.length) { const node = stack.pop(); result.push(node); if (node.children) { stack.push(...node.children); } } return result; }, // 处理树形结构 handleTree(data, idKey = 'uid', parentKey = 'parentUid') { const map = {}; const tree = []; // 创建映射 data.forEach(item => { map[item[idKey]] = { ...item, children: [] }; }); // 构建树 data.forEach(item => { const parentId = item[parentKey]; if (parentId && map[parentId]) { map[parentId].children.push(map[item[idKey]]); } else { tree.push(map[item[idKey]]); } }); return tree; }, // 行点击事件 handleRowClick(row) { this.$nextTick(() => { // 高亮当前行 this.$refs.table.setCurrentRow(row); // 在甘特图中选中对应任务 if (this.ganttInitialized) { gantt.selectTask(row.uid); gantt.showTask(row.uid); } }); }, // 滚动到表格行 scrollToTableRow(taskId) { const row = this.flatData.find(item => item.uid === taskId); if (!row) return; this.$nextTick(() => { // 确保所有父节点都展开 this.expandParents(row); // 高亮当前行 this.$refs.table.setCurrentRow(row); // 滚动到元素 const tableBody = this.$refs.table.$el.querySelector('.el-table__body-wrapper'); const rowEl = this.$refs.table.$el.querySelector(`[data-id="${row.uid}"]`); if (tableBody && rowEl) { const rowTop = rowEl.offsetTop; const tableHeight = tableBody.clientHeight; tableBody.scrollTop = rowTop - tableHeight / 2; } }); }, // 展开父节点 expandParents(row) { if (!row.parentUid) return; const parent = this.flatData.find(item => item.uid === row.parentUid); if (parent && !this.expandedKeys.has(parent.uid)) { this.expandedKeys.add(parent.uid); this.$refs.table.toggleRowExpansion(parent, true); this.expandParents(parent); } }, // 树展开/折叠更新甘特图 handleExpandChange(row, expanded) { // 更新展开状态 if (expanded) { this.expandedKeys.add(row.uid); } else { this.expandedKeys.delete(row.uid); // 折叠时同时折叠所有子节点 this.collapseChildren(row); } // 更新甘特图 this.$nextTick(() => { this.updateGantt(); // 同步到甘特图展开状态 if (this.ganttInitialized && gantt.isTaskExists(row.uid)) { gantt.openTask(row.uid, expanded); } }); }, // 递归折叠子节点 collapseChildren(node) { if (node.children && node.children.length) { node.children.forEach(child => { this.expandedKeys.delete(child.uid); this.$refs.table.toggleRowExpansion(child, false); this.collapseChildren(child); }); } }, // 切换甘特图显示 - 解决重新初始化问题 toggleGantt() { this.showGantt = !this.showGantt; if (this.showGantt) { this.$nextTick(() => { // 确保每次展开都重新初始化甘特图 this.ganttInitialized = false; this.initGantt(); this.updateGantt(); }); } }, // 获取数据详情 async handleUpdate(row) { try { const res = await getPlan(row.uid); this.form = res.data; this.open = true; this.title = '查看治理计划'; } catch (error) { console.error('获取详情失败:', error); } }, // 取消按钮 cancel() { this.open = false; }, // 重置查询 resetQuery() { this.queryParams = { pageNum: 1, pageSize: 1000, name: null, respPerson: null }; this.getList(); } } }; </script> <style scoped> .page-container { padding: 20px; background-color: #f5f7fa; } .search-wrapper { background-color: #fff; padding: 15px 20px; border-radius: 4px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); margin-bottom: 20px; } .table-container { background-color: #fff; padding: 15px; border-radius: 4px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); } .gantt-container { background-color: #fff; border-radius: 4px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); padding: 5px; } .dialog-footer { text-align: right; margin-top: 20px; } .toggle-button { margin-bottom: 15px; } .el-table { width: 100%; } .el-table--border { border: 1px solid #ebeef5; } .el-table__row:hover { background-color: #f5f7fa !important; } .el-progress { margin-top: 8px; } .el-form-item { margin-bottom: 18px; } </style> 能否把甘特图与列表合并呢,这样是是就用考虑太多联动性了
07-24
<template> <div> <el-dialog title="OTA批量升级" :visible.sync="dialogVisible" width="600px" custom-class="ota-dialog" :close-on-click-modal="false" > <div class="dialog-content"> <!-- 操作区域 --> <div class="action-section"> <div class="upload-section"> <el-upload action="/api/upload" :on-success="handleUploadSuccess" :before-upload="beforeUpload" :limit="1" :on-exceed="handleExceed" :file-list="fileList" :class="{ 'has-file': fileList.length }" drag > <i class="el-icon-upload"></i> <div class="el-upload__text"> <div>点击或拖拽文件到此处上传</div> <div class="el-upload__tip">支持.xlsx、.xls格式文件</div> </div> </el-upload> <!-- 文件信息卡片 --> <div v-if="fileList.length" class="file-card"> <div class="file-info"> <i class="el-icon-document"></i> <div class="file-details"> <div class="file-name">{{ fileList[0].name }}</div> <div class="file-size">{{ formatFileSize(fileList[0].size) }}</div> </div> </div> <el-button type="danger" icon="el-icon-delete" circle @click="fileList = []" ></el-button> </div> </div> </div> </div> <!-- 底部按钮 --> <div slot="footer" class="dialog-footer"> <el-button @click="dialogVisible = false">取 消</el-button> <el-button type="primary" @click="otaBatchUpgradeCinfirm()" :disabled="!fileList.length" > 确定 </el-button> </div> </el-dialog> </div> </template> <script> export default { data() { return { dialogVisible: false, fileList: [] }; }, methods: { init() { this.dialogVisible = true; this.fileList = []; }, otaBatchUpgradeCinfirm() { // 升级逻辑 }, handleUploadSuccess(response, file, fileList) { this.$ message.success('文件上传成功'); this.fileList = fileList; }, beforeUpload(file) { const isValidType = [ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-excel' ].includes(file.type); if (!isValidType) { this.$ message.error('请上传Excel格式的文件 (.xlsx 或 .xls)'); } return isValidType; }, handleExceed() { this.$ message.warning('每次只能上传一个文件'); }, formatFileSize(size) { if (size < 1024) return size + ' B'; if (size < 1024 * 1024) return (size / 1024).toFixed(1) + ' KB'; return (size / (1024 * 1024)).toFixed(1) + ' MB'; } } }; </script> <style scoped> /* 操作区域 */ .action-section { display: flex; flex-direction: column; gap: 20px; } .upload-section { position: relative; display: flex; justify-content: center; } /* 文件卡片 */ .file-card { margin-top: 15px; padding: 15px; border-radius: 8px; background-color: #f5f7fa; display: flex; align-items: center; justify-content: space-between; border: 1px solid #ebeef5; } .file-info { display: flex; align-items: center; gap: 12px; } .file-info i { font-size: 28px; color: #409EFF; } .file-details { line-height: 1.5; } .file-name { font-weight: 500; max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .file-size { font-size: 12px; color: #909399; } /* 底部按钮 */ .dialog-footer { display: flex; justify-content: flex-end; padding-top: 15px; border-top: 1px solid #ebeef5; } /* 上传区域激活状态 */ .has-file >>> .el-upload-dragger { border: 1px dashed #67C23A; background-color: rgba(103, 194, 58, 0.05); } </style> 添加上传文件列表和文件预览功能,要保证美观和交互完美,加其他新的功能
07-12
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值