mgacn-commonsdk
v1.1.5
Published
芒果ACN公共组件库:公共组件、工具方法、网络请求与子系统壳层
Maintainers
Readme
mgacn-commonsdk
芒果 ACN 公共组件库:公共组件、工具方法与网络请求封装。
环境
- Node.js
24.19.0
nvm use
npm install安装(接入系统)
npm install mgacn-commonsdkaxios、ali-oss、cropperjs 已作为组件库自带依赖,宿主无需单独安装。
以下一般由宿主项目已有(与 SDK 共用):
vueelement-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) => { /* 注册图标 */ },
});菜单 pathComponent 为 system/menu、system/role 时走壳内页面;其它业务页由宿主 viewModules 解析。
本地 monorepo 可在 Vite 中 alias:
'mgacn-commonsdk/admin' → '../mgacn-commonsdk/src/admin/index.js'peer 依赖需宿主已安装:vue、vue-router、pinia、element-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,失败为 nullutils.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