# sendMixin & XRequest

# Feature Description

A mixin for send operations that provides unified request state management and interruption control, supporting the following features:

  • Supports two request methods: SSE (Server-Sent Events) and Fetch
  • Provides complete request lifecycle management
  • Supports request interruption control
  • Automatically handles request status
  • Error handling mechanism
  • Supports custom data transformers
  • Supports a utility function version for non-component scenarios
  • Integrates the XRequest class to provide powerful request handling capabilities

# Import and Usage

import { sendMixin } from 'vue-element-ui-x';

export default {
  mixins: [sendMixin],
  // ...
};

Note

The import method in the following examples is to resolve an error during the documentation site's packaging. Under normal circumstances, please import it in the standard way.

# Usage Examples

# Basic Usage of sendMixin

Basic Flow

User Action → handleSend/handleAbort/handleFinish → Update loading state → Call user callback

<template>
  <div class="container">
    <div class="btn-list">
      <el-button
        :disabled="loading"
        type="primary"
        @click="handleSend"
      >
        {{ loading ? 'Loading...' : 'Simulate Request' }}
      </el-button>
      <el-button
        :disabled="!loading"
        type="primary"
        @click="handleFinish"
      >
        Finish Request
      </el-button>
      <el-button
        :disabled="!loading"
        type="danger"
        @click="handleAbort"
      >
        Abort Request
      </el-button>
    </div>
    <div
      v-if="basicResult"
      class="result"
    >
      Result: {{ basicResult }}
    </div>
  </div>
</template>
<script>
  let sendMixin = {};
  try {
    if (typeof window !== 'undefined' && window['vue-element-ui-x']) {
      sendMixin = window['vue-element-ui-x'].customMixins.sendMixin;
    } else if (typeof require !== 'undefined') {
      sendMixin = require('vue-element-ui-x').customMixins.sendMixin;
    }
  } catch (e) {
    sendMixin = {
      data() {
        return { loading: false };
      },
      methods: {
        initSend() {},
        handleSend() {},
        handleFinish() {},
        handleAbort() {},
      },
    };
  }
  export default {
    name: 'SendBasicDemo',
    mixins: [sendMixin],
    data() {
      return {
        basicResult: '',
      };
    },
    mounted() {
      // Initialize send configuration
      this.initSend({
        sendHandler: this.startFn,
        finishHandler: this.finishBasicRequest,
        abortHandler: this.abortBasicRequest,
        onAbort: this.onBasicAbort,
      });
    },
    methods: {
      async startFn() {
        // Perform an asynchronous operation here, such as making a request
        console.log('Starting simulated request');
        this.basicResult = '';
      },
      finishBasicRequest() {
        this.basicResult =
          'Request completed successfully! Time: ' + new Date().toLocaleTimeString();
        console.log('Basic request processing finished');
      },
      abortBasicRequest() {
        this.basicResult = 'Request was aborted';
        console.log('Basic request aborted', this.loading);
      },
      onBasicAbort() {
        console.log('Basic request abort callback');
      },
    },
  };
</script>

<style>
  .container {
    display: flex;
    flex-direction: column;
    gap: 12px;
  }
  .btn-list {
    display: flex;
    gap: 12px;
  }
  .btn-list .el-button {
    width: fit-content;
  }
  .result {
    color: #67c23a;
    padding: 8px;
    background-color: #f0f9ff;
    border-radius: 4px;
  }
</style>
Expand Copy

With state control, we can easily customize the loading state of some buttons.

Click any button to start the operation. The button will automatically switch to a loading state and end after a few seconds:

Current Status: voice | Active Button: Idle

