feat(login): 添加租户名称获取功能并优化前端布局

- 在登录控制器中注入租户服务并获取租户名称信息
- 添加租户名称到登录响应结果中
- 更新样式变量定义侧边栏宽度和Logo高度
- 重构公告面板组件统一公告通知显示逻辑
- 简化公告类型图标和样式映射关系
- 更新侧边栏为垂直菜单布局并添加折叠功能
- 优化Logo组件显示租户名称和系统标题
- 调整导航栏布局结构和响应式样式
- 重构主应用容器样式和标签页显示逻辑
This commit is contained in:
2025-12-31 10:28:52 +08:00
parent 10e738edd9
commit 4d4828ea71
54 changed files with 3510 additions and 754 deletions

View File

@@ -1,49 +1,40 @@
<template>
<el-drawer v-model="noticeVisible" title="公告" direction="rtl" size="400px" destroy-on-close>
<el-tabs v-model="activeTab">
<el-tab-pane label="公告" name="notice">
<el-empty v-if="noticeList.length === 0" description="暂无公告" />
<div v-else class="notice-list">
<div v-for="item in noticeList" :key="item.noticeId" class="notice-item" :class="{ 'is-read': isRead(item.noticeId) }" @click="viewDetail(item)">
<div class="notice-title">
<span v-if="!isRead(item.noticeId)" class="unread-dot"></span>
{{ item.noticeTitle }}
</div>
<div class="notice-info">
<span class="notice-type">
<dict-tag :options="sys_notice_type" :value="item.noticeType" />
</span>
<span class="notice-time">{{ parseTime(item.createTime, '{y}-{m}-{d}') }}</span>
</div>
</div>
<el-drawer v-model="noticeVisible" title="公告/通知" direction="rtl" size="400px" destroy-on-close>
<el-empty v-if="noticeList.length === 0" description="暂无公告/通知" />
<div v-else class="notice-list">
<div v-for="item in noticeList" :key="item.noticeId" class="notice-item" :class="{ 'is-read': isRead(item.noticeId), 'unread': !isRead(item.noticeId) }" @click="viewDetail(item)">
<div class="notice-title">
<span v-if="!isRead(item.noticeId)" class="unread-dot"></span>
{{ item.noticeTitle }}
</div>
</el-tab-pane>
<el-tab-pane label="通知" name="notification">
<el-empty v-if="notificationList.length === 0" description="暂无通知" />
<div v-else class="notice-list">
<div v-for="item in notificationList" :key="item.noticeId" class="notice-item" :class="{ 'is-read': isRead(item.noticeId) }" @click="viewDetail(item)">
<div class="notice-title">
<span v-if="!isRead(item.noticeId)" class="unread-dot"></span>
{{ item.noticeTitle }}
</div>
<div class="notice-info">
<span class="notice-type">
<dict-tag :options="sys_notice_type" :value="item.noticeType" />
</span>
<span class="notice-time">{{ parseTime(item.createTime, '{y}-{m}-{d}') }}</span>
</div>
</div>
<div class="notice-info">
<span class="notice-type">
<el-tag :type="getNoticeTypeTagType(item.noticeType)" size="small">
{{ getNoticeTypeText(item.noticeType) }}
</el-tag>
</span>
<span class="notice-priority" v-if="item.priority">
<el-tag :type="getPriorityTagType(item.priority)" size="small" effect="plain">
{{ getPriorityText(item.priority) }}
</el-tag>
</span>
<span class="notice-time">{{ parseTime(item.createTime, '{y}-{m}-{d}') }}</span>
</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
<!-- 公告/通知详情对话框 -->
<el-dialog v-model="detailVisible" :title="currentNotice.noticeTitle" width="800px" append-to-body>
<div class="notice-detail">
<div class="detail-header">
<span class="detail-type">
<dict-tag :options="sys_notice_type" :value="currentNotice.noticeType" />
</span>
<div class="detail-type">
<el-tag :type="getNoticeTypeTagType(currentNotice.noticeType)" size="small">
{{ getNoticeTypeText(currentNotice.noticeType) }}
</el-tag>
<el-tag :type="getPriorityTagType(currentNotice.priority)" size="small" effect="plain" style="margin-left: 8px;">
{{ getPriorityText(currentNotice.priority) }}
</el-tag>
</div>
<span class="detail-time">{{ parseTime(currentNotice.createTime, '{y}-{m}-{d} {h}:{i}') }}</span>
</div>
<div class="detail-content" v-html="currentNotice.noticeContent"></div>
@@ -57,19 +48,13 @@
<script setup>
import {ref} from 'vue'
import {getPublicNoticeList, getReadNoticeIds, getUserNotices, markAsRead} from '@/api/system/notice'
import useUserStore from '@/store/modules/user'
import {getReadNoticeIds, getUserNotices, markAsRead} from '@/api/system/notice'
const { proxy } = getCurrentInstance()
const { sys_notice_type } = proxy.useDict('sys_notice_type')
const emit = defineEmits(['updateUnreadCount'])
const userStore = useUserStore()
const noticeVisible = ref(false)
const detailVisible = ref(false)
const activeTab = ref('notice')
const noticeList = ref([])
const notificationList = ref([])
const currentNotice = ref({})
const readNoticeIds = ref(new Set())
@@ -101,42 +86,82 @@ function loadReadNoticeIds() {
})
}
// 排序:未读的排前面,已读的排后面,同类型按时间
// 排序:未读的排前面,已读的排后面,同状态按优先级和时间
function sortNoticeList(list) {
return list.sort((a, b) => {
const aRead = isRead(a.noticeId)
const bRead = isRead(b.noticeId)
// 未读排在前面
if (aRead !== bRead) {
return aRead ? 1 : -1
}
// 同类型按创建时间倒序(最新的在前
// 同状态按优先级排序1高 2中 3低
const priorityA = a.priority || '3'
const priorityB = b.priority || '3'
if (priorityA !== priorityB) {
return priorityA.localeCompare(priorityB)
}
// 同优先级按创建时间倒序(最新的在前)
return new Date(b.createTime) - new Date(a.createTime)
})
}
// 加载公告和通知
// 加载公告和通知(统一从一个接口获取)
function loadNotices() {
// 加载公告列表
getPublicNoticeList({ pageNum: 1, pageSize: 10 }).then(response => {
let list = response.rows || response.data || []
noticeList.value = sortNoticeList(list)
})
// 加载通知列表
getUserNotices().then(response => {
let list = response.data || []
notificationList.value = sortNoticeList(list)
noticeList.value = sortNoticeList(list)
})
}
// 获取公告类型标签类型
// noticeType: 1=通知, 2=公告
function getNoticeTypeTagType(type) {
const typeMap = {
'1': 'primary', // 通知
'2': 'success' // 公告
}
return typeMap[type] || 'info'
}
// 获取公告类型文本
function getNoticeTypeText(type) {
const textMap = {
'1': '通知',
'2': '公告'
}
return textMap[type] || '公告'
}
// 获取优先级标签类型
// priority: 1=高, 2=中, 3=低
function getPriorityTagType(priority) {
const typeMap = {
'1': 'danger', // 高优先级 - 红色
'2': 'warning', // 中优先级 - 橙色
'3': 'info' // 低优先级 - 灰色
}
return typeMap[priority] || 'info'
}
// 获取优先级文本
function getPriorityText(priority) {
const textMap = {
'1': '高',
'2': '中',
'3': '低'
}
return textMap[priority] || '中'
}
// 查看详情
function viewDetail(item) {
currentNotice.value = item
detailVisible.value = true
// 标记为已读
if (!readNoticeIds.value.has(item.noticeId)) {
markAsRead(item.noticeId).then(() => {
@@ -192,6 +217,10 @@ defineExpose({
color: #909399;
}
}
&.unread {
background-color: #fffbe6;
}
}
.notice-title {
@@ -219,6 +248,12 @@ defineExpose({
align-items: center;
font-size: 12px;
color: #909399;
gap: 8px;
.notice-type,
.notice-priority {
flex-shrink: 0;
}
}
.detail-header {
@@ -228,6 +263,11 @@ defineExpose({
padding-bottom: 16px;
border-bottom: 1px solid #EBEEF5;
margin-bottom: 16px;
.detail-type {
display: flex;
align-items: center;
}
}
.detail-time {

View File

@@ -111,12 +111,11 @@ const unreadCount = computed(() => {
})
// 获取公告类型图标
// noticeType: 1=通知, 2=公告
const getNoticeTypeIcon = (type) => {
const iconMap = {
'1': Bell, // 通知
'2': Warning, // 紧急
'3': InfoFilled, // 信息
'4': CircleCheck // 成功
'2': InfoFilled // 公告
}
return iconMap[type] || InfoFilled
}
@@ -152,36 +151,33 @@ const getPriorityTagType = (priority) => {
}
// 获取公告类型样式类
// noticeType: 1=通知, 2=公告
const getNoticeTypeClass = (type) => {
const classMap = {
'1': 'type-notice',
'2': 'type-urgent',
'3': 'type-info',
'4': 'type-success'
'2': 'type-announcement'
}
return classMap[type] || 'type-info'
return classMap[type] || 'type-announcement'
}
// 获取公告类型标签类型
// noticeType: 1=通知, 2=公告
const getNoticeTypeTagType = (type) => {
const typeMap = {
'1': '',
'2': 'danger',
'3': 'info',
'4': 'success'
'1': 'primary',
'2': 'success'
}
return typeMap[type] || ''
return typeMap[type] || 'info'
}
// 获取公告类型文本
// noticeType: 1=通知, 2=公告
const getNoticeTypeText = (type) => {
const textMap = {
'1': '通知',
'2': '紧急',
'3': '信息',
'4': '成功'
'2': '公告'
}
return textMap[type] || '信息'
return textMap[type] || '公告'
}
// 格式化相对时间
@@ -390,21 +386,11 @@ defineExpose({
background: #e6f7ff;
color: #1890ff;
}
&.type-urgent {
background: #fff1f0;
color: #ff4d4f;
}
&.type-info {
&.type-announcement {
background: #f6ffed;
color: #52c41a;
}
&.type-success {
background: #f9f0ff;
color: #722ed1;
}
}
.notice-item-content {

View File

@@ -127,12 +127,11 @@ const hasUnread = computed(() => {
})
// 获取公告类型图标
// noticeType: 1=通知, 2=公告
const getNoticeTypeIcon = (type) => {
const iconMap = {
'1': Bell, // 通知
'2': Warning, // 紧急
'3': InfoFilled, // 信息
'4': CircleCheck // 成功
'2': InfoFilled // 公告
}
return iconMap[type] || InfoFilled
}
@@ -178,36 +177,33 @@ const getPriorityIcon = (priority) => {
}
// 获取公告类型样式类
// noticeType: 1=通知, 2=公告
const getNoticeTypeClass = (type) => {
const classMap = {
'1': 'type-notice',
'2': 'type-urgent',
'3': 'type-info',
'4': 'type-success'
'2': 'type-announcement'
}
return classMap[type] || 'type-info'
return classMap[type] || 'type-announcement'
}
// 获取公告类型标签类型
// noticeType: 1=通知, 2=公告
const getNoticeTypeTagType = (type) => {
const typeMap = {
'1': '',
'2': 'danger',
'3': 'info',
'4': 'success'
'1': 'primary',
'2': 'success'
}
return typeMap[type] || ''
return typeMap[type] || 'info'
}
// 获取公告类型文本
// noticeType: 1=通知, 2=公告
const getNoticeTypeText = (type) => {
const textMap = {
'1': '通知',
'2': '紧急',
'3': '信息',
'4': '成功'
'2': '公告'
}
return textMap[type] || '信息'
return textMap[type] || '公告'
}
// 格式化日期
@@ -414,21 +410,11 @@ defineExpose({
background: #e6f7ff;
color: #1890ff;
}
&.type-urgent {
background: #fff1f0;
color: #ff4d4f;
}
&.type-info {
&.type-announcement {
background: #f6ffed;
color: #52c41a;
}
&.type-success {
background: #f9f0ff;
color: #722ed1;
}
}
.notice-item-content {