refactor(F006-W17.6): 操作列主次分离 (15 个 list 页 + TableActions 组件)

- 新建 TableActions 通用组件 (src/components/TableActions/, ~120 行)
  * primary 常驻主操作 (el-button text)
  * more 次操作收进 ⋯ el-dropdown (MoreFilled 触发)
  * label/type 支持 string 或函数 (覆盖动态: 上架/下架, 禁用/启用)
  * when 条件渲染 (替代散落各处的 v-if)
  * danger 项自动 divided + 红字
  * 复用 global-popper.scss 已铺好的 el-dropdown 主题

- 15 个按钮≥3 的 list 页改造 (按钮≤2 的 27 页不动):
  5 按钮: GoodListView/ServiceGoodListView/CouponListView
  4 按钮: OrderListView/OpenAppListView
  3 按钮: user/UserListView/CompanyAccountListView/LabelListView/
          ScheduleListView/sys/UserListView/EnterpriselevelListView/
          OrderCommentListView/PromoterListView/CategoryGoodListView/
          RegionListView

- 列宽统一 240/280/220/200/320 → 140 (固定窄列)
- 清理 4 个 rr-actions 页不再使用的 .rr-actions/.rr-actions__sep 样式
- 业务逻辑全保留 (onClick 函数/二次确认/条件渲染逻辑)
- OrderListView 退款条件修正: [1,2,3,4].includes → ===4 (原 v-else-if 链语义)
This commit is contained in:
Sisyphus 2026-07-09 12:08:48 +08:00
parent 557db07231
commit cb2790d318
16 changed files with 302 additions and 219 deletions

View File

