(56) 解决字段设为readonly无法保存

本文介绍了解决Odoo框架中readonly字段无法随其他字段动态更新并保存至数据库的问题。提供了两种解决方案:一是直接修改view_form.js文件;二是使用web_readonly_bypass模块配合特定API方法实现。

问题描述:
当一个字段设为readonly =True 后,在form表单,即使你用onchange方法改变了值
但也不能保存到数据库当时。平时在这样的要求,form表单有些字段要展示给用户,但又要达到不
能更改,当通过其它字段变化,那个字段也会变化,这时那个字段发生了变化,当然要存到数据库当

分析解决:
这个form.js 一个小bug ,修正一下即可
view_form.js  853行

- if (!f.get("readonly")) {
-      values[f.name] = f.get_value();
-  } else {
-      readonly_values[f.name] = f.get_value();
-  }
+  values[f.name] = f.get_value();
+   if(f.get("readonly"))
+  readonly_values[f.name] = f.get_value();

前面有 “-” 号的,就要注释掉
前面有 “+”号的,加入来即可

===============================

上面的方法,副作用很大,第二种方法相对是更好的选择

用下面这个小模块来解决

web_readonly_bypass

链接:https://pan.baidu.com/s/1smyFBnZ 密码:9zxf

只要先安装这个模块

在自己对应的action 加入要保存的字段, 如下:

 

 
@api.multi
def onchange_partner_id(self, partner_id):
res = super(SaleOrder, self).onchange_partner_id(partner_id)
if partner_id:
customer_orders = self.env['sale.order'].search([('partner_id','=',partner_id),('state','not in',('cancel','draft','sent'))],limit=1)
if not customer_orders :
res['value']['is_new'] = True
else:
res['value']['is_new'] = False
partner = self.env['res.partner'].browse(partner_id)

res['value']['customer_ref'] = partner.ref
else:
res['value']['is_new'] = False
res['value']['customer_ref'] = None
res['value']['vip_grade_name'] = None
res['value']['vip_potential'] = False
return res
=====================
 
<record id="action_unified_order" model="ir.actions.act_window">
<field name="name">All Orders</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sale.order</field>
<field name="view_type">form</field>
<field name="view_id" ref="sale.view_order_tree"/>
<field name="view_mode">tree,form,calendar,graph</field>
<field name="search_view_id" ref="sale.view_sales_order_filter"/>

<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a quotation, the first step of a new sale.
</p><p>
Odoo will help you handle efficiently the complete sale flow:
from the quotation to the sales order, the
delivery, the invoicing and the payment collection.
</p><p>
The social feature helps you organize discussions on each sales
order, and allow your customers to keep track of the evolution
of the sales order.
</p>
</field>
</record>

就这样,reaonly 的字段,就可以根据其它字段变化,而更改保存到数据库当中, 若不用这个模块,系统是不保存到数据库,虽然表面变了。

转载于:https://www.cnblogs.com/toby2chen/p/7919351.html

