admin_new P0+P1 全批次合并 (84 commits) #2

Merged
direv merged 85 commits from develop into main 2026-08-07 11:27:17 +08:00
16 changed files with 84 additions and 60 deletions
Showing only changes of commit 2f7ec67903 - Show all commits

View File

@ -1,17 +1,19 @@
<script setup lang="ts">
/**
* F006 通用表格操作列组件 (W17.6 主次分离)
* F006 通用表格操作列组件 (W17.6 主次分离, W17.6.1 按数量自动收纳)
*
* 主操作 (primary) 常驻文字按钮, 次操作 (more) 收进「⋯」下拉菜单。
* 解决按钮多时操作列拥挤、占宽大的问题。
* 按钮数量决定显示方式 (规则符合直觉, 无需人为划分主次):
* - 过滤 when 后 ≤3 个: 全部显示为文字按钮, 不显示 ⋯
* - 过滤 when 后 ≥4 个: 前 visibleCount 个显示为文字按钮, 其余收进 ⋯ 下拉
*
* 用法:
* <TableActions
* :row="row"
* :primary="[{ label: '详情', type: 'primary', onClick: onView }]"
* :more="[
* :actions="[
* { label: '详情', type: 'primary', onClick: onView },
* { label: '审核', type: 'primary', when: r => r.status === 1, onClick: onAudit },
* { label: r => r.onShelf === 1 ? '下架' : '上架', type: r => r.onShelf === 1 ? 'warning' : 'success', onClick: onToggle },
* { label: '改分类', type: 'primary', onClick: onCategory },
* { label: '删除', type: 'danger', divided: true, confirm: '确认删除?', onClick: onDelete }
* ]"
* />
@ -23,7 +25,13 @@
* divided boolean 菜单项前加分隔线 (危险操作前用)
* confirm boolean | string true 用默认提示, string 用自定义提示
* onClick (row) => void 点击处理
*
* Props:
* row 当前行数据 (透传给 label/type/when/onClick)
* actions 操作列表 (按顺序, 前面的是主操作)
* visibleCount 常驻文字按钮数, 默认 2 (仅在按钮 >3 时生效)
*/
import { computed } from 'vue'
import { ElMessageBox, ElMessage } from 'element-plus'
import { MoreFilled } from '@element-plus/icons-vue'
@ -38,11 +46,14 @@ interface ActionItem {
onClick: (row: any) => void
}
const props = defineProps<{
const props = withDefaults(defineProps<{
row: any
primary?: ActionItem[]
more?: ActionItem[]
}>()
actions: ActionItem[]
/** 常驻文字按钮数, 仅在按钮 >3 时生效 (默认 2) */
visibleCount?: number
}>(), {
visibleCount: 2
})
// 解析 label (string 或函数)
function resolveLabel(item: ActionItem): string {
@ -55,10 +66,23 @@ function resolveType(item: ActionItem): ActionType {
return t || 'primary'
}
// more 列表过滤 when 条件后的有效项
function visibleMore(): ActionItem[] {
return (props.more || []).filter(item => !item.when || item.when(props.row))
}
// 过滤 when 条件后的有效项
const visibleActions = computed<ActionItem[]>(() =>
props.actions.filter(item => !item.when || item.when(props.row))
)
// 按钮总数 ≤3 全部常驻, >3 才拆分前 visibleCount 个常驻 + 其余收纳
const isCollapsing = computed(() => visibleActions.value.length > 3)
// 常驻文字按钮 (前 N 个, N = isCollapsing ? visibleCount : 全部)
const primaryActions = computed<ActionItem[]>(() =>
isCollapsing.value ? visibleActions.value.slice(0, props.visibleCount) : visibleActions.value
)
// 收进 ⋯ 的按钮 (第 visibleCount 个及之后, 仅 isCollapsing 时有值)
const moreActions = computed<ActionItem[]>(() =>
isCollapsing.value ? visibleActions.value.slice(props.visibleCount) : []
)
// 执行操作 (处理二次确认)
async function runAction(item: ActionItem) {
@ -80,9 +104,9 @@ async function runAction(item: ActionItem) {
<template>
<div class="rr-table-actions">
<!-- 主操作: 常驻文字按钮 -->
<!-- 常驻文字按钮 -->
<el-button
v-for="(item, i) in (primary || [])"
v-for="(item, i) in primaryActions"
:key="`p${i}`"
text
:type="resolveType(item) as any"
@ -90,11 +114,11 @@ async function runAction(item: ActionItem) {
@click="runAction(item)"
>{{ resolveLabel(item) }}</el-button>
<!-- 次操作: 收进 ⋯ 下拉 (过滤 when 后有剩余项才渲染) -->
<!-- 收纳下拉 (仅在按钮 >3 时渲染) -->
<el-dropdown
v-if="visibleMore().length"
v-if="moreActions.length"
trigger="click"
@command="(cmd: number) => runAction(visibleMore()[cmd])"
@command="(cmd: number) => runAction(moreActions[cmd])"
>
<span class="rr-more-trigger">
<el-icon><MoreFilled /></el-icon>
@ -102,7 +126,7 @@ async function runAction(item: ActionItem) {
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="(item, i) in visibleMore()"
v-for="(item, i) in moreActions"
:key="`m${i}`"
:command="i"
:divided="item.divided"

View File

@ -302,10 +302,10 @@ onMounted(() => { load(); loadCustomers() })
<template #action="{ row }">
<TableActions
:row="row"
:primary="[{ label: '查看动态', type: 'primary', onClick: activityHandle }]"
:more="[
:actions="[
{ label: '查看动态', type: 'primary', onClick: activityHandle },
{ label: '接管', type: 'primary', onClick: takeoverHandle },
{ label: '解绑', type: 'danger', divided: true, onClick: unbindHandle }
{ label: '解绑', type: 'danger', onClick: unbindHandle }
]"
/>
</template>

View File

@ -374,11 +374,11 @@ onMounted(() => { load(); loadCustomers() })
<template #action="{ row }">
<TableActions
:row="row"
:primary="[{ label: '详情', type: 'primary', onClick: detailHandle }]"
:more="[
:actions="[
{ label: '详情', type: 'primary', onClick: detailHandle },
{ label: '重发二维码', type: 'primary', when: (r: any) => r.status === 0 || r.status === 1, onClick: resendQrHandle },
{ label: '重置密钥', type: 'warning', when: (r: any) => r.status === 0 || r.status === 1, onClick: resetSecretHandle },
{ label: '撤销', type: 'danger', when: (r: any) => r.status === 0 || r.status === 1, onClick: revokeHandle },
{ label: '撤销', type: 'danger', divided: true, when: (r: any) => r.status === 0 || r.status === 1, onClick: revokeHandle },
{ label: '删除', type: 'danger', divided: true, when: (r: any) => r.status === 2, onClick: deleteHandle }
]"
/>

View File

@ -276,8 +276,8 @@ onMounted(load)
<template #action="{ row }">
<TableActions
:row="row"
:primary="[{ label: '详情', type: 'primary', onClick: onViewDetail }]"
:more="[
:actions="[
{ label: '详情', type: 'primary', onClick: onViewDetail },
{ label: '付款', type: 'primary', when: (r: any) => r.status === 1, onClick: onPay },
{ label: '发货', type: 'primary', when: (r: any) => r.status === 2, onClick: onShip },
{ label: '收货', type: 'primary', when: (r: any) => r.status === 3, onClick: onConfirmReceipt },

View File

@ -189,10 +189,10 @@ onMounted(load)
<template #default="{ row }">
<TableActions
:row="row"
:primary="[{ label: '修改', type: 'primary', onClick: onEdit }]"
:more="[
:actions="[
{ label: '修改', type: 'primary', onClick: onEdit },
{ label: '新增子分类', type: 'primary', when: (r: any) => r.level < 3, onClick: onAdd },
{ label: '删除', type: 'danger', divided: true, onClick: onDelete }
{ label: '删除', type: 'danger', onClick: onDelete }
]"
/>
</template>

View File

@ -150,10 +150,10 @@ onMounted(load)
<template #action="{ row }">
<TableActions
:row="row"
:primary="[{ label: '修改', type: 'primary', onClick: onEdit }]"
:more="[
:actions="[
{ label: '修改', type: 'primary', onClick: onEdit },
{ label: (r: any) => r.status === 0 ? '禁用' : '启用', type: (r: any) => r.status === 0 ? 'warning' : 'success', onClick: onToggleStatus },
{ label: '删除', type: 'danger', divided: true, onClick: onDelete }
{ label: '删除', type: 'danger', onClick: onDelete }
]"
/>
</template>

View File

@ -184,8 +184,8 @@ onMounted(load)
<template #default="{ row }">
<TableActions
:row="row"
:primary="[{ label: '编辑', type: 'primary', when: (r: any) => r.sourceType === 2, onClick: onEdit }]"
:more="[
:actions="[
{ label: '编辑', type: 'primary', when: (r: any) => r.sourceType === 2, onClick: onEdit },
{ label: (r: any) => r.onShelf === 1 ? '停用' : '启用', type: (r: any) => r.onShelf === 1 ? 'warning' : 'success', onClick: onToggleStatus },
{ label: '领取记录', type: 'primary', when: (r: any) => r.sourceType === 2, onClick: onShowRecords },
{ label: '删除', type: 'danger', divided: true, onClick: onDelete }

View File

@ -392,8 +392,8 @@ onMounted(() => Promise.all([load(), loadStats()]))
<template #action="{ row }">
<TableActions
:row="row"
:primary="[{ label: '详情', type: 'primary', onClick: onShowDetail }]"
:more="[
:actions="[
{ label: '详情', type: 'primary', onClick: onShowDetail },
{ label: '审核', type: 'primary', when: (r: any) => r.checkStatus === 1, onClick: onShowCheckDialog },
{ label: (r: any) => r.onShelf === 1 ? '下架' : '上架', type: (r: any) => r.onShelf === 1 ? 'warning' : 'success', onClick: onOnShelf },
{ label: '改分类', type: 'primary', onClick: onShowCategoryDialog },

View File

@ -112,10 +112,10 @@ onMounted(load)
<template #actions="{ row }">
<TableActions
:row="row"
:primary="[{ label: '详情', type: 'primary', onClick: onView }]"
:more="[
:actions="[
{ label: '详情', type: 'primary', onClick: onView },
{ label: '回复', type: 'primary', when: (r: any) => !r.parentId, onClick: onReply },
{ label: '删除', type: 'danger', divided: true, onClick: onDelete }
{ label: '删除', type: 'danger', onClick: onDelete }
]"
/>
</template>

