Vue全局指令改变表单样式的bug

事情是这样的,我接到的需求是要做一个表单的动态效果,预期效果如图:

img

也就是更改了之后会把对应表单的标签变成红色,现在出现的问题是,当我操作其他数据的时候,即使我没有做更改他也会报错误高亮

img

img

这是全局指令的相关代码

export default {
  bind(el, binding, vnode) {
    resetInitialState(vnode); // 重置初始状态
  },
  inserted(el, binding, vnode) {
    vnode.context.$nextTick(() => {
      initializeState(el, binding, vnode);
    });
  },
  update(el, binding, vnode, oldVnode) {
    if (binding.value !== binding.oldValue) {
      vnode.context.$nextTick(() => {
        let id = vnode.context.form.id
        let newVal = binding.value
        let oldValue = binding.oldValue

        let edited = false
        if (id && newVal !== undefined && oldValue !== undefined) {
          if (newVal !== oldValue) {
            edited = true
          }
        }
        updateLabelStyle(el, edited);
        initializeState(el, binding, vnode);
      });
    }
  },
  unbind(el, vnode) {
    resetInitialValues(vnode); // 重置初始值
    if (el._observer) {
      el._observer.disconnect();
    }
  },
};

function updateLabelStyle(el, edited) {
  const labelElement = el.querySelector('label');
  if (labelElement) {
    edited ? labelElement.classList.add('edited-label') : labelElement.classList.remove('edited-label');
  }
}

function resetInitialState(vnode) {
  vnode.context.initialValues = {};
}

function resetInitialValues(vnode) {
  const instanceUid = vnode.context._uid;
  if (vnode.context.initialValues && vnode.context.initialValues[instanceUid]) {
    delete vnode.context.initialValues[instanceUid];
  }
}

function initializeState(el, binding, vnode) {
  const propName = binding.expression;
  const instanceUid = vnode.context._uid;
  let initialValues = vnode.context.initialValues;
  if (!initialValues) {
    vnode.context.initialValues = initialValues = {};
  }

  if (!initialValues[instanceUid]) {
    initialValues[instanceUid] = {};
  }

  const isNewForm = binding.modifiers.new;
  if (isNewForm) {
    initialValues[instanceUid][propName] = null;
  } else if (initialValues[instanceUid][propName] === undefined) {
    initialValues[instanceUid][propName] = JSON.stringify(binding.value);
  }

  const updateLabelStyle = (edited) => {
    const labelElement = el.querySelector('label');
    if (labelElement) {
      edited ? labelElement.classList.add('edited-label') : labelElement.classList.remove('edited-label');
    }
  };

  const updateLabelColor = (newVal) => {
    let newValue = newVal === undefined || newVal === '' ? '' : JSON.stringify(newVal);
    updateLabelStyle(newValue !== initialValues[instanceUid][propName]);
  };

  updateLabelColor(binding.value);

  vnode.context.$watch(propName, (newVal) => {
    vnode.context.$nextTick(() => {
      updateLabelColor(newVal);
    });
  }, { deep: true });

  const updateLabelCallback = () => updateLabelColor(vnode.context[propName]);

  const classList = ['el-select', 'el-radio-group', 'el-input-number', 'el-checkbox-group', 'el-date-picker'];
  for (const className of classList) {
    if (el.classList.contains(className)) {
      el._observer = observeElement(el.querySelector(`.${className.split('-').slice(-1)[0]}`), updateLabelCallback);
      break;
    }
  }
}

function observeElement(element, callback) {
  if (element) {
    const observer = new MutationObserver(callback);
    observer.observe(element, {
      attributes: true,
      childList: true,
      subtree: true,
    });
    return observer;
  }
}

最好是能远程帮看一下

问题

先确认下问题,
也就是原本要求是:
A标签对应内容修改的话,那么A标签变红色
B/C/D其余标签没有内容修改那么不变化

现在的问题是:
A标签修改内容后,A标签变色
但是没有修改的B标签也变色了?

这个你想的复杂了 添加一个事件就解决了

initializeState这个函数换成下面代码试试


