不知从何时开始,IT从业者已经习惯了*“CV大法”能“CV”的就绝不动一动脑子*。虽然我也经常*“CV”*。这究竟是
人性的扭曲、还是道德的沦丧?
前言
回归正题,本期主要分享一下,如何制作sv-print插件。
通过简单的 hook 机制,vue + antd 写一个插件。
干货满满! 先看效果:
如上图,这两个使用的是 antd 的组件。
插件说明:https://ccsimple.github.io/sv-print-docs/plugin/intro.html
源码获取方式 见文末
开干
创建项目就不多说了吧, 根据CLI自行选择处理这里就以 vue3 为例:
pnpm create vite
安装依赖
pnpm i sv-print @sv-print/vue3 ant-design-vue
新增 plugin 目录,创建需要的文件:
index.ts field.vue
结构如下:
处理hook
index.ts 导出 插件对象:
import type { PluginOptions, HookOptions } from "sv-print";
export default function (config?: any): PluginOptions {
  let configs = {
    ...{ name: "插件默认值之类的" },
    ...config,
  };
  const hook = initHook(configs);
  return {
    name: "plugin-demo",
    description: "vue3,antd-vue 重写字段名参数",
    hooks: [hook],
    leastHiprintVersion: "0.1.0",
  };
}
初始化initHook:
const initHook = (configs: any): HookOptions => {
  return {
    hook: "init", // 关键: hook key
    name: "xx",
    description: "xx",
    priority: 1,
    run: (opts) => initRun(opts, configs),
  };
};
处理 init hook 核心逻辑:
const initRun = (opts: any, configs: any) => {
  console.log(opts);
  console.log(configs);
  const { Config } = opts;
  // 这里返回一个 参数对象
  const filedOption = fieldOptionFun(configs);
  // 注册/重写  参数
  Config.registerItems([filedOption()]);
};
参数具体执行, 我们引入 vue 以及 antd 用到的组件
import { createApp } from "vue";
import fieldVueApp from "./field.vue";
import { AutoComplete, Modal } from "ant-design-vue";
const fieldOptionFun = (configs: any) => {
  const { fieldList = [], dialog = false } = configs;
  return function () {
    function t() {
      this.name = "field";
    }
    return (
      (t.prototype.createTarget = function (t, i, e) {
        //  t: 元素对象,i: 元素options, e: 元素printElementType
        const fileds = t.getFields();
        this.isSelect = fileds && fileds.length > 0;
        const el = globalThis.$(
          `<div class="hiprint-option-item hiprint-option-item-row">
          <div class="hiprint-option-item-label">字段名</div>
          <div class="hiprint-option-item-field">
            <div id="fieldOption">
            </div>
          </div>
          `
        );
        this.target = el;
        // 创建挂载 vue 组件, 传递参数,监听回调
        this.vueApp = createApp(fieldVueApp, {
          onChange: (value) => {
            console.log("onChange", value);
            t && t.submitOption();
          },
          options: fileds || fieldList,
          dialog: dialog,
        });
        this.vueApp.use(Modal);
        this.vueApp.use(AutoComplete);
        setTimeout(() => {
          // 挂载,获得组件实例, 调用内部方法
          this.vueAppIns = this.vueApp.mount("#fieldOption");
        }, 0);
        return this.target;
      }),
      (t.prototype.getValue = function () {
        return this.vueAppIns && this.vueAppIns.getValue();
      }),
      (t.prototype.setValue = function (t: any) {
        setTimeout(() => {
          if (this.vueAppIns) {
            this.vueAppIns.setValue(t);
          }
        }, 0);
      }),
      (t.prototype.destroy = function () {
        if (this.vueApp) {
          this.vueApp.unmount();
        }
        this.target.remove();
      }),
      t
    );
  };
};
上面重写,自定义参数的 核心的几个方法:
- createTarget 创建参数 DOM
 - getValue 获取值
 - setValue 设置值
 - destroy 销毁参数 DOM
 
注意如果你不清楚 重写/自定义参数: 请先查看文章:参数篇
处理vue组件
首先定义传参,因为肯定需要传递参数,回调之类。
const props = defineProps({
  dialog: { // 是否弹窗
    type: Boolean,
    default: false,
  },
  options: { // 字段名列表
    type: String,
    default: [],
  },
  onChange: { // 字段名修改回调
    type: Function,
    default: () => {},
  },
});
然后给外部提供 赋值、获取值的方法。 包含回调处理。
const onChange = (value: string) => {
  if (props.dialog) {
    field.value = value;
    open.value = false;
  }
  props.onChange(field.value);
};
const setValue = (value: string) => {
  field.value = value;
};
const getValue = () => {
  return field.value;
};
defineExpose({ setValue, getValue });
处理 UI 部分
<template v-if="props.dialog">
    <a-auto-complete
      v-model:value="field"
      allowClear
      style="width: 100%"
      placeholder="请输入字段名"
      @click="open = true"
    />
    <a-modal v-model:open="open" title="请选择字段名" :footer="null">
      <div class="btn-group">
        <template v-for="(item, idx) in optionsList" :key="idx">
          <a-button @click="onChange(item.value)">{{ item.text }}({{ item.value }})</a-button>
        </template>
      </div>
    </a-modal>
  </template>
  <template v-else>
    <a-auto-complete
      v-model:value="field"
      :options="optionsList"
      allowClear
      style="width: 100%"
      placeholder="请输入字段名"
      @change="onChange"
      :filter-option="filterOption"
    >
      <template #option="{ value: val, text }">
        <span style="font-weight: bold">{{ text }}({{ val }})</span>
      </template></a-auto-complete
    >
  </template>
到此,咱们简单的一个 init hook插件就制作完成了。在sv-print init 的时候,就会执行相应的逻辑。
插件支持多个 hook。
总结
插件代码不是很复杂,什么时候 createApp 创建组件,何时去挂载执行 mount 获取 vue 组件实例,需要认真探索。
sv-print核心参数执行的流程、赋值、取值的逻辑需要理解清楚。
在使用createApp创建组件时,怎么传参,挂载,调用组件方法。
问题并没有你想象的那么难处理,重要的是找对方法。
如果需要源码,公众号回复:plugin-demo
如果看到这里,你还是疑问,想要一对一技术指导,欢迎私信联系我。
记得点个赞咯~
评论区也可交流,想要不简说说什么技术,疑难解答。
下期再见👋🏻