- 操作列:group-user/操作日志/开放接口日志/推送日志四页手写 h('a') 迁 actionColumn({mode:"text"}),颜色归通道 var(--brand);推送日志「重试」改 disabledWhen 置灰保位
- group-user 状态列 h('Tag')/h('Badge') 字符串组件名改 import 真组件(Vue3 坑)
- 高度:日志三页根节点改 page-wrapper yh-fill;TablePageAlovaNew 对齐 flow-center 同构版(.table-region + useElementHeight 实测 + 页长记忆);RoleAuthzList 转 tree-wrapper 骨架(外边界/间隙 16)
- 主题色:当前学校/当前组/当前角色、应用管理左类型面板、角色管理卡片操作位硬编码蓝改 var(--brand) 系令牌
- 顶栏房子图标钮删除(§4:首页入口收敛面包屑「首页」)
- 含 2026-10-10 试点未提交件:tableActions.ts 七函数、main.scss .op-cell、AppList/ModuleManage/RoleUserManage 迁移
This commit is contained in:
@@ -115,3 +115,44 @@ h4.subtitle {
|
||||
margin-right: 4px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
/* ================= 表格操作列(utils/tableActions.ts)=================
|
||||
.op-cell:flex 横排容器——把 iView Tooltip/Dropdown 的块级包装层降级为 flex item,
|
||||
强制横排(根因:.operating-btn 在通道基线 display:block,.ivu-tooltip 包装层默认 block,
|
||||
逐钮竖排;不要再在 cell 上动 display,会牵连其它列)。
|
||||
先落工程内,四端同构后此段上提 YschTheme 主题层。 */
|
||||
.op-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
|
||||
// 链间距统一由容器 gap 管理,退役通道基线的 padding-right:10px
|
||||
.operating-link {
|
||||
padding-right: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
/* 「更多▾」触发器(Dropdown transfer 后菜单挂 body,样式必须全局) */
|
||||
.op-more {
|
||||
.op-more-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.op-more-arrow {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 「更多」菜单内危险项(红色 + hover 红底,与 text-error 同源 $color-error) */
|
||||
.ivu-dropdown-item.op-item-danger {
|
||||
color: $color-error !important;
|
||||
|
||||
&:hover {
|
||||
color: $color-error !important;
|
||||
background-color: rgba(232, 104, 74, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
<template>
|
||||
<Table :columns="columns" :data="list" :height="tableH" :loading="loading" no-data-text="暂无数据"
|
||||
class="link-table table-bottom-box" @on-selection-change="selectChange">
|
||||
<template #loading>
|
||||
<div class="table-spin-load">加载中</div>
|
||||
</template>
|
||||
<template v-for="slotName in tableSlotNames" :key="slotName" v-slot:[slotName]="slotProps">
|
||||
<slot :name="slotName" v-bind="slotProps" />
|
||||
</template>
|
||||
</Table>
|
||||
<div class="table-region" ref="regionRef">
|
||||
<Table :columns="columns" :data="list" :height="tableH" :loading="loading" no-data-text="暂无数据"
|
||||
class="link-table table-bottom-box" @on-selection-change="selectChange">
|
||||
<template #loading>
|
||||
<div class="table-spin-load">加载中</div>
|
||||
</template>
|
||||
<template v-for="slotName in tableSlotNames" :key="slotName" v-slot:[slotName]="slotProps">
|
||||
<slot :name="slotName" v-bind="slotProps" />
|
||||
</template>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<div class="page-panel">
|
||||
<div class="fl align-middle">
|
||||
<div class="fl ml-15 align-middle">
|
||||
<slot :checkList="selectList" />
|
||||
</div>
|
||||
|
||||
@@ -21,8 +23,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, useSlots } from 'vue'
|
||||
import Utils from '@/utils'
|
||||
import { computed, ref, useSlots, watch } from 'vue'
|
||||
import { useElementHeight } from '@/utils/hook/useElementHeight'
|
||||
import { OperatorEnum } from '@/types'
|
||||
|
||||
interface Props {
|
||||
@@ -44,7 +46,10 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
const slots = useSlots()
|
||||
|
||||
const tableH = ref(props.tableHeight ?? Utils.useTableHeight())
|
||||
const regionRef = ref<HTMLElement | null>(null)
|
||||
const measuredH = useElementHeight(regionRef)
|
||||
// 显式传 tableHeight 则尊重之;否则实测 .table-region(flex:1)高度喂固定表头,首帧未测出前用 undefined(auto)
|
||||
const tableH = computed(() => props.tableHeight ?? (measuredH.value || undefined))
|
||||
const tableSlotNames = computed(() => Object.keys(slots).filter(name => !['default', 'loading'].includes(name)))
|
||||
|
||||
const selectList = ref<number[]>([])
|
||||
@@ -68,18 +73,30 @@ const {
|
||||
total: res => props.formatResponse(res).total,
|
||||
data: res => props.formatResponse(res).list,
|
||||
initialPage: 1,
|
||||
initialPageSize: 10,
|
||||
// 页长记忆(bsch 应用页规范 §3:customPageCount localStorage)
|
||||
initialPageSize: parseInt(localStorage.getItem('customPageCount') || '10') || 10,
|
||||
immediate: props.autoLoad,
|
||||
}
|
||||
)
|
||||
|
||||
// 数据重载后清空勾选,避免批量操作用到旧选中行
|
||||
watch(resp, () => {
|
||||
if (selectList.value.length) {
|
||||
selectList.value = []
|
||||
emit('selection-change', selectList.value)
|
||||
}
|
||||
})
|
||||
|
||||
const list = computed(() => {
|
||||
if (!resp.value) return []
|
||||
return props.dataFilter ? props.dataFilter(resp.value) : resp.value
|
||||
})
|
||||
|
||||
const onPageChange = (p: number) => (page.value = p)
|
||||
const onSizeChange = (s: number) => (pageSize.value = s)
|
||||
const onSizeChange = (s: number) => {
|
||||
pageSize.value = s
|
||||
localStorage.setItem('customPageCount', String(s))
|
||||
}
|
||||
|
||||
function refreshData() {
|
||||
page.value = 1
|
||||
|
||||
@@ -191,11 +191,6 @@ function linkHomePage() {
|
||||
<header class="header-container fix">
|
||||
<!-- 云海 logo 徽标(结构与 Notice layout 对齐,皮肤见通道 base.layout.css .logo-badge) -->
|
||||
<span class="logo-badge fl"><img :src="logoUrl" alt="云海" @error="onLogoError" /></span>
|
||||
<div class="fl align-middle icon-box">
|
||||
<div title="首页" class="icon-btn home-btn" @click="linkHomePage">
|
||||
<SvgIcon name="shouye" :size="22" color="#fff" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="delimiter fl align-middle"></div>
|
||||
<h4 class="fl align-middle">{{ appName }}</h4>
|
||||
<div class="school-bg"></div>
|
||||
@@ -295,12 +290,4 @@ function linkHomePage() {
|
||||
.header-aside .logout svg {
|
||||
display: block;
|
||||
}
|
||||
.home-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.home-btn svg {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { ref, onMounted, onBeforeUnmount, watch, type Ref } from "vue";
|
||||
|
||||
/**
|
||||
* 实测某容器元素的可用高度(px),随尺寸变化实时更新。
|
||||
*
|
||||
* 用途:表格页把 <Table> 放进 flex:1 的 .table-region 里,用本组合式量出该区域
|
||||
* 的真实高度喂给 view-ui 的 `:height`(固定表头需要一个数值高度),从而彻底替代
|
||||
* 旧的 useTableHeight()「视口高 - 一堆写死常量」的算法——加/删工具条、改顶栏高
|
||||
* 都不再需要同步改任何魔法数字。
|
||||
*
|
||||
* @param el 目标容器 ref(模板里 ref 绑定的那个元素)
|
||||
* @returns 响应式高度(px);挂载测量前为 0,调用方可 `:height="h || undefined"` 兜底
|
||||
*/
|
||||
export function useElementHeight(el: Ref<HTMLElement | null>): Ref<number> {
|
||||
const height = ref(0);
|
||||
let ro: ResizeObserver | null = null;
|
||||
|
||||
const measure = () => {
|
||||
if (el.value) height.value = Math.floor(el.value.clientHeight);
|
||||
};
|
||||
|
||||
const observe = () => {
|
||||
ro?.disconnect();
|
||||
if (!el.value || typeof ResizeObserver === "undefined") return;
|
||||
measure();
|
||||
ro = new ResizeObserver(measure);
|
||||
ro.observe(el.value);
|
||||
};
|
||||
|
||||
onMounted(observe);
|
||||
// v-if / v-show 切换导致容器重建时,重新挂观察
|
||||
watch(el, observe);
|
||||
onBeforeUnmount(() => ro?.disconnect());
|
||||
|
||||
return height;
|
||||
}
|
||||
+12
-33
@@ -1,5 +1,5 @@
|
||||
import { IObj, UseTableSetting } from "@/types/Basics";
|
||||
import { resolveComponent } from "vue";
|
||||
import { actionIcon, actionText } from "@/utils/tableActions";
|
||||
export default class Utils {
|
||||
/**
|
||||
* 格式化日期
|
||||
@@ -90,7 +90,8 @@ export default class Utils {
|
||||
}
|
||||
|
||||
/**
|
||||
* iview Tooltip封装
|
||||
* iview Tooltip封装(旧 API,薄封装转调 tableActions.actionIcon,签名不变)
|
||||
* 2026-10-10 起 D1 修正:禁用态 onClick 置 null(原先禁用仍可点)。
|
||||
*/
|
||||
public static operatingHandle(
|
||||
h: any,
|
||||
@@ -99,30 +100,10 @@ export default class Utils {
|
||||
callback: Function,
|
||||
disabled: boolean = false,
|
||||
) {
|
||||
if (disabled) iconClass += " disabled";
|
||||
|
||||
return h(
|
||||
resolveComponent("Tooltip"),
|
||||
{
|
||||
content: info,
|
||||
placement: "bottom",
|
||||
transfer: true,
|
||||
},
|
||||
{
|
||||
default: () => [
|
||||
h(
|
||||
"div",
|
||||
{
|
||||
class: `operating-btn iconfont ${iconClass}`,
|
||||
onClick: callback,
|
||||
},
|
||||
null,
|
||||
),
|
||||
],
|
||||
},
|
||||
);
|
||||
return actionIcon(h, { key: info, label: info, icon: iconClass, on: () => callback(), disabled });
|
||||
}
|
||||
|
||||
/** 旧 API,薄封装转调 tableActions.actionText(签名不变;第三参仅识别 text-error → danger) */
|
||||
public static operatingTextHandle(
|
||||
h: any,
|
||||
info: string,
|
||||
@@ -130,15 +111,13 @@ export default class Utils {
|
||||
callback: Function,
|
||||
disabled: boolean = false,
|
||||
) {
|
||||
if (disabled) className += " disabled";
|
||||
return h(
|
||||
"div",
|
||||
{
|
||||
class: `operating-link ${className}`,
|
||||
onClick: disabled ? null : callback,
|
||||
},
|
||||
info,
|
||||
);
|
||||
return actionText(h, {
|
||||
key: info,
|
||||
label: info,
|
||||
danger: (className || "").includes("text-error"),
|
||||
on: () => callback(),
|
||||
disabled,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* 表格操作列统一原子(方案:outputs/table-action-buttons/操作列统一方案-20261010.md)
|
||||
*
|
||||
* 三条变化轴各自独立,每加一种形态 = 加 1 个渲染器 + RENDERERS 表加 1 行,不动既有分支:
|
||||
* 轴1 形态 actionIcon / actionText / actionMore —— 只画节点,零策略
|
||||
* 轴2 动作集 normalizeActions —— 纯数据:when 过滤 / disabled 归一 / danger 沉底
|
||||
* 轴3 溢出 splitActions —— 纯函数:直出与「更多」分档,零 DOM 可单测
|
||||
* 组装 actionCell / actionColumn —— 唯一懂 DOM 结构与表格列
|
||||
*
|
||||
* 形态由页面【显式声明】(mode: "icon" | "text",缺省 text),不按动作数自动切换——
|
||||
* 工程间风格区分是既定口径(Notice=图标钮 / UserBase=文字链),各自恒定不漂移。
|
||||
* 「更多▾」只是长尾收纳(动作数 ≥ maxInline+2 时启用),不改变形态、不分档。
|
||||
* 菜单项纯文字不带图标(动作一多找不到贴切图标,文字更准);transfer 到 body,
|
||||
* 避免 fixed/sticky 操作列裁剪与层叠上下文遮盖(原型已实证)。
|
||||
*/
|
||||
import { h, resolveComponent } from "vue";
|
||||
|
||||
export interface ActionItem {
|
||||
/** 稳定标识:溢出分桶按 key 去重,必填 */
|
||||
key: string;
|
||||
label: string;
|
||||
/** 点击回调,normalizeActions 阶段闭包绑定行数据 */
|
||||
on: (row: any) => void;
|
||||
/** iconfont 类名(仅 mode:"icon" 时消费;通道 icons.css 全集见 YschTheme) */
|
||||
icon?: string;
|
||||
/** 危险动作:文字转红(text-error)、恒沉底、「更多」内分隔线隔离 */
|
||||
danger?: boolean;
|
||||
/** 返回 false → 该行不渲染此动作(决定「有没有」) */
|
||||
when?: (row: any) => boolean;
|
||||
/** 直接置灰 */
|
||||
disabled?: boolean;
|
||||
/** 按行判定置灰(决定「能不能点」),disabled 显式值优先 */
|
||||
disabledWhen?: (row: any) => boolean;
|
||||
}
|
||||
|
||||
export interface ActionCellOpts {
|
||||
/** 形态显式声明:icon=图标圆钮 / text=文字链,缺省 text;不按动作数自动切换 */
|
||||
mode?: "icon" | "text";
|
||||
/** 超额动作收进「更多」,默认开(防 5+ 动作页面忘传爆宽) */
|
||||
collapsible?: boolean;
|
||||
/** 直出上限,默认 2;n ≤ maxInline+1 时全直出(3 个动作不进更多) */
|
||||
maxInline?: number;
|
||||
/** 列宽;缺省 icon = n*40+40 / text = 180 */
|
||||
width?: number;
|
||||
align?: "center" | "left" | "right";
|
||||
/** 缺省不固定列(与存量页面一致);需要吸右显式传 "right" */
|
||||
fixed?: "right" | "left" | false;
|
||||
}
|
||||
|
||||
/* ============ 轴 1:渲染器(只画节点,零策略) ============ */
|
||||
|
||||
/** 图标圆钮:Tooltip > div.operating-btn(沿用通道既有 class,主题层零改动) */
|
||||
export function actionIcon(h: any, item: ActionItem) {
|
||||
return h(
|
||||
resolveComponent("Tooltip"),
|
||||
{ content: item.label, placement: "bottom", transfer: true },
|
||||
{
|
||||
default: () => [
|
||||
h("div", {
|
||||
class: `operating-btn iconfont ${item.icon}${item.disabled ? " disabled" : ""}`,
|
||||
onClick: item.disabled ? null : item.on,
|
||||
}),
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/** 文字链:danger 输出既有 text-error 类对接主题红色 */
|
||||
export function actionText(h: any, item: ActionItem) {
|
||||
return h(
|
||||
"div",
|
||||
{
|
||||
class: `operating-link${item.danger ? " text-error" : ""}${item.disabled ? " disabled" : ""}`,
|
||||
onClick: item.disabled ? null : item.on,
|
||||
},
|
||||
item.label,
|
||||
);
|
||||
}
|
||||
|
||||
/** 「更多▾」+ 下拉菜单:iView Dropdown transfer 模式,菜单挂 body */
|
||||
export function actionMore(h: any, items: ActionItem[]) {
|
||||
return h(
|
||||
resolveComponent("Dropdown"),
|
||||
{ class: "op-more", placement: "bottom-end", transfer: true },
|
||||
{
|
||||
default: () => [
|
||||
h("div", { class: "operating-link op-more-label" }, [
|
||||
"更多",
|
||||
h(resolveComponent("Icon"), { type: "ios-arrow-down", class: "op-more-arrow" }),
|
||||
]),
|
||||
],
|
||||
list: () => [
|
||||
h(resolveComponent("DropdownMenu"), {}, {
|
||||
default: () => items.map((it) =>
|
||||
h(
|
||||
resolveComponent("DropdownItem"),
|
||||
{
|
||||
key: it.key,
|
||||
divided: !!(it.danger && items.length > 1),
|
||||
disabled: !!it.disabled,
|
||||
class: it.danger ? "op-item-danger" : "",
|
||||
onClick: () => it.on(),
|
||||
},
|
||||
{ default: () => it.label },
|
||||
),
|
||||
),
|
||||
}),
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/* ============ 轴 2:归一化(纯数据) ============ */
|
||||
export function normalizeActions(items: ActionItem[], row: any): ActionItem[] {
|
||||
return items
|
||||
.filter((it) => (it.when ? it.when(row) : true))
|
||||
.map((it) => ({
|
||||
...it,
|
||||
disabled: it.disabled ?? (it.disabledWhen ? it.disabledWhen(row) : false),
|
||||
on: () => it.on(row),
|
||||
}))
|
||||
.sort((a, b) => Number(!!a.danger) - Number(!!b.danger)); // danger 恒沉底(稳定排序)
|
||||
}
|
||||
|
||||
/* ============ 轴 3:溢出策略(纯函数,可单测、零 DOM) ============ */
|
||||
export function splitActions(items: ActionItem[], maxInline = 2): { inline: ActionItem[]; overflow: ActionItem[] } {
|
||||
if (items.length <= maxInline + 1) return { inline: items, overflow: [] };
|
||||
const inline = items.filter((it) => !it.danger).slice(0, maxInline);
|
||||
const inlineKeys = new Set(inline.map((it) => it.key));
|
||||
return { inline, overflow: items.filter((it) => !inlineKeys.has(it.key)) };
|
||||
}
|
||||
|
||||
/* ============ 组装层 ============ */
|
||||
const RENDERERS: Record<string, (h: any, item: ActionItem) => any> = { icon: actionIcon, text: actionText };
|
||||
|
||||
export function actionCell(h: any, items: ActionItem[], opts: ActionCellOpts = {}) {
|
||||
const mode = opts.mode ?? "text";
|
||||
const { inline, overflow } = (opts.collapsible ?? true)
|
||||
? splitActions(items, opts.maxInline ?? 2)
|
||||
: { inline: items, overflow: [] };
|
||||
|
||||
const render = RENDERERS[mode] || actionText;
|
||||
const children = inline.map((it) => render(h, it));
|
||||
if (overflow.length) children.push(actionMore(h, overflow));
|
||||
|
||||
return h("div", { class: "op-cell" }, children); // flex 横排容器,见 main.scss
|
||||
}
|
||||
|
||||
/** 产出可直接塞进 columns 的操作列对象;列宽按「声明数」估算,when 过滤后只宽不窄 */
|
||||
export function actionColumn(items: ActionItem[], opts: ActionCellOpts = {}) {
|
||||
const mode = opts.mode ?? "text";
|
||||
return {
|
||||
title: "操作",
|
||||
key: "action",
|
||||
width: opts.width ?? (mode === "icon" ? items.length * 40 + 40 : 180),
|
||||
align: opts.align ?? "center",
|
||||
...(opts.fixed === undefined ? {} : { fixed: opts.fixed === false ? undefined : opts.fixed }),
|
||||
render: (h: any, p: any) => actionCell(h, normalizeActions(items, p.row), opts),
|
||||
};
|
||||
}
|
||||
@@ -372,14 +372,14 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.school-name {
|
||||
color: #2d8cf0;
|
||||
color: var(--brand);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.authz-panel {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-height: 320px;
|
||||
min-height: 0;
|
||||
padding: 12px 16px;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
|
||||
@@ -603,14 +603,14 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.group-name {
|
||||
color: #2d8cf0;
|
||||
color: var(--brand);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.authz-panel {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-height: 320px;
|
||||
min-height: 0;
|
||||
padding: 12px 16px;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
@@ -729,7 +729,7 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.open-link {
|
||||
color: #2d8cf0;
|
||||
color: var(--brand);
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
|
||||
+37
-55
@@ -31,7 +31,7 @@
|
||||
style="width:220px" @on-search="onSearch" @on-clear="onSearch" />
|
||||
</div>
|
||||
|
||||
<TablePageAlovaNew ref="tableRef" :dataApi="tableApi" :columns="columnsComp" :tableHeight="tableHeight" />
|
||||
<TablePageAlovaNew ref="tableRef" :dataApi="tableApi" :columns="columnsComp" />
|
||||
</div>
|
||||
|
||||
<!-- 容器组件:新增 / 编辑 -->
|
||||
@@ -51,7 +51,6 @@
|
||||
import { ref, computed } from "vue";
|
||||
defineOptions({ name: "AppList" });
|
||||
import { useRouter } from "vue-router";
|
||||
import useCurrentInstance from "@/utils/hook/useCurrentInstance";
|
||||
import MyConfirm from "@/components/MyConfirm";
|
||||
import AddAppContainer from "./AddAppContainer.vue";
|
||||
import EditAppContainer from "./EditAppContainer.vue";
|
||||
@@ -60,9 +59,9 @@ import TablePageAlovaNew from '@/components/TablePageAlovaNew.vue';
|
||||
import { OperatorEnum } from '@/types';
|
||||
import AppSort from './AppSort.vue';
|
||||
import { useAppSchoolScope } from "@/utils/hook/useAppSchoolScope";
|
||||
import { actionColumn } from "@/utils/tableActions";
|
||||
|
||||
const router = useRouter();
|
||||
const { $utils } = useCurrentInstance();
|
||||
|
||||
// 学校管理员上下文:多校显示切换下拉,覆盖仅对所选学校生效(默认并集,写操作须先选定学校)
|
||||
const { isSchoolMode, multiSchool, schoolList, currentSchoolId, requireSchool } = useAppSchoolScope();
|
||||
@@ -146,10 +145,6 @@ function onSortSaved() {
|
||||
tableRef.value?.reloadData(OperatorEnum.sel);
|
||||
}
|
||||
|
||||
const tableHeight = ref(
|
||||
$utils.useTableHeight({ isOperat: false, isPage: true, isSearch: true })
|
||||
);
|
||||
|
||||
/* ---------------- 表格固定列(不含“应用类型”列) ---------------- */
|
||||
const columns = [
|
||||
{ type: "index", title: "序号", width: 70 },
|
||||
@@ -174,25 +169,41 @@ const columns = [
|
||||
width: 80,
|
||||
render: (h: any, { row }: any) => h("span", row.appNature === "BASE" ? "基石" : "业务"),
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "action",
|
||||
align: 'center',
|
||||
width: 280,
|
||||
render: (h: any, { row }: any) => {
|
||||
actionColumn(
|
||||
[
|
||||
// SCHOOL 模式:仅编辑(名称/描述/图标,写学校覆盖表);模块管理/推送设置/删除全部隐藏。
|
||||
// 基石应用(app_nature=BASE)只做身份注册:模块/推送置灰;删除允许——基石也可下线,
|
||||
// 但走「输名确认」删除模(危险确认,与 projman 应用删除同款)。
|
||||
const base = row.appNature === "BASE";
|
||||
const actions = [$utils.operatingTextHandle(h, '编辑', '', () => handleEdit(row))];
|
||||
if (!isSchoolMode.value) {
|
||||
actions.push($utils.operatingTextHandle(h, '模块管理', '', () => goModuleManage(row), base));
|
||||
actions.push($utils.operatingTextHandle(h, '推送设置', '', () => openPushSetting(row), base));
|
||||
actions.push($utils.operatingTextHandle(h, '删除', 'text-error', () => handleDelete(row)));
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
},
|
||||
{ key: "edit", label: "编辑", icon: "icon-edit", on: (r: any) => handleEdit(r) },
|
||||
{
|
||||
key: "module",
|
||||
label: "模块管理",
|
||||
icon: "icon-apps",
|
||||
on: (r: any) => goModuleManage(r),
|
||||
when: () => !isSchoolMode.value,
|
||||
disabledWhen: (r: any) => r.appNature === "BASE",
|
||||
},
|
||||
{
|
||||
key: "push",
|
||||
label: "推送设置",
|
||||
icon: "icon-fasong",
|
||||
on: (r: any) => openPushSetting(r),
|
||||
when: () => !isSchoolMode.value,
|
||||
disabledWhen: (r: any) => r.appNature === "BASE",
|
||||
},
|
||||
{
|
||||
key: "del",
|
||||
label: "删除",
|
||||
icon: "icon-ashbin",
|
||||
on: (r: any) => handleDelete(r),
|
||||
when: () => !isSchoolMode.value,
|
||||
danger: true,
|
||||
},
|
||||
],
|
||||
// UserBase 恒文字链(工程风格显式声明,不随动作数漂移);
|
||||
// icon 字段仅留作未来切 mode:"icon" 的备选。4 动作 → 2 直出 + 推送设置/删除进「更多」
|
||||
{ mode: "text", width: 180 },
|
||||
),
|
||||
];
|
||||
|
||||
// 条件列:仅在「未筛选应用类型」时插入到“应用名称”之后
|
||||
@@ -310,7 +321,7 @@ const goModuleManage = (row: any) => {
|
||||
border-top: 1px solid #eeeeee;
|
||||
|
||||
.ivu-btn-text {
|
||||
color: #577afb;
|
||||
color: var(--brand);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,37 +342,8 @@ const goModuleManage = (row: any) => {
|
||||
}
|
||||
|
||||
li.active {
|
||||
color: #577afb;
|
||||
border-color: #577afb;
|
||||
color: var(--brand);
|
||||
border-color: var(--brand);
|
||||
}
|
||||
}
|
||||
|
||||
.action-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.action-link {
|
||||
cursor: pointer;
|
||||
color: #577afb;
|
||||
font-size: 13px;
|
||||
|
||||
&:hover {
|
||||
color: #3d5afe;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
color: #ed4014;
|
||||
|
||||
&:hover {
|
||||
color: #c51d04;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-divider {
|
||||
margin: 0 8px;
|
||||
color: #dcdee2;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -54,9 +54,7 @@ import { useRoute, useRouter } from "vue-router";
|
||||
import { debounce } from "lodash";
|
||||
import SvgIcon from "@/components/SvgIcon.vue";
|
||||
import MyConfirm from "@/components/MyConfirm";
|
||||
import useCurrentInstance from "@/utils/hook/useCurrentInstance";
|
||||
|
||||
const { $utils } = useCurrentInstance();
|
||||
import { actionColumn } from "@/utils/tableActions";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
@@ -116,22 +114,18 @@ const columns = [
|
||||
{ title: "ID", key: "customId", width: 120, align: "center", render: (h: any, { row }: any) => h("span", row.customId || "-") },
|
||||
{ title: "路径", key: "path", minWidth: 200, render: (h: any, { row }: any) => h("span", row.path || "-") },
|
||||
{ title: "排序号", key: "sortNo", width: 100, align: "center", render: (h: any, { row }: any) => h("span", row.sortNo ?? "-") },
|
||||
{
|
||||
title: "操作",
|
||||
key: "action",
|
||||
width: 220,
|
||||
align: "center",
|
||||
render: (h: any, { row }: any) => {
|
||||
const actions: any[] = [
|
||||
$utils.operatingTextHandle(h, '编辑', '', () => handleEdit(row)),
|
||||
$utils.operatingTextHandle(h, '删除', 'text-error', () => handleDelete(row)),
|
||||
];
|
||||
if (row.level < 5) {
|
||||
actions.splice(1, 0, $utils.operatingTextHandle(h, '添加子模块', '', () => handleAddChild(row)));
|
||||
}
|
||||
return h("div", { style: { display: "flex", justifyContent: "center", gap: "4px" } }, actions);
|
||||
// UserBase 恒文字链;3 动作 ≤ maxInline+1 → 全直出(与改前形态一致),列宽 220 → 180
|
||||
actionColumn([
|
||||
{ key: "edit", label: "编辑", icon: "icon-edit", on: (r: any) => handleEdit(r) },
|
||||
{
|
||||
key: "add",
|
||||
label: "添加子模块",
|
||||
icon: "icon-tianjia",
|
||||
on: (r: any) => handleAddChild(r),
|
||||
when: (r: any) => r.level < 5,
|
||||
},
|
||||
},
|
||||
{ key: "del", label: "删除", icon: "icon-ashbin", on: (r: any) => handleDelete(r), danger: true },
|
||||
], { mode: "text", width: 180 }),
|
||||
];
|
||||
|
||||
/* ---------------- 弹窗状态 ---------------- */
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
v-model="keyword" @on-search="onSearch" @on-clear="onClear" />
|
||||
</div>
|
||||
|
||||
<TablePageAlovaNew ref="tableRef" :dataApi="tableApi" :columns="columns" :tableHeight="tableHeight">
|
||||
<TablePageAlovaNew ref="tableRef" :dataApi="tableApi" :columns="columns">
|
||||
<template #default="{ checkList }">
|
||||
<Button type="primary" ghost :disabled="!checkList?.length" @click="openAdjust(checkList)">调整组</Button>
|
||||
<Button type="warning" ghost class="ml-15" :disabled="!checkList?.length" @click="openBatchStatus(checkList)">设置状态</Button>
|
||||
@@ -59,7 +59,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from "vue";
|
||||
import { Message } from "view-ui-plus";
|
||||
import { Message, Tag, Badge } from "view-ui-plus";
|
||||
import useCurrentInstance from "@/utils/hook/useCurrentInstance";
|
||||
import MyConfirm from "@/components/MyConfirm";
|
||||
import TablePageAlovaNew from '@/components/TablePageAlovaNew.vue';
|
||||
@@ -70,10 +70,11 @@ import EditUserContainer from './user/EditUserContainer.vue';
|
||||
import AdjustGroupContainer from './AdjustGroupContainer.vue';
|
||||
import BatchImportUser from './user/BatchImportUser.vue';
|
||||
import UserSort from './user/UserSort.vue';
|
||||
import { actionColumn } from "@/utils/tableActions";
|
||||
|
||||
defineOptions({ name: "GroupUser" });
|
||||
|
||||
const { $utils, $conf } = useCurrentInstance();
|
||||
const { $conf } = useCurrentInstance();
|
||||
|
||||
const treeApi = () => Apis.SysGroup.getTree();
|
||||
|
||||
@@ -146,8 +147,6 @@ const tableApi = (params: { pageIndex: number; pageSize: number }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const tableHeight = ref($utils.useTableHeight({ isOperat: false, isPage: true, isSearch: true }))
|
||||
|
||||
/* ================= 表格列 ================= */
|
||||
const columns = [
|
||||
{ type: 'selection', width: 55, align: 'center' },
|
||||
@@ -164,49 +163,29 @@ const columns = [
|
||||
{
|
||||
title: '状态', key: 'disabled', width: 130, align: 'center',
|
||||
render: (h: any, { row }: any) => {
|
||||
if (row.disabled) return h('Tag', { color: 'default' }, '禁用');
|
||||
// Vue3 渲染函数须 import 真组件:h('Tag') 会建未知原生元素(无样式不渲染)
|
||||
if (row.disabled) return h(Tag, { color: 'default' }, () => '禁用');
|
||||
if (row.groupStatus === 1) {
|
||||
// 正常 Tag + 转出徽标(view-ui-plus Badge status 模式:圆点 + 文本)
|
||||
return h('div', { style: 'display:inline-flex;align-items:center;gap:6px' }, [
|
||||
h('Tag', { color: 'success' }, '正常'),
|
||||
h('Badge', { props: { status: 'warning', text: '转出' } }),
|
||||
h(Tag, { color: 'success' }, () => '正常'),
|
||||
h(Badge, { status: 'warning', text: '转出' }),
|
||||
]);
|
||||
}
|
||||
return h('Tag', { color: 'success' }, '正常');
|
||||
return h(Tag, { color: 'success' }, () => '正常');
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '排序号', key: 'sortNo', width: 90, align: 'center',
|
||||
render: (h: any, { row }: any) => h('span', row.sortNo == null ? '-' : row.sortNo),
|
||||
},
|
||||
{
|
||||
title: '操作', key: 'action', width: 220, align: 'center', fixed: 'right' as const,
|
||||
render: (h: any, { row }: any) => {
|
||||
const opList: any[] = [
|
||||
h('a', {
|
||||
style: { marginRight: '8px', color: '#577afb' },
|
||||
onClick: () => handleEdit(row),
|
||||
}, '编辑'),
|
||||
];
|
||||
// 仅「单组浏览」(withSub=0)时开放排序:此时每个用户都直接属于当前组,排序号语义明确;
|
||||
// 含子组搜索(withSub=1)下用户可能属于子组,排序号归属不清,故隐藏。
|
||||
if (dataParams.value.withSub === 0) {
|
||||
opList.push(
|
||||
h('a', {
|
||||
style: { marginRight: '8px', color: '#2d8cf0' },
|
||||
onClick: () => openSort(row),
|
||||
}, '排序'),
|
||||
);
|
||||
}
|
||||
opList.push(
|
||||
h('a', {
|
||||
style: { color: '#ed4014' },
|
||||
onClick: () => handleDelete(row),
|
||||
}, '删除'),
|
||||
);
|
||||
return h('div', opList);
|
||||
},
|
||||
},
|
||||
// 文字链形态:颜色走通道 .operating-link = var(--brand),随主题档位变化;
|
||||
// 排序仅「单组浏览」开放——withSub 是与行数据无关的全局模式开关,用 when 整组隐藏合规
|
||||
actionColumn([
|
||||
{ key: 'edit', label: '编辑', on: (r: any) => handleEdit(r) },
|
||||
{ key: 'sort', label: '排序', on: (r: any) => openSort(r), when: () => dataParams.value.withSub === 0 },
|
||||
{ key: 'del', label: '删除', on: (r: any) => handleDelete(r), danger: true },
|
||||
], { mode: 'text', width: 180, fixed: 'right' }),
|
||||
];
|
||||
|
||||
/* ================= 导出组用户 ================= */
|
||||
@@ -368,10 +347,6 @@ function onSortSaved() {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.group-user-page .page-panel {
|
||||
left: 348px !important;
|
||||
}
|
||||
|
||||
/* 左侧树容器改为 flex 纵向布局并收起自身滚动,把滚动条收敛到树节点区域
|
||||
(滚动体是 MyTree 内部的 .ztree,见 components/MyTree.vue),
|
||||
避免树过长时把顶部 opera-ul 操作按钮条滚出屏幕;仅作用于本页,不影响全局 .tree-container */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="main-wrapper">
|
||||
<div class="page-wrapper yh-fill">
|
||||
<div class="main-container">
|
||||
<div class="search-wrap fix">
|
||||
<Input v-model="dataParams.keyword" placeholder="输入接口名/应用名/appKey搜索" clearable suffix="ios-search"
|
||||
@@ -26,7 +26,7 @@
|
||||
<Button type="primary" class="ml-12" @click="handleSearch">查询</Button>
|
||||
</div>
|
||||
|
||||
<TablePageAlovaNew ref="tableRef" :dataApi="tableApi" :columns="columns" :tableHeight="tableHeight" />
|
||||
<TablePageAlovaNew ref="tableRef" :dataApi="tableApi" :columns="columns" />
|
||||
</div>
|
||||
|
||||
<OapiLogDetailModal v-model="detailVisible" :row="currentRow" />
|
||||
@@ -35,15 +35,13 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import useCurrentInstance from '@/utils/hook/useCurrentInstance';
|
||||
import TablePageAlovaNew from '@/components/TablePageAlovaNew.vue';
|
||||
import OapiLogDetailModal from './OapiLogDetailModal.vue';
|
||||
import Utils from '@/utils';
|
||||
import { actionColumn } from '@/utils/tableActions';
|
||||
|
||||
defineOptions({ name: 'OapiLogList' });
|
||||
|
||||
const { $utils } = useCurrentInstance();
|
||||
|
||||
const tableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
|
||||
|
||||
interface DataParams {
|
||||
@@ -98,8 +96,6 @@ const tableApi = (params: { pageIndex: number; pageSize: number }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const tableHeight = ref($utils.useTableHeight({ isOperat: false, isPage: true, isSearch: true }));
|
||||
|
||||
const logLevelMap: Record<number, { text: string; className: string }> = {
|
||||
1: { text: '重要', className: 'text-error' },
|
||||
2: { text: '次要', className: 'text-warning' },
|
||||
@@ -164,18 +160,10 @@ const columns = [
|
||||
width: 180,
|
||||
render: (h: any, { row }: any) => h('span', row.createTime ? Utils.dateFormat(row.createTime, 'yyyy-MM-dd HH:mm:ss') : '-'),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
fixed: 'right' as const,
|
||||
render: (h: any, { row }: any) =>
|
||||
h('a', {
|
||||
style: { color: '#577afb' },
|
||||
onClick: () => openDetail(row),
|
||||
}, '查看详情'),
|
||||
},
|
||||
// 文字链形态:颜色走通道 .operating-link = var(--brand),随主题档位变化
|
||||
actionColumn([
|
||||
{ key: 'detail', label: '查看详情', on: (r: any) => openDetail(r) },
|
||||
], { mode: 'text', width: 100, fixed: 'right' }),
|
||||
];
|
||||
|
||||
const detailVisible = ref(false);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="main-wrapper">
|
||||
<div class="page-wrapper yh-fill">
|
||||
<div class="main-container">
|
||||
<div class="search-wrap fix">
|
||||
<Input v-model="dataParams.keyword" placeholder="接口名称 / 用户名" clearable suffix="ios-search"
|
||||
@@ -27,7 +27,7 @@
|
||||
<Button type="primary" class="ml-12" @click="handleSearch">查询</Button>
|
||||
</div>
|
||||
|
||||
<TablePageAlovaNew ref="tableRef" :dataApi="tableApi" :columns="columns" :tableHeight="tableHeight" />
|
||||
<TablePageAlovaNew ref="tableRef" :dataApi="tableApi" :columns="columns" />
|
||||
</div>
|
||||
|
||||
<OperationLogDetailModal v-model="detailVisible" :row="currentRow" />
|
||||
@@ -36,15 +36,13 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import useCurrentInstance from '@/utils/hook/useCurrentInstance';
|
||||
import TablePageAlovaNew from '@/components/TablePageAlovaNew.vue';
|
||||
import OperationLogDetailModal from './OperationLogDetailModal.vue';
|
||||
import Utils from '@/utils';
|
||||
import { actionColumn } from '@/utils/tableActions';
|
||||
|
||||
defineOptions({ name: 'OperationLogList' });
|
||||
|
||||
const { $utils } = useCurrentInstance();
|
||||
|
||||
const tableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
|
||||
|
||||
interface DataParams {
|
||||
@@ -99,8 +97,6 @@ const tableApi = (params: { pageIndex: number; pageSize: number }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const tableHeight = ref($utils.useTableHeight({ isOperat: false, isPage: true, isSearch: true }));
|
||||
|
||||
const logLevelMap: Record<number, { text: string; className: string }> = {
|
||||
1: { text: '重要', className: 'text-error' },
|
||||
2: { text: '次要', className: 'text-warning' },
|
||||
@@ -160,23 +156,15 @@ const columns = [
|
||||
render: (h: any, { row }: any) => h('span', row.responseSize != null && row.responseSize > 0 ? Utils.getFileSize(row.responseSize) : '-'),
|
||||
},
|
||||
{
|
||||
title: '操作时间ed',
|
||||
title: '操作时间',
|
||||
key: 'createTime',
|
||||
width: 180,
|
||||
render: (h: any, { row }: any) => h('span', row.createTime ? Utils.dateFormat(row.createTime, 'yyyy-MM-dd HH:mm:ss') : '-'),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
fixed: 'right' as const,
|
||||
render: (h: any, { row }: any) =>
|
||||
h('a', {
|
||||
style: { color: '#577afb' },
|
||||
onClick: () => openDetail(row),
|
||||
}, '查看详情'),
|
||||
},
|
||||
// 文字链形态:颜色走通道 .operating-link = var(--brand),随主题档位变化
|
||||
actionColumn([
|
||||
{ key: 'detail', label: '查看详情', on: (r: any) => openDetail(r) },
|
||||
], { mode: 'text', width: 100, fixed: 'right' }),
|
||||
];
|
||||
|
||||
const detailVisible = ref(false);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="main-wrapper">
|
||||
<div class="page-wrapper yh-fill">
|
||||
<div class="main-container">
|
||||
<div class="search-wrap fix">
|
||||
<Select v-model="dataParams.appKey" class="ml-12" clearable filterable placeholder="应用"
|
||||
@@ -27,7 +27,7 @@
|
||||
<Button type="primary" class="ml-12" @click="handleSearch">查询</Button>
|
||||
</div>
|
||||
|
||||
<TablePageAlovaNew ref="tableRef" :dataApi="tableApi" :columns="columns" :tableHeight="tableHeight" />
|
||||
<TablePageAlovaNew ref="tableRef" :dataApi="tableApi" :columns="columns" />
|
||||
</div>
|
||||
|
||||
<PushLogDetailModal v-model="detailVisible" :row="currentRow" />
|
||||
@@ -37,15 +37,13 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { Message } from 'view-ui-plus';
|
||||
import useCurrentInstance from '@/utils/hook/useCurrentInstance';
|
||||
import TablePageAlovaNew from '@/components/TablePageAlovaNew.vue';
|
||||
import Utils from '@/utils';
|
||||
import PushLogDetailModal from './PushLogDetailModal.vue';
|
||||
import { actionColumn } from '@/utils/tableActions';
|
||||
|
||||
defineOptions({ name: 'PushLog' });
|
||||
|
||||
const { $utils } = useCurrentInstance();
|
||||
|
||||
const tableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
|
||||
|
||||
const interfaceOptions = [
|
||||
@@ -113,8 +111,6 @@ const tableApi = (params: { pageIndex: number; pageSize: number }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const tableHeight = ref($utils.useTableHeight({ isOperat: false, isPage: true, isSearch: true }));
|
||||
|
||||
const columns = [
|
||||
{ type: 'index', title: '序号', width: 70, align: 'center' },
|
||||
{ title: '应用', key: 'appName', ellipsis: true, tooltip: true },
|
||||
@@ -163,23 +159,11 @@ const columns = [
|
||||
align: 'right',
|
||||
render: (h: any, { row }: any) => h('span', row.payloadSize != null && row.payloadSize > 0 ? Utils.getFileSize(row.payloadSize) : '-'),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 150,
|
||||
align: 'left',
|
||||
render: (h: any, { row }: any) => {
|
||||
const actions = [
|
||||
h('a', { style: { color: '#577afb', marginRight: '12px' }, onClick: () => openDetail(row) }, '查看详情'),
|
||||
];
|
||||
if (row.success !== 1) {
|
||||
actions.push(
|
||||
h('a', { style: { color: '#ed4014' }, onClick: () => handleRetry(row) }, '重试')
|
||||
);
|
||||
}
|
||||
return h('span', actions);
|
||||
},
|
||||
},
|
||||
// 重试按行数据(成功与否)变化 → disabledWhen 置灰保位(§5.2 按钮位置稳定性红线),禁止 when 隐藏
|
||||
actionColumn([
|
||||
{ key: 'detail', label: '查看详情', on: (r: any) => openDetail(r) },
|
||||
{ key: 'retry', label: '重试', on: (r: any) => handleRetry(r), danger: true, disabledWhen: (r: any) => r.success === 1 },
|
||||
], { mode: 'text', width: 150 }),
|
||||
];
|
||||
|
||||
const detailVisible = ref(false);
|
||||
@@ -225,12 +209,4 @@ loadApps();
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.text-success {
|
||||
color: #19be6b;
|
||||
}
|
||||
|
||||
.text-error {
|
||||
color: #ed4014;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="page-wrapper role-authz-wrapper">
|
||||
<div class="page-wrapper tree-wrapper role-authz-wrapper">
|
||||
<!-- 左:角色列表(可滚动,应对大量角色;顶部 2 个筛选) -->
|
||||
<div class="role-list-panel">
|
||||
<div class="tree-container role-list-panel">
|
||||
<div class="role-filter">
|
||||
<Select v-model="filterAppId" placeholder="按绑定应用筛选" filterable clearable
|
||||
@on-change="onFilterChange">
|
||||
@@ -31,7 +31,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 右:选中角色的授权面板(应用授权 / 用户管理) -->
|
||||
<div class="authz-panel">
|
||||
<div class="main-container authz-panel">
|
||||
<div v-if="selectedRole" class="authz-inner">
|
||||
<div class="authz-bar">
|
||||
<span class="authz-title">当前角色:<b>{{ selectedRole.name }}</b></span>
|
||||
@@ -152,19 +152,12 @@ function goManage() {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.role-authz-wrapper {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
/* 通道 tree-wrapper 骨架:外边界 16 / 两卡间隙 16 / 等高 stretch,高度对齐列表页 */
|
||||
.role-list-panel {
|
||||
width: 300px;
|
||||
flex-shrink: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #ffffff;
|
||||
border-right: 1px solid #ebedf0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -196,12 +189,12 @@ function goManage() {
|
||||
transition: all 0.15s;
|
||||
|
||||
&:hover {
|
||||
border-color: #b8c6ff;
|
||||
border-color: var(--brand-2);
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: #577afb;
|
||||
background: #f3f6ff;
|
||||
border-color: var(--brand);
|
||||
background: var(--brand-soft);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,18 +228,21 @@ function goManage() {
|
||||
border-top: 1px solid #eeeeee;
|
||||
|
||||
.ivu-btn-text {
|
||||
color: #577afb;
|
||||
color: var(--brand);
|
||||
}
|
||||
}
|
||||
|
||||
.authz-panel {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: auto;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.authz-inner {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
@@ -255,10 +251,11 @@ function goManage() {
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 0 4px;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.authz-title b {
|
||||
color: #577afb;
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
.authz-app {
|
||||
@@ -268,9 +265,13 @@ function goManage() {
|
||||
|
||||
.authz-tabs {
|
||||
margin-top: 6px;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.authz-tab-content {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding-top: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -158,10 +158,10 @@ function goAuthz() {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
color: #577afb;
|
||||
color: var(--brand);
|
||||
|
||||
&:hover {
|
||||
color: #6c91f0;
|
||||
color: var(--brand-2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,11 +16,9 @@
|
||||
import { ref, computed, watch } from "vue";
|
||||
import { Message } from "view-ui-plus";
|
||||
import Apis from "@/api";
|
||||
import useCurrentInstance from "@/utils/hook/useCurrentInstance";
|
||||
import MyConfirm from "@/components/MyConfirm";
|
||||
import RoleUserBindModal from "./RoleUserBindModal.vue";
|
||||
|
||||
const { $utils } = useCurrentInstance();
|
||||
import { actionColumn } from "@/utils/tableActions";
|
||||
|
||||
const props = defineProps<{
|
||||
roleId: number;
|
||||
@@ -44,10 +42,10 @@ const columns = [
|
||||
{ title: "账号", key: "account", width: 110 },
|
||||
{ title: "姓名", key: "realName", width: 90 },
|
||||
{ title: "部门", key: "groupPath", minWidth: 200 },
|
||||
{
|
||||
title: "操作", key: "action", width: 80, align: "center",
|
||||
render: (h: any, { row }: any) => $utils.operatingTextHandle(h, "删除", "text-error", () => removeUser(row)),
|
||||
},
|
||||
// UserBase 恒文字链(工程风格显式声明):单删除动作保持红色文字链,列宽 80 不变
|
||||
actionColumn([
|
||||
{ key: "del", label: "删除", icon: "icon-ashbin", on: (r: any) => removeUser(r), danger: true },
|
||||
], { mode: "text", width: 80 }),
|
||||
];
|
||||
|
||||
async function loadUsers() {
|
||||
|
||||
Reference in New Issue
Block a user