npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

mock-proxy-kit

v2.3.1

Published

mock proxy插件kit,目前包含插件相关全部类型

Downloads

14

Readme

安装

npm i mock-proxy-kit -S

团队配置

import { SmartMockRule } from "./smartMock";

/**
 * 操作权限
 */
export type OperationPermissions = {
  /**
   * 场景可否开关
   * @default true
   */
  sceneOpen?: boolean;
  /**
   * 场景可否新增
   * @default true
   */
  sceneAdd?: boolean;
  /**
   * 场景可否更改(mock数据)
   * @default true
   */
  sceneUpdate?: boolean;
  /**
   * 场景可否删除
   * @default true
   */
  sceneDelete?: boolean;
  /**
  * 场景名称可否编辑,需要sceneUpdate为true才会生效
  * @default false
  */
  sceneNameEdit?: boolean;
  /**
   * 默认场景可否编辑(更改或删除)
   * @default true
   */
  defaultSceneEdit?: boolean;
  /**
   * 是否可移动api分组
   * @default true
   */
  apiMove?: boolean;
  /**
   * 是否可创建分组
   * @default true
   */
  groupAdd?: boolean;
  /**
   * 是否可删除分组
   * @default true
   */
  groupDelete?: boolean;
}

/**
 * 项目配置
 */
export interface ProjectConfig {
  /**
   * 项目名称
   */
  name: string;
  /**
   * 项目id
   */
  id: string | number;
  /**
   * 是否跨源。如果该项目请求与domain非同源,则跨源
   * 若跨源,会给redirect url加上标识,这样CORS处理时,匹配到该标识,AC头可返回null,解决跨源问题
   * @default false
   */
  crossOrigin?: boolean;
  /**
   * 项目纬度的智能mock规则
   */
  smartMockRules?: SmartMockRule[];
  /**
   * 其他开发者所需字段,可在自定义脚本中取得
   */
  [key: string]: any;
}

/**
 * 站点配置
 */
export interface SiteConfig {
  /**
   * 站点名称,比如github
   */
  name: string;
  /**
   * 负责人
   */
  owners?: string[];
  /**
   * 描述
   */
  desc?: string;
  /**
   * 站点的域名,暂不支持ip和端口
   */
  domains: string[];
  /**
   * 站点对应的项目配置。可能一个站点对应了多个项目(即oneapi、yapi的project)
   */
  projects: ProjectConfig[];
}

/**
 * 团队配置
 */
export interface TeamConfig {
  sites: SiteConfig[];
  /**
   * 用户自定义crud mock平台脚本地址,脚本export参考UserScript类型
   */
  scriptUrl: string;
  /**
   * CORS配置
   */
  corsConfig: {
    /**
     * 主要是用于篡改access-control-allow-headers,避免引发cors问题
     * 默认会支持 'content-type | authorization | x-requested-with | x-referer' | 'x-xsrf-token'
     */
    accessControlAllowHeaders?: string[];
    /**
     * 重定向的目标地址,比如oneapi.alibaba-inc.com
     */
    redirectTarget: string | string[];
  };
  /**
   * 配置页面地址,用于在未匹配上配置时,提示跳转
   */
  configPageUrl?: string;
  /**
   * 操作权限,默认都开启,但面板会依据script的情况,进行展示
   */
  operationPermissions?: OperationPermissions;
  /**
   * 默认场景id,一是用于进入详情面板后匹配,二是用于未来支持其它平台的默认场景编辑匹配(如rap2)
   * @default 'default'
   */
  defaultSceneId?: string;
  /**
   * 智能mock规则
   */
  smartMockRules?: SmartMockRule[];
}

用户自定义脚本

import {
  ApiMethod
} from "./common";
import { ProjectConfig } from "./teamConfig";

/**
 * 面板中api场景的返回值
 */
export interface SceneResponse {
  /**
   * 场景id
   */
  id: string | number;
  /**
   * 场景名称
   */
  name: string;
  /**
   * 场景数据
   */
  mockData: any;
  /**
   * 场景mock地址
   */
  mockUrl: string;
  /**
   * 用户自定义数据
   */
  [key: string]: any;
}

