This commit is contained in:
张成
2025-10-28 11:24:11 +08:00
parent a8d52f74ad
commit e039ae8c62
39 changed files with 636 additions and 676 deletions

View File

@@ -45,7 +45,7 @@ admin-framework/
│ │ ├── home/ # 主页 │ │ ├── home/ # 主页
│ │ ├── login/ # 登录页 │ │ ├── login/ # 登录页
│ │ ├── system/ # 系统管理页面 │ │ ├── system/ # 系统管理页面
│ │ └── system_high/ # 高级系统页面 │ │ └── system/ # 高级系统页面
│ └── index.js # 框架入口 │ └── index.js # 框架入口
├── dist/ # 打包产物 ├── dist/ # 打包产物
│ └── admin-framework.js # 框架打包文件3.6 MB内置所有依赖 │ └── admin-framework.js # 框架打包文件3.6 MB内置所有依赖

View File

@@ -43,7 +43,7 @@ npm run dev
**主页组件**(欢迎页面,自动显示系统标题) **主页组件**(欢迎页面,自动显示系统标题)
**系统管理页面**sys 开头的所有页面和功能) **系统管理页面**sys 开头的所有页面和功能)
**系统 API**system 和 system_high 所有 API **系统 API**system 和 system 所有 API
**全局组件**Tables、Editor、Upload 等) **全局组件**Tables、Editor、Upload 等)
**布局组件**Main、ParentView **布局组件**Main、ParentView
**登录和错误页面**Login、401、404、500 **登录和错误页面**Login、401、404、500
@@ -139,8 +139,8 @@ import AdminFramework from 'admin-framework'
**已包含** **已包含**
- **主页组件**HomePage - 欢迎页面,显示系统标题) - **主页组件**HomePage - 欢迎页面,显示系统标题)
- 所有系统页面system、system_high - 所有系统页面system、system
- 所有系统 APIsystem、system_high - 所有系统 APIsystem、system
- 所有全局组件Tables、Editor、Upload 等) - 所有全局组件Tables、Editor、Upload 等)
- 布局组件Main、ParentView - 布局组件Main、ParentView
- 登录和错误页面 - 登录和错误页面
@@ -605,7 +605,7 @@ import {
} from 'admin-framework' } from 'admin-framework'
``` ```
#### system_high 目录页面 #### system 目录页面
```javascript ```javascript
import { import {
SysControl, // 控制器管理 SysControl, // 控制器管理
@@ -635,7 +635,7 @@ const routes = [
component: SysRole component: SysRole
}, },
{ {
path: '/system_high/menu', path: '/system/menu',
name: 'sys_menu', name: 'sys_menu',
component: SysMenu component: SysMenu
} }
@@ -662,7 +662,7 @@ const users = await userServer.getList({ page: 1 })
const roles = await roleServer.getList() const roles = await roleServer.getList()
``` ```
#### system_high API #### system API
```javascript ```javascript
import { systemHighApi } from 'admin-framework' import { systemHighApi } from 'admin-framework'
@@ -1229,9 +1229,9 @@ AdminFramework.addComponentMap({
-`system/sys_role.vue` - 角色管理 -`system/sys_role.vue` - 角色管理
-`system/sys_log.vue` - 日志管理 -`system/sys_log.vue` - 日志管理
-`system/sys_param_setup.vue` - 参数设置 -`system/sys_param_setup.vue` - 参数设置
-`system_high/sys_menu.vue` - 菜单管理 -`system/sys_menu.vue` - 菜单管理
-`system_high/sys_control.vue` - 控制器管理 -`system/sys_control.vue` - 控制器管理
-`system_high/sys_title.vue` - 系统标题设置 -`system/sys_title.vue` - 系统标题设置
#### 配置技巧 #### 配置技巧

View File

@@ -1,15 +0,0 @@
class ShpProfitServer {
async report(param) {
let res = await window.framework.http.post('/shpProfit/report', param)
return res
}
async list(param) {
let res = await window.framework.http.post('/shpProfit/list', param)
return res
}
}
const shpProfitServer = new ShpProfitServer()
export default shpProfitServer

View File

@@ -41,7 +41,7 @@
<script> <script>
import menuServer from '@/api/system_high/menuServer.js' import menuServer from '@/api/system/menuServer.js'
import gameParticipantsServer from '@/api/ball/game_participants_server.js' import gameParticipantsServer from '@/api/ball/game_participants_server.js'
export default { export default {
data() { data() {

View File

@@ -50,7 +50,7 @@
<script> <script>
import menuServer from '@/api/system_high/menuServer.js' import menuServer from '@/api/system/menuServer.js'
import gamesServer from '@/api/ball/games_server.js' import gamesServer from '@/api/ball/games_server.js'
export default { export default {
data() { data() {

View File

@@ -50,7 +50,7 @@
<script> <script>
import menuServer from '@/api/system_high/menuServer.js' import menuServer from '@/api/system/menuServer.js'
import venuesServer from '@/api/ball/venues_server.js' import venuesServer from '@/api/ball/venues_server.js'
export default { export default {
data() { data() {

View File

@@ -46,7 +46,7 @@
<script> <script>
import menuServer from '@/api/system_high/menuServer.js' import menuServer from '@/api/system/menuServer.js'
import wch_usersServer from '@/api/ball/wch_users_server.js' import wch_usersServer from '@/api/ball/wch_users_server.js'
export default { export default {
data() { data() {

View File

@@ -45,7 +45,7 @@
<script> <script>
import menuServer from '@/api/system_high/menuServer.js' import menuServer from '@/api/system/menuServer.js'
import walletTransactionsServer from '@/api/ball/wallet_transactions_server.js' import walletTransactionsServer from '@/api/ball/wallet_transactions_server.js'

View File

@@ -26,7 +26,7 @@
<script> <script>
import menuServer from '@/api/system_high/menuServer.js' import menuServer from '@/api/system/menuServer.js'
import wch_professionsServer from '@/api/system/wch_professions_server.js' import wch_professionsServer from '@/api/system/wch_professions_server.js'
export default { export default {
data() { data() {

View File

@@ -4,8 +4,6 @@ export { default as fileServe } from './fileServe'
export { default as plaAccountServer } from './pla_account_server' export { default as plaAccountServer } from './pla_account_server'
export { default as rolePermissionServer } from './rolePermissionServer' export { default as rolePermissionServer } from './rolePermissionServer'
export { default as roleServer } from './roleServer' export { default as roleServer } from './roleServer'
export { default as shpProfitServer } from './shpProfitServer'
export { default as specificationServer } from './specificationServer'
export { default as sysAddressServer } from './sysAddressServer' export { default as sysAddressServer } from './sysAddressServer'
export { default as sysModuleServer } from './sysModuleServer' export { default as sysModuleServer } from './sysModuleServer'
export { default as sysLogServe } from './sys_log_serve' export { default as sysLogServe } from './sys_log_serve'
@@ -13,3 +11,13 @@ export { default as systemTypeServer } from './systemType_server'
export { default as tableServer } from './tableServer' export { default as tableServer } from './tableServer'
export { default as userServer } from './userServer' export { default as userServer } from './userServer'
export { default as formFieldServer } from './formFieldServer'
export { default as formServer } from './formServer'
export { default as menuServer } from './menuServer'
export { default as modelFieldServer } from './modelFieldServer'
export { default as modelServer } from './modelServer'
export { default as paramSetupServer } from './paramSetupServer'
export { default as sysControlTypeServer } from './sysControlTypeServer'

View File

@@ -1,15 +0,0 @@
import http from '@/utils/http'
class ShpProfitServer {
async report(param) {
let res = await http.post('/shpProfit/report', param)
return res
}
async list(param) {
let res = await http.post('/shpProfit/list', param)
return res
}
}
const shpProfitServer = new ShpProfitServer()
export default shpProfitServer

View File

@@ -1,10 +0,0 @@
// 高级系统 API 统一导出
export { default as formFieldServer } from './formFieldServer'
export { default as formServer } from './formServer'
export { default as menuServer } from './menuServer'
export { default as modelFieldServer } from './modelFieldServer'
export { default as modelServer } from './modelServer'
export { default as paramSetupServer } from './paramSetupServer'
export { default as sysControlTypeServer } from './sysControlTypeServer'

View File

@@ -0,0 +1,21 @@
<template>
<DatePicker
:value="value"
v-bind="$attrs"
@on-change="handleChange"
:style="'width:100%;'"
/>
</template>
<script>
export default {
name: 'DatePicker',
props: ['value'],
methods: {
handleChange(date) {
this.$emit('input', date)
this.$emit('change', date)
}
}
}
</script>

84
src/components/index.js Normal file
View File

@@ -0,0 +1,84 @@
import Main from './main'
import ParentView from './parent-view'
// 导入页面组件
import pages from '../views/index'
const {
LoginPage,
Page401,
Page404,
Page500
} = pages
import Tables from './tables'
import UploadSingle from './upload/Single.vue'
import UploadMultiple from './upload/Multiple.vue'
import TreeGrid from './treeGrid'
import AsyncModal from './asyncModal'
import InfoCard from './info-card'
import LoadFlower from './load-flower'
import SplitPane from './split-pane'
import TextArea from './text-area'
import CommonIcon from './common-icon'
import Editor from './editor/index.vue'
import editModal from './tables/editModal.vue'
import fieldItem from './tables/fieldItem.vue'
import FieldRenderer from './tables/fieldRenderer.vue'
// 注册全局组件的方法
export function registerGlobalComponents(Vue) {
Vue.component('Main', Main)
Vue.component('ParentView', ParentView)
Vue.component('Page401', Page401)
Vue.component('Page404', Page404)
Vue.component('Page500', Page500)
Vue.component('LoginPage', LoginPage)
Vue.component('Tables', Tables)
Vue.component('UploadSingle', UploadSingle)
Vue.component('UploadMultiple', UploadMultiple)
Vue.component('TreeGrid', TreeGrid)
Vue.component('AsyncModal', AsyncModal)
Vue.component('InfoCard', InfoCard)
Vue.component('LoadFlower', LoadFlower)
Vue.component('SplitPane', SplitPane)
Vue.component('TextArea', TextArea)
Vue.component('CommonIcon', CommonIcon)
Vue.component('Editor', Editor)
Vue.component('editModal', editModal)
Vue.component('fieldItem', fieldItem)
Vue.component('FieldRenderer', FieldRenderer)
}
// 注册自定义组件的方法
export function registerComponents(Vue, components = {}) {
Object.keys(components).forEach(name => {
Vue.component(name, components[name])
})
}
export default {
Main,
ParentView,
Tables,
UploadSingle,
UploadMultiple,
TreeGrid,
AsyncModal,
InfoCard,
LoadFlower,
SplitPane,
TextArea,
CommonIcon,
Editor,
editModal,
fieldItem,
FieldRenderer,
registerGlobalComponents,
registerComponents
}

View File

@@ -0,0 +1,20 @@
<template>
<Switch
:value="value"
v-bind="$attrs"
@on-change="handleChange"
/>
</template>
<script>
export default {
name: 'Switch',
props: ['value'],
methods: {
handleChange(checked) {
this.$emit('input', checked)
this.$emit('change', checked)
}
}
}
</script>

View File

@@ -13,38 +13,14 @@
<div :class="col.inLine?'inline-row':'line-row'" :key="col.name" v-if="col.key&&!col.display"> <div :class="col.inLine?'inline-row':'line-row'" :key="col.name" v-if="col.key&&!col.display">
<FormItem :label="col.title" :prop="col.key" :rules="curRules[col.key]" :style=" col.rowStyle"> <FormItem :label="col.title" :prop="col.key" :rules="curRules[col.key]" :style=" col.rowStyle">
<Row> <Row>
<!-- 使用专门的字段渲染组件 -->
<Select class="text-left" filterable v-if="col.com==='Select'" v-model='row[col.key]' v-bind="col" :disabled="getDisabled(col)"> <FieldRenderer
<Option :value="item.key" :key="item.key" v-for="item in col.source">{{item.value }}</Option> :col="col"
</Select> :value="row[col.key]"
:disabled="getDisabled(col)"
<RadioGroup v-else-if="col.com==='Radio'" v-model="row[col.key]" v-bind="col" :disabled="getDisabled(col)"> @input="handleFieldInput(col.key, $event)"
<Radio :label="item.key" :key="item.key" v-for="item in col.source"> @change="handleFieldChange(col.key, $event)"
{{item.value}} />
</Radio>
</RadioGroup>
<AutoComplete v-else-if="col.com==='SelectIcon'" icon="ios-search" v-model="row[col.key]" v-bind="col" :disabled="getDisabled(col)">
<Option v-for="(icon,index) in icons" :value="icon" :key="index">
<Icon size="20" :type="icon" />
{{ icon }}
</Option>
</AutoComplete>
<UploadSingle v-else-if="col.com==='upload_Img'" v-model="row[col.key]" v-bind="col" :disabled="getDisabled(col)">
</UploadSingle>
<TextArea v-else-if="col.com==='TextArea'" v-model="row[col.key]" v-bind="col" :disabled="getDisabled(col)">
</TextArea>
<templateRender v-else-if="col.editRender" :value="row[col.key]" :render='col.editRender'></templateRender>
<Input v-else-if="!col.com" v-model="row[col.key]" v-bind="col" style="width:100%;" :disabled="getDisabled(col)" />
<component v-else v-bind:is="col.com" v-model="row[col.key]" v-bind="col" :disabled="getDisabled(col)"></component>
</Row> </Row>
</FormItem> </FormItem>
</div> </div>
@@ -64,21 +40,12 @@
<script> <script>
import Vue from 'vue' import Vue from 'vue'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import templateRender from './templateRender' import FieldRenderer from './fieldRenderer'
// 导入框架中的图标配置
let icons = []
try {
icons = require('../../config/icons.json') || []
} catch (e) {
console.warn('未找到图标配置文件,图标选择功能将不可用', e)
icons = []
}
export default { export default {
props: ['columns', 'width'], props: ['columns', 'width'],
components: { components: {
templateRender FieldRenderer
}, },
data() { data() {
return { return {
@@ -87,7 +54,6 @@ export default {
isRefresh: true, isRefresh: true,
isEdit: false, isEdit: false,
isOPen: false, isOPen: false,
icons: [],
row: {}, row: {},
callback: null callback: null
} }
@@ -123,6 +89,16 @@ export default {
} }
}, },
methods: { methods: {
// 处理字段输入事件
handleFieldInput(key, value) {
this.$set(this.row, key, value)
},
// 处理字段变化事件
handleFieldChange(key, value) {
this.$set(this.row, key, value)
},
// 判断字段是否禁用 // 判断字段是否禁用
getDisabled(col) { getDisabled(col) {
// 如果是编辑模式且字段设置了disabled则禁用 // 如果是编辑模式且字段设置了disabled则禁用
@@ -192,12 +168,6 @@ export default {
this.isOPen = false this.isOPen = false
}, },
async init(row, isgl) { async init(row, isgl) {
// 安全处理图标数据
if (Array.isArray(icons)) {
this.icons = icons.map((item) => item.trim ? item.trim() : item)
} else {
this.icons = []
}
row = row || {} row = row || {}
let rules = this.curRules let rules = this.curRules
if (isgl) { if (isgl) {

View File

@@ -0,0 +1,196 @@
<template>
<div>
<!-- 使用组件映射表来简化条件渲染 -->
<component
:is="getComponentName(col.com)"
v-model="value"
v-bind="getComponentProps(col)"
:disabled="disabled"
:class="getComponentClass(col.com)"
:style="getComponentStyle(col.com)"
@input="handleInput"
@change="handleChange"
>
<!-- Select 组件的选项 -->
<template v-if="col.com === 'Select'">
<Option
:value="item.key"
:key="item.key"
v-for="item in col.source"
>
{{ item.value }}
</Option>
</template>
<!-- Radio 组件的选项 -->
<template v-else-if="col.com === 'Radio'">
<Radio
:label="item.key"
:key="item.key"
v-for="item in col.source"
>
{{ item.value }}
</Radio>
</template>
<!-- SelectIcon 组件的选项 -->
<template v-else-if="col.com === 'SelectIcon'">
<Option
v-for="(icon, index) in icons"
:value="icon"
:key="index"
>
<Icon size="20" :type="icon" />
{{ icon }}
</Option>
</template>
<!-- 自定义渲染 -->
<template v-else-if="col.editRender">
<templateRender
:value="value"
:render="col.editRender"
/>
</template>
</component>
</div>
</template>
<script>
import templateRender from './templateRender'
// 导入框架中的图标配置
let icons = []
try {
icons = require('../../config/icons.json') || []
} catch (e) {
console.warn('未找到图标配置文件,图标选择功能将不可用', e)
icons = []
}
export default {
name: 'FieldRenderer',
components: {
templateRender
},
props: {
col: {
type: Object,
required: true
},
value: {
type: [String, Number, Boolean, Array, Object, Date],
default: ''
},
disabled: {
type: Boolean,
default: false
}
},
data() {
return {
icons: []
}
},
mounted() {
// 安全处理图标数据
if (Array.isArray(icons)) {
this.icons = icons.map((item) => item.trim ? item.trim() : item)
} else {
this.icons = []
}
},
methods: {
// 组件映射表 - 统一管理组件类型
getComponentName(componentType) {
const componentMap = {
'Select': 'Select',
'Radio': 'RadioGroup',
'SelectIcon': 'AutoComplete',
'UploadSingle': 'UploadSingle',
'TextArea': 'TextArea',
'Input': 'Input',
'DatePicker': 'DatePicker',
'TimePicker': 'TimePicker',
'Switch': 'Switch',
'Checkbox': 'Checkbox',
'Slider': 'Slider',
'Rate': 'Rate',
'Cascader': 'Cascader',
'TreeSelect': 'TreeSelect',
'Transfer': 'Transfer',
'Upload': 'Upload',
'ColorPicker': 'ColorPicker',
'InputNumber': 'InputNumber'
}
// 如果没有指定组件类型,默认为 Input
return componentMap[componentType] || 'Input'
},
// 获取组件属性
getComponentProps(col) {
const baseProps = { ...col }
// 移除不需要传递给组件的属性
delete baseProps.com
delete baseProps.source
delete baseProps.editRender
delete baseProps.inLine
delete baseProps.display
delete baseProps.disabled
delete baseProps.disabledOnAdd
delete baseProps.required
delete baseProps.data_type
delete baseProps.type
delete baseProps.rowStyle
// 根据组件类型添加特定属性
if (col.com === 'Select') {
baseProps.filterable = true
baseProps.clearable = true
} else if (col.com === 'SelectIcon') {
baseProps.icon = 'ios-search'
} else if (col.com === 'TextArea') {
baseProps.rows = 4
baseProps.placeholder = '请输入内容'
} else if (col.com === 'Input' || !col.com) {
baseProps.placeholder = col.placeholder || '请输入' + col.title
baseProps.clearable = true
}
return baseProps
},
// 获取组件样式类
getComponentClass(componentType) {
const classMap = {
'Select': 'text-left',
'SelectIcon': 'text-left'
}
return classMap[componentType] || ''
},
// 获取组件样式
getComponentStyle(componentType) {
const styleMap = {
'Input': 'width:100%;',
'TextArea': 'width:100%;',
'DatePicker': 'width:100%;',
'TimePicker': 'width:100%;'
}
return styleMap[componentType] || ''
},
// 处理输入事件
handleInput(value) {
this.$emit('input', value)
},
// 处理变化事件
handleChange(value) {
this.$emit('change', value)
}
}
}
</script>

View File

@@ -75,7 +75,7 @@ export const defaultMenus = [
{ {
id: 120, id: 120,
name: '高级管理', name: '高级管理',
path: '/system_high', path: '/system',
component: '', component: '',
parent_id: 0, parent_id: 0,
type: '菜单', type: '菜单',
@@ -86,8 +86,8 @@ export const defaultMenus = [
{ {
id: 122, id: 122,
name: '菜单管理', name: '菜单管理',
path: '/system_high/menu', path: '/system/menu',
component: 'system_high/sys_menu', component: 'system/sys_menu',
parent_id: 120, parent_id: 120,
type: '页面', type: '页面',
is_show_menu: 1, is_show_menu: 1,
@@ -97,8 +97,8 @@ export const defaultMenus = [
{ {
id: 123, id: 123,
name: '控制管理', name: '控制管理',
path: '/system_high/control', path: '/system/control',
component: 'system_high/sys_control', component: 'system/sys_control',
parent_id: 120, parent_id: 120,
type: '页面', type: '页面',
is_show_menu: 1, is_show_menu: 1,
@@ -108,8 +108,8 @@ export const defaultMenus = [
{ {
id: 124, id: 124,
name: '系统标题', name: '系统标题',
path: '/system_high/title', path: '/system/title',
component: 'system_high/sys_title', component: 'system/sys_title',
parent_id: 120, parent_id: 120,
type: '页面', type: '页面',
is_show_menu: 1, is_show_menu: 1,

View File

@@ -20,197 +20,64 @@ import uiTool from './utils/uiTool'
import http from './utils/http' import http from './utils/http'
import * as tools from './utils/tools' import * as tools from './utils/tools'
import storeModules, { userModule, appModule } from './store' import storeModules, { createStore } from './store'
import { createBaseRoutes, setupRouterGuards } from './router' import { createBaseRoutes, setupRouterGuards, createRouter, getRoutes } from './router'
import HomePage from './views/home/index.vue'
import SysLog from './views/system/sys_log.vue'
import SysParamSetup from './views/system/sys_param_setup.vue'
import SysRole from './views/system/sys_role.vue'
import SysUser from './views/system/sys_user.vue'
import SysControl from './views/system_high/sys_control.vue' import components from './components/index'
import SysMenu from './views/system_high/sys_menu.vue' import pages from './views/index'
import SysTitle from './views/system_high/sys_title.vue'
import LoginPage from './views/login/login.vue'
import Page401 from './views/error-page/401.vue'
import Page404 from './views/error-page/404.vue'
import Page500 from './views/error-page/500.vue'
// 导入页面组件
import Main from './components/main' import Main from './components/main'
import ParentView from './components/parent-view' import ParentView from './components/parent-view'
import Tables from './components/tables' // 通过模块化方式导入页面组件
import UploadSingle from './components/upload/Single.vue' const {
import UploadMultiple from './components/upload/Multiple.vue' HomePage,
import TreeGrid from './components/treeGrid' LoginPage,
import AsyncModal from './components/asyncModal' Page401,
import InfoCard from './components/info-card' Page404,
import LoadFlower from './components/load-flower' Page500,
import SplitPane from './components/split-pane' SysLog,
import TextArea from './components/text-area' SysParamSetup,
import CommonIcon from './components/common-icon' SysRole,
import Editor from './components/editor/index.vue' SysUser,
import editModal from './components/tables/editModal.vue' SysControl,
import fieldItem from './components/tables/fieldItem.vue' SysMenu,
SysTitle,
setupComponentMap
} = pages
// 导入组件相关功能
import components, { registerGlobalComponents, registerComponents } from './components/index'
import * as systemApi from './api/system' import * as systemApi from './api/system'
import * as systemHighApi from './api/system_high'
class AdminFramework { class AdminFramework {
constructor() { constructor() {
this.version = '1.0.0' this.version = '1.0.0'
this.installed = false
this.config = {} this.config = {}
this.store = null this.store = null
this.router = null this.router = null
this.ViewUI = null
this.tools = tools this.tools = tools
this.uiTool = uiTool this.uiTool = uiTool
this.http = http this.http = http
this.storeModules = storeModules this.storeModules = storeModules
this.userModule = userModule
this.appModule = appModule
this.createBaseRoutes = createBaseRoutes this.createBaseRoutes = createBaseRoutes
this.setupRouterGuards = setupRouterGuards this.setupRouterGuards = setupRouterGuards
this.Main = Main this.pages = pages
this.ParentView = ParentView this.components = components
this.LoginPage = LoginPage
this.Page401 = Page401
this.Page404 = Page404
this.Page500 = Page500
this.HomePage = HomePage
this.SysLog = SysLog
this.SysParamSetup = SysParamSetup
this.SysRole = SysRole
this.SysUser = SysUser
this.SysControl = SysControl
this.SysMenu = SysMenu
this.SysTitle = SysTitle
this.systemApi = systemApi this.systemApi = systemApi
this.systemHighApi = systemHighApi
} }
/**
* Vue plugin install method
* @param {Object} Vue - Vue instance
* @param {Object} options - config options
*/
install(Vue, options = {}) {
if (this.installed) return
this.installed = true
const { config = {}, ViewUI, VueRouter, Vuex, createPersistedState, componentMap } = options
this.config = config
this.ViewUI = ViewUI
if (ViewUI) {
Vue.use(ViewUI)
}
if (VueRouter) {
Vue.use(VueRouter)
}
if (Vuex) {
Vue.use(Vuex)
}
Vue.prototype.$config = config
Vue.prototype.$http = http
Vue.prototype.$tools = tools
Vue.prototype.$uiTool = uiTool
Vue.prototype.$framework = this
this.registerGlobalComponents(Vue)
this.setupComponentMap(componentMap)
if (Vuex && !this.store) {
this.store = this.createStore(Vuex, {}, createPersistedState)
http.init(config, this.store)
}
if (VueRouter && !this.router) {
const mainRoute = this.getRoutes({ Main, ParentView, Page404, HomePage: this.HomePage })
this.router = this.createRouter(VueRouter, {
Main,
ParentView,
LoginPage,
Page401,
Page404,
Page500
}, mainRoute ? [mainRoute] : [], ViewUI)
}
}
/**
* Register global components
*/
registerGlobalComponents(Vue) {
Vue.component('Main', Main)
Vue.component('ParentView', ParentView)
Vue.component('Page401', Page401)
Vue.component('Page404', Page404)
Vue.component('Page500', Page500)
Vue.component('LoginPage', LoginPage)
Vue.component('Tables', Tables)
Vue.component('UploadSingle', UploadSingle)
Vue.component('UploadMultiple', UploadMultiple)
Vue.component('TreeGrid', TreeGrid)
Vue.component('AsyncModal', AsyncModal)
Vue.component('InfoCard', InfoCard)
Vue.component('LoadFlower', LoadFlower)
Vue.component('SplitPane', SplitPane)
Vue.component('TextArea', TextArea)
Vue.component('CommonIcon', CommonIcon)
Vue.component('Editor', Editor)
Vue.component('editModal', editModal)
Vue.component('fieldItem', fieldItem)
}
/**
* Setup component map
* @param {Object} customMap - custom component map
*/
setupComponentMap(customMap = {}) {
const components = {
'home/index': HomePage,
'system/sys_log': SysLog,
'system/sys_param_setup': SysParamSetup,
'system/sys_role': SysRole,
'system/sys_user': SysUser,
'system_high/sys_control': SysControl,
'system_high/sys_menu': SysMenu,
'system_high/sys_title': SysTitle,
...customMap
}
const map = {}
Object.keys(components).forEach(path => {
const cleanPath = path.replace(/\.vue$/, '')
map[cleanPath] = components[path]
map[cleanPath + '.vue'] = components[path]
})
uiTool.setComponentMap(map)
}
/** /**
* Add custom component map * Add custom component map
@@ -222,7 +89,7 @@ class AdminFramework {
* }) * })
*/ */
addComponentMap(customMap) { addComponentMap(customMap) {
uiTool.setComponentMap(customMap) setupComponentMap(customMap, uiTool)
} }
/** /**
@@ -235,88 +102,9 @@ class AdminFramework {
this.store = store this.store = store
} }
/**
* Create router instance
* @param {Object} Router - VueRouter class
* @param {Object} components - component object
* @param {Array} customRoutes - custom routes
* @param {Object} ViewUI - ViewUI instance
* @param {String} homeName - home page name
* @returns {Object} router instance
*/
createRouter(Router, components = {}, customRoutes = [], ViewUI, homeName = 'home') {
const { LoginPage, Page401, Page404, Page500 } = components
if (!LoginPage || !Page401 || !Page404 || !Page500) {
console.error('Missing required page components')
return null
}
const baseRoutes = createBaseRoutes(LoginPage, Page401, Page404, Page500)
const router = new Router({
routes: [...baseRoutes, ...customRoutes],
mode: 'hash'
})
if (ViewUI) {
setupRouterGuards(router, ViewUI, homeName)
}
return router
}
/**
* Create Store instance
* @param {Object} Vuex - Vuex class
* @param {Object} customModules - custom modules
* @param {Object} createPersistedState - vuex-persistedstate plugin
* @returns {Object} store instance
*/
createStore(Vuex, customModules = {}, createPersistedState) {
const store = new Vuex.Store({
modules: {
user: userModule,
app: appModule,
...customModules
},
plugins: createPersistedState ? [
createPersistedState({
storage: window.localStorage
})
] : []
})
this.store = store
return store
}
/**
* Get dynamic routes
* @param {Object} components - component object
* @returns {Object} main route config
*/
getRoutes(components = {}) {
const { Main, ParentView, Page404, HomePage } = components
if (!Main || !ParentView || !Page404) {
console.error('Missing required layout components')
return null
}
return uiTool.getRoutes(Main, ParentView, Page404, HomePage || this.HomePage)
}
/**
* Register global components
* @param {Object} Vue - Vue instance
* @param {Object} components - component object
*/
registerComponents(Vue, components = {}) {
Object.keys(components).forEach(name => {
Vue.component(name, components[name])
})
}
/** /**
* Create app with simplified API (推荐使用) * Create app with simplified API (推荐使用)
@@ -329,23 +117,50 @@ class AdminFramework {
* @returns {Object} Vue instance * @returns {Object} Vue instance
*/ */
createApp(config = {}) { createApp(config = {}) {
// Auto install framework using internal dependencies
if (!this.installed) {
const { componentMap, ...appConfig } = config
// 如果没有提供 uploadUrl自动从 apiUrl 推导 // 如果没有提供 uploadUrl自动从 apiUrl 推导
if (!appConfig.uploadUrl && appConfig.apiUrl) { if (!config.uploadUrl && config.apiUrl) {
appConfig.uploadUrl = appConfig.apiUrl + (appConfig.apiUrl.endsWith('/') ? 'upload' : '/upload') config.uploadUrl = config.apiUrl + (config.apiUrl.endsWith('/') ? 'upload' : '/upload')
} }
this.install(Vue, { // 设置配置
config: appConfig, this.config = config
ViewUI,
VueRouter, // 初始化 Vue 插件
Vuex, Vue.use(ViewUI)
createPersistedState: null, Vue.use(VueRouter)
componentMap: componentMap || {} Vue.use(Vuex)
})
// 设置全局属性
Vue.prototype.$config = config
Vue.prototype.$http = http
Vue.prototype.$tools = tools
Vue.prototype.$uiTool = uiTool
Vue.prototype.$framework = this
// 注册全局组件
registerGlobalComponents(Vue)
// 设置组件映射
setupComponentMap(config.componentMap || {}, uiTool)
// 创建 Store
if (!this.store) {
this.store = createStore(Vuex, {}, null)
http.init(config, this.store)
}
// 创建 Router
if (!this.router) {
const mainRoute = getRoutes({ Main, ParentView, Page404, HomePage: this.HomePage }, uiTool)
this.router = createRouter(VueRouter, {
Main,
ParentView,
LoginPage,
Page401,
Page404,
Page500
}, mainRoute ? [mainRoute] : [], ViewUI)
} }
// Create Vue instance with auto menu/title restoration // Create Vue instance with auto menu/title restoration
@@ -421,9 +236,6 @@ export {
http, http,
storeModules, storeModules,
userModule,
appModule,
createBaseRoutes, createBaseRoutes,
setupRouterGuards, setupRouterGuards,

View File

@@ -89,8 +89,45 @@ export const setupRouterGuards = (router, ViewUI, homeName = 'home') => {
return router return router
} }
export default { // 获取动态路由的方法
createBaseRoutes, export function getRoutes(components = {}, uiTool) {
setupRouterGuards const { Main, ParentView, Page404, HomePage } = components
if (!Main || !ParentView || !Page404) {
console.error('Missing required layout components')
return null
}
return uiTool.getRoutes(Main, ParentView, Page404, HomePage)
}
// 创建路由实例的方法
export function createRouter(Router, components = {}, customRoutes = [], ViewUI) {
const { LoginPage, Page401, Page404, Page500 } = components
if (!LoginPage || !Page401 || !Page404 || !Page500) {
console.error('Missing required page components')
return null
}
const baseRoutes = createBaseRoutes(LoginPage, Page401, Page404, Page500)
const router = new Router({
routes: [...baseRoutes, ...customRoutes],
mode: 'hash'
})
if (ViewUI) {
setupRouterGuards(router, ViewUI, 'home')
}
return router
}
export default {
createBaseRoutes,
setupRouterGuards,
createRouter,
getRoutes
} }

View File

@@ -1,5 +1,5 @@
import { getBreadCrumbList, getHomeRoute } from '../utils/tools' import { getBreadCrumbList, getHomeRoute } from '../utils/tools'
import paramSetupServer from '../api/system_high/paramSetupServer' import paramSetupServer from '../api/system/paramSetupServer'
export default { export default {
namespaced: true, namespaced: true,

View File

@@ -1,10 +1,27 @@
import userModule from './user' import userModule from './user'
import appModule from './app' import appModule from './app'
export { userModule, appModule } // 创建 Store 实例的方法
export function createStore(Vuex, customModules = {}, createPersistedState) {
const store = new Vuex.Store({
modules: {
user: appModule,
app: userModule,
...customModules
},
plugins: createPersistedState ? [
createPersistedState({
storage: window.localStorage
})
] : []
})
return store
}
export default { export default {
user: userModule, user: userModule,
app: appModule app: appModule,
createStore
} }

61
src/views/index.js Normal file
View File

@@ -0,0 +1,61 @@
import HomePage from './home/index.vue'
import SysLog from './system/sys_log.vue'
import SysParamSetup from './system/sys_param_setup.vue'
import SysRole from './system/sys_role.vue'
import SysUser from './system/sys_user.vue'
import SysControl from './system/sys_control.vue'
import SysMenu from './system/sys_menu.vue'
import SysTitle from './system/sys_title.vue'
import LoginPage from './login/login.vue'
import Page401 from './error-page/401.vue'
import Page404 from './error-page/404.vue'
import Page500 from './error-page/500.vue'
// 设置组件映射的方法
export function setupComponentMap(customMap = {}, uiTool) {
const componentMap = {
'home/index': HomePage,
'system/sys_log': SysLog,
'system/sys_param_setup': SysParamSetup,
'system/sys_role': SysRole,
'system/sys_user': SysUser,
'system/sys_control': SysControl,
'system/sys_menu': SysMenu,
'system/sys_title': SysTitle,
...customMap
}
const map = {}
Object.keys(componentMap).forEach(path => {
const cleanPath = path.replace(/\.vue$/, '')
map[cleanPath] = componentMap[path]
map[cleanPath + '.vue'] = componentMap[path]
})
uiTool.setComponentMap(map)
}
export default {
HomePage,
SysLog,
SysParamSetup,
SysRole,
SysUser,
SysControl,
SysMenu,
SysTitle,
LoginPage,
Page401,
Page404,
Page500,
setupComponentMap
}

View File

@@ -21,8 +21,8 @@
<script> <script>
import tools from '@/utils/tools' import tools from '@/utils/tools'
import uiTool from '@/utils/uiTool' import uiTool from '@/utils/uiTool'
import modelServer from '@/api/system_high/modelServer.js' import modelServer from '@/api/system/modelServer.js'
import sysControlTypeServer from '@/api/system_high/sysControlTypeServer.js' import sysControlTypeServer from '@/api/system/sysControlTypeServer.js'
export default { export default {
data() { data() {

View File

@@ -46,7 +46,7 @@
<script> <script>
import uiTool from '@/utils/uiTool' import uiTool from '@/utils/uiTool'
import menuServer from '@/api/system_high/menuServer' import menuServer from '@/api/system/menuServer'
export default { export default {
name: 'tree_table_page', name: 'tree_table_page',

View File

@@ -15,7 +15,7 @@
</template> </template>
<script> <script>
import uiTool from '@/utils/uiTool' import uiTool from '@/utils/uiTool'
import paramSetupServer from '@/api/system_high/paramSetupServer' import paramSetupServer from '@/api/system/paramSetupServer'
export default { export default {
name: 'tables_page', name: 'tables_page',

View File

@@ -18,7 +18,7 @@
<script> <script>
import uiTool from '@/utils/uiTool' import uiTool from '@/utils/uiTool'
import roleServer from '@/api/system/roleServer' import roleServer from '@/api/system/roleServer'
import menuServer from '@/api/system_high/menuServer' import menuServer from '@/api/system/menuServer'
export default { export default {
name: 'tables_page', name: 'tables_page',

View File

@@ -17,7 +17,7 @@
<script> <script>
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import paramSetupServer from '@/api/system_high/paramSetupServer' import paramSetupServer from '@/api/system/paramSetupServer'
export default { export default {
data() { data() {
return { return {

View File

@@ -1,33 +0,0 @@
<template>
<Select @on-change="changeInput" :value="value">
<Option v-for="item in source" :value="item.key" :key="item.key">{{ item.value }}</Option>
</Select>
</template>
<script>
export default {
props: ['value'],
data() {
return {
source: [
{ key: 'STRING', value: '字符串-STRING' },
{ key: 'TEXT', value: '富文本-TEXT' },
{ key: 'JSON', value: 'JSON' },
{ key: 'INTEGER', value: '整型-INTEGER' },
{ key: 'BOOLEAN', value: '布尔值-BOOLEAN' },
{ key: 'DOUBLE', value: '双精度浮点数-DOUBLE' },
{ key: 'DATE', value: '日期-DATE' },
{ key: 'ENUM', value: '枚举-ENUM' }
]
}
},
methods: {
changeInput(val) {
this.$emit('input', val)
}
}
}
</script>
<style>
</style>

View File

@@ -1,251 +0,0 @@
<template>
<Card class="pa5" dis-hover v-if="value">
<Select :value="value.comType" @on-change="changeComType">
<Option v-for="item in sourceCom" :value="item.key" :key="item.key">{{ item.value }}</Option>
</Select>
<div style="width:100%;" v-if="value.comType==='Select'|| value.comType==='Radio'|| value.comType==='Checkbox'">
<div class="select-form ">
<div class="select-item">
<label class="label">
<span class="red"> *</span>
<span class="ml5"> 数据源类型</span>
</label>
<div class="item-com">
<RadioGroup :value="value.interfaceType" @on-change="changeInterfaceType">
<Radio :label="item.key" :key="item.key" v-for="item in sourceType">
<span>{{ item.value }}</span>
</Radio>
</RadioGroup>
</div>
</div>
<div v-if="value.interfaceType==='接口'">
<div class="select-item">
<label class="label">
<span class="red"> *</span>
<span class="ml5">选择model</span>
</label>
<div class="item-com">
<Select :value="value.modelKey" placeholder="请选择model" @on-change="changeModel">
<Option v-for="item in modelRows" :value="item.key" :key="item.key">{{ item.value }}</Option>
</Select>
</div>
</div>
<div class="select-item">
<label class="label">
<span class="red"> *</span>
<span class="ml5">key(映射)</span>
</label>
<div class="item-com">
<Select :value="value.modelMap.key" placeholder="请选择字段" @on-change="changeKey">
<Option v-for="item in fieldRows" :value="item.key" :key="item.key">{{ item.value }}</Option>
</Select>
</div>
</div>
<div class="select-item">
<label class="label">
<span class="red"> *</span>
<span class="ml5">value(映射)</span>
</label>
<div class="item-com">
<Select :value="value.modelMap.value" placeholder="请选择字段" @on-change="changeValue">
<Option v-for="item in fieldRows" :value="item.key" :key="item.key">{{ item.value }}</Option>
</Select>
</div>
</div>
</div>
<div v-else>
<div class="bd tag-box">
<Tag v-for="item in value.localData" :key="item.key" :name="item.name" closable @on-close="handleClose(item.key)">{{ item.key+':'+item.value }}</Tag>
</div>
<div class="mt5">
<Input :max="10" :min="1" v-model="localVal.key" type="number" size="small" placeholder="输入key" style="width: 60px"></Input>
<Input class="ml5" v-model="localVal.value" size="small" placeholder="输入value" style="width: 80px" />
<Button class="ml10" size="small" @click="handleAdd">添加数据</Button>
</div>
</div>
</div>
</div>
<div v-else></div>
</Card>
</template>
<script>
import modelServer from '@/api/system_high/modelServer'
import modelFieldServer from '@/api/system_high/modelFieldServer'
export default {
props: ['value'],
data() {
return {
localData: [],
localVal: { key: 0, value: '' },
sourceCom: [
{ key: 'Input', value: '文本输入框-Input', default_option: { data_type: 'STRING', data_length: 50 } },
{ key: 'TextArea', value: '文本区域框-TextArea', default_option: { data_type: 'STRING', data_length: 500 } },
{ key: 'InputNumber', value: '数字输入框-InputNumber', default_option: { data_type: 'INTEGER', data_length: 11, defaultValue: 0 } },
{ key: 'i-switch', value: '开关-Switch', default_option: { data_type: 'BOOLEAN', data_length: 2 } },
{ key: 'Radio', value: '单选框-Radio', default_option: { data_type: 'INTEGER', data_length: 2 } },
{ key: 'Select', value: '下拉选择器-Select', default_option: { data_type: 'STRING', data_length: 50 } },
{ key: 'Checkbox', value: '多选框-Checkbox', default_option: { data_type: 'INTEGER', data_length: 11 } },
{ key: 'DatePicker', value: '日期选择器-DatePicker', default_option: { data_type: 'DATE', data_length: 50 } },
{ key: 'Editor', value: '富文本-Editor', default_option: { data_type: 'TEXT', data_length: 0 } },
{ key: 'UploadSingle', value: '图片框-UploadSingle', default_option: { data_type: 'STRING', data_length: 1000 } }
],
sourceType: [
{ key: '接口', value: '接口' },
{ key: '本地数据', value: '本地数据' }
],
modelRows: [],
fieldRows: []
}
},
computed: {
comType() {
return this.value.comType
}
},
created() {
this.init()
},
methods: {
async init() {
await this.getModel()
await this.getField()
},
handleClose(key) {
const index = this.localData.findIndex((p) => p.key === key)
this.localData.splice(index, 1)
let { comType, localData } = this
let newVal = Object.assign({}, this.value, { comType, localData })
this.$emit('input', newVal)
},
handleAdd() {
if ((this.localVal.key || this.localVal.key === 0) && this.localVal.value) {
const index = this.localData.findIndex((p) => p.key === this.localVal.key)
if (index === -1) {
this.localData.push(this.localVal)
let { comType, localData } = this
let newVal = Object.assign({}, this.value, { comType, localData })
this.$emit('input', newVal)
this.localVal = { key: this.localData.length, value: '' }
} else {
this.$Message.error('不能添加重复项')
}
} else {
this.$Message.error('请输入值')
}
},
async getField() {
this.$nextTick(async () => {
if (this.value) {
let key = this.value.modelKey
if (key) {
let res = await modelFieldServer.allByKey({ key })
let data = res.data.map((p) => {
let { key, name } = p
return { key, value: key }
})
this.fieldRows = [{ key: 'id', value: 'id' }, ...data]
}
}
})
},
async getModel() {
let res = await modelServer.all()
this.modelRows = res.data.map((p) => {
let { key, name } = p
return { key, value: key + '-' + name }
})
},
changeComType(val) {
let remoteKeys = ['Select', 'Radio', 'Checkbox']
let newRow = { comType: val }
if (remoteKeys.indexOf(val) > -1) {
newRow = Object.assign(newRow, { interfaceType: '接口', modelKey: '', modelMap: { key: 'id', value: 'name' } })
}
this.$emit('input', newRow)
let com = this.sourceCom.find((p) => p.key === val)
if (com && com.default_option) {
this.$emit('changeOption', com.default_option)
}
},
changeModel(val) {
let newRow = Object.assign({ modelMap: { key: '', value: '' } }, this.value, { modelKey: val })
this.$emit('input', newRow)
this.getField()
},
changeInterfaceType(val) {
if (val === '接口') {
let newRow = Object.assign({ modelMap: { key: '', value: '' } }, this.value, { interfaceType: val })
this.$emit('input', newRow)
} else {
let newRow = Object.assign({ modelMap: { key: '', value: '' } }, this.value, { interfaceType: val })
console.log(newRow, val)
this.$emit('input', newRow)
}
},
changeKey(val) {
let newRow = Object.assign({ modelMap: { key: '', value: '' } }, this.value)
newRow.modelMap.key = val
this.$emit('input', newRow)
},
changeValue(val) {
let newRow = Object.assign({ modelMap: { key: '', value: '' } }, this.value)
newRow.modelMap.value = val
this.$emit('input', newRow)
}
}
}
</script>
<style lang="less" scoped>
.select-form {
display: flex;
flex-direction: column;
padding: 10px;
.select-item {
display: flex;
flex-direction: row;
margin: 10px 0px;
.label {
display: inline-flex;
width: 100px;
justify-content: flex-start;
align-items: center;
margin-right: 30px;
}
.item-com {
flex: 1;
}
}
}
.tag-box {
line-height: 30px;
width: 100%;
height: 70px;
padding: 5px;
}
.bd {
border: solid 1px #ccc;
}
</style>

View File

@@ -5,11 +5,13 @@
## 📦 框架特性 ## 📦 框架特性
### ✨ 核心功能 ### ✨ 核心功能
-**简化的 API** - 只需调用 `createApp()` 即可完成所有初始化
-**模块化设计** - 组件、路由、状态管理等功能按模块组织
-**完整的系统管理页面** - 用户、角色、菜单、日志等管理 -**完整的系统管理页面** - 用户、角色、菜单、日志等管理
-**登录和权限管理** - 完整的登录流程和权限控制 -**登录和权限管理** - 完整的登录流程和权限控制
-**动态路由管理** - 基于权限菜单的动态路由生成 -**动态路由管理** - 基于权限菜单的动态路由生成
-**Vuex 状态管理** - 用户、应用状态管理 -**Vuex 状态管理** - 用户、应用状态管理
-**全局组件库** - Tables、Editor、Upload、TreeGrid 等 -**全局组件库** - Tables、Editor、Upload、TreeGrid、FieldRenderer
-**工具库** - HTTP、日期、Token、Cookie 等工具 -**工具库** - HTTP、日期、Token、Cookie 等工具
-**内置样式** - base.less、animate.css、iconfont 等 -**内置样式** - base.less、animate.css、iconfont 等
-**响应式布局** - 支持移动端适配 -**响应式布局** - 支持移动端适配
@@ -62,6 +64,34 @@ npm run build
# 3. 产物在 dist/admin-framework.js # 3. 产物在 dist/admin-framework.js
``` ```
## 🎯 极简使用方式
### 只需 3 步即可完成集成!
#### 1. 引入框架
```javascript
import AdminFramework from './admin-framework.js'
```
#### 2. 创建应用
```javascript
const app = AdminFramework.createApp({
title: '我的管理系统',
apiUrl: 'http://localhost:9098/admin_api/',
componentMap: {
'business/product': ProductComponent,
'business/order': OrderComponent
}
})
```
#### 3. 挂载应用
```javascript
app.$mount('#app')
```
**就这么简单!** 框架会自动完成所有初始化工作。
## 📖 完整使用指南 ## 📖 完整使用指南
### 1. 项目结构准备 ### 1. 项目结构准备
@@ -112,7 +142,35 @@ module.exports = {
} }
``` ```
### 4. 创建 main.js ### 4. 创建 main.js(新版本 - 推荐)
```javascript
import AdminFramework from './libs/admin-framework.js'
// 导入业务组件(根据权限菜单接口的 component 字段)
import GamesComponent from './views/ball/games.vue'
import PayOrdersComponent from './views/order/pay_orders.vue'
// 🎉 只需一行代码!框架自动完成所有初始化
const app = AdminFramework.createApp({
title: '我的管理系统',
apiUrl: 'http://localhost:9098/admin_api/',
componentMap: {
'ball/games': GamesComponent,
'order/pay_orders': PayOrdersComponent
// 添加更多业务组件...
},
onReady() {
console.log('应用已启动!')
// 应用启动完成后的回调
}
})
// 挂载应用
app.$mount('#app')
```
### 4.1 传统方式(兼容旧版本)
```javascript ```javascript
import Vue from 'vue' import Vue from 'vue'
@@ -381,9 +439,9 @@ Vue.use(AdminFramework, {
-`system/sys_role` - 角色管理 -`system/sys_role` - 角色管理
-`system/sys_log` - 日志管理 -`system/sys_log` - 日志管理
-`system/sys_param_setup` - 参数设置 -`system/sys_param_setup` - 参数设置
-`system_high/sys_menu` - 菜单管理 -`system/sys_menu` - 菜单管理
-`system_high/sys_control` - 控制器管理 -`system/sys_control` - 控制器管理
-`system_high/sys_title` - 系统标题设置 -`system/sys_title` - 系统标题设置
## 🌐 全局访问 ## 🌐 全局访问

View File

@@ -128,9 +128,9 @@ app.$mount('#app')
- ✅ 角色管理 (`/system/role`) - ✅ 角色管理 (`/system/role`)
- ✅ 日志管理 (`/system/log`) - ✅ 日志管理 (`/system/log`)
- ✅ 参数设置 (`/system/param`) - ✅ 参数设置 (`/system/param`)
- ✅ 菜单管理 (`/system_high/menu`) - ✅ 菜单管理 (`/system/menu`)
- ✅ 权限控制 (`/system_high/control`) - ✅ 权限控制 (`/system/control`)
- ✅ 标题设置 (`/system_high/title`) - ✅ 标题设置 (`/system/title`)
### 3. 全局组件 ### 3. 全局组件
-`<Tables>` - 增强型表格 -`<Tables>` - 增强型表格