refactor(F006): 用户列表简化 - 按用户反馈恢复原字段单行无头像, 仅保留UI美化(胶囊/货币化/等宽/批量栏)
This commit is contained in:
parent
4c27e6b4b2
commit
0519de9428
@ -1,11 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
/**
|
/**
|
||||||
* F006 用户管理 (前台用户列表) - 2026-08-18 按企业列表 UI 标准升级
|
* F006 用户管理 (前台用户列表) - 2026-08-18 v2 简化版
|
||||||
*
|
*
|
||||||
* 按 ui-preview-v2.html 模板: 列 + 搜索 + 操作 + 状态 tab (W17 功能全部保留)
|
* 用户反馈: 列表内容简化 - 单行显示、无头像、字段保持原样 (仅 UI 美化)
|
||||||
* 后端端点: GET /user/user/page (搜索+分页) / GET /user/user/export (Excel)
|
* 字段: 用户ID / 用户昵称 / 手机号 / 是否企业认证 / 关联企业 / 平台账户余额 / 禁用标识 / 注册时间 / 操作
|
||||||
* POST /user/user/update (禁用/启用) / POST /user/user/delete (删除)
|
* 后端端点: GET /user/user/page / GET /user/user/export / POST /user/user/update / POST /user/user/delete
|
||||||
* 新增: 用户头像单元(渐变/图片) / 两行信息列 / 状态胶囊(全局 --st-* 变量暗色自适应) / 批量操作栏
|
* 保留功能: tab 全部/正常/已禁用(stats 与 tab 解耦) / 搜索 / 导出 / 新建编辑(UserFormDialog) / 批量操作
|
||||||
*/
|
*/
|
||||||
import { onMounted, ref, reactive, computed } from 'vue'
|
import { onMounted, ref, reactive, computed } from 'vue'
|
||||||
import { http, httpGet, httpPost } from '@/utils/request'
|
import { http, httpGet, httpPost } from '@/utils/request'
|
||||||
@ -57,39 +57,29 @@ const query = reactive({
|
|||||||
enabled: '' as '' | '0' | '1'
|
enabled: '' as '' | '0' | '1'
|
||||||
})
|
})
|
||||||
|
|
||||||
// 头像品牌渐变色组 (无 header 图片时交替使用, 与企业列表同源风格)
|
|
||||||
const AVATAR_GRADIENTS = [
|
|
||||||
'linear-gradient(135deg, #0b5394, #6fb1e8)',
|
|
||||||
'linear-gradient(135deg, #2563eb, #93c5fd)',
|
|
||||||
'linear-gradient(135deg, #0e7490, #67e8f9)',
|
|
||||||
'linear-gradient(135deg, #7c3aed, #c4b5fd)',
|
|
||||||
'linear-gradient(135deg, #be123c, #fda4af)'
|
|
||||||
]
|
|
||||||
|
|
||||||
// 状态/认证胶囊色 (引用全局 --st-* 变量, 主题切换自动适配暗色)
|
// 状态/认证胶囊色 (引用全局 --st-* 变量, 主题切换自动适配暗色)
|
||||||
const ENABLED_META: Record<number, { label: string; color: string; bg: string }> = {
|
const ENABLED_META: Record<number, { label: string; color: string; bg: string }> = {
|
||||||
0: { label: '正常', color: 'var(--st-ok-fg)', bg: 'var(--st-ok-bg)' },
|
0: { label: '正常', color: 'var(--st-ok-fg)', bg: 'var(--st-ok-bg)' },
|
||||||
1: { label: '禁用', color: 'var(--st-err-fg)', bg: 'var(--st-err-bg)' }
|
1: { label: '禁用', color: 'var(--st-err-fg)', bg: 'var(--st-err-bg)' }
|
||||||
}
|
}
|
||||||
const CERT_META: Record<number, { label: string; color: string; bg: string }> = {
|
const CERT_META: Record<number, { label: string; color: string; bg: string }> = {
|
||||||
1: { label: '已认证', color: 'var(--st-ok-fg)', bg: 'var(--st-ok-bg)' },
|
1: { label: '是', color: 'var(--st-ok-fg)', bg: 'var(--st-ok-bg)' },
|
||||||
0: { label: '未认证', color: 'var(--st-lv4-fg)', bg: 'var(--st-lv4-bg)' }
|
0: { label: '否', color: 'var(--st-lv4-fg)', bg: 'var(--st-lv4-bg)' }
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns: any[] = [
|
const columns: any[] = [
|
||||||
{ type: 'selection', width: 48, fixed: 'left', align: 'center' },
|
{ type: 'selection', width: 48, fixed: 'left', align: 'center' },
|
||||||
{ label: '用户', minWidth: 200, slot: 'user' },
|
{ prop: 'id', label: '用户ID', width: 110, slot: 'id', align: 'center' },
|
||||||
|
{ prop: 'nickName', label: '用户昵称', minWidth: 140, slot: 'nickName' },
|
||||||
{ prop: 'mobile', label: '手机号', width: 140, slot: 'mobile' },
|
{ prop: 'mobile', label: '手机号', width: 140, slot: 'mobile' },
|
||||||
{ label: '企业认证', minWidth: 180, slot: 'enterprise' },
|
{ prop: 'isEnterprise', label: '是否企业认证', width: 120, slot: 'cert', align: 'center' },
|
||||||
{ prop: 'accountBalance', label: '平台余额', width: 140, slot: 'balance', align: 'right' },
|
{ prop: 'enterpriseName', label: '关联企业', minWidth: 180, slot: 'enterpriseName' },
|
||||||
{ prop: 'enabled', label: '状态', width: 100, slot: 'enabled', align: 'center' },
|
{ prop: 'accountBalance', label: '平台账户余额', width: 150, slot: 'balance', align: 'right' },
|
||||||
|
{ prop: 'enabled', label: '禁用标识', width: 100, slot: 'enabled', align: 'center' },
|
||||||
{ label: '注册时间', width: 165, slot: 'createDate' },
|
{ label: '注册时间', width: 165, slot: 'createDate' },
|
||||||
{ label: '操作', width: 170, slot: 'action', fixed: 'right', align: 'center' }
|
{ label: '操作', width: 170, slot: 'action', fixed: 'right', align: 'center' }
|
||||||
]
|
]
|
||||||
|
|
||||||
function avatarStyle(row: FrontUser, index: number) {
|
|
||||||
return { background: AVATAR_GRADIENTS[index % AVATAR_GRADIENTS.length] }
|
|
||||||
}
|
|
||||||
function fmtMoney(n: number): string {
|
function fmtMoney(n: number): string {
|
||||||
const v = Number(n || 0)
|
const v = Number(n || 0)
|
||||||
return `¥ ${v.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
|
return `¥ ${v.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
|
||||||
@ -103,8 +93,7 @@ function fmtDate(ts: number | null | undefined) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建除 enabled (tab) 外的搜索参数 (用于 stats 查询)
|
* 构建除 enabled (tab) 外的搜索参数 (用于 stats 查询) — stats 与 tab 解耦
|
||||||
* stats 必须与 tab 解耦, 只反映当前搜索条件下的全量计数
|
|
||||||
*/
|
*/
|
||||||
function buildSearchParams() {
|
function buildSearchParams() {
|
||||||
const params: any = {}
|
const params: any = {}
|
||||||
@ -151,9 +140,7 @@ async function loadList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 加载 stats (normal/disabled 全量计数) — 与 tab 无关 */
|
||||||
* 加载 stats (normal/disabled 全量计数) — 与 tab 无关, 只跟搜索条件关联
|
|
||||||
*/
|
|
||||||
async function loadStats() {
|
async function loadStats() {
|
||||||
try {
|
try {
|
||||||
const baseParams = buildSearchParams()
|
const baseParams = buildSearchParams()
|
||||||
@ -215,9 +202,7 @@ function onDialogSuccess() {
|
|||||||
load()
|
load()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 导出当前搜索条件下的用户列表为 Excel */
|
||||||
* 导出当前搜索条件下的用户列表为 Excel
|
|
||||||
*/
|
|
||||||
async function exportExcel() {
|
async function exportExcel() {
|
||||||
exporting.value = true
|
exporting.value = true
|
||||||
try {
|
try {
|
||||||
@ -248,7 +233,7 @@ async function exportExcel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 批量操作 (对齐企业列表批量栏) ==========
|
// ========== 批量操作 ==========
|
||||||
async function batchToggle(next: number) {
|
async function batchToggle(next: number) {
|
||||||
const action = next === 1 ? '禁用' : '启用'
|
const action = next === 1 ? '禁用' : '启用'
|
||||||
if (!selectedIds.value.length) { ElMessage.warning('请先选择用户'); return }
|
if (!selectedIds.value.length) { ElMessage.warning('请先选择用户'); return }
|
||||||
@ -326,7 +311,7 @@ onMounted(load)
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="rr-list-card">
|
<div class="rr-list-card">
|
||||||
<!-- 批量操作栏 (对齐企业列表) -->
|
<!-- 批量操作栏 -->
|
||||||
<div class="batch-bar" :class="{ 'has-selection': selectedCount > 0 }">
|
<div class="batch-bar" :class="{ 'has-selection': selectedCount > 0 }">
|
||||||
<span v-if="selectedCount > 0" class="batch-count">
|
<span v-if="selectedCount > 0" class="batch-count">
|
||||||
已选 <strong>{{ selectedCount }}</strong> 个用户
|
已选 <strong>{{ selectedCount }}</strong> 个用户
|
||||||
@ -355,18 +340,14 @@ onMounted(load)
|
|||||||
@limit-change="onLimitChange"
|
@limit-change="onLimitChange"
|
||||||
@selection-change="onSelectionChange"
|
@selection-change="onSelectionChange"
|
||||||
>
|
>
|
||||||
<!-- 用户单元: 头像 + 昵称 + 用户ID -->
|
<!-- 用户ID: 等宽小字 -->
|
||||||
<template #user="{ row, index }">
|
<template #id="{ row }">
|
||||||
<div class="user-cell">
|
<span class="id-cell">{{ row.id }}</span>
|
||||||
<span class="user-avatar" :style="avatarStyle(row, index)">
|
</template>
|
||||||
<el-image v-if="row.header" :src="row.header" fit="cover" class="user-avatar-img" />
|
|
||||||
<template v-else>{{ (row.nickName || '用').charAt(0) }}</template>
|
<!-- 用户昵称: 单行 -->
|
||||||
</span>
|
<template #nickName="{ row }">
|
||||||
<span class="user-text">
|
<span class="nick-cell">{{ row.nickName }}</span>
|
||||||
<span class="user-name">{{ row.nickName }}</span>
|
|
||||||
<span class="user-sub">ID: {{ row.id }}</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 手机号 -->
|
<!-- 手机号 -->
|
||||||
@ -374,22 +355,24 @@ onMounted(load)
|
|||||||
<span class="phone-cell"><el-icon><Phone /></el-icon>{{ row.mobile }}</span>
|
<span class="phone-cell"><el-icon><Phone /></el-icon>{{ row.mobile }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 企业认证: 胶囊 + 企业名两行 -->
|
<!-- 是否企业认证: 胶囊 -->
|
||||||
<template #enterprise="{ row }">
|
<template #cert="{ row }">
|
||||||
<div class="two-line">
|
<span class="chip" :style="{ color: CERT_META[row.isEnterprise].color, background: CERT_META[row.isEnterprise].bg }">
|
||||||
<span class="chip" :style="{ color: CERT_META[row.isEnterprise].color, background: CERT_META[row.isEnterprise].bg }">
|
{{ CERT_META[row.isEnterprise].label }}
|
||||||
{{ CERT_META[row.isEnterprise].label }}
|
</span>
|
||||||
</span>
|
|
||||||
<span class="line-sub">{{ row.enterpriseName || '--' }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 平台余额 -->
|
<!-- 关联企业: 单行 -->
|
||||||
|
<template #enterpriseName="{ row }">
|
||||||
|
<span class="text-ellipsis">{{ row.enterpriseName || '--' }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 平台余额: 货币化 -->
|
||||||
<template #balance="{ row }">
|
<template #balance="{ row }">
|
||||||
<span class="balance-cell">{{ fmtMoney(row.accountBalance) }}</span>
|
<span class="balance-cell">{{ fmtMoney(row.accountBalance) }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 状态: 圆点胶囊 -->
|
<!-- 禁用标识: 圆点胶囊 -->
|
||||||
<template #enabled="{ row }">
|
<template #enabled="{ row }">
|
||||||
<span class="status-tag" :style="{ color: ENABLED_META[row.enabled].color, background: ENABLED_META[row.enabled].bg }">
|
<span class="status-tag" :style="{ color: ENABLED_META[row.enabled].color, background: ENABLED_META[row.enabled].bg }">
|
||||||
<span class="status-dot"></span>{{ ENABLED_META[row.enabled].label }}
|
<span class="status-dot"></span>{{ ENABLED_META[row.enabled].label }}
|
||||||
@ -435,29 +418,22 @@ onMounted(load)
|
|||||||
.batch-hint { font-size: 12px; color: var(--ink-3); }
|
.batch-hint { font-size: 12px; color: var(--ink-3); }
|
||||||
.batch-actions { margin-left: auto; display: flex; gap: 4px; flex-wrap: wrap; }
|
.batch-actions { margin-left: auto; display: flex; gap: 4px; flex-wrap: wrap; }
|
||||||
|
|
||||||
/* ===== 用户单元 ===== */
|
/* ===== 单元格 (单行) ===== */
|
||||||
.user-cell { display: flex; align-items: center; gap: 10px; }
|
.id-cell {
|
||||||
.user-avatar {
|
font-family: 'SF Mono', Menlo, Consolas, monospace;
|
||||||
flex: 0 0 38px; width: 38px; height: 38px; border-radius: 10px;
|
font-size: 12px; color: var(--ink-3);
|
||||||
display: flex; align-items: center; justify-content: center;
|
|
||||||
color: #fff; font-size: 16px; font-weight: 700; flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
.user-avatar-img { width: 100%; height: 100%; border-radius: 10px; }
|
.nick-cell { font-size: 14px; font-weight: 600; color: var(--ink); }
|
||||||
.user-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
.text-ellipsis {
|
||||||
.user-name {
|
display: inline-block; max-width: 100%;
|
||||||
font-size: 14px; font-weight: 600; color: var(--ink);
|
|
||||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||||
|
vertical-align: middle; font-size: 13px; color: var(--ink);
|
||||||
}
|
}
|
||||||
.user-sub { font-size: 12px; color: var(--ink-3); font-family: 'SF Mono', Menlo, Consolas, monospace; }
|
|
||||||
|
|
||||||
/* ===== 单元格通用 ===== */
|
|
||||||
.two-line { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
|
|
||||||
.line-sub { font-size: 12px; color: var(--ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
||||||
.phone-cell { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; color: var(--ink); font-family: 'SF Mono', Menlo, Consolas, monospace; }
|
.phone-cell { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; color: var(--ink); font-family: 'SF Mono', Menlo, Consolas, monospace; }
|
||||||
.date-cell { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--ink-2); font-family: 'SF Mono', Menlo, Consolas, monospace; }
|
.date-cell { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--ink-2); font-family: 'SF Mono', Menlo, Consolas, monospace; }
|
||||||
.balance-cell { font-size: 13px; font-weight: 600; color: var(--ink); font-family: 'SF Mono', Menlo, Consolas, monospace; }
|
.balance-cell { font-size: 13px; font-weight: 600; color: var(--ink); font-family: 'SF Mono', Menlo, Consolas, monospace; }
|
||||||
|
|
||||||
/* ===== 胶囊 (对齐企业列表, 全局 --st-* 变量暗色自适应) ===== */
|
/* ===== 胶囊 (全局 --st-* 变量暗色自适应) ===== */
|
||||||
.chip {
|
.chip {
|
||||||
display: inline-flex; align-items: center; width: fit-content;
|
display: inline-flex; align-items: center; width: fit-content;
|
||||||
font-size: 12px; font-weight: 500; padding: 2px 12px; border-radius: 20px; white-space: nowrap;
|
font-size: 12px; font-weight: 500; padding: 2px 12px; border-radius: 20px; white-space: nowrap;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user