Skip to content

互动信箱功能

文档:https://yxw7ds3a1o.apifox.cn/

优先确认是否拥有权限

如图,类似反馈提交的表单功能:

后台配置

API:新增 反馈

https://yxw7ds3a1o.apifox.cn/292181455e0

请求类型API 地址
POST/interactive-api/interaction/mailbox/web/add/{appId}
json
[
  { "field": "id_Fcldm96uw5l2acc", "title": "姓名", "value": "1" },
  { "field": "id_Fts4m96uw6ctafc", "title": "电子邮箱", "value": "1" },
  { "field": "id_F1w8m9i3zfaoakc", "title": "手机号码", "value": "1" },
  { "field": "id_Fcfmmdy7ypduacc", "title": "意见标题", "value": "1" },
  { "field": "id_Flxsm9i40ddpanc", "title": "您的意见内容", "value": "1" }
]

提示

示例代码仅做参考

ts
function getAppId() {
  return 30007;
}

// @ts-ignore
window.handleSubmitFeedback = function () {
  const name = document.getElementById("feedback-name") as HTMLInputElement;
  const email = document.getElementById("feedback-email") as HTMLInputElement;
  const gender = document.querySelector(
    'input[name="gender"]:checked'
  ) as HTMLInputElement;
  const address = document.getElementById(
    "feedback-address"
  ) as HTMLInputElement;
  const age = document.getElementById("feedback-age") as HTMLInputElement;
  const letterTheme = document.getElementById(
    "feedback-letter-theme"
  ) as HTMLInputElement;
  const phone = document.getElementById("feedback-phone") as HTMLInputElement;
  const letterType = document.querySelector(
    'input[name="letter-type"]:checked'
  ) as HTMLInputElement;
  const content = document.getElementById(
    "feedback-content"
  ) as HTMLTextAreaElement | null;

  const checkList: [HTMLInputElement | null, string][] = [
    [name, "姓名"],
    [email, "电子邮件"],
    [gender, "性别"],
    [address, "联系地址"],
    [age, "年龄"],
    [letterTheme, "来信主题"],
    [phone, "电话"],
    [letterType, "信件类型"],
  ];

  for (const [element, label] of checkList) {
    if (!element) {
      return showError(`${label} 元素异常`);
    }

    if (!element.value || element.value.trim() === "") {
      return showError(`${label} 不能为空`);
    }
  }

  // content 可以为空
  const contentValue = (content && content.value) || "";

  const body = [
    { field: "id_Fcldm96uw5l2acc", title: "姓名", value: name.value },
    { field: "id_Fts4m96uw6ctafc", title: "电子邮件", value: email.value },
    { field: "id_Frmgmgj59hmbacc", title: "性别", value: gender.value },
    { field: "id_Fv7smgj5bk9vafc", title: "联系地址", value: address.value },
    { field: "id_Fkhrmgj5bvjqaic", title: "年龄", value: age.value },
    {
      field: "id_F0o2mgj5d0hpaoc",
      title: "来信主题",
      value: letterTheme.value,
    },
    { field: "id_Fjhomgj5crk1alc", title: "电话", value: phone.value },
    { field: "id_F5zxmgj5e216axc", title: "信件类型", value: letterType.value },
    { field: "id_Fdp8mgj5ddmmauc", title: "具体内容", value: contentValue },
  ];

  addFeedback(getAppId(), body, {
    onSuccess: (data) => {
      const res = JSON.parse(data);
      if (res.code === 200) {
        showError(res.message || "提交成功");
        handleClearFeedback();
      } else {
        showInfo(res.message || "提交失败");
      }
    },
    onError: (error) => {
      showError("提交失败,请稍后尝试");
      console.error(error);
    },
  });
};
ts
import {  HttpRequestDataType, post } from "../util/http";

interface CallBack {
  onSuccess?: (data: string) => void;
  onError?: (error: any) => void;
}

export function addFeedback(
  APPID: string | number = 30007,
  data: HttpRequestDataType,
  callback?: CallBack
) {
  const url = `/interactive-api/interaction/mailbox/web/add/${APPID}`;

  post(url, data, {
    onSuccess: (data) => {
      callback?.onSuccess?.(data);
    },
    onError: (error) => {
      callback?.onError?.(error);
    },
  });
}
ts
type IHttpHeaders = Record<string, string>;
interface JSONType {
  [key: string]: string | number | boolean | null | undefined | JSONType;
}

export type HttpRequestDataType =
  | JSONType
  | FormData
  | undefined
  | Array<JSONType>;

interface IHttpOptions {
  method?: "POST" | "GET" | "PUT" | "DELETE";
  headers?: IHttpHeaders;
  onSuccess: (data: string) => void;
  onError: (error: HttpError) => void;
}