function (el, binding, vnode) {
  const propName = binding.arg || "value";
  const instanceUid = vnode.context.$options._uid;

  let initialValues = vnode.context._initialValues;
  if (!initialValues) {
    vnode.context._initialValues = initialValues = {};
  }

  if (binding.modifiers.new) {
    initialValues[propName] = null;
  } else {
    initialValues[propName] = JSON.stringify(binding.value);
  }

  function updateLabelStyle(edited) {
    if (edited) {
      el.classList.add("edited-label");
    } else {
      el.classList.remove("edited-label");
    }
  }

  function updateLabelColor(newVal) {
    const initialValue = initialValues[propName];
    if (JSON.stringify(newVal) !== initialValue) {
      updateLabelStyle(true);
    } else {
      updateLabelStyle(false);
    }
  }

  updateLabelColor(binding.value);

  vnode.context.$watch(
    () => propName,
    () => {
      vnode.context.$nextTick(() => {
        updateLabelColor(binding.value);
      });
    }
  );

  const classList = ["form-control", "custom-select", "checkbox", "radio"];
  classList.forEach((className) => {
    if (el.classList.contains(className)) {
      const observer = observeElement(el, updateLabelColor);
    }
  });
}

function observeElement(element, callback) {
  const observerConfig = { attributes: true, childList: true, subtree: true };
  const observer = new MutationObserver(callback);
  observer.observe(element, observerConfig);
  return observer;
}
}

用vnode.context.$watch监听试一下呢

是不是null与空字符串的问题

【相关推荐】




如果你已经解决了该问题, 非常希望你能够分享一下解决方案, 写成博客, 将相关链接放在评论区, 以帮助更多的人 ^-^

加一个事件就行,

你的指令怎么使用的, 我看一看


刚才的完整代码

import { isEqual } from "lodash"

const isEmpty = val => {
  return val === "" || val === undefined || val === null
}

const isChanged = (a, b) => {
  if (isEmpty(a) && isEmpty(b)) {
    return false
  }

  // 考虑输入框 数组变成 字符串
  // 0 '0'
  a = typeof a === "number" ? String(a) : a
  b = typeof b === "number" ? String(b) : b

  // TODO: 对象,select, checkbox 点击了之后之后数组顺序会变化,这里可能要考虑是否排序等
  // ...
  return !isEqual(a, b)
}

export default {
  bind(el, binding, vnode) {
    el.initialValue = binding.value
  },
  update(el, binding, vnode, oldVnode) {
    const changed = isChanged(el.initialValue, binding.value)
    const labelElement = el.querySelector("label")

    // 添加一个状态,状态改变的时候再去设置
    const statusChange = isChanged(el.changed, changed)
    el.changed = changed

    if (labelElement && statusChange) {
      changed ? labelElement.classList.add("edited-label") : labelElement.classList.remove("edited-label")
    }
  }
}

你可以试着用v-model指令和watch属性来监听你表单值的变化,卡尼下能不能对应的更新样式。


如果以上回答对您有所帮助,点击一下采纳该答案~谢谢

引用Chatgpt回答

在Vue中,可以使用全局指令来改变表单样式。全局指令是可以在整个应用程序中重复使用的自定义指令。以下是一个示例,演示如何使用全局指令来改变表单样式:

  1. 首先,在Vue实例中定义一个全局指令。在这个例子中,我们将指令命名为form-style
Vue.directive('form-style', {
  bind: function (el, binding) {
    // 在绑定元素时触发
    el.style.border = '1px solid blue';
    el.style.padding = '10px';
    // 也可以根据传入的参数来动态改变样式
    if (binding.value === 'red') {
      el.style.border = '1px solid red';
      el.style.padding = '5px';
    }
  }
});
  1. 在模板中使用该指令。在这个例子中,我们将指令应用到一个输入框元素上:
<input v-form-style type="text" value="Sample Input">

这样,指令会自动应用到该输入框元素上,并改变其样式。

你也可以通过传递参数来动态改变样式。例如,以下代码将应用一个红色的边框和更小的内边距:

<input v-form-style="'red'" type="text" value="Sample Input">

请注意,该指令在所有使用v-form-style的输入框上都会生效,因为它是一个全局指令。

这只是一个简单的示例,你可以根据自己的需求和具体的样式要求来自定义全局指令。希望这能帮到你!如果你有任何其他问题,请随时提问。