diff --git a/src/api/auth.ts b/src/api/auth.ts index d50429b..26a8b46 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -45,3 +45,16 @@ export function register(data: { export function changePassword(oldPassword: string, newPassword: string): Promise { return http.patch('/auth/me/password', { oldPassword, newPassword }) } + +export interface TenantBrand { + name: string + brandName: string | null + logoUrl: string | null + themeColor: string | null + copyright: string | null +} + +/** 当前租户品牌。 */ +export function fetchBrand(signal?: AbortSignal): Promise { + return http.get('/auth/brand', { signal }) +} diff --git a/src/api/tenant.ts b/src/api/tenant.ts index cde6bd0..679511e 100644 --- a/src/api/tenant.ts +++ b/src/api/tenant.ts @@ -6,6 +6,10 @@ export interface Tenant { name: string status: 'enabled' | 'disabled' remark: string | null + brandName: string | null + logoUrl: string | null + themeColor: string | null + copyright: string | null createdAt: number } @@ -32,11 +36,23 @@ export function listTenants(query: TenantListQuery, signal?: AbortSignal): Promi return http.get('/tenants', { params, signal }) } -export function createTenant(data: { code: string; name: string; status?: string; remark?: string }): Promise { +export function createTenant(data: { + code: string + name: string + status?: string + remark?: string + brandName?: string + logoUrl?: string + themeColor?: string + copyright?: string +}): Promise { return http.post('/tenants', data) } -export function updateTenant(id: string, data: { name?: string; status?: string; remark?: string }): Promise { +export function updateTenant( + id: string, + data: { name?: string; status?: string; remark?: string; brandName?: string; logoUrl?: string; themeColor?: string; copyright?: string }, +): Promise { return http.patch(`/tenants/${id}`, data) } diff --git a/src/layouts/default.vue b/src/layouts/default.vue index 08dee7e..730e324 100644 --- a/src/layouts/default.vue +++ b/src/layouts/default.vue @@ -63,7 +63,7 @@ - + @@ -81,7 +81,7 @@ import { useAuthStore } from '@/stores/modules/auth' import { useMenuStore } from '@/stores/modules/menu' import { useDictStore } from '@/stores/modules/dict' import { usePermissionStore } from '@/stores/modules/permission' -import logo from '@/assets/images/logo.png' +import defaultLogo from '@/assets/images/logo.png' import type { LayoutSettings } from '@/config/layout' import type { ResourceNode } from '@/types/resource' import type { BreadcrumbItem } from '@/components/ty-breadcrumb.vue' @@ -100,16 +100,24 @@ const route = useRoute() const router = useRouter() const { isFullscreen, toggle: toggleFullscreen } = useFullscreen() -const title = '投云科技' +const title = computed(() => auth.brand?.brandName || auth.brand?.name || '投云科技') +const logo = computed(() => auth.brand?.logoUrl || defaultLogo) +const copyright = computed(() => auth.brand?.copyright || '云南投云科技有限公司') const HEADER_HEIGHT = 56 const menus = computed(() => menuStore.menus) const settingsOpen = ref(false) -// 登录后拉取菜单、字典与权限码 -onMounted(() => { +// 登录后拉取菜单、字典、权限码与租户品牌 +onMounted(async () => { menuStore.fetch() dictStore.fetch() permissionStore.fetch() + try { + await auth.fetchBrandState() + if (auth.brand?.themeColor) app.themeColor = auth.brand.themeColor + } catch { + // 品牌拉取失败则用默认 + } }) const activePath = computed(() => route.path) diff --git a/src/pages/system/tenant/index.vue b/src/pages/system/tenant/index.vue index 89e7b33..6e17169 100644 --- a/src/pages/system/tenant/index.vue +++ b/src/pages/system/tenant/index.vue @@ -23,6 +23,7 @@ import { createTenant, deleteTenant, listTenants, updateTenant, type TenantListQ const columns: TableColumn[] = [ { prop: 'name', label: '租户名称', minWidth: 140 }, { prop: 'code', label: '租户编码', minWidth: 140 }, + { prop: 'brandName', label: '品牌名', minWidth: 120 }, { prop: 'status', label: '状态', width: 80, align: 'center', type: 'tag', dictCode: 'common_status' }, { prop: 'remark', label: '备注', minWidth: 160 }, { prop: 'createdAt', label: '创建时间', width: 160, type: 'datetime' }, @@ -54,6 +55,10 @@ const formSchema: JsonSchema = { default: 'enabled', }, remark: { type: 'string', title: '备注', format: 'textarea', placeholder: '请输入备注' }, + brandName: { type: 'string', title: '品牌名', placeholder: '如 投云科技' }, + logoUrl: { type: 'string', title: 'Logo 地址', placeholder: '请输入 Logo URL' }, + themeColor: { type: 'string', title: '主色', placeholder: '如 #2E7FA3' }, + copyright: { type: 'string', title: '版权文案', placeholder: '如 云南投云科技有限公司' }, }, required: ['code', 'name'], } @@ -71,8 +76,25 @@ function toListQuery(query: PageQuery): TenantListQuery { const list = (query: PageQuery, signal?: AbortSignal) => listTenants(toListQuery(query), signal) const create = (data: Record) => - createTenant({ code: data.code, name: data.name, status: data.status, remark: data.remark }) + createTenant({ + code: data.code, + name: data.name, + status: data.status, + remark: data.remark, + brandName: data.brandName, + logoUrl: data.logoUrl, + themeColor: data.themeColor, + copyright: data.copyright, + }) const update = (id: string, data: Record) => - updateTenant(id, { name: data.name, status: data.status, remark: data.remark }) + updateTenant(id, { + name: data.name, + status: data.status, + remark: data.remark, + brandName: data.brandName, + logoUrl: data.logoUrl, + themeColor: data.themeColor, + copyright: data.copyright, + }) const remove = (id: string) => deleteTenant(id) diff --git a/src/stores/modules/auth.ts b/src/stores/modules/auth.ts index 9435e39..702df5a 100644 --- a/src/stores/modules/auth.ts +++ b/src/stores/modules/auth.ts @@ -1,16 +1,21 @@ import { computed, ref } from 'vue'; import { defineStore } from 'pinia'; import { http } from '@/api/http'; -import { login as loginApi, type LoginResult } from '@/api/auth'; +import { fetchBrand, login as loginApi, type LoginResult, type TenantBrand } from '@/api/auth'; import { clearTokens, getAccessToken, getMustChangePassword, getRefreshToken, setMustChangePassword, setTokens } from '@/utils/token'; export const useAuthStore = defineStore('authStore', () => { const accessToken = ref(getAccessToken()); const refreshToken = ref(getRefreshToken()); const mustChangePassword = ref(getMustChangePassword()); + const brand = ref(null); const isAuthenticated = computed(() => !!accessToken.value); + async function fetchBrandState(): Promise { + brand.value = await fetchBrand(); + } + async function login(username: string, password: string, tenantCode?: string): Promise { const data = await loginApi(username, password, tenantCode); if ('accessToken' in data) { @@ -44,5 +49,5 @@ export const useAuthStore = defineStore('authStore', () => { setMustChangePassword(false); } - return { accessToken, refreshToken, isAuthenticated, mustChangePassword, login, logout, reset }; + return { accessToken, refreshToken, isAuthenticated, mustChangePassword, brand, login, logout, reset, fetchBrandState }; });