class HttpError extends Error {
  public status: number;
  public responseText?: string;
  constructor(options: {
    message?: string;
    status: number;
    responseText?: string;
  }) {
    super(options.message);
    this.status = options.status;
    this.responseText = options.responseText;
  }
}

export const request = (
  url: string,
  data: HttpRequestDataType,
  config: IHttpOptions
) => {
  const method =
    config.method && typeof config.method === "string"
      ? config.method.toLocaleUpperCase()
      : "GET";

  const http = new XMLHttpRequest();

  let url2 = url;
  if (method === "GET") {
    if (data instanceof FormData) {
      throw new Error("GET 的 data **不** 应该是 FormData");
    }
    const queryString = Array.isArray(data) ? "" : dataToQueryString(data);

    url2 = queryString ? url + "?" + queryString : url;
  }

  http.open(method, url2, true);

  const headers = combinationHeader(config.headers, method);

  // 除了 Content Type 需要单独处理,其他直接设置 Request Header
  let contentType = "";
  for (const key in headers) {
    if (key.toLocaleUpperCase() === "CONTENT-TYPE") {
      contentType = headers[key];
    } else {
      http.setRequestHeader(key, headers[key]);
    }
  }

  if (method === "POST" || method === "PUT") {
    const __data__ = data || {};

    const isFromData = data instanceof FormData;

    if (contentType === "application/json") {
      onPostByJson(http, __data__ as JSONType);
    } else if (contentType === "application/x-www-form-urlencoded") {
      onPostByUrlEncoded(http, __data__ as JSONType);
    } else if (contentType === "multipart/form-data") {
      if (isFromData) {
        onPostByFormData(http, data);
      } else {
        throw new Error("data 不是 FormData");
      }
    } else {
      throw new Error("未实现的 content type: " + contentType);
    }
  } else {
    http.send();
  }

  http.onreadystatechange = function () {
    // readyState
    // 0: 未初始化
    // 1: 已打开
    // 2: 已发送
    // 3: 接收中
    // 4: 完成

    if (http.readyState === 4) {
      if (http.status === 200) {
        config.onSuccess(http.responseText);
      } else {
        config.onError(
          new HttpError({
            message: "请求失败",
            status: http.status,
            responseText: http.responseText,
          })
        );
      }
    }
  };
};

function combinationHeader(header: IHttpHeaders = {}, method: string) {
  const defaultOptions: IHttpHeaders = {
    "Content-Type": "application/json",
  };

  let headers: Record<string, string> = {};

  if (method === "POST" || method === "PUT") {
    headers = { ...defaultOptions };
  }

  headers = { ...headers, ...header };

  return headers;
}

export const get = (
  url: string,
  data: JSONType | undefined,
  config: IHttpOptions
) => {
  return request(url, data, { ...config, method: "GET" });
};

export const post = (
  url: string,
  data: HttpRequestDataType,
  config: IHttpOptions
) => {
  return request(url, data, { ...config, method: "POST" });
};

// ------------------------------------------------------------

function onPostByJson(xhr: XMLHttpRequest, data: JSONType) {
  xhr.setRequestHeader("Content-Type", "application/json");
  const body = JSON.stringify(data);
  xhr.send(body);
}

function onPostByUrlEncoded(xhr: XMLHttpRequest, data: JSONType) {
  xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

  const queryString = dataToQueryString(data);
  xhr.send(queryString);
}

function onPostByFormData(xhr: XMLHttpRequest, data: FormData) {
  if (!data) throw new Error("data is required");

  if (data instanceof FormData === false) {
    throw new Error("data must be a FormData instance");
  }

  xhr.setRequestHeader("Content-Type", "multipart/form-data");
  xhr.send(data);
}

function dataToQueryString(data?: JSONType): string {
  if (!data) {
    return "";
  }

  const params: string[] = [];

  for (const key in data) {
    const value = data[key];
    params.push(`${key}=${value}`);
  }

  return params.join("&");
}

注意!field 字段怎么来?

field 并不是“配置表单”时候出现的字段!
field 并不是“配置表单”时候出现的字段!
field 并不是“配置表单”时候出现的字段!

API:获取 form 配置

https://yxw7ds3a1o.apifox.cn/292178118e0

请求类型API 地址
GET/interactive-api/interaction/mailbox/web/mailboxConfig/{appId}

提示

示例代码仅做参考

ts
window.addEventListener("DOMContentLoaded", function () {
  getFeedbackFormConfiguration(getAppId(), {
    onSuccess(data) {
      // console.log(data);
    },
  });
});
ts
import { get, HttpRequestDataType, post } from "../util/http";