<template>
  <div class="custom-btn-container">
    <div class="btn-description">
      <p>
        Click any button to start the operation. The button will automatically switch to a loading
        state and end after a few seconds:
      </p>
    </div>

    <div class="btn-list">
      <!-- Voice button -->
      <el-button
        v-if="!voiceLoading"
        style="background-color: #9145c8; border-color: #9145c8; color: white;"
        circle
        size="mini"
        @click="startVoice"
      >
        <i class="el-icon-microphone"></i>
      </el-button>

      <el-button
        v-if="voiceLoading"
        style="background-color: #9145c8; border-color: #9145c8; color: white;"
        circle
        size="mini"
        @click="stopVoice"
      >
        <i class="el-icon-loading"></i>
      </el-button>

      <!-- Send button -->
      <el-button
        v-if="!senderLoading"
        style="background-color: transparent; border-color: #c2306a; color: #c2306a;"
        round
        size="mini"
        @click="startSender"
      >
        <i class="el-icon-s-promotion"></i>
        <span style="margin-left: 5px;">Send</span>
      </el-button>

      <el-button
        v-if="senderLoading"
        style="background-color: transparent; border-color: #c2306a; color: #c2306a;"
        round
        size="mini"
        @click="stopSender"
      >
        <i class="el-icon-refresh el-icon-loading"></i>
        <span style="margin-left: 5px;">Sending</span>
      </el-button>

      <!-- Play button -->
      <el-button
        v-if="!readLoading"
        size="mini"
        type="success"
        style="background-color: #ff7f7f; border-color: #ff7f7f;"
        @click="startRead"
      >
        <i
          class="el-icon-video-play"
          style="font-size: 20px; color: #fff;"
        ></i>
        <span style="margin-left: 5px;">Play</span>
      </el-button>

      <el-button
        v-if="readLoading"
        size="mini"
        type="success"
        style="background-color: #ff7f7f; border-color: #ff7f7f;"
        @click="stopRead"
      >
        <i
          class="el-icon-video-pause"
          style="font-size: 20px; color: #fff;"
        ></i>
        <span style="margin-left: 5px;">Playing</span>
      </el-button>

      <!-- Record button -->
      <el-button
        v-if="!recordLoading"
        size="mini"
        circle
        style="background-color: #fff884; border-color: #fff884; color: #104674;"
        @click="startRecord"
      >
        <i class="el-icon-camera"></i>
      </el-button>

      <el-button
        v-if="recordLoading"
        size="mini"
        circle
        style="background-color: #fff884; border-color: #fff884; color: #104674;"
        @click="stopRecord"
      >
        <i class="el-icon-aim el-icon-loading"></i>
      </el-button>
    </div>

    <div class="status-info">
      <p>
        Current Status: {{ type }} | Active Button:
        <span
          v-if="voiceLoading"
          class="status-active"
        >
          Recording Voice
        </span>
        <span
          v-if="senderLoading"
          class="status-active"
        >
          Sending Text
        </span>
        <span
          v-if="readLoading"
          class="status-active"
        >
          Playing Video
        </span>
        <span
          v-if="recordLoading"
          class="status-active"
        >
          Recording Video
        </span>
        <span
          v-if="!voiceLoading && !senderLoading && !readLoading && !recordLoading"
          class="status-idle"
        >
          Idle
        </span>
      </p>
    </div>
  </div>