@ -0,0 +1,139 @@
<script setup lang="ts">
/**
* F006 通用表格操作列组件 (W17.6 主次分离)
*
* 主操作 (primary) 常驻文字按钮, 次操作 (more) 收进「⋯」下拉菜单。
* 解决按钮多时操作列拥挤、占宽大的问题。
*
* 用法:
* <TableActions
* :row="row"
* :primary="[{ label: '详情', type: 'primary', onClick: onView }]"
* :more="[
* { 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: 'danger', divided: true, confirm: '确认删除?', onClick: onDelete }
* ]"
* />
*
* ActionItem:
* label string | (row) => string 文字 (支持动态)
* type string | (row) => string 类型 primary/success/warning/danger (支持动态)
* when (row) => boolean 条件渲染, 不满足则不显示 (替代 v-if)
* divided boolean 菜单项前加分隔线 (危险操作前用)
* confirm boolean | string true 用默认提示, string 用自定义提示
* onClick (row) => void 点击处理
*/
import { ElMessageBox, ElMessage } from 'element-plus'
import { MoreFilled } from '@element-plus/icons-vue'
type ActionType = 'primary' | 'success' | 'warning' | 'danger' | 'info'
interface ActionItem {
label: string | ((row: any) => string)
type?: ActionType | ((row: any) => ActionType)
when?: (row: any) => boolean
divided?: boolean
confirm?: boolean | string
onClick: (row: any) => void
}
const props = defineProps<{
row: any
primary?: ActionItem[]
more?: ActionItem[]
}>()
// 解析 label (string 或函数)
function resolveLabel(item: ActionItem): string {
return typeof item.label === 'function' ? item.label(props.row) : item.label
}
// 解析 type (string 或函数)
function resolveType(item: ActionItem): ActionType {
const t = typeof item.type === 'function' ? item.type(props.row) : item.type
return t || 'primary'
}
// more 列表过滤 when 条件后的有效项
function visibleMore(): ActionItem[] {
return (props.more || []).filter(item => !item.when || item.when(props.row))
}
// 执行操作 (处理二次确认)
async function runAction(item: ActionItem) {
if (item.confirm) {
const msg = typeof item.confirm === 'string' ? item.confirm : `确认${resolveLabel(item)}?`
try {
await ElMessageBox.confirm(msg, '操作确认', { type: 'warning', confirmButtonText: '确定', cancelButtonText: '取消' })
} catch {
return // 用户取消
}
}
try {
item.onClick(props.row)
} catch (e: any) {
ElMessage.error(e?.msg || e?.message || '操作失败')
}
}
</script>
<template>
<div class="rr-table-actions">
<!-- 主操作: 常驻文字按钮 -->
<el-button
v-for="(item, i) in (primary || [])"
:key="`p${i}`"
text
:type="resolveType(item) as any"
size="small"
@click="runAction(item)"
>{{ resolveLabel(item) }}</el-button>
<!-- 次操作: 收进 ⋯ 下拉 (过滤 when 后有剩余项才渲染) -->
<el-dropdown
v-if="visibleMore().length"
trigger="click"
@command="(cmd: number) => runAction(visibleMore()[cmd])"
>
<span class="rr-more-trigger">
<el-icon><MoreFilled /></el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="(item, i) in visibleMore()"
:key="`m${i}`"
:command="i"
:divided="item.divided"
:style="{ color: resolveType(item) === 'danger' ? 'var(--el-color-danger)' : undefined }"
>{{ resolveLabel(item) }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<style scoped lang="scss">
.rr-table-actions {
display: inline-flex;
align-items: center;
gap: 4px;
}
.rr-more-trigger {
display: inline-flex;
align-items: center;
justify-content: center;
width: 26px;
height: 26px;
border-radius: 4px;
cursor: pointer;
color: var(--ink-2, #475569);
font-size: 16px;
transition: all 0.15s;
&:hover {
background: var(--el-fill-color-light, #F4F4F5);
color: var(--el-color-primary, #2563EB);
}
}
</style>

View File

@ -24,6 +24,7 @@ import { httpGet, httpPost } from '@/utils/request'
import PageHeader from '@/components/PageHeader/PageHeader.vue'
import SearchBar from '@/components/SearchBar/SearchBar.vue'
import DataTable from '@/components/DataTable/DataTable.vue'
import TableActions from '@/components/TableActions/TableActions.vue'
interface CompanyAccount {
id: string
@ -266,7 +267,7 @@ onMounted(() => { load(); loadCustomers() })
{ prop: 'activitySummary', label: '动态摘要', width: 280 },
{ prop: 'status', label: '状态', width: 100, slot: 'status' },
{ prop: 'createDate', label: '创建时间', width: 170, slot: 'createDate' },
{ label: '操作', width: 280, slot: 'action' }
{ label: '操作', width: 140, slot: 'action' }
]"
:data="list" :borderless="true"
:loading="loading"
@ -299,24 +300,14 @@ onMounted(() => { load(); loadCustomers() })
</template>
<template #action="{ row }">
<el-button
text
type="primary"
size="small"
@click="activityHandle(row)"
>查看动态</el-button>
<el-button
text
type="primary"
size="small"
@click="takeoverHandle(row)"
>接管</el-button>
<el-button
text
type="primary"
size="small"
@click="unbindHandle(row)"
>解绑</el-button>
<TableActions
:row="row"
:primary="[{ label: '查看动态', type: 'primary', onClick: activityHandle }]"
:more="[
{ label: '接管', type: 'primary', onClick: takeoverHandle },
{ label: '解绑', type: 'danger', divided: true, onClick: unbindHandle }
]"
/>
</template>
</DataTable>
</div>

View File

@ -26,6 +26,7 @@ import { httpGet, httpPost } from '@/utils/request'
import PageHeader from '@/components/PageHeader/PageHeader.vue'
import SearchBar from '@/components/SearchBar/SearchBar.vue'
import DataTable from '@/components/DataTable/DataTable.vue'
import TableActions from '@/components/TableActions/TableActions.vue'
import ApplyApiKeyDialog from './ApplyApiKeyDialog.vue'
interface OpenApp {
@ -164,7 +165,7 @@ const columns = [
{ prop: 'appId', label: 'AppID', minWidth: 220 },
{ prop: 'status', label: '状态', width: 100, slot: 'status' as const },
{ prop: 'createDate', label: '创建时间', width: 170, slot: 'createDate' as const },
{ label: '操作', width: 280, slot: 'action' as const }
{ label: '操作', width: 140, slot: 'action' as const }
]
function onSearch() { page.value = 1; load() }
@ -371,35 +372,16 @@ onMounted(() => { load(); loadCustomers() })
{{ fmtDate(row.createDate) }}
</template>
<template #action="{ row }">
<el-button text type="primary" size="small" @click="detailHandle(row)">详情</el-button>
<el-button
v-if="row.status === 0 || row.status === 1"
text
type="primary"
size="small"
@click="resendQrHandle(row)"
>重发二维码</el-button>
<el-button
v-if="row.status === 0 || row.status === 1"
text
type="warning"
size="small"
@click="resetSecretHandle(row)"
>重置密钥</el-button>
<el-button
v-if="row.status === 0 || row.status === 1"
text
size="small"
class="rr-danger-text"
@click="revokeHandle(row)"
>撤销</el-button>
<el-button
v-if="row.status === 2"
text
size="small"
class="rr-danger-text"
@click="deleteHandle(row)"
>删除</el-button>
<TableActions
:row="row"
:primary="[{ label: '详情', type: 'primary', onClick: detailHandle }]"
:more="[
{ 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 === 2, onClick: deleteHandle }
]"
/>
</template>
</DataTable>
</div>

View File

@ -23,6 +23,7 @@ import PageHeader from '@/components/PageHeader/PageHeader.vue'
import SearchBar from '@/components/SearchBar/SearchBar.vue'
import ListTabs from '@/components/ListTabs/ListTabs.vue'
import DataTable from '@/components/DataTable/DataTable.vue'
import TableActions from '@/components/TableActions/TableActions.vue'
interface Order {
id: string
@ -94,7 +95,7 @@ const columns: any = computed(() => [
{ prop: 'shippedNum', label: '已发', width: 80, align: 'center' },
{ prop: 'status', label: '状态', width: 110, align: 'center', slot: 'status' },
{ prop: 'createDate', label: '下单时间', width: 170, slot: 'time' },
{ label: '操作', width: 280, align: 'center', slot: 'action' }
{ label: '操作', width: 140, align: 'center', slot: 'action' }
])
const tabItems = computed(() => STATUS_TABS.map(t => ({
key: t.value,
@ -273,18 +274,18 @@ onMounted(load)
</el-tag>
</template>
<template #action="{ row }">
<span class="rr-actions">
<el-link type="primary" :underline="false" @click="onViewDetail(row)">详情</el-link>
<span class="rr-actions__sep">|</span>
<el-link v-if="row.status === 1" type="primary" :underline="false" @click="onPay(row)">付款</el-link>
<el-link v-else-if="row.status === 2" type="primary" :underline="false" @click="onShip(row)">发货</el-link>
<el-link v-else-if="row.status === 3" type="primary" :underline="false" @click="onConfirmReceipt(row)">收货</el-link>
<el-link v-else-if="[1,2,3,4].includes(row.status)" type="warning" :underline="false" @click="onRefund(row)">退款</el-link>
<span v-if="[1,2].includes(row.status)" class="rr-actions__sep">|</span>
<el-link v-if="[1,2].includes(row.status)" type="danger" :underline="false" @click="onCancel(row)">取消</el-link>
<span class="rr-actions__sep">|</span>
<el-link :underline="false" @click="onPrint(row)">打印</el-link>
</span>
<TableActions
:row="row"
:primary="[{ label: '详情', type: 'primary', onClick: onViewDetail }]"
:more="[
{ 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 },
{ label: '退款', type: 'warning', when: (r: any) => r.status === 4, onClick: onRefund },
{ label: '取消', type: 'danger', when: (r: any) => [1,2].includes(r.status), onClick: onCancel },
{ label: '打印', type: 'primary', onClick: onPrint }
]"
/>
</template>
</DataTable>
</div>
@ -293,19 +294,6 @@ onMounted(load)
<style scoped lang="scss">
.rr-page { max-width: 1500px; }
.rr-muted { color: var(--ink-3); }
.rr-actions {
display: inline-flex;
align-items: center;
gap: 0;
&__sep {
color: var(--rr-border-color, #E2E8F0);
margin: 0 8px;
font-size: 12px;
}
:deep(.el-link) {
font-size: 13px;
}
}
.rr-amount { font-weight: 600; color: var(--primary); }
.rr-time { font-family: 'SF Mono', Menlo, Monaco, Consolas, monospace; font-size: 12px; color: var(--ink-2); }
</style>

View File

@ -17,6 +17,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
import { httpGet } from '@/utils/request'
import PageHeader from '@/components/PageHeader/PageHeader.vue'
import SearchBar from '@/components/SearchBar/SearchBar.vue'
import TableActions from '@/components/TableActions/TableActions.vue'
interface Category {
id: string
@ -184,11 +185,16 @@ onMounted(load)
<el-button text type="primary" size="small" @click="onMove(row, 2)">下移</el-button>
</template>
</el-table-column>
<el-table-column label="操作" width="320" align="center" fixed="right">
<el-table-column label="操作" width="140" align="center" fixed="right">
<template #default="{ row }">
<el-button v-if="row.level < 3" text type="primary" size="small" @click="onAdd(row)">新增子分类</el-button>
<el-button text type="warning" size="small" @click="onEdit(row)">修改</el-button>
<el-button text type="danger" size="small" @click="onDelete(row)">删除</el-button>
<TableActions
:row="row"
:primary="[{ label: '修改', type: 'primary', onClick: onEdit }]"
:more="[
{ label: '新增子分类', type: 'primary', when: (r: any) => r.level < 3, onClick: onAdd },
{ label: '删除', type: 'danger', divided: true, onClick: onDelete }
]"
/>
</template>
</el-table-column>
</el-table>

View File

@ -19,6 +19,7 @@ import { httpGet } from '@/utils/request'
import DataTable from '@/components/DataTable/DataTable.vue'
import PageHeader from '@/components/PageHeader/PageHeader.vue'
import SearchBar from '@/components/SearchBar/SearchBar.vue'
import TableActions from '@/components/TableActions/TableActions.vue'
interface Label {
id: string
@ -126,7 +127,7 @@ onMounted(load)
{ prop: 'name', label: '标签名称', minWidth: 200 },
{ prop: 'createDate', label: '创建时间', width: 180 },
{ prop: 'status', label: '状态', width: 100, slot: 'status' },
{ label: '操作', width: 240, align: 'center', slot: 'action' }
{ label: '操作', width: 140, align: 'center', slot: 'action' }
]"
:data="list"
:loading="loading"
@ -147,11 +148,14 @@ onMounted(load)
</el-tag>
</template>
<template #action="{ row }">
<el-button text type="primary" size="small" @click="onEdit(row)">修改</el-button>
<el-button text :type="row.status === 0 ? 'warning' : 'success'" size="small" @click="onToggleStatus(row)">
{{ row.status === 0 ? '禁用' : '启用' }}
</el-button>
<el-button text type="danger" size="small" @click="onDelete(row)">删除</el-button>
<TableActions
:row="row"
:primary="[{ label: '修改', type: 'primary', onClick: onEdit }]"
:more="[
{ label: (r: any) => r.status === 0 ? '禁用' : '启用', type: (r: any) => r.status === 0 ? 'warning' : 'success', onClick: onToggleStatus },
{ label: '删除', type: 'danger', divided: true, onClick: onDelete }
]"
/>
</template>
</DataTable>
</div>

View File

@ -15,6 +15,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
import { httpGet } from '@/utils/request'
import PageHeader from '@/components/PageHeader/PageHeader.vue'
import SearchBar from '@/components/SearchBar/SearchBar.vue'
import TableActions from '@/components/TableActions/TableActions.vue'
interface Coupon {
id: string
@ -179,13 +180,17 @@ onMounted(load)
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180" />
<el-table-column label="操作" width="280" align="center" fixed="right">
<el-table-column label="操作" width="140" align="center" fixed="right">
<template #default="{ row }">
<el-button v-if="row.sourceType === 2" text type="primary" size="small" @click="onEdit(row)">编辑</el-button>
<el-button v-if="row.onShelf === 1" text type="warning" size="small" @click="onToggleStatus(row)">停用</el-button>
<el-button v-else text type="success" size="small" @click="onToggleStatus(row)">启用</el-button>
<el-button text type="danger" size="small" @click="onDelete(row)">删除</el-button>
<el-button v-if="row.sourceType === 2" text type="info" size="small" @click="onShowRecords(row)">领取记录</el-button>
<TableActions
:row="row"
:primary="[{ label: '编辑', type: 'primary', when: (r: any) => r.sourceType === 2, onClick: onEdit }]"
:more="[
{ 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 }
]"
/>
</template>
</el-table-column>
</el-table>

View File

@ -6,7 +6,7 @@
* - 搜索: 商品名/供应商/分类/品牌
* - 状态 tab: 全部/待审核/已通过/已驳回/已下架
* - 表格列: 封面/商品/供应商/3级分类/品牌/单位/申请时间/铺货/审核意见/状态
* - 操作: 详情/审核/下架/修改分类/删除
* - 操作: 详情(常驻) + 审核/上下架/改分类/删除(收纳⋯) [W17.6 主次分离]
* - 批量: 批量审核/批量驳回/批量下架/批量改分类/批量删除
* - 详情弹窗: 含 SKU 列表 (编码/组合/封面/原价/现价/秒杀价/拼团价/库存/最小购买)
* - 审核弹窗: 通过/驳回 + 审核意见
@ -21,6 +21,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
import DataTable from '@/components/DataTable/DataTable.vue'
import PageHeader from '@/components/PageHeader/PageHeader.vue'
import SearchBar from '@/components/SearchBar/SearchBar.vue'
import TableActions from '@/components/TableActions/TableActions.vue'
import ListTabs from '@/components/ListTabs/ListTabs.vue'
import { httpGet, httpPost } from '@/utils/request'
@ -141,7 +142,7 @@ const columns = [
{ prop: 'onShelf', label: '铺货', width: 100, align: 'center' as const, slot: 'onShelf' },
{ prop: 'checkStatus', label: '状态', width: 100, align: 'center' as const, slot: 'checkStatus' },
{ prop: 'createDate', label: '申请时间', width: 170 },
{ label: '操作', width: 240, align: 'center' as const, fixed: 'right' as const, slot: 'action' }
{ label: '操作', width: 140, align: 'center' as const, fixed: 'right' as const, slot: 'action' }
]
async function buildSearchParamsForStats() {
@ -389,21 +390,16 @@ onMounted(() => Promise.all([load(), loadStats()]))
</el-tag>
</template>
<template #action="{ row }">
<span class="rr-actions">
<el-link type="primary" :underline="false" @click="onShowDetail(row)">详情</el-link>
<template v-if="row.checkStatus === 1">
<span class="rr-actions__sep">|</span>
<el-link type="primary" :underline="false" @click="onShowCheckDialog(row)">审核</el-link>
</template>
<span class="rr-actions__sep">|</span>
<el-link :type="row.onShelf === 1 ? 'warning' : 'success'" :underline="false" @click="onOnShelf(row)">
{{ row.onShelf === 1 ? '下架' : '上架' }}
</el-link>
<span class="rr-actions__sep">|</span>
<el-link type="primary" :underline="false" @click="onShowCategoryDialog(row)">改分类</el-link>
<span class="rr-actions__sep">|</span>
<el-link type="danger" :underline="false" @click="onDelete(row)">删除</el-link>
</span>
<TableActions
:row="row"
:primary="[{ label: '详情', type: 'primary', onClick: onShowDetail }]"
:more="[
{ 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 },
{ label: '删除', type: 'danger', divided: true, onClick: onDelete }
]"
/>
</template>
</DataTable>
@ -598,17 +594,4 @@ onMounted(() => Promise.all([load(), loadStats()]))
.rr-good-title { font-size: 13.5px; color: var(--ink); }
.rr-sku-title { font-size: 14px; font-weight: 600; color: var(--ink); margin: 24px 0 12px 0; }
.rr-empty-tip { text-align: center; padding: 30px 0; color: var(--ink-3); }
.rr-actions {
display: inline-flex;
align-items: center;
gap: 0;
&__sep {
color: var(--rr-border-color, #E2E8F0);
margin: 0 6px;
font-size: 12px;
}
:deep(.el-link) {
font-size: 13px;
}
}
</style>

View File

@ -12,6 +12,7 @@ import { httpGet } from '@/utils/request'
import PageHeader from '@/components/PageHeader/PageHeader.vue'
import SearchBar from '@/components/SearchBar/SearchBar.vue'
import DataTable from '@/components/DataTable/DataTable.vue'
import TableActions from '@/components/TableActions/TableActions.vue'
interface CommentRow { id:string; userId:string; parentId:string; goodType:number; skuDetailId:string; orderGoodId:string; supplierId:string; supplierGoodId:string; star:number; content:string; createTime:string }
const ENDPOINTS = [
@ -47,7 +48,7 @@ const columns: Col[] = [
{ prop:'star', label:'评分', width:80, slot:'star' },
{ prop:'content', label:'内容', width:280, slot:'content' },
{ prop:'createTime', label:'时间', width:160, slot:'time' },
{ prop:'actions', label:'操作', width:200, slot:'actions', fixed:'right' }
{ prop:'actions', label:'操作', width:140, slot:'actions', fixed:'right' }
]
const STATUS_DEFAULTS = [
{ value:'', label:'全部' },
@ -109,9 +110,14 @@ onMounted(load)
</template>
<template #time="{ row }">{{ row.createTime }}</template>
<template #actions="{ row }">
<el-button type="primary" link @click="onView(row)">详情</el-button>
<el-button v-if="!row.parentId" type="success" link @click="onReply(row)">回复</el-button>
<el-button type="danger" link @click="onDelete(row)">删除</el-button>
<TableActions
:row="row"
:primary="[{ label: '详情', type: 'primary', onClick: onView }]"
:more="[
{ label: '回复', type: 'primary', when: (r: any) => !r.parentId, onClick: onReply },
{ label: '删除', type: 'danger', divided: true, onClick: onDelete }
]"
/>
</template>
</DataTable>
</div>

View File

@ -12,6 +12,7 @@ import { httpGet } from '@/utils/request'
import PageHeader from '@/components/PageHeader/PageHeader.vue'
import SearchBar from '@/components/SearchBar/SearchBar.vue'
import DataTable from '@/components/DataTable/DataTable.vue'
import TableActions from '@/components/TableActions/TableActions.vue'
interface PromoterRow { id:string; enterpriseId:string; enterpriseName:string; promoterName:string; promoterRadio:number; promoterGood:string; beginTime:string; endTime:string; createTime:string }
const ENDPOINTS = [
@ -45,7 +46,7 @@ const columns: Col[] = [
{ prop:'beginTime', label:'开始时间', width:120, slot:'date1' },
{ prop:'endTime', label:'结束时间', width:120, slot:'date2' },
{ prop:'createTime', label:'创建时间', width:160, slot:'time' },
{ prop:'actions', label:'操作', width:200, slot:'actions', fixed:'right' }
{ prop:'actions', label:'操作', width:140, slot:'actions', fixed:'right' }
]
const STATUS_DEFAULTS = [
{ value:'', label:'全部' },
@ -103,9 +104,14 @@ onMounted(load)
<template #date2="{ row }">{{ row.endTime }}</template>
<template #time="{ row }">{{ row.createTime }}</template>
<template #actions="{ row }">
<el-button type="primary" link @click="onView(row)">详情</el-button>
<el-button type="success" link @click="onEdit(row)">编辑</el-button>
<el-button type="danger" link @click="onDelete(row)">删除</el-button>
<TableActions
:row="row"
:primary="[{ label: '详情', type: 'primary', onClick: onView }]"
:more="[
{ label: '编辑', type: 'primary', onClick: onEdit },
{ label: '删除', type: 'danger', divided: true, onClick: onDelete }
]"
/>
</template>
</DataTable>
</div>

View File

@ -17,6 +17,7 @@ import PageHeader from '@/components/PageHeader/PageHeader.vue'
import SearchBar from '@/components/SearchBar/SearchBar.vue'
import ListTabs from '@/components/ListTabs/ListTabs.vue'
import DataTable from '@/components/DataTable/DataTable.vue'
import TableActions from '@/components/TableActions/TableActions.vue'
interface ServiceGood {
id: string
@ -81,7 +82,7 @@ const columns = [
{ prop: 'onShelf', label: '铺货', width: 100, align: 'center' as const, slot: 'onShelf' },
{ prop: 'checkStatus', label: '状态', width: 100, align: 'center' as const, slot: 'checkStatus' },
{ prop: 'createDate', label: '申请时间', width: 170 },
{ label: '操作', width: 280, align: 'center' as const, fixed: 'right' as const, slot: 'action' }
{ label: '操作', width: 140, align: 'center' as const, fixed: 'right' as const, slot: 'action' }
]
const tabItems = computed(() => [
@ -256,19 +257,16 @@ onMounted(() => Promise.all([load(), loadStats()]))
<el-tag :type="(CHECK_STATUS_MAP[row.checkStatus]?.type as any)" size="small" effect="light">{{ CHECK_STATUS_MAP[row.checkStatus]?.label }}</el-tag>
</template>
<template #action="{ row }">
<span class="rr-actions">
<el-link type="primary" :underline="false" @click="onShowDetail(row)">详情</el-link>
<template v-if="row.checkStatus === 1">
<span class="rr-actions__sep">|</span>
<el-link type="primary" :underline="false" @click="onShowCheck(row)">审核</el-link>
</template>
<span class="rr-actions__sep">|</span>
<el-link :type="row.onShelf === 1 ? 'warning' : 'success'" :underline="false" @click="onOnShelf(row)">{{ row.onShelf === 1 ? '下架' : '上架' }}</el-link>
<span class="rr-actions__sep">|</span>
<el-link type="primary" :underline="false" @click="onShowCategory(row)">改分类</el-link>
<span class="rr-actions__sep">|</span>
<el-link type="danger" :underline="false" @click="onDelete(row)">删除</el-link>
</span>
<TableActions
:row="row"
:primary="[{ label: '详情', type: 'primary', onClick: onShowDetail }]"
:more="[
{ 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 },
{ label: '删除', type: 'danger', divided: true, onClick: onDelete }
]"
/>
</template>
</DataTable>
@ -353,17 +351,4 @@ onMounted(() => Promise.all([load(), loadStats()]))
.rr-good-cell { display: flex; align-items: center; gap: 10px; }
.rr-good-icon-placeholder { width: 40px; height: 40px; border-radius: 4px; background: var(--bg-soft); display: flex; align-items: center; justify-content: center; color: var(--ink-3); font-weight: 600; flex-shrink: 0; }
.rr-price { font-family: 'SF Mono', Menlo, Monaco, Consolas, monospace; font-size: 13px; font-weight: 500; color: var(--primary); }
.rr-actions {
display: inline-flex;
align-items: center;
gap: 0;
&__sep {
color: var(--rr-border-color, #E2E8F0);
margin: 0 6px;
font-size: 12px;
}
:deep(.el-link) {
font-size: 13px;
}
}
</style>

View File

@ -13,6 +13,7 @@ import { httpGet, httpPost, httpPut, httpDelete } from '@/utils/request'
import DataTable from '@/components/DataTable/DataTable.vue'
import PageHeader from '@/components/PageHeader/PageHeader.vue'
import SearchBar from '@/components/SearchBar/SearchBar.vue'
import TableActions from '@/components/TableActions/TableActions.vue'
interface Schedule {
id: string
@ -148,7 +149,7 @@ onMounted(load)
{ prop: 'remark', label: '备注', minWidth: 200 },
{ prop: 'status', label: '状态', width: 100, slot: 'status' },
{ prop: 'createDate', label: '创建时间', width: 170, slot: 'createDate' },
{ label: '操作', width: 280, slot: 'action' }
{ label: '操作', width: 140, slot: 'action' }
]"
:data="list"
:loading="loading"
@ -170,10 +171,15 @@ onMounted(load)
</template>
<template #createDate="{ row }"><span class="rr-time">{{ fmtDate(row.createDate) }}</span></template>
<template #action="{ row }">
<el-button text type="primary" size="small" @click="runHandle(row)">执行</el-button>
<el-button v-if="row.status === 1" text type="warning" size="small" @click="pauseHandle(row)">暂停</el-button>
<el-button v-else text type="primary" size="small" @click="resumeHandle(row)">恢复</el-button>
<el-button text type="primary" size="small" @click="deleteHandle(row)">删除</el-button>
<TableActions
:row="row"
:primary="[{ label: '执行', type: 'primary', onClick: runHandle }]"
:more="[
{ 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 }
]"
/>
</template>
</DataTable>
</div>

View File

@ -28,6 +28,7 @@ import { httpGet, httpPost, httpPut, httpDelete } from '@/utils/request'
import DataTable from '@/components/DataTable/DataTable.vue'
import PageHeader from '@/components/PageHeader/PageHeader.vue'
import SearchBar from '@/components/SearchBar/SearchBar.vue'
import TableActions from '@/components/TableActions/TableActions.vue'
interface SysUser {
id: string
@ -252,7 +253,7 @@ onMounted(() => { load() })
{ prop: 'userType', label: '类型', width: 100, slot: 'userType' },
{ prop: 'status', label: '状态', width: 80, slot: 'status' },
{ prop: 'createDate', label: '创建时间', width: 170, slot: 'createDate' },
{ label: '操作', width: 260, slot: 'action' }
{ label: '操作', width: 140, slot: 'action' }
]"
:data="list" :borderless="true"
:loading="loading"
@ -293,32 +294,14 @@ onMounted(() => { load() })
</template>
<template #action="{ row }">
<el-button
v-if="row.status === 1"
text
type="warning"
size="small"
@click="toggleStatus(row)"
>停用</el-button>
<el-button
v-else
text
type="primary"
size="small"
@click="toggleStatus(row)"
>启用</el-button>
<el-button
text
type="primary"
size="small"
@click="resetPwdHandle(row)"
>重置密码</el-button>
<el-button
text
type="primary"
size="small"
@click="deleteHandle(row)"
>删除</el-button>
<TableActions
:row="row"
:primary="[{ label: (r: any) => r.status === 1 ? '停用' : '启用', type: (r: any) => r.status === 1 ? 'warning' : 'success', onClick: toggleStatus }]"
:more="[
{ label: '重置密码', type: 'primary', onClick: resetPwdHandle },
{ label: '删除', type: 'danger', divided: true, onClick: deleteHandle }
]"
/>
</template>
</DataTable>
</div>

View File

@ -14,6 +14,7 @@ import { ref, onMounted, reactive } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { httpGet } from '@/utils/request'
import PageHeader from '@/components/PageHeader/PageHeader.vue'
import TableActions from '@/components/TableActions/TableActions.vue'
interface Region {
id: string
@ -138,11 +139,16 @@ onMounted(load)
</el-table-column>
<el-table-column prop="sort" label="排序" width="100" align="center" />
<el-table-column prop="updateDate" label="更新时间" width="180" />
<el-table-column label="操作" width="240" align="center" fixed="right">
<el-table-column label="操作" width="140" align="center" fixed="right">
<template #default="{ row }">
<el-button v-if="row.treeLevel < 4" text type="primary" size="small" @click="onAdd(row)">新增下级</el-button>
<el-button text type="primary" size="small" @click="onEdit(row)">修改</el-button>
<el-button text type="danger" size="small" @click="onDelete(row)">删除</el-button>
<TableActions
:row="row"
:primary="[{ label: '修改', type: 'primary', onClick: onEdit }]"
:more="[
{ label: '新增下级', type: 'primary', when: (r: any) => r.treeLevel < 4, onClick: onAdd },
{ label: '删除', type: 'danger', divided: true, onClick: onDelete }
]"
/>
</template>
</el-table-column>
</el-table>

View File

@ -13,6 +13,7 @@ import PageHeader from '@/components/PageHeader/PageHeader.vue'
import SearchBar from '@/components/SearchBar/SearchBar.vue'
import ListTabs from '@/components/ListTabs/ListTabs.vue'
import UserFormDialog from './UserFormDialog.vue'
import TableActions from '@/components/TableActions/TableActions.vue'
interface FrontUser {
id: string
@ -87,7 +88,7 @@ const columns: any[] = [
width: 180,
formatter: (row: FrontUser) => fmtDate(row.createDate)
},
{ label: '操作', width: 220, slot: 'action', fixed: 'right' }
{ label: '操作', width: 140, slot: 'action', fixed: 'right' }
]
function fmtDate(ts: number | null | undefined) {
@ -340,15 +341,14 @@ onMounted(load)
<el-tag v-else type="danger" size="small" effect="light" round>禁用</el-tag>
</template>
<template #action="{ row }">
<span class="rr-actions">
<el-link type="primary" :underline="false" @click="onEdit(row)">修改</el-link>
<span class="rr-actions__sep">|</span>
<el-link :type="row.enabled === 0 ? 'warning' : 'success'" :underline="false" @click="toggleEnabled(row)">
{{ row.enabled === 0 ? '禁用' : '启用' }}
</el-link>
<span class="rr-actions__sep">|</span>
<el-link type="danger" :underline="false" @click="deleteUser(row)">删除</el-link>
</span>
<TableActions
:row="row"
:primary="[{ label: '修改', type: 'primary', onClick: onEdit }]"
:more="[
{ label: (r: any) => r.enabled === 0 ? '禁用' : '启用', type: (r: any) => r.enabled === 0 ? 'warning' : 'success', onClick: toggleEnabled },
{ label: '删除', type: 'danger', divided: true, onClick: deleteUser }
]"
/>
</template>
</DataTable>
</div>
@ -356,17 +356,4 @@ onMounted(load)
<style scoped lang="scss">
.rr-page { max-width: 100%; }
.rr-actions {
display: inline-flex;
align-items: center;
gap: 0;
&__sep {
color: var(--rr-border-color, #E2E8F0);
margin: 0 8px;
font-size: 12px;
}
:deep(.el-link) {
font-size: 13px;
}
}
</style>

View File

@ -12,6 +12,7 @@ import { httpGet } from '@/utils/request'
import PageHeader from '@/components/PageHeader/PageHeader.vue'
import SearchBar from '@/components/SearchBar/SearchBar.vue'
import DataTable from '@/components/DataTable/DataTable.vue'
import TableActions from '@/components/TableActions/TableActions.vue'
interface LevelRow { id:string; enterpriseLevel:string; scoreFrom:number; scoreTo:number; createTime:string }
const ENDPOINTS = [
@ -34,7 +35,7 @@ const columns: Col[] = [
{ prop:'enterpriseLevel', label:'等级', width:140 },
{ prop:'scoreFrom', label:'起始分', width:100 },
{ prop:'scoreTo', label:'截止分', width:100 },
{ prop:'actions', label:'操作', width:200, slot:'actions', fixed:'right' }
{ prop:'actions', label:'操作', width:140, slot:'actions', fixed:'right' }
]
const STATUS_DEFAULTS = [
{ value:'', label:'全部' }
@ -89,9 +90,14 @@ onMounted(load)
:borderless="true" :loading="loading" :page="page" :limit="limit" :total="total" row-key="id"
@page-change="(p: number) => { page = p; load() }" @limit-change="(l: number) => { limit = l; page = 1; load() }">
<template #actions="{ row }">
<el-button type="primary" link @click="onView(row)">查看</el-button>
<el-button type="success" link @click="onEdit(row)">编辑</el-button>
<el-button type="danger" link @click="onDelete(row)">删除</el-button>
<TableActions
:row="row"
:primary="[{ label: '查看', type: 'primary', onClick: onView }]"
:more="[
{ label: '编辑', type: 'primary', onClick: onEdit },
{ label: '删除', type: 'danger', divided: true, onClick: onDelete }
]"
/>
</template>
</DataTable>
</div>