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> {
|
export function changePassword(oldPassword: string, newPassword: string): Promise<unknown> {
|
||||||
return http.patch('/auth/me/password', { oldPassword, newPassword })
|
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
|
name: string
|
||||||
status: 'enabled' | 'disabled'
|
status: 'enabled' | 'disabled'
|
||||||
remark: string | null
|
remark: string | null
|
||||||
|
brandName: string | null
|
||||||
|
logoUrl: string | null
|
||||||
|
themeColor: string | null
|
||||||
|
copyright: string | null
|
||||||
createdAt: number
|
createdAt: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,11 +36,23 @@ export function listTenants(query: TenantListQuery, signal?: AbortSignal): Promi
|
|||||||
return http.get<TenantListResult>('/tenants', { params, signal })
|
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)
|
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)
|
return http.patch<Tenant>(`/tenants/${id}`, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -63,7 +63,7 @@
|
|||||||
</keep-alive>
|
</keep-alive>
|
||||||
</router-view>
|
</router-view>
|
||||||
</div>
|
</div>
|
||||||
<ty-footer :fixed="app.footerFixed" />
|
<ty-footer :fixed="app.footerFixed" :copyright="copyright" />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ import { useAuthStore } from '@/stores/modules/auth'
|
|||||||
import { useMenuStore } from '@/stores/modules/menu'
|
import { useMenuStore } from '@/stores/modules/menu'
|
||||||
import { useDictStore } from '@/stores/modules/dict'
|
import { useDictStore } from '@/stores/modules/dict'
|
||||||
import { usePermissionStore } from '@/stores/modules/permission'
|
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 { LayoutSettings } from '@/config/layout'
|
||||||
import type { ResourceNode } from '@/types/resource'
|
import type { ResourceNode } from '@/types/resource'
|
||||||
import type { BreadcrumbItem } from '@/components/ty-breadcrumb.vue'
|
import type { BreadcrumbItem } from '@/components/ty-breadcrumb.vue'
|
||||||
@ -100,16 +100,24 @@ const route = useRoute()
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { isFullscreen, toggle: toggleFullscreen } = useFullscreen()
|
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 HEADER_HEIGHT = 56
|
||||||
const menus = computed(() => menuStore.menus)
|
const menus = computed(() => menuStore.menus)
|
||||||
const settingsOpen = ref(false)
|
const settingsOpen = ref(false)
|
||||||
|
|
||||||
// 登录后拉取菜单、字典与权限码
|
// 登录后拉取菜单、字典、权限码与租户品牌
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
menuStore.fetch()
|
menuStore.fetch()
|
||||||
dictStore.fetch()
|
dictStore.fetch()
|
||||||
permissionStore.fetch()
|
permissionStore.fetch()
|
||||||
|
try {
|
||||||
|
await auth.fetchBrandState()
|
||||||
|
if (auth.brand?.themeColor) app.themeColor = auth.brand.themeColor
|
||||||
|
} catch {
|
||||||
|
// 品牌拉取失败则用默认
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const activePath = computed(() => route.path)
|
const activePath = computed(() => route.path)
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import { createTenant, deleteTenant, listTenants, updateTenant, type TenantListQ
|
|||||||
const columns: TableColumn[] = [
|
const columns: TableColumn[] = [
|
||||||
{ prop: 'name', label: '租户名称', minWidth: 140 },
|
{ prop: 'name', label: '租户名称', minWidth: 140 },
|
||||||
{ prop: 'code', 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: 'status', label: '状态', width: 80, align: 'center', type: 'tag', dictCode: 'common_status' },
|
||||||
{ prop: 'remark', label: '备注', minWidth: 160 },
|
{ prop: 'remark', label: '备注', minWidth: 160 },
|
||||||
{ prop: 'createdAt', label: '创建时间', width: 160, type: 'datetime' },
|
{ prop: 'createdAt', label: '创建时间', width: 160, type: 'datetime' },
|
||||||
@ -54,6 +55,10 @@ const formSchema: JsonSchema = {
|
|||||||
default: 'enabled',
|
default: 'enabled',
|
||||||
},
|
},
|
||||||
remark: { type: 'string', title: '备注', format: 'textarea', placeholder: '请输入备注' },
|
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'],
|
required: ['code', 'name'],
|
||||||
}
|
}
|
||||||
@ -71,8 +76,25 @@ function toListQuery(query: PageQuery): TenantListQuery {
|
|||||||
|
|
||||||
const list = (query: PageQuery, signal?: AbortSignal) => listTenants(toListQuery(query), signal)
|
const list = (query: PageQuery, signal?: AbortSignal) => listTenants(toListQuery(query), signal)
|
||||||
const create = (data: Record<string, any>) =>
|
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>) =>
|
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)
|
const remove = (id: string) => deleteTenant(id)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,16 +1,21 @@
|
|||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { http } from '@/api/http';
|
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';
|
import { clearTokens, getAccessToken, getMustChangePassword, getRefreshToken, setMustChangePassword, setTokens } from '@/utils/token';
|
||||||
|
|
||||||
export const useAuthStore = defineStore('authStore', () => {
|
export const useAuthStore = defineStore('authStore', () => {
|
||||||
const accessToken = ref<string | null>(getAccessToken());
|
const accessToken = ref<string | null>(getAccessToken());
|
||||||
const refreshToken = ref<string | null>(getRefreshToken());
|
const refreshToken = ref<string | null>(getRefreshToken());
|
||||||
const mustChangePassword = ref<boolean>(getMustChangePassword());
|
const mustChangePassword = ref<boolean>(getMustChangePassword());
|
||||||
|
const brand = ref<TenantBrand | null>(null);
|
||||||
|
|
||||||
const isAuthenticated = computed(() => !!accessToken.value);
|
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> {
|
async function login(username: string, password: string, tenantCode?: string): Promise<LoginResult> {
|
||||||
const data = await loginApi(username, password, tenantCode);
|
const data = await loginApi(username, password, tenantCode);
|
||||||
if ('accessToken' in data) {
|
if ('accessToken' in data) {
|
||||||
@ -44,5 +49,5 @@ export const useAuthStore = defineStore('authStore', () => {
|
|||||||
setMustChangePassword(false);
|
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