dman-web-admin/src/views/user/UserList.vue
2025-01-20 22:48:19 +08:00

668 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<page-container>
<div class="user-list">
<div class="table-header">
<h1>用户列表</h1>
</div>
<!-- 添加筛选区域 -->
<div class="table-filter">
<a-form layout="inline">
<a-form-item label="手机号">
<a-input
v-model:value="filterForm.phone"
placeholder="请输入手机号"
style="width: 200px"
allowClear
@change="handleFilterChange"
/>
</a-form-item>
<a-form-item label="用户角色">
<a-select
v-model:value="filterForm.role"
style="width: 200px"
placeholder="请选择角色"
allowClear
@change="handleFilterChange"
>
<a-select-option
v-for="role in availableRoles"
:key="role.value"
:value="role.value"
>
{{ role.label }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" @click="handleSearch">
查询
</a-button>
<a-button @click="handleReset">
重置
</a-button>
</a-space>
</a-form-item>
</a-form>
</div>
<a-table
:columns="columns"
:data-source="tableData"
:pagination="pagination"
:loading="loading"
@change="handleTableChange"
row-key="userid"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'roles'">
<a-space>
<a-tag
v-for="role in record.roles"
:key="role"
:color="getRoleColor(role)"
>
{{ getRoleName(role) }}
</a-tag>
</a-space>
</template>
<template v-if="column.key === 'phone'">
{{ formatPhone(record.phone) }}
</template>
<template v-if="column.key === 'points'">
{{ record.points || 0 }}
</template>
<template v-if="column.key === 'action'">
<a-space>
<a-button type="link" @click="handleEditRoles(record)">
修改角色
</a-button>
<a-button type="link" @click="handleResetPassword(record)">
重置密码
</a-button>
</a-space>
</template>
<template v-if="column.key === 'community_name'">
<a-space>
<span>{{ record.community_name || '-' }}</span>
<a-button type="link" @click="handleEditCommunity(record)">
修改
</a-button>
</a-space>
</template>
</template>
</a-table>
</div>
<!-- 添加角色编辑模态框 -->
<a-modal
v-model:visible="roleModalVisible"
title="修改用户角色"
@ok="handleRolesSave"
@cancel="handleRolesCancel"
:confirmLoading="rolesSaving"
>
<a-form layout="vertical">
<a-form-item label="用户角色">
<a-checkbox-group v-model:value="selectedRoles">
<a-checkbox
v-for="role in availableRoles"
:key="role.value"
:value="role.value"
:disabled="role.value === 'user'"
>
{{ role.label }}
</a-checkbox>
</a-checkbox-group>
</a-form-item>
</a-form>
</a-modal>
<!-- 添加密码重置结果模态框 -->
<a-modal
v-model:visible="passwordModalVisible"
title="重置密码结果"
>
<p>密码重置成功!新密码为:</p>
<div class="password-display">
<a-input ref="passwordInput" v-model:value="newPassword" readonly />
</div>
<p class="password-tip">请及时保存并告知用户</p>
<template #footer>
<a-space>
<a-button type="primary" @click="handleCopyPassword">
复制密码
</a-button>
<a-button @click="handleClosePasswordModal">
关闭
</a-button>
</a-space>
</template>
</a-modal>
<!-- 添加小区选择模态框 -->
<a-modal
v-model:visible="communityModalVisible"
title="修改所属小区"
@ok="handleCommunitySave"
@cancel="handleCommunityCancel"
:confirmLoading="communitySaving"
>
<a-form layout="vertical">
<a-form-item label="所属小区">
<a-select
v-model:value="selectedCommunityId"
style="width: 100%"
placeholder="请选择小区"
allowClear
:options="communityOptions"
:loading="communityLoading"
:fieldNames="{ label: 'name', value: 'id' }"
/>
</a-form-item>
</a-form>
</a-modal>
</page-container>
</template>
<script>
import { defineComponent, ref, onMounted } from 'vue'
import { message, Tag, Modal, Checkbox, Select } from 'ant-design-vue'
import { getUserList } from '@/api/user'
import request from '@/utils/request'
import PageContainer from '@/components/PageContainer.vue'
export default defineComponent({
components: {
PageContainer,
ATag: Tag,
ACheckbox: Checkbox,
ACheckboxGroup: Checkbox.Group,
ASelect: Select,
ASelectOption: Select.Option
},
setup() {
const loading = ref(false)
const tableData = ref([])
const pagination = ref({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showTotal: (total) => `${total} 条记录`
})
const columns = [
{
title: '用户ID',
dataIndex: 'userid',
key: 'userid',
width: 80,
},
{
title: '用户名',
dataIndex: 'nickname',
key: 'nickname',
width: 120,
},
{
title: '手机号',
dataIndex: 'phone',
key: 'phone',
width: 120,
},
{
title: '角色',
dataIndex: 'roles',
key: 'roles',
width: 200,
},
{
title: '用户编号',
dataIndex: 'user_code',
key: 'user_code',
width: 120,
},
{
title: '积分',
dataIndex: 'points',
key: 'points',
width: 80,
align: 'right',
},
{
title: '创建时间',
dataIndex: 'create_time',
key: 'create_time',
width: 180,
},
{
title: '所属小区',
dataIndex: 'community_name',
key: 'community_name',
width: 200
},
{
title: '操作',
key: 'action',
width: 200,
fixed: 'right'
}
]
// 格式化手机号中间4位显示*
const formatPhone = (phone) => {
if (!phone) return ''
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
}
// 添加筛选表单
const filterForm = ref({
role: undefined,
phone: undefined
})
// 获取用户列表数据
const fetchData = async () => {
try {
loading.value = true
const params = {
skip: (pagination.value.current - 1) * pagination.value.pageSize,
limit: pagination.value.pageSize,
role: filterForm.value.role,
phone: filterForm.value.phone // 添加手机号参数
}
// 移除空值参数
Object.keys(params).forEach(key => {
if (params[key] === undefined || params[key] === '') {
delete params[key]
}
})
const res = await getUserList(params)
if (res.code === 200) {
tableData.value = res.data.items
pagination.value.total = res.data.total
} else {
message.error(res.message || '获取数据失败')
}
} catch (error) {
console.error('获取用户列表失败:', error)
message.error('获取数据失败')
} finally {
loading.value = false
}
}
// 表格变化处理(分页、排序等)
const handleTableChange = (pag) => {
pagination.value.current = pag.current
pagination.value.pageSize = pag.pageSize
fetchData()
}
// 添加角色名称映射函数
const getRoleName = (role) => {
const roleMap = {
'admin': '管理员',
'user': '普通用户',
'merchant': '商家',
'deliveryman': '配送员'
}
return roleMap[role] || role
}
// 添加角色颜色映射函数
const getRoleColor = (role) => {
const colorMap = {
'admin': 'red',
'user': 'blue',
'merchant': 'orange',
'deliveryman': 'purple'
}
return colorMap[role] || 'default'
}
// 共用的状态
const currentUserId = ref(null)
// 角色编辑相关状态
const roleModalVisible = ref(false)
const rolesSaving = ref(false)
const selectedRoles = ref([])
// 密码重置相关状态
const passwordModalVisible = ref(false)
const newPassword = ref('')
const passwordInput = ref(null)
// 小区相关状态
const communityModalVisible = ref(false)
const communitySaving = ref(false)
const communityLoading = ref(false)
const selectedCommunityId = ref(undefined)
const communityOptions = ref([])
// 可选角色列表
const availableRoles = [
{ label: '普通用户', value: 'user' },
{ label: '商家', value: 'merchant' },
{ label: '配送员', value: 'deliveryman' }
]
// 角色编辑处理函数
const handleEditRoles = (record) => {
currentUserId.value = record.userid
selectedRoles.value = record.roles || ['user']
roleModalVisible.value = true
}
// 保存角色修改
const handleRolesSave = async () => {
try {
rolesSaving.value = true
// 获取原有角色列表
const originalRoles = tableData.value.find(
user => user.userid === currentUserId.value
)?.roles || []
// 如果原来有 admin 角色,确保保留
const newRoles = selectedRoles.value
if (originalRoles.includes('admin')) {
newRoles.push('admin')
}
// 确保用户角色始终存在
if (!newRoles.includes('user')) {
newRoles.push('user')
}
await request.put(`/api/user/roles?user_id=${currentUserId.value}`, newRoles)
message.success('角色修改成功')
roleModalVisible.value = false
fetchData() // 刷新列表
} catch (error) {
console.error('修改角色失败:', error)
message.error('修改角色失败')
} finally {
rolesSaving.value = false
}
}
// 修改取消处理函数
const handleRolesCancel = () => {
roleModalVisible.value = false
resetStates()
}
// 处理筛选变化
const handleFilterChange = () => {
pagination.value.current = 1 // 重置页码
}
// 查询按钮点击
const handleSearch = () => {
pagination.value.current = 1
fetchData()
}
// 重置按钮点击
const handleReset = () => {
filterForm.value = {
role: undefined,
phone: undefined
}
pagination.value.current = 1
fetchData()
}
// 生成随机密码
const generatePassword = () => {
const chars = '0123456789abcdefghijklmnopqrstuvwxyz'
let password = ''
for (let i = 0; i < 8; i++) {
password += chars.charAt(Math.floor(Math.random() * chars.length))
}
return password
}
// 处理重置密码
const handleResetPassword = (record) => {
Modal.confirm({
title: '重置密码',
content: `确定要重置用户 ${record.nickname || record.phone} 的密码吗?`,
async onOk() {
try {
const password = generatePassword()
await request.post('/api/user/reset-password', {
user_id: record.userid,
new_password: password
})
newPassword.value = password
passwordModalVisible.value = true
message.success('密码重置成功')
} catch (error) {
console.error('重置密码失败:', error)
message.error('重置密码失败')
}
}
})
}
// 复制密码
const handleCopyPassword = async () => {
try {
await navigator.clipboard.writeText(newPassword.value)
message.success('密码已复制到剪贴板')
} catch (err) {
// 降级处理如果剪贴板API不可用使用输入框选择
passwordInput.value.select()
document.execCommand('copy')
message.success('密码已复制到剪贴板')
}
}
// 关闭密码模态框
const handleClosePasswordModal = () => {
passwordModalVisible.value = false
newPassword.value = ''
resetStates()
}
// 获取小区列表
const fetchCommunityList = async () => {
try {
communityLoading.value = true
const res = await request.get('/api/community')
if (res.code === 200 && res.data) {
communityOptions.value = res.data.items || []
}
} catch (error) {
console.error('获取小区列表失败:', error)
message.error('获取小区列表失败')
} finally {
communityLoading.value = false
}
}
// 小区编辑处理函数
const handleEditCommunity = (record) => {
currentUserId.value = record.userid
selectedCommunityId.value = record.community_id
communityModalVisible.value = true
if (communityOptions.value.length === 0) {
fetchCommunityList()
}
}
// 保存小区修改
const handleCommunitySave = async () => {
try {
communitySaving.value = true
await request.put('/api/user/community', {
user_id: currentUserId.value,
community_id: selectedCommunityId.value
})
message.success('修改成功')
communityModalVisible.value = false
fetchData() // 刷新列表
} catch (error) {
console.error('修改所属小区失败:', error)
message.error('修改失败')
} finally {
communitySaving.value = false
}
}
// 修改取消处理函数
const handleCommunityCancel = () => {
communityModalVisible.value = false
resetStates()
}
// 重置状态的函数
const resetStates = () => {
currentUserId.value = null
selectedRoles.value = []
selectedCommunityId.value = undefined
}
onMounted(() => {
fetchData()
fetchCommunityList() // 预加载小区列表
})
return {
loading,
columns,
tableData,
pagination,
handleTableChange,
formatPhone,
getRoleName,
getRoleColor,
roleModalVisible,
rolesSaving,
selectedRoles,
availableRoles,
handleEditRoles,
handleRolesSave,
handleRolesCancel,
filterForm,
handleFilterChange,
handleSearch,
handleReset,
passwordModalVisible,
newPassword,
passwordInput,
handleResetPassword,
handleCopyPassword,
handleClosePasswordModal,
communityModalVisible,
communitySaving,
communityLoading,
selectedCommunityId,
communityOptions,
handleEditCommunity,
handleCommunitySave,
handleCommunityCancel,
currentUserId
}
}
})
</script>
<style scoped>
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.table-header h1 {
margin: 0;
}
:deep(.ant-table-content) {
overflow-x: auto;
}
.user-list {
/* 保留其他必要样式 */
}
:deep(.ant-tag) {
margin: 2px;
}
:deep(.ant-checkbox-group) {
display: flex;
flex-direction: column;
gap: 8px;
}
:deep(.ant-checkbox-wrapper) {
margin-left: 0 !important;
}
:deep(.ant-checkbox-wrapper + .ant-checkbox-wrapper) {
margin-left: 0 !important;
}
.table-filter {
margin-bottom: 16px;
padding: 16px 24px;
background: #fff;
border-radius: 2px;
}
:deep(.ant-form-item) {
margin-bottom: 0;
margin-right: 16px;
}
:deep(.ant-form-item:last-child) {
margin-right: 0;
}
.password-display {
margin: 16px 0;
background: #f5f5f5;
padding: 12px;
border-radius: 4px;
}
.password-tip {
color: #ff4d4f;
margin-top: 8px;
margin-bottom: 0;
}
:deep(.ant-input[readonly]) {
background-color: #fff;
cursor: text;
color: rgba(0, 0, 0, 0.85);
font-family: monospace;
font-size: 16px;
text-align: center;
}
:deep(.ant-select) {
width: 100% !important;
}
:deep(.ant-input) {
width: 200px !important;
}
</style>