From c73afd2325873308134d79b2a2669f405109693e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=88=90?= Date: Mon, 13 Apr 2026 11:16:22 +0800 Subject: [PATCH] 1 --- src/api/system/index.js | 1 + src/api/system/sysTenantServer.js | 22 ++++ src/assets/css/ivewExpand.less | 9 +- src/components/login-form/login-form.vue | 16 ++- src/components/main/main.vue | 23 +++- src/components/tables/index.vue | 94 +++++++++++++++-- src/config/menuConfig.js | 11 ++ src/store/user.js | 28 ++++- src/views/index.js | 3 + src/views/login/login.vue | 8 +- src/views/system/sys_role.vue | 30 ++++-- src/views/system/sys_tenant.vue | 127 +++++++++++++++++++++++ src/views/system/sys_user.vue | 79 ++++++++++++-- 13 files changed, 421 insertions(+), 30 deletions(-) create mode 100644 src/api/system/sysTenantServer.js create mode 100644 src/views/system/sys_tenant.vue diff --git a/src/api/system/index.js b/src/api/system/index.js index 69638dd..ea3bd94 100644 --- a/src/api/system/index.js +++ b/src/api/system/index.js @@ -18,6 +18,7 @@ export { default as modelFieldServer } from './modelFieldServer' export { default as modelServer } from './modelServer' export { default as paramSetupServer } from './paramSetupServer' export { default as sysControlTypeServer } from './sysControlTypeServer' +export { default as sysTenantServer } from './sysTenantServer' diff --git a/src/api/system/sysTenantServer.js b/src/api/system/sysTenantServer.js new file mode 100644 index 0000000..32d1ead --- /dev/null +++ b/src/api/system/sysTenantServer.js @@ -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 diff --git a/src/assets/css/ivewExpand.less b/src/assets/css/ivewExpand.less index 1f1a1f1..0f144ff 100644 --- a/src/assets/css/ivewExpand.less +++ b/src/assets/css/ivewExpand.less @@ -186,20 +186,21 @@ text-overflow: ellipsis; } -/* 自适应表格宽度 */ +/* 与 View Design 一致:横向/纵向滚动由 .ivu-table-body 上的 overflow 类控制,避免 wrapper 与整表同高导致横向条在页面最底 */ .ivu-table-wrapper { width: 100%; height: 100%; - overflow-x: auto; - overflow-y: auto; + overflow: hidden; flex: 1; display: flex; flex-direction: column; + min-height: 0; } .ivu-table { width: 100% !important; flex: 1; + min-height: 0; } /* 移除 table-layout: auto,使用 iView 默认的 fixed 布局以确保列对齐 */ @@ -208,8 +209,8 @@ } */ .ivu-table-body { - overflow-y: auto; flex: 1; + min-height: 0; } /* 按钮风格一致 */ diff --git a/src/components/login-form/login-form.vue b/src/components/login-form/login-form.vue index e0399a6..2784779 100644 --- a/src/components/login-form/login-form.vue +++ b/src/components/login-form/login-form.vue @@ -14,6 +14,13 @@ + + + + + + + @@ -40,7 +47,8 @@ export default { return { form: { userName: '', - password: '' + password: '', + tenantCode: 'default' } } }, @@ -48,7 +56,8 @@ export default { rules() { return { userName: this.userNameRules, - password: this.passwordRules + password: this.passwordRules, + tenantCode: [{ required: false }] } } }, @@ -58,7 +67,8 @@ export default { if (valid) { this.$emit('on-success-valid', { userName: this.form.userName, - password: this.form.password + password: this.form.password, + tenantCode: (this.form.tenantCode || '').trim() || 'default' }) } }) diff --git a/src/components/main/main.vue b/src/components/main/main.vue index 56eaffb..73dd524 100644 --- a/src/components/main/main.vue +++ b/src/components/main/main.vue @@ -17,6 +17,11 @@
+ {{ currentTenant.name }}
@@ -42,7 +47,7 @@ import User from './components/user' import ABackTop from './components/a-back-top' import Fullscreen from './components/fullscreen' import Language from './components/language' -import { mapGetters } from 'vuex' +import { mapGetters, mapState } from 'vuex' import headerBar from './components/header-bar' import loadFlower from '../load-flower/index' import Terminal from './components/terminal' @@ -77,6 +82,7 @@ export default { userName: 'user/userName', userAvator: 'user/avatorImgPath' }), + ...mapState('user', ['currentTenant']), cacheList() { return ['ParentView'] } @@ -149,6 +155,21 @@ export default { z-index: 999999; } +.main-tenant-tag { + margin-right: 16px; + 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 { display: flex; justify-content: center; diff --git a/src/components/tables/index.vue b/src/components/tables/index.vue index 776dcfb..cf60791 100644 --- a/src/components/tables/index.vue +++ b/src/components/tables/index.vue @@ -13,11 +13,24 @@ 下载 - - - - -
+
+ + + + +
+
@@ -81,6 +94,9 @@ export default { }, // 动态计算的偏移量 calculatedOffset: null, + /** 传给 iView Table 的 max-height(px),使表体在可视区内滚动,横向条始终在视口底部 */ + bodyViewportMaxHeight: null, + _tableContentResizeObserver: null, } }, @@ -145,6 +161,13 @@ export default { // 使用手动指定的偏移量 return `calc(100vh - ${this.maxHeightOffset}px)` }, + + /** 未传 height 时由容器测量得到,避免宽表横向滚动条落在整表最底部 */ + effectiveTableMaxHeight() { + if (this.height) return undefined + if (this.bodyViewportMaxHeight == null) return undefined + return this.bodyViewportMaxHeight + }, }, methods: { @@ -174,6 +197,7 @@ export default { // 触发表格重新渲染 this.$refs.tablesMain.$forceUpdate() } + this.syncTableBodyMaxHeight() }) }, onChangePage(page) { @@ -184,6 +208,43 @@ export default { 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() { this.$nextTick(() => { @@ -203,6 +264,9 @@ export default { // 计算总偏移量 this.calculatedOffset = topOffset + pageBoxHeight + bottomPadding + this.$nextTick(() => { + this.syncTableBodyMaxHeight() + }) }) }, }, @@ -243,6 +307,7 @@ export default { // 动态计算偏移量 this.calculateOffset() + this.setupTableContentResizeObserver() // 如果数据或列配置为空,输出警告 if (!this.insideTableData || this.insideTableData.length === 0) { @@ -255,11 +320,17 @@ export default { // 监听窗口大小变化,重新计算偏移量 window.addEventListener('resize', this.calculateOffset) + window.addEventListener('resize', this.syncTableBodyMaxHeight) }, beforeDestroy() { // 移除窗口大小监听 window.removeEventListener('resize', this.calculateOffset) + window.removeEventListener('resize', this.syncTableBodyMaxHeight) + if (this._tableContentResizeObserver) { + this._tableContentResizeObserver.disconnect() + this._tableContentResizeObserver = null + } }, } @@ -268,8 +339,12 @@ export default { width: 100%; .table-content { - overflow-y: auto; + /* 纵向由 Table 内部表体滚动;避免与外层双滚动导致横向条在整页最底 */ + overflow: hidden; width: 100%; + min-height: 0; + display: flex; + flex-direction: column; .table-title { font-weight: bold; @@ -279,10 +354,17 @@ export default { padding: 0rem 0.15rem; height: 50px; line-height: 50px; + flex-shrink: 0; .right-link { float: right; } } + + .table-main { + flex: 1; + min-height: 0; + overflow: hidden; + } } .page-box { diff --git a/src/config/menuConfig.js b/src/config/menuConfig.js index 9af152c..2441481 100644 --- a/src/config/menuConfig.js +++ b/src/config/menuConfig.js @@ -115,6 +115,17 @@ export const defaultMenus = [ is_show_menu: 1, icon: 'md-text', 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 } ] } diff --git a/src/store/user.js b/src/store/user.js index 2bbdd42..35e83a7 100644 --- a/src/store/user.js +++ b/src/store/user.js @@ -3,6 +3,16 @@ import uiTool from '../utils/uiTool' import { defaultMenus, filterMenusByIds } from '../config/menuConfig' 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 { namespaced: true, state: { @@ -10,7 +20,9 @@ export default { avatorImgPath: '', token: getToken(), 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: { setAvator(state, avatorPath) { @@ -31,6 +43,14 @@ export default { setMenuList(state, menus) { state.menuList = 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: { @@ -162,6 +182,11 @@ export default { commit('setUserName', name) commit('setToken', token) + if (res.data.tenant) { + commit('setCurrentTenant', res.data.tenant) + } else { + commit('setCurrentTenant', null) + } // 登录接口返回的 authorityMenus 是菜单 ID 数组字符串 console.log('登录返回的菜单 IDs:', authorityMenusIds) @@ -205,6 +230,7 @@ export default { commit('setToken', '') commit('setAuthorityMenus', '[]') commit('setMenuList', []) + commit('setCurrentTenant', null) localStorage.removeItem('menuList') window.location.reload() } diff --git a/src/views/index.js b/src/views/index.js index df359a9..e551f17 100644 --- a/src/views/index.js +++ b/src/views/index.js @@ -4,6 +4,7 @@ import SysLog from './system/sys_log.vue' import SysParamSetup from './system/sys_param_setup.vue' import SysRole from './system/sys_role.vue' import SysUser from './system/sys_user.vue' +import SysTenant from './system/sys_tenant.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_role': SysRole, 'system/sys_user': SysUser, + 'system/sys_tenant': SysTenant, 'system/sys_control': SysControl, 'system/sys_menu': SysMenu, 'system/sys_title': SysTitle, @@ -60,6 +62,7 @@ export default { SysParamSetup, SysRole, SysUser, + SysTenant, SysControl, SysMenu, SysTitle, diff --git a/src/views/login/login.vue b/src/views/login/login.vue index 3d7ed73..76c7e80 100644 --- a/src/views/login/login.vue +++ b/src/views/login/login.vue @@ -43,9 +43,13 @@ export default { }, methods: { ...mapActions('user', ['handleLogin']), - async handleSubmit({ userName, password }) { + async handleSubmit({ userName, password, tenantCode }) { try { - let userFrom = { name: userName, password: password } + let userFrom = { + name: userName, + password: password, + tenant_code: tenantCode || 'default' + } await this.handleLogin({ userFrom, Main, diff --git a/src/views/system/sys_role.vue b/src/views/system/sys_role.vue index d982b6f..3213613 100644 --- a/src/views/system/sys_role.vue +++ b/src/views/system/sys_role.vue @@ -1,5 +1,8 @@