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 🙏

© 2026 – Pkg Stats / Ryan Hefner

mgacn-commonsdk

v1.1.5

Published

芒果ACN公共组件库:公共组件、工具方法、网络请求与子系统壳层

Readme

mgacn-commonsdk

芒果 ACN 公共组件库:公共组件、工具方法与网络请求封装。

环境

  • Node.js 24.19.0
nvm use
npm install

安装(接入系统)

npm install mgacn-commonsdk

axiosali-osscropperjs 已作为组件库自带依赖,宿主无需单独安装

以下一般由宿主项目已有(与 SDK 共用):

  • vue
  • element-plus
  • @element-plus/icons-vue

使用

// 顺序不能乱:先 EP 默认,再品牌主题(主题不要被 JS 副作用提前加载)
import 'element-plus/dist/index.css';
import 'mgacn-commonsdk/style.css';
import MgacnCommonSdk from 'mgacn-commonsdk';

app.use(MgacnCommonSdk, {
  tokenKey: 'your_token_key',
  baseURL: import.meta.env.VITE_API_BASE_URL,
  systemId: '', // 可选;子应用也可接口拿到后 setSystemId
});

style.css 已包含门户与子系统共用的主题样式(必须放在 element-plus 样式之后;SDK 的 JS 入口不再副作用引入主题,避免被 Vite CSS 去重后排到 EP 前面):

  • element-variables.scss — 品牌色与 EP 变量
  • common.scss — 通用工具类
  • reset-element.scss — Element Plus 样式重置

子应用从接口获取 systemId 后(接口待定):

import { setSystemId } from 'mgacn-commonsdk';

setSystemId(systemId); // 之后请求头会带 systemId

按钮权限

app.use 时传入权限列表读取函数(通常来自菜单 store 的 permissions),SDK 会注册指令并提供 hasPermission

import { useMenuStore } from '@/store/menuStore';

app.use(MgacnCommonSdk, {
  tokenKey: 'mgacn_token',
  getPermissions: () => useMenuStore().permissions,
});

也可事后设置:

import { setPermissionGetter, hasPermission, checkPermission } from 'mgacn-commonsdk';

setPermissionGetter(() => useMenuStore().permissions);

hasPermission('menu:add');
hasPermission(['menu:add', 'menu:edit']); // 且
hasPermission(['menu:add', 'menu:edit'], 'or'); // 或
checkPermission(['menu:add'], 'menu:add'); // 纯函数,不依赖 getter

模板指令(install 时自动注册):

<el-button v-hasPermission="'menu:add'">新增</el-button>
<el-button v-hasAnyPermission="'menu:add,menu:edit'">编辑类</el-button>
<span v-hasNoPermission="'menu:add'">无新增权限时可见</span>

未传入 getPermissions / setPermissionGetter 时,hasPermission 不拦截(恒为 true)。