/**
 * 面板中添加api场景的payload
 */
export interface AddScenePayload {
  /**
   * 场景名称
   */
  name: string;
  /**
   * 场景数据
   */
  mockData: any;
}

/**
 * 面板中添加api场景的response
 */
export interface AddSceneResponse {
  /**
   * 新增场景id
   */
  id: string | number;
  /**
   * 用户自定义数据
   */
  [key: string]: any;
}

/**
 * 面板中接口概览的返回值
 */
export type OverviewApiResponse = {
  /**
   * api id
   */
  id: number | string;
  /**
   * api path, 标识mock平台的api路径,未必是真实的路径
   */
  path: string;
  /**
   * 真实的api路径,界面展示路径也会用此字段。如果没有,则用path。
   * @example 比如oneapi如果存在某个资源的get和delete restful接口,那么api path会是get/api/* resource/ resourceId和delete/api/resource/resourceId
   * 但真实路径其实是:api/resource/{resourceId}
   * @caution 注意,如果真实路径带有{},插件会自动将其转为regexp,当网页中发送,api/resource/* xxx时,可顺利匹配到规则进行转发
   */
  realPath?: string;
  /**
   * 如果申明了,会以此为规则的regexFilter。优先级 regexFilter > realPath > path
   * @see RE2 syntax
   */
  regexFilter?: string;
  /**
   * api method
   */
  method: ApiMethod;
  /**
   * api名称
   */
  name: string;
  /**
   * api描述
   */
  desc?: string;
  /**
   * api创建者
   */
  creator?: string;
  /**
   * mock地址
   */
  mockUrl: string;
  /**
   * 原接口地址
   */
  sourceUrl?: string;
  /**
   * 用户自定义数据
   */
  [key: string]: any;
}

/**
 * 面板中接口详情的返回值
 */
export interface ApiResponse {
  /**
   * api id
   */
  id: number | string;
  /**
   * api path, 标识mock平台的api路径,未必是真实的路径
   */
  path: string;
  /**
   * 真实的api路径,界面展示路径也会用此字段。如果没有,则用path。
   * @example 比如oneapi如果存在某个资源的get和delete restful接口,那么api path会是get/api/* resource/ resourceId和delete/api/resource/resourceId
   * 但真实路径其实是:api/resource/{resourceId}
   * @caution 注意,如果真实路径带有{},插件会自动将其转为regexp,当网页中发送,api/resource/* xxx时,可顺利匹配到规则进行转发
   */
  realPath?: string;
  /**
   * 如果申明了,会以此为规则的regexFilter。优先级 regexFilter > realPath > path
   * @see RE2 syntax
   */
  regexFilter?: string;
  /**
   * api method
   */
  method: ApiMethod;
  /**
   * api名称
   */
  name: string;
  /**
   * api描述
   */
  desc?: string;
  /**
   * api创建者
   */
  creator?: string;
  /**
   * mock地址
   */
  mockUrl: string;
  /**
   * mock数据
   */
  mockData: any;
  /**
   * 原接口地址
   */
  sourceUrl?: string;
  /**
   * 多场景数据
   */
  scenes?: SceneResponse[];
  /**
   * 用户自定义数据
   */
  [key: string]: any;
}

/**
 * 面板中接口分组的返回值
 */
export interface GroupResponse {
  /**
  * 分组id
  */
  id: number | string;
  /**
   * 分组名
   */
  name: string;
  /**
   * api返回
   */
  apis: OverviewApiResponse[];
  /**
   * 用户自定义数据
   */
  [key: string]: any;
}

/**
 * 面板中项目的返回值
 */
export interface ProjectResponse {
  /**
   * 分组返回
   */
  groups: GroupResponse[];
  /**
   * 用户自定义数据
   */
  [key: string]: any;
}

interface RequestParams {
  projectConfig: ProjectConfig;
  projectResponse: ProjectResponse;
  groupResponse: GroupResponse;
  overviewApiResponse: OverviewApiResponse;
  apiResponse: ApiResponse;
  sceneResponse: SceneResponse;
}

