本地 RAG 知识库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

117 lines
8.4 KiB

<template>
<t-card title="👥 用户管理" :bordered="false">
<!-- 搜索栏 -->
<div class="toolbar">
<t-input v-model="keyword" placeholder="搜索用户名/昵称/邮箱" size="small" style="width:220px;" @enter="loadData" />
<t-select v-model="filterEnabled" :options="statusOpts" placeholder="全部状态" size="small" style="width:120px;" @change="loadData" />
<t-button size="small" @click="loadData">搜索</t-button>
<div style="flex:1;" />
<t-button theme="primary" size="small" @click="openCreate">+ 新建用户</t-button>
</div>
<t-table :data="users" :columns="columns" row-key="id"
:pagination="{ current: page, total: total, pageSize: pageSize, showJumper: true }" @page-change="onPageChange">
<template #role_names="{ row }">
<t-tag v-for="r in (row.role_names||'').split(',').filter(Boolean)" :key="r" size="small" variant="light" theme="primary" style="margin-right:2px;">{{ r }}</t-tag>
<t-tag v-if="!row.role_names" size="small" variant="light" theme="default">未分配</t-tag>
</template>
<template #enabled="{ row }">
<t-tag size="small" :theme="row.enabled?'success':'danger'" variant="light">{{ row.enabled?'启用':'禁用' }}</t-tag>
</template>
<template #op="{ row }">
<t-space :size="2">
<t-button size="small" variant="text" @click="openEdit(row)">编辑</t-button>
<t-button size="small" variant="text" @click="openRoleAssign(row)">角色</t-button>
<t-button size="small" variant="text" @click="openPasswordChange(row)">改密</t-button>
<t-button size="small" variant="text" :theme="row.enabled?'warning':'success'" @click="handleToggle(row)">{{ row.enabled?'禁用':'启用' }}</t-button>
</t-space>
</template>
</t-table>
<!-- 新建/编辑弹窗 -->
<t-dialog v-model:visible="modal.visible" :header="modal.isEdit?'编辑用户':'新建用户'" width="480px" :footer="false">
<t-form label-align="top">
<t-form-item label="用户名"><t-input v-model="modal.username" :disabled="modal.isEdit" placeholder="登录账号" /></t-form-item>
<t-form-item v-if="!modal.isEdit" label="密码"><t-input v-model="modal.password" type="password" placeholder="初始密码" /></t-form-item>
<t-form-item label="昵称"><t-input v-model="modal.nickname" placeholder="显示昵称" /></t-form-item>
<t-form-item label="邮箱"><t-input v-model="modal.email" placeholder="邮箱地址" /></t-form-item>
<t-form-item label="手机"><t-input v-model="modal.phone" placeholder="手机号" /></t-form-item>
</t-form>
<div class="dialog-footer">
<t-button variant="outline" @click="modal.visible=false">取消</t-button>
<t-button theme="primary" @click="handleSave" :loading="saving">保存</t-button>
</div>
</t-dialog>
<!-- 角色分配弹窗 -->
<t-dialog v-model:visible="roleModal.visible" :header="'分配角色 — '+roleModal.username" width="480px" :footer="false">
<t-checkbox-group v-model="roleModal.selectedRoleIds">
<t-checkbox v-for="r in allRoles" :key="r.id" :value="String(r.id)">{{ r.name }} ({{ r.roleKey }})</t-checkbox>
</t-checkbox-group>
<div class="dialog-footer">
<t-button variant="outline" @click="roleModal.visible=false">取消</t-button>
<t-button theme="primary" @click="handleRoleAssign">保存</t-button>
</div>
</t-dialog>
<!-- 改密弹窗 -->
<t-dialog v-model:visible="pwdModal.visible" :header="'修改密码 — '+pwdModal.username" width="400px" :footer="false">
<t-form label-align="top"><t-form-item label="新密码"><t-input v-model="pwdModal.newPassword" type="password" placeholder="请输入新密码" /></t-form-item></t-form>
<div class="dialog-footer">
<t-button variant="outline" @click="pwdModal.visible=false">取消</t-button>
<t-button theme="primary" @click="handlePasswordChange">确认修改</t-button>
</div>
</t-dialog>
</t-card>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { listSysUsers, createSysUser, updateSysUser, toggleSysUser, assignSysUserRoles, changeSysUserPassword, getAllSysRoles } from '@/api/user'
import { toast } from '@/utils/toast'
import { formatDate } from '@/utils/format'
import { useConfirm } from '@/composables/useConfirm'
const { confirm } = useConfirm()
const users = ref<any[]>([]); const total=ref(0); const page=ref(1); const pageSize=ref(20)
const keyword=ref(''); const filterEnabled=ref<boolean|null>(null); const allRoles=ref<any[]>([]); const saving=ref(false)
const statusOpts=[{label:'全部状态',value:null},{label:'已启用',value:true},{label:'已禁用',value:false}]
const columns = [
{colKey:'username',title:'用户名',width:100},{colKey:'nickname',title:'昵称',width:80},{colKey:'email',title:'邮箱',width:140},
{colKey:'role_names',title:'角色',width:160},{colKey:'enabled',title:'状态',width:60},{colKey:'last_login_time',title:'最后登录',width:160,cell:(_:any,{row}:any)=>formatDate(row.last_login_time)},
{colKey:'create_time',title:'创建时间',width:160,cell:(_:any,{row}:any)=>formatDate(row.create_time)},{colKey:'op',title:'操作',width:220},
]
const modal = reactive({visible:false,isEdit:false,userId:null as any,username:'',password:'',nickname:'',email:'',phone:''})
const roleModal = reactive({visible:false,userId:null as any,username:'',selectedRoleIds:[] as string[]})
const pwdModal = reactive({visible:false,userId:null as any,username:'',newPassword:''})
onMounted(()=>{loadData();loadRoles()})
async function loadData(){try{const r=await listSysUsers(page.value,pageSize.value,keyword.value,filterEnabled.value as any);if(r.success){users.value=r.data.records||[];total.value=r.data.total||0}}catch(e){console.error(e)}}
async function loadRoles(){try{const r=await getAllSysRoles();if(r.success)allRoles.value=r.data||[]}catch(e){console.error(e)}}
function onPageChange(i:{current:number;pageSize:number}){page.value=i.current;pageSize.value=i.pageSize;loadData()}
function openCreate(){Object.assign(modal,{visible:true,isEdit:false,userId:null,username:'',password:'',nickname:'',email:'',phone:''})}
function openEdit(u:any){Object.assign(modal,{visible:true,isEdit:true,userId:u.id,username:u.username,nickname:u.nickname||'',email:u.email||'',phone:u.phone||'',password:''})}
async function handleSave(){saving.value=true
try{let r;if(modal.isEdit)r=await updateSysUser(modal.userId,{nickname:modal.nickname,email:modal.email,phone:modal.phone})
else{if(!modal.username||!modal.password){toast('用户名和密码不能为空','error');return}r=await createSysUser({username:modal.username,password:modal.password,nickname:modal.nickname,email:modal.email,phone:modal.phone})}
if(r.success){toast(r.message||'操作成功','success');modal.visible=false;loadData()}else toast(r.message||'操作失败','error')}catch(e:any){toast(e.message||'操作失败','error')}finally{saving.value=false}}
async function handleToggle(u:any){if(!await confirm(`确定${u.enabled?'禁用':'启用'}${u.username}」?`))return
try{const r=await toggleSysUser(u.id,!u.enabled);if(r.success){toast(r.message||'操作成功','success');loadData()}else toast(r.message||'操作失败','error')}catch(e:any){toast(e.message||'操作失败','error')}}
function openRoleAssign(u:any){roleModal.userId=u.id;roleModal.username=u.username
const rk=(u.role_keys||'').split(',').filter(Boolean);roleModal.selectedRoleIds=allRoles.value.filter(r=>rk.includes(r.roleKey)).map(r=>String(r.id));roleModal.visible=true}
async function handleRoleAssign(){try{const r=await assignSysUserRoles(roleModal.userId,roleModal.selectedRoleIds);if(r.success){toast('角色分配成功','success');roleModal.visible=false;loadData()}else toast(r.message||'分配失败','error')}catch(e:any){toast(e.message||'分配失败','error')}}
function openPasswordChange(u:any){pwdModal.userId=u.id;pwdModal.username=u.username;pwdModal.newPassword='';pwdModal.visible=true}
async function handlePasswordChange(){if(!pwdModal.newPassword){toast('请输入新密码','error');return}
try{const r=await changeSysUserPassword(pwdModal.userId,pwdModal.newPassword);if(r.success){toast('密码修改成功','success');pwdModal.visible=false}else toast(r.message||'修改失败','error')}catch(e:any){toast(e.message||'修改失败','error')}}
</script>
<style scoped></style>