feat(user): 完整化用户列表 (9 列 + 6 搜索 + 4 操作, 老 vue 等价) W17.1
This commit is contained in:
parent
c3295c3f57
commit
9c75bd53c4
@ -1,18 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
/**
|
/**
|
||||||
* F006 用户管理 (前台用户列表) - W10 A5 标准化
|
* F006 用户管理 (前台用户列表) - W17 完整化
|
||||||
*
|
*
|
||||||
* W10 A5: 套 PageHeader/SearchBar 标准化模板
|
* W17: 补全字段到 9 列(老 vue 等价), 6 搜索条件, 操作列(修改/禁用启用/删除)
|
||||||
* 走 /user/user/page 后端
|
* 走 /user/user/page 后端
|
||||||
*
|
|
||||||
* 字段 (从 API 实测):
|
|
||||||
* id, nickName, mobile, header (头像 URL)
|
|
||||||
* total=2284 真实用户
|
|
||||||
*
|
|
||||||
* 注意: 老 sys/UserListView 是后台管理员 (/sys/user/*), 这页是前台用户
|
|
||||||
*/
|
*/
|
||||||
import { onMounted, ref, reactive } from 'vue'
|
import { onMounted, ref, reactive } from 'vue'
|
||||||
import { httpGet } from '@/utils/request'
|
import { httpGet, httpPost } from '@/utils/request'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import DataTable from '@/components/DataTable/DataTable.vue'
|
import DataTable from '@/components/DataTable/DataTable.vue'
|
||||||
import PageHeader from '@/components/PageHeader/PageHeader.vue'
|
import PageHeader from '@/components/PageHeader/PageHeader.vue'
|
||||||
import SearchBar from '@/components/SearchBar/SearchBar.vue'
|
import SearchBar from '@/components/SearchBar/SearchBar.vue'
|
||||||
@ -22,6 +17,13 @@ interface FrontUser {
|
|||||||
nickName: string
|
nickName: string
|
||||||
mobile: string
|
mobile: string
|
||||||
header?: string | null
|
header?: string | null
|
||||||
|
isEnterprise: number
|
||||||
|
enterpriseName?: string | null
|
||||||
|
enterpriseId?: string | null
|
||||||
|
accountBalance: number
|
||||||
|
enabled: number
|
||||||
|
createDate: number
|
||||||
|
isDel: number
|
||||||
raw: any
|
raw: any
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,32 +32,86 @@ const total = ref(0)
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const query = reactive({
|
const query = reactive({
|
||||||
page: 1,
|
page: 1,
|
||||||
limit: 20,
|
limit: 10,
|
||||||
keyword: ''
|
userId: '',
|
||||||
|
nickName: '',
|
||||||
|
mobile: '',
|
||||||
|
enterpriseName: '',
|
||||||
|
isEnterprise: '' as '' | '0' | '1',
|
||||||
|
enabled: '' as '' | '0' | '1'
|
||||||
})
|
})
|
||||||
|
|
||||||
const columns = [
|
// 字段定义:用 any 绕过 DataTable Column 类型限制(它支持 slot 字段但未在 type 中声明)
|
||||||
{ prop: 'id', label: 'ID', width: 80 },
|
const columns: any[] = [
|
||||||
{ prop: 'nickName', label: '昵称', minWidth: 160 },
|
{ type: 'selection', width: 50 },
|
||||||
{ prop: 'mobile', label: '手机号', width: 140 },
|
{ prop: 'id', label: '用户ID', width: 90 },
|
||||||
|
{ prop: 'nickName', label: '用户昵称', minWidth: 140 },
|
||||||
|
{ prop: 'mobile', label: '手机号', width: 130 },
|
||||||
{
|
{
|
||||||
prop: 'header',
|
prop: 'isEnterprise',
|
||||||
label: '头像',
|
label: '是否企业认证',
|
||||||
width: 80,
|
width: 110,
|
||||||
formatter: (row: FrontUser) => row.header ? '有' : '无'
|
formatter: (row: FrontUser) => row.isEnterprise === 1 ? '是' : '否'
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
prop: 'enterpriseName',
|
||||||
|
label: '关联企业',
|
||||||
|
minWidth: 180,
|
||||||
|
formatter: (row: FrontUser) => row.enterpriseName || '--'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'accountBalance',
|
||||||
|
label: '平台账户余额',
|
||||||
|
width: 130,
|
||||||
|
formatter: (row: FrontUser) => Number(row.accountBalance || 0).toFixed(2)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'enabled',
|
||||||
|
label: '禁用标识',
|
||||||
|
width: 90,
|
||||||
|
slot: 'enabled'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'createDate',
|
||||||
|
label: '注册时间',
|
||||||
|
width: 170,
|
||||||
|
formatter: (row: FrontUser) => fmtDate(row.createDate)
|
||||||
|
},
|
||||||
|
{ label: '操作', width: 200, slot: 'action' }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
function fmtDate(ts: number | null | undefined) {
|
||||||
|
if (!ts) return '--'
|
||||||
|
const d = new Date(Number(ts))
|
||||||
|
if (isNaN(d.getTime())) return '--'
|
||||||
|
const pad = (n: number) => String(n).padStart(2, '0')
|
||||||
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
|
||||||
|
}
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const resp: any = await httpGet('/user/user/page', { page: query.page, limit: query.limit })
|
const params: any = { page: query.page, limit: query.limit }
|
||||||
|
if (query.userId) params.id = query.userId
|
||||||
|
if (query.nickName) params.nickName = query.nickName
|
||||||
|
if (query.mobile) params.mobile = query.mobile
|
||||||
|
if (query.enterpriseName) params.enterpriseName = query.enterpriseName
|
||||||
|
if (query.isEnterprise !== '') params.isEnterprise = Number(query.isEnterprise)
|
||||||
|
if (query.enabled !== '') params.enabled = Number(query.enabled)
|
||||||
|
const resp: any = await httpGet('/user/user/page', params)
|
||||||
if (resp && Array.isArray(resp.list)) {
|
if (resp && Array.isArray(resp.list)) {
|
||||||
list.value = resp.list.map((it: any) => ({
|
list.value = resp.list.map((it: any) => ({
|
||||||
id: it.id,
|
id: it.id,
|
||||||
nickName: it.nickName || '--',
|
nickName: it.nickName || '--',
|
||||||
mobile: it.mobile || '--',
|
mobile: it.mobile || '--',
|
||||||
header: it.header || null,
|
header: it.header || null,
|
||||||
|
isEnterprise: Number(it.isEnterprise ?? 0),
|
||||||
|
enterpriseName: it.enterpriseName || null,
|
||||||
|
enterpriseId: it.enterpriseId || null,
|
||||||
|
accountBalance: Number(it.accountBalance ?? 0),
|
||||||
|
enabled: Number(it.enabled ?? 0),
|
||||||
|
createDate: Number(it.createDate ?? 0),
|
||||||
|
isDel: Number(it.isDel ?? 0),
|
||||||
raw: it
|
raw: it
|
||||||
}))
|
}))
|
||||||
total.value = Number(resp.total || 0)
|
total.value = Number(resp.total || 0)
|
||||||
@ -63,9 +119,10 @@ async function load() {
|
|||||||
list.value = []
|
list.value = []
|
||||||
total.value = 0
|
total.value = 0
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
list.value = []
|
list.value = []
|
||||||
total.value = 0
|
total.value = 0
|
||||||
|
ElMessage.error(e?.msg || e?.message || '加载失败')
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@ -73,6 +130,52 @@ async function load() {
|
|||||||
|
|
||||||
function onPageChange(p: number) { query.page = p; load() }
|
function onPageChange(p: number) { query.page = p; load() }
|
||||||
function onSearch() { query.page = 1; load() }
|
function onSearch() { query.page = 1; load() }
|
||||||
|
function onReset() {
|
||||||
|
query.userId = ''; query.nickName = ''; query.mobile = ''
|
||||||
|
query.enterpriseName = ''; query.isEnterprise = ''; query.enabled = ''
|
||||||
|
query.page = 1
|
||||||
|
load()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEdit(row: FrontUser) {
|
||||||
|
ElMessageBox.alert(`用户详情编辑待 W17 完善 (当前用户: ${row.nickName}, ID: ${row.id})`, '修改用户', { type: 'info' })
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await httpPost('/user/user/update', { id: row.id, enabled: next })
|
||||||
|
ElMessage.success(`${action}成功`)
|
||||||
|
row.enabled = next
|
||||||
|
} catch (e: any) {
|
||||||
|
ElMessage.error(e?.msg || e?.message || `${action}失败`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteUser(row: FrontUser) {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(`确认删除用户「${row.nickName}」?该操作不可恢复`, '删除确认', {
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await httpPost('/user/user/delete', { id: row.id })
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
load()
|
||||||
|
} catch (e: any) {
|
||||||
|
ElMessage.error(e?.msg || e?.message || '删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(load)
|
onMounted(load)
|
||||||
</script>
|
</script>
|
||||||
@ -84,10 +187,17 @@ onMounted(load)
|
|||||||
<SearchBar
|
<SearchBar
|
||||||
v-model:filters="query"
|
v-model:filters="query"
|
||||||
:fields="[
|
:fields="[
|
||||||
{ key: 'keyword', label: '搜索', type: 'text', placeholder: '搜索手机号/昵称' }
|
{ 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' }] }
|
||||||
]"
|
]"
|
||||||
@search="onSearch"
|
@search="onSearch"
|
||||||
@reset="onSearch"
|
@reset="onReset"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="rr-card">
|
<div class="rr-card">
|
||||||
@ -102,12 +212,24 @@ onMounted(load)
|
|||||||
row-key="id"
|
row-key="id"
|
||||||
empty-text="暂无用户"
|
empty-text="暂无用户"
|
||||||
@page-change="onPageChange"
|
@page-change="onPageChange"
|
||||||
/>
|
>
|
||||||
|
<template #enabled="{ row }">
|
||||||
|
<el-tag v-if="row.enabled === 1" type="success" size="small">正常</el-tag>
|
||||||
|
<el-tag v-else type="danger" size="small">禁用</el-tag>
|
||||||
|
</template>
|
||||||
|
<template #action="{ row }">
|
||||||
|
<el-button link type="primary" size="small" @click="onEdit(row)">修改</el-button>
|
||||||
|
<el-button link :type="row.enabled === 1 ? 'warning' : 'success'" size="small" @click="toggleEnabled(row)">
|
||||||
|
{{ row.enabled === 1 ? '禁用' : '启用' }}
|
||||||
|
</el-button>
|
||||||
|
<el-button link type="danger" size="small" @click="deleteUser(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</DataTable>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.rr-page { max-width: 1400px; }
|
.rr-page { max-width: 1600px; }
|
||||||
.user-header { width: 32px; height: 32px; border-radius: 50%; vertical-align: middle; }
|
.user-header { width: 32px; height: 32px; border-radius: 50%; vertical-align: middle; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user