Compare commits
28 Commits
cac6e71adf
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b122a34331 | ||
|
|
463d7921c1 | ||
|
|
a147fb8a28 | ||
|
|
46e654aace | ||
|
|
d1546678e1 | ||
|
|
ff2d6004b6 | ||
|
|
ce7916c097 | ||
|
|
3e40ab372b | ||
|
|
af16450eb6 | ||
|
|
9a93a6dc99 | ||
|
|
cdd78aa057 | ||
|
|
9b5f89aa10 | ||
|
|
29efa21f0a | ||
|
|
4a8c9c4a2d | ||
|
|
ae36d6da81 | ||
|
|
2066796977 | ||
|
|
bad718ecb9 | ||
|
|
869dda515d | ||
|
|
2c7d438750 | ||
|
|
9a2ca57313 | ||
|
|
795cc37b49 | ||
|
|
5ae32a550d | ||
|
|
02c2d1a31c | ||
|
|
84563bafa1 | ||
|
|
6953193cf5 | ||
|
|
9d507c88ab | ||
|
|
c734e698de | ||
|
|
b02853d5f7 |
264
README.md
264
README.md
@@ -1,264 +0,0 @@
|
|||||||
# Admin Framework
|
|
||||||
|
|
||||||
一个基于 Vue2 的通用后台管理系统框架,包含完整的系统功能、登录、路由管理、布局等核心功能。
|
|
||||||
|
|
||||||
## ✨ 新版本亮点
|
|
||||||
|
|
||||||
**使用超级简单!只需 20 行代码即可启动完整的后台管理系统!**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// 引入框架(内置所有依赖)
|
|
||||||
import AdminFramework from './admin-framework.js'
|
|
||||||
import componentMap from './router/component-map.js'
|
|
||||||
|
|
||||||
// 一行搞定!
|
|
||||||
const app = AdminFramework.createApp({
|
|
||||||
title: '我的管理系统',
|
|
||||||
apiUrl: 'http://localhost:9098/admin_api/', // uploadUrl 会自动设置
|
|
||||||
componentMap: componentMap
|
|
||||||
})
|
|
||||||
|
|
||||||
app.$mount('#app')
|
|
||||||
```
|
|
||||||
|
|
||||||
**对比旧版本:**
|
|
||||||
- 代码量减少 **75%+**(从 80+ 行到 20 行)
|
|
||||||
- **无需手动引入** Vue、VueRouter、Vuex、ViewUI
|
|
||||||
- **无需手动配置** 路由、状态管理
|
|
||||||
- **自动处理** 菜单恢复、标题设置
|
|
||||||
|
|
||||||
👉 查看详细说明:[简化使用说明.md](./简化使用说明.md)
|
|
||||||
|
|
||||||
## 📦 项目结构
|
|
||||||
|
|
||||||
```
|
|
||||||
admin-framework/
|
|
||||||
├── src/ # 框架源码
|
|
||||||
│ ├── api/ # API 接口
|
|
||||||
│ ├── assets/ # 资源文件(样式、图片、字体)
|
|
||||||
│ ├── components/ # 全局组件
|
|
||||||
│ ├── config/ # 配置文件
|
|
||||||
│ ├── router/ # 路由配置
|
|
||||||
│ ├── store/ # Vuex 状态管理
|
|
||||||
│ ├── utils/ # 工具函数
|
|
||||||
│ ├── views/ # 页面组件
|
|
||||||
│ │ ├── home/ # 主页
|
|
||||||
│ │ ├── login/ # 登录页
|
|
||||||
│ │ ├── system/ # 系统管理页面
|
|
||||||
│ │ └── system/ # 高级系统页面
|
|
||||||
│ └── index.js # 框架入口
|
|
||||||
├── dist/ # 打包产物
|
|
||||||
│ └── admin-framework.js # 框架打包文件(3.6 MB,内置所有依赖)
|
|
||||||
├── demo-project/ # 完整示例项目 ⭐
|
|
||||||
│ ├── src/
|
|
||||||
│ │ ├── config/ # 配置
|
|
||||||
│ │ ├── libs/ # 框架文件
|
|
||||||
│ │ ├── views/ # 业务页面
|
|
||||||
│ │ ├── App.vue
|
|
||||||
│ │ └── main.js
|
|
||||||
│ ├── README.md # Demo 使用说明
|
|
||||||
│ └── INSTALL.md # 安装指南
|
|
||||||
├── webpack.config.js # 构建配置
|
|
||||||
├── package.json
|
|
||||||
└── 完整使用文档.md # 详细文档
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🚀 快速开始
|
|
||||||
|
|
||||||
### 方式一:运行 Demo 项目(推荐)⭐
|
|
||||||
|
|
||||||
**Demo 项目现已更新为使用框架源码**,更方便调试和开发!
|
|
||||||
|
|
||||||
#### 🎯 一键启动
|
|
||||||
|
|
||||||
**Windows 用户**:
|
|
||||||
```bash
|
|
||||||
cd demo-project
|
|
||||||
双击运行 start.bat
|
|
||||||
```
|
|
||||||
|
|
||||||
**Linux/Mac 用户**:
|
|
||||||
```bash
|
|
||||||
cd demo-project
|
|
||||||
chmod +x start.sh
|
|
||||||
./start.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 📝 手动启动
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. 进入 demo 项目
|
|
||||||
cd demo-project
|
|
||||||
|
|
||||||
# 2. 安装依赖(首次必须)
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# 3. 启动开发服务器
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
浏览器会自动打开 `http://localhost:8080`
|
|
||||||
|
|
||||||
#### 📚 Demo 项目文档
|
|
||||||
|
|
||||||
- [快速启动.md](./demo-project/快速启动.md) - 快速启动指南 ⭐
|
|
||||||
- [README.md](./demo-project/README.md) - 项目说明
|
|
||||||
- [CHANGELOG.md](./demo-project/CHANGELOG.md) - 更新日志(重要)
|
|
||||||
- [INSTALL.md](./demo-project/INSTALL.md) - 安装指南
|
|
||||||
- [PROJECT_STRUCTURE.md](./demo-project/PROJECT_STRUCTURE.md) - 项目结构说明
|
|
||||||
|
|
||||||
#### ⚠️ 重要提示
|
|
||||||
|
|
||||||
- Demo 项目现在**直接使用框架源码**(`../../src/index.js`)
|
|
||||||
- **首次运行必须执行** `npm install` 安装新依赖
|
|
||||||
- 如遇到错误,删除 `node_modules` 和 `package-lock.json` 后重新安装
|
|
||||||
|
|
||||||
### 方式二:构建框架
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. 安装依赖
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# 2. 构建框架
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
# 3. 产物在 dist/admin-framework.js
|
|
||||||
```
|
|
||||||
|
|
||||||
## ✨ 特性
|
|
||||||
|
|
||||||
### 核心功能
|
|
||||||
- ✅ **极简使用** - 只需 20 行代码启动完整系统
|
|
||||||
- ✅ **内置依赖** - Vue、VueRouter、Vuex、ViewUI 全部内置
|
|
||||||
- ✅ **自动配置** - 自动初始化路由、状态管理、菜单恢复
|
|
||||||
- ✅ **主页组件** - 欢迎页面,自动显示系统标题
|
|
||||||
- ✅ **系统管理页面** - 用户、角色、菜单等管理
|
|
||||||
- ✅ **登录和错误页面** - 完整的登录流程和错误处理
|
|
||||||
- ✅ **动态路由管理** - 基于权限的路由控制
|
|
||||||
- ✅ **Vuex 状态管理** - 用户、应用状态管理
|
|
||||||
- ✅ **内置样式** - base.less、animate.css、iconfont
|
|
||||||
- ✅ **工具库** - HTTP、日期、Token 等工具
|
|
||||||
|
|
||||||
## 📚 文档
|
|
||||||
|
|
||||||
- **⚡ 快速开始**:[快速开始.md](./快速开始.md) - **5 分钟上手指南**
|
|
||||||
- **🔥 简化使用说明**:[简化使用说明.md](./简化使用说明.md) - **详细对比和说明**
|
|
||||||
- **完整使用文档**:[完整使用文档.md](./完整使用文档.md)
|
|
||||||
- **Demo 项目说明**:[demo-project/README.md](./demo-project/README.md)
|
|
||||||
- **安装指南**:[demo-project/INSTALL.md](./demo-project/INSTALL.md)
|
|
||||||
|
|
||||||
## 🎯 Demo 项目预览
|
|
||||||
|
|
||||||
Demo 项目包含:
|
|
||||||
|
|
||||||
1. **登录页面**(`/login`)
|
|
||||||
- 完整的登录表单
|
|
||||||
- 自动跳转功能
|
|
||||||
|
|
||||||
2. **主页**(`/home`)
|
|
||||||
- 欢迎页面
|
|
||||||
- 显示系统标题
|
|
||||||
|
|
||||||
3. **业务示例**(`/business/product`)
|
|
||||||
- 产品列表(Table)
|
|
||||||
- CRUD 操作示例
|
|
||||||
- Modal、Message 使用
|
|
||||||
|
|
||||||
4. **系统页面**(框架内置)
|
|
||||||
- 用户管理
|
|
||||||
- 角色管理
|
|
||||||
- 菜单管理
|
|
||||||
|
|
||||||
## 🔧 使用方式
|
|
||||||
|
|
||||||
### 1. 在新项目中使用
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import Vue from 'vue'
|
|
||||||
import VueRouter from 'vue-router'
|
|
||||||
import Vuex from 'vuex'
|
|
||||||
import ViewUI from 'view-design'
|
|
||||||
import createPersistedState from 'vuex-persistedstate'
|
|
||||||
import AdminFramework from './libs/admin-framework.js'
|
|
||||||
import config from './config'
|
|
||||||
|
|
||||||
// 使用框架 - 自动完成所有初始化
|
|
||||||
Vue.use(AdminFramework, {
|
|
||||||
config,
|
|
||||||
ViewUI,
|
|
||||||
VueRouter,
|
|
||||||
Vuex,
|
|
||||||
createPersistedState
|
|
||||||
})
|
|
||||||
|
|
||||||
// 创建 Vue 实例
|
|
||||||
new Vue({
|
|
||||||
router: AdminFramework.router,
|
|
||||||
store: AdminFramework.store,
|
|
||||||
render: h => h(App)
|
|
||||||
}).$mount('#app')
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 按需使用组件
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { HomePage, SysUser, SysRole } from './libs/admin-framework.js'
|
|
||||||
|
|
||||||
// 在路由中使用
|
|
||||||
const routes = [
|
|
||||||
{ path: '/home', component: HomePage },
|
|
||||||
{ path: '/system/user', component: SysUser }
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📝 版本信息
|
|
||||||
|
|
||||||
**当前版本**: 1.0.0
|
|
||||||
|
|
||||||
**更新日志**:
|
|
||||||
- v1.0.0 - 初始版本
|
|
||||||
- ✅ 完整的系统管理功能
|
|
||||||
- ✅ 主页组件
|
|
||||||
- ✅ 登录和权限管理
|
|
||||||
- ✅ 动态路由
|
|
||||||
- ✅ 内置样式
|
|
||||||
- ✅ Demo 示例项目
|
|
||||||
|
|
||||||
## 💻 技术栈
|
|
||||||
|
|
||||||
- Vue 2.6+
|
|
||||||
- Vue Router 3.x
|
|
||||||
- Vuex 3.x
|
|
||||||
- View Design (iView) 4.x
|
|
||||||
- Axios
|
|
||||||
- Less
|
|
||||||
- Webpack 5
|
|
||||||
|
|
||||||
## 🛠️ 开发
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 安装依赖
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# 开发模式
|
|
||||||
npm run dev
|
|
||||||
|
|
||||||
# 构建生产版本
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📄 许可证
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
## 👨💻 作者
|
|
||||||
|
|
||||||
light
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**祝开发愉快!** 🎉
|
|
||||||
|
|
||||||
如有问题,请查看[完整使用文档.md](./完整使用文档.md)或查看 Demo 项目示例。
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
- ✅ **登录和权限管理** - 完整的登录流程和权限控制
|
- ✅ **登录和权限管理** - 完整的登录流程和权限控制
|
||||||
- ✅ **动态路由管理** - 基于权限菜单的动态路由生成
|
- ✅ **动态路由管理** - 基于权限菜单的动态路由生成
|
||||||
- ✅ **Vuex 状态管理** - 用户、应用状态管理
|
- ✅ **Vuex 状态管理** - 用户、应用状态管理
|
||||||
- ✅ **全局组件库** - Tables、Editor、Upload、TreeGrid、FieldRenderer 等
|
- ✅ **全局组件库** - Tables、Editor、Upload、TreeGrid、FieldRenderer、FloatPanel 等
|
||||||
- ✅ **工具库** - HTTP、日期、Token、Cookie 等工具
|
- ✅ **工具库** - HTTP、日期、Token、Cookie 等工具
|
||||||
- ✅ **内置样式** - base.less、animate.css、iconfont 等
|
- ✅ **内置样式** - base.less、animate.css、iconfont 等
|
||||||
- ✅ **响应式布局** - 支持移动端适配
|
- ✅ **响应式布局** - 支持移动端适配
|
||||||
@@ -450,6 +450,166 @@ export default {
|
|||||||
- ✅ **浏览器兼容**:支持所有现代浏览器
|
- ✅ **浏览器兼容**:支持所有现代浏览器
|
||||||
- ✅ **内存管理**:自动清理临时 URL 对象
|
- ✅ **内存管理**:自动清理临时 URL 对象
|
||||||
|
|
||||||
|
## 🎨 全局组件使用
|
||||||
|
|
||||||
|
### FloatPanel - 浮动面板组件
|
||||||
|
|
||||||
|
`FloatPanel` 是一个浮动在父窗体上的面板组件,类似于抽屉效果,常用于详情展示、表单编辑等场景。
|
||||||
|
|
||||||
|
**基本使用:**
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Button @click="showPanel">打开浮动面板</Button>
|
||||||
|
|
||||||
|
<FloatPanel
|
||||||
|
ref="floatPanel"
|
||||||
|
title="详情面板"
|
||||||
|
position="right"
|
||||||
|
:show-back="true"
|
||||||
|
back-text="返回"
|
||||||
|
@back="handleBack"
|
||||||
|
>
|
||||||
|
<div>这里是面板内容</div>
|
||||||
|
</FloatPanel>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
showPanel() {
|
||||||
|
// 通过 ref 调用 show 方法显示面板
|
||||||
|
this.$refs.floatPanel.show()
|
||||||
|
},
|
||||||
|
hidePanel() {
|
||||||
|
// 通过 ref 调用 hide 方法隐藏面板
|
||||||
|
this.$refs.floatPanel.hide()
|
||||||
|
},
|
||||||
|
handleBack() {
|
||||||
|
console.log('返回按钮被点击')
|
||||||
|
this.hidePanel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
**属性说明:**
|
||||||
|
|
||||||
|
| 属性 | 类型 | 默认值 | 说明 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| `title` | String | `''` | 面板标题 |
|
||||||
|
| `width` | String/Number | `'100%'` | 面板宽度(字符串或数字),默认占满父容器 |
|
||||||
|
| `height` | String/Number | `'100%'` | 面板高度(字符串或数字),默认占满父容器 |
|
||||||
|
| `position` | String | `'right'` | 面板位置:`left`、`right`、`top`、`bottom`、`center` |
|
||||||
|
| `showBack` | Boolean | `true` | 是否显示返回按钮 |
|
||||||
|
| `showClose` | Boolean | `false` | 是否显示关闭按钮 |
|
||||||
|
| `backText` | String | `'返回'` | 返回按钮文字 |
|
||||||
|
| `closeOnClickBackdrop` | Boolean | `false` | 点击遮罩是否关闭 |
|
||||||
|
| `mask` | Boolean | `false` | 是否显示遮罩(默认不显示) |
|
||||||
|
| `zIndex` | Number | `1000` | 层级 |
|
||||||
|
|
||||||
|
**方法:**
|
||||||
|
|
||||||
|
| 方法 | 说明 | 参数 |
|
||||||
|
|------|------|------|
|
||||||
|
| `show(callback)` | 显示面板 | `callback`: 可选的回调函数 |
|
||||||
|
| `hide()` | 隐藏面板 | - |
|
||||||
|
|
||||||
|
**事件:**
|
||||||
|
|
||||||
|
| 事件 | 说明 | 参数 |
|
||||||
|
|------|------|------|
|
||||||
|
| `back` | 点击返回按钮时触发 | - |
|
||||||
|
|
||||||
|
**插槽:**
|
||||||
|
|
||||||
|
| 插槽 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `default` | 面板主体内容 |
|
||||||
|
| `header-right` | 头部右侧内容(可用于添加自定义按钮) |
|
||||||
|
|
||||||
|
**位置说明:**
|
||||||
|
|
||||||
|
- `left`: 从左侧滑入
|
||||||
|
- `right`: 从右侧滑入(默认)
|
||||||
|
- `top`: 从顶部滑入
|
||||||
|
- `bottom`: 从底部滑入
|
||||||
|
- `center`: 居中显示,带缩放动画
|
||||||
|
|
||||||
|
**完整示例:**
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Button @click="openDetailPanel">查看详情</Button>
|
||||||
|
|
||||||
|
<FloatPanel
|
||||||
|
ref="detailPanel"
|
||||||
|
title="用户详情"
|
||||||
|
position="right"
|
||||||
|
:show-back="true"
|
||||||
|
:show-close="true"
|
||||||
|
back-text="返回"
|
||||||
|
@back="handleBack"
|
||||||
|
>
|
||||||
|
<template #header-right>
|
||||||
|
<Button type="primary" @click="handleSave">保存</Button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="detail-content">
|
||||||
|
<Form :model="formData" :label-width="100">
|
||||||
|
<FormItem label="用户名">
|
||||||
|
<Input v-model="formData.username" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="邮箱">
|
||||||
|
<Input v-model="formData.email" />
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</FloatPanel>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
formData: {
|
||||||
|
username: '',
|
||||||
|
email: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openDetailPanel() {
|
||||||
|
this.$refs.detailPanel.show()
|
||||||
|
},
|
||||||
|
handleBack() {
|
||||||
|
this.$refs.detailPanel.hide()
|
||||||
|
},
|
||||||
|
handleSave() {
|
||||||
|
// 保存逻辑
|
||||||
|
console.log('保存数据', this.formData)
|
||||||
|
this.$Message.success('保存成功')
|
||||||
|
this.$refs.detailPanel.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
**特性说明:**
|
||||||
|
|
||||||
|
- ✅ 基于父元素定位,不会遮挡菜单
|
||||||
|
- ✅ 宽度和高度默认 100%,占满父容器
|
||||||
|
- ✅ 无遮罩背景,完全浮在父页面上
|
||||||
|
- ✅ 路由切换或组件销毁时自动关闭
|
||||||
|
- ✅ 支持多种位置和动画效果
|
||||||
|
- ✅ 支持自定义头部右侧内容
|
||||||
|
|
||||||
## 📝 业务开发示例
|
## 📝 业务开发示例
|
||||||
|
|
||||||
### 创建业务页面
|
### 创建业务页面
|
||||||
178
_doc/完整使用文档.md
178
_doc/完整使用文档.md
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
## 🎯 Demo 项目
|
## 🎯 Demo 项目
|
||||||
|
|
||||||
📦 **已提供完整的示例项目**:`demo-project/`
|
📦 **已提供完整的示例项目**:`demo/`
|
||||||
|
|
||||||
一个开箱即用的完整示例,包含:
|
一个开箱即用的完整示例,包含:
|
||||||
- ✅ 框架集成配置
|
- ✅ 框架集成配置
|
||||||
@@ -18,12 +18,12 @@
|
|||||||
|
|
||||||
**快速体验**:
|
**快速体验**:
|
||||||
```bash
|
```bash
|
||||||
cd demo-project
|
cd demo
|
||||||
npm install
|
npm install
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
详细说明:[demo-project/README.md](./demo-project/README.md)
|
详细说明:[demo/README.md](./demo/README.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ npm run dev
|
|||||||
✅ **主页组件**(欢迎页面,自动显示系统标题)
|
✅ **主页组件**(欢迎页面,自动显示系统标题)
|
||||||
✅ **系统管理页面**(sys 开头的所有页面和功能)
|
✅ **系统管理页面**(sys 开头的所有页面和功能)
|
||||||
✅ **系统 API**(system 和 system 所有 API)
|
✅ **系统 API**(system 和 system 所有 API)
|
||||||
✅ **全局组件**(Tables、Editor、Upload、FieldRenderer 等)
|
✅ **全局组件**(Tables、Editor、Upload、FieldRenderer、FloatPanel 等)
|
||||||
✅ **布局组件**(Main、ParentView)
|
✅ **布局组件**(Main、ParentView)
|
||||||
✅ **文件下载**(支持 CSV 等格式,自动处理换行符)
|
✅ **文件下载**(支持 CSV 等格式,自动处理换行符)
|
||||||
✅ **登录和错误页面**(Login、401、404、500)
|
✅ **登录和错误页面**(Login、401、404、500)
|
||||||
@@ -63,11 +63,11 @@ npm run dev
|
|||||||
|
|
||||||
### 🎯 方式一:使用 Demo 项目(推荐新手)
|
### 🎯 方式一:使用 Demo 项目(推荐新手)
|
||||||
|
|
||||||
我们提供了一个完整的 **demo-project** 示例项目,可以直接运行查看效果!
|
我们提供了一个完整的 **demo** 示例项目,可以直接运行查看效果!
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. 进入 demo 项目
|
# 1. 进入 demo 项目
|
||||||
cd demo-project
|
cd demo
|
||||||
|
|
||||||
# 2. 安装依赖
|
# 2. 安装依赖
|
||||||
npm install
|
npm install
|
||||||
@@ -81,7 +81,7 @@ npm run dev
|
|||||||
- `/home` - 主页
|
- `/home` - 主页
|
||||||
- `/business/product` - 业务示例页面
|
- `/business/product` - 业务示例页面
|
||||||
|
|
||||||
**详细说明**:查看 `demo-project/README.md` 和 `demo-project/INSTALL.md`
|
**详细说明**:查看 `demo/README.md`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -174,6 +174,8 @@ const app = AdminFramework.createApp({
|
|||||||
'order/pay_orders': PayOrdersComponent
|
'order/pay_orders': PayOrdersComponent
|
||||||
// 添加更多业务组件...
|
// 添加更多业务组件...
|
||||||
},
|
},
|
||||||
|
|
||||||
|
HomePage: CustomHomePage, // 可选:自定义首页组件,覆盖整个首页
|
||||||
onReady() {
|
onReady() {
|
||||||
console.log('应用已启动!')
|
console.log('应用已启动!')
|
||||||
}
|
}
|
||||||
@@ -628,6 +630,7 @@ import {
|
|||||||
```javascript
|
```javascript
|
||||||
import {
|
import {
|
||||||
SysLog, // 系统日志管理
|
SysLog, // 系统日志管理
|
||||||
|
SysLogOperate, // 系统操作日志
|
||||||
SysParamSetup, // 参数设置
|
SysParamSetup, // 参数设置
|
||||||
SysRole, // 角色管理
|
SysRole, // 角色管理
|
||||||
SysUser // 用户管理
|
SysUser // 用户管理
|
||||||
@@ -645,7 +648,7 @@ import {
|
|||||||
|
|
||||||
#### 在路由中使用
|
#### 在路由中使用
|
||||||
```javascript
|
```javascript
|
||||||
import { HomePage, SysUser, SysRole, SysMenu } from 'admin-framework'
|
import { HomePage, SysUser, SysRole, SysMenu, SysLog, SysLogOperate } from 'admin-framework'
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
@@ -667,6 +670,16 @@ const routes = [
|
|||||||
path: '/system/menu',
|
path: '/system/menu',
|
||||||
name: 'sys_menu',
|
name: 'sys_menu',
|
||||||
component: SysMenu
|
component: SysMenu
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/system/log',
|
||||||
|
name: 'sys_log',
|
||||||
|
component: SysLog
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/system/log_operate',
|
||||||
|
name: 'sys_log_operate',
|
||||||
|
component: SysLogOperate
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
@@ -691,19 +704,35 @@ const users = await userServer.getList({ page: 1 })
|
|||||||
const roles = await roleServer.getList()
|
const roles = await roleServer.getList()
|
||||||
```
|
```
|
||||||
|
|
||||||
#### system API
|
#### system API(所有 API 都在 systemApi 中)
|
||||||
```javascript
|
```javascript
|
||||||
import { systemHighApi } from 'admin-framework'
|
import { systemApi } from 'admin-framework'
|
||||||
|
|
||||||
// 使用示例
|
// 使用示例
|
||||||
const {
|
const {
|
||||||
|
userServer,
|
||||||
|
roleServer,
|
||||||
|
sysLogServe,
|
||||||
|
fileServe,
|
||||||
menuServer,
|
menuServer,
|
||||||
paramSetupServer,
|
paramSetupServer,
|
||||||
modelServer,
|
modelServer,
|
||||||
|
formServer,
|
||||||
|
formFieldServer,
|
||||||
|
modelFieldServer,
|
||||||
|
tableServer,
|
||||||
|
rolePermissionServer,
|
||||||
|
sysControlTypeServer,
|
||||||
|
sysModuleServer,
|
||||||
|
sysAddressServer,
|
||||||
|
systemTypeServer,
|
||||||
|
plaAccountServer,
|
||||||
// ... 其他 API
|
// ... 其他 API
|
||||||
} = systemHighApi
|
} = systemApi
|
||||||
|
|
||||||
// 调用 API
|
// 调用 API
|
||||||
|
const users = await userServer.getList({ page: 1 })
|
||||||
|
const roles = await roleServer.getList()
|
||||||
const menus = await menuServer.getTree()
|
const menus = await menuServer.getTree()
|
||||||
const params = await paramSetupServer.getOne('sys_title')
|
const params = await paramSetupServer.getOne('sys_title')
|
||||||
```
|
```
|
||||||
@@ -719,10 +748,137 @@ const params = await paramSetupServer.getOne('sys_title')
|
|||||||
<Editor v-model="content" />
|
<Editor v-model="content" />
|
||||||
<UploadSingle v-model="fileUrl" />
|
<UploadSingle v-model="fileUrl" />
|
||||||
<TreeGrid :data="treeData" />
|
<TreeGrid :data="treeData" />
|
||||||
|
<FloatPanel ref="floatPanel" title="详情面板" position="right" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### FloatPanel - 浮动面板组件
|
||||||
|
|
||||||
|
`FloatPanel` 是一个浮动在父窗体上的面板组件,类似于抽屉效果,调用方式和 `AsyncModal` 类似。
|
||||||
|
|
||||||
|
**基本使用:**
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Button @click="showPanel">打开浮动面板</Button>
|
||||||
|
|
||||||
|
<FloatPanel
|
||||||
|
ref="floatPanel"
|
||||||
|
title="详情面板"
|
||||||
|
position="right"
|
||||||
|
:show-back="true"
|
||||||
|
back-text="返回"
|
||||||
|
@back="handleBack"
|
||||||
|
>
|
||||||
|
<div>这里是面板内容</div>
|
||||||
|
</FloatPanel>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
showPanel() {
|
||||||
|
this.$refs.floatPanel.show()
|
||||||
|
},
|
||||||
|
hidePanel() {
|
||||||
|
this.$refs.floatPanel.hide()
|
||||||
|
},
|
||||||
|
handleBack() {
|
||||||
|
console.log('返回按钮被点击')
|
||||||
|
this.hidePanel()
|
||||||
|
},
|
||||||
|
handleSave() {
|
||||||
|
console.log('保存')
|
||||||
|
this.hidePanel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
**调用方式(类似 AsyncModal):**
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Button @click="openPanel">打开面板</Button>
|
||||||
|
|
||||||
|
<FloatPanel ref="floatPanel" title="详情" position="right">
|
||||||
|
<div>内容</div>
|
||||||
|
</FloatPanel>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
openPanel() {
|
||||||
|
// 通过 ref 调用 show 方法
|
||||||
|
this.$refs.floatPanel.show()
|
||||||
|
},
|
||||||
|
closePanel() {
|
||||||
|
// 通过 ref 调用 hide 方法
|
||||||
|
this.$refs.floatPanel.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
**属性说明:**
|
||||||
|
|
||||||
|
| 属性 | 类型 | 默认值 | 说明 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| `title` | String | `''` | 面板标题 |
|
||||||
|
| `width` | String/Number | `'100%'` | 面板宽度(字符串或数字),默认占满父容器 |
|
||||||
|
| `height` | String/Number | `'100%'` | 面板高度(字符串或数字),默认占满父容器 |
|
||||||
|
| `position` | String | `'right'` | 面板位置:`left`、`right`、`top`、`bottom`、`center` |
|
||||||
|
| `showBack` | Boolean | `true` | 是否显示返回按钮 |
|
||||||
|
| `showClose` | Boolean | `false` | 是否显示关闭按钮 |
|
||||||
|
| `backText` | String | `'返回'` | 返回按钮文字 |
|
||||||
|
| `closeOnClickBackdrop` | Boolean | `false` | 点击遮罩是否关闭 |
|
||||||
|
| `mask` | Boolean | `false` | 是否显示遮罩(默认不显示) |
|
||||||
|
| `zIndex` | Number | `1000` | 层级 |
|
||||||
|
|
||||||
|
**方法:**
|
||||||
|
|
||||||
|
| 方法 | 说明 | 参数 |
|
||||||
|
|------|------|------|
|
||||||
|
| `show(callback)` | 显示面板 | `callback`: 可选的回调函数 |
|
||||||
|
| `hide()` | 隐藏面板 | - |
|
||||||
|
|
||||||
|
**事件:**
|
||||||
|
|
||||||
|
| 事件 | 说明 | 参数 |
|
||||||
|
|------|------|------|
|
||||||
|
| `back` | 点击返回按钮时触发 | - |
|
||||||
|
|
||||||
|
**插槽:**
|
||||||
|
|
||||||
|
| 插槽 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `default` | 面板主体内容 |
|
||||||
|
| `header-right` | 头部右侧内容 |
|
||||||
|
|
||||||
|
**位置说明:**
|
||||||
|
|
||||||
|
- `left`: 从左侧滑入
|
||||||
|
- `right`: 从右侧滑入(默认)
|
||||||
|
- `top`: 从顶部滑入
|
||||||
|
- `bottom`: 从底部滑入
|
||||||
|
- `center`: 居中显示,带缩放动画
|
||||||
|
|
||||||
|
**特性说明:**
|
||||||
|
|
||||||
|
- ✅ 基于父元素定位,不会遮挡菜单
|
||||||
|
- ✅ 宽度和高度默认 100%,占满父容器
|
||||||
|
- ✅ 无遮罩背景,完全浮在父页面上
|
||||||
|
- ✅ 路由切换或组件销毁时自动关闭
|
||||||
|
- ✅ 支持多种位置和动画效果
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🛠️ 开发指南
|
## 🛠️ 开发指南
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ app.$mount('#app')
|
|||||||
- ✅ `<AsyncModal>` - 异步弹窗
|
- ✅ `<AsyncModal>` - 异步弹窗
|
||||||
- ✅ `<Editor>` - 富文本编辑器
|
- ✅ `<Editor>` - 富文本编辑器
|
||||||
- ✅ `<CommonIcon>` - 图标选择器
|
- ✅ `<CommonIcon>` - 图标选择器
|
||||||
|
- ✅ `<FloatPanel>` - 浮动面板
|
||||||
|
|
||||||
### 4. 工具方法
|
### 4. 工具方法
|
||||||
|
|
||||||
203
demo/package-lock.json
generated
203
demo/package-lock.json
generated
@@ -21,6 +21,8 @@
|
|||||||
"css-loader": "^5.0.0",
|
"css-loader": "^5.0.0",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
"html-webpack-plugin": "^5.5.0",
|
"html-webpack-plugin": "^5.5.0",
|
||||||
|
"less": "^4.1.0",
|
||||||
|
"less-loader": "^10.0.0",
|
||||||
"style-loader": "^2.0.0",
|
"style-loader": "^2.0.0",
|
||||||
"vue-loader": "^15.9.0",
|
"vue-loader": "^15.9.0",
|
||||||
"vue-style-loader": "^4.1.0",
|
"vue-style-loader": "^4.1.0",
|
||||||
@@ -2904,6 +2906,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/copy-anything": {
|
||||||
|
"version": "2.0.6",
|
||||||
|
"resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz",
|
||||||
|
"integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-what": "^3.14.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/mesqueeb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/core-js-compat": {
|
"node_modules/core-js-compat": {
|
||||||
"version": "3.45.1",
|
"version": "3.45.1",
|
||||||
"resolved": "https://registry.npmmirror.com/core-js-compat/-/core-js-compat-3.45.1.tgz",
|
"resolved": "https://registry.npmmirror.com/core-js-compat/-/core-js-compat-3.45.1.tgz",
|
||||||
@@ -3331,6 +3346,20 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/errno": {
|
||||||
|
"version": "0.1.8",
|
||||||
|
"resolved": "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz",
|
||||||
|
"integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"prr": "~1.0.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"errno": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/es-define-property": {
|
"node_modules/es-define-property": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
@@ -4290,6 +4319,20 @@
|
|||||||
"postcss": "^8.1.0"
|
"postcss": "^8.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/image-size": {
|
||||||
|
"version": "0.5.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz",
|
||||||
|
"integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"bin": {
|
||||||
|
"image-size": "bin/image-size.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/import-local": {
|
"node_modules/import-local": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmmirror.com/import-local/-/import-local-3.2.0.tgz",
|
"resolved": "https://registry.npmmirror.com/import-local/-/import-local-3.2.0.tgz",
|
||||||
@@ -4466,6 +4509,13 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-what": {
|
||||||
|
"version": "3.14.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz",
|
||||||
|
"integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/is-wsl": {
|
"node_modules/is-wsl": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-2.2.0.tgz",
|
"resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-2.2.0.tgz",
|
||||||
@@ -4581,6 +4631,16 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/klona": {
|
||||||
|
"version": "2.0.6",
|
||||||
|
"resolved": "https://registry.npmmirror.com/klona/-/klona-2.0.6.tgz",
|
||||||
|
"integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/launch-editor": {
|
"node_modules/launch-editor": {
|
||||||
"version": "2.11.1",
|
"version": "2.11.1",
|
||||||
"resolved": "https://registry.npmmirror.com/launch-editor/-/launch-editor-2.11.1.tgz",
|
"resolved": "https://registry.npmmirror.com/launch-editor/-/launch-editor-2.11.1.tgz",
|
||||||
@@ -4592,6 +4652,80 @@
|
|||||||
"shell-quote": "^1.8.3"
|
"shell-quote": "^1.8.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/less": {
|
||||||
|
"version": "4.4.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/less/-/less-4.4.2.tgz",
|
||||||
|
"integrity": "sha512-j1n1IuTX1VQjIy3tT7cyGbX7nvQOsFLoIqobZv4ttI5axP923gA44zUj6miiA6R5Aoms4sEGVIIcucXUbRI14g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"copy-anything": "^2.0.1",
|
||||||
|
"parse-node-version": "^1.0.1",
|
||||||
|
"tslib": "^2.3.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"lessc": "bin/lessc"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"errno": "^0.1.1",
|
||||||
|
"graceful-fs": "^4.1.2",
|
||||||
|
"image-size": "~0.5.0",
|
||||||
|
"make-dir": "^2.1.0",
|
||||||
|
"mime": "^1.4.1",
|
||||||
|
"needle": "^3.1.0",
|
||||||
|
"source-map": "~0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/less-loader": {
|
||||||
|
"version": "10.2.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/less-loader/-/less-loader-10.2.0.tgz",
|
||||||
|
"integrity": "sha512-AV5KHWvCezW27GT90WATaDnfXBv99llDbtaj4bshq6DvAihMdNjaPDcUMa6EXKLRF+P2opFenJp89BXg91XLYg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"klona": "^2.0.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.13.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/webpack"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"less": "^3.5.0 || ^4.0.0",
|
||||||
|
"webpack": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/less/node_modules/make-dir": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"pify": "^4.0.1",
|
||||||
|
"semver": "^5.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/less/node_modules/semver": {
|
||||||
|
"version": "5.7.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz",
|
||||||
|
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"optional": true,
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/loader-runner": {
|
"node_modules/loader-runner": {
|
||||||
"version": "4.3.1",
|
"version": "4.3.1",
|
||||||
"resolved": "https://registry.npmmirror.com/loader-runner/-/loader-runner-4.3.1.tgz",
|
"resolved": "https://registry.npmmirror.com/loader-runner/-/loader-runner-4.3.1.tgz",
|
||||||
@@ -4886,6 +5020,38 @@
|
|||||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/needle": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/needle/-/needle-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"iconv-lite": "^0.6.3",
|
||||||
|
"sax": "^1.2.4"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"needle": "bin/needle"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4.4.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/needle/node_modules/iconv-lite": {
|
||||||
|
"version": "0.6.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
|
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/negotiator": {
|
"node_modules/negotiator": {
|
||||||
"version": "0.6.4",
|
"version": "0.6.4",
|
||||||
"resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.4.tgz",
|
"resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.4.tgz",
|
||||||
@@ -5118,6 +5284,16 @@
|
|||||||
"tslib": "^2.0.3"
|
"tslib": "^2.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/parse-node-version": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/parseurl": {
|
"node_modules/parseurl": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz",
|
"resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
@@ -5202,6 +5378,17 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pify": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pkg-dir": {
|
"node_modules/pkg-dir": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-4.2.0.tgz",
|
"resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-4.2.0.tgz",
|
||||||
@@ -5396,6 +5583,14 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prr": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/pseudomap": {
|
"node_modules/pseudomap": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmmirror.com/pseudomap/-/pseudomap-1.0.2.tgz",
|
"resolved": "https://registry.npmmirror.com/pseudomap/-/pseudomap-1.0.2.tgz",
|
||||||
@@ -5704,6 +5899,14 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/sax": {
|
||||||
|
"version": "1.4.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.3.tgz",
|
||||||
|
"integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BlueOak-1.0.0",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/schema-utils": {
|
"node_modules/schema-utils": {
|
||||||
"version": "2.7.1",
|
"version": "2.7.1",
|
||||||
"resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-2.7.1.tgz",
|
"resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-2.7.1.tgz",
|
||||||
|
|||||||
@@ -21,6 +21,8 @@
|
|||||||
"css-loader": "^5.0.0",
|
"css-loader": "^5.0.0",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
"html-webpack-plugin": "^5.5.0",
|
"html-webpack-plugin": "^5.5.0",
|
||||||
|
"less": "^4.1.0",
|
||||||
|
"less-loader": "^10.0.0",
|
||||||
"style-loader": "^2.0.0",
|
"style-loader": "^2.0.0",
|
||||||
"vue-loader": "^15.9.0",
|
"vue-loader": "^15.9.0",
|
||||||
"vue-style-loader": "^4.1.0",
|
"vue-style-loader": "^4.1.0",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// 引入 Admin Framework(框架内部已包含所有依赖和样式)
|
// 开发环境直接引用源码以支持热更新
|
||||||
import AdminFramework from '../../dist/admin-framework.js'
|
import AdminFramework from '../../dist/admin-framework.js'
|
||||||
|
|
||||||
// 引入组件映射表
|
// 引入组件映射表
|
||||||
|
|||||||
@@ -21,6 +21,51 @@
|
|||||||
@changePage="query"></tables>
|
@changePage="query"></tables>
|
||||||
</div>
|
</div>
|
||||||
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules"> </editModal>
|
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules"> </editModal>
|
||||||
|
|
||||||
|
<FloatPanel ref="floatPanel" title="城市详情" position="right">
|
||||||
|
<div v-if="currentRow" class="detail-content">
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>ID:</label>
|
||||||
|
<span>{{ currentRow.id }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>城市名称(中文):</label>
|
||||||
|
<span>{{ currentRow.cn_city }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>城市代码:</label>
|
||||||
|
<span>{{ currentRow.city_code }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>省份(中文):</label>
|
||||||
|
<span>{{ currentRow.cn_state || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>国家(中文):</label>
|
||||||
|
<span>{{ currentRow.cn_country || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>城市名称(英文):</label>
|
||||||
|
<span>{{ currentRow.city || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>省份(英文):</label>
|
||||||
|
<span>{{ currentRow.state || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>国家(英文):</label>
|
||||||
|
<span>{{ currentRow.country || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>省份代码:</label>
|
||||||
|
<span>{{ currentRow.state_code || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>国家代码:</label>
|
||||||
|
<span>{{ currentRow.country_code || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FloatPanel>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
@@ -36,6 +81,7 @@ export default {
|
|||||||
rules["cn_country"] = [{ required: false, message: '请填写国家' }];
|
rules["cn_country"] = [{ required: false, message: '请填写国家' }];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
currentRow: null,
|
||||||
seachTypes: [
|
seachTypes: [
|
||||||
{ key: 'cn_city', value: '城市名称' },
|
{ key: 'cn_city', value: '城市名称' },
|
||||||
{ key: 'city_code', value: '城市代码' },
|
{ key: 'city_code', value: '城市代码' },
|
||||||
@@ -69,10 +115,17 @@ export default {
|
|||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
width: 200,
|
width: 250,
|
||||||
type: 'template',
|
type: 'template',
|
||||||
render: (h, params) => {
|
render: (h, params) => {
|
||||||
let btns = [
|
let btns = [
|
||||||
|
{
|
||||||
|
title: '详情',
|
||||||
|
type: 'info',
|
||||||
|
click: () => {
|
||||||
|
this.showDetail(params.row)
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '编辑',
|
title: '编辑',
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
@@ -138,7 +191,43 @@ export default {
|
|||||||
wch_citiesServer.exportCsv(this.gridOption.param).then(res => {
|
wch_citiesServer.exportCsv(this.gridOption.param).then(res => {
|
||||||
tools.downloadFile(res, '城市管理.csv');
|
tools.downloadFile(res, '城市管理.csv');
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
showDetail(row) {
|
||||||
|
this.currentRow = row
|
||||||
|
this.$refs.floatPanel.show()
|
||||||
|
},
|
||||||
|
hideDetail() {
|
||||||
|
this.$refs.floatPanel.hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.detail-content {
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
.detail-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
min-width: 120px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #515a6e;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
flex: 1;
|
||||||
|
color: #17233d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ module.exports = {
|
|||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, 'dist'),
|
path: path.resolve(__dirname, 'dist'),
|
||||||
filename: 'app.js',
|
filename: 'app.js',
|
||||||
clean: true
|
clean: true,
|
||||||
|
publicPath: '/'
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@@ -24,6 +25,10 @@ module.exports = {
|
|||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
use: ['vue-style-loader', 'css-loader']
|
use: ['vue-style-loader', 'css-loader']
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: /\.less$/,
|
||||||
|
use: ['vue-style-loader', 'css-loader', 'less-loader']
|
||||||
|
},
|
||||||
{
|
{
|
||||||
test: /\.(png|jpe?g|gif|svg|woff2?|eot|ttf|otf)$/,
|
test: /\.(png|jpe?g|gif|svg|woff2?|eot|ttf|otf)$/,
|
||||||
type: 'asset/resource'
|
type: 'asset/resource'
|
||||||
@@ -45,7 +50,7 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
devServer: {
|
devServer: {
|
||||||
hot: true,
|
hot: true, // 启用热更新
|
||||||
open: true,
|
open: true,
|
||||||
port: 8080,
|
port: 8080,
|
||||||
historyApiFallback: true
|
historyApiFallback: true
|
||||||
|
|||||||
346
src/components/FloatPanel/index.vue
Normal file
346
src/components/FloatPanel/index.vue
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
<template>
|
||||||
|
<transition name="float-panel">
|
||||||
|
<div v-if="showPanel" class="float-panel-wrapper" @click.self="handleBackdropClick">
|
||||||
|
<div class="float-panel" :class="panelClass" :style="panelStyle">
|
||||||
|
<div class="float-panel-header">
|
||||||
|
<div class="header-left">
|
||||||
|
<Button v-if="showBack" type="text" icon="ios-arrow-back" @click="handleBack">
|
||||||
|
{{ backText }}
|
||||||
|
</Button>
|
||||||
|
<span v-if="title" class="panel-title">{{ title }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<slot name="header-right"></slot>
|
||||||
|
<Button v-if="showClose" type="text" icon="ios-close" @click="hide"></Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="float-panel-body">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'FloatPanel',
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: '100%'
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: '100%'
|
||||||
|
},
|
||||||
|
position: {
|
||||||
|
type: String,
|
||||||
|
default: 'right', // left, right, top, bottom, center
|
||||||
|
validator: (value) => ['left', 'right', 'top', 'bottom', 'center'].includes(value)
|
||||||
|
},
|
||||||
|
showBack: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
showClose: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
backText: {
|
||||||
|
type: String,
|
||||||
|
default: '返回'
|
||||||
|
},
|
||||||
|
closeOnClickBackdrop: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
mask: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
zIndex: {
|
||||||
|
type: Number,
|
||||||
|
default: 1000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showPanel: false,
|
||||||
|
callback: null,
|
||||||
|
parentElement: null,
|
||||||
|
originalParentPosition: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
panelClass() {
|
||||||
|
return {
|
||||||
|
[`float-panel-${this.position}`]: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
panelStyle() {
|
||||||
|
const style = {
|
||||||
|
zIndex: this.zIndex,
|
||||||
|
width: typeof this.width === 'number' ? `${this.width}px` : this.width,
|
||||||
|
height: typeof this.height === 'number' ? `${this.height}px` : this.height
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
show(callback) {
|
||||||
|
this.ensureParentPosition()
|
||||||
|
// 使用 requestAnimationFrame 确保在下一帧渲染时显示,避免闪烁
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.showPanel = true
|
||||||
|
this.callback = callback
|
||||||
|
})
|
||||||
|
},
|
||||||
|
hide() {
|
||||||
|
this.showPanel = false
|
||||||
|
this.callback = null
|
||||||
|
// 延迟恢复父容器定位,等待动画完成
|
||||||
|
setTimeout(() => {
|
||||||
|
this.restoreParentPosition()
|
||||||
|
}, 300)
|
||||||
|
},
|
||||||
|
ensureParentPosition() {
|
||||||
|
// 确保父容器有 position: relative,提前设置避免闪烁
|
||||||
|
if (!this.parentElement) {
|
||||||
|
// 在组件挂载后立即获取父元素
|
||||||
|
if (this.$el && this.$el.parentElement) {
|
||||||
|
this.parentElement = this.$el.parentElement
|
||||||
|
} else {
|
||||||
|
// 如果组件还未挂载,使用 $nextTick
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.$el && this.$el.parentElement) {
|
||||||
|
this.parentElement = this.$el.parentElement
|
||||||
|
}
|
||||||
|
this.setParentPosition()
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.setParentPosition()
|
||||||
|
},
|
||||||
|
setParentPosition() {
|
||||||
|
if (this.parentElement) {
|
||||||
|
const computedStyle = window.getComputedStyle(this.parentElement)
|
||||||
|
const position = computedStyle.position
|
||||||
|
if (position === 'static' || !position) {
|
||||||
|
this.originalParentPosition = this.parentElement.style.position || ''
|
||||||
|
// 使用 will-change 优化性能
|
||||||
|
this.parentElement.style.willChange = 'transform'
|
||||||
|
this.parentElement.style.position = 'relative'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
restoreParentPosition() {
|
||||||
|
// 恢复父容器的原始定位
|
||||||
|
if (this.parentElement && this.originalParentPosition !== null) {
|
||||||
|
if (this.originalParentPosition) {
|
||||||
|
this.parentElement.style.position = this.originalParentPosition
|
||||||
|
} else {
|
||||||
|
this.parentElement.style.position = ''
|
||||||
|
}
|
||||||
|
this.parentElement.style.willChange = ''
|
||||||
|
this.originalParentPosition = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleBack() {
|
||||||
|
this.$emit('back')
|
||||||
|
this.hide()
|
||||||
|
},
|
||||||
|
handleBackdropClick() {
|
||||||
|
if (this.closeOnClickBackdrop) {
|
||||||
|
this.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
showPanel(newVal) {
|
||||||
|
// 不再控制 body 的 overflow,让父容器控制
|
||||||
|
},
|
||||||
|
// 监听路由变化
|
||||||
|
'$route'(to, from) {
|
||||||
|
if (this.showPanel) {
|
||||||
|
this.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// 组件挂载后立即获取父元素并设置定位,避免后续闪烁
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.$el && this.$el.parentElement) {
|
||||||
|
this.parentElement = this.$el.parentElement
|
||||||
|
// 预先设置 position,避免显示时闪烁
|
||||||
|
this.setParentPosition()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// 组件销毁时关闭面板并清理
|
||||||
|
if (this.showPanel) {
|
||||||
|
this.hide()
|
||||||
|
} else {
|
||||||
|
this.restoreParentPosition()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.float-panel-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
/* 使用 will-change 优化性能,避免重排 */
|
||||||
|
will-change: transform;
|
||||||
|
/* 使用 transform 代替 position,避免触发重排 */
|
||||||
|
transform: translateZ(0);
|
||||||
|
backface-visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-panel {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: #fff;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
/* 使用 GPU 加速,避免闪烁 */
|
||||||
|
will-change: transform;
|
||||||
|
transform: translateZ(0);
|
||||||
|
backface-visibility: hidden;
|
||||||
|
|
||||||
|
&.float-panel-right {
|
||||||
|
animation: slideInRight 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.float-panel-left {
|
||||||
|
animation: slideInLeft 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.float-panel-top {
|
||||||
|
animation: slideInTop 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.float-panel-bottom {
|
||||||
|
animation: slideInBottom 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.float-panel-center {
|
||||||
|
animation: fadeInScale 0.3s ease-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-panel-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.panel-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #17233d;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-panel-body {
|
||||||
|
flex: 1;
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 动画效果
|
||||||
|
.float-panel-enter-active,
|
||||||
|
.float-panel-leave-active {
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
/* 使用 transform 代替 opacity,性能更好 */
|
||||||
|
will-change: transform, opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-panel-enter,
|
||||||
|
.float-panel-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
/* 使用 transform 避免重排 */
|
||||||
|
transform: translateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInRight {
|
||||||
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInLeft {
|
||||||
|
from {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInTop {
|
||||||
|
from {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInBottom {
|
||||||
|
from {
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInScale {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import Main from './main'
|
import Main from './main'
|
||||||
import ParentView from './parent-view'
|
import ParentView from './parent-view'
|
||||||
|
|
||||||
|
|
||||||
// 导入页面组件
|
// 导入页面组件
|
||||||
import pages from '../views/index'
|
import pages from '../views/index'
|
||||||
const {LoginPage,Page401,Page404,Page500} = pages
|
const {LoginPage,Page401,Page404,Page500} = pages
|
||||||
@@ -19,6 +20,7 @@ import Editor from './editor/index.vue'
|
|||||||
import editModal from './tables/editModal.vue'
|
import editModal from './tables/editModal.vue'
|
||||||
import fieldItem from './tables/fieldItem.vue'
|
import fieldItem from './tables/fieldItem.vue'
|
||||||
import FieldRenderer from './tables/fieldRenderer.vue'
|
import FieldRenderer from './tables/fieldRenderer.vue'
|
||||||
|
import FloatPanel from './FloatPanel/index.vue'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -48,6 +50,7 @@ const registerGlobalComponents = (Vue) => {
|
|||||||
Vue.component('editModal', editModal)
|
Vue.component('editModal', editModal)
|
||||||
Vue.component('fieldItem', fieldItem)
|
Vue.component('fieldItem', fieldItem)
|
||||||
Vue.component('FieldRenderer', FieldRenderer)
|
Vue.component('FieldRenderer', FieldRenderer)
|
||||||
|
Vue.component('FloatPanel', FloatPanel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注册自定义组件的方法
|
// 注册自定义组件的方法
|
||||||
@@ -80,7 +83,7 @@ export default {
|
|||||||
editModal,
|
editModal,
|
||||||
fieldItem,
|
fieldItem,
|
||||||
FieldRenderer,
|
FieldRenderer,
|
||||||
|
FloatPanel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.header-con {
|
.header-con {
|
||||||
padding: 0px 10px;
|
padding: 0px 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -20,16 +22,37 @@
|
|||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.main-content-con {
|
.main-content-con {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: darkgray;
|
color: darkgray;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-layout-con {
|
.main-layout-con {
|
||||||
display: flex;
|
display: flex;
|
||||||
display: -webkit-flex;
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-con-main {
|
||||||
|
height: 64px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-layout-content {
|
||||||
|
height: calc(100% - 64px);
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
|
|
||||||
<Layout style="height: 100%" class="main">
|
<Layout style="height: 100%; display: flex; flex-direction: row;" class="main">
|
||||||
|
|
||||||
<Sider hide-trigger collapsible :width="256" :collapsed-width="64" v-model="collapsed" class="left-sider" :style="{overflow: 'hidden'}">
|
<Sider hide-trigger collapsible :width="256" :collapsed-width="64" v-model="collapsed" class="left-sider" :style="{overflow: 'hidden'}">
|
||||||
<div class="sidebar-brand" @click="goHome" v-if="!collapsed">
|
<div class="sidebar-brand" @click="goHome" v-if="!collapsed">
|
||||||
@@ -14,13 +14,13 @@
|
|||||||
<Content class="main-content-con">
|
<Content class="main-content-con">
|
||||||
<Layout class="main-layout-con">
|
<Layout class="main-layout-con">
|
||||||
|
|
||||||
<Header>
|
<Header class="header-con-main">
|
||||||
<headerBar class="header-con" @on-coll-change="collpasedChange" :collapsed="collapsed">
|
<headerBar class="header-con" @on-coll-change="collpasedChange" :collapsed="collapsed">
|
||||||
<Terminal></Terminal>
|
<Terminal></Terminal>
|
||||||
<user :userName="userName" :user-avator="userAvator || ''" />
|
<user :userName="userName" :user-avator="userAvator || ''" />
|
||||||
</headerBar>
|
</headerBar>
|
||||||
</Header>
|
</Header>
|
||||||
<Layout>
|
<Layout class="main-layout-content">
|
||||||
<Content class="content-wrapper">
|
<Content class="content-wrapper">
|
||||||
|
|
||||||
<router-view />
|
<router-view />
|
||||||
|
|||||||
@@ -91,11 +91,19 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
// 处理字段输入事件
|
// 处理字段输入事件
|
||||||
handleFieldInput(key, value) {
|
handleFieldInput(key, value) {
|
||||||
|
// 确保接收到的是值而不是事件对象
|
||||||
|
if (value && typeof value === 'object' && value.target) {
|
||||||
|
value = value.target.value
|
||||||
|
}
|
||||||
this.$set(this.row, key, value)
|
this.$set(this.row, key, value)
|
||||||
},
|
},
|
||||||
|
|
||||||
// 处理字段变化事件
|
// 处理字段变化事件
|
||||||
handleFieldChange(key, value) {
|
handleFieldChange(key, value) {
|
||||||
|
// 确保接收到的是值而不是事件对象
|
||||||
|
if (value && typeof value === 'object' && value.target) {
|
||||||
|
value = value.target.value
|
||||||
|
}
|
||||||
this.$set(this.row, key, value)
|
this.$set(this.row, key, value)
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -186,15 +194,23 @@ export default {
|
|||||||
.forEach((col) => {
|
.forEach((col) => {
|
||||||
let defaultVal = row[col.key]
|
let defaultVal = row[col.key]
|
||||||
|
|
||||||
|
// 如果值是 undefined 或 null,不赋值,保持原状态
|
||||||
|
if (defaultVal === undefined || defaultVal === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 传递过来什么值就保持什么值
|
||||||
if (col.data_type === 'number') {
|
if (col.data_type === 'number') {
|
||||||
defaultVal = parseFloat(defaultVal) || 0
|
// 保持原始数值,包括 0
|
||||||
Vue.set(this.row, col.key, defaultVal)
|
const numVal = parseFloat(defaultVal)
|
||||||
|
Vue.set(this.row, col.key, isNaN(numVal) ? defaultVal : numVal)
|
||||||
} else if (col.data_type === 'date') {
|
} else if (col.data_type === 'date') {
|
||||||
Vue.set(this.row, col.key, dayjs(defaultVal).toDate())
|
Vue.set(this.row, col.key, dayjs(defaultVal).toDate())
|
||||||
} else if (col.data_type === 'boolean') {
|
} else if (col.data_type === 'boolean') {
|
||||||
Vue.set(this.row, col.key, defaultVal === 1 || defaultVal === true)
|
// 保持原始布尔值,包括 false
|
||||||
|
Vue.set(this.row, col.key, defaultVal === 1 || defaultVal === true || defaultVal === '1')
|
||||||
} else {
|
} else {
|
||||||
defaultVal = defaultVal !== undefined ? defaultVal : ''
|
// 保持原始值,包括空字符串 ''
|
||||||
Vue.set(this.row, col.key, defaultVal)
|
Vue.set(this.row, col.key, defaultVal)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div style="width: 100%;">
|
||||||
<!-- 使用组件映射表来简化条件渲染 -->
|
<!-- 使用组件映射表来简化条件渲染 -->
|
||||||
<component
|
<component
|
||||||
:is="getComponentName(col.com)"
|
:is="getComponentName(col.com)"
|
||||||
v-model="value"
|
:value="col.com === 'Radio' ? radioValue : value"
|
||||||
v-bind="getComponentProps(col)"
|
v-bind="getComponentProps(col)"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:class="getComponentClass(col.com)"
|
:class="getComponentClass(col.com)"
|
||||||
:style="getComponentStyle(col.com)"
|
:style="getComponentStyle(col.com)"
|
||||||
@input="handleInput"
|
@input="handleInput"
|
||||||
@change="handleChange"
|
@on-change="handleChange"
|
||||||
>
|
>
|
||||||
<!-- Select 组件的选项 -->
|
<!-- Select 组件的选项 -->
|
||||||
<template v-if="col.com === 'Select'">
|
<template v-if="col.com === 'Select'">
|
||||||
@@ -25,11 +25,11 @@
|
|||||||
<!-- Radio 组件的选项 -->
|
<!-- Radio 组件的选项 -->
|
||||||
<template v-else-if="col.com === 'Radio'">
|
<template v-else-if="col.com === 'Radio'">
|
||||||
<Radio
|
<Radio
|
||||||
:label="item.key"
|
:label="getRadioValue(item)"
|
||||||
:key="item.key"
|
:key="getRadioKey(item)"
|
||||||
v-for="item in col.source"
|
v-for="item in getRadioSource(col)"
|
||||||
>
|
>
|
||||||
{{ item.value }}
|
{{ getRadioLabel(item) }}
|
||||||
</Radio>
|
</Radio>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -92,6 +92,12 @@ export default {
|
|||||||
icons: []
|
icons: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
// 直接返回原始值,不进行类型转换
|
||||||
|
radioValue() {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// 安全处理图标数据
|
// 安全处理图标数据
|
||||||
if (Array.isArray(icons)) {
|
if (Array.isArray(icons)) {
|
||||||
@@ -135,6 +141,7 @@ export default {
|
|||||||
// 移除不需要传递给组件的属性
|
// 移除不需要传递给组件的属性
|
||||||
delete baseProps.com
|
delete baseProps.com
|
||||||
delete baseProps.source
|
delete baseProps.source
|
||||||
|
delete baseProps.options
|
||||||
delete baseProps.editRender
|
delete baseProps.editRender
|
||||||
delete baseProps.inLine
|
delete baseProps.inLine
|
||||||
delete baseProps.display
|
delete baseProps.display
|
||||||
@@ -162,6 +169,59 @@ export default {
|
|||||||
return baseProps
|
return baseProps
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 获取 Radio 数据源(支持 source 和 options 两种格式)
|
||||||
|
getRadioSource(col) {
|
||||||
|
return col.source || col.options || []
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取 Radio 选项的值(支持多种数据格式)
|
||||||
|
getRadioValue(item) {
|
||||||
|
// 支持 { key: value, value: label } 格式
|
||||||
|
if (item.key !== undefined) {
|
||||||
|
return item.key
|
||||||
|
}
|
||||||
|
// 支持 { value: value, label: label } 格式
|
||||||
|
if (item.value !== undefined) {
|
||||||
|
return item.value
|
||||||
|
}
|
||||||
|
// 支持字符串数组
|
||||||
|
if (typeof item === 'string' || typeof item === 'number') {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取 Radio 选项的 key(用于 v-for)
|
||||||
|
getRadioKey(item) {
|
||||||
|
if (item.key !== undefined) {
|
||||||
|
return item.key
|
||||||
|
}
|
||||||
|
if (item.value !== undefined) {
|
||||||
|
return item.value
|
||||||
|
}
|
||||||
|
if (typeof item === 'string' || typeof item === 'number') {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
return JSON.stringify(item)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取 Radio 选项的显示文本
|
||||||
|
getRadioLabel(item) {
|
||||||
|
// 支持 { key: value, value: label } 格式
|
||||||
|
if (item.value !== undefined && typeof item.value === 'string') {
|
||||||
|
return item.value
|
||||||
|
}
|
||||||
|
// 支持 { value: value, label: label } 格式
|
||||||
|
if (item.label !== undefined) {
|
||||||
|
return item.label
|
||||||
|
}
|
||||||
|
// 支持字符串数组
|
||||||
|
if (typeof item === 'string' || typeof item === 'number') {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
return item.value || item.key || item
|
||||||
|
},
|
||||||
|
|
||||||
// 获取组件样式类
|
// 获取组件样式类
|
||||||
getComponentClass(componentType) {
|
getComponentClass(componentType) {
|
||||||
const classMap = {
|
const classMap = {
|
||||||
@@ -184,11 +244,21 @@ export default {
|
|||||||
|
|
||||||
// 处理输入事件
|
// 处理输入事件
|
||||||
handleInput(value) {
|
handleInput(value) {
|
||||||
|
// 如果接收到的是事件对象,提取值
|
||||||
|
if (value && typeof value === 'object' && value.target) {
|
||||||
|
value = value.target.value
|
||||||
|
}
|
||||||
|
// 直接传递值,不进行类型转换
|
||||||
this.$emit('input', value)
|
this.$emit('input', value)
|
||||||
},
|
},
|
||||||
|
|
||||||
// 处理变化事件
|
// 处理变化事件
|
||||||
handleChange(value) {
|
handleChange(value) {
|
||||||
|
// 如果接收到的是事件对象,提取值
|
||||||
|
if (value && typeof value === 'object' && value.target) {
|
||||||
|
value = value.target.value
|
||||||
|
}
|
||||||
|
// 直接传递值,不进行类型转换
|
||||||
this.$emit('change', value)
|
this.$emit('change', value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,13 +90,27 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
insideColumns() {
|
insideColumns() {
|
||||||
let columns = this.columns.map((item, index) => {
|
// 确保 columns 是数组
|
||||||
// 设置默认对齐方式
|
if (!Array.isArray(this.columns) || this.columns.length === 0) {
|
||||||
if (!item.align) {
|
return []
|
||||||
item.align = 'center'
|
}
|
||||||
|
|
||||||
if (item.children && item.children.length > 0) {
|
let columns = this.columns.map((item, index) => {
|
||||||
item.children = item.children.map((sonItem) => {
|
// 确保 item 是对象
|
||||||
|
if (!item || typeof item !== 'object') {
|
||||||
|
console.warn(`[Tables] 列配置第 ${index} 项格式不正确:`, item)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新对象,避免直接修改原对象
|
||||||
|
let column = { ...item }
|
||||||
|
|
||||||
|
// 设置默认对齐方式
|
||||||
|
if (!column.align) {
|
||||||
|
column.align = 'center'
|
||||||
|
|
||||||
|
if (column.children && column.children.length > 0) {
|
||||||
|
column.children = column.children.map((sonItem) => {
|
||||||
if (!sonItem.align) {
|
if (!sonItem.align) {
|
||||||
sonItem.align = 'center'
|
sonItem.align = 'center'
|
||||||
}
|
}
|
||||||
@@ -107,12 +121,12 @@ export default {
|
|||||||
|
|
||||||
// 确保列宽设置正确,优先使用 width,其次使用 minWidth
|
// 确保列宽设置正确,优先使用 width,其次使用 minWidth
|
||||||
// 如果都没有设置,则设置默认 minWidth
|
// 如果都没有设置,则设置默认 minWidth
|
||||||
if (!item.width && !item.minWidth) {
|
if (!column.width && !column.minWidth) {
|
||||||
item.minWidth = 120
|
column.minWidth = 120
|
||||||
}
|
}
|
||||||
|
|
||||||
return item
|
return column
|
||||||
})
|
}).filter(item => item !== null) // 过滤掉无效的列配置
|
||||||
|
|
||||||
return columns
|
return columns
|
||||||
},
|
},
|
||||||
@@ -139,11 +153,27 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
handleTableData() {
|
handleTableData() {
|
||||||
let values = this.value || []
|
// 确保 value 是数组
|
||||||
let data = values.filter((p) => p)
|
let values = Array.isArray(this.value) ? this.value : []
|
||||||
this.insideTableData = data.map((item, index) => {
|
|
||||||
let res = item
|
// 过滤掉 null 和 undefined,但保留其他 falsy 值(如 0、false、空字符串等)
|
||||||
return res
|
let data = values.filter((p) => p !== null && p !== undefined)
|
||||||
|
|
||||||
|
// 使用 $set 确保响应式更新
|
||||||
|
this.$set(this, 'insideTableData', data.map((item, index) => {
|
||||||
|
// 确保每个数据项都是对象
|
||||||
|
if (typeof item !== 'object' || item === null) {
|
||||||
|
return { value: item }
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
}))
|
||||||
|
|
||||||
|
// 强制更新表格(如果表格已渲染)
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.$refs.tablesMain) {
|
||||||
|
// 触发表格重新渲染
|
||||||
|
this.$refs.tablesMain.$forceUpdate()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onChangePage(page) {
|
onChangePage(page) {
|
||||||
@@ -177,11 +207,28 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
value(val) {
|
// 监听 value 变化
|
||||||
|
value: {
|
||||||
|
handler(val) {
|
||||||
this.handleTableData()
|
this.handleTableData()
|
||||||
},
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
// 监听 columns 变化,确保列配置更新时表格重新渲染
|
||||||
|
columns: {
|
||||||
|
handler() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.$refs.tablesMain) {
|
||||||
|
this.$refs.tablesMain.$forceUpdate()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
// 确保数据已处理
|
||||||
this.handleTableData()
|
this.handleTableData()
|
||||||
|
|
||||||
// 自适应高度
|
// 自适应高度
|
||||||
@@ -196,6 +243,14 @@ export default {
|
|||||||
|
|
||||||
// 动态计算偏移量
|
// 动态计算偏移量
|
||||||
this.calculateOffset()
|
this.calculateOffset()
|
||||||
|
|
||||||
|
// 如果数据或列配置为空,输出警告
|
||||||
|
if (!this.insideTableData || this.insideTableData.length === 0) {
|
||||||
|
console.warn('[Tables] 表格数据为空,请检查 value 属性是否正确绑定')
|
||||||
|
}
|
||||||
|
if (!this.columns || this.columns.length === 0) {
|
||||||
|
console.warn('[Tables] 表格列配置为空,请检查 columns 属性是否正确绑定')
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 监听窗口大小变化,重新计算偏移量
|
// 监听窗口大小变化,重新计算偏移量
|
||||||
|
|||||||
@@ -15,8 +15,19 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
colStyle(col) {
|
colStyle(col) {
|
||||||
const width = col && col.width ? col.width : '150px'
|
// 为不同类型的列设置合适的默认宽度
|
||||||
return { width }
|
let defaultWidth = '150px'
|
||||||
|
if (col && col.type === 'operation') {
|
||||||
|
defaultWidth = '200px' // 操作列需要更多空间
|
||||||
|
} else if (col && col.key === 'name' || col.key === 'title') {
|
||||||
|
defaultWidth = '200px' // 名称列通常需要更多空间
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = col && col.width ? col.width : defaultWidth
|
||||||
|
return {
|
||||||
|
width,
|
||||||
|
minWidth: col.minWidth || width
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<table cellspacing="0" width="100%" cellpadding="0" border="0">
|
<table cellspacing="0" cellpadding="0" border="0" style="width: 100%; min-width: inherit;">
|
||||||
<SubColmns :columns="columns"></SubColmns>
|
<SubColmns :columns="columns"></SubColmns>
|
||||||
<tbody class="endTbody" v-for="(row, index) in subData" :key="row.id">
|
<tbody class="endTbody" v-for="(row, index) in subData" :key="row.id">
|
||||||
|
|
||||||
@@ -8,10 +8,7 @@
|
|||||||
{ 'td-expand': sonColIndex === 0 },
|
{ 'td-expand': sonColIndex === 0 },
|
||||||
{ 'td-operations': col.type === 'operation' },
|
{ 'td-operations': col.type === 'operation' },
|
||||||
col.className
|
col.className
|
||||||
]" :style="{
|
]" :style="getCellStyle(col, sonColIndex)">
|
||||||
width: col.width || 'auto',
|
|
||||||
minWidth: col.minWidth || (sonColIndex === 0 ? '200px' : '100px')
|
|
||||||
}">
|
|
||||||
<RenderCol v-if="col.render" :col="col.render" :param="{ row, index, col }" />
|
<RenderCol v-if="col.render" :col="col.render" :param="{ row, index, col }" />
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="first-box" v-if="sonColIndex == 1">
|
<div class="first-box" v-if="sonColIndex == 1">
|
||||||
@@ -73,6 +70,30 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
// 获取单元格样式
|
||||||
|
getCellStyle(col, colIndex) {
|
||||||
|
// 为不同类型的列设置合适的默认宽度
|
||||||
|
let defaultWidth = '150px'
|
||||||
|
let defaultMinWidth = '100px'
|
||||||
|
|
||||||
|
if (col.type === 'operation') {
|
||||||
|
defaultWidth = '200px'
|
||||||
|
defaultMinWidth = '180px'
|
||||||
|
} else if (col.key === 'name' || col.key === 'title' || colIndex === 1) {
|
||||||
|
defaultWidth = '200px'
|
||||||
|
defaultMinWidth = '180px'
|
||||||
|
} else if (colIndex === 0) {
|
||||||
|
defaultWidth = '200px'
|
||||||
|
defaultMinWidth = '150px'
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: col.width || defaultWidth,
|
||||||
|
minWidth: col.minWidth || defaultMinWidth,
|
||||||
|
maxWidth: col.maxWidth || 'none'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// 本地存储 key,用于持久化展开状态
|
// 本地存储 key,用于持久化展开状态
|
||||||
_expandStoreKey() {
|
_expandStoreKey() {
|
||||||
return 'treegrid_expand_map_v1'
|
return 'treegrid_expand_map_v1'
|
||||||
@@ -204,14 +225,10 @@ table {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: separate;
|
border-collapse: separate;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
|
/* 继承父容器的最小宽度设置 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 确保表格可以在容器内滚动 */
|
/* endTbody 样式现在由父容器控制滚动 */
|
||||||
.endTbody {
|
|
||||||
max-height: calc(100vh - 200px);
|
|
||||||
/* 预留头部和其他元素的空间 */
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
color: #2d8cf0;
|
color: #2d8cf0;
|
||||||
@@ -293,35 +310,48 @@ table {
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
border-bottom: 1px solid #e8eaec;
|
border-bottom: 1px solid #e8eaec;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
/* 基础文本溢出处理 */
|
/* 改进的文本溢出处理 */
|
||||||
&:not(.td-operations) {
|
&:not(.td-operations):not(.td-expand) {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
max-width: 0; /* 配合 table-layout 实现更好的省略号效果 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确保内容不会溢出单元格 */
|
||||||
|
> label, > div {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 展开列样式 */
|
/* 展开列样式优化 */
|
||||||
.td-expand {
|
.td-expand {
|
||||||
width: auto;
|
width: auto;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
/* 给展开图标和文本预留足够空间 */
|
max-width: 300px;
|
||||||
|
|
||||||
|
.first-box {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 操作列样式 */
|
/* 操作列样式优化 */
|
||||||
.td-operations {
|
.td-operations {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
width: auto;
|
width: auto;
|
||||||
min-width: 180px;
|
min-width: 180px;
|
||||||
/* 增加按钮空间 */
|
max-width: 250px;
|
||||||
padding-right: 16px;
|
padding-right: 16px;
|
||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
/* 强制确保内容可见 */
|
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
/* 确保按钮在最上层 */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 确保操作列中的按钮组样式 */
|
/* 确保操作列中的按钮组样式 */
|
||||||
@@ -329,6 +359,7 @@ table {
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
flex-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 确保单个按钮样式 */
|
/* 确保单个按钮样式 */
|
||||||
@@ -337,6 +368,7 @@ table {
|
|||||||
visibility: visible;
|
visibility: visible;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
margin: 0 2px;
|
margin: 0 2px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div class="tree-grid-container">
|
||||||
<div class="ivu-table-wrapper">
|
<div class="ivu-table-wrapper">
|
||||||
<div class="ivu-table ivu-table-border">
|
<div class="ivu-table ivu-table-border">
|
||||||
<table cellspacing="0" width="100%" cellpadding="0" border="0">
|
<div class="table-scroll-container">
|
||||||
|
<table cellspacing="0" cellpadding="0" border="0" :style="{ minWidth: tableMinWidth }">
|
||||||
<SubColmns :columns="columns"></SubColmns>
|
<SubColmns :columns="columns"></SubColmns>
|
||||||
<subTheads :columns="columns"></subTheads>
|
<subTheads :columns="columns"></subTheads>
|
||||||
|
|
||||||
<tr class="ivu-table-tbody">
|
<tr class="ivu-table-tbody">
|
||||||
<td style="width: 100%;" :colspan="columns.length" class="non-right">
|
<td :colspan="columns.length" class="non-right">
|
||||||
<SubTreeGrid :columns="columns" :data="data" :grade="1" v-bind="$attrs" />
|
<SubTreeGrid :columns="columns" :data="data" :grade="1" v-bind="$attrs" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -14,6 +16,8 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -31,18 +35,54 @@ export default {
|
|||||||
SubColmns,
|
SubColmns,
|
||||||
subTheads
|
subTheads
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
// 计算表格的最小宽度,确保所有列都能正常显示
|
||||||
|
tableMinWidth() {
|
||||||
|
if (!this.columns || !this.columns.length) return '100%'
|
||||||
|
|
||||||
|
const totalWidth = this.columns.reduce((sum, col) => {
|
||||||
|
const width = col.width || (col.type === 'operation' ? 200 : 150)
|
||||||
|
const numWidth = typeof width === 'string' ? parseInt(width) || 0 : width
|
||||||
|
return sum + numWidth
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
// 如果计算出的总宽度小于容器宽度,则使用100%
|
||||||
|
return Math.max(totalWidth, 800) + 'px'
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {},
|
methods: {},
|
||||||
mounted() {}
|
mounted() {}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
.tree-grid-container {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ivu-table-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.ivu-table {
|
.ivu-table {
|
||||||
/* 限制最大宽度,超出显示横向滚动 */
|
width: 100%;
|
||||||
overflow: auto !important;
|
border: 1px solid #dcdee2;
|
||||||
max-width: 100%;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.table-scroll-container {
|
||||||
|
width: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
overflow-y: visible;
|
||||||
|
/* 横向滚动条始终可见,不需要滚动到底 */
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
min-width: 800px; /* 设置最小表格宽度 */
|
||||||
|
}
|
||||||
|
|
||||||
.ivu-table-tbody {
|
.ivu-table-tbody {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@@ -51,4 +91,23 @@ export default {
|
|||||||
border: none !important;
|
border: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 确保滚动条样式美观 */
|
||||||
|
.table-scroll-container::-webkit-scrollbar {
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-scroll-container::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-scroll-container::-webkit-scrollbar-thumb {
|
||||||
|
background: #c1c1c1;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-scroll-container::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #a8a8a8;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
14
src/index.js
14
src/index.js
@@ -25,11 +25,11 @@ import storeModules, { createStore } from './store'
|
|||||||
import { createBaseRoutes, setupRouterGuards, createRouter, getRoutes } from './router'
|
import { createBaseRoutes, setupRouterGuards, createRouter, getRoutes } from './router'
|
||||||
|
|
||||||
|
|
||||||
import components ,{ registerGlobalComponents} from './components/index'
|
import components ,{ registerGlobalComponents, registerComponents} from './components/index'
|
||||||
|
|
||||||
import pages from './views/index'
|
import pages from './views/index'
|
||||||
|
|
||||||
const { LoginPage, Page401, Page404, Page500, setupComponentMap } = pages
|
const { LoginPage, Page401, Page404, Page500, setupComponentMap, HomePage } = pages
|
||||||
|
|
||||||
|
|
||||||
// 导入页面组件
|
// 导入页面组件
|
||||||
@@ -44,6 +44,7 @@ class AdminFramework {
|
|||||||
this.config = {}
|
this.config = {}
|
||||||
this.store = null
|
this.store = null
|
||||||
this.router = null
|
this.router = null
|
||||||
|
this.HomePage = pages.HomePage // 默认使用框架内置的 HomePage
|
||||||
|
|
||||||
this.tools = tools
|
this.tools = tools
|
||||||
this.uiTool = uiTool
|
this.uiTool = uiTool
|
||||||
@@ -53,7 +54,7 @@ class AdminFramework {
|
|||||||
|
|
||||||
this.createBaseRoutes = createBaseRoutes
|
this.createBaseRoutes = createBaseRoutes
|
||||||
this.setupRouterGuards = setupRouterGuards
|
this.setupRouterGuards = setupRouterGuards
|
||||||
|
this.registerComponents = registerComponents
|
||||||
this.pages = pages
|
this.pages = pages
|
||||||
this.components = components
|
this.components = components
|
||||||
this.systemApi = systemApi
|
this.systemApi = systemApi
|
||||||
@@ -93,6 +94,7 @@ class AdminFramework {
|
|||||||
* @param {String} config.apiUrl - API base URL
|
* @param {String} config.apiUrl - API base URL
|
||||||
* @param {String} config.uploadUrl - upload URL (可选,默认为 apiUrl + 'upload')
|
* @param {String} config.uploadUrl - upload URL (可选,默认为 apiUrl + 'upload')
|
||||||
* @param {Object} config.componentMap - custom component map (optional)
|
* @param {Object} config.componentMap - custom component map (optional)
|
||||||
|
* @param {Component} config.HomePage - custom home page component (optional)
|
||||||
* @param {Function} config.onReady - callback when app is ready (optional)
|
* @param {Function} config.onReady - callback when app is ready (optional)
|
||||||
* @returns {Object} Vue instance
|
* @returns {Object} Vue instance
|
||||||
*/
|
*/
|
||||||
@@ -102,6 +104,11 @@ class AdminFramework {
|
|||||||
config.uploadUrl = config.apiUrl + (config.apiUrl.endsWith('/') ? 'upload' : '/upload')
|
config.uploadUrl = config.apiUrl + (config.apiUrl.endsWith('/') ? 'upload' : '/upload')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果提供了自定义 HomePage,使用自定义的,否则使用默认的
|
||||||
|
if (config.HomePage) {
|
||||||
|
this.HomePage = config.HomePage
|
||||||
|
}
|
||||||
|
|
||||||
// 设置配置
|
// 设置配置
|
||||||
this.config = config
|
this.config = config
|
||||||
|
|
||||||
@@ -175,6 +182,7 @@ class AdminFramework {
|
|||||||
Main: framework.Main,
|
Main: framework.Main,
|
||||||
ParentView: framework.ParentView,
|
ParentView: framework.ParentView,
|
||||||
Page404: framework.Page404,
|
Page404: framework.Page404,
|
||||||
|
HomePage: framework.HomePage,
|
||||||
authorityMenus: authorityMenus
|
authorityMenus: authorityMenus
|
||||||
})
|
})
|
||||||
console.log('Menu restored')
|
console.log('Menu restored')
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export default {
|
|||||||
menuList: state => state.menuList
|
menuList: state => state.menuList
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async setAuthorityMenus({ state, commit }, { Main, ParentView, Page404, authorityMenus, menuIds }) {
|
async setAuthorityMenus({ state, commit }, { Main, ParentView, Page404, HomePage, authorityMenus, menuIds }) {
|
||||||
// 如果传入了 authorityMenus,直接使用;否则从接口获取
|
// 如果传入了 authorityMenus,直接使用;否则从接口获取
|
||||||
let menus = authorityMenus
|
let menus = authorityMenus
|
||||||
|
|
||||||
@@ -95,9 +95,9 @@ export default {
|
|||||||
commit('setAuthorityMenus', JSON.stringify(menus))
|
commit('setAuthorityMenus', JSON.stringify(menus))
|
||||||
|
|
||||||
// 生成路由菜单(传递 HomePage 组件)
|
// 生成路由菜单(传递 HomePage 组件)
|
||||||
// 从框架导出的组件中获取 HomePage
|
// 优先使用传入的 HomePage,如果没有则从框架实例中获取
|
||||||
const HomePage = window.framework && HomePage ? HomePage : null
|
const homePageComponent = HomePage || (window.framework && window.framework.HomePage) || null
|
||||||
let mainMenu = uiTool.getRoutes(Main, ParentView, Page404, HomePage)
|
let mainMenu = uiTool.getRoutes(Main, ParentView, Page404, homePageComponent)
|
||||||
console.log('生成的主菜单:', mainMenu)
|
console.log('生成的主菜单:', mainMenu)
|
||||||
|
|
||||||
if (mainMenu && mainMenu.children) {
|
if (mainMenu && mainMenu.children) {
|
||||||
@@ -182,10 +182,13 @@ export default {
|
|||||||
|
|
||||||
// 调用 authorityMenus 接口获取完整菜单数据
|
// 调用 authorityMenus 接口获取完整菜单数据
|
||||||
// 如果接口失败,会使用默认菜单配置和 menuIds 进行过滤
|
// 如果接口失败,会使用默认菜单配置和 menuIds 进行过滤
|
||||||
|
// 从框架实例获取 HomePage
|
||||||
|
const HomePage = (window.framework && window.framework.HomePage) || null
|
||||||
await dispatch('setAuthorityMenus', {
|
await dispatch('setAuthorityMenus', {
|
||||||
Main,
|
Main,
|
||||||
ParentView,
|
ParentView,
|
||||||
Page404,
|
Page404,
|
||||||
|
HomePage,
|
||||||
menuIds
|
menuIds
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -130,8 +130,8 @@ export default class uiTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static delConfirm(callback) {
|
static delConfirm(callback) {
|
||||||
const Modal = (window.framework && window.framework.ViewUI && window.framework.ViewUI.Modal) || window.$Modal
|
const Modal = window.rootVue.$Modal
|
||||||
if (Modal) {
|
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '温馨提示',
|
title: '温馨提示',
|
||||||
content: '<p>你确定删除吗?</p>',
|
content: '<p>你确定删除吗?</p>',
|
||||||
@@ -139,11 +139,11 @@ export default class uiTool {
|
|||||||
callback && callback()
|
callback && callback()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static showConfirm({ title = '温馨提示', content = '内容' }, callback) {
|
static showConfirm({ title = '温馨提示', content = '内容' }, callback) {
|
||||||
const Modal = (window.framework && window.framework.ViewUI && window.framework.ViewUI.Modal) || window.$Modal
|
const Modal = window.rootVue.$Modal
|
||||||
if (Modal) {
|
if (Modal) {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title,
|
title,
|
||||||
|
|||||||
@@ -24,8 +24,14 @@ import Page500 from './error-page/500.vue'
|
|||||||
|
|
||||||
// 设置组件映射的方法
|
// 设置组件映射的方法
|
||||||
export function setupComponentMap(customMap = {}, uiTool) {
|
export function setupComponentMap(customMap = {}, uiTool) {
|
||||||
|
|
||||||
|
|
||||||
|
if (!customMap["home/index"]) {
|
||||||
|
customMap["home/index"] = HomePage
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const componentMap = {
|
const componentMap = {
|
||||||
'home/index': HomePage,
|
|
||||||
'system/sys_log': SysLog,
|
'system/sys_log': SysLog,
|
||||||
'system/sys_param_setup': SysParamSetup,
|
'system/sys_param_setup': SysParamSetup,
|
||||||
'system/sys_role': SysRole,
|
'system/sys_role': SysRole,
|
||||||
@@ -48,8 +54,8 @@ export function setupComponentMap(customMap = {}, uiTool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
HomePage,
|
|
||||||
SysLog,
|
SysLog,
|
||||||
|
SysLogOperate,
|
||||||
SysParamSetup,
|
SysParamSetup,
|
||||||
SysRole,
|
SysRole,
|
||||||
SysUser,
|
SysUser,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="content-view ">
|
<div class="content-view ">
|
||||||
<div class="tree-box">
|
<div class="tree-box">
|
||||||
<div class="btn-top-box pa10">
|
<div class="btn-top-box pa10">
|
||||||
<Button type="warning" size="small" @click="delAll">全部删除</Button>
|
<Button type="warning" size="small" @click="delAll">全部删除2</Button>
|
||||||
</div>
|
</div>
|
||||||
<Tree class="mt10" :data="treeData" :render="renderContent" @on-select-change="selectChange"></Tree>
|
<Tree class="mt10" :data="treeData" :render="renderContent" @on-select-change="selectChange"></Tree>
|
||||||
</div>
|
</div>
|
||||||
@@ -83,25 +83,33 @@ export default {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
h('span', data.title),
|
h('span', data.title),
|
||||||
h('Icon', {
|
h('span', {
|
||||||
props: {
|
|
||||||
type: 'ios-trash',
|
|
||||||
size: '18'
|
|
||||||
},
|
|
||||||
on: {
|
on: {
|
||||||
click: () => {
|
click: (e) => {
|
||||||
|
e.stopPropagation() // 阻止事件冒泡
|
||||||
|
e.preventDefault() // 阻止默认行为
|
||||||
this.deleteLog(data)
|
this.deleteLog(data)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
'margin-left': '8px'
|
'margin-left': '8px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'inline-block'
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
h('Icon', {
|
||||||
|
props: {
|
||||||
|
type: 'ios-trash',
|
||||||
|
size: '18'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
|
])
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
async selectChange(row) {
|
async selectChange(row) {
|
||||||
|
|
||||||
row[0].selected = true
|
row[0].selected = true
|
||||||
this.selectRow = row[0].title
|
this.selectRow = row[0].title
|
||||||
let res = await sys_log_serve.detail({ title: this.selectRow })
|
let res = await sys_log_serve.detail({ title: this.selectRow })
|
||||||
@@ -135,7 +143,7 @@ export default {
|
|||||||
.content-view {
|
.content-view {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
.tree-box {
|
.log-box {
|
||||||
height: 90vh;
|
height: 90vh;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
border-right: solid 1px #ccc;
|
border-right: solid 1px #ccc;
|
||||||
|
|||||||
@@ -4,9 +4,7 @@
|
|||||||
<Button type="primary" @click="addWarp()">新增</Button>
|
<Button type="primary" @click="addWarp()">新增</Button>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-body">
|
<div class="table-body">
|
||||||
<card class="tree-box">
|
|
||||||
<TreeGrid :columns="gridOption.columns" :data="gridOption.data"></TreeGrid>
|
<TreeGrid :columns="gridOption.columns" :data="gridOption.data"></TreeGrid>
|
||||||
</card>
|
|
||||||
</div>
|
</div>
|
||||||
<editModal ref="editModal" :columns="gridOption.editColumns" :rules="gridOption.rules">
|
<editModal ref="editModal" :columns="gridOption.editColumns" :rules="gridOption.rules">
|
||||||
<div slot="bottom">
|
<div slot="bottom">
|
||||||
@@ -253,7 +251,17 @@ export default {
|
|||||||
let menuTree = uiTool.transformTree(menuRows)
|
let menuTree = uiTool.transformTree(menuRows)
|
||||||
this.gridOption.menuData = this.mapTree(menuTree)
|
this.gridOption.menuData = this.mapTree(menuTree)
|
||||||
|
|
||||||
this.$store.dispatch('setAuthorityMenus')
|
// 菜单更新后重新加载权限菜单
|
||||||
|
const framework = this.$framework || window.framework
|
||||||
|
if (framework) {
|
||||||
|
this.$store.dispatch('user/setAuthorityMenus', {
|
||||||
|
Main: framework.components.Main,
|
||||||
|
ParentView: framework.components.ParentView,
|
||||||
|
Page404: framework.pages.Page404,
|
||||||
|
HomePage: framework.HomePage
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
async initCol() {
|
async initCol() {
|
||||||
let res = await menuServer.modelAll()
|
let res = await menuServer.modelAll()
|
||||||
@@ -393,9 +401,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.tree-box {
|
|
||||||
overflow: auto;
|
|
||||||
height: 100%;
|
|
||||||
max-height: calc(100vh - 200px);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user