优化账号管理页面:调整表格列宽,美化按钮样式,完善角色显示
This commit is contained in:
parent
a6163bfc1c
commit
802c57003c
@ -203,6 +203,40 @@ class AccountManager:
|
||||
logger.error(f"更新密码失败: {e}")
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def reset_password(account_id: str):
|
||||
"""重置账号密码"""
|
||||
try:
|
||||
# 验证account_id是否为有效UUID
|
||||
try:
|
||||
uuid.UUID(account_id)
|
||||
except ValueError:
|
||||
logger.warning(f"无效的account_id格式: {account_id}")
|
||||
return False
|
||||
|
||||
# 使用固定密码
|
||||
new_password = "Welcome123!"
|
||||
hashed_password, password_salt = AccountManager.hash_password(new_password)
|
||||
|
||||
# 更新密码
|
||||
updated_at = datetime.now(timezone.utc)
|
||||
update_query = """
|
||||
UPDATE accounts
|
||||
SET password = %s, password_salt = %s, updated_at = %s
|
||||
WHERE id = %s::uuid;
|
||||
"""
|
||||
rows_affected = execute_update(update_query, (hashed_password, password_salt, updated_at, account_id))
|
||||
|
||||
if rows_affected > 0:
|
||||
logger.info(f"账号 {account_id} 的密码已重置!")
|
||||
return True
|
||||
else:
|
||||
logger.warning(f"未找到ID为 {account_id} 的账号。")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"重置密码失败: {e}")
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def associate_with_tenant(account_id, tenant_id, role="normal", invited_by=None, current=False):
|
||||
"""将账户与租户关联"""
|
||||
@ -250,3 +284,35 @@ class AccountManager:
|
||||
except Exception as e:
|
||||
logger.error(f"获取租户账户失败: {e}")
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def get_account_tenants(account_id):
|
||||
"""获取账户关联的所有租户"""
|
||||
try:
|
||||
# 验证account_id是否为有效UUID
|
||||
try:
|
||||
uuid.UUID(account_id)
|
||||
except ValueError:
|
||||
logger.error(f"无效的account_id格式: {account_id}")
|
||||
return []
|
||||
|
||||
query = """
|
||||
SELECT t.id, t.name, j.role, j.current
|
||||
FROM tenants t
|
||||
JOIN tenant_account_joins j ON t.id = j.tenant_id
|
||||
WHERE j.account_id = %s::uuid;
|
||||
"""
|
||||
tenants = execute_query(query, (account_id,))
|
||||
if not tenants:
|
||||
logger.info(f"账号 {account_id} 未关联任何租户")
|
||||
return []
|
||||
|
||||
return [{
|
||||
"tenant_id": str(t[0]),
|
||||
"tenant_name": t[1],
|
||||
"role": str(t[2]).lower(), # 确保角色值统一为小写
|
||||
"current": t[3]
|
||||
} for t in tenants]
|
||||
except Exception as e:
|
||||
logger.error(f"获取账户租户失败: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
102
api/app.py
102
api/app.py
@ -1,3 +1,4 @@
|
||||
import uuid
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from fastapi import FastAPI, Depends, HTTPException, status, Request, Body, Response
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
@ -25,7 +26,7 @@ logger = logging.getLogger(__name__)
|
||||
# JWT配置
|
||||
SECRET_KEY = "your-secret-key-here" # 生产环境应该从环境变量获取
|
||||
ALGORITHM = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES = 1440 # 延长至24小时
|
||||
|
||||
# 密码哈希
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
@ -114,18 +115,21 @@ async def get_current_user(token: str = Depends(oauth2_scheme)):
|
||||
username: str = payload.get("sub")
|
||||
if username is None:
|
||||
raise credentials_exception
|
||||
|
||||
# 验证用户(兼容前后端用户)
|
||||
user = backend_account_manager.get_user_by_username(username) or \
|
||||
AccountManager.get_user_by_username(username)
|
||||
if not user:
|
||||
raise credentials_exception
|
||||
|
||||
return {
|
||||
"id": str(user.get("id")),
|
||||
"username": user.get("username"),
|
||||
"email": user.get("email", "")
|
||||
}
|
||||
except JWTError:
|
||||
raise credentials_exception
|
||||
|
||||
user = AccountManager.get_user_by_username(username)
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
return {
|
||||
"id": user["id"],
|
||||
"username": user["username"],
|
||||
"email": user["email"]
|
||||
}
|
||||
|
||||
# 认证路由组
|
||||
@app.options("/api/auth/register", include_in_schema=False)
|
||||
async def auth_register_options():
|
||||
@ -260,29 +264,46 @@ async def search_accounts(
|
||||
|
||||
@app.get("/api/dify_accounts/{username}")
|
||||
async def get_dify_account(username: str, current_user: dict = Depends(get_current_user)):
|
||||
"""查询Dify账户信息"""
|
||||
"""查询Dify账户信息及关联租户"""
|
||||
try:
|
||||
account = AccountManager.get_user_by_username(username)
|
||||
if not account:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Dify账户不存在")
|
||||
|
||||
# 获取关联租户信息
|
||||
tenant_info = AccountManager.get_account_tenants(account["id"])
|
||||
|
||||
return {
|
||||
"user_id": str(account["id"]),
|
||||
"username": account["username"],
|
||||
"email": account["email"],
|
||||
"created_at": account["created_at"]
|
||||
"created_at": account["created_at"],
|
||||
"tenants": tenant_info # 直接使用已格式化的租户信息
|
||||
}
|
||||
except ValueError as e:
|
||||
logger.error(f"参数格式错误: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="参数格式错误"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"查询Dify账户失败: {e}")
|
||||
logger.error(f"查询Dify账户失败: {e}", exc_info=True)
|
||||
if "404" in str(e):
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Dify账户不存在")
|
||||
raise HTTPException(status_code=400, detail="查询Dify账户失败")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Dify账户不存在"
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="服务器内部错误"
|
||||
)
|
||||
|
||||
@app.put("/api/accounts/password")
|
||||
async def change_password(
|
||||
password_change: PasswordChange,
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""修改密码"""
|
||||
"""修改当前用户密码"""
|
||||
try:
|
||||
user = AccountManager.get_user_by_username(current_user["username"])
|
||||
if not AccountManager.verify_password(
|
||||
@ -305,6 +326,55 @@ async def change_password(
|
||||
logger.error(f"修改密码失败: {e}")
|
||||
raise HTTPException(status_code=400, detail="修改密码失败")
|
||||
|
||||
@app.post("/api/accounts/{account_id}/reset-password")
|
||||
async def reset_password(
|
||||
account_id: str,
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""管理员重置用户密码"""
|
||||
try:
|
||||
# 检查当前用户是否是admin
|
||||
admin_user = backend_account_manager.get_user_by_username(current_user["username"])
|
||||
if not admin_user or admin_user.get("username") != "admin":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="需要管理员权限"
|
||||
)
|
||||
|
||||
# 增强account_id格式验证
|
||||
logger.info(f"完整请求参数: account_id={account_id}")
|
||||
|
||||
# 去除可能的空格和引号
|
||||
clean_account_id = account_id.strip().strip('"').strip("'")
|
||||
logger.info(f"清理后的account_id: {clean_account_id}")
|
||||
|
||||
try:
|
||||
# 严格验证UUID格式
|
||||
if len(clean_account_id) != 36 or clean_account_id.count("-") != 4:
|
||||
raise ValueError("UUID格式不正确")
|
||||
|
||||
parsed_uuid = uuid.UUID(clean_account_id)
|
||||
logger.info(f"成功解析为UUID: {parsed_uuid}")
|
||||
except ValueError as e:
|
||||
logger.error(f"无效的account_id格式: {clean_account_id}, 原始值: {account_id}, 错误: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"账号ID格式无效: {clean_account_id} (必须是标准的UUID格式)"
|
||||
)
|
||||
|
||||
# 重置密码
|
||||
success = AccountManager.reset_password(account_id)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="账号不存在"
|
||||
)
|
||||
|
||||
return {"message": "密码重置成功"}
|
||||
except Exception as e:
|
||||
logger.error(f"重置密码失败: {e}")
|
||||
raise HTTPException(status_code=400, detail="重置密码失败")
|
||||
|
||||
# 租户管理路由组
|
||||
@app.post("/api/tenants/")
|
||||
async def create_tenant(tenant: TenantCreate, current_user: dict = Depends(get_current_user)):
|
||||
|
||||
27
api/keys/privkeys/default/private.pem
Normal file
27
api/keys/privkeys/default/private.pem
Normal file
@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEA5j3p4ndQP1Qc8v2Jl8CVHu13phNaJ/gBEtt9Rrxmx149n/7X
|
||||
Wgs3GY7/MMV+AvrZuDDy8tY1J7B1yQySG10Z/I25MHrgSvuNfdzxxcN64wb2cgKz
|
||||
hsfjqGYOtd5dRRTjLAP1H3+lN7hd9Y0izPN4rM8I9Nz+GUpcks3nxcw5BEHolWIo
|
||||
LM5L2Or9S4ZIcVtI65rkwoucSzfCDfVbda4rXEFCZjkDQyI/pwRfta6EZQrez/Lf
|
||||
M6b3sJjBP7Ub9EaKxwAbc2YgcFHIvq8HCtEkSCsjvq+75inADUet0hXlaL1xl0Uf
|
||||
PYEKk6CftEX/PKEdsWrDdJEth9+R1/xJEYavOQIDAQABAoIBAApagNMMLliOHvf/
|
||||
w7lGUc5b4BRObOeF7iS3KDA86MhHoPnw0dy8yxYopM5lAf4jT8uA0/9sidHPlWIT
|
||||
TtpjcXekbeCKuW/DekDfwNoaTVWVxhG7tKZJ4B9HIC3KxyMWWCduDUWW+6Y3cbdb
|
||||
SMHXEUmkqFCFaGbrdMYbPLRJRYLRWBQQIfHWJKoWfngUr5iRqStmSoUhyY1PwgAO
|
||||
rD4DhQZOqOYE8DotIA7q5raawKcX5qIf6sEjm+QtmVNQ3g53QPNSTTwlXVQ7vTYz
|
||||
znkQZBgHIc/6Wb0iLDGUyMT7abHVcr8/EuRCLYSn5Xlcp9nESYE0y4agOx4MPHxw
|
||||
/x5Kn1MCgYEA7Gab2umVqcS+e+OP8vVPpVvC+G2XeALg7g0M5xnHRYLmlm1GoycO
|
||||
tG8XcEpI6IsZFBqKtyrpmE+qE2F1mf5oxab6pI0IGvH6IVVibpKdi60CV3lF+d5T
|
||||
ayZ/7nvMBrXSdhOXacuk+o3qqhjTsqKoy85CNQqwNcYAZu+4+UT0k58CgYEA+VSW
|
||||
HYnlya0AKWXza8SxU8n3Y1UYMZbbjpUt5Xq6n7nfxQlMRcqwii21u6WeXza2binH
|
||||
KTOzWW2fLyT4n67bhM6IGYLcJY6hSjvvXGAzftt+tbOGu9PJlWVe4ELLz+kHdhV3
|
||||
rbjZDEISRi8VGTVZRQghjLU8yerkMMLxcQ5ejicCgYEAyzjuVMOnMFl88x3Oeqtd
|
||||
+6YlttDnfHjlCl/Xrrefcec0+S4ZoloKLxytRo/lm1swhPLIOuw+AfzCFYUbxvVI
|
||||
9lk0cM74n8lTIOK5CpspqpBhSfdsK4Bvr9ZZ9hcgbshRk8YFzSIOwoHLsMxE+PUS
|
||||
LJo0mkqE7sU3RUZhepBHvLsCgYEA20KglK9tDXL+/mjyrSYXD2lADfGKSimxQO09
|
||||
pF3OeqJ5/4uSsJlzsMBL3g3ifTbfLXe99iTKJu25HDt2DO83is4Zb93dfYW1n1Of
|
||||
xmuvPXMHNgD/jnPMBX5U9gCnvVnfPt/YFETHUvlTmrbS5g09SPDCmDvVjnfrXlpA
|
||||
+zw4uOcCgYEAthxwyIrBFJfSR1YWsuhFIXcjXb68UODOL0uJJsnQB61A1+mVLlO9
|
||||
SO/zQ9ItZYGu4/OFB1fYb7r9n0P0TMICJ0d/uK8AfAcgV6Y1ruTvzgVkhyoPb/p4
|
||||
eE/C8xdf99RLUfd30D3UeQXYIi9EU57sHCfoupjNGk7P+g7XwrWojQ8=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@ -23,3 +23,6 @@ python-jose>=3.3.0
|
||||
passlib>=1.7.4
|
||||
python-multipart>=0.0.6
|
||||
werkzeug>=2.3.7
|
||||
|
||||
# 邮箱验证
|
||||
email_validator>=2.2.0
|
||||
@ -12,8 +12,12 @@ export const fetchAccounts = (params: AccountListParams) =>
|
||||
total: number
|
||||
}>({
|
||||
method: 'GET',
|
||||
url: '/accounts/search',
|
||||
params
|
||||
url: '/api/accounts/search',
|
||||
params: {
|
||||
page: params.page,
|
||||
page_size: params.page_size,
|
||||
search: params.search
|
||||
}
|
||||
})
|
||||
|
||||
export const updateAccount = (id: string, data: UpdateAccountParams) =>
|
||||
@ -21,7 +25,7 @@ export const updateAccount = (id: string, data: UpdateAccountParams) =>
|
||||
message: string
|
||||
}>({
|
||||
method: 'PATCH',
|
||||
url: `/accounts/${id}`,
|
||||
url: `/api/accounts/${id}`,
|
||||
data
|
||||
})
|
||||
|
||||
@ -30,7 +34,7 @@ export const resetPassword = (id: string, data: ResetPasswordParams) =>
|
||||
message: string
|
||||
}>({
|
||||
method: 'POST',
|
||||
url: `/accounts/${id}/reset-password`,
|
||||
url: `/api/accounts/${id}/reset-password`,
|
||||
data
|
||||
})
|
||||
|
||||
@ -39,7 +43,7 @@ export const toggleAccountStatus = (id: string) =>
|
||||
message: string
|
||||
}>({
|
||||
method: 'POST',
|
||||
url: `/accounts/${id}/toggle-status`
|
||||
url: `/api/accounts/${id}/toggle-status`
|
||||
})
|
||||
|
||||
export const createAccount = (data: { username: string }) =>
|
||||
@ -48,6 +52,56 @@ export const createAccount = (data: { username: string }) =>
|
||||
account: AccountItem
|
||||
}>({
|
||||
method: 'POST',
|
||||
url: '/accounts',
|
||||
url: '/api/accounts',
|
||||
data
|
||||
})
|
||||
|
||||
// Dify账号相关API
|
||||
export const getDifyAccount = (username: string) =>
|
||||
request<{
|
||||
user_id: string
|
||||
username: string
|
||||
email: string
|
||||
created_at: string
|
||||
tenants: Array<{
|
||||
tenant_id: string
|
||||
tenant_name: string
|
||||
role: string
|
||||
current: boolean
|
||||
}>
|
||||
}>({
|
||||
method: 'GET',
|
||||
url: `/api/dify_accounts/${username}`
|
||||
})
|
||||
|
||||
export const getDifyAccounts = (params: { page: number; page_size: number }) =>
|
||||
request<{
|
||||
accounts: AccountItem[]
|
||||
total: number
|
||||
}>({
|
||||
method: 'GET',
|
||||
url: '/api/dify_accounts',
|
||||
params
|
||||
})
|
||||
|
||||
export const createDifyAccount = (data: {
|
||||
username: string
|
||||
email: string
|
||||
password: string
|
||||
}) =>
|
||||
request<{
|
||||
message: string
|
||||
account: AccountItem
|
||||
}>({
|
||||
method: 'POST',
|
||||
url: '/api/dify_accounts',
|
||||
data
|
||||
})
|
||||
|
||||
export const resetDifyPassword = (id: string) =>
|
||||
request<{
|
||||
message: string
|
||||
}>({
|
||||
method: 'POST',
|
||||
url: `/api/accounts/${id}/reset-password`
|
||||
})
|
||||
|
||||
@ -1,3 +1,10 @@
|
||||
export interface TenantInfo {
|
||||
tenant_id: string
|
||||
tenant_name: string
|
||||
role: string
|
||||
current: boolean
|
||||
}
|
||||
|
||||
export interface AccountItem {
|
||||
id: string
|
||||
username: string
|
||||
@ -5,11 +12,12 @@ export interface AccountItem {
|
||||
status: 'active' | 'disabled'
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
tenants?: TenantInfo[]
|
||||
}
|
||||
|
||||
export interface AccountListParams {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
page_size?: number
|
||||
username?: string
|
||||
email?: string
|
||||
status?: 'active' | 'disabled'
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { request } from '../../axios/service'
|
||||
import type { LoginParams, RegisterParams } from '@/api/auth/types'
|
||||
import type { LoginParams, RegisterParams, LoginForm } from '@/api/auth/types'
|
||||
|
||||
export const login = (formData: FormData) =>
|
||||
export const login = (formData: FormData | LoginForm) =>
|
||||
request<{ access_token: string }>({
|
||||
method: 'POST',
|
||||
url: '/api/auth/login',
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
export type LoginParams = {
|
||||
export interface LoginParams {
|
||||
username: string
|
||||
password: string
|
||||
tenantId?: string
|
||||
}
|
||||
|
||||
export interface LoginForm {
|
||||
username: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export type LoginFormData = FormData
|
||||
|
||||
export type RegisterParams = {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { loginApi } from '../../api/login'
|
||||
import type { LoginForm } from '../../api/login/types'
|
||||
import { login } from '../../api/auth'
|
||||
import type { LoginForm } from '../../api/auth/types'
|
||||
import { setToken as setStorageToken, removeToken } from '../../utils/storage'
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
@ -17,10 +17,13 @@ export const useUserStore = defineStore('user', () => {
|
||||
userInfo.value = info
|
||||
}
|
||||
|
||||
const login = async (form: LoginForm) => {
|
||||
const res = await loginApi(form)
|
||||
setToken(res.token)
|
||||
setUserInfo(res.userInfo)
|
||||
const login = async (params: LoginForm): Promise<{ access_token: string }> => {
|
||||
const formData = new FormData()
|
||||
formData.append('username', params.username)
|
||||
formData.append('password', params.password)
|
||||
|
||||
const res = await login(formData as FormData)
|
||||
setToken(res.access_token)
|
||||
return res
|
||||
}
|
||||
|
||||
|
||||
@ -21,9 +21,10 @@
|
||||
</div>
|
||||
|
||||
<el-table :data="filteredAccountList" border style="width: 100%">
|
||||
<el-table-column prop="id" label="ID" width="240" />
|
||||
<el-table-column prop="username" label="用户名" width="180" />
|
||||
<el-table-column prop="email" label="邮箱" width="220" />
|
||||
<el-table-column prop="status" label="状态" width="120">
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 'active' ? 'success' : 'danger'">
|
||||
{{ row.status === 'active' ? '启用' : '禁用' }}
|
||||
@ -31,9 +32,24 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createdAt" label="创建时间" width="180" />
|
||||
<el-table-column label="操作" width="220">
|
||||
<el-table-column label="操作" width="280" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button
|
||||
plain
|
||||
size="small"
|
||||
@click="handleResetPassword(row)"
|
||||
style="color: #409EFF; border-color: #c6e2ff; background-color: #ecf5ff;"
|
||||
>
|
||||
重置密码
|
||||
</el-button>
|
||||
<el-button
|
||||
plain
|
||||
size="small"
|
||||
@click="handleShowTenants(row.username)"
|
||||
style="color: #67C23A; border-color: #e1f3d8; background-color: #f0f9eb; margin-left: 10px;"
|
||||
>
|
||||
关联租户
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="danger"
|
||||
@ -49,10 +65,32 @@
|
||||
v-model:current-page="pagination.current"
|
||||
v-model:page-size="pagination.size"
|
||||
:total="pagination.total"
|
||||
@current-change="fetchAccounts"
|
||||
@current-change="fetchAccountData"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- 租户信息弹窗 -->
|
||||
<el-dialog v-model="tenantDialogVisible" title="关联租户信息" width="60%">
|
||||
<el-table :data="tenantList" border v-loading="loading">
|
||||
<el-table-column prop="tenant_id" label="租户ID" width="220" />
|
||||
<el-table-column prop="tenant_name" label="租户名称" width="180" />
|
||||
<el-table-column prop="role" label="角色" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getRoleTagType(row.role)">
|
||||
{{ getRoleDisplayName(row.role) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="current" label="当前租户" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.current ? 'success' : 'info'">
|
||||
{{ row.current ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -62,7 +100,8 @@ import {
|
||||
fetchAccounts,
|
||||
toggleAccountStatus,
|
||||
createAccount,
|
||||
updateAccount
|
||||
resetPassword,
|
||||
getDifyAccount
|
||||
} from '@/api/account'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
@ -72,12 +111,15 @@ interface Account {
|
||||
email: string
|
||||
status: 'active' | 'disabled'
|
||||
createdAt: string
|
||||
isDify?: boolean
|
||||
}
|
||||
|
||||
const accountList = ref<Account[]>([])
|
||||
const filteredAccountList = ref<Account[]>([])
|
||||
const tenantList = ref<any[]>([])
|
||||
const searchQuery = ref('')
|
||||
const loading = ref(false)
|
||||
const tenantDialogVisible = ref(false)
|
||||
|
||||
const pagination = ref({
|
||||
current: 1,
|
||||
@ -90,10 +132,15 @@ const fetchAccountData = async () => {
|
||||
loading.value = true
|
||||
const res = await fetchAccounts({
|
||||
page: pagination.value.current,
|
||||
pageSize: pagination.value.size
|
||||
page_size: pagination.value.size,
|
||||
search: searchQuery.value || undefined
|
||||
})
|
||||
accountList.value = res.accounts
|
||||
accountList.value = res.accounts.map(account => ({
|
||||
...account,
|
||||
isDify: account.email?.endsWith('@dify.com')
|
||||
}))
|
||||
pagination.value.total = res.total
|
||||
filteredAccountList.value = res.accounts
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@ -125,38 +172,79 @@ const handleCreate = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const handleEdit = (account: Account) => {
|
||||
ElMessageBox.prompt('请输入新用户名', '编辑账号', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputValue: account.username,
|
||||
inputPattern: /^[a-zA-Z0-9_]{4,20}$/,
|
||||
inputErrorMessage: '用户名必须为4-20位字母数字或下划线'
|
||||
}).then(async ({ value }) => {
|
||||
try {
|
||||
await updateAccount(account.id, { username: value })
|
||||
ElMessage.success('账号更新成功')
|
||||
await fetchAccountData()
|
||||
} catch (error) {
|
||||
ElMessage.error('账号更新失败')
|
||||
const handleResetPassword = async (account: Account) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要重置该账号的密码吗?', '重置密码', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
await resetPassword(account.id)
|
||||
ElMessage.success('密码重置成功')
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error('密码重置失败')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearchClear = () => {
|
||||
const handleShowTenants = async (username: string) => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await getDifyAccount(username)
|
||||
console.log('租户信息:', res) // 调试日志
|
||||
tenantList.value = res.tenants || []
|
||||
if (tenantList.value.length === 0) {
|
||||
ElMessage.warning('该账号未关联任何租户')
|
||||
} else {
|
||||
tenantDialogVisible.value = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取租户信息失败:', error)
|
||||
ElMessage.error('获取租户信息失败: ' + error.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearchClear = async () => {
|
||||
searchQuery.value = ''
|
||||
await fetchAccountData()
|
||||
filteredAccountList.value = accountList.value
|
||||
}
|
||||
|
||||
const getRoleTagType = (role) => {
|
||||
const roleMap = {
|
||||
'owner': 'danger',
|
||||
'administrator': 'warning',
|
||||
'editor': 'primary',
|
||||
'member': 'info'
|
||||
}
|
||||
return roleMap[role.toLowerCase()] || 'primary'
|
||||
}
|
||||
|
||||
const getRoleDisplayName = (role) => {
|
||||
const roleMap = {
|
||||
'owner': '所有者',
|
||||
'administrator': '管理员',
|
||||
'editor': '编辑者',
|
||||
'member': '成员'
|
||||
}
|
||||
return roleMap[role.toLowerCase()] || role
|
||||
}
|
||||
|
||||
const handleSearch = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
pagination.value.current = 1
|
||||
const res = await fetchAccounts({
|
||||
page: 1,
|
||||
pageSize: pagination.value.size,
|
||||
page_size: pagination.value.size,
|
||||
search: searchQuery.value
|
||||
})
|
||||
filteredAccountList.value = res.accounts
|
||||
pagination.value.total = res.total
|
||||
accountList.value = res.accounts
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user