diff --git a/openhis-ui-vue3/src/api/login.js b/openhis-ui-vue3/src/api/login.js index e074afdf6..eb936ea63 100755 --- a/openhis-ui-vue3/src/api/login.js +++ b/openhis-ui-vue3/src/api/login.js @@ -97,6 +97,7 @@ export function sign(practitionerId, mac, ip) { method: 'post', }) } + // 锁屏解锁(验证登录状态) export function unlockScreen(password) { return request({ diff --git a/openhis-ui-vue3/src/api/system/notice.js b/openhis-ui-vue3/src/api/system/notice.js index 4c2273a48..fc01181b7 100755 --- a/openhis-ui-vue3/src/api/system/notice.js +++ b/openhis-ui-vue3/src/api/system/notice.js @@ -108,3 +108,29 @@ export function getReadNoticeIds() { method: 'get' }) } + +// 获取顶部公告/通知列表(最新N条) +export function listNoticeTop(query) { + return request({ + url: '/system/notice/public/top', + method: 'get', + params: query + }) +} + +// 标记单条公告/通知为已读 +export function markNoticeRead(noticeId) { + return request({ + url: '/system/notice/public/read/' + noticeId, + method: 'post' + }) +} + +// 批量标记公告/通知为已读(逗号分隔的ID字符串) +export function markNoticeReadAll(noticeIds) { + return request({ + url: '/system/notice/public/read/all', + method: 'post', + data: noticeIds + }) +} diff --git a/openhis-ui-vue3/src/layout/components/HeaderNotice/index.vue b/openhis-ui-vue3/src/layout/components/HeaderNotice/index.vue index 2dfabd0f3..feb7ea592 100644 --- a/openhis-ui-vue3/src/layout/components/HeaderNotice/index.vue +++ b/openhis-ui-vue3/src/layout/components/HeaderNotice/index.vue @@ -1,184 +1,360 @@ diff --git a/openhis-ui-vue3/src/layout/components/Navbar.vue b/openhis-ui-vue3/src/layout/components/Navbar.vue index 6d75cfa83..74b52c7bc 100755 --- a/openhis-ui-vue3/src/layout/components/Navbar.vue +++ b/openhis-ui-vue3/src/layout/components/Navbar.vue @@ -11,7 +11,7 @@ - +
- - + -
- - - +
+
+ 消息中心 + 全部已读 +
+ + + + + + + + + + + + +
+ +
+
+
+ + +
+
+
+ {{ item.noticeTitle }} + 未读 +
+
{{ noticeFormatTime(item.createTime) }}
+
+
+
- - + + +
- 个人中心 + 个人中心 - 锁定屏幕 + 锁定屏幕 - 退出登录 + 退出登录 @@ -138,7 +178,7 @@
- 确定 + 确定 - 取消 + 取消
- - + +
- + + diff --git a/openhis-ui-vue3/src/layout/components/Settings/index.vue b/openhis-ui-vue3/src/layout/components/Settings/index.vue index 7193d08b2..a5e2a53f5 100755 --- a/openhis-ui-vue3/src/layout/components/Settings/index.vue +++ b/openhis-ui-vue3/src/layout/components/Settings/index.vue @@ -134,7 +134,7 @@ const appStore = useAppStore() const settingsStore = useSettingsStore() const permissionStore = usePermissionStore() const showSettings = ref(false) -const navType = ref(settingsStore.navType) +const navType = ref(settingsStore.topNav) const theme = ref(settingsStore.theme) const sideTheme = ref(settingsStore.sideTheme) const tagsViewPersist = ref(settingsStore.tagsViewPersist) @@ -162,7 +162,7 @@ function handleTheme(val) { } function handleNavType(val) { - settingsStore.navType = val + settingsStore.topNav = val navType.value = val } @@ -191,7 +191,7 @@ function saveSetting() { proxy.$cache.local.remove('tags-view-visited') } let layoutSetting = { - "navType": storeSettings.value.navType, + "topNav": storeSettings.value.topNav, "tagsView": storeSettings.value.tagsView, "tagsIcon": storeSettings.value.tagsIcon, "tagsViewStyle": storeSettings.value.tagsViewStyle, diff --git a/openhis-ui-vue3/src/layout/components/TagsView/index.vue b/openhis-ui-vue3/src/layout/components/TagsView/index.vue index 040767397..a77ca3023 100755 --- a/openhis-ui-vue3/src/layout/components/TagsView/index.vue +++ b/openhis-ui-vue3/src/layout/components/TagsView/index.vue @@ -2,6 +2,7 @@
useTagsViewStore().visitedViews); const routes = computed(() => usePermissionStore().routes); const theme = computed(() => useSettingsStore().theme); +const tagsViewStyle = computed(() => useSettingsStore().tagsViewStyle); watch(route, () => { addTags(); @@ -407,6 +409,27 @@ function handleScroll() { } } } + &.chrome-style { + background: #f1f3f4; + .tags-view-wrapper { + .tags-view-item { + border: none; + border-radius: 8px 8px 0 0; + background: #dadce0; + color: #5f6368; + margin-left: -1px; + padding: 0 16px; + &.active { + background: #fff; + color: #202124; + border-color: transparent; + &::before { + display: none; + } + } + } + } + } } @@ -435,5 +458,26 @@ function handleScroll() { } } } + &.chrome-style { + background: #f1f3f4; + .tags-view-wrapper { + .tags-view-item { + border: none; + border-radius: 8px 8px 0 0; + background: #dadce0; + color: #5f6368; + margin-left: -1px; + padding: 0 16px; + &.active { + background: #fff; + color: #202124; + border-color: transparent; + &::before { + display: none; + } + } + } + } + } } \ No newline at end of file diff --git a/openhis-ui-vue3/src/layout/index.vue b/openhis-ui-vue3/src/layout/index.vue index 200aec9ab..4fed45dda 100755 --- a/openhis-ui-vue3/src/layout/index.vue +++ b/openhis-ui-vue3/src/layout/index.vue @@ -14,7 +14,7 @@
@@ -138,11 +138,19 @@ defineExpose({ position: fixed; top: 50px; right: 0; - left: 0; + left: 200px; z-index: 9; - width: 100%; + width: calc(100% - 200px); padding: 0 15px; background: #fff; + box-sizing: border-box; +} + +.sidebar-hidden { + .fixed-header { + left: 0 !important; + width: 100% !important; + } } .sidebarHide { diff --git a/openhis-ui-vue3/src/router/index.js b/openhis-ui-vue3/src/router/index.js index 4af5f3e40..89494c3e1 100755 --- a/openhis-ui-vue3/src/router/index.js +++ b/openhis-ui-vue3/src/router/index.js @@ -454,19 +454,9 @@ export const dynamicRoutes = [ } ]; -// 合并常量路由和动态路由,确保所有路由都能被访问 -const allRoutes = [...constantRoutes, ...dynamicRoutes]; - -// 添加404路由到所有路由的最后 -allRoutes.push({ - path: "/:pathMatch(.*)*", - component: () => import('@/views/error/404'), - hidden: true -}); - const router = createRouter({ history: createWebHistory(), - routes: allRoutes, + routes: constantRoutes, scrollBehavior(to, from, savedPosition) { if (savedPosition) { return savedPosition @@ -476,4 +466,17 @@ const router = createRouter({ }, }); + +// 动态路由加载完成后再添加 404 catch-all(Vue Router 4 要求) +export function addNotFoundRoute() { + if (!router.hasRoute('not-found')) { + router.addRoute({ + path: '/:pathMatch(.*)*', + name: 'not-found', + component: () => import('@/views/error/404'), + hidden: true + }) + } +} + export default router; diff --git a/openhis-ui-vue3/src/store/modules/notice.js b/openhis-ui-vue3/src/store/modules/notice.js new file mode 100644 index 000000000..08ed51754 --- /dev/null +++ b/openhis-ui-vue3/src/store/modules/notice.js @@ -0,0 +1,105 @@ +import { getUnreadCount, getUserNotices, markAsRead, markAllAsRead, listNoticeTop } from '@/api/system/notice' + +const useNoticeStore = defineStore('notice', { + state: () => ({ + unreadCount: 0, + noticeList: [], + readIds: new Set(), + loaded: false + }), + + getters: { + unreadList(state) { + return state.noticeList.filter(n => !state.readIds.has(n.noticeId)) + }, + noticeTypeList(state) { + return state.noticeList.filter(n => n.noticeType === '2') + }, + informTypeList(state) { + return state.noticeList.filter(n => n.noticeType === '1') + } + }, + + actions: { + // 获取未读数量 + async fetchUnreadCount() { + try { + const res = await getUnreadCount() + this.unreadCount = res.data || 0 + } catch (e) { + // ignore + } + }, + + // 获取通知列表 + async fetchNotices() { + try { + const res = await getUserNotices() + this.noticeList = res.data || [] + this.loaded = true + // 计算未读数 + this.unreadCount = this.noticeList.filter(n => !n.readFlag || n.readFlag === '0').length + } catch (e) { + // ignore + } + }, + + // 获取顶部通知列表 + async fetchTopNotices(query) { + try { + const res = await listNoticeTop(query) + this.noticeList = res.data || [] + this.loaded = true + } catch (e) { + // ignore + } + }, + + // 标记单条已读 + async markRead(noticeId) { + try { + await markAsRead(noticeId) + this.readIds.add(noticeId) + // 更新列表中的已读状态 + const item = this.noticeList.find(n => n.noticeId === noticeId) + if (item) { + item.isRead = true + item.readFlag = '1' + } + this.unreadCount = Math.max(0, this.unreadCount - 1) + } catch (e) { + // ignore + } + }, + + // 全部标记已读 + async markAllRead() { + try { + const unreadIds = this.noticeList + .filter(n => !this.readIds.has(n.noticeId)) + .map(n => n.noticeId) + if (unreadIds.length > 0) { + await markAllAsRead(unreadIds) + unreadIds.forEach(id => this.readIds.add(id)) + this.noticeList.forEach(n => { + n.isRead = true + n.readFlag = '1' + }) + this.unreadCount = 0 + } + } catch (e) { + // ignore + } + }, + + // 重置状态 + reset() { + this.unreadCount = 0 + this.noticeList = [] + this.readIds = new Set() + this.loaded = false + } + } +}) + +export default useNoticeStore diff --git a/openhis-ui-vue3/src/store/modules/permission.js b/openhis-ui-vue3/src/store/modules/permission.js index 39f31e9c5..1cbc4e392 100755 --- a/openhis-ui-vue3/src/store/modules/permission.js +++ b/openhis-ui-vue3/src/store/modules/permission.js @@ -1,5 +1,5 @@ import auth from '@/plugins/auth' -import router, {constantRoutes, dynamicRoutes} from '@/router' +import router, {constantRoutes, dynamicRoutes, addNotFoundRoute} from '@/router' import {getRouters} from '@/api/menu' import Layout from '@/layout/index' import ParentView from '@/components/ParentView' @@ -44,6 +44,7 @@ const usePermissionStore = defineStore( const defaultRoutes = filterAsyncRouter(defaultData) const asyncRoutes = filterDynamicRoutes(dynamicRoutes) asyncRoutes.forEach(route => { router.addRoute(route) }) + addNotFoundRoute() this.setRoutes(rewriteRoutes) this.setSidebarRouters(constantRoutes.concat(sidebarRoutes)) this.setDefaultRoutes(sidebarRoutes) diff --git a/openhis-ui-vue3/src/store/modules/settings.js b/openhis-ui-vue3/src/store/modules/settings.js index 40cc08399..6b5c25758 100755 --- a/openhis-ui-vue3/src/store/modules/settings.js +++ b/openhis-ui-vue3/src/store/modules/settings.js @@ -2,7 +2,7 @@ import defaultSettings from '@/settings' import {useDynamicTitle} from '@/utils/dynamicTitle' const { - sideTheme, showSettings, navType: topNav, tagsView, tagsViewPersist, + sideTheme, showSettings, topNav, tagsView, tagsViewPersist, tagsIcon, tagsViewStyle, fixedHeader, sidebarLogo, dynamicTitle, footerVisible, footerContent } = defaultSettings