export function getFeedbackFormConfiguration(
  APPID: string | number = 30007,
  callback?: CallBack
) {
  const url = `/interactive-api/interaction/mailbox/web/mailboxConfig/${APPID}`;

  get(url, undefined, {
    onSuccess: (data) => {
      callback?.onSuccess?.(data);
    },
    onError: (error) => {
      callback?.onError?.(error);
    },
  });
}

interface CallBack {
  onSuccess?: (data: string) => void;
  onError?: (error: any) => void;
}
ts
type IHttpHeaders = Record<string, string>;
interface JSONType {
  [key: string]: string | number | boolean | null | JSONType;
}

export type HttpRequestDataType =
  | JSONType
  | FormData
  | undefined
  | Array<JSONType>;

interface IHttpOptions {
  method?: "POST" | "GET" | "PUT" | "DELETE";
  headers?: IHttpHeaders;
  onSuccess: (data: string) => void;
  onError: (error: any) => void;
}

class HttpError extends Error {
  constructor(message: string, public status: number) {
    super(message);
  }
}

export const request = (
  url: string,
  data: HttpRequestDataType,
  config: IHttpOptions
) => {
  const method =
    config.method && typeof config.method === "string"
      ? config.method.toLocaleUpperCase()
      : "GET";

  const http = new XMLHttpRequest();

  let url2 = url;
  if (method === "GET") {
    if (data instanceof FormData) {
      throw new Error("GET 的 data **不** 应该是 FormData");
    }
    const queryString = Array.isArray(data) ? "" : dataToQueryString(data);

    url2 = queryString ? url + "?" + queryString : url;
  }

  http.open(method, url2, true);

  const headers = combinationHeader(config.headers, method);

  // 除了 Content Type 需要单独处理,其他直接设置 Request Header
  let contentType = "";
  for (const key in headers) {
    if (key.toLocaleUpperCase() === "CONTENT-TYPE") {
      contentType = headers[key];
    } else {
      http.setRequestHeader(key, headers[key]);
    }
  }

  if (method === "POST" || method === "PUT") {
    const __data__ = data || {};

    const isFromData = data instanceof FormData;

    if (contentType === "application/json") {
      onPostByJson(http, __data__ as JSONType);
    } else if (contentType === "application/x-www-form-urlencoded") {
      onPostByUrlEncoded(http, __data__ as JSONType);
    } else if (contentType === "multipart/form-data") {
      if (isFromData) {
        onPostByFormData(http, data);
      } else {
        throw new Error("data 不是 FormData");
      }
    } else {
      throw new Error("未实现的 content type: " + contentType);
    }
  } else {
    http.send();
  }

  http.onreadystatechange = function () {
    // readyState
    // 0: 未初始化
    // 1: 已打开
    // 2: 已发送
    // 3: 接收中
    // 4: 完成

    if (http.readyState === 4) {
      if (http.status === 200) {
        config.onSuccess(http.responseText);
      } else {
        config.onError(new HttpError("请求失败", http.status));
      }
    }
  };
};

function combinationHeader(header: IHttpHeaders = {}, method: string) {
  const defaultOptions: IHttpHeaders = {
    "Content-Type": "application/json",
  };

  let headers: Record<string, string> = {};

  if (method === "POST" || method === "PUT") {
    headers = { ...defaultOptions };
  }

  headers = { ...headers, ...header };

  return headers;
}

export const get = (
  url: string,
  data: JSONType | undefined,
  config: IHttpOptions
) => {
  return request(url, data, { ...config, method: "GET" });
};

export const post = (
  url: string,
  data: HttpRequestDataType,
  config: IHttpOptions
) => {
  return request(url, data, { ...config, method: "POST" });
};

// ------------------------------------------------------------

function onPostByJson(xhr: XMLHttpRequest, data: JSONType) {
  xhr.setRequestHeader("Content-Type", "application/json");
  const body = JSON.stringify(data);
  xhr.send(body);
}

function onPostByUrlEncoded(xhr: XMLHttpRequest, data: JSONType) {
  xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

  const queryString = dataToQueryString(data);
  xhr.send(queryString);
}

function onPostByFormData(xhr: XMLHttpRequest, data: FormData) {
  if (!data) throw new Error("data is required");

  if (data instanceof FormData === false) {
    throw new Error("data must be a FormData instance");
  }

  xhr.setRequestHeader("Content-Type", "multipart/form-data");
  xhr.send(data);
}