</template>
<script>
  export default {
    name: 'CustomButtonDemo',
    data() {
      return {
        type: 'voice',
        // Multiple independent loading states
        voiceLoading: false,
        senderLoading: false,
        readLoading: false,
        recordLoading: false,

        // Store timer references
        timers: {},
      };
    },
    methods: {
      // Voice button operations
      startVoice() {
        this.type = 'voice';
        this.voiceLoading = true;
        this.$message.success('Custom voice button, starting recording!');

        // Simulate auto-finish after 3 seconds
        this.timers.voice = setTimeout(() => {
          this.stopVoice();
        }, 3000);
      },
      stopVoice() {
        this.voiceLoading = false;
        this.$message.info('Custom voice button, finishing recording!');
        if (this.timers.voice) {
          clearTimeout(this.timers.voice);
          delete this.timers.voice;
        }
      },

      // Send button operations
      startSender() {
        this.type = 'sender';
        this.senderLoading = true;
        this.$message.success('Custom send button, starting to send text!');

        // Simulate auto-finish after 2 seconds
        this.timers.sender = setTimeout(() => {
          this.stopSender();
        }, 2000);
      },
      stopSender() {
        this.senderLoading = false;
        this.$message.info('Custom send button, finished sending!');
        if (this.timers.sender) {
          clearTimeout(this.timers.sender);
          delete this.timers.sender;
        }
      },

      // Play button operations
      startRead() {
        this.type = 'read';
        this.readLoading = true;
        this.$message.success('Custom play, starting to play!');

        // Simulate auto-finish after 4 seconds
        this.timers.read = setTimeout(() => {
          this.stopRead();
        }, 4000);
      },
      stopRead() {
        this.readLoading = false;
        this.$message.info('Custom play button, finished playing!');
        if (this.timers.read) {
          clearTimeout(this.timers.read);
          delete this.timers.read;
        }
      },

      // Record button operations
      startRecord() {
        this.type = 'record';
        this.recordLoading = true;
        this.$message.success('Custom record, starting to record!');

        // Simulate auto-finish after 5 seconds
        this.timers.record = setTimeout(() => {
          this.stopRecord();
        }, 5000);
      },
      stopRecord() {
        this.recordLoading = false;
        this.$message.info('Custom record button, finished recording!');
        if (this.timers.record) {
          clearTimeout(this.timers.record);
          delete this.timers.record;
        }
      },
    },

    beforeDestroy() {
      // Clear all timers
      Object.values(this.timers).forEach(timer => {
        if (timer) clearTimeout(timer);
      });
    },
  };
</script>

<style>
  .custom-btn-container {
    display: flex;
    flex-direction: column;
    gap: 20px;
    padding: 20px;
    border: 1px solid #ebeef5;
    border-radius: 8px;
    background-color: #fafafa;
  }

  .btn-description {
    text-align: center;
  }

  .btn-description p {
    margin: 0;
    color: #606266;
    font-size: 14px;
  }

  .btn-list {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 20px;
    flex-wrap: wrap;
  }

  .btn-list .el-button {
    min-width: fit-content;
    transition: all 0.3s ease;
  }

  .btn-list .el-button:hover {
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  }

  .status-info {
    text-align: center;
    padding-top: 10px;
    border-top: 1px solid #e4e7ed;
  }

  .status-info p {
    margin: 0;
    color: #909399;
    font-size: 13px;
  }

  .status-active {
    color: #409eff;
    font-weight: bold;
  }

  .status-idle {
    color: #67c23a;
  }

  /* Icon animation effect */
  .el-icon-loading {
    animation: rotating 2s linear infinite;
  }

  @keyframes rotating {
    0% {
      transform: rotate(0deg);
    }
    100% {
      transform: rotate(360deg);
    }
  }

  /* Responsive Design */
  @media (max-width: 768px) {
    .btn-list {
      gap: 15px;
    }

    .btn-list .el-button {
      min-width: 80px;
    }
  }
</style>
Expand Copy

The above examples control the front-end loading state. Of course, request state control is also essential. Next, let's introduce the basic usage of the utility class XRequest.

# XRequest Basic Usage (sse)

The SSE request method supported by the XRequest utility class

Received Messages:

Disconnected
No messages yet
<template>
  <div class="xrequest-container">
    <div class="description">
      <p>The SSE request method supported by the XRequest utility class</p>
    </div>

    <div class="btn-list">
      <el-button
        type="primary"
        :disabled="loading"
        @click="startSSERequest"
      >
        {{ loading ? 'Requesting...' : 'Initiate SSE Request' }}
      </el-button>

      <el-button
        type="danger"
        :disabled="!loading"
        @click="abortRequest"
      >
        Cancel Request
      </el-button>

      <el-button
        type="info"
        @click="clearResult"
      >
        Clear Result
      </el-button>
    </div>

    <div class="result-container">
      <div class="result-header">
        <h4>Received Messages:</h4>
        <el-tag
          v-if="loading"
          type="success"
          size="mini"
        >
          Connected
        </el-tag>
        <el-tag
          v-else
          type="info"
          size="mini"
        >
          Disconnected
        </el-tag>
      </div>
      <div class="result-content">
        <div
          v-if="!str"
          class="empty-state"
        >
          No messages yet
        </div>
        <pre
          v-else
          class="message-display"
        >
{{ str }}</pre
        >
      </div>
    </div>
  </div>