/**
 * 开发者自定义函数上下文。
 * 可通过fetchJson,拉取或向服务端传递json数据,不受同源限制,拥有高权限
 */
export interface Context {
  /**
   * 跟fetch一致,会自动res.json()
   */
  fetchJSON: <Response = any>(...args: Parameters<typeof fetch>) => Promise<Response>;
  /**
   * 当前页面信息
   */
  tabInfo: {
    /**
     * 页面的url
     */
    url: string;
    /**
     * 页面的ua
     */
    userAgent: string;
  }
}

/**
 * 获取project详情的请求,由开发者自定义
 */
export type GetProjectRequestParams = Pick<RequestParams, 'projectConfig'>;
export type GetProjectRequest<
  P extends GetProjectRequestParams = GetProjectRequestParams,
  R extends ProjectResponse = ProjectResponse
  > = (
    params: P,
    context: Context
  ) => Promise<R>;

/**
 * 获取api详情的请求,由开发者自定义
 */
export type GetApiRequestParams = Pick<RequestParams, 'projectConfig' | 'projectResponse' | 'overviewApiResponse'>;
export type GetApiRequest<
  P extends GetApiRequestParams = GetApiRequestParams,
  R extends ApiResponse = ApiResponse
  > = (params: P, context: Context) => Promise<R>;

/**
* 移动api到其它分组的请求,由开发者自定义
*/
export type MoveApiRequestParams = Pick<RequestParams, 'projectConfig' | 'projectResponse' | 'overviewApiResponse'> & {
  'groupPayload': GroupResponse
};
export type MoveApiRequest<
  P extends MoveApiRequestParams = MoveApiRequestParams,
  R = any
  > = (
    params: P,
    context: Context
  ) => Promise<R>;

/**
 * 更改api场景数据,由开发者自定义
 */
export type UpdateApiSceneRequestParams = Pick<RequestParams, 'projectConfig' | 'projectResponse' | 'apiResponse' | 'sceneResponse'>;
export type UpdateApiSceneRequest<
  P extends UpdateApiSceneRequestParams = UpdateApiSceneRequestParams,
  R = any
  > = (
    params: P,
    context: Context
  ) => Promise<R>;

/**
 * 添加api场景,由开发者自定义
 */
export type AddApiSceneRequestParams = Pick<RequestParams, 'projectConfig' | 'projectResponse' | 'apiResponse'> & {
  addScenePayload: AddScenePayload
};
export type AddApiSceneRequest<
  P extends AddApiSceneRequestParams = AddApiSceneRequestParams,
  R extends AddSceneResponse = AddSceneResponse,
  > = (
    params: P,
    context: Context
  ) => Promise<R>;

/**
 * 删除api场景,由开发者自定义
 */
export type DeleteApiSceneRequestParams = Pick<RequestParams, 'projectConfig' | 'projectResponse' | 'apiResponse' | 'sceneResponse'>;
export type DeleteApiSceneRequest<
  P extends DeleteApiSceneRequestParams = DeleteApiSceneRequestParams,
  R = any
  > = (
    params: P,
    context: Context
  ) => Promise<R>;

/**
 * 增加分组,由开发者自定义
 */
export type AddGroupRequestParams = Pick<RequestParams, 'projectConfig' | 'projectResponse'> & {
  addGroupPayload: {
    name: string;
  }
};
export type AddGroupRequest<
  P extends AddGroupRequestParams = AddGroupRequestParams,
  R = any
  > = (
    params: P,
    context: Context
  ) => Promise<R>;

/**
 * 删除分组,由开发者自定义
 */
 export type DeleteGroupRequestParams = Pick<RequestParams, 'projectConfig' | 'projectResponse' | 'groupResponse'>;
 export type DeleteGroupRequest<
   P extends DeleteGroupRequestParams = DeleteGroupRequestParams,
   R = any
   > = (
     params: P,
     context: Context
   ) => Promise<R>;

/**
 * 自定义脚本接口
 */
