feat: 完善用户列表功能和次数设置弹窗
- 为用户列表添加mock数据,包含8个用户的完整信息 - 支持手机号和邮箱搜索功能 - 修改操作列,只显示详情和次数设置按钮 - 新增次数设置弹窗,包含剩余次数、类型选择、体验次数调整和备注功能 - 完善弹窗交互逻辑和样式设计
This commit is contained in:
parent
c66d0dffde
commit
85ced0bf36
@ -40,7 +40,111 @@ export default {
|
|||||||
// auditlog
|
// auditlog
|
||||||
getAuditLogList: (params = {}) => request.get('/auditlog/list', { params }),
|
getAuditLogList: (params = {}) => request.get('/auditlog/list', { params }),
|
||||||
// app users (客户端用户管理) - 使用现有的后端接口
|
// app users (客户端用户管理) - 使用现有的后端接口
|
||||||
getAppUserList: (params = {}) => request.get('/app-user/list', { params }),
|
getAppUserList: (params = {}) => {
|
||||||
|
// Mock 数据
|
||||||
|
const mockUsers = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
phone: '13800138001',
|
||||||
|
email: 'zhang.wei@example.com',
|
||||||
|
created_at: '2024-01-15T10:30:00Z',
|
||||||
|
last_login: '2024-11-13T09:15:00Z',
|
||||||
|
is_active: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
phone: '13800138002',
|
||||||
|
email: 'li.ming@example.com',
|
||||||
|
created_at: '2024-02-20T14:20:00Z',
|
||||||
|
last_login: '2024-11-12T16:45:00Z',
|
||||||
|
is_active: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
phone: '13800138003',
|
||||||
|
email: 'wang.fang@example.com',
|
||||||
|
created_at: '2024-03-10T08:45:00Z',
|
||||||
|
last_login: null,
|
||||||
|
is_active: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
phone: '13800138004',
|
||||||
|
email: 'chen.jun@example.com',
|
||||||
|
created_at: '2024-04-05T11:30:00Z',
|
||||||
|
last_login: '2024-11-10T13:20:00Z',
|
||||||
|
is_active: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
phone: '13800138005',
|
||||||
|
email: 'liu.xia@example.com',
|
||||||
|
created_at: '2024-05-12T16:15:00Z',
|
||||||
|
last_login: '2024-11-11T10:30:00Z',
|
||||||
|
is_active: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
phone: '13800138006',
|
||||||
|
email: null,
|
||||||
|
created_at: '2024-06-18T09:00:00Z',
|
||||||
|
last_login: null,
|
||||||
|
is_active: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
phone: '13800138007',
|
||||||
|
email: 'zhao.lei@example.com',
|
||||||
|
created_at: '2024-07-22T12:45:00Z',
|
||||||
|
last_login: '2024-11-13T08:30:00Z',
|
||||||
|
is_active: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
phone: '13800138008',
|
||||||
|
email: 'sun.mei@example.com',
|
||||||
|
created_at: '2024-08-30T15:20:00Z',
|
||||||
|
last_login: '2024-11-09T14:15:00Z',
|
||||||
|
is_active: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 模拟分页和搜索
|
||||||
|
let filteredUsers = [...mockUsers]
|
||||||
|
|
||||||
|
// 手机号搜索
|
||||||
|
if (params.phone) {
|
||||||
|
filteredUsers = filteredUsers.filter(user =>
|
||||||
|
user.phone.includes(params.phone)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 邮箱搜索
|
||||||
|
if (params.email) {
|
||||||
|
filteredUsers = filteredUsers.filter(user =>
|
||||||
|
user.email && user.email.includes(params.email)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页处理
|
||||||
|
const page = params.page || 1
|
||||||
|
const pageSize = params.page_size || 10
|
||||||
|
const startIndex = (page - 1) * pageSize
|
||||||
|
const endIndex = startIndex + pageSize
|
||||||
|
const paginatedUsers = filteredUsers.slice(startIndex, endIndex)
|
||||||
|
|
||||||
|
// 返回 Promise 模拟异步请求
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve({
|
||||||
|
data: paginatedUsers,
|
||||||
|
total: filteredUsers.length,
|
||||||
|
page: page,
|
||||||
|
page_size: pageSize
|
||||||
|
})
|
||||||
|
}, 300) // 模拟网络延迟
|
||||||
|
})
|
||||||
|
},
|
||||||
getAppUserById: (params = {}) => request.get('/app-user/detail', { params }),
|
getAppUserById: (params = {}) => request.get('/app-user/detail', { params }),
|
||||||
createAppUser: (data = {}) => request.post('/app-user/register', data),
|
createAppUser: (data = {}) => request.post('/app-user/register', data),
|
||||||
updateAppUser: (data = {}) => request.post('/app-user/update', data),
|
updateAppUser: (data = {}) => request.post('/app-user/update', data),
|
||||||
|
|||||||
@ -9,6 +9,12 @@ import {
|
|||||||
NSwitch,
|
NSwitch,
|
||||||
NTag,
|
NTag,
|
||||||
NPopconfirm,
|
NPopconfirm,
|
||||||
|
NModal,
|
||||||
|
NCard,
|
||||||
|
NSelect,
|
||||||
|
NInputNumber,
|
||||||
|
NText,
|
||||||
|
NDivider,
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
|
|
||||||
import CommonPage from '@/components/page/CommonPage.vue'
|
import CommonPage from '@/components/page/CommonPage.vue'
|
||||||
@ -27,6 +33,23 @@ const $table = ref(null)
|
|||||||
const queryItems = ref({})
|
const queryItems = ref({})
|
||||||
const vPermission = resolveDirective('permission')
|
const vPermission = resolveDirective('permission')
|
||||||
|
|
||||||
|
// 次数设置弹窗相关状态
|
||||||
|
const limitModalVisible = ref(false)
|
||||||
|
const limitForm = ref({
|
||||||
|
remainingCount: 0,
|
||||||
|
type: '免费体验',
|
||||||
|
experienceCount: 1,
|
||||||
|
notes: ''
|
||||||
|
})
|
||||||
|
const currentUser = ref(null)
|
||||||
|
|
||||||
|
// 类型选项
|
||||||
|
const typeOptions = [
|
||||||
|
{ label: '免费体验', value: '免费体验' },
|
||||||
|
{ label: '付费用户', value: '付费用户' },
|
||||||
|
{ label: 'VIP用户', value: 'VIP用户' }
|
||||||
|
]
|
||||||
|
|
||||||
const {
|
const {
|
||||||
modalVisible,
|
modalVisible,
|
||||||
modalTitle,
|
modalTitle,
|
||||||
@ -156,36 +179,78 @@ const columns = [
|
|||||||
NButton,
|
NButton,
|
||||||
{
|
{
|
||||||
size: 'small',
|
size: 'small',
|
||||||
type: row.is_active ? 'warning' : 'success',
|
type: 'primary',
|
||||||
style: 'margin-right: 8px;',
|
style: 'margin-right: 8px;',
|
||||||
onClick: () => handleUpdateStatus(row),
|
onClick: () => handleSetLimit(row),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
default: () => (row.is_active ? '冻结' : '解冻'),
|
default: () => '次数设置',
|
||||||
icon: renderIcon(row.is_active ? 'material-symbols:block' : 'material-symbols:check-circle', { size: 16 }),
|
icon: renderIcon('material-symbols:settings', { size: 16 }),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
[[vPermission, 'post/api/v1/app_user/update']]
|
[[vPermission, 'post/api/v1/app_user/set_limit']]
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
// 修改用户状态
|
|
||||||
async function handleUpdateStatus(row) {
|
|
||||||
if (!row.id) return
|
|
||||||
|
|
||||||
// 由于现有后端API限制,暂时只显示提示信息
|
|
||||||
$message.info('用户状态修改功能需要后端支持')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查看用户详情
|
// 查看用户详情
|
||||||
function handleViewDetail(row) {
|
function handleViewDetail(row) {
|
||||||
// 这里可以跳转到详情页面或打开详情弹窗
|
// 这里可以跳转到详情页面或打开详情弹窗
|
||||||
$message.info('查看用户详情功能待实现')
|
$message.info('查看用户详情功能待实现')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 次数设置
|
||||||
|
function handleSetLimit(row) {
|
||||||
|
currentUser.value = row
|
||||||
|
// 初始化表单数据,这里可以从后端获取用户当前的次数设置
|
||||||
|
limitForm.value = {
|
||||||
|
remainingCount: row.remaining_count || 0,
|
||||||
|
type: row.user_type || '免费体验',
|
||||||
|
experienceCount: row.experience_count || 1,
|
||||||
|
notes: row.notes || ''
|
||||||
|
}
|
||||||
|
limitModalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存次数设置
|
||||||
|
async function handleSaveLimitSetting() {
|
||||||
|
try {
|
||||||
|
// 这里调用API保存次数设置
|
||||||
|
const data = {
|
||||||
|
user_id: currentUser.value.id,
|
||||||
|
...limitForm.value
|
||||||
|
}
|
||||||
|
// await api.setUserLimit(data)
|
||||||
|
$message.success('次数设置保存成功')
|
||||||
|
limitModalVisible.value = false
|
||||||
|
$table.value?.handleSearch()
|
||||||
|
} catch (error) {
|
||||||
|
$message.error('保存失败: ' + error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消次数设置
|
||||||
|
function handleCancelLimitSetting() {
|
||||||
|
limitModalVisible.value = false
|
||||||
|
limitForm.value = {
|
||||||
|
remainingCount: 0,
|
||||||
|
type: '免费体验',
|
||||||
|
experienceCount: 1,
|
||||||
|
notes: ''
|
||||||
|
}
|
||||||
|
currentUser.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 体验次数增减
|
||||||
|
function handleExperienceCountChange(delta) {
|
||||||
|
const newCount = limitForm.value.experienceCount + delta
|
||||||
|
if (newCount >= 0) {
|
||||||
|
limitForm.value.experienceCount = newCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const validateForm = {
|
const validateForm = {
|
||||||
phone: [
|
phone: [
|
||||||
{
|
{
|
||||||
@ -211,9 +276,9 @@ const validateForm = {
|
|||||||
<template>
|
<template>
|
||||||
<CommonPage show-footer title="用户列表">
|
<CommonPage show-footer title="用户列表">
|
||||||
<template #action>
|
<template #action>
|
||||||
<NButton v-permission="'post/api/v1/app_user/create'" type="primary" @click="handleAdd">
|
<!-- <NButton v-permission="'post/api/v1/app_user/create'" type="primary" @click="handleAdd">
|
||||||
<TheIcon icon="material-symbols:add" :size="18" class="mr-5" />新建用户
|
<TheIcon icon="material-symbols:add" :size="18" class="mr-5" />新建用户
|
||||||
</NButton>
|
</NButton> -->
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 表格 -->
|
<!-- 表格 -->
|
||||||
@ -270,5 +335,130 @@ const validateForm = {
|
|||||||
</NFormItem>
|
</NFormItem>
|
||||||
</NForm>
|
</NForm>
|
||||||
</CrudModal>
|
</CrudModal>
|
||||||
|
|
||||||
|
<!-- 次数设置弹窗 -->
|
||||||
|
<NModal
|
||||||
|
v-model:show="limitModalVisible"
|
||||||
|
preset="card"
|
||||||
|
title="估值设置"
|
||||||
|
style="width: 500px;"
|
||||||
|
:bordered="false"
|
||||||
|
size="huge"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
>
|
||||||
|
<div class="limit-setting-form">
|
||||||
|
<!-- 剩余估值次数 -->
|
||||||
|
<div class="form-row">
|
||||||
|
<span class="label">剩余估值次数:</span>
|
||||||
|
<span class="value">{{ limitForm.remainingCount }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<NDivider style="margin: 16px 0;" />
|
||||||
|
|
||||||
|
<!-- 类型选择 -->
|
||||||
|
<div class="form-row">
|
||||||
|
<span class="label">类型:</span>
|
||||||
|
<NSelect
|
||||||
|
v-model:value="limitForm.type"
|
||||||
|
:options="typeOptions"
|
||||||
|
style="width: 120px;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 体验次数 -->
|
||||||
|
<div class="form-row">
|
||||||
|
<span class="label">体验次数:</span>
|
||||||
|
<div class="count-control">
|
||||||
|
<NButton
|
||||||
|
size="small"
|
||||||
|
@click="handleExperienceCountChange(-1)"
|
||||||
|
:disabled="limitForm.experienceCount <= 0"
|
||||||
|
>
|
||||||
|
-
|
||||||
|
</NButton>
|
||||||
|
<span class="count-value">{{ limitForm.experienceCount }}</span>
|
||||||
|
<NButton
|
||||||
|
size="small"
|
||||||
|
@click="handleExperienceCountChange(1)"
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 备注 -->
|
||||||
|
<div class="form-row notes-row">
|
||||||
|
<span class="label">备注:</span>
|
||||||
|
</div>
|
||||||
|
<NInput
|
||||||
|
v-model:value="limitForm.notes"
|
||||||
|
type="textarea"
|
||||||
|
placeholder="请输入备注信息"
|
||||||
|
:rows="4"
|
||||||
|
style="margin-top: 8px;"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<div class="action-buttons">
|
||||||
|
<NButton @click="handleCancelLimitSetting">
|
||||||
|
取消
|
||||||
|
</NButton>
|
||||||
|
<NButton type="primary" @click="handleSaveLimitSetting">
|
||||||
|
确定
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</NModal>
|
||||||
</CommonPage>
|
</CommonPage>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.limit-setting-form {
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notes-row {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
min-width: 100px;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.count-control {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.count-value {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
min-width: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 24px;
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user