</template>
<script>
  export default {
    name: 'XRequestDemo',
    data() {
      return {
        str: '',
        loading: false,
        sse: null,
      };
    },
    mounted() {
      // Initialize XRequest instance
      this.initXRequest();
    },
    methods: {
      initXRequest() {
        // Import XRequest class
        const { XRequest } = require('vue-element-ui-x').customMixins;

        // Create XRequest instance
        this.sse = new XRequest({
          baseURL: 'https://testsse.element-ui-x.com',
          type: 'sse', // Use SSE mode

          onMessage: msg => {
            console.log('Message received:', msg);
            // Assume message format is { data: string }
            if (msg && msg.data) {
              this.str += `
              ${msg.data}`;
            } else {
              this.str += `
              ${JSON.stringify(msg)}`;
            }
          },
          onOpen: () => {
            console.log('SSE connection opened');
            this.$message.success('SSE connection established');
            this.loading = true;
          },
          onError: error => {
            console.error('SSE error:', error);
            this.loading = false;
          },
          onAbort: () => {
            console.log('SSE connection aborted');
            this.loading = false;
          },
          onFinish: messages => {
            console.log('SSE connection finished, total messages received:', messages?.length || 0);
            this.$message.success('Connection finished');
            this.loading = false;
          },
        });
      },

      startSSERequest() {
        if (this.loading) {
          return;
        }

        // Clear previous results
        this.str = '';

        try {
          // Initiate SSE request
          this.sse.send('/api/sse');
          this.$message.info('Establishing SSE connection...');
        } catch (error) {
          console.error('Failed to initiate request:', error);
          this.$message.error('Failed to initiate request');
        }
      },

      abortRequest() {
        if (this.sse) {
          this.sse.abort();
        }
      },

      clearResult() {
        this.str = '';
      },
    },

    beforeDestroy() {
      // Clean up resources when the component is destroyed
      if (this.sse) {
        this.sse.abort();
        this.sse = null;
      }
    },
  };
</script>

<style>
  .xrequest-container {
    display: flex;
    flex-direction: column;
    gap: 20px;
    padding: 20px;
    border: 1px solid #ebeef5;
    border-radius: 8px;
    background-color: #fafafa;
  }

  .description {
    text-align: center;
  }

  .description p {
    margin: 0;
    color: #606266;
    font-size: 14px;
  }

  .btn-list {
    display: flex;
    justify-content: center;
    gap: 12px;
    flex-wrap: wrap;
  }

  .result-container {
    border: 1px solid #e4e7ed;
    border-radius: 6px;
    background-color: #fff;
    overflow: hidden;
  }

  .result-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 12px 16px;
    background-color: #f5f7fa;
    border-bottom: 1px solid #e4e7ed;
  }

  .result-header h4 {
    margin: 0;
    font-size: 14px;
    color: #303133;
  }

  .result-content {
    padding: 16px;
    min-height: 120px;
    max-height: 300px;
    overflow-y: auto;
  }

  .empty-state {
    color: #909399;
    text-align: center;
    font-style: italic;
    line-height: 88px;
  }

  .message-display {
    margin: 0;
    padding: 12px;
    background-color: #f9f9f9;
    border: 1px solid #e4e7ed;
    border-radius: 4px;
    font-family: 'Courier New', monospace;
    font-size: 13px;
    line-height: 1.5;
    white-space: pre-wrap;
    word-break: break-all;
    color: #2c3e50;
  }

  /* Responsive Design */
  @media (max-width: 768px) {
    .btn-list {
      flex-direction: column;
      gap: 8px;
    }

    .btn-list .el-button {
      width: 100%;
    }

    .result-header {
      flex-direction: column;
      gap: 8px;
      align-items: flex-start;
    }
  }
</style>
Expand Copy

# XRequest Basic Usage (fetch)

The fetch request method of the XRequest utility class

Received Messages:

