style(frontend): 表格操作列统一样式与确认框视觉收敛

- 新增 table-actions.css:操作列 flex 居中、间距交给 gap、破坏性动作前
  加分组竖线、链接按钮悬停加深并加下划线(默认浅靛蓝在表格底色上对比度
  不足);包装 span 改 flex,消除行内盒基线导致的约 1.7px 垂直错位
- 11 个列表页操作列套用统一容器与分隔线,列宽按动作自然宽度调整并居中
- 确认框不再传 type:warning(Element Plus 的实心黄圆图标与正文并排成
  两列,标题行反被留空),图标改由全局样式画在标题行左侧,正文与标题
  文字左对齐;图标为 CSS 伪元素,不进无障碍树

验证:pnpm type-check 绿

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
liuwenxin 2026-10-19 16:33:06 +08:00
parent cc4c51359c
commit cfd2a196f2
15 changed files with 299 additions and 164 deletions

View File

@ -81,3 +81,38 @@ body {
.toolbar { .toolbar {
margin-bottom: 12px; margin-bottom: 12px;
} }
/*
* 确认框(ElMessageBox)统一视觉
*
* Element Plus 默认把 warning 图标渲染在正文左侧、与正文并排成两列:
* 多行正文时图标悬在文字块中间,左侧留出一列空白,标题行反而是空的;
* 实心黄圆也与本站「中性灰白 + 靛蓝点缀」体系冲突。
* 这里去掉调用处的 type,把图标改画在标题行左侧:18px 描边样式,
* 深琥珀 #b7791f + 浅琥珀底 #fbf3e4(警示语义,饱和度压到与靛蓝同级);
* 正文与标题文字左对齐,图标单独悬挂在左侧。图标为 CSS 伪元素,天然不进无障碍树,
* 属于装饰元素,语义由标题文字承载。
* MessageBox 挂载在 body 下,scoped 样式够不到,因此放在全局文件。
*/
.ys-confirm-box .el-message-box__title {
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
color: var(--ys-ink);
}
.ys-confirm-box .el-message-box__title::before {
content: '';
flex: none;
width: 18px;
height: 18px;
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 18 18' fill='none'%3E%3Ccircle cx='9' cy='9' r='8.25' fill='%23FBF3E4'/%3E%3Ccircle cx='9' cy='9' r='7.4' stroke='%23B7791F' stroke-width='1.3'/%3E%3Cpath d='M9 5.3V9.8' stroke='%23B7791F' stroke-width='1.5' stroke-linecap='round'/%3E%3Ccircle cx='9' cy='12.4' r='0.95' fill='%23B7791F'/%3E%3C/svg%3E")
center / 18px 18px no-repeat;
}
.ys-confirm-box .el-message-box__message {
/* 与标题文字对齐,让图标单独悬挂在左侧:18px 图标 + 8px gap */
padding-left: 26px;
color: var(--ys-ink-2);
}

View File

@ -0,0 +1,57 @@
/*
* 表格操作列统一样式。
*
* 用法:用 <div class="table-actions"> 包住该行的操作按钮;破坏性动作
* (删除/禁用等)前插入 <span class="table-actions__divider"></span> 分组。
* 列宽须容纳整行动作的自然宽度,否则 Element Plus 会把末尾按钮折到第二行。
*/
.table-actions {
display: flex;
align-items: center;
justify-content: center;
gap: 14px;
white-space: nowrap;
}
/* 间距统一交给 gap,抵消相邻按钮默认的 12px 外边距 */
.table-actions .el-button + .el-button {
margin-left: 0;
}
/*
* 带 title 提示或 tooltip 的按钮常包在 span 里。行内盒的基线留白会让包装
* 按钮比直接子按钮低约 1.7px,改为 flex 后与整行严格垂直对齐。
*/
.table-actions > span:not(.table-actions__divider) {
display: flex;
align-items: center;
}
/* 悬停加深并加下划线:Element 默认悬停变浅,浅靛蓝在表格底色上对比度不足 */
.table-actions .el-button.is-link:hover {
text-decoration: underline;
text-underline-offset: 3px;
}
.table-actions .el-button.is-link.el-button--primary:hover {
color: var(--el-color-primary-dark-2);
}
.table-actions .el-button.is-link.el-button--success:hover {
color: var(--el-color-success-dark-2);
}
.table-actions .el-button.is-link.el-button--warning:hover {
color: var(--el-color-warning-dark-2);
}
.table-actions .el-button.is-link.el-button--danger:hover {
color: var(--el-color-danger-dark-2);
}
.table-actions__divider {
flex: none;
width: 1px;
height: 12px;
background-color: var(--ys-line);
}

View File

@ -29,7 +29,9 @@ export function useConfirmAction() {
}): Promise<boolean> { }): Promise<boolean> {
try { try {
await ElMessageBox.confirm(options.confirmText, options.title ?? '操作确认', { await ElMessageBox.confirm(options.confirmText, options.title ?? '操作确认', {
type: 'warning', // 不传 type:Element Plus 的 warning 图标会渲染在正文左侧独立成列,
// 标题行反被留空。图标改由全局样式画在标题行内(.ys-confirm-box)。
customClass: 'ys-confirm-box',
confirmButtonText: options.confirmButtonText ?? '确定', confirmButtonText: options.confirmButtonText ?? '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
}) })

View File

@ -8,5 +8,6 @@ import pinia from './store'
import 'element-plus/dist/index.css' import 'element-plus/dist/index.css'
import './assets/styles/index.css' import './assets/styles/index.css'
import './assets/styles/table-actions.css'
createApp(App).use(pinia).use(router).use(ElementPlus, { locale: zhCn }).mount('#app') createApp(App).use(pinia).use(router).use(ElementPlus, { locale: zhCn }).mount('#app')

View File

@ -398,12 +398,17 @@ onMounted(() => {
<el-table-column label="归属人员" min-width="110"> <el-table-column label="归属人员" min-width="110">
<template #default="{ row }">{{ row.owner_personnel_name ?? '(无归属)' }}</template> <template #default="{ row }">{{ row.owner_personnel_name ?? '(无归属)' }}</template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="230" fixed="right"> <el-table-column label="操作" width="260" fixed="right" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-button link type="primary" size="small" @click="openEdit(row)">编辑</el-button> <div class="table-actions">
<el-button link type="primary" size="small" @click="openOwnership(row)">归属变更</el-button> <el-button link type="primary" size="small" @click="openEdit(row)">编辑</el-button>
<el-button link type="primary" size="small" @click="openLogs(row)">变更记录</el-button> <el-button link type="primary" size="small" @click="openOwnership(row)">
<el-button link type="danger" size="small" @click="handleDelete(row)">删除</el-button> 归属变更
</el-button>
<el-button link type="primary" size="small" @click="openLogs(row)">变更记录</el-button>
<span class="table-actions__divider" aria-hidden="true"></span>
<el-button link type="danger" size="small" @click="handleDelete(row)">删除</el-button>
</div>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>

View File

@ -452,14 +452,19 @@ onMounted(() => {
<StatusTag :value="row.status" active-text="在职" inactive-text="离职" /> <StatusTag :value="row.status" active-text="在职" inactive-text="离职" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="230" fixed="right"> <el-table-column label="操作" width="270" fixed="right" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-button link type="primary" size="small" @click="openEdit(row)">编辑</el-button> <div class="table-actions">
<el-button link type="primary" size="small" @click="openSubTables(row)">区域与计划</el-button> <el-button link type="primary" size="small" @click="openEdit(row)">编辑</el-button>
<el-button link type="warning" size="small" @click="handleToggleStatus(row)"> <el-button link type="primary" size="small" @click="openSubTables(row)">
{{ row.status === 1 ? '设为离职' : '设为在职' }} 区域与计划
</el-button> </el-button>
<el-button link type="danger" size="small" @click="handleDelete(row)">删除</el-button> <el-button link type="warning" size="small" @click="handleToggleStatus(row)">
{{ row.status === 1 ? '设为离职' : '设为在职' }}
</el-button>
<span class="table-actions__divider" aria-hidden="true"></span>
<el-button link type="danger" size="small" @click="handleDelete(row)">删除</el-button>
</div>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>

View File

@ -269,13 +269,16 @@ onMounted(() => {
<StatusTag :value="row.status" /> <StatusTag :value="row.status" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="170" fixed="right"> <el-table-column label="操作" width="170" fixed="right" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-button link type="primary" size="small" @click="openEdit(row)">编辑</el-button> <div class="table-actions">
<el-button link type="warning" size="small" @click="handleToggleStatus(row)"> <el-button link type="primary" size="small" @click="openEdit(row)">编辑</el-button>
{{ row.status === 1 ? '停用' : '启用' }} <el-button link type="warning" size="small" @click="handleToggleStatus(row)">
</el-button> {{ row.status === 1 ? '停用' : '启用' }}
<el-button link type="danger" size="small" @click="handleDelete(row)">删除</el-button> </el-button>
<span class="table-actions__divider" aria-hidden="true"></span>
<el-button link type="danger" size="small" @click="handleDelete(row)">删除</el-button>
</div>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>

View File

@ -690,37 +690,46 @@ onMounted(() => {
<span class="cell-amount">{{ row.total_received_amount ?? '—' }}</span> <span class="cell-amount">{{ row.total_received_amount ?? '—' }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="300" fixed="right"> <el-table-column label="操作" width="300" fixed="right" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-button link type="primary" size="small" @click="openDrawer(row)">详情</el-button> <div class="table-actions">
<el-tooltip content="已完成或已终止的业务禁止编辑" :disabled="row.status === 1" placement="top"> <el-button link type="primary" size="small" @click="openDrawer(row)">详情</el-button>
<span class="op-wrap"> <el-tooltip
<el-button content="已完成或已终止的业务禁止编辑"
link :disabled="row.status === 1"
type="primary" placement="top"
size="small" >
:disabled="row.status !== 1" <span class="op-wrap">
@click="openEdit(row)" <el-button
> link
编辑 type="primary"
</el-button> size="small"
</span> :disabled="row.status !== 1"
</el-tooltip> @click="openEdit(row)"
<el-tooltip content="终态不可再变更" :disabled="row.status === 1" placement="top"> >
<span class="op-wrap"> 编辑
<el-button </el-button>
link </span>
type="primary" </el-tooltip>
size="small" <el-tooltip content="终态不可再变更" :disabled="row.status === 1" placement="top">
:disabled="row.status !== 1" <span class="op-wrap">
@click="openStatus(row)" <el-button
> link
状态 type="primary"
</el-button> size="small"
</span> :disabled="row.status !== 1"
</el-tooltip> @click="openStatus(row)"
<el-button link type="primary" size="small" @click="openOwnership(row)">归属</el-button> >
<el-button link type="danger" size="small" @click="handleDelete(row)">删除</el-button> 状态
</el-button>
</span>
</el-tooltip>
<el-button link type="primary" size="small" @click="openOwnership(row)">
归属
</el-button>
<span class="table-actions__divider" aria-hidden="true"></span>
<el-button link type="danger" size="small" @click="handleDelete(row)">删除</el-button>
</div>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>

View File

@ -282,8 +282,11 @@ onMounted(() => {
<el-table-column prop="sort_order" label="排序" width="70" align="center" /> <el-table-column prop="sort_order" label="排序" width="70" align="center" />
<el-table-column label="操作" width="110" align="center"> <el-table-column label="操作" width="110" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-button link type="primary" size="small" @click="openEdit(row)">编辑</el-button> <div class="table-actions">
<el-button link type="danger" size="small" @click="removeGrade(row)">删除</el-button> <el-button link type="primary" size="small" @click="openEdit(row)">编辑</el-button>
<span class="table-actions__divider" aria-hidden="true"></span>
<el-button link type="danger" size="small" @click="removeGrade(row)">删除</el-button>
</div>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>

View File

@ -346,42 +346,45 @@ onMounted(() => {
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="130" fixed="right"> <el-table-column label="操作" width="130" fixed="right" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-tooltip <div class="table-actions">
content="已核算回款不可变更" <el-tooltip
:disabled="row.settlement_status !== 1" content="已核算回款不可变更"
placement="top" :disabled="row.settlement_status !== 1"
> placement="top"
<span class="op-wrap"> >
<el-button <span class="op-wrap">
link <el-button
type="primary" link
size="small" type="primary"
:disabled="row.settlement_status === 1" size="small"
@click="openEdit(row)" :disabled="row.settlement_status === 1"
> @click="openEdit(row)"
编辑 >
</el-button> 编辑
</span> </el-button>
</el-tooltip> </span>
<el-tooltip </el-tooltip>
content="已核算回款不可变更" <span class="table-actions__divider" aria-hidden="true"></span>
:disabled="row.settlement_status !== 1" <el-tooltip
placement="top" content="已核算回款不可变更"
> :disabled="row.settlement_status !== 1"
<span class="op-wrap"> placement="top"
<el-button >
link <span class="op-wrap">
type="danger" <el-button
size="small" link
:disabled="row.settlement_status === 1" type="danger"
@click="handleDelete(row)" size="small"
> :disabled="row.settlement_status === 1"
删除 @click="handleDelete(row)"
</el-button> >
</span> 删除
</el-tooltip> </el-button>
</span>
</el-tooltip>
</div>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>

View File

@ -337,10 +337,13 @@ onMounted(() => {
<span class="cell-amount">{{ row.total_amount }}</span> <span class="cell-amount">{{ row.total_amount }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="130" fixed="right"> <el-table-column label="操作" width="130" fixed="right" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-button link type="primary" size="small" @click="openEdit(row)">编辑</el-button> <div class="table-actions">
<el-button link type="danger" size="small" @click="handleDelete(row)">删除</el-button> <el-button link type="primary" size="small" @click="openEdit(row)">编辑</el-button>
<span class="table-actions__divider" aria-hidden="true"></span>
<el-button link type="danger" size="small" @click="handleDelete(row)">删除</el-button>
</div>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>

View File

@ -258,31 +258,34 @@ onMounted(() => {
<StatusTag :value="row.status" /> <StatusTag :value="row.status" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="190" fixed="right"> <el-table-column label="操作" width="190" fixed="right" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-button link type="primary" size="small" @click="openEdit(row)">编辑</el-button> <div class="table-actions">
<span :title="builtinHint(row)"> <el-button link type="primary" size="small" @click="openEdit(row)">编辑</el-button>
<el-button <span :title="builtinHint(row)">
link <el-button
type="warning" link
size="small" type="warning"
:disabled="isBuiltin(row)" size="small"
@click="handleToggleStatus(row)" :disabled="isBuiltin(row)"
> @click="handleToggleStatus(row)"
{{ row.status === 1 ? '禁用' : '启用' }} >
</el-button> {{ row.status === 1 ? '禁用' : '启用' }}
</span> </el-button>
<span :title="builtinHint(row)"> </span>
<el-button <span class="table-actions__divider" aria-hidden="true"></span>
link <span :title="builtinHint(row)">
type="danger" <el-button
size="small" link
:disabled="isBuiltin(row)" type="danger"
@click="handleDelete(row)" size="small"
> :disabled="isBuiltin(row)"
删除 @click="handleDelete(row)"
</el-button> >
</span> 删除
</el-button>
</span>
</div>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>

View File

@ -193,7 +193,7 @@ async function submitAll(): Promise<void> {
await ElMessageBox.confirm( await ElMessageBox.confirm(
`共 ${pendingList.value.length} 项变更,${effectiveText.value}。提交后逐项留痕,未生效前可整批撤回。`, `共 ${pendingList.value.length} 项变更,${effectiveText.value}。提交后逐项留痕,未生效前可整批撤回。`,
'提交确认', '提交确认',
{ confirmButtonText: '提交', cancelButtonText: '再想想', type: 'warning' }, { confirmButtonText: '提交', cancelButtonText: '再想想', customClass: 'ys-confirm-box' },
) )
} catch { } catch {
return return

View File

@ -224,31 +224,34 @@ onMounted(load)
<el-table-column label="用户数" width="90" align="center"> <el-table-column label="用户数" width="90" align="center">
<template #default="{ row }">{{ row.user_count }}</template> <template #default="{ row }">{{ row.user_count }}</template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="170" fixed="right"> <el-table-column label="操作" width="170" fixed="right" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-button link type="primary" size="small" @click="openEdit(row)">编辑</el-button> <div class="table-actions">
<span :title="isBuiltin(row) ? '内置管理员角色不可禁用' : ''"> <el-button link type="primary" size="small" @click="openEdit(row)">编辑</el-button>
<el-button <span :title="isBuiltin(row) ? '内置管理员角色不可禁用' : ''">
link <el-button
type="warning" link
size="small" type="warning"
:disabled="isBuiltin(row)" size="small"
@click="handleToggleStatus(row)" :disabled="isBuiltin(row)"
> @click="handleToggleStatus(row)"
{{ row.status === 1 ? '禁用' : '启用' }} >
</el-button> {{ row.status === 1 ? '禁用' : '启用' }}
</span> </el-button>
<span :title="deleteHint(row)"> </span>
<el-button <span class="table-actions__divider" aria-hidden="true"></span>
link <span :title="deleteHint(row)">
type="danger" <el-button
size="small" link
:disabled="isBuiltin(row) || row.user_count > 0" type="danger"
@click="handleDelete(row)" size="small"
> :disabled="isBuiltin(row) || row.user_count > 0"
删除 @click="handleDelete(row)"
</el-button> >
</span> 删除
</el-button>
</span>
</div>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>

View File

@ -331,34 +331,37 @@ onMounted(() => {
<el-table-column label="最后登录" min-width="170"> <el-table-column label="最后登录" min-width="170">
<template #default="{ row }">{{ formatDateTime(row.last_login_time) }}</template> <template #default="{ row }">{{ formatDateTime(row.last_login_time) }}</template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="250" fixed="right"> <el-table-column label="操作" width="250" fixed="right" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-button link type="primary" size="small" @click="openEdit(row)">编辑</el-button> <div class="table-actions">
<el-button link type="primary" size="small" @click="openReset(row)"> <el-button link type="primary" size="small" @click="openEdit(row)">编辑</el-button>
重置密码 <el-button link type="primary" size="small" @click="openReset(row)">
</el-button> 重置密码
<span :title="selfHint(row)">
<el-button
link
type="warning"
size="small"
:disabled="isSelf(row)"
@click="handleToggleStatus(row)"
>
{{ row.status === 1 ? '禁用' : '启用' }}
</el-button> </el-button>
</span> <span :title="selfHint(row)">
<span :title="selfHint(row)"> <el-button
<el-button link
link type="warning"
type="danger" size="small"
size="small" :disabled="isSelf(row)"
:disabled="isSelf(row)" @click="handleToggleStatus(row)"
@click="handleDelete(row)" >
> {{ row.status === 1 ? '禁用' : '启用' }}
删除 </el-button>
</el-button> </span>
</span> <span class="table-actions__divider" aria-hidden="true"></span>
<span :title="selfHint(row)">
<el-button
link
type="danger"
size="small"
:disabled="isSelf(row)"
@click="handleDelete(row)"
>
删除
</el-button>
</span>
</div>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>