javascript 用Vue 3和Vite构建可嵌入的单个JS文件Widget

b91juud3  于 2022-12-28  发布在  Java
关注(0)|答案(1)|浏览(411)

bounty将于明天到期。此问题的答案可获得+100声望奖励。Lowtrux正在寻找规范答案:我希望这个答案能解决主要的问题,即如何使用Vue 3在Vite中生成一个JS文件,该文件可以使用HTML属性来动态更改Vue小部件内部的参数,同时我不能使用Web组件和/或Iframe来加载内容。

我需要制作一个生产交付件(一个小部件,基本上是一个客户服务聊天)以单个.JS文件的形式,用户可以在其中传递一系列HTML数据属性(基本上是我的Vue实现中传递给Widget组件的所有 prop ),以便自定义API端点、WebSocket URL图像路径等。因此,问题是如何从我的Vue实现中生成此类小部件:
我正在使用Vue 3、Vite和Vuex管理WS通信:

<!-- Widget -->
<script src="https://cdn.xyz.xyz/widget.min.js" 
   data-icon="path_to_icon.jpg"
   data-title="Brand support"
   data-jwt-url="jwt token url"
   data-rubiko-url="another url"
   data-company-websocket-url="websocket url"
   data-theme="default"
   data-brand-id="brand-id">
</script>
<!-- end of Widget -->

首先,我创建了Widget组件,它是哑的--它只显示我从外部提供给它的内容。基本上,一系列的Props在最后将用作来自.JS文件的html属性。有一个传递JWT以验证API的完整过程,但这并不重要:

<template>
  <div class="widget-container" v-show="isShow">
    <div class="widget-content">
      <section class="widget-header"><h1>Hello There !!</h1></section>
      <div class="widget-body">
        <div class="widget-body-container">
          <h3>
            👋 Hi there {{ endUserName }} {{ endUserLastName }}! Welcome .
          </h3>
          <h4>
            You are <span v-if="isVip">a Vip user</span>
            <span v-else>not a VIP user</span>
          </h4>
          <h4>The WS connection ID is {{ connectionId }}</h4>
          <section class="messageList" v-for="category in categories">
            <p>Category ID is: {{ category.id }}</p>
            <p>Category Name is: {{ category.name }}</p>
          </section>
          <section class="messageList" v-for="category in categories">
            <p>Category ID is: {{ category.id }}</p>
            <p>Category Name is: {{ category.name }}</p>
          </section>
        </div>
      </div>
      <section class="widget-input">
        <form @submit.prevent="sendMessage" class="chat-form relative mt-6">
          <textarea
            name="message"
            id=""
            placeholder="Add your message here.."
            aria-label="Send a message"
            tabindex="0"
          ></textarea>
          <div class="widget-input-buttons">
            <button class="widget-button" type="submit" value="Send">
              <i size="16"
                ><svg
                  width="16"
                  height="16"
                  fill="none"
                  xmlns="http://www.w3.org/2000/svg"
                >
                  <circle
                    cx="8"
                    cy="8"
                    r="6.725"
                    stroke="#757575"
                    stroke-width="1.3"
                  ></circle>
                  <path
                    fill-rule="evenodd"
                    clip-rule="evenodd"
                    d="M5.818 7.534a1.1 1.1 0 100-2.2 1.1 1.1 0 000 2.2zm4.364 0a1.1 1.1 0 100-2.2 1.1 1.1 0 000 2.2z"
                    fill="#757575"
                  ></path>
                  <path
                    d="M10 10c-.44.604-1.172 1-2 1-.828 0-1.56-.396-2-1"
                    stroke="#757575"
                    stroke-width="1.3"
                    stroke-linecap="round"
                  ></path>
                </svg>
              </i>
            </button>
          </div>
        </form>
      </section>
    </div>
  </div>
</template>

<script setup>
const props = defineProps({
  categories: Array,
  connectionId: String,
  endUserName: String,
  endUserLastName: String,
  endUserEmail: String,
  endUserSub: String,
  isShow: Boolean,
  isVip: Boolean,
});
</script>