Disconnected
No messages yet
<template>
  <div class="xrequest-container">
    <div class="description">
      <p>The fetch request method of the XRequest utility class</p>
    </div>

    <div class="btn-list">
      <el-button
        type="primary"
        :disabled="loading"
        @click="startSSERequest"
      >
        {{ loading ? 'Requesting...' : 'Initiate Request' }}
      </el-button>

      <el-button
        type="danger"
        :disabled="!loading"
        @click="abortRequest"
      >
        Cancel Request
      </el-button>

      <el-button
        type="info"
        @click="clearResult"
      >
        Clear Result
      </el-button>
    </div>

    <div class="result-container">
      <div class="result-header">
        <h4>Received Messages:</h4>
        <el-tag
          v-if="loading"
          type="success"
          size="mini"
        >
          Connected
        </el-tag>
        <el-tag
          v-else
          type="info"
          size="mini"
        >
          Disconnected
        </el-tag>
      </div>
      <div class="result-content">
        <div
          v-if="!str"
          class="empty-state"
        >
          No messages yet
        </div>
        <pre
          v-else
          class="message-display"
        >
{{ str }}</pre
        >
      </div>
    </div>
  </div>
</template>
<script>
  export default {
    name: 'XRequestDemo',
    data() {
      return {
        str: '',
        loading: false,
        sse: null,
      };
    },
    mounted() {
      // Initialize XRequest instance
      this.initXRequest();
    },
    methods: {
      initXRequest() {
        // Import XRequest class
        const { XRequest } = require('vue-element-ui-x').customMixins;

        // Create XRequest instance
        this.sse = new XRequest({
          baseURL: 'https://testsse.element-ui-x.com',
          type: 'fetch', // Use fetch mode
          onMessage: msg => {
            // Assume message format is { data: string }
            if (msg && msg.data) {
              this.str += `
              ${msg.data}`;
            } else {
              this.str += `
              ${JSON.stringify(msg)}`;
            }
          },

          onError: error => {
            console.error('Error:', error);
            this.$message.error('An error occurred during the request');
            this.loading = false;
          },
          onAbort: () => {
            console.log('Request aborted');
            // this.$message.info('Request aborted');
            this.loading = false;
          },
          onFinish: messages => {
            console.log('Request finished, total messages received:', messages?.length || 0);
            this.$message.success('Request finished');
            this.loading = false;
          },
        });
      },

      startSSERequest() {
        if (this.loading) {
          return;
        }

        // Clear previous results
        this.str = '';

        try {
          // Initiate fetch request
          this.sse.send('/api/sse');
          this.$message.info('Requesting...');
          this.loading = true;
        } catch (error) {
          console.error('Failed to initiate request:', error);
          this.$message.error('Failed to initiate request');
        }
      },

      abortRequest() {
        if (this.sse) {
          this.sse.abort();
        }
      },

      clearResult() {
        this.str = '';
      },
    },

    beforeDestroy() {
      // Clean up resources when the component is destroyed
      if (this.sse) {
        this.sse.abort();
        this.sse = null;
      }
    },
  };
</script>

<style>
  .xrequest-container {
    display: flex;
    flex-direction: column;
    gap: 20px;
    padding: 20px;
    border: 1px solid #ebeef5;
    border-radius: 8px;
    background-color: #fafafa;
  }

  .description {
    text-align: center;
  }

  .description p {
    margin: 0;
    color: #606266;
    font-size: 14px;
  }

  .btn-list {
    display: flex;
    justify-content: center;
    gap: 12px;
    flex-wrap: wrap;
  }

  .result-container {
    border: 1px solid #e4e7ed;
    border-radius: 6px;
    background-color: #fff;
    overflow: hidden;
  }

  .result-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 12px 16px;
    background-color: #f5f7fa;
    border-bottom: 1px solid #e4e7ed;
  }

  .result-header h4 {
    margin: 0;
    font-size: 14px;
    color: #303133;
  }

  .result-content {
    padding: 16px;
    min-height: 120px;
    max-height: 300px;
    overflow-y: auto;
  }

  .empty-state {
    color: #909399;
    text-align: center;
    font-style: italic;
    line-height: 88px;
  }

  .message-display {
    margin: 0;
    padding: 12px;
    background-color: #f9f9f9;
    border: 1px solid #e4e7ed;
    border-radius: 4px;
    font-family: 'Courier New', monospace;
    font-size: 13px;
    line-height: 1.5;
    white-space: pre-wrap;
    word-break: break-all;
    color: #2c3e50;
  }

  /* Responsive Design */
  @media (max-width: 768px) {
    .btn-list {
      flex-direction: column;
      gap: 8px;
    }

    .btn-list .el-button {
      width: 100%;
    }

    .result-header {
      flex-direction: column;
      gap: 8px;
      align-items: flex-start;
    }
  }
