mvframe
v1.1.3
Published
**English** | [简体中文](README.cn.md)
Readme
MVFrame
English | 简体中文
A Vue 3 + Vite oriented admin-shell toolkit: Frame layout, common business components, global utilities & directives, Pinia wiring with tabs/router, composable modules, and SCSS utilities & tokens. Priorities favor SaaS-style admin apps over a fully pluggable, zero-opinion utility library.
Stack & dependencies
| Kind | Notes |
|------|------|
| Runtime | vue ^3.3, vue-router ^4.6, pinia ^3 |
| Demo / dev | element-plus (demo forms, etc.), vite ^6, sass-embedded |
| Build | Library output is ESM; vue / pinia / vue-router are externals and must be supplied by the host app |
package.json exports include:
"."→dist/index.js(main plugin; default export is theinstallfunction)"./composition"→dist/composition.js(e.g.import { useMap } from 'mvframe/composition')"./store","./directive","./util"→ matching chunks underdist"./notify"→dist/notify.js(browser helper for the localmvframe-notifyservice)"./style/mixin"→src/style/chip/mixin.scss(shared breakpoint and helper mixins for host SCSS)"./style"→dist/css/style.css(full toolkit CSS fromsrc/style/index.scssentry)"./style/cpt"→dist/css/cpt.css(component-extracted CSS; usuallyimport 'mvframe/style'is enough)
Repository layout
src/ — library source
| Path | Role |
|------|------|
| src/index.js | Install entry: registers config → global components (cpt) → util → directive → store → router |
| src/config/ | Merges defaults with app options → globalThis.$config (chip/base.js: name, copyright, version, etc.) |
| src/cpt/index.js | Auto-registers every component/*/index.vue via import.meta.glob; component name equals the folder name (PascalCase, e.g. BtnGroup) |
| src/component/ | Implementations; root class Mvc + name (see .cursor/rules/component-hierarchy.mdc) |
| src/util/index.js | Global helpers on app.config.globalProperties & globalThis ($getLang, $fa, $copy, $deepClone, …) |
| src/directive/index.js | Directives: v-copy (optional .dblclick), v-focus |
| src/store/ | createPinia + built-in init / tab / rmenu; optional storeChips; provide("store", store) |
| src/router/ | createWebHistory router; optional guard; built-in guardThis (meta.admin, afterEach + tabs, document.title) |
| src/composition/ | Import on demand; exports lang, dom, data, media, … (Vite alias @cps → this folder) |
| src/style/ | index.scss aggregates chip/*.scss (tokens, spacing, typography, layout, overrides) |
Globally registered components (folder name = registration name):
BtnGroup, Form, Frame, Icon, Input, Page, Select, SelectV2, Table, Tabs, Textarea, VTable
VTable is registered with defineAsyncComponent by default so the VisActor chunk is loaded only when <VTable> is rendered.
chip/ subfolders hold internal pieces (e.g. Frame/chip/Menu.vue); they are not registered as root components.
demo/ — local preview app
With NODE_ENV=development, Vite root is ./demo (see vite.config.js) to exercise Frame, routing, and components.
| File | Role |
|------|------|
| demo/index.html | HTML shell |
| demo/main.js | Boot: ElementPlus + ../src/index.js + ../src/style/index.scss + routes |
| demo/App.vue | Frame layout, menu: { iconClass: 'imicon', routes }, slots logo / logomini |
| demo/routes.js | Sample routes: / → Overview/Home, /a → A/Home; nested children for the sidebar |
| demo/views/Overview/Home.vue | Page + Tabs switching FormPanel / IconPanel |
| demo/views/Overview/Home/FormPanel.vue | Form demo |
| demo/views/Overview/Home/IconPanel.vue, iconfontAntNames.js | Icon demo & Ant icon name list |
| demo/auto-imports.d.ts | Types from unplugin-auto-import (Vue / vue-router) |
Local dev:
yarn install
yarn devDev server port 8088 (see vite.config.js).
Path aliases
| Alias | Points to |
|------|-----------|
| @ | src/ |
| @cps | src/composition/ |
| @scss | assets/scss (if present) |
Demo routes use paths like import("/views/Overview/Home.vue") resolved from the demo root.
Using in a host application
1. Install peer-style deps
Align versions of vue, vue-router, pinia. For Form / Table tied to Element Plus, add element-plus and app.use(ElementPlus).
2. Register MVFrame
import { createApp } from "vue";
import mvframe from "mvframe";
// Styles: use package path when depending on source / monorepo; published `dist`-only—see “Styles”
import "mvframe/src/style/index.scss";
const app = createApp(App);
app.use(mvframe, {
components: {
async: ["VTable"],
},
vueRouter: {
routes: yourRoutes,
// optional: custom beforeEach, etc.
// guard: (router) => { /* ... */ },
// useAdmin: true,
// adminPermission: () => true,
// noaccess: (next) => next(false),
},
pinia: {
useTab: true, // multi-tab + localStorage restore for `tabs` / `ctab`
// storeChips: import.meta.glob("./pinia/chip/*.js", { eager: true }),
},
config: {
// merged with src/config/chip/base.js → globalThis.$config
iconfont: {
url: "//at.alicdn.com/t/c/your_font.js",
prefix: "ant",
},
},
});components.async controls async global component registration. VTable is async by default to keep @visactor/vtable out of the initial synchronous component chain; keeping it explicit in app config documents that choice.
Frame injects a <script> for icon fonts from globalThis.$config.iconfont.url on mount (implementation removes url afterward to avoid duplicate injection).
3. Globals at a glance
globalThis.$config— merged app configglobalThis.$router— router instance assigned by this library after installinject("store")— store factory (store.tab(),store.rmenu(), custom chips)- Global components — use
Frame,Page,Table, … without local registration - Directives —
v-copy,v-focus - Composition —
import { ... } from "mvframe/composition"or your@cpsalias
Full helper list: .cursor/rules/util.mdc and src/util/index.js.
4. Styles
- Utility classes & tokens:
.cursor/rules/style-system.mdc(--color-*, spacing, typography, layout). vite.config.jsadditionalDatainjectssrc/style/chip/mixin.scss; mirror this in apps that compile the same SCSS tree.- Breakpoints are centralized in
mixin.scss:xs / sm / md / lg / xl / xxl. Host SCSS can@use "mvframe/style/mixin" as *;and use@include media-up(md),media-down(md),media-only(md), ormedia-between(sm, lg). mvframe/stylenow ships responsive utility classes in the shape{breakpoint}-{up|down|only}-{utility}, for examplemd-down-hide,lg-up-flexMode, andxs-only-block.- Grid containers also support breakpoint column-count classes such as
class="grid col2 md2 lg3 sm1": default 2 columns,sm1 column,md2 columns,lg3 columns. - Published packages can use
import 'mvframe/style'(dist/css/style.css). For SCSS overrides, still linksrc/style/index.scssfrom Git/monorepo when needed.
Host SCSS example:
@use "mvframe/style/mixin" as *;
.BillingPanel {
padding: 1.5rem;
@include media-down(sm) {
padding: 1rem;
}
}Building the library
yarn buildProduction uses library mode with entry src/index.js and obfuscation-related plugins (see vite.config.js). NODE_ENV=development points Vite at the demo app; to verify a real build locally, run production vite build (or your script that sets production env).
DingTalk Notify Service
MVFrame ships a local Node service for DingTalk robot messages only:
yarn notify
yarn exec mvframe-notify
yarn d
yarn exec mvframe-dThe service reads .env.mvframe-notify from the host root:
MVFRAME_NOTIFY_PORT=3300
MVFRAME_NOTIFY_HOST=127.0.0.1
MVFRAME_NOTIFY_TOKEN=
DINGTALK_WEBHOOK="https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxx"
DINGTALK_SECRET="SECxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"Browser code should only configure the local endpoint/token:
app.use(mvframe, {
config: {
notify: {
enabled: true,
endpoint: import.meta.env.VITE_MVFRAME_NOTIFY_ENDPOINT || "http://127.0.0.1:3300",
token: import.meta.env.VITE_MVFRAME_NOTIFY_TOKEN || "",
},
},
});Send messages with globalThis.$notify.send("message") or import { notify } from "mvframe/notify". Keep DingTalk webhook/secret out of browser-bundled files.
Design notes
- Layout, guards, tabs, and menu behavior are opinionated; heavy customization may mean forking
src/router,src/store/tab.js, or wrapping from the app. - CSS naming & units: root
Mvc*, children camelCase; prefer rem for sizes other than 1px/2px (16px root)—seestyle-system.mdc. - Use
globalThisorstore/piniaexports outsidesetup(e.g. guards), as insrc/router/chip/guard.js.
Form Labels
Input, Textarea, Select, and SelectV2 support a shared label API:
<Input v-model="form.name" label="Name" />
<Input v-model="form.name" label="Material Name" material-label />
<Textarea v-model="form.remark" label="Remark" />
<Textarea v-model="form.remark" label="Material Remark" material-label />
<Select v-model="form.type" :options="options" label="Type" />
<Select v-model="form.type" :options="options" label="Material Type" material-label />Rules:
labelonly: renders a normal top labellabel+material-label: renders a Material-style floating label- no
label: keeps the default control appearance
The floating label shell is implemented centrally in src/style/chip/element.scss. When you want a Material-style form field, prefer the built-in label + material-label API instead of recreating the same pattern locally.
CLI: project skeleton
From an empty folder or existing Vite root, generates src/views, src/component, src/api, src/assets/img, src/assets/style, src/router, src/pinia/chip, src/config, plus starter main.js / App.vue with app.use(mvframe, …) (routes, Pinia storeChips, config, and components.async: ["VTable"]). It also writes/updates host-project AGENTS.md with MVFrame Codex rules, including a requirement that AI send a completion message with yarn exec mvframe-notify --once --message "..." after each finished development task, and copies /.cursor/rules/*.mdc from the package (same as this repo’s Cursor rules). Adds index.html and vite.config.js only if missing (use --force to overwrite). Merges scripts / dependencies / devDependencies into package.json (existing values win for shared keys), initializes .env.mvframe-notify.example / .env.local.example and config.notify, then runs yarn install; use --no-package-json to skip package changes and install. Then run yarn dev or explicitly run yarn exec mvframe-d to start Vite with the notify service.
cd /path/to/your-app
node /path/to/mvframe/scripts/scaffold-app.js
node /path/to/mvframe/scripts/scaffold-app.js --force
node /path/to/mvframe/scripts/scaffold-app.js -f
# Skip package.json (only scaffold sources + Vite config):
node /path/to/mvframe/scripts/scaffold-app.js --no-package-json
node /path/to/mvframe/scripts/scaffold-app.js -n
yarn exec mvframe-init-app
# or: npx mvframe-init-appSee MVFRAME-SCAFFOLD.md in the target project. Set MVFRAME_SCAFFOLD_TARGET to point at the project root.
Codex Rules (AGENTS.md)
Codex does not automatically read Cursor .cursor/rules/*.mdc. For host projects, install the MVFrame AGENTS.md section so Codex prefers MVFrame global components, global helpers, and style utilities before writing local replacements:
cd /path/to/your-app
yarn exec mvframe-init-rules
# or: npx mvframe-init-rules
# Codex rules only
yarn exec mvframe-install-codex-rules
# or: npx mvframe-install-codex-rules
# Full checkout / monorepo path to mvframe
node /path/to/mvframe/scripts/init-rules.js
node /path/to/mvframe/scripts/init-rules.js /path/to/your-app
# Codex rules only
node /path/to/mvframe/scripts/install-codex-agents.js
node /path/to/mvframe/scripts/install-codex-agents.js /path/to/your-appThe scaffold command already runs this step. The generated block is bounded by MVFRAME-CODEX-RULES markers, so rerunning the command updates only that block and preserves other AGENTS.md content.
Cursor Rules (.cursor/rules)
The unified mvframe-init-rules command also adds the package’s *.mdc files into the target project’s /.cursor/rules/ (component-hierarchy, script-setup, style-system, views, router, global-components, data, util).
If the host project already has a same-named rule file, the host file is kept and only missing rules are added.
Install Cursor rules only:
cd /path/to/your-app
yarn exec mvframe-install-cursor-rules
# or: npx mvframe-install-cursor-rules
# Full checkout / monorepo path to mvframe
node /path/to/mvframe/scripts/install-cursor-rules.js
node /path/to/mvframe/scripts/install-cursor-rules.js /path/to/your-appCursor Skill (app scaffold)
This repo ships .cursor/skills/mvframe-app-init. Copy it into your app’s .cursor/skills so Cursor can pick up MVFrame integration conventions.
From your app root, run either:
# Full checkout / monorepo path to mvframe
node /path/to/mvframe/scripts/install-cursor-skill.js
node /path/to/mvframe/scripts/install-cursor-skill.js /path/to/your-app
# After installing the npm package (the tarball must include `scripts/` and `.cursor/skills`—see `package.json` `files`)
cd /path/to/your-app
yarn exec mvframe-install-cursor-skill
# or: npx mvframe-install-cursor-skillDefault target is ./.cursor/skills/mvframe-app-init under the current working directory. Override with MVFRAME_CURSOR_SKILL_OUT=/path/to/your-app.
Changelog
- Added
mvframe-notify, a DingTalk-only local Node notification service. - Added
mvframe/notifyand global$notifyfor browser calls throughconfig.notify. - Added framework command
yarn d/mvframe-dto runyarn devwith the notify service. mvframe-init-appnow initializesconfig.notify,.env.mvframe-notify/.env.mvframe-notify.examplewith the MVFrame test DingTalk robot, and.env.local.examplewithout forcing hostdscripts.VTablenow uses async global registration by default;mvframe-init-appwritescomponents.async: ["VTable"]inmain.js.
License
ISC (see package.json).
