This commit is contained in:
张成
2026-03-25 11:28:45 +08:00
parent c3623d4a95
commit b13fabb370
3 changed files with 452 additions and 25 deletions

417
README.md
View File

@@ -344,28 +344,421 @@ await this.$store.dispatch('app/getSysTitle', {
--- ---
## 11. 内置页面与组件`pages` / `components` ## 11. 内置页面(`pages`
- **页面**`LoginPage``Page401``Page404``Page500` 及多套 `system/*` 管理页(见 `src/views/index.js` 导出)。 - **页面**`LoginPage``Page401``Page404``Page500``HomePage` 及多套 `system/*` 管理页(见 `src/views/index.js`)。
- **布局**`Main``ParentView` - 业务中也可通过 `AdminFramework.pages` 取到上述导出
- **通用组件**`TreeGrid``Tables``editModal``UploadSingle` / `UploadMultiple``Editor` 等(见 `src/components/index.js`)。
二次封装时可:
```javascript
const { TreeGrid, Main } = AdminFramework.components
```
--- ---
## 12. 调试 ## 12. 组件使用说明(`src/components`
`createApp` 执行时会对下列组件**全局注册**(见 `src/components/index.js``registerGlobalComponents`),在任意 `.vue` 模板中可直接写 **PascalCase 或 kebab-case** 标签名(如 `<Tables>` / `<tree-grid>`)。也可按需:
```javascript
import { Tables, TreeGrid, editModal } from 'admin-framework'
// 或在源码工程中import Tables from '@/components/tables'
const { TreeGrid, Main } = AdminFramework.components
```
以下按功能分类;**Props / 事件以源码为准**,此处列出常用项。
### 12.1 布局与路由壳
| 组件 | 说明 |
|------|------|
| **Main** | 后台主布局:左侧 `SideMenu`、顶栏 `HeaderBar`、用户信息、`Terminal`、主内容区 `<router-view />`、内嵌 `LoadFlower`、返回顶部。无业务 Props标题/Logo 来自 Vuex `app/sysFormModel`,菜单来自 `user/menuList`。 |
| **ParentView** | 仅渲染一层 `<router-view />`,用于菜单多级嵌套时的中间层。 |
```vue
<!-- 动态路由中由框架注册一般不在页面手写 Main -->
```
#### Main 子模块说明(`src/components/main/components`
以下组件**默认未在 `registerGlobalComponents` 中注册**,由 **Main** 内部引用;若要在自定义布局中复用,需从路径手动 `import`(示例路径以源码工程 `@/components/...` 为准)。
**组合关系(`main.vue`**`Layout` 左侧为 `SideMenu` + 品牌区;右侧顶部 `HeaderBar`,其默认插槽内为 `Terminal``User`;内容区为 `<router-view />``LoadFlower``ABackTop`
---
**1. `SideMenu``side-menu/side-menu.vue`**
侧栏菜单,基于 iView `Menu`,支持折叠态下的 `CollapsedMenu` 下拉。
| Props | 类型 | 说明 |
|-------|------|------|
| `menuList` | `Array` | 菜单树,项需含 `name``meta`(如 `icon``title`)、子节点 `children` 等,与动态路由菜单结构一致 |
| `collapsed` | `Boolean` | 是否折叠(仅图标 / 下拉) |
| `theme` | `String` | 菜单主题,默认 `light`Main 中传入 `dark` |
| `activeName` | `String` | 当前激活菜单 `name`,一般传 `$route.name` |
| `openNames` | `Array` | 初始展开的 Submenu 名称列表 |
| `rootIconSize` / `iconSize` | `Number` | 折叠/展开时图标大小,默认 `20` / `16` |
| 插槽 | 说明 |
|------|------|
| 默认 | 菜单上方的额外区域(可选) |
| 事件 | 说明 |
|------|------|
| `on-select` | 选中菜单项时,参数为路由 `name` 字符串(或外链特殊格式 `isTurnByHref_xxx`,由 Main 中 `turnToPage` 处理) |
```vue
<SideMenu
theme="dark"
:menu-list="menuList"
:collapsed="collapsed"
:active-name="$route.name"
@on-select="handleMenuSelect"
/>
```
**2. `SideMenuItem``side-menu/side-menu-item.vue`**
`SideMenu` 内部递归子组件:渲染 `Submenu` 标题与一层子 `menu-item`。依赖 mixin 提供的 `parentItem``children` 等逻辑,**一般不单独使用**。
**3. `CollapsedMenu``side-menu/collapsed-menu.vue`**
侧栏折叠时的下拉菜单(`Dropdown` 嵌套)。由 `SideMenu` 使用。
| Props | 说明 |
|-------|------|
| `parentItem` | 当前菜单节点 |
| `hideTitle` | 为 true 时仅图标(侧栏窄条场景) |
| `rootIconSize` / `iconSize` | 图标尺寸 |
| `theme` | 与侧栏主题一致,用于图标颜色 |
| 事件 | `on-click` — 点击子项时传出菜单 `name` |
---
**4. `HeaderBar``header-bar/header-bar.vue`**
顶栏:左侧 **折叠开关** + **面包屑**,右侧 **默认插槽**Main 里放终端图标、用户区等)。
| Props | 说明 |
|-------|------|
| `collapsed` | 当前侧栏是否折叠,传给 `SiderTrigger` |
| 插槽 | 说明 |
|------|------|
| 默认 | 顶栏右侧自定义内容(如 `Terminal``User` |
| 事件 | 说明 |
|------|------|
| `on-coll-change` | 折叠按钮切换,`payload` 为**新的** `collapsed` 布尔值(与 `SiderTrigger``on-change` 一致) |
面包屑数据来自 **`$route.matched`**(去掉第一项),每项包含 `name``path``meta`;展示标题用 `tools.showTitle`
```vue
<HeaderBar :collapsed="collapsed" @on-coll-change="collapsed = $event">
<Terminal /><User :userName="name" :user-avator="avatar" />
</HeaderBar>
```
**5. `SiderTrigger``header-bar/sider-trigger/sider-trigger.vue`**
折叠侧栏的图标按钮。
| Props | `collapsed``icon`(默认 `navicon-round`Main 里为 `md-menu`)、`size`(默认 `26` |
| 事件 | `on-change` — 参数为切换后的 `collapsed` 状态(布尔) |
**6. `customBreadCrumb``header-bar/custom-bread-crumb/custom-bread-crumb.vue`**
自定义面包屑(`Breadcrumb`)。
| Props | `list` `{ name, path, meta, to?, icon? }[]` )、`fontSize`(默认 `14`)、`showIcon` |
| 说明 | 文案通过 `showTitle(item, this)` 解析,支持 `meta.title` 等 |
---
**7. `User``user/user.vue`**
右上角用户名 + 头像下拉,**退出登录**走 Vuex `user/handleLogOut`
| Props | `userName``userAvator`(字符串 URL也可传对象未用字符串时显示内置默认图 |
| 说明 | 内置 `message` 方法曾跳转 `message_page`,可按需扩展菜单项 |
**8. Terminal`terminal/index.vue` + `terminal/terminal.vue`**
入口为一颗 **iView `Icon`**,点击后用 **`AsyncModal`** 弹层展示内层终端视图。当前实现中日志区域为占位文案(**终端功能暂未启用**),按钮「重新加载 / 清空」会提示未启用。
复用时需保证工程内已注册 **`AsyncModal`**。`index.vue``ref="asyncModal"` 调用 `show()` 打开弹窗。
**9. `Language``language/language.vue`**
语言下拉(`zh-CN` / `zh-TW` / `en-US`)。需在工程中配置 **`vue-i18n`**`this.$i18n`)方可切换文案。
| Props | `lang` 当前语言代码 |
| 事件 | `on-lang-change` — 选中的语言 key |
> `main.vue` 当前模板未挂载 `Language`,若在自定义 Main 中需要多语言顶栏,可自行加入。
**10. `Fullscreen``fullscreen/fullscreen.vue`**
全屏切换;非 IE 显示按钮。支持 **`v-model`**`value` 是否全屏),全屏状态变化时触发 `input``on-change`
> `main.vue` 当前未使用该组件,可按需放入 `HeaderBar` 插槽。
**11. `ABackTop``a-back-top/index.vue`**
回到顶部浮动按钮,监听**滚动容器**滚动。
| Props | `height`(显示阈值,默认 `400`)、`bottom``right``duration`(滚动动画 ms`container`(默认 `window`,可为选择器字符串如 `'.content-wrapper'` |
| 插槽 | 默认:自定义按钮外观;未提供时用内置箭头图标 |
| 方法 | 内部 `scrollTop` 滚动;点击触发回到顶部 |
Main 中示例:`<ABackTop container=".content-wrapper" :height="100" :bottom="80" :right="50" />`
---
**小结**:自定义后台壳时,可照搬 `main.vue` 结构,替换或增删 `HeaderBar` 插槽内子组件;侧栏行为与菜单数据结构强相关,需与 **`user/menuList`**、**路由 `meta`** 保持一致。
### 12.2 表格:`Tables`
基于 View Design `Table` 封装,支持分页条、导出、列配置、可编辑列等。
| Props | 说明 |
|-------|------|
| `value` | 表格数据数组 |
| `columns` | 列配置(同 iView Table `columns`,框架内会做对齐等默认处理) |
| `title` / `tip` | 表格标题与灰色提示 |
| `isDown` | 为 true 且 `value` 非空时显示「下载」链接触发导出 |
| `pageOption` | 分页:`{ page, pageSize, total }`,存在则显示 `Page` |
| `width` / `height` | 传给内部 Table |
| `maxHeightOffset` | 表格外层最大高度:`auto`(默认,按视口计算)或数字,用于 `calc(100vh - offset)` |
| 插槽 | 说明 |
|------|------|
| `header` | 标题行右侧区域 |
| 默认 | 透传给内部 Table`slot-scope` 列) |
| `footer` / `loading` | 透传 Table 同名插槽 |
| 事件 | 说明 |
|------|------|
| `changePage` | 分页变更(内部已更新 `pageOption.page` |
| `on-select` | 多选变化 |
| `downExecl` | 点击下载时,`{ value, columns }` |
```vue
<Tables
:value="tableData"
:columns="columns"
:page-option="{ page: 1, pageSize: 10, total: 100 }"
title="用户列表"
@changePage="loadData"
/>
```
### 12.3 树形表格:`TreeGrid`
树形多级表格,表头与行由 `columns``data` 驱动(数据需含 `children` 等树结构)。
| Props | 说明 |
|-------|------|
| `columns` | 列配置 |
| `data` | 树形数据 |
其余属性通过 `v-bind="$attrs"` 传到内部子组件。
```vue
<TreeGrid :columns="gridOption.columns" :data="gridOption.data" />
```
### 12.4 上传:`UploadSingle` / `UploadMultiple`
| 组件 | 说明 |
|------|------|
| **UploadSingle** | 单图。`v-model` 绑定服务器返回的**相对路径**字符串;上传地址为 `apiUrl + 'sys_file/upload_oos_img'`,请求头带 `admin-token`。成功:`input``change`。预览/删除支持。 |
| **UploadMultiple** | 多图(内部列表最多约 4 张)。`@handleSuccess` 回调 `{ file, uploadList }`;可调用 `setList(list)` / `getList()` 同步列表。 |
```vue
<UploadSingle v-model="form.avatar" />
<UploadMultiple ref="multi" @handleSuccess="onUploaded" />
```
### 12.5 富文本:`Editor`
基于 **wangEditor**。使用 `this.$config.apiUrl`(或等价配置)作为图片上传地址。
| Props | 说明 |
|-------|------|
| `value` | HTML 字符串,支持 `v-model` |
```vue
<Editor v-model="form.content" />
```
### 12.6 弹窗:`AsyncModal` / `editModal`
**AsyncModal** — 简单确认弹窗:
| Props | 说明 |
|-------|------|
| `title` | 标题 |
| `width` | 宽度,默认约 `80`Modal 数字宽度) |
| 方法 | 说明 |
|------|------|
| `show(callback)` | 打开;确定时**先**执行 `callback`(可 async`on-ok` 并关闭 |
```vue
<AsyncModal ref="asyncModal" title="提示">
<p>自定义内容</p>
</AsyncModal>
<!-- this.$refs.asyncModal.show(async () => { await api(); }) -->
```
**editModal** — 由 `columns` 驱动表单项(内部用 `FieldRenderer`),用于新增/编辑:
| Props | 说明 |
|-------|------|
| `columns` | 列/字段配置:`key``title``com`Input、Select、Radio、`editRender` 等) |
| `width` | 弹窗宽度 |
| `rules` | 可通过父级传入覆盖默认校验(与 `Form` 一致) |
| 插槽 | 说明 |
|------|------|
| `top` | 表单最上方 |
| `bottom` | 表单最下方(如额外表单项) |
| 方法 | 说明 |
|------|------|
| `addShow(row, callback)` | 新增;确定后 `callback(提交行数据)` |
| `editShow(row, callback)` | 编辑 |
| `showModal(row)` | 有 `id` 走编辑,否则新增 |
| `hide` / `setFooter` / `refresh` | 关闭、控制底部按钮、强制重渲染表单 |
| 事件 | 说明 |
|------|------|
| `on-save` | `{ data, isEdit }` |
| `on-visible-change` | Modal 显隐 |
```vue
<editModal ref="editModal" :columns="editColumns" :rules="rules">
<div slot="bottom"><fieldItem name="备注"><Input v-model="extra" /></fieldItem></div>
</editModal>
```
### 12.7 表单项辅助:`fieldItem` / `FieldRenderer`
| 组件 | 说明 |
|------|------|
| **fieldItem** | 简单包装:`name`(标签)、`required`,默认插槽放控件。 |
| **FieldRenderer** | 根据 `col.com` 渲染 Input、Select、Radio、`UploadSingle``DatePicker` 等;一般被 `editModal` 使用,也可在自定义表单中复用。 |
```vue
<fieldItem name="名称" :required="true">
<Input v-model="form.name" />
</fieldItem>
```
### 12.8 卡片:`InfoCard`
左侧色块 + 图标,右侧自定义内容。
| Props | 说明 |
|-------|------|
| `left` | 左侧宽度占比,默认 `36`(即 36% |
| `color` | 左侧背景色 |
| `icon` / `iconSize` | 图标名与尺寸(走 `CommonIcon` |
| `shadow` | 是否 Card 阴影 |
```vue
<InfoCard icon="md-person" color="#19be6b">
<p>自定义统计文案</p>
</InfoCard>
```
### 12.9 分割面板:`SplitPane`
可拖拽调整左右或上下比例。
| Props | 说明 |
|-------|------|
| `value` | `0~1` 或带 `px` 的字符串,初始分割位置 |
| `mode` | `horizontal`(左右)或 `vertical`(上下) |
| `min` / `max` | 拖拽极限,默认约 `40px` |
| 插槽 | 说明 |
|------|------|
| `left` / `right``top` / `bottom` | 两侧内容 |
| `trigger` | 自定义拖拽条 |
| 事件 | 说明 |
|------|------|
| `on-move-start` / `on-moving` / `on-move-end` | 拖拽过程;`on-moving` 事件对象上可带 `atMin``atMax` |
```vue
<SplitPane v-model="split" mode="horizontal">
<div slot="left"></div>
<div slot="right"></div>
</SplitPane>
```
### 12.10 侧滑层:`FloatPanel`
从一侧滑出的面板,带返回/关闭。
| Props | 说明 |
|-------|------|
| `title` | 标题 |
| `width` / `height` | 尺寸,支持数字或带单位字符串 |
| `position` | `left` / `right` / `top` / `bottom` / `center` |
| `showBack` / `backText` | 返回按钮与文案 |
| `showClose` | 右上角关闭 |
| `closeOnClickBackdrop` | 点击遮罩是否关闭 |
| `mask` | 是否遮罩 |
| `zIndex` | 层级 |
| 方法 | 说明 |
|------|------|
| `show(callback)` | 显示;可选回调 |
| `hide()` | 隐藏 |
| 插槽 | 说明 |
|------|------|
| 默认 | 主体内容 |
| `header-right` | 标题栏右侧 |
```vue
<FloatPanel ref="panel" title="详情" position="right" :width="480">
<p>内容</p>
</FloatPanel>
<!-- this.$refs.panel.show() -->
```
### 12.11 其他全局组件
| 组件 | 说明 |
|------|------|
| **TextArea** | 多行输入,`value` + `input` 事件,封装 iView `Input type="textarea"`。 |
| **CommonIcon** | `type`:普通 `Icon` 名;自定义图标以 `_` 开头配合 `md-icons``size``color` 可选。 |
| **LoadFlower** | 全屏 loading 占位(与 `http``showLoad` 通过 DOM `id` 联动),一般随 `Main` 挂载,无需单独使用。 |
### 12.12 未全局注册、按需引用的组件
以下在 `src/components` 下,需在页面中 **import** 后注册或局部引用:
| 路径/名称 | 说明 |
|-----------|------|
| `login-form` | 登录表单:`userNameRules``passwordRules`;校验通过 `@on-success-valid` 抛出 `{ userName, password }`。 |
| `date-picker/index.vue` | 对 `DatePicker``v-model` 封装,`change`/`input`。 |
| `switch/index.vue` | 对 `Switch``v-model` 封装。 |
| `cron-input` | Cron 表达式下拉选择。 |
| `main/pageHead.vue` | 页面顶栏条:当 `$route.meta.type === '功能'` 时显示返回;默认插槽放按钮。 |
| `markdown``cropper` 等 | 见各目录 `index.js` / 入口组件。 |
---
## 13. 调试
- 浏览器中:`window.framework``window.rootVue`(根实例,在 `createApp` 创建后设置)。 - 浏览器中:`window.framework``window.rootVue`(根实例,在 `createApp` 创建后设置)。
- 需要未压缩源码调试时,可在业务工程里直接引用框架 **`src/index.js`**(需配置好 alias、loader 与 peer 依赖),或使用浏览器「在源映射中黑盒脚本」等能力。 - 需要未压缩源码调试时,可在业务工程里直接引用框架 **`src/index.js`**(需配置好 alias、loader 与 peer 依赖),或使用浏览器「在源映射中黑盒脚本」等能力。
--- ---
## 13. Demo 项目 ## 14. Demo 项目
`demo/` 为示例:请先在**仓库根目录**执行 `npm run build` 生成 `dist/admin-framework.js`,再在 `demo` 目录安装依赖并执行 `npm run dev``demo/src/main.js` 通过相对路径引用上级 `dist` 中的 UMD 包。 `demo/` 为示例:请先在**仓库根目录**执行 `npm run build` 生成 `dist/admin-framework.js`,再在 `demo` 目录安装依赖并执行 `npm run dev``demo/src/main.js` 通过相对路径引用上级 `dist` 中的 UMD 包。

View File

@@ -1,6 +1,7 @@
<template> <template>
<Switch <!-- iView / View Design 全局注册为 i-switch勿用 <Switch> 与本组件 name 形成自引用 -->
:value="value" <i-switch
:value="innerValue"
v-bind="$attrs" v-bind="$attrs"
@on-change="handleChange" @on-change="handleChange"
/> />
@@ -8,12 +9,23 @@
<script> <script>
export default { export default {
name: 'Switch', name: 'FrameworkSwitch',
props: ['value'], props: ['value'],
inheritAttrs: false,
computed: {
innerValue() {
const v = this.value
if (v === true || v === 1 || v === '1' || v === 'true') return true
if (v === false || v === 0 || v === '0' || v === 'false') return false
if (v === '' || v === null || v === undefined) return false
return Boolean(v)
}
},
methods: { methods: {
handleChange(checked) { handleChange(checked) {
this.$emit('input', checked) this.$emit('input', checked)
this.$emit('change', checked) this.$emit('change', checked)
this.$emit('on-change', checked)
} }
} }
} }

View File

@@ -3,7 +3,7 @@
<!-- 使用组件映射表来简化条件渲染 --> <!-- 使用组件映射表来简化条件渲染 -->
<component <component
:is="getComponentName(col.com)" :is="getComponentName(col.com)"
:value="col.com === 'Radio' ? radioValue : value" :value="inputBindValue"
v-bind="getComponentProps(col)" v-bind="getComponentProps(col)"
:disabled="disabled" :disabled="disabled"
:class="getComponentClass(col.com)" :class="getComponentClass(col.com)"
@@ -58,6 +58,7 @@
<script> <script>
import templateRender from './templateRender' import templateRender from './templateRender'
import FrameworkSwitch from '../switch/index.vue'
// 导入框架中的图标配置 // 导入框架中的图标配置
let icons = [] let icons = []
@@ -71,7 +72,8 @@ try {
export default { export default {
name: 'FieldRenderer', name: 'FieldRenderer',
components: { components: {
templateRender templateRender,
FrameworkSwitch
}, },
props: { props: {
col: { col: {
@@ -93,9 +95,21 @@ export default {
} }
}, },
computed: { computed: {
// 直接返回原始值,不进行类型转换
radioValue() { radioValue() {
return this.value return this.value
},
inputBindValue() {
if (this.col.com === 'Radio') {
return this.radioValue
}
if (this.col.com === 'Switch') {
const v = this.value
if (v === true || v === 1 || v === '1' || v === 'true') return true
if (v === false || v === 0 || v === '0' || v === 'false') return false
if (v === '' || v === null || v === undefined) return false
return Boolean(v)
}
return this.value
} }
}, },
mounted() { mounted() {
@@ -118,7 +132,7 @@ export default {
'Input': 'Input', 'Input': 'Input',
'DatePicker': 'DatePicker', 'DatePicker': 'DatePicker',
'TimePicker': 'TimePicker', 'TimePicker': 'TimePicker',
'Switch': 'Switch', 'Switch': 'FrameworkSwitch',
'Checkbox': 'Checkbox', 'Checkbox': 'Checkbox',
'Slider': 'Slider', 'Slider': 'Slider',
'Rate': 'Rate', 'Rate': 'Rate',
@@ -151,6 +165,9 @@ export default {
delete baseProps.data_type delete baseProps.data_type
delete baseProps.type delete baseProps.type
delete baseProps.rowStyle delete baseProps.rowStyle
delete baseProps.key
delete baseProps.title
delete baseProps.render
// 根据组件类型添加特定属性 // 根据组件类型添加特定属性
if (col.com === 'Select') { if (col.com === 'Select') {
@@ -237,7 +254,9 @@ export default {
'Input': 'width:100%;', 'Input': 'width:100%;',
'TextArea': 'width:100%;', 'TextArea': 'width:100%;',
'DatePicker': 'width:100%;', 'DatePicker': 'width:100%;',
'TimePicker': 'width:100%;' 'TimePicker': 'width:100%;',
"inputNumber": "width:100%;",
} }
return styleMap[componentType] || '' return styleMap[componentType] || ''
}, },
@@ -252,14 +271,17 @@ export default {
this.$emit('input', value) this.$emit('input', value)
}, },
// 处理变化事件 // 处理变化事件i-switch 等只发 on-change需同步 input 供 editModal 更新行)
handleChange(value) { handleChange(value) {
// 如果接收到的是事件对象,提取值
if (value && typeof value === 'object' && value.target) { if (value && typeof value === 'object' && value.target) {
value = value.target.value value = value.target.value
} }
// 直接传递值,不进行类型转换 const out =
this.$emit('change', value) this.col.com === 'Switch'
? !!value
: value
this.$emit('input', out)
this.$emit('change', out)
} }
} }
} }