</style>
Expand Copy

# Mixed Usage of sendMixin and XRequest

Use sendMixin for front-end state control and XRequest for back-end request control.

No messages yet
<template>
  <div class="btn-list">
    <el-button
      v-if="!loading"
      style="background-color: #c2306a; border-color:#c2306a; color: white;"
      round
      plain
      icon="el-icon-position"
      size="large"
      @click="handleSend"
    ></el-button>

    <el-button
      v-if="loading"
      style="background-color: #c2306a; border-color:#c2306a; color: white;"
      round
      plain
      icon="el-icon-loading"
      size="large"
      @click="handleAbort"
    ></el-button>
  </div>
  <div class="result-content">
    <div
      v-if="!str"
      class="empty-state"
    >
      No messages yet
    </div>
    <pre
      v-else
      class="message-display"
    >
{{ str }}</pre
    >
  </div>
</template>
<script>
  let sendMixin = {};
  try {
    if (typeof window !== 'undefined' && window['vue-element-ui-x']) {
      sendMixin = window['vue-element-ui-x'].customMixins.sendMixin;
    } else if (typeof require !== 'undefined') {
      sendMixin = require('vue-element-ui-x').customMixins.sendMixin;
    }
  } catch (e) {
    sendMixin = {
      data() {
        return { loading: false };
      },
      methods: {
        initSend() {},
        handleSend() {},
        handleFinish() {},
        handleAbort() {},
      },
    };
  }
  export default {
    name: 'XRequestDemo',
    mixins: [sendMixin],
    data() {
      return {
        str: '',
        sse: null,
      };
    },
    mounted() {
      // Initialize XRequest instance
      this.initXRequest();
      this.initSend({
        sendHandler: this.startFn,
        finishHandler: this.finishBasicRequest,
        abortHandler: this.abortBasicRequest,
        onAbort: this.onBasicAbort,
      });
    },
    methods: {
      initXRequest() {
        // Import XRequest class
        const { XRequest } = require('vue-element-ui-x').customMixins;

        // Create XRequest instance
        this.sse = new XRequest({
          baseURL: 'https://testsse.element-ui-x.com',
          type: 'fetch', // Use fetch mode

          onMessage: msg => {
            console.log('Message received:', msg);
            if (msg && msg.data) {
              this.str += `
              ${msg.data}`;
            } else {
              this.str += `
              ${JSON.stringify(msg)}`;
            }
          },

          onError: error => {
            console.error('Error:', error);
            this.$message.error('An error occurred during the request');
          },
          onAbort: () => {},
          onFinish: messages => {
            this.handleFinish();
            console.log('Request finished, total messages received:', messages?.length || 0);
          },
        });
      },
      finishBasicRequest() {
        console.log('Basic request processing finished');
      },
      abortBasicRequest() {
        if (this.sse) {
          this.sse.abort();
        }
        console.log('Basic request aborted', this.loading);
      },
      onBasicAbort() {
        console.log('Basic request abort callback');
      },
      startFn() {
        this.str = '';
        this.sse.send('/api/sse');
      },
    },

    beforeDestroy() {
      // Clean up resources when the component is destroyed
      if (this.sse) {
        this.sse.abort();
        this.sse = null;
      }
    },
  };
</script>
Expand Copy

# Mixin Properties

Parameter Description Type Default
loading Request loading state Boolean false
_sendPromise Send operation Promise Promise null
_sendController Send controller Object null

# Mixin Methods

