用户原话: 不是让你沿用老vue页面内容了吗, 只是更换成新版本的UI 用户原话: 要能够交付使用, 像原有的vue平台一样使用 整改 (5 个 view 从严重简化变完整版): - OrderListView 240 行: 订单号/合同号/供应商/状态/时间范围 完整搜索, 8 种状态 tab, 操作 (详情/付款/发货/收货/退款/取消/打印), 批量导出, type 自适应 good/service/group - OrderDetailView 582 行: 完整 4-tab (订单信息/商品明细/物流/付款发票), 状态卡 + 操作按钮 (按状态动态显示), 9 种订单状态, 推广信息, 多状态按需渲染 - GoodListView 507 行: 12 列表格 (封面/商品/供应商/3 级分类/品牌/单位/铺货/审核/状态), 4 种状态 tab, 5 种操作 (详情/审核/上下架/改分类/删除), 5 种批量操作, 详情弹窗含 SKU 列表 (编码/原价/现价/秒杀价/拼团价/库存) - ServiceGoodListView 302 行: 同上针对服务商品 - InquiryDetailView 229 行: 询价基本信息 (产品/品牌/规格/数量/收货地址/交货日期/价格有效), 附件下载, 报价列表 (供应商/状态/金额/次数/来源/查看报价) 字段 100% 沿用老 vue (supplierGoodId/contractCode/groupNumber/acceptNum 等), 新增 mock 数据 (3-8 条覆盖各种业务场景), 弹窗/操作/批量/导出 全部走通 (mock 实现) + W3.x C P0-P2 累计 30+ 个 view 文件补 commit (inquiry/openapi/order/sys 等 11+ 模块) router 调整: 9 个 order 路由统一到 OrderListView (type 自动推断) + /supplier/servicegood 路由独立 (避免被 tree-shake)
296 lines
7.7 KiB
Vue
296 lines
7.7 KiB
Vue
<script setup lang="ts">
|
|
/**
|
|
* F006 通用 DataTable 组件
|
|
* W3.3:抽取自 InquiryListView + DashboardView 近期询价表
|
|
*
|
|
* 用法:
|
|
* <DataTable
|
|
* :columns="[
|
|
* { prop: 'id', label: '单号', width: 180 },
|
|
* { prop: 'customer', label: '客户', minWidth: 180 },
|
|
* { prop: 'status', label: '状态', width: 110, type: 'status',
|
|
* statusMap: { pending: '待报价', quoting: '报价中', done: '已成交', cancelled: '已取消' } },
|
|
* { label: '操作', width: 100, slot: 'action' }
|
|
* ]"
|
|
* :data="list"
|
|
* :loading="loading"
|
|
* :total="total"
|
|
* :page="page"
|
|
* :limit="limit"
|
|
* :show-pagination="true"
|
|
* row-key="id"
|
|
* @row-click="onRowClick"
|
|
* @page-change="onPageChange"
|
|
* >
|
|
* <template #action="{ row }">
|
|
* <el-button text type="primary" size="small" @click.stop="onView(row)">查看</el-button>
|
|
* </template>
|
|
* </DataTable>
|
|
*
|
|
* 列定义支持:
|
|
* - prop: 字段名
|
|
* - label: 列标题
|
|
* - width / minWidth: 列宽
|
|
* - type: 'status' 自动渲染 el-tag(需 statusMap)
|
|
* - statusMap: { value: '中文标签' }
|
|
* - slot: 自定义插槽名
|
|
* - formatter: (row) => string 自定义格式化
|
|
* - align: 对齐方式
|
|
*/
|
|
import { computed } from 'vue'
|
|
|
|
interface Column {
|
|
prop?: string
|
|
label: string
|
|
width?: number | string
|
|
minWidth?: number | string
|
|
type?: 'status' | 'text'
|
|
statusMap?: Record<string, { label: string; type: string }>
|
|
slot?: string
|
|
formatter?: (row: any) => string
|
|
align?: 'left' | 'center' | 'right'
|
|
}
|
|
|
|
interface Props {
|
|
columns: Column[]
|
|
data: any[]
|
|
loading?: boolean
|
|
total?: number
|
|
page?: number
|
|
limit?: number
|
|
showPagination?: boolean
|
|
pageSizes?: number[]
|
|
rowKey?: string
|
|
stripe?: boolean
|
|
borderless?: boolean
|
|
emptyText?: string
|
|
errorText?: string
|
|
error?: string | null
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
loading: false,
|
|
total: 0,
|
|
page: 1,
|
|
limit: 20,
|
|
showPagination: true,
|
|
pageSizes: () => [10, 20, 50, 100],
|
|
rowKey: 'id',
|
|
stripe: true,
|
|
borderless: false,
|
|
emptyText: '暂无数据',
|
|
errorText: '',
|
|
error: null
|
|
})
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'row-click', row: any): void
|
|
(e: 'page-change', page: number): void
|
|
(e: 'limit-change', limit: number): void
|
|
}>()
|
|
|
|
const hasStatusCol = computed(() => props.columns.some(c => c.type === 'status'))
|
|
const showEmpty = computed(() => !props.loading && props.data.length === 0)
|
|
const showPager = computed(() => props.showPagination && props.total > 0 && props.data.length > 0)
|
|
|
|
function getStatusMeta(col: Column, value: string) {
|
|
return col.statusMap?.[value] || { label: value || '--', type: 'info' }
|
|
}
|
|
|
|
function onRowClick(row: any) {
|
|
emit('row-click', row)
|
|
}
|
|
function onPageChange(p: number) {
|
|
emit('page-change', p)
|
|
}
|
|
function onLimitChange(l: number) {
|
|
emit('limit-change', l)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="rr-data-table" :class="{ borderless }">
|
|
<el-table
|
|
:data="data"
|
|
:loading="loading"
|
|
:stripe="stripe"
|
|
:border="!borderless"
|
|
:row-key="rowKey"
|
|
style="width: 100%"
|
|
@row-click="onRowClick"
|
|
row-class-name="rr-clickable-row"
|
|
:header-row-style="{ height: '40px' }"
|
|
:row-style="{ height: '52px' }"
|
|
:header-cell-style="{
|
|
background: '#F8FAFC',
|
|
color: '#475569',
|
|
fontWeight: '600',
|
|
fontSize: '12.5px',
|
|
padding: '10px 14px',
|
|
borderBottom: '1px solid #E2E8F0',
|
|
lineHeight: '18.75px',
|
|
textAlign: 'center',
|
|
height: '40px'
|
|
}"
|
|
:cell-style="{
|
|
padding: '0 14px',
|
|
borderBottom: '1px solid #E2E8F0',
|
|
fontSize: '13.5px',
|
|
lineHeight: '20.25px',
|
|
color: '#0F172A',
|
|
height: '52px'
|
|
}"
|
|
>
|
|
<el-table-column
|
|
v-for="(col, idx) in columns"
|
|
:key="idx"
|
|
:prop="col.type === 'status' || col.slot ? undefined : col.prop"
|
|
:label="col.label"
|
|
:width="col.width"
|
|
:min-width="col.minWidth"
|
|
:align="col.align || 'center'"
|
|
:show-overflow-tooltip="true"
|
|
>
|
|
<template v-if="col.slot" #default="{ row }">
|
|
<slot :name="col.slot" :row="row" />
|
|
</template>
|
|
<template v-else-if="col.type === 'status'" #default="{ row }">
|
|
<el-tag
|
|
:type="(getStatusMeta(col, row[col.prop!]).type) as any"
|
|
size="small"
|
|
effect="light"
|
|
>
|
|
{{ getStatusMeta(col, row[col.prop!]).label }}
|
|
</el-tag>
|
|
</template>
|
|
<template v-else-if="col.formatter" #default="{ row }">
|
|
{{ col.formatter?.(row) }}
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<template #empty>
|
|
<div class="rr-empty">
|
|
<span v-if="error">{{ error }}</span>
|
|
<span v-else>{{ emptyText }}</span>
|
|
</div>
|
|
</template>
|
|
</el-table>
|
|
|
|
<div v-if="showPager" class="rr-pagination">
|
|
<span class="rr-pagination-total">共 {{ total }} 条记录</span>
|
|
<el-pagination
|
|
background
|
|
layout="prev, pager, next"
|
|
:total="total"
|
|
:page-size="limit"
|
|
:current-page="page"
|
|
:page-sizes="[10, 20, 50, 100]"
|
|
:prev-text="'上一页'"
|
|
:next-text="'下一页'"
|
|
:pager-count="5"
|
|
@current-change="onPageChange"
|
|
class="rr-pager"
|
|
/>
|
|
<span class="rr-pagination-size">
|
|
每页
|
|
<el-select :model-value="limit" size="small" @change="onLimitChange" class="rr-page-size-select">
|
|
<el-option v-for="s in pageSizes" :key="s" :label="`${s} 行`" :value="s" />
|
|
</el-select>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped lang="scss">
|
|
.rr-data-table {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
overflow: hidden;
|
|
&.borderless {
|
|
border: 0;
|
|
border-radius: 0;
|
|
}
|
|
}
|
|
.rr-clickable-row { cursor: pointer; }
|
|
.rr-clickable-row:hover > td { background: var(--bg-soft) !important; }
|
|
.rr-empty {
|
|
padding: 32px 0;
|
|
color: var(--ink-3);
|
|
font-size: 13px;
|
|
text-align: center;
|
|
}
|
|
.rr-pagination {
|
|
padding: 11px 14px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
border-top: 1px solid var(--border);
|
|
color: var(--ink-3);
|
|
font-size: 12.5px;
|
|
gap: 8px;
|
|
}
|
|
.rr-pagination-total {
|
|
font-weight: 400;
|
|
color: var(--ink-3);
|
|
}
|
|
.rr-pagination-unit {
|
|
font-size: 12.5px;
|
|
color: var(--ink-3);
|
|
margin-left: -4px;
|
|
}
|
|
.rr-pagination :deep(.el-pagination) {
|
|
flex: 1;
|
|
justify-content: flex-end;
|
|
}
|
|
.rr-pagination :deep(.el-pagination .el-pagination__sizes .el-select .el-input__inner) {
|
|
font-size: 12.5px;
|
|
}
|
|
.rr-pagination-size {
|
|
display: inline-flex; align-items: center; gap: 6px;
|
|
font-size: 12px; color: var(--ink-3);
|
|
flex-shrink: 0;
|
|
}
|
|
.rr-page-size-select {
|
|
width: 80px;
|
|
}
|
|
.rr-page-size-select :deep(.el-input__wrapper) {
|
|
background: var(--surface);
|
|
box-shadow: 0 0 0 1px var(--border) inset;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
/* 分页器全部按钮 12px(原 14px 太大) + 圆角 + 紧凑间距 */
|
|
:deep(.rr-pager .btn-prev),
|
|
:deep(.rr-pager .btn-next),
|
|
:deep(.rr-pager .el-pager li) {
|
|
font-size: 12px !important;
|
|
min-width: 28px !important;
|
|
height: 28px !important;
|
|
line-height: 28px !important;
|
|
border-radius: 50% !important;
|
|
padding: 0 !important;
|
|
}
|
|
:deep(.rr-pager .btn-prev),
|
|
:deep(.rr-pager .btn-next) {
|
|
padding: 0 8px !important;
|
|
border-radius: 14px !important;
|
|
}
|
|
:deep(.rr-pager .el-pager li) {
|
|
margin: 0 2px !important;
|
|
}
|
|
:deep(.rr-pager) {
|
|
gap: 4px;
|
|
}
|
|
:deep(.rr-pager .el-pager li.is-active) {
|
|
border-radius: 50% !important;
|
|
}
|
|
|
|
/* 单元格 nowrap 截断(套设计稿 RFQ-20668…) */
|
|
:deep(.el-table .cell) {
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
</style>
|