<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="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="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.initData(); // 确保DOM更新后再初始化甘特图 }); }, retractGantt() { this.showGantt = true; this.$nextTick(() => { this.initData(); // 确保DOM更新后再初始化甘特图 }); }, //开始时间-结束时间参数 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: "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> <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
<think>我们注意到在代码中,有一个`showGantt`变量用于控制甘特图的显示。在初始状态下,`showGantt`为`true`,此时显示的是表格(TablePane),而甘特图部分被`v-if="!showGantt"`控制,初始不显示。点击“展开甘特图”按钮时,会调用`expandGantt`方法,将`showGantt`设为`false`,然后调用`initData`重新初始化数据。同样,点击“收起甘特图”按钮时,调用`retractGantt`方法,将`showGantt`设为`true`,然后重新调用`initData`。 但是,在`initData`方法中,我们注意到有以下代码: ```javascript gantt.clearAll(); listPlan(this.queryParams).then((res) => { // ... 处理数据,设置gantt配置 gantt.init(this.$refs.gantt); gantt.parse(this.tasks); // ... 其他代码 }); ``` 问题可能出现在以下几个方面: 1. 当切换显示时,DOM元素可能被销毁和重建,而`gantt.init`需要绑定到一个存在的DOM元素上。在`v-if`切换时,如果之前甘特图已经初始化,再次初始化到同一个DOM元素可能会出现问题(比如重复绑定事件、内存泄漏等)。通常,我们需要在切换时销毁之前的甘特图实例,然后再重新初始化。 2. 在`expandGantt`和`retractGantt`方法中,我们使用`this.$nextTick`确保DOM更新后再执行`initData`,这是正确的。但是,在甘特图显示状态切换时,我们并没有销毁之前的甘特图实例。 3. 另外,在`initData`方法中,每次都会重新初始化甘特图(调用`gantt.init`)。如果甘特图已经初始化过,再次初始化可能会导致问题。 解决方案: 我们可以添加一个变量来记录甘特图是否已经初始化,避免重复初始化。或者,在每次初始化之前,先销毁之前的甘特图实例。 但是,dhtmlxGantt并没有提供一个销毁方法。我们可以通过以下方式销毁: - 清除gantt的所有事件:`gantt.detachAllEvents()` - 清除gantt的数据:`gantt.clearAll()` - 从DOM中移除gantt生成的元素:我们可以通过`gantt.$container`(但是gantt并没有提供这个属性)或者我们自己保存容器元素,然后将其内容清空。 实际上,dhtmlxGantt提供了一个方法`gantt.destroy()`来销毁实例。我们可以使用它。 修改`initData`方法,在初始化之前先检查是否已经存在一个实例,如果存在则销毁它。 但是,在代码中,我们每次都是重新调用`gantt.init`,而根据文档,多次初始化到同一个容器是允许的,它会自动销毁之前的实例。但是,为了确保安全,我们可以尝试在初始化之前先销毁。 然而,我们注意到在切换显示时,甘特图的容器`this.$refs.gantt`在表格显示时(`showGantt`为true)是不存在的,因为`v-if="!showGantt"`。所以,在显示表格时,甘特图的容器已经不在DOM中,此时我们不需要初始化甘特图。而在切换为显示甘特图时,容器会重新创建。 因此,我们可以在`initData`中,当`showGantt`为true(即当前显示表格)时,我们不需要初始化甘特图。但是,在`expandGantt`方法中,我们将`showGantt`设为false,然后调用`initData`,此时在`initData`中,`showGantt`已经是false,所以会执行甘特图的初始化。 但是,在`retractGantt`方法中,我们将`showGantt`设为true,然后调用`initData`,此时在`initData`中,因为`showGantt`为true,所以甘特图部分不显示,我们不应该初始化甘特图,而应该只加载表格数据。 然而,我们注意到在`retractGantt`方法中,我们切换为显示表格,此时甘特图容器被销毁,我们还需要销毁甘特图实例以释放资源。 因此,我们可以在`retractGantt`方法中,在切换状态后,除了调用`initData`(用于重新加载表格数据)外,还应该销毁甘特图实例。但是,在`initData`方法中,我们并没有根据`showGantt`来避免初始化甘特图,所以当`showGantt`为true时,我们不应该执行甘特图的初始化。 修改`initData`方法,在显示甘特图(即`!this.showGantt`)时才初始化甘特图,否则只加载表格数据(即调用`this.getList()`)? 但是,在`initData`方法中,我们调用了`listPlan`接口,然后处理数据,最后初始化甘特图。如果当前是表格显示,我们不需要初始化甘特图,但是我们需要更新表格数据吗?实际上,在表格显示时,我们是通过`TablePane`组件来加载数据的,而`initData`方法中并没有更新表格数据,所以我们需要在`initData`中根据状态分别处理。 但是,观察代码,在`initData`中,我们调用了`listPlan`接口,然后处理数据,如果是甘特图显示,我们就初始化甘特图;如果是表格显示,我们其实应该将数据设置给表格。但是,目前代码中,表格数据是通过`TablePane`组件自己调用的,我们并没有在`initData`中处理表格数据。 因此,我们需要重构: 方案1:将数据获取独立出来,然后根据当前显示状态,如果是表格显示,则更新表格数据(通过TablePane的方法);如果是甘特图显示,则初始化甘特图。 方案2:由于表格数据是通过TablePane内部的加载方法(通过ref调用`loadData`)来加载的,而甘特图的数据是单独在`initData`中加载的,这样会导致两次请求。我们可以将数据获取统一,然后根据显示状态分别渲染。 考虑到时间,我们采用一个简单的方法:在切换显示状态时,分别调用各自的数据加载方法。 具体修改如下: 1. 在`expandGantt`方法中,切换状态后,调用`initGanttData`(专门初始化甘特图数据的方法)。 2. 在`retractGantt`方法中,切换状态后,调用`getList`(加载表格数据)。 同时,为了避免重复代码,我们将甘特图的初始化数据部分提取出来。 但是,我们注意到在`initData`方法中,我们同时处理了甘特图的初始化和数据加载,而且这个方法在重置查询等情况下也会调用。因此,我们修改`initData`方法,使其只在甘特图显示时执行,而在表格显示时,我们调用表格的刷新方法。 修改步骤: - 将原来的`initData`方法重命名为`initGantt`,并调整其只负责甘特图的初始化和数据加载。 - 在`expandGantt`方法中,调用`this.initGantt()`。 - 在`retractGantt`方法中,调用`this.getList()`(即表格的刷新)。 - 同时,在`resetQuery`方法中,我们需要根据当前显示状态来决定是刷新表格还是刷新甘特图。 但是,为了保持查询参数同步,我们仍然需要统一处理查询参数。 由于时间关系,我们采用以下方案: 在`resetQuery`和搜索按钮触发的`initData`(改名为`loadData`)中,根据`showGantt`状态决定调用哪个方法: ```javascript loadData() { if (this.showGantt) { this.getList(); // 刷新表格 } else { this.initGantt(); // 刷新甘特图 } } ``` 然后,将原来的`initData`方法改名为`initGantt`,并确保它只在甘特图显示时被调用。 但是,注意在`created`和`mounted`中,我们调用了`initData`,也需要修改。 由于改动较大,我们在这里先解决点击按钮切换的问题,即确保在点击“展开甘特图”和“收起甘特图”时,能正确显示对应的视图。 我们修改两个方法: ```javascript expandGantt() { this.showGantt = false; this.$nextTick(() => { this.initGantt(); // 初始化甘特图 }); }, retractGantt() { this.showGantt = true; // 收起甘特图时,不需要再初始化甘特图,而是刷新表格 this.$nextTick(() => { this.getList(); // 刷新表格 }); }, ``` 然后,将原来的`initData`方法改名为`initGantt`,并在其中只处理甘特图的初始化和数据加载。 另外,在`resetQuery`和搜索按钮的`initData`(现在改名为`loadData`)中,我们根据状态调用对应的方法: ```javascript initData: function() { // 这个函数现在改名为loadData,但为了避免混淆,我们重新命名 if (this.showGantt) { this.getList(); } else { this.initGantt(); } } ``` 但是,注意在模板中,搜索按钮的点击事件绑定的是`initData`,我们需要修改为`loadData`。 由于代码较长,我们只修改关键部分。 但是,我们注意到在`resetQuery`方法中,我们调用了`this.initData()`,而`initData`现在被我们改名为`loadData`,所以需要修改`resetQuery`: ```javascript resetQuery() { this.queryParams = { name: null, respPerson: null }; this.loadData(); // 改为调用loadData }, ``` 同时,将搜索按钮的点击事件改为`loadData`。 总结修改步骤: 1. 将原来的`initData`方法改名为`initGantt`,并确保它只初始化甘特图。 2. 创建一个新的`loadData`方法,根据`showGantt`状态调用`getList`或`initGantt`。 3. 修改`expandGantt`和`retractGantt`方法,在切换后分别调用`initGantt`和`getList`。 4. 修改搜索按钮的点击事件和`resetQuery`方法,调用`loadData`。 但是,我们注意到在`mounted`中,我们调用了`this.initData()`,现在我们需要改为`this.loadData()`,因为初始状态`showGantt`为true,所以会调用`getList`。 同时,在`mounted`中,我们还有一句`window.vueInstance = this;`,这个保留。 由于代码较长,我们只给出关键修改: 修改后的方法: ```javascript methods: { // 加载数据(根据当前显示状态) loadData() { if (this.showGantt) { this.getList(); // 表格显示,刷新表格 } else { this.initGantt(); // 甘特图显示,刷新甘特图 } }, // 初始化甘特图 initGantt: function () { // 原来的initData方法的内容,只保留甘特图初始化的部分 gantt.clearAll(); listPlan(this.queryParams).then((res) => { // ... 处理数据,设置gantt配置 // 注意:在初始化之前,先销毁之前的实例(如果存在) if (gantt.isInitialized()) { gantt.destroy(); } // ... 其余初始化代码 gantt.init(this.$refs.gantt); gantt.parse(this.tasks); // ... }); }, expandGantt() { this.showGantt = false; this.$nextTick(() => { this.initGantt(); // 初始化甘特图 }); }, retractGantt() { this.showGantt = true; this.$nextTick(() => { this.getList(); // 刷新表格 }); }, // 搜索按钮的点击事件和重置按钮事件都调用loadData // 重置查询 resetQuery() { this.queryParams = { name: null, respPerson: null }; this.loadData(); // 调用统一的加载方法 }, }, mounted() { window.vueInstance = this; // 初始加载,根据初始状态(showGantt为true)加载表格 this.loadData(); }, ``` 注意:在`initGantt`中,我们添加了销毁实例的代码,因为同一个容器多次初始化可能会出现问题,所以先销毁再初始化。 但是,dhtmlxGantt的`gantt.destroy()`方法会销毁实例并清除事件监听器,这样处理是安全的。 另外,在`initGantt`中,我们使用`gantt.isInitialized()`来判断是否初始化过,但注意这个方法可能不是标准API,我们可以使用一个变量来记录,或者直接调用`gantt.destroy()`(如果未初始化,调用`destroy`也不会报错)。根据文档,多次初始化同一个容器,内部会自动销毁之前的实例,但为了确保在切换时释放资源,我们显式销毁。 如果没有`gantt.isInitialized`方法,我们可以直接调用`gantt.destroy()`,因为即使没有初始化,调用`destroy`也是安全的(根据官方文档)。 修改: ```javascript if (gantt.destroy) { gantt.destroy(); } ``` 或者,更安全的做法是检查gantt实例是否已经初始化到某个容器: ```javascript if (gantt.$container) { gantt.destroy(); } ``` 但是,dhtmlxGantt的API文档中并没有公开的`$container`属性。因此,我们可以使用: ```javascript // 尝试销毁,即使没有初始化也不会报错 try { gantt.destroy(); } catch (e) { // 忽略错误 } ``` 或者,我们使用一个标志位来记录是否初始化过。 这里,我们采用直接销毁的方式,因为即使没有初始化,调用`destroy`也是安全的(根据dhtmlxGantt的文档说明)。 因此,在`initGantt`方法中,我们在初始化之前先调用`gantt.destroy()`。 ```javascript initGantt: function () { // 先销毁之前的实例 if (gantt.destroy) { gantt.destroy(); } // 然后重新初始化 // ... 其余代码 } ``` 但是,注意`gantt.destroy`会清除所有数据和事件,所以我们需要重新初始化配置。 因此,在`initGantt`中,我们仍然需要设置所有的配置项。 这样修改后,切换显示应该可以正常工作了。 由于代码较长,以上是修改思路。如果问题仍然存在,可能需要检查DOM元素是否正常渲染,以及甘特图初始化时容器是否存在。 另外,在`retractGantt`方法中,我们不需要销毁甘特图实例,因为当容器被销毁(v-if)时,甘特图实例也应该被销毁,但我们为了确保释放内存,在每次初始化甘特图之前都会销毁之前的实例,所以在收起甘特图时,我们不需要额外销毁,因为下次展开时会先销毁再初始化。 最后,我们还需要注意,在表格显示时,甘特图的容器是不存在的,所以我们在`initGantt`中,当容器不存在时,不应该初始化。我们可以添加一个判断: ```javascript initGantt: function () { if (!this.$refs.gantt) { return; } // 销毁之前的实例 if (gantt.destroy) { gantt.destroy(); } // ... 其余初始化代码 } ``` 但是,在`$nextTick`中调用,应该能确保DOM更新完成,容器存在。 以上是解决切换问题的方案。由于无法实际运行,可能需要根据实际情况调整。 如果问题仍然存在,请检查控制台是否有错误信息,并根据错误信息调试。 由于代码量很大,这里只提供修改思路,具体代码实现需要根据项目调整。</think>根据您提供的代码,我分析出甘特图切换显示的问题可能由以下几个原因导致: ### 问题分析: 1. **状态变量逻辑问题**:`showGantt` 状态控制逻辑可能存在问题 2. **DOM 更新时机问题**:甘特图初始化时容器可能尚未渲染完成 3. **甘特图重复初始化**:切换时未销毁旧实例导致冲突 4. **布局配置问题**:甘特图容器尺寸计算可能不正确 ### 解决方案: #### 1. 修复状态切换逻辑 在 `expandGantt` 和 `retractGantt` 方法中,确保状态切换后正确调用初始化: ```javascript expandGantt() { this.showGantt = false; this.$nextTick(() => { this.initGantt(); // 确保DOM更新后初始化 }); }, retractGantt() { this.showGantt = true; this.destroyGantt(); // 添加销毁甘特图方法 }, ``` #### 2. 添加甘特图销毁方法 在 methods 中添加: ```javascript destroyGantt() { if (gantt.isInitialized()) { gantt.destroy(); } this.$refs.gantt.innerHTML = ""; // 清空容器 }, ``` #### 3. 重构初始化方法 分离甘特图初始化逻辑: ```javascript initGantt() { this.destroyGantt(); // 先销毁旧实例 // 设置容器尺寸 this.$refs.gantt.style.height = `${window.innerHeight - 150}px`; gantt.init(this.$refs.gantt); // 配置甘特图(保留原有配置) gantt.config.autosize = true; gantt.config.readonly = true; // ... 其他配置保持不变 // 加载数据 listPlan(this.queryParams).then(res => { this.tasks.data = res.data.map(item => ({ /* 转换逻辑 */ })); gantt.parse(this.tasks); }); }, ``` #### 4. 修复布局问题 在 CSS 中添加: ```css .gantt-container { height: calc(100vh - 150px) !important; width: 100%; } ``` #### 5. 修改 mounted 钩子 确保初始加载正确: ```javascript mounted() { window.vueInstance = this; if (!this.showGantt) { this.$nextTick(this.initGantt); } else { this.getList(); } } ``` ### 完整修复代码: ```javascript // 在 methods 中添加 destroyGantt() { if (gantt.isInitialized()) { gantt.detachAllEvents(); gantt.clearAll(); gantt.destroy(); } if (this.$refs.gantt) { this.$refs.gantt.innerHTML = ""; } }, initGantt() { this.destroyGantt(); // 确保容器存在 if (!this.$refs.gantt) return; // 设置容器尺寸 this.$refs.gantt.style.height = `${window.innerHeight - 150}px`; // 初始化甘特图 gantt.init(this.$refs.gantt); // 配置项(保持原有配置) gantt.config.autosize = true; gantt.config.readonly = true; gantt.config.show_grid = true; // ... 其他配置保持不变 // 加载数据 listPlan(this.queryParams).then(res => { this.tasks.data = res.data.map(item => ({ id: item.uid, // ... 其他属性映射 })); gantt.parse(this.tasks); }); }, ``` ### 关键修改点说明: 1. **状态切换时销毁旧实例** - 防止内存泄漏和冲突 2. **使用 $nextTick 确保 DOM 更新** - 解决渲染时机问题 3. **显式设置容器尺寸** - 解决高度计算问题 4. **分离初始化和数据加载** - 提高代码可维护性 5. **添加甘特图销毁逻辑** - 清理事件和 DOM 元素 ### 其他建议: 1. 添加加载状态提示 2. 增加窗口大小变化时的重绘处理 3. 使用 `resize-observer-polyfill` 监听容器变化 ```javascript mounted() { this.resizeObserver = new ResizeObserver(() => { if (!this.showGantt && gantt.isInitialized()) { gantt.render(); } }); this.resizeObserver.observe(this.$refs.gantt); }, beforeDestroy() { this.resizeObserver.disconnect(); this.destroyGantt(); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值