export interface UserScript {
  getProject: GetProjectRequest;
  getApi: GetApiRequest;
  moveApi?: MoveApiRequest;
  updateApiScene?: UpdateApiSceneRequest;
  addApiScene?: AddApiSceneRequest;
  deleteApiScene?: DeleteApiSceneRequest;
  addGroup?: AddGroupRequest;
  deleteGroup?: DeleteGroupRequest;
}

示例自定义脚本(oneapi)

import {
  ProjectConfig,
  GroupResponse,
  OverviewApiResponse,
  ApiResponse,
  SceneResponse,
  userScript,
  AddSceneResponse,
  GetApiRequestParams,
  AddApiSceneRequestParams,
  UpdateApiSceneRequestParams,
  MoveApiRequestParams,
  DeleteApiSceneRequestParams,
  AddGroupRequestParams,
  DeleteGroupRequestParams
} from 'mock-proxy-kit';

import {
  OneapiOriginalQueryApiResponse,
  OneapiOriginalQueryProjectResponse,
  OneapiOriginalApiOverview
} from './types';

interface RequestMap {
  /**
   * key为api path
   * value为realPath
   */
  [path: string]: string;
}

interface OneapiProjectConfig extends ProjectConfig {
  requestMap?: RequestMap;
}

interface OneapiOverviewApiResponse extends OverviewApiResponse {
}

interface OneapiApiResponse extends ApiResponse {
}

interface OneapiAddSceneResponse extends AddSceneResponse {
}

interface OneapiSceneResponse extends SceneResponse {
}

const oneapiBaseUrl = 'https://oneapi.alibaba-inc.com';

const DEFAULT_SCENE_NAME = 'default';

// 如果描述有mtop,则认为该接口是mtop接口,将描述作为真实路径
function getRealPath(project: OneapiProjectConfig, api: OneapiOriginalApiOverview): string {
  if ((api.description || '').includes('mtop')) return api.description as string;

  return project.requestMap?.[api.apiName] || api.apiName;
}

function isMtopPath(realPath: string) {
  return realPath.startsWith('/mtop') || realPath.startsWith('mtop');
}

function getMockUrl(project: OneapiProjectConfig, api: OneapiOriginalApiOverview, realPath: string, groupName?: string): string {
  if (isMtopPath(realPath)) {
    return `${oneapiBaseUrl}/mock/${project.id}/${api.apiName}${groupName ? `?_tag=${groupName}&wrapper=mtop` : '?wrapper=mtop'}`;
  }

  return `${oneapiBaseUrl}/mock/${project.id}/${api.apiName}${groupName ? `?_tag=${groupName}` : ''}`;
}

function getSourceUrl(project: OneapiProjectConfig, api: OneapiOriginalApiOverview): string {
  return `${oneapiBaseUrl}/eapi/interface-manager?projectCode=${project.id}&apiName=${encodeURIComponent(api.apiName)}&method=${api.method}`;
}

function getApiRegExp(realPath: string) {
  if (isMtopPath(realPath)) {
    return `\\w+/${realPath.replace(/\./g, '\\.')}/\\d\\.\\d\/?`
  }

  return '';
}

/**
 * 获取项目及分组(下面的api)的请求映射
 * @param params
 * @param context
 * @returns
 */
export const getProject: userScript.GetProjectRequest<{ projectConfig: OneapiProjectConfig }> = (params, context) => {
  const { projectConfig } = params;
  return context.fetchJSON<OneapiOriginalQueryProjectResponse>(`${oneapiBaseUrl}/api/oneapi/group/query?projectCode=${projectConfig.id}`).then((res) => {
    if (!res.success) throw new Error('获取项目失败');

    const defaultGroup: GroupResponse = {
      id: 'default',
      name: '默认分组',
      apis: (res.content.apis || []).map(api => {
        const realPath = getRealPath(projectConfig, api);
        const ret: OverviewApiResponse = {
          id: api.id,
          name: api.description || '',
          method: api.method,
          path: api.apiName,
          realPath,
          creator: api.creator?.nickName,
          mockUrl: getMockUrl(projectConfig, api, realPath),
          sourceUrl: getSourceUrl(projectConfig, api),
          regexFilter: getApiRegExp(realPath),
        };
        return ret;
      }),
    }

    const otherGroups: GroupResponse[] = (res.content.catalogs || []).map(group => {
      const ret: GroupResponse = {
        id: group.id,
        name: group.name,
        apis: (group.apis || []).map(api => {
          const realPath = getRealPath(projectConfig, api);
          const ret: OverviewApiResponse = {
            id: api.id,
            name: api.description || '',
            method: api.method,
            path: api.apiName,
            realPath,
            creator: api.creator?.nickName,
            mockUrl: getMockUrl(projectConfig, api, realPath),
            sourceUrl: getSourceUrl(projectConfig, api),
          };
          return ret;
        }),
      };
      return ret;
    });

    const groups = [defaultGroup, ...otherGroups]

    return {
      groups
    }
  });
}

