fix: 修复员工门户登录租户选择和数据展示问题
## 主要修改 ### 1. 登录租户选择修复 - 新增全局租户状态管理(useLoginTenantId hook) - 使用 @vueuse/core 的 createGlobalState 持久化租户选择 - 确保组件重新挂载时租户ID不丢失 - 修复登录时租户自动跳回第一个的问题 - 删除登录时强制覆盖租户ID的代码 - 用户选择的租户现在会被正确使用 - 恢复"记住登录"功能 - 自动恢复上次登录的租户、用户名、密码 ### 2. ERP 动态API迁移 - 员工门户和经销商门户的ERP API从硬编码迁移到动态API系统 - /erp/customer/* → /erp/dynamic/v1/customer/* - 新增 customer/list, customer/detail, sales-areas, brands API - 修复API响应嵌套结构问题 - 动态API返回数据嵌套在 data 字段中 - 调整响应类型定义和数据处理逻辑 ### 3. 经销商管理数据显示修复 - 处理动态API响应的嵌套数据结构 - 兼容 res.rows 和 res.data.rows 两种格式 - 添加详细调试日志便于排查问题 ## 文件清单 - 新增文件: - hzhub-portal-employee/src/hooks/useLoginTenantId.ts - hzhub-portal-dealer/src/hooks/useLoginTenantId.ts - hzhub-portal-employee/src/api/erp/index.ts - hzhub-portal-dealer/src/api/erp/index.ts - 修改文件: - 登录组件:TenantAccountPassword.vue, AccountPassword.vue - API文件:auth/index.ts, auth/types.ts - 经销商页面:dealer/index.vue ## 测试验证 - ✅ 租户下拉列表正常显示 - ✅ 选择租户后不会跳回第一个 - ✅ 记住登录功能可用 - ✅ 经销商管理页面数据正常显示 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
import type { EmailCodeDTO, LoginDTO, LoginVO, RegisterDTO } from './types';
|
||||
import { post } from '@/utils/request';
|
||||
import type { EmailCodeDTO, LoginDTO, LoginVO, RegisterDTO, TenantResp } from './types';
|
||||
import { post, get } from '@/utils/request';
|
||||
|
||||
export const login = (data: LoginDTO) => post<LoginVO>('/auth/login', data).json();
|
||||
|
||||
// 获取租户列表
|
||||
export const tenantList = () => get<TenantResp>('/auth/tenant/list').json();
|
||||
|
||||
// 邮箱验证码
|
||||
export const emailCode = (data: EmailCodeDTO) => post('/resource/email/code', data).json();
|
||||
|
||||
|
||||
@@ -16,6 +16,19 @@ export interface LoginVO {
|
||||
userInfo?: LoginUser;
|
||||
}
|
||||
|
||||
// 租户选项
|
||||
export interface TenantOption {
|
||||
companyName: string;
|
||||
domain?: string;
|
||||
tenantId: string;
|
||||
}
|
||||
|
||||
// 租户列表响应
|
||||
export interface TenantResp {
|
||||
tenantEnabled: boolean;
|
||||
voList: TenantOption[];
|
||||
}
|
||||
|
||||
/**
|
||||
* LoginUser,登录用户身份权限
|
||||
*/
|
||||
|
||||
92
hzhub-portal-dealer/src/api/erp/index.ts
Normal file
92
hzhub-portal-dealer/src/api/erp/index.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import request from '@/utils/request';
|
||||
|
||||
/**
|
||||
* 测试 ERP 数据库连接
|
||||
*/
|
||||
export function testErpConnection() {
|
||||
return request.get<{
|
||||
status: string;
|
||||
database: string;
|
||||
version: string;
|
||||
error?: string;
|
||||
}>('/erp/test/connection').json();
|
||||
}
|
||||
|
||||
/**
|
||||
* ERP 健康检查
|
||||
*/
|
||||
export function erpHealth() {
|
||||
return request.get<string>('/erp/test/health').json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户档案接口类型
|
||||
*/
|
||||
export interface CustomerVO {
|
||||
customerCode: string;
|
||||
customerName: string;
|
||||
companyCode: string;
|
||||
companyName: string;
|
||||
brand: string;
|
||||
brandName: string;
|
||||
contactName: string;
|
||||
salesAreaCode: string;
|
||||
salesAreaName: string;
|
||||
salesPersonCode: string;
|
||||
salesPersonName: string;
|
||||
saleDocCode: string;
|
||||
saleDocName: string;
|
||||
pricePlanCode: string;
|
||||
pricePlanName: string;
|
||||
customerType: string;
|
||||
address: string;
|
||||
phone: string;
|
||||
email: string;
|
||||
sdOrgCode: string;
|
||||
sdOrgName: string;
|
||||
province: string;
|
||||
city: string;
|
||||
isStop: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询客户列表
|
||||
* 使用动态API: /erp/dynamic/v1/customer/list
|
||||
*/
|
||||
export function getCustomerList(params: {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
keyword?: string;
|
||||
companyCode?: string;
|
||||
salesAreaCode?: string;
|
||||
brand?: string;
|
||||
}) {
|
||||
return request.get<{ rows: CustomerVO[]; total: number; code: number; msg: string }>(
|
||||
'/erp/dynamic/v1/customer/list',
|
||||
params
|
||||
).json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户详情
|
||||
* 使用动态API: /erp/dynamic/v1/customer/detail
|
||||
*/
|
||||
export function getCustomerDetail(customerCode: string) {
|
||||
return request.get<{ code: number; msg: string; data: CustomerVO }>(`/erp/dynamic/v1/customer/detail?customerCode=${customerCode}`).json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取销区列表
|
||||
* 使用动态API: /erp/dynamic/v1/customer/sales-areas
|
||||
*/
|
||||
export function getSalesAreas() {
|
||||
return request.get<{ code: number; msg: string; data: CustomerVO[] }>('/erp/dynamic/v1/customer/sales-areas').json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取品牌列表
|
||||
* 使用动态API: /erp/dynamic/v1/customer/brands
|
||||
*/
|
||||
export function getBrands() {
|
||||
return request.get<{ code: number; msg: string; data: CustomerVO[] }>('/erp/dynamic/v1/customer/brands').json();
|
||||
}
|
||||
@@ -1,26 +1,36 @@
|
||||
<!-- 账号密码登录表单 -->
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import type { LoginDTO } from '@/api/auth/types';
|
||||
import { reactive, ref } from 'vue';
|
||||
import type { LoginDTO, TenantResp } from '@/api/auth/types';
|
||||
import { reactive, ref, onMounted, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { login } from '@/api';
|
||||
import { login, tenantList } from '@/api';
|
||||
import { useUserStore } from '@/stores';
|
||||
import { useLoginFormStore } from '@/stores/modules/loginForm';
|
||||
import { useSessionStore } from '@/stores/modules/session';
|
||||
import { useLoginTenantId } from '@/hooks/useLoginTenantId';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const sessionStore = useSessionStore();
|
||||
const loginFromStore = useLoginFormStore();
|
||||
|
||||
// 使用全局租户ID状态
|
||||
const { loginTenantId } = useLoginTenantId();
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
// 租户信息
|
||||
const tenantInfo = ref<TenantResp>({
|
||||
tenantEnabled: false,
|
||||
voList: [],
|
||||
});
|
||||
|
||||
const formModel = reactive<LoginDTO>({
|
||||
username: '',
|
||||
password: '',
|
||||
clientId: import.meta.env.VITE_CLIENT_ID,
|
||||
grantType: 'password',
|
||||
tenantId: '000000',
|
||||
tenantId: loginTenantId.value, // 使用全局状态
|
||||
uuid: 'a5705def96be468f80e4b8bde3127c31',
|
||||
});
|
||||
|
||||
@@ -29,12 +39,55 @@ const rules = reactive<FormRules<LoginDTO>>({
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||||
});
|
||||
|
||||
// 监听表单中的租户ID变化,同步到全局状态(添加防抖,避免循环触发)
|
||||
watch(() => formModel.tenantId, (newTenantId, oldTenantId) => {
|
||||
// 仅在值真正改变时更新全局状态
|
||||
if (newTenantId !== oldTenantId && newTenantId !== loginTenantId.value) {
|
||||
console.log('租户ID变化:', oldTenantId, '->', newTenantId);
|
||||
loginTenantId.value = newTenantId || '000000';
|
||||
console.log('全局状态已同步:', loginTenantId.value);
|
||||
}
|
||||
});
|
||||
|
||||
// 加载租户列表
|
||||
async function loadTenant() {
|
||||
try {
|
||||
console.log('开始加载租户列表,当前全局租户ID:', loginTenantId.value);
|
||||
const resp = await tenantList();
|
||||
tenantInfo.value = resp;
|
||||
console.log('租户列表加载完成,启用多租户:', resp.tenantEnabled, '租户数量:', resp.voList.length);
|
||||
|
||||
// 仅在全局租户ID为默认值时才设置第一个租户
|
||||
if (resp.tenantEnabled && resp.voList.length > 0 && loginTenantId.value === '000000') {
|
||||
const firstTenantId = resp.voList[0].tenantId;
|
||||
console.log('全局租户ID为默认值,设置第一个租户:', firstTenantId);
|
||||
loginTenantId.value = firstTenantId;
|
||||
formModel.tenantId = firstTenantId;
|
||||
} else {
|
||||
// 如果全局状态已有值,同步到表单
|
||||
console.log('使用全局租户ID:', loginTenantId.value);
|
||||
formModel.tenantId = loginTenantId.value;
|
||||
}
|
||||
console.log('最终表单租户ID:', formModel.tenantId);
|
||||
} catch (error) {
|
||||
console.error('加载租户列表失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadTenant();
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
console.log('=== 开始登录流程 ===');
|
||||
console.log('表单租户ID:', formModel.tenantId);
|
||||
console.log('全局租户ID:', loginTenantId.value);
|
||||
|
||||
await formRef.value?.validate();
|
||||
const res = await login(formModel);
|
||||
console.log(res.data.access_token, 'res');
|
||||
console.log('登录响应:', res.data.access_token, 'res');
|
||||
res.data.access_token && userStore.setToken(res.data.access_token);
|
||||
// res.data.userInfo && userStore.setUserInfo(res.data.userInfo);
|
||||
ElMessage.success('登录成功');
|
||||
@@ -44,7 +97,7 @@ async function handleSubmit() {
|
||||
router.replace('/');
|
||||
}
|
||||
catch (error) {
|
||||
console.error('请求错误:', error);
|
||||
console.error('登录请求错误:', error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -58,6 +111,18 @@ async function handleSubmit() {
|
||||
style="width: 230px"
|
||||
@submit.prevent="handleSubmit"
|
||||
>
|
||||
<!-- 租户选择(仅当启用多租户时显示) -->
|
||||
<el-form-item v-if="tenantInfo.tenantEnabled" prop="tenantId">
|
||||
<el-select v-model="formModel.tenantId" placeholder="请选择租户" style="width: 100%">
|
||||
<el-option
|
||||
v-for="tenant in tenantInfo.voList"
|
||||
:key="tenant.tenantId"
|
||||
:label="tenant.companyName"
|
||||
:value="tenant.tenantId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model="formModel.username" placeholder="请输入用户名">
|
||||
<template #prefix>
|
||||
|
||||
15
hzhub-portal-dealer/src/hooks/useLoginTenantId.ts
Normal file
15
hzhub-portal-dealer/src/hooks/useLoginTenantId.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ref } from 'vue';
|
||||
import { createGlobalState } from '@vueuse/core';
|
||||
|
||||
/**
|
||||
* 全局租户ID状态
|
||||
* 使用 createGlobalState 确保组件重新挂载时状态不会丢失
|
||||
* @see https://vueuse.org/shared/createGlobalState/
|
||||
*/
|
||||
export const useLoginTenantId = createGlobalState(() => {
|
||||
const loginTenantId = ref('000000');
|
||||
|
||||
return {
|
||||
loginTenantId,
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user