function dataToQueryString(data?: JSONType): string {
  if (!data) {
    return "";
  }

  const params: string[] = [];

  for (const key in data) {
    const value = data[key];
    params.push(`${key}=${value}`);
  }

  return params.join("&");
}
响应示例
json
{
  "traceId": "1976221376620797952",
  "code": 200,
  "msg": "操作成功",
  "message": "操作成功",
  "data": [
    {
      "type": "input",
      "field": "F291m96uw5l2abc",
      "title": "姓名",
      "info": "",
      "$required": true,
      "_fc_id": "id_Fcldm96uw5l2acc",
      "name": "ref_Fkcfm96uw5l3adc",
      "display": true,
      "hidden": false,
      "_fc_drag_tag": "input",
      "props": {
        "placeholder": "请输入",
        "columnShow": true
      }
    },
    {
      "type": "input",
      "field": "Fyalm96uw6ctaec",
      "title": "电子邮件",
      "info": "",
      "$required": true,
      "_fc_id": "id_Fts4m96uw6ctafc",
      "name": "ref_F2f2m96uw6ctagc",
      "display": true,
      "hidden": false,
      "_fc_drag_tag": "input",
      "props": {
        "placeholder": "请输入",
        "columnShow": true
      }
    },
    {
      "type": "radio",
      "field": "F0homgj59hmbabc",
      "title": "性别",
      "info": "",
      "effect": {
        "fetch": ""
      },
      "$required": true,
      "props": {
        "columnShow": true
      },
      "options": [
        {
          "label": "男",
          "value": "male"
        },
        {
          "label": "女",
          "value": "female"
        }
      ],
      "_fc_id": "id_Frmgmgj59hmbacc",
      "name": "ref_Fzy9mgj59hmbadc",
      "display": true,
      "hidden": false,
      "_fc_drag_tag": "radio"
    },
    {
      "type": "input",
      "field": "Fgvhmgj5bk9vaec",
      "title": "联系地址",
      "info": "",
      "$required": true,
      "props": {
        "columnShow": true
      },
      "_fc_id": "id_Fv7smgj5bk9vafc",
      "name": "ref_F4xumgj5bk9vagc",
      "display": true,
      "hidden": false,
      "_fc_drag_tag": "input"
    },
    {
      "type": "input",
      "field": "Fe8tmgj5bvjqahc",
      "title": "年龄",
      "info": "",
      "$required": true,
      "props": {
        "columnShow": true
      },
      "_fc_id": "id_Fkhrmgj5bvjqaic",
      "name": "ref_Fs66mgj5bvjqajc",
      "display": true,
      "hidden": false,
      "_fc_drag_tag": "input"
    },
    {
      "type": "input",
      "field": "F9lvmgj5crk1akc",
      "title": "电话",
      "info": "",
      "$required": true,
      "props": {
        "columnShow": true
      },
      "_fc_id": "id_Fjhomgj5crk1alc",
      "name": "ref_Fjwxmgj5crk1amc",
      "display": true,
      "hidden": false,
      "_fc_drag_tag": "input"
    },
    {
      "type": "input",
      "field": "Fjltmgj5d0hpanc",
      "title": "来信主题",
      "info": "",
      "$required": true,
      "props": {
        "columnShow": true
      },
      "_fc_id": "id_F0o2mgj5d0hpaoc",
      "name": "ref_Fh0rmgj5d0hpapc",
      "display": true,
      "hidden": false,
      "_fc_drag_tag": "input"
    },
    {
      "type": "radio",
      "field": "Ftbemgj5e215awc",
      "title": "信件类型",
      "info": "",
      "effect": {
        "fetch": ""
      },
      "$required": true,
      "props": {
        "columnShow": true
      },
      "options": [
        {
          "label": "建议",
          "value": "suggestion"
        },
        {
          "label": "求助",
          "value": "help"
        },
        {
          "label": "投诉",
          "value": "complaint"
        },
        {
          "label": "咨询",
          "value": "consultation"
        },
        {
          "label": "其它",
          "value": "other"
        }
      ],
      "_fc_id": "id_F5zxmgj5e216axc",
      "name": "ref_Fz6hmgj5e216ayc",
      "display": true,
      "hidden": false,
      "_fc_drag_tag": "radio"
    },
    {
      "type": "input",
      "field": "Flbnmgj5ddmmatc",
      "title": "具体内容",
      "info": "",
      "$required": false,
      "props": {
        "type": "textarea",
        "columnShow": true
      },
      "_fc_id": "id_Fdp8mgj5ddmmauc",
      "name": "ref_Fg3gmgj5ddmmavc",
      "display": true,
      "hidden": false,
      "_fc_drag_tag": "textarea"
    }
  ],
  "timestamp": "2025-10-09 17:41:07"
}

API:回复列表

https://yxw7ds3a1o.apifox.cn/292178357e0

应该用不上

其他信息

参考已经上线的站点:

长寿人大-公众意见征集:https://www.cqcsrd.gov.cn/web/column/col5000843.html

重庆妇女网-维权咨询:https://www.cqwomen.org.cn/web/column/col1827873.html

todo

统一战线-部长直通车:https://www.cqtzb.gov.cn/kuai/Message/index

武隆区人大-人大信箱:http://www.cqwlrd.gov.cn/content/rdxx/