/**
 * 获取api详情的请求映射
 * @param params
 * @param context
 * @returns
 */
export const getApi: userScript.GetApiRequest<GetApiRequestParams & {
  projectConfig: OneapiProjectConfig,
  overviewApiResponse: OneapiOverviewApiResponse
}, OneapiApiResponse> = (params, context) => {
  const { projectConfig, overviewApiResponse, } = params;
  return context.fetchJSON<OneapiOriginalQueryApiResponse>(`${oneapiBaseUrl}/api/info/getApi?projectCode=${projectConfig.id}&apiName=${encodeURIComponent(overviewApiResponse.path)}`).then((res) => {
    if (!res.success) throw new Error('获取接口失败');
    const scenes: SceneResponse[] = [];

    const realPath = getRealPath(projectConfig, res.content);

    Object.entries(res.content.tagResponses).forEach(([groupName, mockData]) => {
      scenes.push({
        // oneapi不允许存在相同的场景名称
        id: groupName,
        name: groupName,
        mockUrl: getMockUrl(
          projectConfig,
          res.content,
          realPath,
          groupName === DEFAULT_SCENE_NAME ? '' : groupName
        ),
        mockData: mockData,
      })
    })

    const ret: ApiResponse = {
      id: res.content.id,
      name: res.content.description || '',
      // desc: res.content.description,
      method: res.content.method,
      path: res.content.apiName,
      realPath,
      regexFilter: getApiRegExp(realPath),
      creator: res.content.creator?.nickName,
      mockUrl: getMockUrl(projectConfig, res.content, realPath),
      sourceUrl: getSourceUrl(projectConfig, res.content),
      mockData: res.content.tagResponses?.default,
      scenes
    };
    return ret;
  });
}

/**
 * 移动api的请求映射
 * @param params
 * @param context
 * @returns
 */
export const moveApi: userScript.MoveApiRequest<
  MoveApiRequestParams & {
    projectConfig: OneapiProjectConfig,
    overviewApiResponse: OneapiOverviewApiResponse,
  },
  {
    success: boolean
  }
> = (params, context) => {
  const {
    projectConfig,
    overviewApiResponse,
    groupPayload,
  } = params;

  const payload: any = {
    id: overviewApiResponse.id,
    apiId: overviewApiResponse.id,
    apiName: overviewApiResponse.path,
    projectCode: projectConfig.id,
    csrf: '',
  };

  if (groupPayload.id !== 'default') {
    payload.pid = groupPayload.id;
  }

  return context.fetchJSON<{
    success: boolean
  }>(`${oneapiBaseUrl}/api/info/update`, {
    method: 'POST',
    body: JSON.stringify(payload),
    headers: {
      'Content-Type': 'application/json'
    }
  }).then((res) => {
    if (!res.success) throw new Error('更新失败');
    return res;
  });
}

/**
 * 添加场景的请求映射
 * @param params
 * @param context
 * @returns
 */
export const addApiScene: userScript.AddApiSceneRequest<
  AddApiSceneRequestParams & {
    projectConfig: OneapiProjectConfig,
    apiResponse: OneapiApiResponse,
  },
  OneapiAddSceneResponse
