feat(components): 按 ui-preview-v2.html 模板 v2 整改 (PageHeader扁平/SearchBar无label/ListTabs pill/Table uppercase左对齐) W17.2.1

This commit is contained in:
Sisyphus 2026-07-07 13:02:07 +08:00
parent 7724b4a3b1
commit fe07612f29
5 changed files with 200 additions and 333 deletions

View File

@ -1,41 +1,26 @@
<script setup lang="ts">
/**
* F006 通用 DataTable 组件
* W3.3:抽取自 InquiryListView + DashboardView 近期询价表
* F006 通用 DataTable 组件 (W17 设计规范 v2 整改)
*
* 按 ui-preview-v2.html 模板:
* - 表头 text-transform: uppercase + 左对齐
* - 单元格紧凑 padding 12px 16px + 左对齐
* - 颜色全用 var(--xxx) 主题化
* - 表格外无卡片包裹
*
* 用法:
* <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"
* :data="list" :loading="loading" :total="total" :page="page" :limit="limit"
* @page-change="onPageChange"
* >
* <template #action="{ row }">
* <el-button text type="primary" size="small" @click.stop="onView(row)">查看</el-button>
* <el-button text type="primary" size="small">查看</el-button>
* </template>
* </DataTable>
*
* 列定义支持:
* - prop: 字段名
* - label: 列标题
* - width / minWidth: 列宽
* - type: 'status' 自动渲染 el-tag(需 statusMap)
* - statusMap: { value: '中文标签' }
* - slot: 自定义插槽名
* - formatter: (row) => string 自定义格式化
* - align: 对齐方式
*/
import { computed } from 'vue'
@ -49,6 +34,7 @@ interface Column {
slot?: string
formatter?: (row: any) => string
align?: 'left' | 'center' | 'right'
fixed?: 'left' | 'right' | true
}
interface Props {
@ -59,7 +45,6 @@ interface Props {
page?: number
limit?: number
showPagination?: boolean
carded?: boolean
pageSizes?: number[]
rowKey?: string
stripe?: boolean
@ -77,7 +62,7 @@ const props = withDefaults(defineProps<Props>(), {
showPagination: true,
pageSizes: () => [10, 20, 50, 100],
rowKey: 'id',
stripe: true,
stripe: false,
borderless: false,
emptyText: '暂无数据',
errorText: '',
@ -100,49 +85,44 @@ function getStatusMeta(col: Column, value: string) {
}
function onSelectionChange(rows: any[]) { emit('selection-change', rows) }
function onRowClick(row: any) {
emit('row-click', row)
}
function onPageChange(p: number) {
emit('page-change', p)
}
function onLimitChange(l: number) {
emit('limit-change', l)
}
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, carded }">
<div class="rr-data-table" :class="{ borderless }">
<el-table
:data="data"
:loading="loading"
:stripe="stripe"
:border="!borderless"
:row-key="rowKey"
style="width: 100%" :cell-class-name="'rr-data-cell'"
style="width: 100%"
@row-click="onRowClick"
@selection-change="onSelectionChange"
row-class-name="rr-clickable-row"
:header-row-style="{ height: '40px' }"
:row-style="{ height: '52px' }"
:header-cell-style="{
background: 'var(--rr-table-header-bg, #F8FAFC)',
color: 'var(--rr-text-secondary, #475569)',
color: 'var(--rr-text-secondary, #64748B)',
fontWeight: '600',
fontSize: '12.5px',
padding: '10px 14px',
fontSize: '12px',
padding: '11px 16px',
borderBottom: '1px solid var(--rr-border-color, #E2E8F0)',
lineHeight: '18.75px',
textAlign: 'center',
textTransform: 'uppercase',
letterSpacing: '0.04em',
whiteSpace: 'nowrap',
textAlign: 'left',
height: '40px'
}"
:cell-style="{
padding: '0 14px',
padding: '12px 16px',
borderBottom: '1px solid var(--rr-border-color, #E2E8F0)',
fontSize: '13.5px',
lineHeight: '20.25px',
color: 'var(--rr-text-primary, #0F172A)',
height: '52px'
color: 'var(--rr-text-secondary, #334155)',
fontSize: '13px',
textAlign: 'left',
verticalAlign: 'middle'
}"
>
<template v-for="(col, idx) in columns" :key="idx">
@ -150,7 +130,8 @@ function onLimitChange(l: number) {
v-if="col.type === 'selection'"
type="selection"
:width="col.width || 50"
:align="col.align || 'center'"
:align="col.align || 'left'"
:fixed="col.fixed"
/>
<el-table-column
v-else
@ -158,7 +139,8 @@ function onLimitChange(l: number) {
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:align="col.align || 'center'"
:align="col.align || 'left'"
:fixed="col.fixed"
:show-overflow-tooltip="true"
>
<template v-if="col.slot" #default="{ row }">
@ -178,129 +160,66 @@ function onLimitChange(l: number) {
</template>
</el-table-column>
</template>
<template #empty>
<div class="rr-empty">
<span v-if="error">{{ error }}</span>
<span v-if="error" class="rr-empty__error">{{ error || errorText }}</span>
<span v-else>{{ emptyText }}</span>
</div>
</template>
</el-table>
<div v-if="showPager" class="rr-pagination">
<span class="rr-pagination-total">共 {{ total }} 条记录</span>
<div v-if="showPager" class="rr-pager">
<div class="rr-pager__info">
共 {{ total }} 条
</div>
<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"
:page-size="limit"
:total="total"
:page-sizes="pageSizes"
layout="sizes, prev, pager, next, jumper"
background
@current-change="onPageChange"
class="rr-pager"
@size-change="onLimitChange"
/>
<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;
width: 100%;
// 行 hover 高亮
:deep(.el-table__row) {
transition: background 0.1s;
}
:deep(.el-table__row:hover > td.el-table__cell) {
background-color: var(--rr-row-hover, #F8FAFC) !important;
}
// 隐藏全局边线(模板的 table-card 在外层,这里走全局)
&.borderless :deep(.el-table__inner-wrapper) {
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;
padding: 32px 0;
color: var(--ink-3, #94A3B8);
font-size: 13px;
&__error {
color: var(--red, #EF4444);
}
.rr-pagination {
padding: 11px 14px;
}
.rr-pager {
display: flex;
justify-content: space-between;
align-items: center;
border-top: 1px solid var(--border);
color: var(--ink-3);
justify-content: space-between;
padding: 14px 16px;
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;
color: var(--ink-3, #94A3B8);
background: var(--rr-bg-card, #FFFFFF);
border: 1px solid var(--rr-border-color, #E2E8F0);
border-top: none;
border-radius: 0 0 8px 8px;
&__info { color: var(--ink-3, #94A3B8); }
}
</style>

View File

@ -1,19 +1,10 @@
<script setup lang="ts">
/**
* F006 通用 ListTabs 组件 (W17 设计规范整改)
* F006 通用 ListTabs 组件 (W17 设计规范 v2 整改)
*
* 状态 tab + 数字 badge, 完全按设计规范:
* 全部 128 待报价 23 进行中 45 已完成 52 已取消 8
* 按 ui-preview-v2.html 模板: padding 9px 16px + 下划线高亮 + tab-count 圆角 pill
*
* 用法:
* <ListTabs
* v-model="activeTab"
* :items="[
* { key: 'all', label: '全部', count: 128 },
* { key: 'pending', label: '待报价', count: 23 },
* ...
* ]"
* />
* 全部 [128] 待报价 [23] 进行中 [45] 已完成 [52] 已取消 [8]
*/
export interface ListTabItem {
key: string
@ -57,61 +48,47 @@ function onClick(key: string) {
.rr-list-tabs {
display: flex;
align-items: center;
gap: 32px;
padding: 0 0 12px 0;
border-bottom: 1px solid var(--rr-border-color, #E2E8F0);
margin-bottom: 16px;
gap: 0;
margin-bottom: 14px;
border-bottom: 1px solid var(--border, #E2E8F0);
}
.rr-list-tab {
padding: 9px 16px;
font-size: 13px;
color: var(--ink-3, #64748B);
cursor: pointer;
border-bottom: 2px solid transparent;
transition: all 0.15s;
font-weight: 500;
position: relative;
user-select: none;
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 0;
font-size: 14px;
color: var(--rr-text-secondary, #475569);
cursor: pointer;
position: relative;
user-select: none;
transition: color 0.2s;
&::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: -13px;
height: 2px;
background: transparent;
border-radius: 1px;
transition: background 0.2s;
}
&:hover {
color: var(--rr-color-primary, #2563EB);
}
&:hover { color: var(--ink, #0F172A); }
&.is-active {
color: var(--rr-color-primary, #2563EB);
color: var(--brand, #2563EB);
font-weight: 600;
&::after {
background: var(--rr-color-primary, #2563EB);
}
}
&__label {
line-height: 1;
border-bottom-color: var(--brand, #2563EB);
}
&__count {
font-size: 12px;
color: var(--rr-text-tertiary, #94A3B8);
font-weight: 400;
line-height: 1;
font-size: 11px;
background: var(--surface-2, #F1F5F9);
color: var(--ink-4, #94A3B8);
padding: 0 6px;
border-radius: 999px;
font-weight: 500;
line-height: 18px;
min-width: 20px;
text-align: center;
}
&.is-active &__count {
color: var(--rr-color-primary, #2563EB);
background: var(--brand-soft, rgba(37, 99, 235, 0.08));
color: var(--brand, #2563EB);
}
}
</style>

View File

@ -1,8 +1,8 @@
<script setup lang="ts">
/**
* F006 通用 PageHeader 组件 (W17 设计规范整改)
* F006 通用 PageHeader 组件 (W17 设计规范 v2 整改)
*
* 页面头: 卡片化 (白底圆角阴影) + 标题 + 副标题 + 操作按钮区
* 页面头: 标题 + 副标题 + 操作按钮区 (按 ui-preview-v2.html 模板, 无卡片化)
*
* 用法:
* <PageHeader title="订单管理" :sub="`共 ${total} 条`">
@ -13,16 +13,14 @@
interface Props {
title: string
sub?: string
bordered?: boolean
}
withDefaults(defineProps<Props>(), {
sub: '',
bordered: true
sub: ''
})
</script>
<template>
<div class="rr-page-header" :class="{ 'is-bordered': bordered }">
<div class="rr-page-header">
<div class="rr-page-header-text">
<h2 class="rr-page-title">{{ title }}</h2>
<p v-if="sub" class="rr-page-sub">{{ sub }}</p>
@ -36,14 +34,8 @@ withDefaults(defineProps<Props>(), {
<style scoped lang="scss">
.rr-page-header {
display: flex;
align-items: center;
justify-content: space-between;
align-items: flex-start;
gap: 16px;
padding: 20px 24px;
background: var(--rr-bg-card, #FFFFFF);
border: 1px solid var(--rr-border-color, #E2E8F0);
border-radius: 8px;
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
margin-bottom: 16px;
}
.rr-page-header-text {
@ -51,16 +43,17 @@ withDefaults(defineProps<Props>(), {
min-width: 0;
}
.rr-page-title {
font-size: 18px;
font-weight: 600;
color: var(--rr-text-primary, #0F172A);
margin: 0 0 4px 0;
font-size: 20px;
font-weight: 700;
color: var(--ink, #0F172A);
margin: 0;
line-height: 1.3;
letter-spacing: -0.02em;
}
.rr-page-sub {
color: var(--rr-text-tertiary, #94A3B8);
font-size: 12.5px;
margin: 0;
color: var(--ink-3, #64748B);
margin: 2px 0 0 0;
line-height: 1.4;
}
.rr-page-actions {

View File

@ -1,30 +1,21 @@
<script setup lang="ts">
/**
* F006 通用 SearchBar 组件 (W17 设计规范整改)
* F006 通用 SearchBar 组件 (W17 设计规范 v2 整改)
*
* 标准搜索条: 紧凑布局 + 已选 chip 标签 + 查询/重置按钮
* 按 ui-preview-v2.html 模板: 紧凑布局 + 无 label + 同行末尾 chip + 简洁按钮
*
* 用法:
* <SearchBar
* v-model:filters="filters"
* :fields="[
* { key: 'name', label: '名称', type: 'text', placeholder: '请输入' },
* { key: 'status', label: '状态', type: 'select', options: [...] },
* { key: 'date', label: '日期', type: 'daterange' }
* { key: 'status', label: '状态', type: 'select', options: [...] }
* ]"
* @search="onSearch"
* @reset="onReset"
* />
*
* 字段类型:
* - text: el-input
* - select: el-select + options
* - date: el-date-picker (单日)
* - daterange: el-date-picker type=datertange
* - number: el-input-number
*/
import { computed } from 'vue'
import { Close } from '@element-plus/icons-vue'
export interface SearchFieldOption {
value: any
@ -39,6 +30,7 @@ export interface SearchField {
options?: SearchFieldOption[]
width?: number | string
clearable?: boolean
showLabel?: boolean
}
interface Props {
@ -46,11 +38,13 @@ interface Props {
fields: SearchField[]
showReset?: boolean
showChips?: boolean
showLabel?: boolean
}
const props = withDefaults(defineProps<Props>(), {
showReset: true,
showChips: true
showChips: true,
showLabel: false
})
const emit = defineEmits<{
@ -65,22 +59,15 @@ const localFilters = computed({
})
function onSearch() { emit('search') }
function onReset() {
for (const f of props.fields) {
if (f.type === 'daterange') {
localFilters.value[f.key] = []
} else if (f.type === 'number') {
localFilters.value[f.key] = undefined
} else {
localFilters.value[f.key] = ''
}
if (f.type === 'daterange') localFilters.value[f.key] = []
else if (f.type === 'number') localFilters.value[f.key] = undefined
else localFilters.value[f.key] = ''
}
emit('reset')
}
function onEnter() { onSearch() }
function onClearField(key: string) {
const f = props.fields.find(it => it.key === key)
if (!f) return
@ -89,7 +76,6 @@ function onClearField(key: string) {
else localFilters.value[key] = ''
}
// 提取已选 chip
const activeChips = computed(() => {
return props.fields
.map(f => {
@ -116,7 +102,7 @@ const activeChips = computed(() => {
<div class="rr-search-bar">
<div class="rr-search-bar__row">
<div class="rr-search-bar__field" v-for="f in fields" :key="f.key">
<span class="rr-search-bar__label">{{ f.label }}</span>
<span v-if="showLabel || f.showLabel" class="rr-search-bar__label">{{ f.label }}</span>
<el-input
v-if="f.type === 'text' || !f.type"
v-model="localFilters[f.key]"
@ -130,7 +116,7 @@ const activeChips = computed(() => {
v-model="localFilters[f.key]"
:placeholder="f.placeholder || `请选择${f.label}`"
:clearable="f.clearable !== false"
:style="{ width: f.width || 180 }"
:style="{ width: f.width || 160 }"
>
<el-option
v-for="opt in f.options || []"
@ -164,86 +150,69 @@ const activeChips = computed(() => {
:style="{ width: f.width || 180 }"
/>
</div>
<div class="rr-search-bar__actions">
<el-button type="primary" @click="onSearch">查询</el-button>
<el-button v-if="showReset" @click="onReset">重置</el-button>
</div>
</div>
<div v-if="showChips && activeChips.length" class="rr-search-bar__chips">
<span class="rr-search-bar__chips-label">已选:</span>
<el-tag
<div class="rr-search-bar__chips" v-if="showChips && activeChips.length">
<div
v-for="c in activeChips"
:key="c.key"
class="rr-search-bar__chip"
closable
:disable-transitions="true"
@close="onClearField(c.key)"
>
{{ c.label }}
</el-tag>
<el-link v-if="activeChips.length > 1" type="primary" :underline="false" @click="onReset" class="rr-search-bar__clear-all">
清空
</el-link>
<span class="rr-search-bar__chip-close" @click="onClearField(c.key)">×</span>
</div>
</div>
<div class="rr-search-bar__actions">
<el-button type="primary" size="small" @click="onSearch">查询</el-button>
<el-button v-if="showReset" size="small" @click="onReset">重置</el-button>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.rr-search-bar {
padding: 16px 20px;
background: var(--rr-bg-card, #FFFFFF);
border: 1px solid var(--rr-border-color, #E2E8F0);
border-radius: 8px;
margin-bottom: 16px;
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
margin-bottom: 14px;
&__row {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
align-items: center;
gap: 12px 16px;
}
&__field {
display: flex;
display: inline-flex;
align-items: center;
gap: 8px;
}
&__label {
font-size: 13px;
color: var(--rr-text-secondary, #475569);
white-space: nowrap;
flex-shrink: 0;
color: var(--ink-2, #334155);
margin-right: 6px;
}
&__chips {
display: inline-flex;
align-items: center;
gap: 6px;
}
&__chip {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
background: var(--brand-soft, rgba(37, 99, 235, 0.08));
color: var(--brand, #2563EB);
border-radius: 999px;
font-size: 12px;
font-weight: 500;
&-close {
cursor: pointer;
opacity: 0.6;
font-size: 14px;
line-height: 1;
&:hover { opacity: 1; }
}
}
&__actions {
display: flex;
display: inline-flex;
gap: 8px;
margin-left: auto;
}
&__chips {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-top: 12px;
padding-top: 12px;
border-top: 1px dashed var(--rr-border-color, #E2E8F0);
}
&__chips-label {
font-size: 12px;
color: var(--rr-text-tertiary, #94A3B8);
}
&__chip {
font-size: 12px;
}
&__clear-all {
font-size: 12px;
}
}
</style>

View File

@ -1,18 +1,16 @@
<script setup lang="ts">
/**
* F006 用户管理 (前台用户列表) - W17 设计规范完整版
* F006 用户管理 (前台用户列表) - W17 设计规范 v2 完整版
*
* 9 列 + 6 搜索 + 4 操作, 符合设计规范:
* - 状态 chip (是否企业认证 / 禁用标识)
* - 操作列 el-link + 竖线分隔
* - 白卡片化表格
* 按 ui-preview-v2.html 模板: 9 列 + 6 搜索 + 4 操作 + 状态 tab
*/
import { onMounted, ref, reactive } from 'vue'
import { onMounted, ref, reactive, computed } from 'vue'
import { httpGet, httpPost } from '@/utils/request'
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 ListTabs from '@/components/ListTabs/ListTabs.vue'
interface FrontUser {
id: string
@ -31,7 +29,9 @@ interface FrontUser {
const list = ref<FrontUser[]>([])
const total = ref(0)
const enabledStats = reactive({ normal: 0, disabled: 0 })
const loading = ref(false)
const activeTab = ref('all')
const query = reactive({
page: 1,
limit: 20,
@ -117,6 +117,9 @@ async function load() {
raw: it
}))
total.value = Number(resp.total || 0)
// 统计 (在当前搜索结果中)
enabledStats.normal = list.value.filter(u => u.enabled === 1).length
enabledStats.disabled = list.value.filter(u => u.enabled === 0).length
} else {
list.value = []
total.value = 0
@ -130,11 +133,26 @@ async function load() {
}
}
const tabItems = computed(() => [
{ key: 'all', label: '全部', count: total.value },
{ key: 'normal', label: '正常', count: enabledStats.normal },
{ key: 'disabled', label: '已禁用', count: enabledStats.disabled }
])
function onTabChange(key: string) {
if (key === 'all') query.enabled = ''
else if (key === 'normal') query.enabled = '1'
else if (key === 'disabled') query.enabled = '0'
query.page = 1
load()
}
function onPageChange(p: number) { query.page = p; load() }
function onSearch() { query.page = 1; load() }
function onReset() {
query.userId = ''; query.nickName = ''; query.mobile = ''
query.enterpriseName = ''; query.isEnterprise = ''; query.enabled = ''
activeTab.value = 'all'
query.page = 1
load()
}
@ -147,12 +165,8 @@ async function toggleEnabled(row: FrontUser) {
const next = row.enabled === 1 ? 0 : 1
const action = next === 1 ? '启用' : '禁用'
try {
await ElMessageBox.confirm(`确认${action}用户「${row.nickName}」?`, `${action}确认`, {
type: 'warning'
})
} catch {
return
}
await ElMessageBox.confirm(`确认${action}用户「${row.nickName}」?`, `${action}确认`, { type: 'warning' })
} catch { return }
try {
await httpPost('/user/user/update', { id: row.id, enabled: next })
ElMessage.success(`${action}成功`)
@ -164,12 +178,8 @@ async function toggleEnabled(row: FrontUser) {
async function deleteUser(row: FrontUser) {
try {
await ElMessageBox.confirm(`确认删除用户「${row.nickName}」?该操作不可恢复`, '删除确认', {
type: 'warning'
})
} catch {
return
}
await ElMessageBox.confirm(`确认删除用户「${row.nickName}」?该操作不可恢复`, '删除确认', { type: 'warning' })
} catch { return }
try {
await httpPost('/user/user/delete', { id: row.id })
ElMessage.success('删除成功')
@ -185,21 +195,21 @@ onMounted(load)
<template>
<div class="rr-page">
<PageHeader title="用户列表" :sub="`管理前台注册用户,共 ${total} 个账号`">
<el-button type="primary" :icon="'Plus'" @click="ElMessageBox.alert('导出功能待 W17 完善', '提示', { type: 'info' })">导出</el-button>
<el-button type="primary" @click="ElMessageBox.alert('新增用户功能待 W17 完善', '提示', { type: 'info' })">新增</el-button>
<el-button @click="ElMessageBox.alert('导出功能待 W17 完善', '提示', { type: 'info' })">导出</el-button>
<el-button type="primary" @click="ElMessageBox.alert('新增用户功能待 W17 完善', '提示', { type: 'info' })">+ 新增用户</el-button>
</PageHeader>
<ListTabs v-model="activeTab" :items="tabItems" @change="onTabChange" />
<SearchBar
v-model:filters="query"
:fields="[
{ key: 'userId', label: '用户ID', type: 'text', placeholder: '用户ID' },
{ key: 'nickName', label: '用户昵称', type: 'text', placeholder: '用户昵称' },
{ key: 'mobile', label: '手机号', type: 'text', placeholder: '手机号' },
{ key: 'isEnterprise', label: '是否企业认证', type: 'select', placeholder: '是否企业认证',
options: [{ label: '是', value: '1' }, { label: '否', value: '0' }] },
{ key: 'enterpriseName', label: '企业名称', type: 'text', placeholder: '企业名称' },
{ key: 'enabled', label: '禁用标识', type: 'select', placeholder: '禁用标识',
options: [{ label: '正常', value: '0' }, { label: '禁用', value: '1' }] }
{ key: 'isEnterprise', label: '是否企业认证', type: 'select', placeholder: '所有认证',
options: [{ label: '已认证', value: '1' }, { label: '未认证', value: '0' }] },
{ key: 'enterpriseName', label: '企业名称', type: 'text', placeholder: '企业名称' }
]"
@search="onSearch"
@reset="onReset"
@ -214,7 +224,7 @@ onMounted(load)
:limit="query.limit"
:page-sizes="[10, 20, 50, 100]"
:show-pagination="true"
:carded="true"
:borderless="true"
row-key="id"
empty-text="暂无用户"
@page-change="onPageChange"
@ -240,7 +250,6 @@ onMounted(load)
<style scoped lang="scss">
.rr-page { max-width: 100%; }
.user-header { width: 32px; height: 32px; border-radius: 50%; vertical-align: middle; }
.rr-actions {
display: inline-flex;
align-items: center;