然后我有一个负责小部件业务逻辑的容器。为了这个POC的目的,它将连接到一个WebSocket(我有一个Vuex商店,负责与WS的通信和错误处理),检索来自WS的连接ID,然后还将从API检索一些信息作为小部件的内容。

<template>
  <Widget
    :connectionId="retrieveConnectionId"
    :endUserName="name"
    :endUserLastName="lastName"
    :endUserEmail="email"
    :endUserSub="sub"
    :isVip="isVip"
    :categories="categories"
    :isShow="isShow"
  />
  <WidgetTrigger @click="isShow = !isShow" :isOpened="!isShow" />
</template>

<script>
import { ref, onMounted } from "vue";
import { Buffer } from "buffer";
import { useActions } from "vuex-composition-helpers";
import { useGetters } from "vuex-composition-helpers";
import Widget from "@/components/Widget.vue";
import WidgetTrigger from "@/components/WidgetTrigger.vue";
export default {
  components: {
    Widget,
    WidgetTrigger,
  },
  setup() {
    const categories = ref([]);
    const email = ref("");
    const error = ref(null);
    const isShow = ref(false);
    const isVip = ref();
    const lastName = ref("");
    const license = ref("");
    const name = ref("");
    const token = ref("");
    const sub = ref("");
    const { callWebsocket } = useActions({
      callWebsocket: "websocket/processWebsocket",
    });
    const { retrieveConnectionId } = useGetters({
      retrieveConnectionId: "websocket/getConnectionId",
    });
    const retrieveSignedJWT = async () => {
      fetch("http://xyz")
        .then(async (response) => {
          const data = await response.text();
          // check for error response
          if (!response.ok) {
            // get error message from body or default to response statusText
            const error = (data && data.message) || response.statusText;
            return Promise.reject(error);
          }
          let jwt = data;
          token.value = data;
          decodeToken(jwt);
          retrieveCategories();
        })
        .catch((error) => {
          error.value = error;
          console.error("There was an error!", error);
        });
    };
    const retrieveCategories = async () => {
      fetch(
        " http://xyz/categories",
        {
          method: "GET",
          headers: {
            "Content-Type": "application/x-www-form-urlencoded",
            Authorization: `Bearer ${token.value}`,
          },
        }
      )
        .then(async (response) => {
          const data = await response.json();
          // check for error response
          if (!response.ok) {
            // get error message from body or default to response statusText
            const error = (data && data.message) || response.statusText;
            return Promise.reject(error);
          }
          categories.value = data;
        })
        .catch((error) => {
          error.value = error;
          console.error("There was an error!", error);
        });
    };
    const decodeToken = async (jwt) => {
      let base64Url = jwt.split(".")[1];
      let base64 = base64Url.replace("-", "+").replace("_", "/");
      let decodedData = JSON.parse(
        Buffer.from(base64, "base64").toString("binary")
      );
      sub.value = decodedData.sub;
      name.value = decodedData.name;
      lastName.value = decodedData.last_name;
      email.value = decodedData.email;
      isVip.value = decodedData.is_vip;
    };
    onMounted(async () => {
      await retrieveSignedJWT();
      await callWebsocket();
    });
    return {
      categories,
      callWebsocket,
      decodeToken,
      email,
      error,
      retrieveConnectionId,
      retrieveSignedJWT,
      retrieveCategories,
      isShow,
      isVip,
      lastName,
      license,
      name,
      retrieveConnectionId,
      sub,
      token,
    };
  },
};
</script>

到目前为止,一切都按预期工作,但我找不到反馈,说明哪种方法是正确的,可以将单个.JS文件以小部件的形式提供给小部件,在小部件中我可以使用HTML数据属性,以便小部件可以将它们用作 prop 。

bvjxkvbb

bvjxkvbb1#

您可以使用Lit Element
使用的示例是Googlemodel viewer
它使用rollup(查看roolup.config.js)来创建一个js,您可以在html中添加自定义标记,它没有依赖项,因此可以与任何框架一起使用
例如:

<script type="module" src="generated.js"></script>
<yourcustomtag withstuff="1">

我不确定你是否可以用Vue写一个lit元素,但是你仍然可以用rollup来写你的小部件。我不知道Vue,也不知道它是如何独立的(lit元素使用阴影dom,所以它被设计成独立的)

相关问题