feat: 完善多租户客户切换和数据隔离功能
## 主要改进 ### 1. 修复JavaScript数字精度问题 - 将所有customerId从number类型改为string类型,避免大整数精度丢失 - 修复customerId=1968304677554302978被截断为1968304677554303000的问题 - 更新CustomerSwitcher、CustomerSelector等组件的类型定义 ### 2. 完善客户切换功能 - 新增TenantInfo组件显示当前租户信息 - 修复CustomerSwitcher下拉选择事件处理 - 完善客户切换的用户体验和错误处理 ### 3. 实现真正的多租户数据隔离 - 后端SysOrgUnitsSearchDTO新增customerId字段支持 - SysOrgUnitsFacadeImpl中实现customerId到id的自动转换 - 修改/sys_org_units/page和/sys_org_units/tree接口支持customerId过滤 ### 4. 优化API调用机制 - 前端移除手动传递id=0的逻辑 - 让请求拦截器自动注入customerId参数 - 后端自动将customerId转换为有效的查询条件 ### 5. 新增客户配置管理 - TCustomerConfigController新增/all-customers接口 - 为客户选择器提供标准的客户列表API ## 技术细节 - 前端:Vue 3 + TypeScript + Pinia状态管理 - 后端:Spring Boot + MyBatis-Plus - 数据隔离:customerId即sys_org_units表的id字段 - 精度安全:JavaScript大整数使用string类型处理 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
This commit is contained in:
@@ -13,8 +13,8 @@ LJWX_ADMIN_VERSION=2.0.1
|
||||
# 服务器IP地址 - 根据实际部署环境修改
|
||||
SERVER_IP=192.168.1.83
|
||||
# 各服务外部端口配置
|
||||
MYSQL_EXTERNAL_PORT=3306
|
||||
REDIS_EXTERNAL_PORT=6379
|
||||
MYSQL_EXTERNAL_PORT=23306
|
||||
REDIS_EXTERNAL_PORT=26379
|
||||
LJWX_BOOT_EXTERNAL_PORT=9999
|
||||
LJWX_BIGSCREEN_EXTERNAL_PORT=8001
|
||||
LJWX_ADMIN_EXTERNAL_PORT=8088
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<div class="customer-selector">
|
||||
<NSelect
|
||||
v-model:value="selectedCustomerId"
|
||||
:options="customerOptions"
|
||||
:loading="loading"
|
||||
placeholder="请选择客户"
|
||||
filterable
|
||||
clearable
|
||||
@update:value="handleCustomerChange"
|
||||
>
|
||||
<template #empty>
|
||||
<div class="text-center py-4 text-gray-500">
|
||||
{{ loading ? '加载中...' : '暂无客户数据' }}
|
||||
</div>
|
||||
</template>
|
||||
</NSelect>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import type { SelectOption } from 'naive-ui';
|
||||
import { NSelect } from 'naive-ui';
|
||||
import { fetchGetAllAvailableCustomers } from '@/service/api/customer/customer';
|
||||
import { useAuthStore } from '@/store/modules/auth';
|
||||
|
||||
interface CustomerOption {
|
||||
customerId: string;
|
||||
customerName: string;
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [customerId: string | null, customerName: string | null];
|
||||
}>();
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const loading = ref(false);
|
||||
const customers = ref<CustomerOption[]>([]);
|
||||
const selectedCustomerId = ref<string | null>(null);
|
||||
|
||||
// 转换为 NSelect 需要的选项格式
|
||||
const customerOptions = computed<SelectOption[]>(() => {
|
||||
return customers.value.map(customer => ({
|
||||
label: customer.customerName,
|
||||
value: customer.customerId
|
||||
}));
|
||||
});
|
||||
|
||||
// 获取客户列表
|
||||
const loadCustomers = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const { data } = await fetchGetAllAvailableCustomers();
|
||||
// 转换 customerId 为字符串类型避免精度问题
|
||||
customers.value = (data || []).map(item => ({
|
||||
customerId: item.customerId.toString(),
|
||||
customerName: item.customerName
|
||||
}));
|
||||
|
||||
// 如果当前用户有 customerId,设置为默认选中
|
||||
if (authStore.userInfo.customerId && authStore.userInfo.customerId !== 0) {
|
||||
selectedCustomerId.value = authStore.userInfo.customerId.toString();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载客户列表失败:', error);
|
||||
customers.value = [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理客户切换
|
||||
const handleCustomerChange = (customerId: string | null) => {
|
||||
const selectedCustomer = customers.value.find(c => c.customerId === customerId);
|
||||
const customerName = selectedCustomer?.customerName || null;
|
||||
|
||||
console.log('客户切换:', { customerId, customerName });
|
||||
|
||||
// 发送事件给父组件
|
||||
emit('change', customerId, customerName);
|
||||
|
||||
// 这里可以设置全局的 customerId
|
||||
// 例如保存到 localStorage 或者 store 中
|
||||
if (customerId) {
|
||||
localStorage.setItem('selectedCustomerId', customerId);
|
||||
localStorage.setItem('selectedCustomerName', customerName || '');
|
||||
} else {
|
||||
localStorage.removeItem('selectedCustomerId');
|
||||
localStorage.removeItem('selectedCustomerName');
|
||||
}
|
||||
};
|
||||
|
||||
// 获取当前选中的客户信息
|
||||
const getCurrentCustomer = () => {
|
||||
return customers.value.find(c => c.customerId === selectedCustomerId.value) || null;
|
||||
};
|
||||
|
||||
// 设置选中的客户
|
||||
const setSelectedCustomer = (customerId: string | null) => {
|
||||
selectedCustomerId.value = customerId;
|
||||
handleCustomerChange(customerId);
|
||||
};
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
getCurrentCustomer,
|
||||
setSelectedCustomer,
|
||||
loadCustomers
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
loadCustomers();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.customer-selector {
|
||||
min-width: 200px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { ref, computed, onMounted, h } from 'vue';
|
||||
import { NDropdown, NButton, NInput, NSpace, NSpin, NEmpty, NTag, NAvatar } from 'naive-ui';
|
||||
import type { DropdownOption } from 'naive-ui';
|
||||
import { useCustomerStore } from '@/store/modules/customer';
|
||||
@@ -66,10 +66,7 @@ const dropdownOptions = computed((): DropdownOption[] => {
|
||||
.slice(0, 3)
|
||||
.map(customer => ({
|
||||
key: `recent-${customer.id}`,
|
||||
label: customer.name,
|
||||
props: {
|
||||
onClick: () => handleCustomerSwitch(customer.id)
|
||||
}
|
||||
label: customer.name
|
||||
}))
|
||||
});
|
||||
}
|
||||
@@ -84,10 +81,7 @@ const dropdownOptions = computed((): DropdownOption[] => {
|
||||
.filter(customer => customer.id !== currentCustomer.value?.id)
|
||||
.map(customer => ({
|
||||
key: `all-${customer.id}`,
|
||||
label: customer.name,
|
||||
props: {
|
||||
onClick: () => handleCustomerSwitch(customer.id)
|
||||
}
|
||||
label: customer.name
|
||||
}))
|
||||
});
|
||||
}
|
||||
@@ -95,15 +89,29 @@ const dropdownOptions = computed((): DropdownOption[] => {
|
||||
return options;
|
||||
});
|
||||
|
||||
// 处理下拉选择
|
||||
const handleSelect = (key: string) => {
|
||||
console.log('下拉选择:', key);
|
||||
|
||||
// 解析客户ID
|
||||
const match = key.match(/(recent|all)-(.+)/);
|
||||
if (match) {
|
||||
const customerId = match[2]; // 保持为字符串
|
||||
handleCustomerSwitch(customerId);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理客户切换
|
||||
const handleCustomerSwitch = async (customerId: number) => {
|
||||
const handleCustomerSwitch = async (customerId: string) => { // 改为字符串类型
|
||||
try {
|
||||
console.log('开始切换客户:', customerId);
|
||||
await customerStore.switchCustomer(customerId);
|
||||
dropdownVisible.value = false;
|
||||
searchValue.value = '';
|
||||
console.log('客户切换成功');
|
||||
} catch (error) {
|
||||
console.error('切换客户失败:', error);
|
||||
// 这里可以添加错误提示
|
||||
window.$message?.error('切换客户失败');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -130,8 +138,17 @@ const getCustomerAvatar = (customer: typeof currentCustomer.value) => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
console.log('[CustomerSwitcher] 组件挂载:', {
|
||||
canSwitch: canSwitch.value,
|
||||
currentCustomer: currentCustomer.value,
|
||||
currentCustomerId: customerStore.currentCustomerId,
|
||||
customerListLength: customerStore.customerList.length,
|
||||
userName: authStore.userInfo?.userName
|
||||
});
|
||||
|
||||
// 组件挂载时加载客户列表(如果有权限)
|
||||
if (canSwitch.value && customerStore.customerList.length === 0) {
|
||||
console.log('[CustomerSwitcher] 开始加载客户列表');
|
||||
customerStore.loadCustomerList();
|
||||
}
|
||||
});
|
||||
@@ -148,6 +165,7 @@ onMounted(() => {
|
||||
size="medium"
|
||||
:show-arrow="false"
|
||||
@show="handleDropdownShow"
|
||||
@select="handleSelect"
|
||||
>
|
||||
<template #default>
|
||||
<NButton
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div v-if="showTenantInfo" class="tenant-info">
|
||||
<div class="tenant-badge">
|
||||
<SvgIcon icon="mdi:domain" class="text-blue-500" />
|
||||
<span class="tenant-text">
|
||||
<span class="label">当前租户:</span>
|
||||
<span class="tenant-name">{{ currentCustomer?.name || '未选择' }}</span>
|
||||
<span v-if="currentCustomer?.id" class="tenant-id">(ID: {{ currentCustomer.id }})</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, watchEffect } from 'vue';
|
||||
import SvgIcon from '@/components/custom/svg-icon.vue';
|
||||
import { useAuthStore } from '@/store/modules/auth';
|
||||
import { useCustomerStore } from '@/store/modules/customer';
|
||||
|
||||
defineOptions({
|
||||
name: 'TenantInfo'
|
||||
});
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const customerStore = useCustomerStore();
|
||||
|
||||
// 当前客户信息
|
||||
const currentCustomer = computed(() => customerStore.currentCustomer);
|
||||
|
||||
// 是否显示租户信息(只有admin用户才显示)
|
||||
const showTenantInfo = computed(() => {
|
||||
return authStore.userInfo?.userName === 'admin' && customerStore.canSwitchCustomer;
|
||||
});
|
||||
|
||||
// 调试信息
|
||||
watchEffect(() => {
|
||||
console.log('[TenantInfo] 状态更新:', {
|
||||
showTenantInfo: showTenantInfo.value,
|
||||
currentCustomer: currentCustomer.value,
|
||||
currentCustomerId: customerStore.currentCustomerId,
|
||||
userName: authStore.userInfo?.userName,
|
||||
canSwitch: customerStore.canSwitchCustomer
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tenant-info {
|
||||
@apply flex items-center;
|
||||
}
|
||||
|
||||
.tenant-badge {
|
||||
@apply flex items-center px-3 py-1.5 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-700;
|
||||
@apply text-sm font-medium;
|
||||
}
|
||||
|
||||
.tenant-text {
|
||||
@apply ml-2 flex items-center space-x-1;
|
||||
}
|
||||
|
||||
.label {
|
||||
@apply text-gray-600 dark:text-gray-400;
|
||||
}
|
||||
|
||||
.tenant-name {
|
||||
@apply text-blue-700 dark:text-blue-300 font-semibold;
|
||||
}
|
||||
|
||||
.tenant-id {
|
||||
@apply text-gray-500 dark:text-gray-400 text-xs;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.tenant-badge {
|
||||
@apply px-2 py-1 text-xs;
|
||||
}
|
||||
|
||||
.tenant-id {
|
||||
@apply hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,8 @@
|
||||
import { computed } from 'vue';
|
||||
import { useAuthStore } from '@/store/modules/auth';
|
||||
import { useCustomerStore, type CustomerAccess } from '@/store/modules/customer';
|
||||
import { fetchGetAccessibleCustomers, fetchCustomerIdByOrgId } from '@/service/api/customer';
|
||||
import { fetchGetAllAvailableCustomers } from '@/service/api/customer/customer';
|
||||
import { fetchCustomerIdByOrgId } from '@/service/api';
|
||||
|
||||
/**
|
||||
* 客户业务逻辑 Hook
|
||||
@@ -11,7 +12,7 @@ export function useCustomer() {
|
||||
const customerStore = useCustomerStore();
|
||||
|
||||
// 当前用户角色
|
||||
const userRole = computed(() => authStore.userInfo?.roles?.[0] || '');
|
||||
const userRole = computed(() => authStore.userInfo?.roleIds?.[0] || '');
|
||||
const userInfo = computed(() => authStore.userInfo);
|
||||
|
||||
// 是否是 admin 用户
|
||||
@@ -29,29 +30,38 @@ export function useCustomer() {
|
||||
|
||||
if (isAdmin.value) {
|
||||
// admin 用户:可以切换客户,需要加载所有客户列表
|
||||
const { data } = await fetchGetAccessibleCustomers();
|
||||
const { data } = await fetchGetAllAvailableCustomers();
|
||||
const customerList = (data || []).map(item => ({
|
||||
id: item.customerId.toString(), // 转换为字符串避免精度问题
|
||||
name: item.customerName,
|
||||
code: item.customerId.toString(),
|
||||
status: 'ACTIVE',
|
||||
description: `客户ID: ${item.customerId}`
|
||||
}));
|
||||
|
||||
return {
|
||||
canSwitch: true,
|
||||
defaultCustomerId: null, // 需要手动选择
|
||||
customerList: data || []
|
||||
customerList
|
||||
};
|
||||
} else {
|
||||
// 其他用户:不能切换,使用自己的客户ID
|
||||
let customerId = user.customerId;
|
||||
|
||||
// 如果用户没有直接的 customerId,尝试通过 orgId 获取
|
||||
if (!customerId && user.orgId && Array.isArray(user.orgId) && user.orgId.length > 0) {
|
||||
try {
|
||||
const { data } = await fetchCustomerIdByOrgId(user.orgId[0]);
|
||||
customerId = data.customerId;
|
||||
} catch (error) {
|
||||
console.error('根据组织ID获取客户ID失败:', error);
|
||||
}
|
||||
}
|
||||
// 注意:如果用户类型定义中没有 orgId 字段,这部分逻辑需要调整
|
||||
// if (!customerId && user.orgId && Array.isArray(user.orgId) && user.orgId.length > 0) {
|
||||
// try {
|
||||
// const { data } = await fetchCustomerIdByOrgId(user.orgId[0]);
|
||||
// customerId = data.customerId;
|
||||
// } catch (error) {
|
||||
// console.error('根据组织ID获取客户ID失败:', error);
|
||||
// }
|
||||
// }
|
||||
|
||||
return {
|
||||
canSwitch: false,
|
||||
defaultCustomerId: customerId || 0,
|
||||
defaultCustomerId: customerId ? customerId.toString() : null, // 转换为字符串
|
||||
customerList: []
|
||||
};
|
||||
}
|
||||
@@ -72,7 +82,8 @@ export function useCustomer() {
|
||||
customerStore.setCustomerAccess(access);
|
||||
|
||||
// 如果有默认客户ID且当前没有设置,则设置默认值
|
||||
if (access.defaultCustomerId && !customerStore.currentCustomerId) {
|
||||
// 注意:对于admin用户,优先使用localStorage中保存的customerId
|
||||
if (!customerStore.currentCustomerId && access.defaultCustomerId) {
|
||||
customerStore.setCurrentCustomerId(access.defaultCustomerId);
|
||||
}
|
||||
|
||||
@@ -116,7 +127,7 @@ export function useCustomer() {
|
||||
/**
|
||||
* 切换客户(仅超级管理员)
|
||||
*/
|
||||
const switchCustomer = async (customerId: number) => {
|
||||
const switchCustomer = async (customerId: string) => { // 改为字符串类型
|
||||
if (!customerStore.canSwitchCustomer) {
|
||||
throw new Error('当前用户无权限切换客户');
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import GlobalSearch from '../global-search/index.vue';
|
||||
import ThemeButton from './components/theme-button.vue';
|
||||
import UserAvatar from './components/user-avatar.vue';
|
||||
import CustomerSwitcher from '@/components/common/CustomerSwitcher.vue';
|
||||
import TenantInfo from '@/components/common/TenantInfo.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'GlobalHeader'
|
||||
@@ -44,6 +45,7 @@ const { isFullscreen, toggle } = useFullscreen();
|
||||
<LangSwitch :lang="appStore.locale" :lang-options="appStore.localeOptions" @change-lang="appStore.changeLocale" />
|
||||
<ThemeSchemaSwitch :theme-schema="themeStore.themeScheme" :is-dark="themeStore.darkMode" @switch="themeStore.toggleThemeScheme" />
|
||||
<ThemeButton />
|
||||
<TenantInfo class="mr-3" />
|
||||
<CustomerSwitcher class="mr-3" />
|
||||
<UserAvatar />
|
||||
</div>
|
||||
|
||||
@@ -81,6 +81,7 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
|
||||
monitor_system: () => import("@/views/monitor/system/index.vue"),
|
||||
"multi-menu_first_child": () => import("@/views/multi-menu/first_child/index.vue"),
|
||||
"multi-menu_second_child_home": () => import("@/views/multi-menu/second_child_home/index.vue"),
|
||||
"system_customer-select": () => import("@/views/system/customer-select/index.vue"),
|
||||
"system_dynamic-menu": () => import("@/views/system/dynamic-menu/index.vue"),
|
||||
system_license: () => import("@/views/system/license/index.vue"),
|
||||
task: () => import("@/views/task/index.vue"),
|
||||
|
||||
@@ -829,6 +829,15 @@ export const generatedRoutes: GeneratedRoute[] = [
|
||||
i18nKey: 'route.system'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'system_customer-select',
|
||||
path: '/system/customer-select',
|
||||
component: 'view.system_customer-select',
|
||||
meta: {
|
||||
title: 'system_customer-select',
|
||||
i18nKey: 'route.system_customer-select'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system_dynamic-menu',
|
||||
path: '/system/dynamic-menu',
|
||||
|
||||
@@ -255,6 +255,7 @@ const routeMap: RouteMap = {
|
||||
"multi-menu_second_child": "/multi-menu/second/child",
|
||||
"multi-menu_second_child_home": "/multi-menu/second/child/home",
|
||||
"system": "/system",
|
||||
"system_customer-select": "/system/customer-select",
|
||||
"system_dynamic-menu": "/system/dynamic-menu",
|
||||
"system_license": "/system/license",
|
||||
"task": "/task",
|
||||
|
||||
@@ -78,4 +78,14 @@ export function fetchRecordCustomerSwitch(data: {
|
||||
method: 'POST',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
// =============== Customer Config APIs ===============
|
||||
|
||||
/** 获取所有可用的客户列表(用于客户选择器) */
|
||||
export function fetchGetAllAvailableCustomers() {
|
||||
return request<Array<{ customerId: number; customerName: string }>>({
|
||||
url: '/t_customer_config/all-customers',
|
||||
method: 'GET'
|
||||
});
|
||||
}
|
||||
@@ -91,10 +91,12 @@ export function fetchDeleteOrgUnits(data: Api.Common.DeleteParams) {
|
||||
}
|
||||
|
||||
/** get org page tree */
|
||||
export function fetchGetOrgUnitsTree(id: number) {
|
||||
export function fetchGetOrgUnitsTree(id?: number) {
|
||||
const params = id ? { id } : {};
|
||||
return request<Api.SystemManage.OrgUnitsTree[]>({
|
||||
url: `/sys_org_units/tree?id=${id}`,
|
||||
method: 'GET'
|
||||
url: '/sys_org_units/tree',
|
||||
method: 'GET',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { AxiosResponse } from 'axios';
|
||||
import { BACKEND_ERROR_CODE, REQUEST_LANGUAGE, createFlatRequest, createRequest } from '@sa/axios';
|
||||
import { useAuthStore } from '@/store/modules/auth';
|
||||
import { useCustomerStore } from '@/store/modules/customer';
|
||||
import { $t } from '@/locales';
|
||||
import { localStg } from '@/utils/storage';
|
||||
import { getServiceBaseURL } from '@/utils/service';
|
||||
@@ -28,15 +29,35 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
|
||||
{
|
||||
async onRequest(config) {
|
||||
const authStore = useAuthStore();
|
||||
const customerStore = useCustomerStore();
|
||||
const Authorization = getAuthorization();
|
||||
const language = getLanguage();
|
||||
|
||||
// 确定要使用的customerId
|
||||
let customerId = authStore.userInfo.customerId;
|
||||
|
||||
// 如果是admin用户且已选择客户,使用选中的客户ID
|
||||
if (authStore.userInfo?.userName === 'admin' && customerStore.currentCustomerId) {
|
||||
customerId = customerStore.currentCustomerId;
|
||||
}
|
||||
|
||||
// 自动添加customerId (排除白名单URL)
|
||||
const shouldAddCustomerId = authStore.userInfo.customerId !== undefined &&
|
||||
const shouldAddCustomerId = customerId !== undefined &&
|
||||
!EXCLUDE_CUSTOMER_ID_URLS.some(excludeUrl => config.url?.includes(excludeUrl));
|
||||
// 调试信息
|
||||
if (import.meta.env.DEV) {
|
||||
console.log('[Request] customerId injection:', {
|
||||
url: config.url,
|
||||
method: config.method,
|
||||
userCustomerId: authStore.userInfo.customerId,
|
||||
selectedCustomerId: customerStore.currentCustomerId,
|
||||
finalCustomerId: customerId,
|
||||
userName: authStore.userInfo?.userName,
|
||||
shouldAdd: shouldAddCustomerId
|
||||
});
|
||||
}
|
||||
|
||||
if (shouldAddCustomerId) {
|
||||
const customerId = authStore.userInfo.customerId;
|
||||
|
||||
if (config.method?.toLowerCase() === 'get') {
|
||||
// GET请求:添加到查询参数
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref, computed } from 'vue';
|
||||
import { SetupStoreId } from '@/enum';
|
||||
import { fetchGetCustomerList, fetchCustomerIdByOrgId } from '@/service/api';
|
||||
import { fetchGetAllAvailableCustomers } from '@/service/api/customer/customer';
|
||||
import { fetchCustomerIdByOrgId } from '@/service/api';
|
||||
|
||||
export interface CustomerInfo {
|
||||
id: number;
|
||||
id: string; // 改为字符串类型避免精度问题
|
||||
name: string;
|
||||
code?: string;
|
||||
logo?: string;
|
||||
@@ -14,16 +15,16 @@ export interface CustomerInfo {
|
||||
|
||||
export interface CustomerAccess {
|
||||
canSwitch: boolean;
|
||||
defaultCustomerId: number | null;
|
||||
defaultCustomerId: string | null; // 改为字符串类型
|
||||
customerList: CustomerInfo[];
|
||||
}
|
||||
|
||||
export const useCustomerStore = defineStore(SetupStoreId.Customer, () => {
|
||||
// 状态
|
||||
const currentCustomerId = ref<number | null>(null);
|
||||
const currentCustomerId = ref<string | null>(null); // 改为字符串类型
|
||||
const customerList = ref<CustomerInfo[]>([]);
|
||||
const canSwitchCustomer = ref(false);
|
||||
const recentCustomers = ref<number[]>([]);
|
||||
const recentCustomers = ref<string[]>([]); // 改为字符串数组
|
||||
const loading = ref(false);
|
||||
|
||||
// 计算属性
|
||||
@@ -43,7 +44,7 @@ export const useCustomerStore = defineStore(SetupStoreId.Customer, () => {
|
||||
});
|
||||
|
||||
// Actions
|
||||
const setCurrentCustomerId = (customerId: number | null) => {
|
||||
const setCurrentCustomerId = (customerId: string | null) => { // 改为字符串类型
|
||||
currentCustomerId.value = customerId;
|
||||
|
||||
// 更新最近访问记录
|
||||
@@ -56,7 +57,7 @@ export const useCustomerStore = defineStore(SetupStoreId.Customer, () => {
|
||||
|
||||
// 持久化到 localStorage
|
||||
if (customerId) {
|
||||
localStorage.setItem('currentCustomerId', customerId.toString());
|
||||
localStorage.setItem('currentCustomerId', customerId); // 直接存储字符串
|
||||
localStorage.setItem('recentCustomers', JSON.stringify(recentCustomers.value));
|
||||
} else {
|
||||
localStorage.removeItem('currentCustomerId');
|
||||
@@ -77,14 +78,17 @@ export const useCustomerStore = defineStore(SetupStoreId.Customer, () => {
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
const { data } = await fetchGetCustomerList({
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
status: 'ACTIVE'
|
||||
});
|
||||
const { data } = await fetchGetAllAvailableCustomers();
|
||||
|
||||
if (data?.records) {
|
||||
customerList.value = data.records;
|
||||
// 转换数据格式
|
||||
if (data) {
|
||||
customerList.value = data.map(item => ({
|
||||
id: item.customerId.toString(), // 转换为字符串避免精度问题
|
||||
name: item.customerName,
|
||||
code: item.customerId.toString(),
|
||||
status: 'ACTIVE', // 接口返回的都是可用的客户
|
||||
description: `客户ID: ${item.customerId}`
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载客户列表失败:', error);
|
||||
@@ -93,7 +97,7 @@ export const useCustomerStore = defineStore(SetupStoreId.Customer, () => {
|
||||
}
|
||||
};
|
||||
|
||||
const switchCustomer = async (customerId: number) => {
|
||||
const switchCustomer = async (customerId: string) => { // 改为字符串类型
|
||||
if (!canSwitchCustomer.value) {
|
||||
console.warn('当前用户无权限切换客户');
|
||||
return;
|
||||
@@ -102,17 +106,26 @@ export const useCustomerStore = defineStore(SetupStoreId.Customer, () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
console.log('切换客户: customerId =', customerId);
|
||||
|
||||
// 设置新的客户ID
|
||||
setCurrentCustomerId(customerId);
|
||||
|
||||
// 清理相关缓存
|
||||
await clearCustomerRelatedCache();
|
||||
|
||||
// 重新加载页面数据
|
||||
window.location.reload();
|
||||
// 显示成功消息
|
||||
const customerName = customerList.value.find(c => c.id === customerId)?.name || customerId;
|
||||
window.$message?.success(`已切换到客户: ${customerName}`);
|
||||
|
||||
// 延迟一下再重新加载页面,让用户看到成功消息
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 500);
|
||||
|
||||
} catch (error) {
|
||||
console.error('切换客户失败:', error);
|
||||
window.$message?.error('切换客户失败');
|
||||
throw error;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
@@ -151,7 +164,7 @@ export const useCustomerStore = defineStore(SetupStoreId.Customer, () => {
|
||||
const savedRecentCustomers = localStorage.getItem('recentCustomers');
|
||||
|
||||
if (savedCustomerId) {
|
||||
currentCustomerId.value = parseInt(savedCustomerId);
|
||||
currentCustomerId.value = savedCustomerId; // 直接使用字符串,不进行数字转换
|
||||
}
|
||||
|
||||
if (savedRecentCustomers) {
|
||||
|
||||
+2
@@ -12,6 +12,7 @@ declare module 'vue' {
|
||||
ButtonIcon: typeof import('./../components/custom/button-icon.vue')['default']
|
||||
CountTo: typeof import('./../components/custom/count-to.vue')['default']
|
||||
CustomerSelectionPrompt: typeof import('./../components/common/CustomerSelectionPrompt.vue')['default']
|
||||
CustomerSelector: typeof import('./../components/business/CustomerSelector.vue')['default']
|
||||
CustomerSwitcher: typeof import('./../components/common/CustomerSwitcher.vue')['default']
|
||||
DarkModeContainer: typeof import('./../components/common/dark-mode-container.vue')['default']
|
||||
ExceptionBase: typeof import('./../components/common/exception-base.vue')['default']
|
||||
@@ -129,6 +130,7 @@ declare module 'vue' {
|
||||
SystemLogo: typeof import('./../components/common/system-logo.vue')['default']
|
||||
TableColumnSetting: typeof import('./../components/advanced/table-column-setting.vue')['default']
|
||||
TableHeaderOperation: typeof import('./../components/advanced/table-header-operation.vue')['default']
|
||||
TenantInfo: typeof import('./../components/common/TenantInfo.vue')['default']
|
||||
ThemeSchemaSwitch: typeof import('./../components/common/theme-schema-switch.vue')['default']
|
||||
WaveBg: typeof import('./../components/custom/wave-bg.vue')['default']
|
||||
}
|
||||
|
||||
+2
@@ -109,6 +109,7 @@ declare module "@elegant-router/types" {
|
||||
"multi-menu_second_child": "/multi-menu/second/child";
|
||||
"multi-menu_second_child_home": "/multi-menu/second/child/home";
|
||||
"system": "/system";
|
||||
"system_customer-select": "/system/customer-select";
|
||||
"system_dynamic-menu": "/system/dynamic-menu";
|
||||
"system_license": "/system/license";
|
||||
"task": "/task";
|
||||
@@ -268,6 +269,7 @@ declare module "@elegant-router/types" {
|
||||
| "monitor_system"
|
||||
| "multi-menu_first_child"
|
||||
| "multi-menu_second_child_home"
|
||||
| "system_customer-select"
|
||||
| "system_dynamic-menu"
|
||||
| "system_license"
|
||||
| "task"
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { useAuthStore } from '@/store/modules/auth';
|
||||
|
||||
/**
|
||||
* 检查用户是否有指定权限
|
||||
* @param permission 权限码,如 'license:management:import'
|
||||
* @returns boolean
|
||||
*/
|
||||
export function hasPermission(permission: string): boolean {
|
||||
const authStore = useAuthStore();
|
||||
|
||||
// 如果没有权限数组,返回 false
|
||||
if (!authStore.userInfo.permissions || authStore.userInfo.permissions.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查权限数组中是否包含指定权限
|
||||
return authStore.userInfo.permissions.includes(permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否有任意一个权限
|
||||
* @param permissions 权限码数组
|
||||
* @returns boolean
|
||||
*/
|
||||
export function hasAnyPermission(permissions: string[]): boolean {
|
||||
return permissions.some(permission => hasPermission(permission));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否拥有所有指定权限
|
||||
* @param permissions 权限码数组
|
||||
* @returns boolean
|
||||
*/
|
||||
export function hasAllPermissions(permissions: string[]): boolean {
|
||||
return permissions.every(permission => hasPermission(permission));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否为超级管理员
|
||||
* @returns boolean
|
||||
*/
|
||||
export function isSuperAdmin(): boolean {
|
||||
const authStore = useAuthStore();
|
||||
return authStore.isStaticSuper;
|
||||
}
|
||||
@@ -21,3 +21,14 @@ export function convertToBeijingTime(timestamp: dayjs.ConfigType) {
|
||||
export function formatDate(date: dayjs.ConfigType, format: string = 'YYYY-MM-DD') {
|
||||
return dayjs(date).format(format);
|
||||
}
|
||||
|
||||
/**
|
||||
* format time
|
||||
*
|
||||
* @param time time
|
||||
* @param format format pattern, default is 'HH:mm:ss'
|
||||
* @returns formatted time
|
||||
*/
|
||||
export function formatTime(time: dayjs.ConfigType, format: string = 'HH:mm:ss') {
|
||||
return dayjs(time).format(format);
|
||||
}
|
||||
|
||||
@@ -48,8 +48,8 @@ const { columns, columnChecks, data, loading, getData, getDataByPage, mobilePagi
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
name: null,
|
||||
status: null,
|
||||
id: authStore.userInfo?.customerId // 使用用户的customerId作为orgId查询参数
|
||||
status: null
|
||||
// customerId 将由请求拦截器自动添加
|
||||
},
|
||||
columns: () => [
|
||||
{
|
||||
|
||||
@@ -40,7 +40,7 @@ const editingData: Ref<Api.SystemManage.OrgUnits | null> = ref(null);
|
||||
|
||||
/** init */
|
||||
const init = async () => {
|
||||
const { data, error } = await fetchGetOrgUnitsTree(authStore.userInfo?.customerId);
|
||||
const { data, error } = await fetchGetOrgUnitsTree();
|
||||
if (!error && data) {
|
||||
orgUnitsTreeData.value = data;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<div class="h-full overflow-hidden">
|
||||
<NCard title="客户选择" class="h-full shadow-sm">
|
||||
<div class="space-y-6">
|
||||
<!-- 当前客户信息 -->
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<h3 class="text-lg font-medium text-blue-900 mb-2">当前选中客户</h3>
|
||||
<div v-if="currentCustomer" class="text-blue-700">
|
||||
<p><span class="font-medium">客户ID:</span> {{ currentCustomer.customerId }}</p>
|
||||
<p><span class="font-medium">客户名称:</span> {{ currentCustomer.customerName }}</p>
|
||||
</div>
|
||||
<div v-else class="text-gray-500">
|
||||
未选择客户
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- API 请求信息 -->
|
||||
<div class="bg-green-50 border border-green-200 rounded-lg p-4">
|
||||
<h3 class="text-lg font-medium text-green-900 mb-2">API 请求参数</h3>
|
||||
<div class="text-green-700">
|
||||
<p><span class="font-medium">当前用户角色:</span> {{ userInfo?.userName || 'N/A' }}</p>
|
||||
<p><span class="font-medium">用户原始customerId:</span> {{ userInfo?.customerId || 'N/A' }}</p>
|
||||
<p><span class="font-medium">选择器选中customerId:</span> {{ customerStore.currentCustomerId || 'N/A' }}</p>
|
||||
<p><span class="font-medium">实际使用customerId:</span> {{ getEffectiveCustomerId() }}</p>
|
||||
<p class="text-sm text-green-600 mt-2">
|
||||
说明:{{ getCustomerIdExplanation() }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 客户选择器 -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-lg font-medium text-gray-900">选择客户</h3>
|
||||
<div class="flex items-center space-x-4">
|
||||
<label class="text-sm font-medium text-gray-700 whitespace-nowrap">
|
||||
客户:
|
||||
</label>
|
||||
<div class="flex-1 max-w-md">
|
||||
<CustomerSelector
|
||||
ref="customerSelectorRef"
|
||||
@change="handleCustomerChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作说明 */
|
||||
<div class="bg-gray-50 border border-gray-200 rounded-lg p-4">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">功能说明</h3>
|
||||
<ul class="text-sm text-gray-600 space-y-1">
|
||||
<li>• 管理员可以切换不同的客户进行管理</li>
|
||||
<li>• 选择的客户ID会全局生效,影响后续的数据查询</li>
|
||||
<li>• 客户信息会保存在本地存储中</li>
|
||||
<li>• 普通用户只能看到自己所属的客户</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 */
|
||||
<div class="flex space-x-4">
|
||||
<NButton type="primary" @click="refreshCustomerList">
|
||||
<template #icon>
|
||||
<SvgIcon icon="mdi:refresh" />
|
||||
</template>
|
||||
刷新客户列表
|
||||
</NButton>
|
||||
|
||||
<NButton @click="clearSelection">
|
||||
<template #icon>
|
||||
<SvgIcon icon="mdi:close" />
|
||||
</template>
|
||||
清除选择
|
||||
</NButton>
|
||||
|
||||
<NButton type="info" @click="testApiCall">
|
||||
<template #icon>
|
||||
<SvgIcon icon="mdi:api" />
|
||||
</template>
|
||||
测试API调用
|
||||
</NButton>
|
||||
</div>
|
||||
|
||||
<!-- 调试信息 -->
|
||||
<div v-if="showDebug" class="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
||||
<h3 class="text-lg font-medium text-yellow-900 mb-2">调试信息</h3>
|
||||
<pre class="text-xs text-yellow-700 overflow-auto">{{ debugInfo }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</NCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { NCard, NButton } from 'naive-ui';
|
||||
import SvgIcon from '@/components/custom/svg-icon.vue';
|
||||
import CustomerSelector from '@/components/business/CustomerSelector.vue';
|
||||
import { useAuthStore } from '@/store/modules/auth';
|
||||
import { useCustomerStore } from '@/store/modules/customer';
|
||||
import { fetchGetAllAvailableCustomers } from '@/service/api/customer/customer';
|
||||
|
||||
// Store
|
||||
const authStore = useAuthStore();
|
||||
const customerStore = useCustomerStore();
|
||||
|
||||
// 组件引用
|
||||
const customerSelectorRef = ref<InstanceType<typeof CustomerSelector>>();
|
||||
|
||||
// 当前选中的客户信息
|
||||
const currentCustomer = ref<{ customerId: string; customerName: string } | null>(null);
|
||||
|
||||
// 用户信息
|
||||
const userInfo = computed(() => authStore.userInfo);
|
||||
|
||||
// 是否显示调试信息
|
||||
const showDebug = ref(false);
|
||||
|
||||
// 调试信息
|
||||
const debugInfo = computed(() => {
|
||||
return {
|
||||
currentCustomer: currentCustomer.value,
|
||||
localStorage: {
|
||||
selectedCustomerId: localStorage.getItem('selectedCustomerId'),
|
||||
selectedCustomerName: localStorage.getItem('selectedCustomerName')
|
||||
},
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
});
|
||||
|
||||
// 处理客户切换
|
||||
const handleCustomerChange = (customerId: string | null, customerName: string | null) => {
|
||||
console.log('客户切换事件:', { customerId, customerName });
|
||||
|
||||
if (customerId && customerName) {
|
||||
currentCustomer.value = { customerId, customerName };
|
||||
window.$message?.success(`已切换到客户: ${customerName} (ID: ${customerId})`);
|
||||
} else {
|
||||
currentCustomer.value = null;
|
||||
window.$message?.info('已清除客户选择');
|
||||
}
|
||||
};
|
||||
|
||||
// 刷新客户列表
|
||||
const refreshCustomerList = () => {
|
||||
customerSelectorRef.value?.loadCustomers();
|
||||
window.$message?.info('正在刷新客户列表...');
|
||||
};
|
||||
|
||||
// 清除选择
|
||||
const clearSelection = () => {
|
||||
customerSelectorRef.value?.setSelectedCustomer(null);
|
||||
currentCustomer.value = null;
|
||||
};
|
||||
|
||||
// 获取实际生效的 customerId
|
||||
const getEffectiveCustomerId = () => {
|
||||
const userCustomerId = userInfo.value?.customerId;
|
||||
const selectedCustomerId = customerStore.currentCustomerId;
|
||||
|
||||
if (userInfo.value?.userName === 'admin' && selectedCustomerId) {
|
||||
return selectedCustomerId;
|
||||
}
|
||||
return userCustomerId || 'N/A';
|
||||
};
|
||||
|
||||
// 获取 customerId 使用说明
|
||||
const getCustomerIdExplanation = () => {
|
||||
if (userInfo.value?.userName === 'admin') {
|
||||
if (customerStore.currentCustomerId) {
|
||||
return 'Admin用户,使用选择器选中的客户ID';
|
||||
} else {
|
||||
return 'Admin用户,未选择客户时使用用户原始ID';
|
||||
}
|
||||
} else {
|
||||
return '普通用户,使用用户自身的客户ID';
|
||||
}
|
||||
};
|
||||
|
||||
// 测试API调用
|
||||
const testApiCall = async () => {
|
||||
try {
|
||||
console.log('测试API调用开始');
|
||||
const response = await fetchGetAllAvailableCustomers();
|
||||
console.log('API响应:', response);
|
||||
window.$message?.success(`API调用成功,返回${response.data?.length || 0}个客户`);
|
||||
} catch (error) {
|
||||
console.error('API调用失败:', error);
|
||||
window.$message?.error('API调用失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 开发模式下显示调试信息
|
||||
if (import.meta.env.DEV) {
|
||||
showDebug.value = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
/* 可以添加一些自定义样式 */
|
||||
</style>
|
||||
+32
@@ -53,7 +53,9 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
import java.io.File;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Controller 控制层
|
||||
@@ -365,5 +367,35 @@ public class TCustomerConfigController {
|
||||
return Result.failure("更新失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有可用的客户列表(用于客户选择器)
|
||||
*/
|
||||
@GetMapping("/all-customers")
|
||||
@Operation(operationId = "12", summary = "获取所有可用的客户列表")
|
||||
public Result<List<Map<String, Object>>> getAllAvailableCustomers() {
|
||||
try {
|
||||
List<TCustomerConfig> customerConfigs = tCustomerConfigService.list();
|
||||
|
||||
List<Map<String, Object>> customers = customerConfigs.stream()
|
||||
.filter(config -> config.getIsDeleted() == null || config.getIsDeleted() == 0) // 过滤未删除的
|
||||
.map(config -> {
|
||||
Map<String, Object> customer = new HashMap<>();
|
||||
customer.put("customerId", config.getId());
|
||||
customer.put("customerName", config.getCustomerName());
|
||||
log.debug("Customer: id={}, name={}, isDeleted={}",
|
||||
config.getId(), config.getCustomerName(), config.getIsDeleted());
|
||||
return customer;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
log.info("获取可用客户列表成功,共{}个客户", customers.size());
|
||||
return Result.data(customers);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("获取可用客户列表失败", e);
|
||||
return Result.failure("获取客户列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+12
-2
@@ -54,6 +54,7 @@ public class SysOrgUnitsController {
|
||||
System.out.println(" id: " + sysOrgUnitsSearchDTO.getId());
|
||||
System.out.println(" name: " + sysOrgUnitsSearchDTO.getName());
|
||||
System.out.println(" status: " + sysOrgUnitsSearchDTO.getStatus());
|
||||
System.out.println(" customerId: " + sysOrgUnitsSearchDTO.getCustomerId());
|
||||
return Result.data(sysOrgUnitsFacade.listSysOrgUnitsPage(pageQuery, sysOrgUnitsSearchDTO));
|
||||
}
|
||||
|
||||
@@ -93,8 +94,17 @@ public class SysOrgUnitsController {
|
||||
@GetMapping("/tree")
|
||||
@SaCheckPermission("sys:org:units:tree")
|
||||
@Operation(operationId = "6", summary = "获取租户/部门树结构数据")
|
||||
public Result<List<SysOrgUnitsTreeVO>> tree(@RequestParam(value = "id", required = false) Long id) {
|
||||
return Result.data(sysOrgUnitsFacade.queryAllOrgUnitsListConvertToTree(id));
|
||||
public Result<List<SysOrgUnitsTreeVO>> tree(@RequestParam(value = "id", required = false) Long id,
|
||||
@RequestParam(value = "customerId", required = false) Long customerId) {
|
||||
System.out.println("🔍 SysOrgUnitsController.tree - 接收到参数:");
|
||||
System.out.println(" id: " + id);
|
||||
System.out.println(" customerId: " + customerId);
|
||||
|
||||
// 如果传入了customerId,说明要按租户过滤,将customerId作为id进行查询
|
||||
Long effectiveId = (customerId != null) ? customerId : id;
|
||||
System.out.println(" effectiveId: " + effectiveId);
|
||||
|
||||
return Result.data(sysOrgUnitsFacade.queryAllOrgUnitsListConvertToTree(effectiveId));
|
||||
}
|
||||
|
||||
@PostMapping("/delete-precheck")
|
||||
|
||||
+3
@@ -52,4 +52,7 @@ public class SysOrgUnitsSearchDTO implements Serializable {
|
||||
@Schema(description = "ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "客户ID")
|
||||
private Long customerId;
|
||||
|
||||
}
|
||||
+6
@@ -107,6 +107,12 @@ public class SysOrgUnitsFacadeImpl implements ISysOrgUnitsFacade {
|
||||
@Override
|
||||
public RPage<SysOrgUnitsTreeVO> listSysOrgUnitsPage(PageQuery pageQuery, SysOrgUnitsSearchDTO sysOrgUnitsSearchDTO) {
|
||||
SysOrgUnitsBO sysOrgUnitsBO = CglibUtil.convertObj(sysOrgUnitsSearchDTO, SysOrgUnitsBO::new);
|
||||
|
||||
// 如果传入了customerId,说明要按租户过滤,将customerId作为id进行查询
|
||||
if (sysOrgUnitsSearchDTO.getCustomerId() != null) {
|
||||
System.out.println("🔍 SysOrgUnitsFacadeImpl - 根据customerId过滤: " + sysOrgUnitsSearchDTO.getCustomerId());
|
||||
sysOrgUnitsBO.setId(sysOrgUnitsSearchDTO.getCustomerId());
|
||||
}
|
||||
IPage<SysOrgUnits> sysOrgUnitsIPage = sysOrgUnitsService.listSysOrgUnitsPage(pageQuery, sysOrgUnitsBO);
|
||||
List<SysOrgUnits> topOrgUnits = sysOrgUnitsIPage.getRecords();
|
||||
if (topOrgUnits.isEmpty()) {
|
||||
|
||||
+8
-3
@@ -102,7 +102,9 @@ public class OrgUnitsChangeListener {
|
||||
interfaceService.remove(new QueryWrapper<TInterface>().eq("customer_id", o.getId())); // 先清理
|
||||
List<TInterface> exists = interfaceService.list(new QueryWrapper<TInterface>().eq("customer_id", o.getId()));
|
||||
Set<String> existNames = exists.stream().map(TInterface::getName).collect(Collectors.toSet());
|
||||
interfaceService.list(new QueryWrapper<TInterface>().eq("customer_id", 1).eq("is_deleted", 0)).stream()
|
||||
List<TInterface> templateInterfaces = interfaceService.list(new QueryWrapper<TInterface>().eq("customer_id", 0).eq("is_deleted", 0));
|
||||
log.warn("Found {} template interfaces to clone", templateInterfaces.size());
|
||||
templateInterfaces.stream()
|
||||
.filter(i -> !existNames.contains(i.getName()))
|
||||
.forEach(i -> {
|
||||
log.warn("Inserting interface name={} for orgId={}", i.getName(), o.getId());
|
||||
@@ -113,7 +115,7 @@ public class OrgUnitsChangeListener {
|
||||
TInterface n = new TInterface();
|
||||
n.setName(i.getName()); n.setUrl(i.getUrl()); n.setCallInterval(i.getCallInterval());
|
||||
n.setMethod(i.getMethod()); n.setDescription(i.getDescription()); n.setEnabled(i.getEnabled());
|
||||
n.setCustomerId(o.getId()); n.setDeleted(0); n.setCreateTime(i.getCreateTime()); n.setUpdateTime(i.getUpdateTime());
|
||||
n.setCustomerId(o.getId()); n.setIsDeleted(0); n.setCreateTime(i.getCreateTime()); n.setUpdateTime(i.getUpdateTime());
|
||||
n.setApiAuth(i.getApiAuth());
|
||||
n.setApiId(i.getApiId());
|
||||
interfaceService.saveOrUpdate(n);
|
||||
@@ -123,7 +125,10 @@ public class OrgUnitsChangeListener {
|
||||
private void cloneHealthDataConfig(SysOrgUnits o) { // 复制健康数据配置
|
||||
log.warn("cloneHealthDataConfig called for orgId={}", o.getId());
|
||||
healthDataConfigService.remove(new QueryWrapper<THealthDataConfig>().eq("customer_id", o.getId())); // 先清理
|
||||
healthDataConfigService.list(new QueryWrapper<THealthDataConfig>().eq("customer_id", 1)).forEach(h -> {
|
||||
List<THealthDataConfig> templateConfigs = healthDataConfigService.list(new QueryWrapper<THealthDataConfig>().eq("customer_id", 0).eq("is_deleted", 0));
|
||||
log.warn("Found {} template health data configs to clone", templateConfigs.size());
|
||||
templateConfigs.forEach(h -> {
|
||||
log.warn("Cloning health data config: dataType={} for orgId={}", h.getDataType(), o.getId());
|
||||
THealthDataConfig n = new THealthDataConfig();
|
||||
n.setCustomerId(o.getId()); n.setDataType(h.getDataType()); n.setFrequencyInterval(h.getFrequencyInterval());
|
||||
n.setIsRealtime(h.getIsRealtime()); n.setIsEnabled(h.getIsEnabled()); n.setIsDefault(h.getIsDefault());
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
# 更新时间: 2025-06-26
|
||||
|
||||
# 核心应用版本
|
||||
LJWX_VERSION=2.0.2
|
||||
LJWX_MYSQL_VERSION=2.0.2
|
||||
LJWX_REDIS_VERSION=2.0.2
|
||||
LJWX_BOOT_VERSION=2.0.2
|
||||
LJWX_BIGSCREEN_VERSION=2.0.2
|
||||
LJWX_ADMIN_VERSION=2.0.2
|
||||
LJWX_VERSION=2.0.3
|
||||
LJWX_MYSQL_VERSION=2.0.3
|
||||
LJWX_REDIS_VERSION=2.0.3
|
||||
LJWX_BOOT_VERSION=2.0.3
|
||||
LJWX_BIGSCREEN_VERSION=2.0.3
|
||||
LJWX_ADMIN_VERSION=2.0.3
|
||||
|
||||
# 定制化监控组件版本
|
||||
LJWX_GRAFANA_VERSION=1.2.6
|
||||
|
||||
Reference in New Issue
Block a user