Merge branch 'main' of https://git.light120.com/zc/admin_core
This commit is contained in:
376
README.md
376
README.md
@@ -4,6 +4,112 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 快速导航(按角色)
|
||||||
|
|
||||||
|
### 框架使用者(接入业务项目)
|
||||||
|
|
||||||
|
- 目标:最快把框架跑起来并完成菜单页面映射。
|
||||||
|
- 建议阅读顺序:`§1 依赖与环境` → `§2 引入方式` → `§3 核心 API` → `§7 组件映射与动态路由` → `§12 组件使用说明`。
|
||||||
|
- 最小落地步骤:
|
||||||
|
1. 安装 peer 依赖并引入 `admin-framework.js`。
|
||||||
|
2. 调用 `AdminFramework.createApp({ title, apiUrl, componentMap })`。
|
||||||
|
3. 在 `componentMap` 中注册业务页面路径并与后端菜单 `component` 字段对齐。
|
||||||
|
|
||||||
|
### 业务开发者(写页面和接口联调)
|
||||||
|
|
||||||
|
- 目标:在现有框架内开发业务页、调用接口、使用内置组件。
|
||||||
|
- 建议阅读顺序:`§5 HTTP 模块` → `§6 uiTool/tools` → `§8 systemApi` → `§9 Vuex` → `§12 组件使用说明`。
|
||||||
|
- 高频关注:
|
||||||
|
- 请求约定(`code === 0`、`401` 自动处理、参数预处理)。
|
||||||
|
- `this.$http` / `this.$uiTool` / `this.$tools` 的用法。
|
||||||
|
- 全局组件与按需组件边界(见 `§12.14` 索引)。
|
||||||
|
|
||||||
|
### 二次封装维护者(改框架内核)
|
||||||
|
|
||||||
|
- 目标:扩展框架能力(路由、Store、组件注册、系统 API 聚合等)。
|
||||||
|
- 建议阅读顺序:`§3.2 createApp 执行流程` → `§4 框架实例` → `§7 动态路由` → `§8 systemApi` → `§9 createStore`。
|
||||||
|
- 变更建议:
|
||||||
|
1. 先改 `src/index.js` 的初始化路径(插件、原型、Store、Router)。
|
||||||
|
2. 同步维护 `src/views/index.js` 的默认 `componentMap`。
|
||||||
|
3. 变更 `src/api/system` 时同步更新 `src/api/system/index.js` 聚合导出与 README `§8`。
|
||||||
|
|
||||||
|
### 常见任务直达
|
||||||
|
|
||||||
|
| 任务 | 直达章节 |
|
||||||
|
|------|----------|
|
||||||
|
| 第一次接入并启动 | `§1` + `§2` + `§3` |
|
||||||
|
| 菜单点击后页面不显示 | `§7`(重点看 `componentMap`) |
|
||||||
|
| 请求报错或 token 失效 | `§5` + `§9.1 user` |
|
||||||
|
| 顶部标题/Logo 不正确 | `§9.2 app`(`getSysTitle`) |
|
||||||
|
| 想知道组件怎么用 | `§12`(先看 `§12.14` 索引) |
|
||||||
|
|
||||||
|
### 5分钟上手清单
|
||||||
|
|
||||||
|
> 目标:5 分钟内把框架跑起来并看到一个业务页面。
|
||||||
|
|
||||||
|
#### 第 1 步:安装依赖(1 分钟)
|
||||||
|
|
||||||
|
在业务项目安装 peer 依赖:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm i vue vue-router vuex view-design axios
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 第 2 步:准备框架包(1 分钟)
|
||||||
|
|
||||||
|
- 已有 `dist/admin-framework.js`:直接拷贝到业务项目(例如 `src/vendor/`)。
|
||||||
|
- 没有构建产物:在框架项目根目录执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 第 3 步:准备页面映射(1 分钟)
|
||||||
|
|
||||||
|
创建一个业务页面并准备 `componentMap`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 例如 src/router/component-map.js
|
||||||
|
import DemoPage from '@/views/demo/index.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
'demo/index': DemoPage,
|
||||||
|
'demo/index.vue': DemoPage
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 第 4 步:入口创建应用(1 分钟)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import Vue from 'vue'
|
||||||
|
import AdminFramework from '@/vendor/admin-framework.js'
|
||||||
|
import componentMap from '@/router/component-map'
|
||||||
|
|
||||||
|
const app = AdminFramework.createApp({
|
||||||
|
title: '后台管理系统',
|
||||||
|
apiUrl: 'http://127.0.0.1:3000/admin_api/',
|
||||||
|
componentMap
|
||||||
|
})
|
||||||
|
|
||||||
|
app.$mount('#app')
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 第 5 步:验证(1 分钟)
|
||||||
|
|
||||||
|
- 能进入登录页(或已有 token 时进入主框架页)。
|
||||||
|
- 左侧菜单点击后,页面能正常渲染(无“页面组件未加载”提示)。
|
||||||
|
- 打开浏览器控制台,确认可访问:
|
||||||
|
- `window.framework`
|
||||||
|
- `window.rootVue`
|
||||||
|
|
||||||
|
#### 常见 3 个坑(上手必看)
|
||||||
|
|
||||||
|
1. 后端菜单里的 `component` 路径必须和 `componentMap` 键一致(推荐同时写带 `.vue` 和不带 `.vue` 两种)。
|
||||||
|
2. `apiUrl` 建议以 `/admin_api/` 结尾,避免接口前缀不一致。
|
||||||
|
3. 若登录后菜单空白,优先检查 `authorityMenus` 返回格式与 `user/setAuthorityMenus` 动态路由是否执行。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 1. 依赖与环境
|
## 1. 依赖与环境
|
||||||
|
|
||||||
### 1.1 Peer 依赖(由业务项目安装)
|
### 1.1 Peer 依赖(由业务项目安装)
|
||||||
@@ -70,6 +176,19 @@ import AdminFramework from './vendor/admin-framework.js'
|
|||||||
| `HomePage` | `Vue` 组件 | 可选,自定义首页;默认框架内置欢迎页 |
|
| `HomePage` | `Vue` 组件 | 可选,自定义首页;默认框架内置欢迎页 |
|
||||||
| `onReady` | `function` | 可选,根实例 `created` 末尾调用,`this` 为 Vue 根实例 |
|
| `onReady` | `function` | 可选,根实例 `created` 末尾调用,`this` 为 Vue 根实例 |
|
||||||
|
|
||||||
|
### 3.2 `createApp` 执行流程(源码行为)
|
||||||
|
|
||||||
|
1. 自动补齐 `uploadUrl`(未传时按 `apiUrl` 拼接 `/upload`)。
|
||||||
|
2. 注册 `ViewUI`、`VueRouter`、`Vuex`。
|
||||||
|
3. 注入全局原型:`$config`、`$http`、`$tools`、`$uiTool`、`$framework`。
|
||||||
|
4. 全局注册组件(`registerGlobalComponents`)。
|
||||||
|
5. 建立 `componentMap`(内置系统页 + 业务传入映射)。
|
||||||
|
6. 创建 Store 并初始化 HTTP。
|
||||||
|
7. 创建 Router(基础路由 + 动态主路由),并挂守卫。
|
||||||
|
8. 创建根 Vue;恢复菜单与系统标题;最后执行 `onReady`。
|
||||||
|
|
||||||
|
> 注意:框架实例是单例,`store/router` 初始化后会复用;通常整个应用只调用一次 `createApp`。
|
||||||
|
|
||||||
**示例:**
|
**示例:**
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
@@ -113,6 +232,13 @@ app.$mount('#app')
|
|||||||
| `storeModules` | 默认 `user`、`app` 模块描述,扩展 Store 时可参考 |
|
| `storeModules` | 默认 `user`、`app` 模块描述,扩展 Store 时可参考 |
|
||||||
| `createBaseRoutes` / `setupRouterGuards` / `registerComponents` | 高级自定义路由/组件时使用 |
|
| `createBaseRoutes` / `setupRouterGuards` / `registerComponents` | 高级自定义路由/组件时使用 |
|
||||||
|
|
||||||
|
### 4.4 运行时全局对象
|
||||||
|
|
||||||
|
浏览器环境下,框架会暴露:
|
||||||
|
|
||||||
|
- `window.framework`:框架单例。
|
||||||
|
- `window.rootVue`:`createApp` 创建的根实例(便于调试)。
|
||||||
|
|
||||||
### 4.2 `addComponentMap(customMap)`
|
### 4.2 `addComponentMap(customMap)`
|
||||||
|
|
||||||
在运行时追加组件映射(与 `createApp` 里的 `componentMap` 语义相同)。
|
在运行时追加组件映射(与 `createApp` 里的 `componentMap` 语义相同)。
|
||||||
@@ -151,6 +277,7 @@ AdminFramework.initHttp(
|
|||||||
|
|
||||||
- 成功:后端 JSON 需 `code === 0`,否则走全局错误提示并 `reject`。
|
- 成功:后端 JSON 需 `code === 0`,否则走全局错误提示并 `reject`。
|
||||||
- `401`:清空 token,并跳转登录路由(依赖 `window.framework.router`)。
|
- `401`:清空 token,并跳转登录路由(依赖 `window.framework.router`)。
|
||||||
|
- 请求参数会做预处理:递归移除对象中值为 `''` 的键,并自动格式化 `Date`。
|
||||||
|
|
||||||
### 5.3 方法说明
|
### 5.3 方法说明
|
||||||
|
|
||||||
@@ -188,6 +315,12 @@ export default {
|
|||||||
await this.$http.get('/xxx', { id: 1 }, { hideLoad: true })
|
await this.$http.get('/xxx', { id: 1 }, { hideLoad: true })
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 5.4 常见注意点
|
||||||
|
|
||||||
|
- `post(url, body, config)` 当前实现固定会显示 loading(`showLoad`),`hideLoad` 只在 `get` 内部判断。
|
||||||
|
- `postFormData` 使用 `application/x-www-form-urlencoded`。
|
||||||
|
- `fileExport` 返回 `blob` 响应;`is_down=false` 时可自行处理文件流。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. uiTool 模块
|
## 6. uiTool 模块
|
||||||
@@ -260,20 +393,47 @@ AdminFramework.createApp({
|
|||||||
|
|
||||||
框架内置已注册一批系统页(如 `system/sys_menu` 等),见 `src/views/index.js` 中 `setupComponentMap`;业务模块在 `componentMap` 中追加即可。
|
框架内置已注册一批系统页(如 `system/sys_menu` 等),见 `src/views/index.js` 中 `setupComponentMap`;业务模块在 `componentMap` 中追加即可。
|
||||||
|
|
||||||
|
### 7.1 内置映射(默认可用)
|
||||||
|
|
||||||
|
默认已注入以下路径(可直接在后端菜单配置里使用):
|
||||||
|
|
||||||
|
- `home/index`
|
||||||
|
- `system/sys_log`
|
||||||
|
- `system/sys_log_operate`
|
||||||
|
- `system/sys_param_setup`
|
||||||
|
- `system/sys_role`
|
||||||
|
- `system/sys_user`
|
||||||
|
- `system/sys_tenant`
|
||||||
|
- `system/sys_control`
|
||||||
|
- `system/sys_menu`
|
||||||
|
- `system/sys_title`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. 系统 API(`systemApi`)
|
## 8. 系统 API(`systemApi`)
|
||||||
|
|
||||||
`AdminFramework.systemApi` 聚合导出 `src/api/system` 下各 `*Server`,例如:
|
`AdminFramework.systemApi` 聚合导出 `src/api/system/index.js` 中的服务(以下为当前版本完整导出):
|
||||||
|
|
||||||
| 导出名 | 模块文件 | 典型用途 |
|
| 导出名 | 模块文件 | 典型用途 |
|
||||||
|--------|----------|----------|
|
|--------|----------|----------|
|
||||||
|
| `fileServe` | `fileServe.js` | 文件上传下载 |
|
||||||
|
| `plaAccountServer` | `pla_account_server.js` | 平台账号管理 |
|
||||||
|
| `rolePermissionServer` | `rolePermissionServer.js` | 角色权限 |
|
||||||
|
| `roleServer` | `roleServer.js` | 角色管理 |
|
||||||
|
| `sysAddressServer` | `sysAddressServer.js` | 地址字典 |
|
||||||
|
| `sysModuleServer` | `sysModuleServer.js` | 模块管理 |
|
||||||
|
| `sysLogServe` | `sys_log_serve.js` | 系统日志 |
|
||||||
|
| `systemTypeServer` | `systemType_server.js` | 系统类型配置 |
|
||||||
|
| `tableServer` | `tableServer.js` | 表结构相关 |
|
||||||
| `userServer` | `userServer.js` | 登录、用户 CRUD、权限菜单 |
|
| `userServer` | `userServer.js` | 登录、用户 CRUD、权限菜单 |
|
||||||
|
| `formFieldServer` | `formFieldServer.js` | 表单字段 |
|
||||||
|
| `formServer` | `formServer.js` | 表单模型 |
|
||||||
| `menuServer` | `menuServer.js` | 菜单管理 |
|
| `menuServer` | `menuServer.js` | 菜单管理 |
|
||||||
| `roleServer` / `rolePermissionServer` | 角色与权限 |
|
| `modelFieldServer` | `modelFieldServer.js` | 数据模型字段 |
|
||||||
| `paramSetupServer` | 系统参数(如站点标题、Logo) |
|
| `modelServer` | `modelServer.js` | 数据模型 |
|
||||||
| `fileServe` | 文件上传下载 |
|
| `paramSetupServer` | `paramSetupServer.js` | 系统参数(标题、Logo) |
|
||||||
| `modelServer` / `tableServer` / `formServer` 等 | 低代码/表单相关 |
|
| `sysControlTypeServer` | `sysControlTypeServer.js` | 控件类型 |
|
||||||
|
| `sysTenantServer` | `sysTenantServer.js` | 租户管理 |
|
||||||
|
|
||||||
**调用示例:**
|
**调用示例:**
|
||||||
|
|
||||||
@@ -290,19 +450,36 @@ async function login() {
|
|||||||
|
|
||||||
实际请求仍走全局配置的 `http`(`apiUrl`、token 头)。
|
实际请求仍走全局配置的 `http`(`apiUrl`、token 头)。
|
||||||
|
|
||||||
|
> 说明:`src/api/system` 中若存在未在 `index.js` 聚合导出的文件(例如实验性模块),不会出现在 `AdminFramework.systemApi`。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 9. Vuex(`this.$store`)
|
## 9. Vuex(`this.$store`)
|
||||||
|
|
||||||
默认模块:
|
默认模块:
|
||||||
|
|
||||||
|
### 9.0 `createStore`(扩展自定义模块)
|
||||||
|
|
||||||
|
框架导出 `createStore(Vuex, customModules, createPersistedState)`,可在默认 `user/app` 基础上合并业务模块:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import Vuex from 'vuex'
|
||||||
|
// 源码工程示例:按项目结构从 store 导入 createStore
|
||||||
|
import { createStore } from '@/store'
|
||||||
|
import foo from './store/foo'
|
||||||
|
|
||||||
|
const store = createStore(Vuex, { foo }, null)
|
||||||
|
```
|
||||||
|
|
||||||
|
`createPersistedState` 传入后会启用本地持久化插件(`window.localStorage`)。
|
||||||
|
|
||||||
### 9.1 `user`(`namespaced: true`)
|
### 9.1 `user`(`namespaced: true`)
|
||||||
|
|
||||||
| 项 | 说明 |
|
| 项 | 说明 |
|
||||||
|----|------|
|
|----|------|
|
||||||
| `state` | `token`、`userName`、`authorityMenus`、`menuList` 等 |
|
| `state` | `token`、`userName`、`avatorImgPath`、`authorityMenus`、`menuList`、`currentTenant` |
|
||||||
| `mutations` | `setToken`、`setUserName`、`setAuthorityMenus`、`setMenuList` |
|
| `mutations` | `setToken`、`setUserName`、`setAuthorityMenus`、`setMenuList`、`setCurrentTenant` |
|
||||||
| `actions` | `setAuthorityMenus`:拉取或传入菜单 JSON,生成动态路由并写入 `localStorage` |
|
| `actions` | `setAuthorityMenus`(生成动态路由)、`handleLogin`、`handleLogOut` |
|
||||||
|
|
||||||
**登录后刷新菜单示例:**
|
**登录后刷新菜单示例:**
|
||||||
|
|
||||||
@@ -322,7 +499,8 @@ await this.$store.dispatch('user/setAuthorityMenus', {
|
|||||||
| 项 | 说明 |
|
| 项 | 说明 |
|
||||||
|----|------|
|
|----|------|
|
||||||
| `state` | `sysFormModel`(标题、Logo)、面包屑、首页路由 |
|
| `state` | `sysFormModel`(标题、Logo)、面包屑、首页路由 |
|
||||||
| `actions` | `getSysTitle`:根据参数与接口更新站点标题与 Logo |
|
| `mutations` | `setBreadCrumb`、`setHomeRoute`、`setSysTitle` |
|
||||||
|
| `actions` | `getSysTitle`:根据登录状态和参数接口更新站点标题与 Logo |
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
await this.$store.dispatch('app/getSysTitle', {
|
await this.$store.dispatch('app/getSysTitle', {
|
||||||
@@ -737,18 +915,178 @@ Main 中示例:`<ABackTop container=".content-wrapper" :height="100" :bottom="
|
|||||||
| **CommonIcon** | `type`:普通 `Icon` 名;自定义图标以 `_` 开头配合 `md-icons`。`size`、`color` 可选。 |
|
| **CommonIcon** | `type`:普通 `Icon` 名;自定义图标以 `_` 开头配合 `md-icons`。`size`、`color` 可选。 |
|
||||||
| **LoadFlower** | 全屏 loading 占位(与 `http` 的 `showLoad` 通过 DOM `id` 联动),一般随 `Main` 挂载,无需单独使用。 |
|
| **LoadFlower** | 全屏 loading 占位(与 `http` 的 `showLoad` 通过 DOM `id` 联动),一般随 `Main` 挂载,无需单独使用。 |
|
||||||
|
|
||||||
### 12.12 未全局注册、按需引用的组件
|
### 12.12 未全局注册、按需引用的组件(详细)
|
||||||
|
|
||||||
以下在 `src/components` 下,需在页面中 **import** 后注册或局部引用:
|
以下组件不在 `registerGlobalComponents` 中,需要页面内 `import` 后再注册。示例路径基于源码工程 `@/components/...`。
|
||||||
|
|
||||||
| 路径/名称 | 说明 |
|
#### 12.12.1 `login-form/login-form.vue`(`LoginForm`)
|
||||||
|-----------|------|
|
|
||||||
| `login-form` | 登录表单:`userNameRules`、`passwordRules`;校验通过 `@on-success-valid` 抛出 `{ userName, password }`。 |
|
- 用途:登录页账号密码表单,内置 Enter 提交与 `Form.validate`。
|
||||||
| `date-picker/index.vue` | 对 `DatePicker` 的 `v-model` 封装,`change`/`input`。 |
|
- Props:
|
||||||
| `switch/index.vue` | 对 `Switch` 的 `v-model` 封装。 |
|
- `userNameRules: Array`(默认必填)
|
||||||
| `cron-input` | Cron 表达式下拉选择。 |
|
- `passwordRules: Array`(默认必填)
|
||||||
| `main/pageHead.vue` | 页面顶栏条:当 `$route.meta.type === '功能'` 时显示返回;默认插槽放按钮。 |
|
- 事件:
|
||||||
| `markdown`、`cropper` 等 | 见各目录 `index.js` / 入口组件。 |
|
- `on-success-valid`:校验成功后抛出 `{ userName, password }`
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<LoginForm @on-success-valid="handleLogin" />
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 12.12.2 `date-picker/index.vue`(`DatePicker` 封装)
|
||||||
|
|
||||||
|
- 用途:统一 `DatePicker` 的 `v-model` 语义。
|
||||||
|
- Props:`value`
|
||||||
|
- 事件:`input`、`change`
|
||||||
|
- 说明:其余能力通过 `v-bind="$attrs"` 透传到 iView `DatePicker`。
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<DatePicker v-model="form.start_time" type="datetime" />
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 12.12.3 `switch/index.vue`(`FrameworkSwitch`)
|
||||||
|
|
||||||
|
- 用途:布尔/0/1 字段统一开关封装,避免 `<Switch>` 名称冲突。
|
||||||
|
- Props:`value`(支持 `true/false/1/0/'1'/'0'/'true'/'false'`)
|
||||||
|
- 事件:`input`、`change`、`on-change`
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<FrameworkSwitch v-model="form.is_platform" />
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 12.12.4 `cron-input/index.vue`
|
||||||
|
|
||||||
|
- 用途:可视化拼 Cron(`minute hour day month dayOfWeek`)字符串。
|
||||||
|
- Props:`value`(Cron 字符串)
|
||||||
|
- 事件:`input`
|
||||||
|
- 说明:点击下拉后可选分钟/小时/日/月/周,点“确定”写回 Cron。
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<cron-input v-model="form.cron_time" />
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 12.12.5 `main/pageHead.vue`
|
||||||
|
|
||||||
|
- 用途:页面头部工具条;当 `this.$route.meta.type === '功能'` 时显示返回按钮。
|
||||||
|
- 插槽:默认插槽(放新增/导出等按钮)
|
||||||
|
- 内置方法:`goBack()`(`this.$router.go(-1)`)
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<pageHead>
|
||||||
|
<Button type="primary">新增</Button>
|
||||||
|
</pageHead>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 12.12.6 `markdown/markdown.vue`(`MarkdownEditor`)
|
||||||
|
|
||||||
|
- 用途:基于 `simplemde` 的 Markdown 编辑器。
|
||||||
|
- Props:
|
||||||
|
- `value: String`
|
||||||
|
- `options: Object`(SimpleMDE 配置)
|
||||||
|
- `localCache: Boolean`(默认 `true`,写入 `localStorage.markdownContent`)
|
||||||
|
- 事件:`input`、`on-change`、`on-focus`、`on-blur`
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<MarkdownEditor v-model="form.md_content" :local-cache="false" />
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 12.12.7 `cropper/index.vue`(`Cropper`)
|
||||||
|
|
||||||
|
- 用途:图片裁剪(`cropperjs`),默认 1:1 比例。
|
||||||
|
- Props:`src`、`preview`、`moveStep`、`cropButtonText`
|
||||||
|
- 事件:`on-crop`(回传裁剪后的 `File`)
|
||||||
|
- 说明:默认要求图片尺寸至少 `500x500`,否则提示错误。
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<Cropper :src="imageUrl" @on-crop="handleCroppedFile" />
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 12.12.8 `upload/Custom.vue`
|
||||||
|
|
||||||
|
- 用途:通用文件上传(显示文件名与上传进度,上传至 OSS)。
|
||||||
|
- Props:`value`、`accept`、`btnText`
|
||||||
|
- 事件:`input`(上传成功后的 OSS 地址)
|
||||||
|
- 方法:`clear()`
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<CustomUpload v-model="form.file_url" accept=".zip,.pdf" btn-text="上传附件" />
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 12.12.9 `md-icons/icons.vue`(`Icons`)
|
||||||
|
|
||||||
|
- 用途:渲染项目内 iconfont 图标(`iconfont icon-${type}`)。
|
||||||
|
- Props:`type`(必填)、`color`、`size`
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<Icons type="dashboard" :size="18" color="#2d8cf0" />
|
||||||
|
```
|
||||||
|
|
||||||
|
### 12.13 仅内部子组件(通常不单独直接使用)
|
||||||
|
|
||||||
|
这些组件由父组件组合使用,业务页面一般不直接引用;如需复用,建议复制其父组件交互一起接入。
|
||||||
|
|
||||||
|
| 组件路径 | 主要被谁使用 | 作用 |
|
||||||
|
|----------|--------------|------|
|
||||||
|
| `main/components/side-menu/side-menu-item.vue` | `SideMenu` | 递归渲染 `Submenu` 节点与子菜单。 |
|
||||||
|
| `main/components/terminal/terminal.vue` | `main/components/terminal/index.vue` | 终端内容面板(当前为“终端功能暂未启用”占位)。 |
|
||||||
|
| `split-pane/trigger.vue` | `split-pane/split.vue` | 分割条拖拽手柄视图。 |
|
||||||
|
| `treeGrid/component/subThead.vue` | `treeGrid/index.vue` | 树表头部渲染。 |
|
||||||
|
| `treeGrid/component/subColmns.vue` | `treeGrid/index.vue`/`subTreeGrid.vue` | `colgroup` 宽度控制。 |
|
||||||
|
| `treeGrid/component/subTreeGrid.vue` | `treeGrid/index.vue` | 子树递归渲染、展开收起状态维护。 |
|
||||||
|
| `treeGrid/component/renderCol.vue` | `treeGrid/index.vue`/`subTreeGrid.vue` | 安全执行列 `render(h, params)`。 |
|
||||||
|
| `upload/mod/fileBtn.vue` | `upload/Custom.vue`/`upload/mod/fileListModal.vue` | 文件选择按钮。 |
|
||||||
|
| `upload/mod/fileListModal.vue` | 上传业务页(按需) | 批量选文件 +(可选)批量上传 + 回调。 |
|
||||||
|
|
||||||
|
### 12.14 `src/components` 全量组件索引(按路径)
|
||||||
|
|
||||||
|
> 说明:下表覆盖 `src/components/**/*.vue`。`注册方式` 里“全局”表示由 `registerGlobalComponents` 自动注册;“按需”表示需手动引入;“内部”表示被父组件组合使用。
|
||||||
|
|
||||||
|
| 组件路径 | 注册方式 | 入口章节 |
|
||||||
|
|----------|----------|----------|
|
||||||
|
| `asyncModal/index.vue` | 全局 | 12.6 |
|
||||||
|
| `common-icon/common-icon.vue` | 全局 | 12.11 |
|
||||||
|
| `cropper/index.vue` | 按需 | 12.12.7 |
|
||||||
|
| `cron-input/index.vue` | 按需 | 12.12.4 |
|
||||||
|
| `date-picker/index.vue` | 按需 | 12.12.2 |
|
||||||
|
| `editor/index.vue` | 全局 | 12.5 |
|
||||||
|
| `FloatPanel/index.vue` | 全局 | 12.10 |
|
||||||
|
| `info-card/infor-card.vue` | 全局(`InfoCard`) | 12.8 |
|
||||||
|
| `load-flower/index.vue` | 全局 | 12.11 |
|
||||||
|
| `login-form/login-form.vue` | 按需 | 12.12.1 |
|
||||||
|
| `main/main.vue` | 全局(`Main`) | 12.1 |
|
||||||
|
| `main/pageHead.vue` | 按需 | 12.12.5 |
|
||||||
|
| `main/components/a-back-top/index.vue` | 内部(可按需) | 12.1 / 12.11 |
|
||||||
|
| `main/components/fullscreen/fullscreen.vue` | 内部(可按需) | 12.1 |
|
||||||
|
| `main/components/header-bar/header-bar.vue` | 内部(可按需) | 12.1 |
|
||||||
|
| `main/components/header-bar/custom-bread-crumb/custom-bread-crumb.vue` | 内部(可按需) | 12.1 |
|
||||||
|
| `main/components/header-bar/sider-trigger/sider-trigger.vue` | 内部(可按需) | 12.1 |
|
||||||
|
| `main/components/language/language.vue` | 内部(可按需) | 12.1 |
|
||||||
|
| `main/components/side-menu/collapsed-menu.vue` | 内部 | 12.1 |
|
||||||
|
| `main/components/side-menu/side-menu-item.vue` | 内部 | 12.13 |
|
||||||
|
| `main/components/side-menu/side-menu.vue` | 内部(可按需) | 12.1 |
|
||||||
|
| `main/components/terminal/index.vue` | 内部(可按需) | 12.1 |
|
||||||
|
| `main/components/terminal/terminal.vue` | 内部 | 12.13 |
|
||||||
|
| `main/components/user/user.vue` | 内部(可按需) | 12.1 |
|
||||||
|
| `markdown/markdown.vue` | 按需 | 12.12.6 |
|
||||||
|
| `md-icons/icons.vue` | 按需 | 12.12.9 |
|
||||||
|
| `parent-view/parent-view.vue` | 全局(`ParentView`) | 12.1 |
|
||||||
|
| `split-pane/split.vue` | 全局(`SplitPane`) | 12.9 |
|
||||||
|
| `split-pane/trigger.vue` | 内部 | 12.13 |
|
||||||
|
| `switch/index.vue` | 按需 | 12.12.3 |
|
||||||
|
| `tables/editModal.vue` | 全局(`editModal`) | 12.6 |
|
||||||
|
| `tables/fieldItem.vue` | 全局(`fieldItem`) | 12.7 |
|
||||||
|
| `tables/fieldRenderer.vue` | 全局(`FieldRenderer`) | 12.7 |
|
||||||
|
| `tables/index.vue` | 全局(`Tables`) | 12.2 |
|
||||||
|
| `text-area/index.vue` | 全局(`TextArea`) | 12.11 |
|
||||||
|
| `treeGrid/index.vue` | 全局(`TreeGrid`) | 12.3 |
|
||||||
|
| `treeGrid/component/renderCol.vue` | 内部 | 12.13 |
|
||||||
|
| `treeGrid/component/subColmns.vue` | 内部 | 12.13 |
|
||||||
|
| `treeGrid/component/subThead.vue` | 内部 | 12.13 |
|
||||||
|
| `treeGrid/component/subTreeGrid.vue` | 内部 | 12.13 |
|
||||||
|
| `upload/Custom.vue` | 按需 | 12.12.8 |
|
||||||
|
| `upload/Multiple.vue` | 全局(`UploadMultiple`) | 12.4 |
|
||||||
|
| `upload/Single.vue` | 全局(`UploadSingle`) | 12.4 |
|
||||||
|
| `upload/mod/fileBtn.vue` | 内部 | 12.13 |
|
||||||
|
| `upload/mod/fileListModal.vue` | 内部(可按需) | 12.13 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
26
package-lock.json
generated
26
package-lock.json
generated
@@ -28,7 +28,6 @@
|
|||||||
"@babel/preset-env": "^7.12.0",
|
"@babel/preset-env": "^7.12.0",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"babel-loader": "^8.2.0",
|
"babel-loader": "^8.2.0",
|
||||||
"cross-env": "^10.1.0",
|
|
||||||
"css-loader": "^5.0.0",
|
"css-loader": "^5.0.0",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
"less": "^4.0.0",
|
"less": "^4.0.0",
|
||||||
@@ -1814,13 +1813,6 @@
|
|||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@epic-web/invariant": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/@epic-web/invariant/-/invariant-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
"resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
@@ -2849,24 +2841,6 @@
|
|||||||
"@cropper/utils": "^2.0.1"
|
"@cropper/utils": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cross-env": {
|
|
||||||
"version": "10.1.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/cross-env/-/cross-env-10.1.0.tgz",
|
|
||||||
"integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@epic-web/invariant": "^1.0.0",
|
|
||||||
"cross-spawn": "^7.0.6"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"cross-env": "dist/bin/cross-env.js",
|
|
||||||
"cross-env-shell": "dist/bin/cross-env-shell.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export { default as modelFieldServer } from './modelFieldServer'
|
|||||||
export { default as modelServer } from './modelServer'
|
export { default as modelServer } from './modelServer'
|
||||||
export { default as paramSetupServer } from './paramSetupServer'
|
export { default as paramSetupServer } from './paramSetupServer'
|
||||||
export { default as sysControlTypeServer } from './sysControlTypeServer'
|
export { default as sysControlTypeServer } from './sysControlTypeServer'
|
||||||
|
export { default as sysTenantServer } from './sysTenantServer'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
22
src/api/system/sysTenantServer.js
Normal file
22
src/api/system/sysTenantServer.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import http from '@/utils/http'
|
||||||
|
|
||||||
|
class SysTenantServer {
|
||||||
|
async list() {
|
||||||
|
return http.get('/sys_tenant/index', {})
|
||||||
|
}
|
||||||
|
|
||||||
|
async add(row) {
|
||||||
|
return http.post('/sys_tenant/add', row)
|
||||||
|
}
|
||||||
|
|
||||||
|
async edit(row) {
|
||||||
|
return http.post('/sys_tenant/edit', row)
|
||||||
|
}
|
||||||
|
|
||||||
|
async del(row) {
|
||||||
|
return http.post('/sys_tenant/del', row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sysTenantServer = new SysTenantServer()
|
||||||
|
export default sysTenantServer
|
||||||
@@ -110,6 +110,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.table-warp {
|
.table-warp {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
.ivu-card-body {
|
.ivu-card-body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -186,20 +189,21 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 自适应表格宽度 */
|
/* 与 View Design 一致:横向/纵向滚动由 .ivu-table-body 上的 overflow 类控制,避免 wrapper 与整表同高导致横向条在页面最底 */
|
||||||
.ivu-table-wrapper {
|
.ivu-table-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-x: auto;
|
overflow: hidden;
|
||||||
overflow-y: auto;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ivu-table {
|
.ivu-table {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 移除 table-layout: auto,使用 iView 默认的 fixed 布局以确保列对齐 */
|
/* 移除 table-layout: auto,使用 iView 默认的 fixed 布局以确保列对齐 */
|
||||||
@@ -208,8 +212,8 @@
|
|||||||
} */
|
} */
|
||||||
|
|
||||||
.ivu-table-body {
|
.ivu-table-body {
|
||||||
overflow-y: auto;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 按钮风格一致 */
|
/* 按钮风格一致 */
|
||||||
|
|||||||
@@ -5,14 +5,24 @@
|
|||||||
height: 64px;
|
height: 64px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.custom-content-con {
|
.header-breadcrumb {
|
||||||
height: 64px;
|
margin-left: 30px;
|
||||||
padding-right: 120px;
|
|
||||||
line-height: 64px;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: top;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
line-height: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-right-slot {
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: 64px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
padding-right: 10px;
|
||||||
|
line-height: 64px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="header-bar">
|
<div class="header-bar">
|
||||||
<sider-trigger :collapsed="collapsed" icon="md-menu" @on-change="handleCollpasedChange"></sider-trigger>
|
<sider-trigger :collapsed="collapsed" icon="md-menu" @on-change="handleCollpasedChange"></sider-trigger>
|
||||||
<custom-bread-crumb show-icon style="margin-left: 30px;" :list="breadCrumbList"></custom-bread-crumb>
|
<custom-bread-crumb class="header-breadcrumb" show-icon :list="breadCrumbList"></custom-bread-crumb>
|
||||||
<div class="custom-content-con">
|
<div class="header-right-slot">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export default {
|
|||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
iconclass() {
|
iconclass() {
|
||||||
let curClass = 'terminal-icon ml10'
|
let curClass = 'terminal-icon'
|
||||||
// Terminal 功能暂时禁用
|
// Terminal 功能暂时禁用
|
||||||
// if (this.isServerRun) {
|
// if (this.isServerRun) {
|
||||||
// curClass += ' run'
|
// curClass += ' run'
|
||||||
@@ -39,6 +39,8 @@ export default {
|
|||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.terminal-box {
|
.terminal-box {
|
||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
|
margin-right: 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.terminal-icon {
|
.terminal-icon {
|
||||||
color: #c5c8ce;
|
color: #c5c8ce;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<label class="loginName-box">{{userName}}</label>
|
<label class="loginName-box">{{userName}}</label>
|
||||||
<Dropdown>
|
<Dropdown>
|
||||||
|
|
||||||
<Avatar :src="typeof userAvator === 'string' ? userAvator : userIcon" />
|
<Avatar :src="avatarSrc" />
|
||||||
|
|
||||||
<Icon :size="18" type="md-arrow-dropdown"></Icon>
|
<Icon :size="18" type="md-arrow-dropdown"></Icon>
|
||||||
<DropdownMenu slot="list">
|
<DropdownMenu slot="list">
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import './user.less'
|
import './user.less'
|
||||||
import { mapMutations, mapActions } from 'vuex'
|
import { mapActions } from 'vuex'
|
||||||
|
|
||||||
const userIcon = require("@/assets/images/administrato.png").default
|
const userIcon = require("@/assets/images/administrato.png").default
|
||||||
|
|
||||||
@@ -39,6 +39,23 @@ export default {
|
|||||||
userIcon: userIcon
|
userIcon: userIcon
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
/** 接口无头像或空串时用本地默认图,避免 Avatar 空 src 显示灰色块 */
|
||||||
|
avatarSrc() {
|
||||||
|
const v = this.userAvator
|
||||||
|
if (v != null && typeof v === 'string' && v.trim() !== '') {
|
||||||
|
const s = v.trim()
|
||||||
|
if (/^https?:\/\//i.test(s)) {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
if (this.$http && typeof this.$http.ImgSrc === 'function') {
|
||||||
|
return this.$http.ImgSrc(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return this.userIcon
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions('user', ['handleLogOut']),
|
...mapActions('user', ['handleLogOut']),
|
||||||
logout() {
|
logout() {
|
||||||
@@ -55,9 +72,9 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.user-avator-dropdown {
|
.user-avator-dropdown {
|
||||||
position: absolute;
|
display: flex;
|
||||||
top: 0px;
|
align-items: center;
|
||||||
right: 10px;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loginName-box {
|
.loginName-box {
|
||||||
|
|||||||
@@ -16,8 +16,15 @@
|
|||||||
|
|
||||||
<Header class="header-con-main">
|
<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>
|
<div class="header-user-area">
|
||||||
<user :userName="userName" :user-avator="userAvator || ''" />
|
<Terminal></Terminal>
|
||||||
|
<span
|
||||||
|
v-if="currentTenant"
|
||||||
|
class="main-tenant-tag"
|
||||||
|
:title="'租户:' + currentTenant.name + '(' + currentTenant.code + ')'"
|
||||||
|
>{{ currentTenant.name }}</span>
|
||||||
|
<user :userName="userName" :user-avator="userAvator || ''" />
|
||||||
|
</div>
|
||||||
</headerBar>
|
</headerBar>
|
||||||
</Header>
|
</Header>
|
||||||
<Layout class="main-layout-content">
|
<Layout class="main-layout-content">
|
||||||
@@ -42,7 +49,7 @@ import User from './components/user'
|
|||||||
import ABackTop from './components/a-back-top'
|
import ABackTop from './components/a-back-top'
|
||||||
import Fullscreen from './components/fullscreen'
|
import Fullscreen from './components/fullscreen'
|
||||||
import Language from './components/language'
|
import Language from './components/language'
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters, mapState } from 'vuex'
|
||||||
import headerBar from './components/header-bar'
|
import headerBar from './components/header-bar'
|
||||||
import loadFlower from '../load-flower/index'
|
import loadFlower from '../load-flower/index'
|
||||||
import Terminal from './components/terminal'
|
import Terminal from './components/terminal'
|
||||||
@@ -77,6 +84,7 @@ export default {
|
|||||||
userName: 'user/userName',
|
userName: 'user/userName',
|
||||||
userAvator: 'user/avatorImgPath'
|
userAvator: 'user/avatorImgPath'
|
||||||
}),
|
}),
|
||||||
|
...mapState('user', ['currentTenant']),
|
||||||
cacheList() {
|
cacheList() {
|
||||||
return ['ParentView']
|
return ['ParentView']
|
||||||
}
|
}
|
||||||
@@ -149,6 +157,30 @@ export default {
|
|||||||
z-index: 999999;
|
z-index: 999999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-user-area {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-tenant-tag {
|
||||||
|
margin-right: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 0 10px;
|
||||||
|
height: 28px;
|
||||||
|
line-height: 28px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #2d8cf0;
|
||||||
|
background: rgba(45, 140, 240, 0.08);
|
||||||
|
border-radius: 4px;
|
||||||
|
max-width: 160px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar-brand {
|
.sidebar-brand {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -59,15 +59,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import templateRender from './templateRender'
|
import templateRender from './templateRender'
|
||||||
import FrameworkSwitch from '../switch/index.vue'
|
import FrameworkSwitch from '../switch/index.vue'
|
||||||
|
import { SELECT_ICON_OPTIONS } from './selectIconOptions'
|
||||||
// 导入框架中的图标配置
|
|
||||||
let icons = []
|
|
||||||
try {
|
|
||||||
icons = require('../../config/icons.json') || []
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('未找到图标配置文件,图标选择功能将不可用', e)
|
|
||||||
icons = []
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FieldRenderer',
|
name: 'FieldRenderer',
|
||||||
@@ -91,7 +83,7 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
icons: []
|
icons: SELECT_ICON_OPTIONS
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -112,21 +104,13 @@ export default {
|
|||||||
return this.value
|
return this.value
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
|
||||||
// 安全处理图标数据
|
|
||||||
if (Array.isArray(icons)) {
|
|
||||||
this.icons = icons.map((item) => item.trim ? item.trim() : item)
|
|
||||||
} else {
|
|
||||||
this.icons = []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
// 组件映射表 - 统一管理组件类型
|
// 组件映射表 - 统一管理组件类型
|
||||||
getComponentName(componentType) {
|
getComponentName(componentType) {
|
||||||
const componentMap = {
|
const componentMap = {
|
||||||
'Select': 'Select',
|
'Select': 'Select',
|
||||||
'Radio': 'RadioGroup',
|
'Radio': 'RadioGroup',
|
||||||
'SelectIcon': 'AutoComplete',
|
'SelectIcon': 'Select',
|
||||||
'UploadSingle': 'UploadSingle',
|
'UploadSingle': 'UploadSingle',
|
||||||
'TextArea': 'TextArea',
|
'TextArea': 'TextArea',
|
||||||
'Input': 'Input',
|
'Input': 'Input',
|
||||||
@@ -174,7 +158,9 @@ export default {
|
|||||||
baseProps.filterable = true
|
baseProps.filterable = true
|
||||||
baseProps.clearable = true
|
baseProps.clearable = true
|
||||||
} else if (col.com === 'SelectIcon') {
|
} else if (col.com === 'SelectIcon') {
|
||||||
baseProps.icon = 'ios-search'
|
baseProps.filterable = true
|
||||||
|
baseProps.clearable = true
|
||||||
|
baseProps.placeholder = col.placeholder || '搜索或选择图标'
|
||||||
} else if (col.com === 'TextArea') {
|
} else if (col.com === 'TextArea') {
|
||||||
baseProps.rows = 4
|
baseProps.rows = 4
|
||||||
baseProps.placeholder = '请输入内容'
|
baseProps.placeholder = '请输入内容'
|
||||||
@@ -255,6 +241,7 @@ export default {
|
|||||||
'TextArea': 'width:100%;',
|
'TextArea': 'width:100%;',
|
||||||
'DatePicker': 'width:100%;',
|
'DatePicker': 'width:100%;',
|
||||||
'TimePicker': 'width:100%;',
|
'TimePicker': 'width:100%;',
|
||||||
|
'SelectIcon': 'width:100%;',
|
||||||
"inputNumber": "width:100%;",
|
"inputNumber": "width:100%;",
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,24 @@
|
|||||||
<a v-if="isDown&&value&&value.length>0" class="link right-link" @click="downExecl">下载</a>
|
<a v-if="isDown&&value&&value.length>0" class="link right-link" @click="downExecl">下载</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Table :class="tbClass" ref="tablesMain" border :data="insideTableData" :width='defaultWidth' :height='defaultHeight' :columns="insideColumns" v-bind="$attrs" @on-selection-change='onSelect'>
|
<div class="table-main">
|
||||||
<slot></slot>
|
<Table
|
||||||
<slot name="footer" slot="footer"></slot>
|
v-bind="$attrs"
|
||||||
<slot name="loading" slot="loading"></slot>
|
:class="tbClass"
|
||||||
</Table>
|
ref="tablesMain"
|
||||||
|
border
|
||||||
|
:data="insideTableData"
|
||||||
|
:width="defaultWidth"
|
||||||
|
:height="defaultHeight"
|
||||||
|
:max-height="effectiveTableMaxHeight"
|
||||||
|
:columns="insideColumns"
|
||||||
|
@on-selection-change="onSelect"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
<slot name="footer" slot="footer"></slot>
|
||||||
|
<slot name="loading" slot="loading"></slot>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -81,6 +94,9 @@ export default {
|
|||||||
},
|
},
|
||||||
// 动态计算的偏移量
|
// 动态计算的偏移量
|
||||||
calculatedOffset: null,
|
calculatedOffset: null,
|
||||||
|
/** 传给 iView Table 的 max-height(px),使表体在可视区内滚动,横向条始终在视口底部 */
|
||||||
|
bodyViewportMaxHeight: null,
|
||||||
|
_tableContentResizeObserver: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -145,6 +161,13 @@ export default {
|
|||||||
// 使用手动指定的偏移量
|
// 使用手动指定的偏移量
|
||||||
return `calc(100vh - ${this.maxHeightOffset}px)`
|
return `calc(100vh - ${this.maxHeightOffset}px)`
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** 未传 height 时由容器测量得到,避免宽表横向滚动条落在整表最底部 */
|
||||||
|
effectiveTableMaxHeight() {
|
||||||
|
if (this.height) return undefined
|
||||||
|
if (this.bodyViewportMaxHeight == null) return undefined
|
||||||
|
return this.bodyViewportMaxHeight
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -174,6 +197,7 @@ export default {
|
|||||||
// 触发表格重新渲染
|
// 触发表格重新渲染
|
||||||
this.$refs.tablesMain.$forceUpdate()
|
this.$refs.tablesMain.$forceUpdate()
|
||||||
}
|
}
|
||||||
|
this.syncTableBodyMaxHeight()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onChangePage(page) {
|
onChangePage(page) {
|
||||||
@@ -184,6 +208,43 @@ export default {
|
|||||||
this.$emit('on-select', selection, row)
|
this.$emit('on-select', selection, row)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
syncTableBodyMaxHeight() {
|
||||||
|
if (this.height) {
|
||||||
|
this.bodyViewportMaxHeight = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const wrap = this.$refs.table_content
|
||||||
|
if (!wrap) return
|
||||||
|
const titleEl = wrap.querySelector('.table-title')
|
||||||
|
const titleH = (this.title || this.isDown) && titleEl ? titleEl.offsetHeight : 0
|
||||||
|
const avail = wrap.clientHeight - titleH
|
||||||
|
const next = Math.max(160, Math.floor(avail))
|
||||||
|
if (this.bodyViewportMaxHeight !== next) {
|
||||||
|
this.bodyViewportMaxHeight = next
|
||||||
|
}
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const inst = this.$refs.tablesMain
|
||||||
|
if (inst && typeof inst.handleResize === 'function') {
|
||||||
|
inst.handleResize()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
setupTableContentResizeObserver() {
|
||||||
|
const el = this.$refs.table_content
|
||||||
|
if (!el || typeof ResizeObserver === 'undefined') return
|
||||||
|
if (this._tableContentResizeObserver) {
|
||||||
|
this._tableContentResizeObserver.disconnect()
|
||||||
|
}
|
||||||
|
const ro = new ResizeObserver(() => {
|
||||||
|
this.syncTableBodyMaxHeight()
|
||||||
|
})
|
||||||
|
ro.observe(el)
|
||||||
|
this._tableContentResizeObserver = ro
|
||||||
|
},
|
||||||
|
|
||||||
// 动态计算表格容器上方所有元素的高度总和
|
// 动态计算表格容器上方所有元素的高度总和
|
||||||
calculateOffset() {
|
calculateOffset() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
@@ -203,6 +264,9 @@ export default {
|
|||||||
|
|
||||||
// 计算总偏移量
|
// 计算总偏移量
|
||||||
this.calculatedOffset = topOffset + pageBoxHeight + bottomPadding
|
this.calculatedOffset = topOffset + pageBoxHeight + bottomPadding
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.syncTableBodyMaxHeight()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -243,6 +307,7 @@ export default {
|
|||||||
|
|
||||||
// 动态计算偏移量
|
// 动态计算偏移量
|
||||||
this.calculateOffset()
|
this.calculateOffset()
|
||||||
|
this.setupTableContentResizeObserver()
|
||||||
|
|
||||||
// 如果数据或列配置为空,输出警告
|
// 如果数据或列配置为空,输出警告
|
||||||
if (!this.insideTableData || this.insideTableData.length === 0) {
|
if (!this.insideTableData || this.insideTableData.length === 0) {
|
||||||
@@ -255,21 +320,34 @@ export default {
|
|||||||
|
|
||||||
// 监听窗口大小变化,重新计算偏移量
|
// 监听窗口大小变化,重新计算偏移量
|
||||||
window.addEventListener('resize', this.calculateOffset)
|
window.addEventListener('resize', this.calculateOffset)
|
||||||
|
window.addEventListener('resize', this.syncTableBodyMaxHeight)
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
// 移除窗口大小监听
|
// 移除窗口大小监听
|
||||||
window.removeEventListener('resize', this.calculateOffset)
|
window.removeEventListener('resize', this.calculateOffset)
|
||||||
|
window.removeEventListener('resize', this.syncTableBodyMaxHeight)
|
||||||
|
if (this._tableContentResizeObserver) {
|
||||||
|
this._tableContentResizeObserver.disconnect()
|
||||||
|
this._tableContentResizeObserver = null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.table-warp {
|
.table-warp {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
.table-content {
|
.table-content {
|
||||||
overflow-y: auto;
|
/* 纵向由 Table 内部表体滚动;避免与外层双滚动导致横向条在整页最底 */
|
||||||
|
overflow: hidden;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
.table-title {
|
.table-title {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -279,10 +357,17 @@ export default {
|
|||||||
padding: 0rem 0.15rem;
|
padding: 0rem 0.15rem;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
line-height: 50px;
|
line-height: 50px;
|
||||||
|
flex-shrink: 0;
|
||||||
.right-link {
|
.right-link {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-main {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-box {
|
.page-box {
|
||||||
|
|||||||
306
src/components/tables/selectIconOptions.js
Normal file
306
src/components/tables/selectIconOptions.js
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
/**
|
||||||
|
* 菜单/表单「图标」下拉选项(与 View Design Icon 的 type 一致)
|
||||||
|
* 不依赖 icons.json,保证 UMD 打包后无需额外静态资源即可使用
|
||||||
|
*/
|
||||||
|
export const SELECT_ICON_OPTIONS = [
|
||||||
|
'md-add',
|
||||||
|
'md-add-circle',
|
||||||
|
'md-alarm',
|
||||||
|
'md-albums',
|
||||||
|
'md-alert',
|
||||||
|
'md-american-football',
|
||||||
|
'md-analytics',
|
||||||
|
'md-aperture',
|
||||||
|
'md-apps',
|
||||||
|
'md-appstore',
|
||||||
|
'md-archive',
|
||||||
|
'md-arrow-back',
|
||||||
|
'md-arrow-down',
|
||||||
|
'md-arrow-dropdown',
|
||||||
|
'md-arrow-dropdown-circle',
|
||||||
|
'md-arrow-dropleft',
|
||||||
|
'md-arrow-dropleft-circle',
|
||||||
|
'md-arrow-dropright',
|
||||||
|
'md-arrow-dropright-circle',
|
||||||
|
'md-arrow-dropup',
|
||||||
|
'md-arrow-dropup-circle',
|
||||||
|
'md-arrow-forward',
|
||||||
|
'md-arrow-round-back',
|
||||||
|
'md-arrow-round-down',
|
||||||
|
'md-arrow-round-forward',
|
||||||
|
'md-arrow-round-up',
|
||||||
|
'md-arrow-up',
|
||||||
|
'md-at',
|
||||||
|
'md-attach',
|
||||||
|
'md-backspace',
|
||||||
|
'md-barcode',
|
||||||
|
'md-baseball',
|
||||||
|
'md-basket',
|
||||||
|
'md-basketball',
|
||||||
|
'md-battery-charging',
|
||||||
|
'md-battery-dead',
|
||||||
|
'md-battery-full',
|
||||||
|
'md-beaker',
|
||||||
|
'md-beer',
|
||||||
|
'md-bicycle',
|
||||||
|
'md-bluetooth',
|
||||||
|
'md-boat',
|
||||||
|
'md-body',
|
||||||
|
'md-bonfire',
|
||||||
|
'md-book',
|
||||||
|
'md-bookmark',
|
||||||
|
'md-bookmarks',
|
||||||
|
'md-bowtie',
|
||||||
|
'md-briefcase',
|
||||||
|
'md-browsers',
|
||||||
|
'md-brush',
|
||||||
|
'md-bug',
|
||||||
|
'md-build',
|
||||||
|
'md-bulb',
|
||||||
|
'md-bus',
|
||||||
|
'md-cafe',
|
||||||
|
'md-calculator',
|
||||||
|
'md-calendar',
|
||||||
|
'md-call',
|
||||||
|
'md-camera',
|
||||||
|
'md-car',
|
||||||
|
'md-card',
|
||||||
|
'md-cart',
|
||||||
|
'md-cash',
|
||||||
|
'md-chatboxes',
|
||||||
|
'md-chatbubbles',
|
||||||
|
'md-checkbox',
|
||||||
|
'md-checkbox-outline',
|
||||||
|
'md-checkmark',
|
||||||
|
'md-checkmark-circle',
|
||||||
|
'md-checkmark-circle-outline',
|
||||||
|
'md-clipboard',
|
||||||
|
'md-clock',
|
||||||
|
'md-close',
|
||||||
|
'md-close-circle',
|
||||||
|
'md-closed-captioning',
|
||||||
|
'md-cloud',
|
||||||
|
'md-cloud-circle',
|
||||||
|
'md-cloud-done',
|
||||||
|
'md-cloud-download',
|
||||||
|
'md-cloud-outline',
|
||||||
|
'md-cloud-upload',
|
||||||
|
'md-cloudy',
|
||||||
|
'md-cloudy-night',
|
||||||
|
'md-code',
|
||||||
|
'md-code-download',
|
||||||
|
'md-code-working',
|
||||||
|
'md-cog',
|
||||||
|
'md-color-fill',
|
||||||
|
'md-color-filter',
|
||||||
|
'md-color-palette',
|
||||||
|
'md-color-wand',
|
||||||
|
'md-compass',
|
||||||
|
'md-construct',
|
||||||
|
'md-contact',
|
||||||
|
'md-contacts',
|
||||||
|
'md-contract',
|
||||||
|
'md-contrast',
|
||||||
|
'md-copy',
|
||||||
|
'md-create',
|
||||||
|
'md-crop',
|
||||||
|
'md-cube',
|
||||||
|
'md-cut',
|
||||||
|
'md-desktop',
|
||||||
|
'md-disc',
|
||||||
|
'md-document',
|
||||||
|
'md-done-all',
|
||||||
|
'md-download',
|
||||||
|
'md-easel',
|
||||||
|
'md-egg',
|
||||||
|
'md-exit',
|
||||||
|
'md-expand',
|
||||||
|
'md-eye',
|
||||||
|
'md-eye-off',
|
||||||
|
'md-fastforward',
|
||||||
|
'md-female',
|
||||||
|
'md-filing',
|
||||||
|
'md-film',
|
||||||
|
'md-finger-print',
|
||||||
|
'md-flag',
|
||||||
|
'md-flame',
|
||||||
|
'md-flash',
|
||||||
|
'md-flask',
|
||||||
|
'md-flower',
|
||||||
|
'md-folder',
|
||||||
|
'md-folder-open',
|
||||||
|
'md-football',
|
||||||
|
'md-funnel',
|
||||||
|
'md-game-controller-a',
|
||||||
|
'md-game-controller-b',
|
||||||
|
'md-git-branch',
|
||||||
|
'md-git-commit',
|
||||||
|
'md-git-compare',
|
||||||
|
'md-git-merge',
|
||||||
|
'md-git-network',
|
||||||
|
'md-git-pull-request',
|
||||||
|
'md-glasses',
|
||||||
|
'md-globe',
|
||||||
|
'md-grid',
|
||||||
|
'md-hammer',
|
||||||
|
'md-hand',
|
||||||
|
'md-happy',
|
||||||
|
'md-headset',
|
||||||
|
'md-heart',
|
||||||
|
'md-heart-outline',
|
||||||
|
'md-help',
|
||||||
|
'md-help-buoy',
|
||||||
|
'md-help-circle',
|
||||||
|
'md-home',
|
||||||
|
'md-ice-cream',
|
||||||
|
'md-image',
|
||||||
|
'md-images',
|
||||||
|
'md-infinite',
|
||||||
|
'md-information',
|
||||||
|
'md-information-circle',
|
||||||
|
'md-ionic',
|
||||||
|
'md-ionitron',
|
||||||
|
'md-jet',
|
||||||
|
'md-key',
|
||||||
|
'md-keypad',
|
||||||
|
'md-laptop',
|
||||||
|
'md-leaf',
|
||||||
|
'md-link',
|
||||||
|
'md-list',
|
||||||
|
'md-list-box',
|
||||||
|
'md-locate',
|
||||||
|
'md-lock',
|
||||||
|
'md-log-in',
|
||||||
|
'md-log-out',
|
||||||
|
'md-magnet',
|
||||||
|
'md-mail',
|
||||||
|
'md-mail-open',
|
||||||
|
'md-male',
|
||||||
|
'md-man',
|
||||||
|
'md-map',
|
||||||
|
'md-medal',
|
||||||
|
'md-medical',
|
||||||
|
'md-medkit',
|
||||||
|
'md-megaphone',
|
||||||
|
'md-menu',
|
||||||
|
'md-mic',
|
||||||
|
'md-mic-off',
|
||||||
|
'md-microphone',
|
||||||
|
'md-moon',
|
||||||
|
'md-more',
|
||||||
|
'md-move',
|
||||||
|
'md-musical-note',
|
||||||
|
'md-musical-notes',
|
||||||
|
'md-navigate',
|
||||||
|
'md-no-smoking',
|
||||||
|
'md-notifications',
|
||||||
|
'md-notifications-off',
|
||||||
|
'md-notifications-outline',
|
||||||
|
'md-nuclear',
|
||||||
|
'md-nutrition',
|
||||||
|
'md-open',
|
||||||
|
'md-options',
|
||||||
|
'md-outlet',
|
||||||
|
'md-paper',
|
||||||
|
'md-paper-plane',
|
||||||
|
'md-partly-sunny',
|
||||||
|
'md-pause',
|
||||||
|
'md-paw',
|
||||||
|
'md-people',
|
||||||
|
'md-person',
|
||||||
|
'md-person-add',
|
||||||
|
'md-phone-landscape',
|
||||||
|
'md-phone-portrait',
|
||||||
|
'md-photos',
|
||||||
|
'md-pie',
|
||||||
|
'md-pin',
|
||||||
|
'md-pint',
|
||||||
|
'md-pizza',
|
||||||
|
'md-plane',
|
||||||
|
'md-planet',
|
||||||
|
'md-play',
|
||||||
|
'md-podium',
|
||||||
|
'md-power',
|
||||||
|
'md-pricetag',
|
||||||
|
'md-pricetags',
|
||||||
|
'md-print',
|
||||||
|
'md-pulse',
|
||||||
|
'md-qr-scanner',
|
||||||
|
'md-quote',
|
||||||
|
'md-radio',
|
||||||
|
'md-radio-button-off',
|
||||||
|
'md-radio-button-on',
|
||||||
|
'md-rainy',
|
||||||
|
'md-recording',
|
||||||
|
'md-redo',
|
||||||
|
'md-refresh',
|
||||||
|
'md-refresh-circle',
|
||||||
|
'md-remove',
|
||||||
|
'md-remove-circle',
|
||||||
|
'md-reorder',
|
||||||
|
'md-repeat',
|
||||||
|
'md-resize',
|
||||||
|
'md-restaurant',
|
||||||
|
'md-return-left',
|
||||||
|
'md-return-right',
|
||||||
|
'md-reverse-camera',
|
||||||
|
'md-rewind',
|
||||||
|
'md-ribbon',
|
||||||
|
'md-rose',
|
||||||
|
'md-sad',
|
||||||
|
'md-school',
|
||||||
|
'md-search',
|
||||||
|
'md-send',
|
||||||
|
'md-settings',
|
||||||
|
'md-share',
|
||||||
|
'md-share-alt',
|
||||||
|
'md-shirt',
|
||||||
|
'md-shuffle',
|
||||||
|
'md-skip-backward',
|
||||||
|
'md-skip-forward',
|
||||||
|
'md-snow',
|
||||||
|
'md-speedometer',
|
||||||
|
'md-square',
|
||||||
|
'md-square-outline',
|
||||||
|
'md-star',
|
||||||
|
'md-star-half',
|
||||||
|
'md-star-outline',
|
||||||
|
'md-stats',
|
||||||
|
'md-stopwatch',
|
||||||
|
'md-subway',
|
||||||
|
'md-sunny',
|
||||||
|
'md-swap',
|
||||||
|
'md-switch',
|
||||||
|
'md-sync',
|
||||||
|
'md-tablet-landscape',
|
||||||
|
'md-tablet-portrait',
|
||||||
|
'md-tennisball',
|
||||||
|
'md-text',
|
||||||
|
'md-thermometer',
|
||||||
|
'md-thumbs-down',
|
||||||
|
'md-thumbs-up',
|
||||||
|
'md-thunderstorm',
|
||||||
|
'md-time',
|
||||||
|
'md-timer',
|
||||||
|
'md-train',
|
||||||
|
'md-transgender',
|
||||||
|
'md-trash',
|
||||||
|
'md-trending-down',
|
||||||
|
'md-trending-up',
|
||||||
|
'md-trophy',
|
||||||
|
'md-umbrella',
|
||||||
|
'md-undo',
|
||||||
|
'md-unlock',
|
||||||
|
'md-videocam',
|
||||||
|
'md-volume-down',
|
||||||
|
'md-volume-mute',
|
||||||
|
'md-volume-off',
|
||||||
|
'md-volume-up',
|
||||||
|
'md-walk',
|
||||||
|
'md-warning',
|
||||||
|
'md-watch',
|
||||||
|
'md-water',
|
||||||
|
'md-wifi',
|
||||||
|
'md-wine',
|
||||||
|
'md-woman'
|
||||||
|
]
|
||||||
@@ -115,6 +115,17 @@ export const defaultMenus = [
|
|||||||
is_show_menu: 1,
|
is_show_menu: 1,
|
||||||
icon: 'md-text',
|
icon: 'md-text',
|
||||||
sort: 3
|
sort: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 125,
|
||||||
|
name: '租户管理',
|
||||||
|
path: '/system/tenant',
|
||||||
|
component: 'system/sys_tenant',
|
||||||
|
parent_id: 120,
|
||||||
|
type: '页面',
|
||||||
|
is_show_menu: 1,
|
||||||
|
icon: 'md-git-branch',
|
||||||
|
sort: 4
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,16 @@ import uiTool from '../utils/uiTool'
|
|||||||
import { defaultMenus, filterMenusByIds } from '../config/menuConfig'
|
import { defaultMenus, filterMenusByIds } from '../config/menuConfig'
|
||||||
import userServer from '../api/system/userServer'
|
import userServer from '../api/system/userServer'
|
||||||
|
|
||||||
|
function readTenantLs() {
|
||||||
|
try {
|
||||||
|
const s = localStorage.getItem('currentTenant')
|
||||||
|
if (!s || s === 'undefined') return null
|
||||||
|
return JSON.parse(s)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: {
|
state: {
|
||||||
@@ -10,7 +20,9 @@ export default {
|
|||||||
avatorImgPath: '',
|
avatorImgPath: '',
|
||||||
token: getToken(),
|
token: getToken(),
|
||||||
authorityMenus: [],
|
authorityMenus: [],
|
||||||
menuList: localStorage.getItem('menuList') ? JSON.parse(localStorage.getItem('menuList')) : []
|
menuList: localStorage.getItem('menuList') ? JSON.parse(localStorage.getItem('menuList')) : [],
|
||||||
|
/** 登录返回的租户信息:{ id, name, code, is_platform } */
|
||||||
|
currentTenant: readTenantLs()
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
setAvator(state, avatorPath) {
|
setAvator(state, avatorPath) {
|
||||||
@@ -31,6 +43,14 @@ export default {
|
|||||||
setMenuList(state, menus) {
|
setMenuList(state, menus) {
|
||||||
state.menuList = menus
|
state.menuList = menus
|
||||||
localStorage.setItem('menuList', JSON.stringify(menus))
|
localStorage.setItem('menuList', JSON.stringify(menus))
|
||||||
|
},
|
||||||
|
setCurrentTenant(state, tenant) {
|
||||||
|
state.currentTenant = tenant && typeof tenant === 'object' ? tenant : null
|
||||||
|
if (state.currentTenant) {
|
||||||
|
localStorage.setItem('currentTenant', JSON.stringify(state.currentTenant))
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem('currentTenant')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
@@ -162,6 +182,11 @@ export default {
|
|||||||
|
|
||||||
commit('setUserName', name)
|
commit('setUserName', name)
|
||||||
commit('setToken', token)
|
commit('setToken', token)
|
||||||
|
if (res.data.tenant) {
|
||||||
|
commit('setCurrentTenant', res.data.tenant)
|
||||||
|
} else {
|
||||||
|
commit('setCurrentTenant', null)
|
||||||
|
}
|
||||||
|
|
||||||
// 登录接口返回的 authorityMenus 是菜单 ID 数组字符串
|
// 登录接口返回的 authorityMenus 是菜单 ID 数组字符串
|
||||||
console.log('登录返回的菜单 IDs:', authorityMenusIds)
|
console.log('登录返回的菜单 IDs:', authorityMenusIds)
|
||||||
@@ -205,6 +230,7 @@ export default {
|
|||||||
commit('setToken', '')
|
commit('setToken', '')
|
||||||
commit('setAuthorityMenus', '[]')
|
commit('setAuthorityMenus', '[]')
|
||||||
commit('setMenuList', [])
|
commit('setMenuList', [])
|
||||||
|
commit('setCurrentTenant', null)
|
||||||
localStorage.removeItem('menuList')
|
localStorage.removeItem('menuList')
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,27 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { formatDate } from './tools'
|
import { formatDate } from './tools'
|
||||||
|
|
||||||
|
/** 递归删除对象中值为空字符串的键(不修改数组内的空字符串元素) */
|
||||||
|
function stripEmptyStringKeys(value) {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.map((item) => stripEmptyStringKeys(item))
|
||||||
|
}
|
||||||
|
if (typeof value === 'object' && Object.prototype.toString.call(value) === '[object Object]') {
|
||||||
|
const next = {}
|
||||||
|
for (const k of Object.keys(value)) {
|
||||||
|
const v = value[k]
|
||||||
|
if (v === '') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
next[k] = stripEmptyStringKeys(v)
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
class Http {
|
class Http {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -131,13 +152,18 @@ class Http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
param = JSON.parse(JSON.stringify(param))
|
param = JSON.parse(JSON.stringify(param))
|
||||||
return param
|
return stripEmptyStringKeys(param)
|
||||||
}
|
}
|
||||||
|
|
||||||
formatFormDataParam(param) {
|
formatFormDataParam(param) {
|
||||||
|
param = param || {}
|
||||||
let formData = new FormData()
|
let formData = new FormData()
|
||||||
Object.keys(param).forEach(key => {
|
Object.keys(param).forEach((key) => {
|
||||||
formData.append(key, param[key])
|
const v = param[key]
|
||||||
|
if (v === '') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
formData.append(key, v)
|
||||||
})
|
})
|
||||||
return formData
|
return formData
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import SysLog from './system/sys_log.vue'
|
|||||||
import SysParamSetup from './system/sys_param_setup.vue'
|
import SysParamSetup from './system/sys_param_setup.vue'
|
||||||
import SysRole from './system/sys_role.vue'
|
import SysRole from './system/sys_role.vue'
|
||||||
import SysUser from './system/sys_user.vue'
|
import SysUser from './system/sys_user.vue'
|
||||||
|
import SysTenant from './system/sys_tenant.vue'
|
||||||
import SysLogOperate from './system/sys_log_operate.vue'
|
import SysLogOperate from './system/sys_log_operate.vue'
|
||||||
|
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ export function setupComponentMap(customMap = {}, uiTool) {
|
|||||||
'system/sys_param_setup': SysParamSetup,
|
'system/sys_param_setup': SysParamSetup,
|
||||||
'system/sys_role': SysRole,
|
'system/sys_role': SysRole,
|
||||||
'system/sys_user': SysUser,
|
'system/sys_user': SysUser,
|
||||||
|
'system/sys_tenant': SysTenant,
|
||||||
'system/sys_control': SysControl,
|
'system/sys_control': SysControl,
|
||||||
'system/sys_menu': SysMenu,
|
'system/sys_menu': SysMenu,
|
||||||
'system/sys_title': SysTitle,
|
'system/sys_title': SysTitle,
|
||||||
@@ -60,6 +62,7 @@ export default {
|
|||||||
SysParamSetup,
|
SysParamSetup,
|
||||||
SysRole,
|
SysRole,
|
||||||
SysUser,
|
SysUser,
|
||||||
|
SysTenant,
|
||||||
SysControl,
|
SysControl,
|
||||||
SysMenu,
|
SysMenu,
|
||||||
SysTitle,
|
SysTitle,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="login-con">
|
<div class="login-con">
|
||||||
<Card icon="log-in" title="欢迎登录" style="width:350px;height:300px;" :bordered="false">
|
<Card icon="log-in" title="欢迎登录" style="width:350px;height:260px;" :bordered="false">
|
||||||
<div class="form-con">
|
<div class="form-con">
|
||||||
<login-form @on-success-valid="handleSubmit"></login-form>
|
<login-form @on-success-valid="handleSubmit"></login-form>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,7 +45,10 @@ export default {
|
|||||||
...mapActions('user', ['handleLogin']),
|
...mapActions('user', ['handleLogin']),
|
||||||
async handleSubmit({ userName, password }) {
|
async handleSubmit({ userName, password }) {
|
||||||
try {
|
try {
|
||||||
let userFrom = { name: userName, password: password }
|
let userFrom = {
|
||||||
|
name: userName,
|
||||||
|
password: password
|
||||||
|
}
|
||||||
await this.handleLogin({
|
await this.handleLogin({
|
||||||
userFrom,
|
userFrom,
|
||||||
Main,
|
Main,
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="content-view">
|
<div class="content-view">
|
||||||
|
<Alert type="info" show-icon closable style="margin-bottom: 12px">
|
||||||
|
角色为<strong>全库共用</strong>,不按租户隔离;各租户用户可在「用户管理」中绑定同一角色,菜单权限以角色配置为准。
|
||||||
|
</Alert>
|
||||||
<div class="table-head-tool">
|
<div class="table-head-tool">
|
||||||
<Button type="primary" @click="showAddWarp">新增</Button>
|
<Button type="primary" @click="showAddWarp">新增</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -73,6 +76,23 @@ export default {
|
|||||||
this.init()
|
this.init()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
parseRoleMenuIds(menus) {
|
||||||
|
if (menus == null || menus === '') {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
if (Array.isArray(menus)) {
|
||||||
|
return menus.map((id) => Number(id)).filter((id) => !Number.isNaN(id))
|
||||||
|
}
|
||||||
|
if (typeof menus === 'string') {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(menus)
|
||||||
|
return this.parseRoleMenuIds(parsed)
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
},
|
||||||
async init() {
|
async init() {
|
||||||
let res = await roleServer.list()
|
let res = await roleServer.list()
|
||||||
this.gridOption.data = res.data
|
this.gridOption.data = res.data
|
||||||
@@ -120,12 +140,8 @@ export default {
|
|||||||
|
|
||||||
let res = await menuServer.list()
|
let res = await menuServer.list()
|
||||||
let tree = uiTool.transformTree(res.data)
|
let tree = uiTool.transformTree(res.data)
|
||||||
if (row.menus) {
|
this.expandTreeAll = this.parseRoleMenuIds(row.menus)
|
||||||
this.expandTreeAll = JSON.parse(row.menus)
|
this.treeData = this.mapTree(tree)
|
||||||
this.treeData = this.mapTree(tree)
|
|
||||||
} else {
|
|
||||||
this.treeData = this.mapTree(tree)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isShowPermission = true
|
this.isShowPermission = true
|
||||||
},
|
},
|
||||||
@@ -136,7 +152,7 @@ export default {
|
|||||||
p.children = this.mapTree(p.children)
|
p.children = this.mapTree(p.children)
|
||||||
}
|
}
|
||||||
|
|
||||||
let row = this.expandTreeAll.find((p2) => p2 === p.id)
|
let row = this.expandTreeAll.find((p2) => Number(p2) === Number(p.id))
|
||||||
if (row) {
|
if (row) {
|
||||||
p.checked = true
|
p.checked = true
|
||||||
}
|
}
|
||||||
|
|||||||
143
src/views/system/sys_tenant.vue
Normal file
143
src/views/system/sys_tenant.vue
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
<template>
|
||||||
|
<div class="content-view">
|
||||||
|
<Alert type="warning" show-icon style="margin-bottom: 12px">
|
||||||
|
仅<strong>平台租户</strong>(is_platform=1)可维护租户列表;普通租户登录后本页仅能看到自身租户信息。
|
||||||
|
新增时<strong>租户编码可留空</strong>,留空则由服务端自动生成;填写则使用自定义编码(须唯一)。需在数据库执行迁移脚本创建 <code>sys_tenant</code> 表及默认数据。
|
||||||
|
</Alert>
|
||||||
|
<div class="table-head-tool">
|
||||||
|
<Button type="primary" @click="showAddWarp">新增租户</Button>
|
||||||
|
</div>
|
||||||
|
<div class="table-body">
|
||||||
|
<tables ref="tables" v-model="gridOption.data" :columns="gridOption.columns" />
|
||||||
|
</div>
|
||||||
|
<editModal ref="editModal" :columns="gridOption.columns" :data="gridOption.editRow" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import uiTool from '@/utils/uiTool'
|
||||||
|
import sysTenantServer from '@/api/system/sysTenantServer'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'sys_tenant_page',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
gridOption: {
|
||||||
|
editRow: {},
|
||||||
|
columns: [
|
||||||
|
{ title: 'id', key: 'id' },
|
||||||
|
{ title: '名称', key: 'name', name: 'name', required: true },
|
||||||
|
{
|
||||||
|
title: '租户编码',
|
||||||
|
key: 'code',
|
||||||
|
name: 'code',
|
||||||
|
placeholder: '留空则保存时自动生成;填写则使用自定义编码(须唯一)'
|
||||||
|
},
|
||||||
|
{ title: '备注', key: 'remark', name: 'remark' },
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
key: 'status',
|
||||||
|
name: 'status',
|
||||||
|
com: 'Radio',
|
||||||
|
source: [
|
||||||
|
{ key: 1, value: '启用' },
|
||||||
|
{ key: 0, value: '停用' }
|
||||||
|
],
|
||||||
|
render(h, p) {
|
||||||
|
return h('span', p.row.status === 1 ? '启用' : '停用')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '平台租户',
|
||||||
|
key: 'is_platform',
|
||||||
|
name: 'is_platform',
|
||||||
|
com: 'Switch',
|
||||||
|
render(h, p) {
|
||||||
|
return h('span', Number(p.row.is_platform) === 1 ? '是' : '否')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
render: (h, params) => {
|
||||||
|
if (params.row.id === 1) {
|
||||||
|
return h('span', { style: { color: '#999' } }, '内置租户')
|
||||||
|
}
|
||||||
|
const btns = [
|
||||||
|
{
|
||||||
|
title: '修改',
|
||||||
|
type: 'primary',
|
||||||
|
click: () => this.showEditWarp(params.row)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '删除',
|
||||||
|
type: 'primary',
|
||||||
|
click: () => this.delConfirm(params.row)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return uiTool.getBtn(h, btns)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
data: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.init()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
normalizeTenantRow(row) {
|
||||||
|
const r = { ...row }
|
||||||
|
r.is_platform = r.is_platform === true || r.is_platform === 1 || r.is_platform === '1' ? 1 : 0
|
||||||
|
if (r.code != null && typeof r.code === 'string' && !r.code.trim()) {
|
||||||
|
delete r.code
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
},
|
||||||
|
async init() {
|
||||||
|
const res = await sysTenantServer.list()
|
||||||
|
if (res && res.code === 0) {
|
||||||
|
this.gridOption.data = res.data || []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showAddWarp() {
|
||||||
|
this.$refs.editModal.addShow(
|
||||||
|
{ status: 1, is_platform: 0 },
|
||||||
|
async (row) => {
|
||||||
|
await sysTenantServer.add(this.normalizeTenantRow(row))
|
||||||
|
this.$Message.success('新增成功')
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
showEditWarp(row) {
|
||||||
|
this.$refs.editModal.editShow(row, async (newRow) => {
|
||||||
|
await sysTenantServer.edit(this.normalizeTenantRow(newRow))
|
||||||
|
this.$Message.success('修改成功')
|
||||||
|
this.init()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
delConfirm(row) {
|
||||||
|
uiTool.delConfirm(async () => {
|
||||||
|
await sysTenantServer.del(row)
|
||||||
|
this.$Message.success('删除成功')
|
||||||
|
this.init()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.content-view {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.table-body {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="content-view">
|
<div class="content-view">
|
||||||
|
<Alert v-if="currentTenant" type="info" show-icon closable style="margin-bottom: 12px">
|
||||||
|
当前租户:<strong>{{ currentTenant.name }}</strong>({{ currentTenant.code }})。用户按租户隔离;
|
||||||
|
<strong>角色全库共用</strong>,下拉中的角色对所有租户一致。
|
||||||
|
</Alert>
|
||||||
|
|
||||||
<div class="table-head-tool">
|
<div class="table-head-tool">
|
||||||
<Button type="primary" @click="showAddWarp">新增</Button>
|
<Button type="primary" @click="showAddWarp">新增</Button>
|
||||||
@@ -16,9 +20,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters, mapState } from 'vuex'
|
||||||
import userServer from '@/api/system/userServer'
|
import userServer from '@/api/system/userServer'
|
||||||
import roleServer from '@/api/system/roleServer'
|
import roleServer from '@/api/system/roleServer'
|
||||||
|
import sysTenantServer from '@/api/system/sysTenantServer'
|
||||||
import uiTool from '@/utils/uiTool'
|
import uiTool from '@/utils/uiTool'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -29,8 +34,21 @@ export default {
|
|||||||
gridOption: {
|
gridOption: {
|
||||||
editRow: {},
|
editRow: {},
|
||||||
columns: [
|
columns: [
|
||||||
{ title: '登陆名', key: 'name' },
|
{ title: '登陆名', key: 'name', name: 'name' },
|
||||||
|
{
|
||||||
|
title: '租户',
|
||||||
|
key: 'tenant_id',
|
||||||
|
name: 'tenant_id',
|
||||||
|
com: 'Select',
|
||||||
|
required: true,
|
||||||
|
render(h, params) {
|
||||||
|
if (params.row.tenant) {
|
||||||
|
const t = params.row.tenant
|
||||||
|
return h('span', `${t.name || ''}(${t.code || ''})`)
|
||||||
|
}
|
||||||
|
return h('span', params.row.tenant_id != null ? String(params.row.tenant_id) : '')
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '密码',
|
title: '密码',
|
||||||
key: 'password',
|
key: 'password',
|
||||||
@@ -41,6 +59,7 @@ export default {
|
|||||||
{
|
{
|
||||||
title: '所属角色',
|
title: '所属角色',
|
||||||
key: 'roleId',
|
key: 'roleId',
|
||||||
|
name: 'roleId',
|
||||||
com: 'Select',
|
com: 'Select',
|
||||||
render: (h, params) => {
|
render: (h, params) => {
|
||||||
if (params.row.role) {
|
if (params.row.role) {
|
||||||
@@ -83,6 +102,12 @@ export default {
|
|||||||
...mapGetters({
|
...mapGetters({
|
||||||
shopList: 'shop/shopList',
|
shopList: 'shop/shopList',
|
||||||
}),
|
}),
|
||||||
|
...mapState('user', ['currentTenant']),
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
currentTenant() {
|
||||||
|
this.initCol()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.init()
|
this.init()
|
||||||
@@ -94,31 +119,53 @@ export default {
|
|||||||
this.gridOption.data = res.data
|
this.gridOption.data = res.data
|
||||||
},
|
},
|
||||||
async initCol() {
|
async initCol() {
|
||||||
let res = await roleServer.list()
|
const res = await roleServer.list()
|
||||||
this.roles = res.data
|
this.roles = res.data || []
|
||||||
let roleSource = this.roles.map((p) => {
|
const roleSource = this.roles.map((p) => ({
|
||||||
return {
|
key: p.id,
|
||||||
key: p.id,
|
value: p.name,
|
||||||
value: p.name,
|
}))
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
let roleRow = this.gridOption.columns.find((p) => p.key === 'roleId')
|
const roleRow = this.gridOption.columns.find((p) => p.key === 'roleId')
|
||||||
if (roleRow) {
|
if (roleRow) {
|
||||||
roleRow.source = roleSource
|
roleRow.source = roleSource
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tenantRes = await sysTenantServer.list()
|
||||||
|
const tenants = tenantRes && tenantRes.code === 0 && Array.isArray(tenantRes.data) ? tenantRes.data : []
|
||||||
|
const tenantSource = tenants.map((t) => ({
|
||||||
|
key: Number(t.id),
|
||||||
|
value: `${t.name || ''}(${t.code || ''})`,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const platform = this.currentTenant && Number(this.currentTenant.is_platform) === 1
|
||||||
|
const tenantCol = this.gridOption.columns.find((p) => p.key === 'tenant_id')
|
||||||
|
if (tenantCol) {
|
||||||
|
tenantCol.source = tenantSource
|
||||||
|
tenantCol.disabled = !platform
|
||||||
|
tenantCol.disabledOnAdd = !platform
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
showAddWarp() {
|
showAddWarp() {
|
||||||
this.$refs.editModal.addShow({}, async (newRow) => {
|
const tid = this.currentTenant && this.currentTenant.id != null ? Number(this.currentTenant.id) : undefined
|
||||||
await userServer.add(newRow)
|
this.$refs.editModal.addShow({ tenant_id: tid }, async (newRow) => {
|
||||||
|
const payload = { ...newRow }
|
||||||
|
if (payload.tenant_id != null) {
|
||||||
|
payload.tenant_id = Number(payload.tenant_id)
|
||||||
|
}
|
||||||
|
await userServer.add(payload)
|
||||||
this.$Message.success('新增成功!')
|
this.$Message.success('新增成功!')
|
||||||
this.init()
|
this.init()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
showEditWarp(row) {
|
showEditWarp(row) {
|
||||||
this.$refs.editModal.editShow(row, async (newRow) => {
|
this.$refs.editModal.editShow({ ...row }, async (newRow) => {
|
||||||
await userServer.edit(newRow)
|
const payload = { ...newRow }
|
||||||
|
if (payload.tenant_id != null) {
|
||||||
|
payload.tenant_id = Number(payload.tenant_id)
|
||||||
|
}
|
||||||
|
await userServer.edit(payload)
|
||||||
this.$Message.success('修改成功!')
|
this.$Message.success('修改成功!')
|
||||||
this.init()
|
this.init()
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user