20260807230157

This commit is contained in:
oneao committed 2026-08-07 23:01:57 +08:00
1 parent a07b1b6fdb
commit fa510854b4
7 files changed
+135 -57

No files matched your search

@@ -876,7 +876,6 @@ defineExpose({
min-height: 0; min-height: 0;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
border: 1px solid var(--fms-border, #e5e7eb);
border-radius: 6px; border-radius: 6px;
background: var(--fms-surface, #fff); background: var(--fms-surface, #fff);
} }
@@ -244,8 +244,7 @@ watch(() => props.queryItems, initializeDrafts, { immediate: true });
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 10px; gap: 10px;
padding: 8px 12px; padding: 8px 0;
border-bottom: 1px solid var(--fms-border, #e5e7eb);
background: var(--fms-surface, #fff); background: var(--fms-surface, #fff);
} }
@@ -253,6 +253,8 @@ const customVars = {
"--header-row-height": `${headerRowHeight}px`, "--header-row-height": `${headerRowHeight}px`,
"--row-height": `${rowHeight}px`, "--row-height": `${rowHeight}px`,
"--footer-row-height": "36px", "--footer-row-height": "36px",
"--th-bgc": "#fff",
"--tf-bgc": "#fff",
}; };
// 选择列:已选状态管理 + 渲染逻辑全部内聚在 createSelectionColumn 中 // 选择列:已选状态管理 + 渲染逻辑全部内聚在 createSelectionColumn 中
@@ -498,10 +500,10 @@ defineExpose({
:data-source="dataSource" :data-source="dataSource"
:drag-row-config="{ mode: rowDraggable ? 'insert' : 'none' }" :drag-row-config="{ mode: rowDraggable ? 'insert' : 'none' }"
:row-key="rowKey" :row-key="rowKey"
:bordered="props.editable ? true : 'body-h'"
:no-data-full="noDataFull" :no-data-full="noDataFull"
:show-overflow="showOverflow" :show-overflow="showOverflow"
:area-selection="editable" :area-selection="editable"
:bordered="'h'"
@area-selection-change="handleAreaSelectionChange" @area-selection-change="handleAreaSelectionChange"
@row-order-change="handleRowOrderChange" @row-order-change="handleRowOrderChange"
> >
@@ -529,6 +531,14 @@ defineExpose({
<!-- 非 scoped 全局样式:选择列原生 checkbox 外观模拟 antdv-next Checkbox, <!-- 非 scoped 全局样式:选择列原生 checkbox 外观模拟 antdv-next Checkbox,
避免每行创建 Checkbox 组件实例导致的滚动性能退化 --> 避免每行创建 Checkbox 组件实例导致的滚动性能退化 -->
<style lang="scss"> <style lang="scss">
/* 双类选择器 .fms-table__inner.stk-table 权重 (0,2,1) > 库默认 .stk-table (0,1,1) */
.fms-table__inner.stk-table {
th,
td {
font-size: 13px;
}
}
.fms-checkbox { .fms-checkbox {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -454,8 +454,8 @@ onMounted(() => {
color: #3f3f46; color: #3f3f46;
font-weight: 500; font-weight: 500;
box-shadow: box-shadow:
0 1px 3px rgb(0 0 0 / 4%), 0 1px 2px rgb(0 0 0 / 2%),
0 2px 4px rgb(0 0 0 / 3%); 0 1px 1px rgb(0 0 0 / 1%);
} }
&.collapsed { &.collapsed {
@@ -149,6 +149,20 @@ function handleTabKeydown(event, index) {
focusAdjacentTab(index, event.key === "ArrowLeft" ? -1 : 1); focusAdjacentTab(index, event.key === "ArrowLeft" ? -1 : 1);
} }
function handleWheel(event) {
const track = trackRef.value;
if (!track || track.scrollWidth <= track.clientWidth) return;
const delta =
Math.abs(event.deltaY) >= Math.abs(event.deltaX) ? event.deltaY : event.deltaX;
if (!delta) return;
event.preventDefault();
track.scrollLeft += delta;
}
function refreshTab(key) {
appStore.refreshRoute(key);
}
</script> </script>
<template> <template>
@@ -166,12 +180,12 @@ function handleTabKeydown(event, index) {
</button> </button>
<!-- 标签轨道 --> <!-- 标签轨道 -->
<div ref="trackRef" class="tab-track" role="tablist" @scroll.passive="updateScrollState"> <div ref="trackRef" class="tab-track" role="tablist" @scroll.passive="updateScrollState" @wheel="handleWheel">
<div <div
v-for="(item, index) in tabItems" v-for="(item, index) in tabItems"
:key="item.key" :key="item.key"
class="tab-item" class="tab-item"
:class="{ 'tab-item--active': item.key === activeTab }" :class="{ 'tab-item--active': item.key === activeTab, 'tab-item--closable': item.closable }"
> >
<button <button
type="button" type="button"
@@ -182,6 +196,7 @@ function handleTabKeydown(event, index) {
:tabindex="item.key === activeTab ? 0 : -1" :tabindex="item.key === activeTab ? 0 : -1"
@click="changeTab(item.key)" @click="changeTab(item.key)"
@keydown="handleTabKeydown($event, index)" @keydown="handleTabKeydown($event, index)"
@dblclick.prevent="refreshTab(item.key)"
> >
<span>{{ getDisplayTabLabel(item) }}</span> <span>{{ getDisplayTabLabel(item) }}</span>
</button> </button>
@@ -274,8 +289,9 @@ function handleTabKeydown(event, index) {
} }
} }
/* ---- 标签项(应用 tab 药丸式) ---- */ /* ---- 标签项(浅灰高亮,与侧栏选中语言统一) ---- */
.tab-item { .tab-item {
position: relative;
display: flex; display: flex;
flex: none; flex: none;
min-width: 72px; min-width: 72px;
@@ -283,32 +299,34 @@ function handleTabKeydown(event, index) {
align-items: center; align-items: center;
overflow: hidden; overflow: hidden;
border-radius: 8px; border-radius: 8px;
background: transparent; background: #f4f4f5;
color: #71717a; color: #71717a;
transition: transition:
background-color 0.15s ease, background-color 0.15s ease,
color 0.15s ease; color 0.15s ease;
background: #f4f4f5;
&:hover { &:hover {
background: #ebebeb; background: #f4f4f5;
color: #3f3f46; color: #3f3f46;
} }
/* 选中态:实心深色药丸,应用 tab 的醒目语言 */ /* 选中态:纯白凸起药丸(inset 描边 + 轻投影,边缘利落不脏);关闭 X 作为会话标签的强区分信号 */
&--active { &--active {
color: #ffffff; color: #27272a;
font-weight: 500; font-weight: 500;
background: #18181b; background: #ffffff;
box-shadow:
inset 0 0 0 1px #e4e4e7,
0 1px 2px rgb(0 0 0 / 6%),
0 1px 1px rgb(0 0 0 / 4%);
&:hover { &:hover {
background: #27272a; background: #ffffff;
color: #ffffff; color: #27272a;
} }
.tab-item__close:hover { .tab-item__close:hover {
background: rgb(255 255 255 / 12%); background: rgb(0 0 0 / 8%);
} }
} }
@@ -330,6 +348,7 @@ function handleTabKeydown(event, index) {
padding: 0 8px; padding: 0 8px;
font-size: 13px; font-size: 13px;
overflow: hidden; overflow: hidden;
transition: padding-right 0.15s ease;
span { span {
white-space: nowrap; white-space: nowrap;
@@ -338,16 +357,30 @@ function handleTabKeydown(event, index) {
} }
} }
/* 仅可关闭标签预留右侧 X 空间;不可关闭(如工作台)保持对称居中 */
&--closable &__select {
padding-right: 24px;
}
&__close { &__close {
position: absolute;
top: 0;
right: 2px;
bottom: 0;
display: grid; display: grid;
flex: none; flex: none;
width: 22px; width: 22px;
padding: 0; padding: 0;
place-items: center; place-items: center;
border-radius: 4px; border-radius: 4px;
color: #a1a1aa;
transition:
background-color 0.15s ease,
color 0.15s ease;
&:hover { &:hover {
background: rgb(0 0 0 / 8%); background: rgb(0 0 0 / 8%);
color: #71717a;
} }
.lucide { .lucide {
@@ -89,7 +89,9 @@ function handleClick() {
background: #f4f4f5; background: #f4f4f5;
color: #3f3f46; color: #3f3f46;
font-weight: 500; font-weight: 500;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%), 0 2px 4px rgb(0 0 0 / 3%); box-shadow:
0 1px 2px rgb(0 0 0 / 2%),
0 1px 1px rgb(0 0 0 / 1%);
} }
/* ---- 可展开的父级按钮 ---- */ /* ---- 可展开的父级按钮 ---- */
@@ -1138,7 +1138,34 @@ void permissionStore
<main class="mm-main"> <main class="mm-main">
<FmsLoadingOverlay :loading="configLoading"> <FmsLoadingOverlay :loading="configLoading">
<template v-if="selectedModule"> <template v-if="selectedModule">
<!-- 顶部栏:tabs + 开关 + 操作 --> <!-- 顶部操作栏:模块级操作(保存 / 启用 / 显示菜单),独立于 tab 导航 -->
<div class="mm-action-bar">
<a-button
v-if="canManage('update')"
type="primary"
:loading="configSaving"
:disabled="configLoading || configSyncing"
@click="saveAll"
>
保存
</a-button>
<div class="mm-action-bar__switches">
<label class="mm-switch"><span>启用</span><a-switch
:checked="drafts.module?.b_canuse === 1"
:disabled="busy"
@update:checked="(v) => onSetField('b_canuse', v ? 1 : 0)"
/></label>
<template v-if="drafts.module?.b_module_type !== 'data'">
<label class="mm-switch"><span>显示菜单</span><a-switch
:checked="drafts.module?.b_canmenu === 1"
:disabled="busy || ['data', 'data_category'].includes(drafts.module?.b_module_type)"
@update:checked="(v) => onSetField('b_canmenu', v ? 1 : 0)"
/></label>
</template>
</div>
</div>
<!-- 顶部栏:tab 导航 -->
<div class="mm-tab-bar"> <div class="mm-tab-bar">
<nav class="mm-tab-nav"> <nav class="mm-tab-nav">
<button <button
@@ -1152,29 +1179,6 @@ void permissionStore
{{ tab.tab }} {{ tab.tab }}
</button> </button>
</nav> </nav>
<div class="mm-tab-bar__right">
<label class="mm-switch"><span>启用</span><a-checkbox
:checked="drafts.module?.b_canuse === 1"
:disabled="busy"
@update:checked="(v) => onSetField('b_canuse', v ? 1 : 0)"
/></label>
<template v-if="drafts.module?.b_module_type !== 'data'">
<label class="mm-switch"><span>显示菜单</span><a-checkbox
:checked="drafts.module?.b_canmenu === 1"
:disabled="busy || ['data', 'data_category'].includes(drafts.module?.b_module_type)"
@update:checked="(v) => onSetField('b_canmenu', v ? 1 : 0)"
/></label>
</template>
<a-button
v-if="canManage('update')"
type="primary"
:loading="configSaving"
:disabled="configLoading || configSyncing"
@click="saveAll"
>
保存
</a-button>
</div>
</div> </div>
<!-- 面板内容 --> <!-- 面板内容 -->
@@ -1353,20 +1357,32 @@ void permissionStore
background: var(--fms-surface); background: var(--fms-surface);
} }
/* ---- 顶部栏:tabs + 开关 + 操作 ---- */ /* ---- 顶部操作栏:模块级操作(启用 / 显示菜单 / 保存),独立于 tab 导航 ---- */
.mm-action-bar {
display: flex;
flex: none;
align-items: center;
justify-content: flex-start;
gap: 16px;
min-height: 48px;
padding: 0 20px;
&__switches {
display: flex;
align-items: center;
gap: 16px;
min-width: 0;
}
}
/* ---- 顶部栏:tab 导航 ---- */
.mm-tab-bar { .mm-tab-bar {
display: flex; display: flex;
flex: none; flex: none;
align-items: center; align-items: center;
height: 44px; height: 44px;
padding: 0 20px; padding: 0 20px;
border-bottom: 1px solid #e4e4e7;
&__right {
display: flex;
flex: none;
align-items: center;
gap: 12px;
}
} }
.mm-switch { .mm-switch {
@@ -1383,12 +1399,19 @@ void permissionStore
display: flex; display: flex;
flex: 1; flex: 1;
min-width: 0; min-width: 0;
align-self: stretch;
align-items: center; align-items: center;
gap: 2px; gap: 2px;
overflow-x: auto;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
&__item { &__item {
display: inline-flex; display: inline-flex;
height: 32px; align-self: stretch;
align-items: center; align-items: center;
padding: 0 12px; padding: 0 12px;
border: 0; border: 0;
@@ -1397,6 +1420,7 @@ void permissionStore
color: #71717a; color: #71717a;
cursor: pointer; cursor: pointer;
font-size: 13px; font-size: 13px;
position: relative;
transition: transition:
background-color 0.15s ease, background-color 0.15s ease,
color 0.15s ease; color 0.15s ease;
@@ -1407,13 +1431,24 @@ void permissionStore
} }
&.active { &.active {
background: #f4f4f5; color: #3f3f46;
color: #0a0a0a;
font-weight: 500; font-weight: 500;
background: transparent;
&::after {
content: '';
position: absolute;
left: 12px;
right: 12px;
bottom: -1px;
height: 2px;
border-radius: 2px;
background: #3f3f46;
}
&:hover { &:hover {
background: #ebebeb; background: #f4f4f5;
color: #0a0a0a; color: #3f3f46;
} }
} }
} }