Files
im_manager/web/src/views/imTenantOptions.ts
T

125 lines
4.4 KiB
TypeScript

import { Ref } from 'vue';
import { List as TenantList } from '@/api/imTenant';
import { useUserStore } from '@/store/modules/user';
import type { FormSchema } from '@/components/Form';
export interface ImTenantOption {
label: string;
value: number;
code?: string;
name?: string;
}
export function formatImTenantLabel(item: any): string {
const code = item?.code || item?.tenantCode || '';
const name = item?.name || item?.tenantName || '当前商户';
return code ? `${name} (${code})` : name;
}
export function getDefaultTenantValue(options: any[]) {
const defaultTenant = options.find(
(item) => item.code === 'default' || String(item.label || '').includes('(default)')
);
return defaultTenant?.value || options.find((item) => Number(item.value) > 0)?.value || 0;
}
export async function loadImTenantOptions(
target: Ref<ImTenantOption[]>,
params: Record<string, any> = { page: 1, perPage: 100 }
) {
const boundTenantId = getBoundTenantId();
// 登录用户已绑定商户租户:仅返回自己商户选项(与后端 resolveImAdminScope 强制范围一致)
if (boundTenantId > 0) {
const info = readCurrentUserInfo();
try {
const res = await TenantList(params);
target.value = (res?.list || []).map(toTenantOption);
} catch {
const tenant = { id: boundTenantId, name: info?.tenantName, code: info?.tenantCode };
target.value = [toTenantOption(tenant)];
}
return target.value;
}
// 平台账号:加载全部商户;失败时保留旧选项,避免页面初始化被单个请求失败打断
try {
const res = await TenantList(params);
target.value = (res?.list || []).map(toTenantOption);
} catch (err) {
console.error('loadImTenantOptions failed:', err);
}
return target.value;
}
// 登录用户绑定的商户租户ID(按绑定租户判定,与后端 resolveImAdminScope 的范围逻辑一致)
export function getBoundTenantId(): number {
const userInfo = readCurrentUserInfo();
return Number(userInfo?.tenantId || userInfo?.tenantID || 0);
}
// 登录用户是否为商户绑定账号(true=下拉框只显示并锁定自己商户)
export function isTenantBound(): boolean {
return getBoundTenantId() > 0;
}
// 商户绑定账号:锁定搜索表单中的商户下拉项(仅一项、不可清除、不可更改)
export function lockTenantSchema(schema?: FormSchema) {
if (!schema || !isTenantBound()) return;
const props: any = (schema.componentProps = schema.componentProps || {});
props.disabled = true;
props.clearable = false;
}
/**
* 统一搜索重置回调:BasicForm 的 resetFields 会把包括 tenantId 在内的全部搜索字段清空,
* 随后的自动查询因缺商户条件命中前端空列表 guard(或被后端以「请选择IM租户」拒绝)。
* 此工厂返回的回调在 reset 之后把商户下拉回填为当前选项列表的第一项
* (商户绑定账号=自己商户;平台账号=全部商户列表第一项),再触发查询——
* 与各页面初始加载(onMounted 默认选中第一项)的行为保持一致。
*
* setFieldsValueFn 必须传页面 useForm() 解构出的 setFieldsValue(与 onMounted 同一通道),
* 不要用模板 ref 上的实例方法(可靠性不足)。
*/
export function imSearchReset(
setFieldsValueFn: (values: Record<string, any>) => Promise<void> | void,
tenantOptions: Ref<ImTenantOption[]>,
reload: () => unknown,
) {
return async () => {
const first = Number(tenantOptions.value[0]?.value || 0);
if (first > 0) {
await setFieldsValueFn({ tenantId: first });
}
await reload();
};
}
function toTenantOption(item: any): ImTenantOption {
return {
label: formatImTenantLabel(item),
value: Number(item.id),
code: item.code,
name: item.name,
};
}
function readCurrentUserInfo() {
const userStore = useUserStore();
return { ...readTokenUserInfo(userStore.getToken), ...(userStore.getUserInfo as any) };
}
function readTokenUserInfo(token: string) {
try {
const payload = token.split('.')[1];
if (!payload) return {};
const normalized = payload.replace(/-/g, '+').replace(/_/g, '/');
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, '=');
const binary = window.atob(padded);
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
return JSON.parse(new TextDecoder().decode(bytes));
} catch {
return {};
}
}