feat(tenant): 登录后应用租户品牌(主色/标题/Logo/版权)+ 租户表单品牌字段
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
64bbb0a987
commit
3746198f19
@ -45,3 +45,16 @@ export function register(data: {
|
||||
export function changePassword(oldPassword: string, newPassword: string): Promise<unknown> {
|
||||
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<TenantBrand> {
|
||||
return http.get<TenantBrand>('/auth/brand', { signal })
|
||||
}
|
||||
|
||||
@ -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<TenantListResult>('/tenants', { params, signal })
|
||||
}
|
||||
|
||||
export function createTenant(data: { code: string; name: string; status?: string; remark?: string }): Promise<Tenant> {
|
||||
export function createTenant(data: {
|
||||
code: string
|
||||
name: string
|
||||
status?: string
|
||||
remark?: string
|
||||
brandName?: string
|
||||
logoUrl?: string
|
||||
themeColor?: string
|
||||
copyright?: string
|
||||
}): Promise<Tenant> {
|
||||
return http.post<Tenant>('/tenants', data)
|
||||
}
|
||||
|
||||
export function updateTenant(id: string, data: { name?: string; status?: string; remark?: string }): Promise<Tenant> {
|
||||
export function updateTenant(
|
||||
id: string,
|
||||
data: { name?: string; status?: string; remark?: string; brandName?: string; logoUrl?: string; themeColor?: string; copyright?: string },
|
||||
): Promise<Tenant> {
|
||||
return http.patch<Tenant>(`/tenants/${id}`, data)
|
||||
}
|
||||
|
||||
|
||||
@ -63,7 +63,7 @@
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
</div>
|
||||
<ty-footer :fixed="app.footerFixed" />
|
||||
<ty-footer :fixed="app.footerFixed" :copyright="copyright" />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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<string, any>) =>
|
||||
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<string, any>) =>
|
||||
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)
|
||||
</script>
|
||||
|
||||
@ -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<string | null>(getAccessToken());
|
||||
const refreshToken = ref<string | null>(getRefreshToken());
|
||||
const mustChangePassword = ref<boolean>(getMustChangePassword());
|
||||
const brand = ref<TenantBrand | null>(null);
|
||||
|
||||
const isAuthenticated = computed(() => !!accessToken.value);
|
||||
|
||||
async function fetchBrandState(): Promise<void> {
|
||||
brand.value = await fetchBrand();
|
||||
}
|
||||
|
||||
async function login(username: string, password: string, tenantCode?: string): Promise<LoginResult> {
|
||||
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 };
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user