子系统壳层(mgacn-commonsdk/admin

门户不要引入;仅各业务子系统使用。仍是同一个 npm 包,通过子路径导出,无需发第二个包。

包含:顶栏、侧栏、布局、user/system/menu store、动态路由、菜单管理、角色管理。

import { bootstrapAdminApp } from 'mgacn-commonsdk/admin';
import 'element-plus/dist/index.css';
import 'mgacn-commonsdk/style.css';
import App from './App.vue';
import Homepage from './views/homepage/index.vue';

bootstrapAdminApp({
  App,
  homepageComponent: Homepage,
  deployKey: 'account_cms', // 部署目录名,匹配当前系统
  appTitle: '芒果ACN账号库',
  portalUrl: import.meta.env.VITE_PORTAL_URL, // 如 /portal_cms#
  ossDomain: import.meta.env.VITE_OSS_URL,
  viewModules: import.meta.glob('./views/**/*.vue'),
  isDev: import.meta.env.DEV,
  setupIcons: (app) => { /* 注册图标 */ },
});

菜单 pathComponentsystem/menusystem/role 时走壳内页面;其它业务页由宿主 viewModules 解析。

本地 monorepo 可在 Vite 中 alias:

'mgacn-commonsdk/admin' → '../mgacn-commonsdk/src/admin/index.js'

peer 依赖需宿主已安装:vuevue-routerpiniaelement-plus@element-plus/icons-vue

网络请求(宿主使用 .GET / .POST)

app.use 后,页面里这样调:

// Options API
this.$request.GET('/yhAdmin/user/queryUserById', { params: { id: 1 } });
this.$request.POST('/yhAdmin/user/edit', { name: '张三' });
this.$request.DELETE(`/yhAdmin/portal/deleteManager/${userId}`);

// Composition API / <script setup>
import { getRequest } from 'mgacn-commonsdk';

const request = getRequest();
await request.GET('/path', { params: { page: 1 } });
await request.POST('/path', { foo: 1 });

也可继续用封装好的 api:

import { getApi } from 'mgacn-commonsdk';
await getApi().getUserInfo(userId);
await getApi().listUserForSelect({ realName: '张三', status: 0 });
await getApi().logout(username);
await getApi().getOssSts(dir);

工具方法

统一通过 utils 调用,不单独导出各方法:

import { utils } from 'mgacn-commonsdk';

utils.formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss');
utils.formatNum(134343432); // '134,343,432'
utils.debounce(fn, 300);
utils.throttle(fn, 300);
utils.uniqueId('row');

utils.formatDate(date, format?) 日期格式化

第二个参数是格式模板,连接符可自定义(- / . / 中文「年月日」等):

| 占位符 | 含义 | | ---------- | ------------------- | | YYYY | 年 | | MM / M | 月(补零 / 不补零) | | DD / D | 日(补零 / 不补零) | | HH / H | 时 | | mm / m | 分 | | ss / s | 秒 |

utils.formatDate(date); // 2026-08-07 14:30:00(默认)
utils.formatDate(date, 'YYYY-MM-DD'); // 2026-08-07
utils.formatDate(date, 'YYYY.MM.DD'); // 2026.08.07
utils.formatDate(date, 'YYYY/MM/DD'); // 2026/08/07
utils.formatDate(date, 'YYYY年MM月DD日'); // 2026年08月07日
utils.formatDate(date, 'YYYY年M月D日'); // 2026年8月7日
utils.formatDate(date, 'YYYY年MM月DD日 HH:mm'); // 2026年08月07日 14:30

非法日期返回空字符串 ''

Safari 兼容: 不要依赖 new Date('2026-08-07 14:30:00')(Safari 会 Invalid)。formatDate / parseDate 已对常见字符串做手动拆解,以下均可:

utils.formatDate('2026-08-07 14:30:00');
utils.formatDate('2026/08/07 14:30:00');
utils.formatDate('2026年08月07日');
utils.parseDate('2026-08-07'); // 返回 Date,失败为 null

utils.getRecentMonthRange() 最近一个月起止时间

返回最近 30 天区间,开始为 00:00:00,结束为当天 23:59:59

const { start, end } = utils.getRecentMonthRange();
// start: '2026-07-08 00:00:00'
// end:   '2026-08-07 23:59:59'

utils.cleanParams(params) 清理请求参数

去掉 undefined / null / '' / 空数组,避免空条件传给后端:

utils.cleanParams({
  name: '张三',
  projectIds: [],
  type: undefined,
});
// => { name: '张三' }

utils.formatNum(val, digits?) 数字千分位

用于 PV / UV、统计数量等展示。最多保留 digits 位小数(默认 2),并去掉小数末尾多余的 0。空值保持原值,不转成 0。

utils.formatNum(134343432); // '134,343,432'
utils.formatNum(1234.5); // '1,234.5'
utils.formatNum(1234.56); // '1,234.56'
utils.formatNum(1000, 0); // '1,000'
utils.formatNum(null); // null
utils.formatNum('abc'); // 'abc'

utils.debounce(fn, delay?) 防抖

连续触发时只执行最后一次,适合搜索输入、窗口 resize 等。delay 默认 300(毫秒)。

<script setup>
// ✅ 在 setup 顶层创建一次,后续复用同一个函数
const handleInput = utils.debounce(val => {
  getRequest().POST('/xxx/search', { keyword: val });
}, 400);
</script>

<template>
  <el-input @input="handleInput" />
</template>

Options API:

export default {
  created() {
    this.handleInput = utils.debounce(val => {
      // ...
    }, 400);
  },
};

注意事项:

  • debounce 返回的是新函数,必须存下来复用(赋给常量 / this.xxx),不要在每次事件回调里再包一层 utils.debounce(...),否则每次都是新实例,防抖会失效。
  • 错误示例:
// ❌ 每次输入都新建 debounce,防不住
function handleInput(val) {
  utils.debounce(() => {
    getRequest().POST('/xxx/search', { keyword: val });
  }, 400)();
}
  • 组件卸载后若仍可能触发(少见),注意清理定时器;当前实现未暴露 cancel,一般页面内搜索场景无需额外处理。

utils.throttle(fn, delay?) 节流

在固定时间间隔内最多执行一次,适合滚动监听、频繁点击、拖拽等。delay 默认 300(毫秒)。

和防抖的区别:

| | debounce 防抖 | throttle 节流 | | -------- | ---------------- | -------------------- | | 行为 | 停下来之后才执行 | 每隔一段时间执行一次 | | 典型场景 | 搜索框输入 | 页面滚动、按钮连点 |

<script setup>
// ✅ 同样要在 setup 顶层创建一次并复用
const onScroll = utils.throttle(() => {
  console.log('滚动位置', window.scrollY);
}, 200);

onMounted(() => window.addEventListener('scroll', onScroll));
onBeforeUnmount(() => window.removeEventListener('scroll', onScroll));
</script>

按钮防连点:

接入系统(如 portal_cms)若已有 v-noMoreClick 自定义指令,按钮场景优先用指令,不必再用 utils.throttle

<el-button type="primary" v-noMoreClick @click="onSubmit">提交</el-button>

utils.throttle 更适合滚动、resize、拖拽等非点击场景。

注意事项:

  • debounce 一样,返回的是新函数,必须存下来复用,不要在事件回调里每次 utils.throttle(...)
  • 当前实现是「时间戳版」:间隔内再次触发会直接忽略,不会在间隔结束后补跑最后一次
  • 第一次调用会立刻执行(满足间隔条件时)。

手动创建

import { createRequest, createApi } from 'mgacn-commonsdk';

const request = createRequest({
  tokenKey: 'your_token_key',
  baseURL: '/api',
  systemId: '', // 可选;也可事后 setSystemId
});
const api = createApi(request);

图片上传

<template>
  <MgImageUpload
    v-model="urls"
    v-model:uploading="uploading"
    fold-name="portal_cms"
    :file-num="3"
    :max-file-size="5"
  />
  <el-button :disabled="uploading" @click="submit">提交</el-button>
</template>

<script>
export default {
  data() {
    return {
      urls: [], // 统一为 string[],无图时为 []
      uploading: false,
    };
  },
  methods: {
    submit() {
      if (this.uploading) {
        this.$message.warning('图片上传中,请稍候');
        return;
      }
      // this.urls => ['https://...', 'https://...']
    },
  },
};
</script>

也可通过 ref 判断:

if (this.$refs.upload.isUploading()) return;

app.use(MgacnCommonSdk, { tokenKey, baseURL }) 后,组件内部通过项目 api.getOssSts 获取凭证。

搜索栏 MgSearchForm

配置驱动,支持字段类型:

| type | 说明 | | ------------------------- | ------------------ | | input / text | 文本输入 | | select | 下拉选择 | | remoteSelect / remote | 远程搜索下拉 | | numberRange | 数字最小~最大区间 | | date | 日期 | | dateRange | 日期区间 | | slot | 自定义插槽 |

<MgSearchForm
  v-model="query"
  :fields="[
    { type: 'input', prop: 'name', label: '姓名' },
    { type: 'select', prop: 'status', label: '状态', options: [{ label: '有效', value: 1 }] },
    { type: 'remoteSelect', prop: 'deptId', label: '部门', remoteMethod: searchDept },
    { type: 'numberRange', prop: 'age', label: '年龄', startProp: 'ageMin', endProp: 'ageMax' },
    { type: 'date', prop: 'birthday', label: '生日' },
    {
      type: 'dateRange',
      prop: 'createTime',
      label: '创建时间',
      startProp: 'start',
      endProp: 'end',
    },
  ]"
  @search="onSearch"
  @reset="onReset"
/>

表格 MgDataTable

列配置 + 具名插槽自定义单元格:

<MgDataTable
  v-model:page="page"
  v-model:page-size="pageSize"
  :data="list"
  :columns="[
    { prop: 'status', label: '状态', slot: 'status' },
    { prop: 'name', label: '姓名', minWidth: 120 },
  ]"
  :total="total"
  @page-change="load"
  @size-change="load"
>
  <template #status="{ row }">
    <span>{{ row.status === 1 ? '有效' : '锁定' }}</span>
  </template>
</MgDataTable>

本地开发 / 发布

npm run dev
npm run build
npm publish