View File

@ -106,10 +106,10 @@ onMounted(load)
<template #actions="{ row }">
<TableActions
:row="row"
:primary="[{ label: '详情', type: 'primary', onClick: onView }]"
:more="[
:actions="[
{ label: '详情', type: 'primary', onClick: onView },
{ label: '编辑', type: 'primary', onClick: onEdit },
{ label: '删除', type: 'danger', divided: true, onClick: onDelete }
{ label: '删除', type: 'danger', onClick: onDelete }
]"
/>
</template>

View File

@ -259,8 +259,8 @@ onMounted(() => Promise.all([load(), loadStats()]))
<template #action="{ row }">
<TableActions
:row="row"
:primary="[{ label: '详情', type: 'primary', onClick: onShowDetail }]"
:more="[
:actions="[
{ label: '详情', type: 'primary', onClick: onShowDetail },
{ label: '审核', type: 'primary', when: (r: any) => r.checkStatus === 1, onClick: onShowCheck },
{ label: (r: any) => r.onShelf === 1 ? '下架' : '上架', type: (r: any) => r.onShelf === 1 ? 'warning' : 'success', onClick: onOnShelf },
{ label: '改分类', type: 'primary', onClick: onShowCategory },

View File

@ -173,11 +173,11 @@ onMounted(load)
<template #action="{ row }">
<TableActions
:row="row"
:primary="[{ label: '执行', type: 'primary', onClick: runHandle }]"
:more="[
:actions="[
{ label: '执行', type: 'primary', onClick: runHandle },
{ label: '暂停', type: 'warning', when: (r: any) => r.status === 1, onClick: pauseHandle },
{ label: '恢复', type: 'primary', when: (r: any) => r.status !== 1, onClick: resumeHandle },
{ label: '删除', type: 'danger', divided: true, onClick: deleteHandle }
{ label: '删除', type: 'danger', onClick: deleteHandle }
]"
/>
</template>