> = (
  params,
  context
) => {
    const {
      projectConfig,
      apiResponse,
      addScenePayload,
    } = params;

    const payload = {
      apiName: apiResponse.path,
      projectCode: projectConfig.id,
      tagName: addScenePayload.name,
      tagResponse: addScenePayload.mockData,
      csrf: '',
    };

    return context.fetchJSON<{
      success: boolean
      content: string
    }>(`${oneapiBaseUrl}/api/info/createTag`, {
      method: 'POST',
      body: JSON.stringify(payload),
      headers: {
        'Content-Type': 'application/json'
      }
    }).then((res) => {
      if (!res.success) throw new Error('创建失败');
      return {
        id: res.content
      };
    });
  }

/**
 * 更新场景的请求映射
 * @param params
 * @param context
 * @returns
 */
export const updateApiScene: userScript.UpdateApiSceneRequest<
  UpdateApiSceneRequestParams & {
    projectConfig: OneapiProjectConfig,
    apiResponse: OneapiApiResponse,
    sceneResponse: OneapiSceneResponse,
  },
  {
    success: boolean
  }
> = (params, context) => {
  const {
    projectConfig,
    apiResponse,
    sceneResponse,
  } = params;

  const payload = {
    apiName: apiResponse.path,
    projectCode: projectConfig.id,
    tagName: sceneResponse.name,
    tagResponse: sceneResponse.mockData,
    csrf: '',
  };

  return context.fetchJSON<{
    success: boolean
  }>(`${oneapiBaseUrl}/api/info/updateTag`, {
    method: 'POST',
    body: JSON.stringify(payload),
    headers: {
      'Content-Type': 'application/json'
    }
  }).then((res) => {
    if (!res.success) throw new Error('更新失败');
    return res;
  });
}

/**
 * 删除场景的请求映射
 * @param params
 * @param context
 * @returns
 */
export const deleteApiScene: userScript.DeleteApiSceneRequest<
  DeleteApiSceneRequestParams & {
    projectConfig: OneapiProjectConfig,
    apiResponse: OneapiApiResponse,
    sceneResponse: OneapiSceneResponse,
  },
  {
    success: boolean
  }
> = (
  params,
  context,
  ) => {
    const {
      projectConfig,
      apiResponse,
      sceneResponse,
    } = params;

    const payload = {
      apiName: apiResponse.path,
      projectCode: projectConfig.id,
      tagName: sceneResponse.name,
      csrf: '',
    };

    return context.fetchJSON<{
      success: boolean
    }>(`${oneapiBaseUrl}/api/info/deleteTag`, {
      method: 'POST',
      body: JSON.stringify(payload),
      headers: {
        'Content-Type': 'application/json'
      }
    }).then((res) => {
      if (!res.success) throw new Error('删除失败');
      return res;
    });
  }

/**
 * 添加分组的请求映射
 * @param params
 * @param context
 * @returns
 */
export const addGroup: userScript.AddGroupRequest<
  AddGroupRequestParams & {
    projectConfig: OneapiProjectConfig,
  },
  {
    success: boolean
  }
> = (
  params,
  context,
  ) => {
    const {
      projectConfig,
      addGroupPayload
    } = params;

    return context.fetchJSON<{
      success: boolean
    }>(`${oneapiBaseUrl}/api/oneapi/group/create?name=${addGroupPayload.name}&projectCode=${projectConfig.id}`).then((res) => {
      if (!res.success) throw new Error('创建失败');
      return res;
    });
  }

/**
 * 删除分组的请求映射
 * @param params
 * @param context
 * @returns
 */
export const deleteGroup: userScript.DeleteGroupRequest<
  DeleteGroupRequestParams & {
    projectConfig: OneapiProjectConfig,
  },
  {
    success: boolean
  }
> = (
  params,
  context,
  ) => {
    const {
      groupResponse
    } = params;

    if (groupResponse.id === 'default') {
      return Promise.reject(new Error('默认分组无法删除'));
    }

    return context.fetchJSON<{
      success: boolean
    }>(`${oneapiBaseUrl}/api/oneapi/group/delete?id=${groupResponse.id}`).then((res) => {
      if (!res.success) throw new Error('删除失败');
      return res;
    });
  }