Method Name Description Parameters Return Value
initSend Initialize send configuration options: { onAbort, sendHandler, abortHandler, finishHandler } -
handleSend Execute send operation ...args: Arguments passed to sendHandler -
handleAbort Abort send operation - -
handleFinish Finish send operation - -
createXRequest Create XRequest instance options: XRequest configuration options XRequest

# XRequest Class

# Constructor Options

Parameter Description Type Default
baseURL Base URL String ''
type Request type (sse/fetch) String 'sse'
transformer Data transformer Function -
baseOptions Base configuration options Object {}
onAbort Abort callback Function -
onMessage Message callback Function -
onError Error callback Function -
onOpen Connection open callback (SSE mode) Function -
onFinish Finish callback Function -

# XRequest Methods

Method Name Description Parameters Return Value
send Send request url: Request URL, options: options XRequest
abort Abort request - -

# Events

The mixin itself does not trigger events directly, but it supports configuring callback functions in initSend:

Callback Function Description Callback Parameters
onAbort Triggered when the request is aborted -
sendHandler Send handler ...args: send parameters
abortHandler Abort handler -
finishHandler Finish handler -

# Utility Function Version

For non-component scenarios, a utility function version is provided:

import { createSendUtils, XRequest } from 'vue-element-ui-x';

// Create send utility
const sendUtils = createSendUtils({
  sendHandler: (...args) => {
    console.log('Start sending:', args);
    // Execute sending logic
  },
  abortHandler: () => console.log('Request aborted'),
  finishHandler: () => console.log('Request finished'),
  onAbort: () => console.log('Abort callback'),
});

// Use utility to send request
function sendRequest() {
  sendUtils.send('param1', 'param2');
}

// Abort request
function abortRequest() {
  sendUtils.abort();
}

// Get current status
function getStatus() {
  return {
    isLoading: sendUtils.state.loading,
  };
}

// Use XRequest class directly
const xRequest = new XRequest({
  baseURL: 'https://api.example.com',
  type: 'fetch',
  transformer: data => JSON.parse(data),
  onMessage: message => console.log('Message:', message),
  onError: error => console.error('Error:', error),
  onFinish: messages => console.log('Finished:', messages.length, 'messages'),
});

// Send request
xRequest.send('/api/stream', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ query: 'hello' }),
});

// Abort request
xRequest.abort();

# Precautions

  1. This mixin supports modern browsers' Fetch API and EventSource API. Please ensure browser compatibility before use.
  2. SSE mode is suitable for server-push scenarios, while Fetch mode is for streaming response data.
  3. A custom transformer function can be used to preprocess received data.
  4. Resources are automatically cleaned up when the component is destroyed to prevent memory leaks.
  5. For long-running requests, it is recommended to implement an appropriate error retry mechanism.
  6. The baseOptions configuration is only effective in SSE mode and is used for the EventSource constructor.
  7. The options parameter in Fetch mode is passed to the fetch function and supports all standard fetch options.

# SSE Connection End Detection Mechanism

The XRequest class in SSE mode uses multiple detection mechanisms to accurately determine if a connection has ended normally:

  1. End Message Detection: Supports detecting end markers like [DONE] or data: [DONE].
  2. Timeout Detection: If no new message arrives within 10 seconds and the connection is not in a connecting state, it is considered ended.
  3. State Detection: A comprehensive judgment based on the EventSource's readyState and the number of messages received.
  4. Duplicate Trigger Prevention: Uses an internal flag to prevent the end event from being triggered multiple times for the same connection.

This design effectively prevents normally closed connections from being mistaken for errors, ensuring that the onFinish and onError callbacks are triggered correctly.

# Custom Timeout

If you need to adjust the timeout detection period, you can configure it when creating the XRequest instance:

const xRequest = new XRequest({
  // Other configurations...
  onMessage: msg => {
    // Process message
    console.log('Message received:', msg);
  },
  onFinish: messages => {
    console.log('Connection finished normally, received', messages.length, 'messages in total');
  },
  onError: error => {
    console.error('An error occurred with the connection:', error);
  },
});

// You can customize the detection interval by modifying the instance's timeout period
// Note: This must be set before calling send
xRequest._finishTimeout = 5000; // 5-second timeout

</rewritten_file>