View File

@ -296,10 +296,10 @@ onMounted(() => { load() })
<template #action="{ row }">
<TableActions
:row="row"
:primary="[{ label: (r: any) => r.status === 1 ? '停用' : '启用', type: (r: any) => r.status === 1 ? 'warning' : 'success', onClick: toggleStatus }]"
:more="[
:actions="[
{ label: (r: any) => r.status === 1 ? '停用' : '启用', type: (r: any) => r.status === 1 ? 'warning' : 'success', onClick: toggleStatus },
{ label: '重置密码', type: 'primary', onClick: resetPwdHandle },
{ label: '删除', type: 'danger', divided: true, onClick: deleteHandle }
{ label: '删除', type: 'danger', onClick: deleteHandle }
]"
/>
</template>

View File

@ -143,10 +143,10 @@ onMounted(load)
<template #default="{ row }">
<TableActions
:row="row"
:primary="[{ label: '修改', type: 'primary', onClick: onEdit }]"
:more="[
:actions="[
{ label: '修改', type: 'primary', onClick: onEdit },
{ label: '新增下级', type: 'primary', when: (r: any) => r.treeLevel < 4, onClick: onAdd },
{ label: '删除', type: 'danger', divided: true, onClick: onDelete }
{ label: '删除', type: 'danger', onClick: onDelete }
]"
/>
</template>

View File

@ -343,10 +343,10 @@ onMounted(load)
<template #action="{ row }">
<TableActions
:row="row"
:primary="[{ label: '修改', type: 'primary', onClick: onEdit }]"
:more="[
:actions="[
{ label: '修改', type: 'primary', onClick: onEdit },
{ label: (r: any) => r.enabled === 0 ? '禁用' : '启用', type: (r: any) => r.enabled === 0 ? 'warning' : 'success', onClick: toggleEnabled },
{ label: '删除', type: 'danger', divided: true, onClick: deleteUser }
{ label: '删除', type: 'danger', onClick: deleteUser }
]"
/>
</template>

View File

@ -92,10 +92,10 @@ onMounted(load)
<template #actions="{ row }">
<TableActions
:row="row"
:primary="[{ label: '查看', type: 'primary', onClick: onView }]"
:more="[
:actions="[
{ label: '查看', type: 'primary', onClick: onView },
{ label: '编辑', type: 'primary', onClick: onEdit },
{ label: '删除', type: 'danger', divided: true, onClick: onDelete }
{ label: '删除', type: 'danger', onClick: onDelete }
]"
/>
</template>