fix: 完成: 诊所设置中心,消息中心
This commit is contained in:
@@ -13,11 +13,12 @@ import {
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
// eslint-disable-next-line n/no-extraneous-import
|
||||
import { Base64 } from 'js-base64';
|
||||
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
import { refreshTokenApi } from './core';
|
||||
import {Base64} from "js-base64";
|
||||
|
||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||
|
||||
@@ -45,7 +46,6 @@ function createRequestClient(baseURL: string) {
|
||||
* 重新认证逻辑
|
||||
*/
|
||||
async function doReAuthenticate() {
|
||||
console.warn('Access token or refresh token is invalid or expired. ');
|
||||
const accessStore = useAccessStore();
|
||||
const authStore = useAuthStore();
|
||||
accessStore.setAccessToken(null);
|
||||
@@ -55,7 +55,7 @@ function createRequestClient(baseURL: string) {
|
||||
) {
|
||||
accessStore.setLoginExpired(true);
|
||||
} else {
|
||||
await authStore.logout();
|
||||
await authStore.logout(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ function createRequestClient(baseURL: string) {
|
||||
// 这里吧 data 换成了 result
|
||||
const { code, result } = responseData;
|
||||
// 判断返回状态码
|
||||
if (status >= 200 && status < 400) {
|
||||
if (status >= 200 && status <= 500) {
|
||||
switch (code) {
|
||||
case 0: {
|
||||
// 解密
|
||||
@@ -113,8 +113,16 @@ function createRequestClient(baseURL: string) {
|
||||
return result;
|
||||
}
|
||||
case 401: {
|
||||
response.status = 401;
|
||||
// 401 错误,需要重新登录
|
||||
return doReAuthenticate();
|
||||
authenticateResponseInterceptor({
|
||||
client,
|
||||
doReAuthenticate,
|
||||
doRefreshToken,
|
||||
enableRefreshToken: preferences.app.enableRefreshToken,
|
||||
formatToken,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 403:
|
||||
case 404:
|
||||
|
||||
@@ -60,37 +60,6 @@ const typeBorderColors = {
|
||||
2: 'border-emerald-200',
|
||||
};
|
||||
|
||||
// const notifications = ref<NotificationItem[]>([
|
||||
// // TODO 通知列表
|
||||
// {
|
||||
// avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB',
|
||||
// date: '3小时前',
|
||||
// isRead: true,
|
||||
// message: '描述信息描述信息描述信息',
|
||||
// title: '收到了 14 份新周报',
|
||||
// },
|
||||
// {
|
||||
// avatar: 'https://avatar.vercel.sh/1',
|
||||
// date: '刚刚',
|
||||
// isRead: false,
|
||||
// message: '描述信息描述信息描述信息',
|
||||
// title: '朱偏右 回复了你',
|
||||
// },
|
||||
// {
|
||||
// avatar: 'https://avatar.vercel.sh/1',
|
||||
// date: '2024-01-01',
|
||||
// isRead: false,
|
||||
// message: '描述信息描述信息描述信息',
|
||||
// title: '曲丽丽 评论了你',
|
||||
// },
|
||||
// {
|
||||
// avatar: 'https://avatar.vercel.sh/satori',
|
||||
// date: '1天前',
|
||||
// isRead: false,
|
||||
// message: '描述信息描述信息描述信息',
|
||||
// title: '代办提醒',
|
||||
// },
|
||||
// ]);
|
||||
const notifications = ref<NotificationItem[]>([]);
|
||||
|
||||
// 获取消息列表数据
|
||||
@@ -99,16 +68,31 @@ const getNoticeList = async () => {
|
||||
const res = await getNoticeListApi({
|
||||
status: 0,
|
||||
});
|
||||
notifications.value = res.items.map(item => ({
|
||||
id: item.id,
|
||||
icon: typeIcons[item.type],
|
||||
// 将API返回的数据映射到组件需要的格式
|
||||
message: item.title,
|
||||
title: getMessageTypeName(item.type),
|
||||
date: formatTimeToRelative(item.created_at),
|
||||
color: typeColors[item.type],
|
||||
isRead: item.status
|
||||
}));
|
||||
let message = '未读消息';
|
||||
if (res.items.length > 0) {
|
||||
if (res.items.length > notifications.value.length) {
|
||||
if (notifications.value.length === 0) {
|
||||
message = '新消息';
|
||||
}
|
||||
notification.info({
|
||||
message: `您有${message}`,
|
||||
description: `您有 ${res.items.length} 条${message}请注意查收`,
|
||||
duration: 5,
|
||||
});
|
||||
}
|
||||
|
||||
notifications.value = res.items.map(item => ({
|
||||
id: item.id,
|
||||
icon: typeIcons[item.type],
|
||||
// 将API返回的数据映射到组件需要的格式
|
||||
message: item.title,
|
||||
title: getMessageTypeName(item.type),
|
||||
date: formatTimeToRelative(item.created_at),
|
||||
color: typeColors[item.type],
|
||||
isRead: item.status
|
||||
}));
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取消息列表失败:', error);
|
||||
} finally {
|
||||
@@ -177,8 +161,8 @@ const goNoticeList = () => {
|
||||
const handleItemClick = (item) => {
|
||||
notifications.value.forEach((value) => {
|
||||
if (value.id === item.id) {
|
||||
// 删除这个对象
|
||||
value.status = 1;
|
||||
value.isRead = 1;
|
||||
}
|
||||
});
|
||||
router.push(`/notice/detail/${item.id}`);
|
||||
|
||||
@@ -11,10 +11,11 @@ export const overridesPreferences = defineOverridesPreferences({
|
||||
name: import.meta.env.VITE_APP_TITLE,
|
||||
layout: 'sidebar-mixed-nav',
|
||||
// 登录过期模式
|
||||
loginExpiredMode: 'modal',
|
||||
// loginExpiredMode: 'modal',
|
||||
loginExpiredMode: 'page',
|
||||
// 权限模式 frontend 默认前端控制 backend 后端控制
|
||||
accessMode: 'backend',
|
||||
enableRefreshToken: true,
|
||||
enableRefreshToken: false,
|
||||
defaultAvatar:
|
||||
'https://yanydy.oss-cn-hangzhou.aliyuncs.com/uploads/20241219/2b38e38414a2b9383b8643983e3f69d7.png',
|
||||
watermark: true,
|
||||
|
||||
@@ -109,7 +109,7 @@ function setupAccessGuard(router: Router) {
|
||||
accessStore.setIsAccessChecked(true);
|
||||
const redirectPath = (from.query.redirect ??
|
||||
(to.path === DEFAULT_HOME_PATH
|
||||
? userInfo.homePath || DEFAULT_HOME_PATH
|
||||
? userInfo.home_path || DEFAULT_HOME_PATH
|
||||
: to.fullPath)) as string;
|
||||
|
||||
return {
|
||||
|
||||
@@ -13,16 +13,19 @@ import { routes } from './routes';
|
||||
* @zh_CN 创建vue-router实例
|
||||
*/
|
||||
const router = createRouter({
|
||||
history:
|
||||
import.meta.env.VITE_ROUTER_HISTORY === 'hash'
|
||||
? createWebHashHistory(import.meta.env.VITE_BASE)
|
||||
: createWebHistory(import.meta.env.VITE_BASE),
|
||||
// 根据环境变量配置选择合适的路由历史模式
|
||||
history: import.meta.env.VITE_ROUTER_HISTORY === 'hash'
|
||||
? createWebHashHistory(import.meta.env.VITE_BASE)
|
||||
: createWebHistory(import.meta.env.VITE_BASE),
|
||||
// 应该添加到路由的初始路由列表。
|
||||
routes,
|
||||
// 自定义滚动行为以提升用户体验
|
||||
scrollBehavior: (to, _from, savedPosition) => {
|
||||
// 如果有保存的位置,则恢复到该位置
|
||||
if (savedPosition) {
|
||||
return savedPosition;
|
||||
}
|
||||
// 如果路由带有哈希,则平滑滚动到该哈希对应的元素;否则滚动到页面顶部
|
||||
return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 };
|
||||
},
|
||||
// 是否应该禁止尾部斜杠。
|
||||
|
||||
@@ -53,10 +53,7 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
userStore.setUserInfo(userInfo);
|
||||
accessStore.setAccessCodes(accessCodes);
|
||||
|
||||
console.log(userInfo.home_path);
|
||||
console.log(DEFAULT_HOME_PATH);
|
||||
console.log(userInfo.home_path || DEFAULT_HOME_PATH, 'ssssssssssssssss');
|
||||
console.log(userInfo.home_path || DEFAULT_HOME_PATH, 'ssssssssssssssss');
|
||||
console.log('userInfo', accessStore.loginExpired);
|
||||
if (accessStore.loginExpired) {
|
||||
accessStore.setLoginExpired(false);
|
||||
} else {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { $t } from '@vben/locales';
|
||||
import { sendVerificationCode } from '#/api';
|
||||
import { useAuthStore } from '#/store';
|
||||
import {message} from "ant-design-vue";
|
||||
import {useRouter} from "vue-router";
|
||||
|
||||
defineOptions({ name: 'Login' });
|
||||
|
||||
|
||||
@@ -136,12 +136,12 @@ function selectDiagnosis(item, type = 1) {
|
||||
</Col>
|
||||
<Col :span="24">
|
||||
<Badge v-for="item in doctorOrderMyList" :count="0" class="mt-5">
|
||||
<Button style="margin: 0 10px;" :type="item.isSelect === 1 ? 'primary': ''" @click="selectDiagnosis(item)">{{ item.content }}</Button>
|
||||
<Button style="margin: 0 10px;" type="primary" :ghost="item.isSelect !== 1" @click="selectDiagnosis(item)">{{ item.content }}</Button>
|
||||
</Badge>
|
||||
</Col>
|
||||
<Col :span="24">
|
||||
<Badge v-for="item in doctorOrderCommonList" :count="0" class="mt-5">
|
||||
<Button style="margin: 0 10px;" :type="item.isSelect === 1 ? 'primary': ''" @click="selectDiagnosis(item, 2)">{{ item.name }}</Button>
|
||||
<Button style="margin: 0 10px;" type="primary" :ghost="item.isSelect !== 1" @click="selectDiagnosis(item, 2)">{{ item.name }}</Button>
|
||||
</Badge>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -1,81 +1,76 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { Page } from '@vben/common-ui';
|
||||
import {
|
||||
ArrowLeft,
|
||||
Bell,
|
||||
CalendarClock,
|
||||
Clock,
|
||||
Megaphone,
|
||||
User,
|
||||
} from 'lucide-vue-next';
|
||||
import {formatTimeToRelative} from "#/util/tool";
|
||||
import {getNoticeDetailApi} from "#/views/notice/api";
|
||||
import { ref, onMounted } from "vue"
|
||||
import { useRouter } from "vue-router"
|
||||
import { useRoute } from "vue-router"
|
||||
import { Page } from "@vben/common-ui"
|
||||
import { ArrowLeft, Bell, CalendarClock, Clock, Megaphone, User } from "lucide-vue-next"
|
||||
import { formatTimeToRelative } from "#/util/tool"
|
||||
import { getNoticeDetailApi } from "#/views/notice/api"
|
||||
import { useTabs } from "@vben/hooks"
|
||||
|
||||
// 假设有一个获取通知详情的API
|
||||
// import { getNoticeDetailApi, markNoticeReadApi } from "#/views/notice/api";
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const noticeId = ref(route.params.id)
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const noticeId = ref(route.params.id);
|
||||
const notice = ref(null)
|
||||
const loading = ref(true)
|
||||
const error = ref(null)
|
||||
|
||||
const notice = ref(null);
|
||||
const loading = ref(true);
|
||||
const error = ref(null);
|
||||
const { setTabTitle } = useTabs()
|
||||
|
||||
// 消息类型定义
|
||||
const messageTypes = [
|
||||
{ id: 0, name: '系统公告', icon: Megaphone, color: 'text-sky-500', bgColor: 'bg-sky-50', borderColor: 'border-sky-200' },
|
||||
{ id: 1, name: '系统通知', icon: Bell, color: 'text-amber-500', bgColor: 'bg-amber-50', borderColor: 'border-amber-200' },
|
||||
{ id: 2, name: '周报提醒', icon: CalendarClock, color: 'text-emerald-500', bgColor: 'bg-emerald-50', borderColor: 'border-emerald-200' },
|
||||
];
|
||||
{
|
||||
id: 0,
|
||||
name: "系统公告",
|
||||
icon: Megaphone,
|
||||
color: "text-sky-500",
|
||||
darkColor: "dark:text-sky-400",
|
||||
bgColor: "bg-sky-50",
|
||||
darkBgColor: "dark:bg-sky-900/30",
|
||||
borderColor: "border-sky-200",
|
||||
darkBorderColor: "dark:border-sky-800",
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: "系统通知",
|
||||
icon: Bell,
|
||||
color: "text-amber-500",
|
||||
darkColor: "dark:text-amber-400",
|
||||
bgColor: "bg-amber-50",
|
||||
darkBgColor: "dark:bg-amber-900/30",
|
||||
borderColor: "border-amber-200",
|
||||
darkBorderColor: "dark:border-amber-800",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "周报提醒",
|
||||
icon: CalendarClock,
|
||||
color: "text-emerald-500",
|
||||
darkColor: "dark:text-emerald-400",
|
||||
bgColor: "bg-emerald-50",
|
||||
darkBgColor: "dark:bg-emerald-900/30",
|
||||
borderColor: "border-emerald-200",
|
||||
darkBorderColor: "dark:border-emerald-800",
|
||||
},
|
||||
]
|
||||
|
||||
// 获取通知详情
|
||||
const getNoticeDetail = async () => {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
// 实际项目中应该调用API获取数据
|
||||
// const res = await getNoticeDetailApi(noticeId);
|
||||
// notice.value = res;
|
||||
|
||||
getNoticeDetailApi(noticeId.value).then((res) => {
|
||||
notice.value = res;
|
||||
loading.value = false;
|
||||
})
|
||||
// 模拟API响应
|
||||
// setTimeout(() => {
|
||||
// notice.value = {
|
||||
// id: Number(noticeId.value),
|
||||
// title: "更新诊所中药销售价格",
|
||||
// detail: "您执行的任务已经完成",
|
||||
// content: "执行成功,价格已经更新完成!\n本次更新了:748条数据!\n\n更新时间:2025-04-10 14:05:08\n\n请注意检查更新后的价格是否符合预期。如有问题,请联系系统管理员。",
|
||||
// type: 1,
|
||||
// user_id: 1,
|
||||
// status: 0,
|
||||
// created_at: "2025-04-10 14:05:08",
|
||||
// admin: {
|
||||
// id: 1,
|
||||
// nick_name: "李琦"
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// // 如果通知未读,标记为已读
|
||||
// if (notice.value && notice.value.status === 0) {
|
||||
// markAsRead();
|
||||
// }
|
||||
//
|
||||
// loading.value = false;
|
||||
// }, 800);
|
||||
const res = await getNoticeDetailApi(noticeId.value)
|
||||
notice.value = res
|
||||
setTabTitle(`通知详情-【${res.id}】`)
|
||||
loading.value = false
|
||||
} catch (err) {
|
||||
console.error('获取通知详情失败:', err);
|
||||
error.value = '获取通知详情失败,请稍后重试';
|
||||
loading.value = false;
|
||||
console.error("获取通知详情失败:", err)
|
||||
error.value = "获取通知详情失败,请稍后重试"
|
||||
loading.value = false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 标记通知为已读
|
||||
const markAsRead = async () => {
|
||||
@@ -83,31 +78,31 @@ const markAsRead = async () => {
|
||||
// 实际项目中应该调用API
|
||||
// await markNoticeReadApi(noticeId);
|
||||
if (notice.value) {
|
||||
notice.value.status = 1;
|
||||
notice.value.status = 1
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('标记已读失败:', err);
|
||||
console.error("标记已读失败:", err)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 返回列表
|
||||
const goBack = () => {
|
||||
router.push('/notice');
|
||||
};
|
||||
router.push("/notice")
|
||||
}
|
||||
|
||||
// 获取类型信息
|
||||
const getTypeInfo = (typeId) => {
|
||||
return messageTypes.find(t => t.id === typeId) || messageTypes[0];
|
||||
};
|
||||
return messageTypes.find((t) => t.id === typeId) || messageTypes[0]
|
||||
}
|
||||
|
||||
// 格式化内容,将换行符转换为<br>
|
||||
const formatContent = (content) => {
|
||||
return content ? content.replace(/\n/g, '<br>') : '';
|
||||
};
|
||||
return content ? content.replace(/\n/g, "<br>") : ""
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getNoticeDetail();
|
||||
});
|
||||
getNoticeDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -116,26 +111,26 @@ onMounted(() => {
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
@click="goBack"
|
||||
class="flex h-8 w-8 items-center justify-center rounded-full text-gray-500 hover:bg-gray-100 hover:text-gray-700"
|
||||
class="flex h-8 w-8 items-center justify-center rounded-full text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
|
||||
>
|
||||
<ArrowLeft class="h-5 w-5" />
|
||||
</button>
|
||||
<h1 class="text-xl font-semibold">通知详情</h1>
|
||||
<h1 class="text-xl font-semibold text-gray-900 dark:text-gray-100">通知详情</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="mx-auto max-w-3xl">
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="flex justify-center py-20">
|
||||
<div class="h-10 w-10 animate-spin rounded-full border-4 border-gray-200 border-t-sky-500"></div>
|
||||
<div class="h-10 w-10 animate-spin rounded-full border-4 border-gray-200 dark:border-gray-700 border-t-sky-500 dark:border-t-sky-400"></div>
|
||||
</div>
|
||||
|
||||
<!-- 错误状态 -->
|
||||
<div v-else-if="error" class="rounded-lg border border-red-200 bg-red-50 p-4 text-center text-red-600">
|
||||
<div v-else-if="error" class="rounded-lg border border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900/20 p-4 text-center text-red-600 dark:text-red-400 transition-colors">
|
||||
{{ error }}
|
||||
<button
|
||||
@click="getNoticeDetail"
|
||||
class="mt-2 rounded-md bg-red-100 px-3 py-1 text-sm font-medium text-red-700 hover:bg-red-200"
|
||||
class="mt-2 rounded-md bg-red-100 dark:bg-red-900/30 px-3 py-1 text-sm font-medium text-red-700 dark:text-red-300 hover:bg-red-200 dark:hover:bg-red-900/50 transition-colors"
|
||||
>
|
||||
重试
|
||||
</button>
|
||||
@@ -144,26 +139,35 @@ onMounted(() => {
|
||||
<!-- 通知详情 -->
|
||||
<div v-else-if="notice" class="space-y-6">
|
||||
<!-- 标题和类型 -->
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<div class="rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-6 shadow-sm transition-colors">
|
||||
<div class="mb-4 flex items-start justify-between">
|
||||
<h2 class="text-xl font-bold text-gray-900">{{ notice.title }}</h2>
|
||||
<h2 class="text-xl font-bold text-gray-900 dark:text-gray-100 transition-colors">{{ notice.title }}</h2>
|
||||
<div
|
||||
:class="[getTypeInfo(notice.type).bgColor]"
|
||||
class="flex items-center gap-1.5 rounded-full px-3 py-1"
|
||||
:class="[
|
||||
getTypeInfo(notice.type).bgColor,
|
||||
getTypeInfo(notice.type).darkBgColor,
|
||||
]"
|
||||
class="flex items-center gap-1.5 rounded-full px-3 py-1 transition-colors"
|
||||
>
|
||||
<component
|
||||
:is="getTypeInfo(notice.type).icon"
|
||||
:class="[getTypeInfo(notice.type).color]"
|
||||
:class="[
|
||||
getTypeInfo(notice.type).color,
|
||||
getTypeInfo(notice.type).darkColor,
|
||||
]"
|
||||
class="h-4 w-4"
|
||||
/>
|
||||
<span :class="[getTypeInfo(notice.type).color]" class="text-xs font-medium">
|
||||
<span :class="[
|
||||
getTypeInfo(notice.type).color,
|
||||
getTypeInfo(notice.type).darkColor,
|
||||
]" class="text-xs font-medium">
|
||||
{{ getTypeInfo(notice.type).name }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 元信息 -->
|
||||
<div class="mb-6 flex flex-wrap items-center gap-x-6 gap-y-2 text-sm text-gray-500">
|
||||
<div class="mb-6 flex flex-wrap items-center gap-x-6 gap-y-2 text-sm text-gray-500 dark:text-gray-400 transition-colors">
|
||||
<div class="flex items-center gap-1.5">
|
||||
来自:
|
||||
<User class="h-4 w-4" />
|
||||
@@ -176,7 +180,7 @@ onMounted(() => {
|
||||
<span>{{ notice.admin?.nick_name || '系统' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-6 flex flex-wrap items-center gap-x-6 gap-y-2 text-sm text-gray-500">
|
||||
<div class="mb-6 flex flex-wrap items-center gap-x-6 gap-y-2 text-sm text-gray-500 dark:text-gray-400 transition-colors">
|
||||
<div v-html="formatContent(notice.detail)"></div>
|
||||
<div class="flex items-center gap-1.5" style="margin-left: auto;">
|
||||
<Clock class="h-4 w-4" />
|
||||
@@ -185,11 +189,11 @@ onMounted(() => {
|
||||
</div>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<div class="mb-6 h-px w-full bg-gray-100"></div>
|
||||
<div class="mb-6 h-px w-full bg-gray-100 dark:bg-gray-700 transition-colors"></div>
|
||||
|
||||
<!-- 内容 -->
|
||||
<div class="rounded-lg bg-gray-50 p-4">
|
||||
<div class="prose prose-sm max-w-none text-gray-700" v-html="formatContent(notice.content)"></div>
|
||||
<div class="rounded-lg bg-gray-50 dark:bg-gray-700 p-4 transition-colors">
|
||||
<div class="prose prose-sm dark:prose-invert max-w-none text-gray-700 dark:text-gray-300 transition-colors" v-html="formatContent(notice.content)"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -197,7 +201,7 @@ onMounted(() => {
|
||||
<div class="flex justify-between">
|
||||
<button
|
||||
@click="goBack"
|
||||
class="flex items-center gap-2 rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50"
|
||||
class="flex items-center gap-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
<ArrowLeft class="h-4 w-4" />
|
||||
返回列表
|
||||
@@ -210,13 +214,13 @@ onMounted(() => {
|
||||
</div>
|
||||
|
||||
<!-- 通知不存在 -->
|
||||
<div v-else class="rounded-lg border border-gray-200 bg-white p-8 text-center shadow-sm">
|
||||
<Bell class="mx-auto mb-4 h-12 w-12 text-gray-300" />
|
||||
<h3 class="mb-2 text-lg font-medium text-gray-900">通知不存在</h3>
|
||||
<p class="mb-4 text-gray-500">该通知可能已被删除或您没有权限查看</p>
|
||||
<div v-else class="rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-8 text-center shadow-sm transition-colors">
|
||||
<Bell class="mx-auto mb-4 h-12 w-12 text-gray-300 dark:text-gray-600" />
|
||||
<h3 class="mb-2 text-lg font-medium text-gray-900 dark:text-gray-100 transition-colors">通知不存在</h3>
|
||||
<p class="mb-4 text-gray-500 dark:text-gray-400 transition-colors">该通知可能已被删除或您没有权限查看</p>
|
||||
<button
|
||||
@click="goBack"
|
||||
class="rounded-md bg-sky-500 px-4 py-2 text-sm font-medium text-white hover:bg-sky-600"
|
||||
class="rounded-md bg-sky-500 dark:bg-sky-600 px-4 py-2 text-sm font-medium text-white hover:bg-sky-600 dark:hover:bg-sky-500 transition-colors"
|
||||
>
|
||||
返回通知列表
|
||||
</button>
|
||||
@@ -241,4 +245,13 @@ onMounted(() => {
|
||||
:deep(.prose) {
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
/* 暗黑模式特有样式 */
|
||||
.dark .bg-gray-750 {
|
||||
background-color: #1e293b;
|
||||
}
|
||||
|
||||
.dark .bg-gray-650 {
|
||||
background-color: #334155;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
Search,
|
||||
X,
|
||||
} from 'lucide-vue-next';
|
||||
import {getNoticeListApi, readAllApi, readApi} from "#/views/notice/api";
|
||||
import {notification} from "ant-design-vue";
|
||||
import { getNoticeListApi, readAllApi, readApi } from "#/views/notice/api";
|
||||
import { notification } from "ant-design-vue";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
@@ -104,21 +104,21 @@ const typeIcons = {
|
||||
};
|
||||
|
||||
const typeColors = {
|
||||
0: 'text-sky-500',
|
||||
1: 'text-amber-500',
|
||||
2: 'text-emerald-500',
|
||||
0: 'text-sky-500 dark:text-sky-400',
|
||||
1: 'text-amber-500 dark:text-amber-400',
|
||||
2: 'text-emerald-500 dark:text-emerald-400',
|
||||
};
|
||||
|
||||
const typeBgColors = {
|
||||
0: 'bg-sky-50',
|
||||
1: 'bg-amber-50',
|
||||
2: 'bg-emerald-50',
|
||||
0: 'bg-sky-50 dark:bg-sky-900/30',
|
||||
1: 'bg-amber-50 dark:bg-amber-900/30',
|
||||
2: 'bg-emerald-50 dark:bg-emerald-900/30',
|
||||
};
|
||||
|
||||
const typeBorderColors = {
|
||||
0: 'border-sky-200',
|
||||
1: 'border-amber-200',
|
||||
2: 'border-emerald-200',
|
||||
0: 'border-sky-200 dark:border-sky-800',
|
||||
1: 'border-amber-200 dark:border-amber-800',
|
||||
2: 'border-emerald-200 dark:border-emerald-800',
|
||||
};
|
||||
|
||||
// 处理消息点击
|
||||
@@ -130,14 +130,14 @@ const handleItemClick = (item) => {
|
||||
const markAsRead = async (event, item) => {
|
||||
event.stopPropagation();
|
||||
try {
|
||||
readApi(item.id).then(() => {
|
||||
item.status = 1; // 临时更新UI状态
|
||||
// 成功后可以重新获取数据或者直接更新本地状态
|
||||
notification.success({
|
||||
message: '标记成功',
|
||||
duration: 2,
|
||||
});
|
||||
await readApi(item.id);
|
||||
item.status = 1; // 临时更新UI状态
|
||||
// 成功后可以重新获取数据或者直接更新本地状态
|
||||
notification.success({
|
||||
message: '标记成功',
|
||||
duration: 2,
|
||||
});
|
||||
getNoticeList();
|
||||
} catch (error) {
|
||||
console.error('标记已读失败:', error);
|
||||
}
|
||||
@@ -146,19 +146,16 @@ const markAsRead = async (event, item) => {
|
||||
// 标记所有消息为已读
|
||||
const markAllAsRead = async () => {
|
||||
try {
|
||||
// 这里应该调用标记全部已读的API
|
||||
// await markAllAsReadApi();
|
||||
readAllApi().then(() => {
|
||||
messages.value.forEach(msg => {
|
||||
msg.status = 1;
|
||||
});
|
||||
// 成功后可以重新获取数据或者直接更新本地状态
|
||||
notification.success({
|
||||
message: '标记成功',
|
||||
duration: 2,
|
||||
});
|
||||
})
|
||||
await readAllApi();
|
||||
messages.value.forEach(msg => {
|
||||
msg.status = 1;
|
||||
});
|
||||
// 成功后可以重新获取数据或者直接更新本地状态
|
||||
notification.success({
|
||||
message: '标记成功',
|
||||
duration: 2,
|
||||
});
|
||||
getNoticeList();
|
||||
} catch (error) {
|
||||
console.error('标记全部已读失败:', error);
|
||||
}
|
||||
@@ -205,10 +202,10 @@ const toggleExpand = (event, id) => {
|
||||
<Page title="消息中心">
|
||||
<template #extra>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-gray-500">共 {{ total }} 条消息</span>
|
||||
<span class="text-gray-500 dark:text-gray-400">共 {{ total }} 条消息</span>
|
||||
<span
|
||||
v-if="unreadCount > 0"
|
||||
class="rounded-full bg-red-500 px-2 py-0.5 text-xs font-medium text-white"
|
||||
class="rounded-full bg-red-500 dark:bg-red-600 px-2 py-0.5 text-xs font-medium text-white"
|
||||
>
|
||||
{{ unreadCount }} 未读
|
||||
</span>
|
||||
@@ -223,7 +220,7 @@ const toggleExpand = (event, id) => {
|
||||
<!-- 筛选下拉菜单 -->
|
||||
<div class="relative">
|
||||
<button
|
||||
class="flex items-center gap-2 rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2"
|
||||
class="flex items-center gap-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-sky-500 dark:focus:ring-sky-400 focus:ring-offset-2 dark:focus:ring-offset-gray-900 transition-colors"
|
||||
@click="toggleFilterDropdown"
|
||||
>
|
||||
<Filter class="h-4 w-4" />
|
||||
@@ -233,7 +230,7 @@ const toggleExpand = (event, id) => {
|
||||
|
||||
<div
|
||||
v-if="showFilterDropdown"
|
||||
class="absolute left-0 top-full z-10 mt-1 w-56 origin-top-left rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||
class="absolute left-0 top-full z-10 mt-1 w-56 origin-top-left rounded-md bg-white dark:bg-gray-800 shadow-lg ring-1 ring-black ring-opacity-5 dark:ring-white dark:ring-opacity-10 focus:outline-none"
|
||||
>
|
||||
<div class="py-1">
|
||||
<button
|
||||
@@ -241,10 +238,10 @@ const toggleExpand = (event, id) => {
|
||||
:key="type.id"
|
||||
:class="
|
||||
filterType === type.id
|
||||
? 'bg-gray-50 font-medium text-sky-600'
|
||||
: 'text-gray-700'
|
||||
? 'bg-gray-50 dark:bg-gray-700 font-medium text-sky-600 dark:text-sky-400'
|
||||
: 'text-gray-700 dark:text-gray-200'
|
||||
"
|
||||
class="flex w-full items-center gap-2 px-4 py-2 text-left text-sm hover:bg-gray-100"
|
||||
class="flex w-full items-center gap-2 px-4 py-2 text-left text-sm hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
|
||||
@click="selectFilterType(type.id)"
|
||||
>
|
||||
<component
|
||||
@@ -264,11 +261,11 @@ const toggleExpand = (event, id) => {
|
||||
<div
|
||||
class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3"
|
||||
>
|
||||
<Search class="h-4 w-4 text-gray-400" />
|
||||
<Search class="h-4 w-4 text-gray-400 dark:text-gray-500" />
|
||||
</div>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
class="block w-full rounded-lg border border-gray-200 bg-white py-2 pl-10 pr-10 text-sm text-gray-700 focus:border-sky-500 focus:outline-none focus:ring-1 focus:ring-sky-500"
|
||||
class="block w-full rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 py-2 pl-10 pr-10 text-sm text-gray-700 dark:text-gray-200 focus:border-sky-500 dark:focus:border-sky-400 focus:outline-none focus:ring-1 focus:ring-sky-500 dark:focus:ring-sky-400 transition-colors"
|
||||
placeholder="搜索消息..."
|
||||
type="text"
|
||||
/>
|
||||
@@ -277,28 +274,28 @@ const toggleExpand = (event, id) => {
|
||||
class="absolute inset-y-0 right-0 flex items-center pr-3"
|
||||
@click="clearSearch"
|
||||
>
|
||||
<X class="h-4 w-4 text-gray-400 hover:text-gray-600" />
|
||||
<X class="h-4 w-4 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 transition-colors" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 标记全部已读按钮 -->
|
||||
<button
|
||||
v-if="unreadCount > 0"
|
||||
class="flex items-center gap-1 rounded-lg bg-white px-3 py-2 text-sm font-medium text-gray-700 shadow-sm ring-1 ring-gray-200 hover:bg-gray-50"
|
||||
class="flex items-center gap-1 rounded-lg bg-white dark:bg-gray-800 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 shadow-sm ring-1 ring-gray-200 dark:ring-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||
@click="markAllAsRead"
|
||||
>
|
||||
<CheckCircle2 class="h-4 w-4 text-gray-500" />
|
||||
<CheckCircle2 class="h-4 w-4 text-gray-500 dark:text-gray-400" />
|
||||
标记全部已读
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 消息列表 -->
|
||||
<div
|
||||
class="overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm"
|
||||
class="overflow-hidden rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 shadow-sm"
|
||||
>
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="flex justify-center py-12">
|
||||
<div class="h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-sky-500"></div>
|
||||
<div class="h-8 w-8 animate-spin rounded-full border-4 border-gray-200 dark:border-gray-700 border-t-sky-500 dark:border-t-sky-400"></div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
@@ -306,22 +303,22 @@ const toggleExpand = (event, id) => {
|
||||
v-else-if="messages.length === 0"
|
||||
class="flex flex-col items-center justify-center py-16"
|
||||
>
|
||||
<Bell class="mb-4 h-16 w-16 text-gray-200" />
|
||||
<p class="text-gray-500">暂无消息</p>
|
||||
<Bell class="mb-4 h-16 w-16 text-gray-200 dark:text-gray-700" />
|
||||
<p class="text-gray-500 dark:text-gray-400">暂无消息</p>
|
||||
<p
|
||||
v-if="searchQuery || filterType !== -1"
|
||||
class="mt-2 text-sm text-gray-400"
|
||||
class="mt-2 text-sm text-gray-400 dark:text-gray-500"
|
||||
>
|
||||
尝试调整筛选条件
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 消息列表 -->
|
||||
<ul v-else class="divide-y divide-gray-100">
|
||||
<ul v-else class="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
<li
|
||||
v-for="item in messages"
|
||||
:key="item.id"
|
||||
:class="[item.status === 0 ? 'bg-gray-50' : 'hover:bg-gray-50']"
|
||||
:class="[item.status === 0 ? 'bg-gray-50 dark:bg-gray-900' : 'bg-gray-50 dark:bg-gray-800']"
|
||||
class="group relative cursor-pointer transition-all duration-200"
|
||||
@click="handleItemClick(item)"
|
||||
>
|
||||
@@ -330,7 +327,7 @@ const toggleExpand = (event, id) => {
|
||||
<div
|
||||
:class="[
|
||||
item.status === 1
|
||||
? 'bg-gray-100 group-hover:bg-gray-200'
|
||||
? 'bg-gray-100 dark:bg-gray-700 '
|
||||
: typeBgColors[item.type],
|
||||
]"
|
||||
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full transition-all duration-200"
|
||||
@@ -339,7 +336,7 @@ const toggleExpand = (event, id) => {
|
||||
:is="typeIcons[item.type]"
|
||||
:class="[
|
||||
item.status === 1
|
||||
? 'text-gray-400 group-hover:text-gray-600'
|
||||
? 'text-gray-400 dark:text-gray-500 group-hover:text-gray-600 dark:group-hover:text-gray-300'
|
||||
: typeColors[item.type],
|
||||
]"
|
||||
class="h-5 w-5 transition-all duration-200"
|
||||
@@ -352,15 +349,15 @@ const toggleExpand = (event, id) => {
|
||||
<h3
|
||||
:class="[
|
||||
item.status === 0
|
||||
? 'font-semibold text-gray-900'
|
||||
: 'text-gray-700 group-hover:text-gray-900',
|
||||
? 'font-semibold text-gray-900 dark:text-white'
|
||||
: 'text-gray-700 dark:text-gray-300 group-hover:text-gray-900 dark:group-hover:text-white',
|
||||
]"
|
||||
class="text-sm font-medium transition-all duration-200 sm:text-base"
|
||||
>
|
||||
{{ item.title }}
|
||||
</h3>
|
||||
<span
|
||||
class="shrink-0 whitespace-nowrap text-xs text-gray-400"
|
||||
class="shrink-0 whitespace-nowrap text-xs text-gray-400 dark:text-gray-500"
|
||||
>{{ item.date }}</span>
|
||||
</div>
|
||||
|
||||
@@ -368,7 +365,7 @@ const toggleExpand = (event, id) => {
|
||||
<span
|
||||
:class="[
|
||||
item.status === 1
|
||||
? 'border-gray-200 text-gray-500'
|
||||
? 'border-gray-200 dark:border-gray-700 text-gray-500 dark:text-gray-400'
|
||||
: `${typeColors[item.type]} ${typeBorderColors[item.type]}`,
|
||||
]"
|
||||
class="inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium transition-all duration-200"
|
||||
@@ -380,7 +377,7 @@ const toggleExpand = (event, id) => {
|
||||
<div class="mt-1 w-full">
|
||||
<p
|
||||
:class="[
|
||||
item.status === 1 ? 'text-gray-500' : 'text-gray-700',
|
||||
item.status === 1 ? 'text-gray-500 dark:text-gray-400' : 'text-gray-700 dark:text-gray-300',
|
||||
expandedMessage === item.id ? '' : 'line-clamp-1',
|
||||
]"
|
||||
class="text-xs transition-all duration-200"
|
||||
@@ -388,15 +385,15 @@ const toggleExpand = (event, id) => {
|
||||
{{ item.detail }}
|
||||
</p>
|
||||
|
||||
<!-- <!– 展开/收起按钮 –>-->
|
||||
<!-- <button-->
|
||||
<!-- v-if="item.detail && item.detail.includes('\n')"-->
|
||||
<!-- class="mt-1 flex items-center gap-1 text-xs text-sky-500 hover:text-sky-600"-->
|
||||
<!-- @click="toggleExpand($event, item.id)"-->
|
||||
<!-- >-->
|
||||
<!-- <Eye class="h-3 w-3" />-->
|
||||
<!-- {{ expandedMessage === item.id ? '收起' : '展开' }}-->
|
||||
<!-- </button>-->
|
||||
<!-- 展开/收起按钮 -->
|
||||
<button
|
||||
v-if="item.detail && item.detail.length > 50"
|
||||
class="mt-1 flex items-center gap-1 text-xs text-sky-500 dark:text-sky-400 hover:text-sky-600 dark:hover:text-sky-300 transition-colors"
|
||||
@click="toggleExpand($event, item.id)"
|
||||
>
|
||||
<Eye class="h-3 w-3" />
|
||||
{{ expandedMessage === item.id ? '收起' : '展开' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -405,14 +402,14 @@ const toggleExpand = (event, id) => {
|
||||
<div class="flex shrink-0 items-center gap-2">
|
||||
<button
|
||||
v-if="item.status === 0"
|
||||
class="rounded-full p-1.5 text-gray-400 transition-all duration-200 hover:bg-gray-100 hover:text-gray-600"
|
||||
class="rounded-full p-1.5 text-gray-400 dark:text-gray-500 transition-all duration-200 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
title="标记为已读"
|
||||
@click="markAsRead($event, item)"
|
||||
>
|
||||
<CheckCircle2 class="h-5 w-5" />
|
||||
</button>
|
||||
<ChevronRight
|
||||
class="h-5 w-5 text-gray-300 transition-all duration-200 group-hover:text-gray-400"
|
||||
class="h-5 w-5 text-gray-300 dark:text-gray-600 transition-all duration-200 group-hover:text-gray-400 dark:group-hover:text-gray-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -429,17 +426,17 @@ const toggleExpand = (event, id) => {
|
||||
<!-- 分页控件 -->
|
||||
<div
|
||||
v-if="totalPages > 1"
|
||||
class="flex items-center justify-between border-t border-gray-200 bg-gray-50 px-4 py-3 sm:px-6"
|
||||
class="flex items-center justify-between border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-750 px-4 py-3 sm:px-6"
|
||||
>
|
||||
<div class="flex flex-1 justify-between sm:hidden">
|
||||
<button
|
||||
:class="[
|
||||
currentPage === 1
|
||||
? 'text-gray-300'
|
||||
: 'text-gray-700 hover:bg-gray-50',
|
||||
? 'text-gray-300 dark:text-gray-600'
|
||||
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700',
|
||||
]"
|
||||
:disabled="currentPage === 1"
|
||||
class="relative inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium"
|
||||
class="relative inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-4 py-2 text-sm font-medium transition-colors"
|
||||
@click="goToPage(currentPage - 1)"
|
||||
>
|
||||
上一页
|
||||
@@ -447,11 +444,11 @@ const toggleExpand = (event, id) => {
|
||||
<button
|
||||
:class="[
|
||||
currentPage === totalPages
|
||||
? 'text-gray-300'
|
||||
: 'text-gray-700 hover:bg-gray-50',
|
||||
? 'text-gray-300 dark:text-gray-600'
|
||||
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700',
|
||||
]"
|
||||
:disabled="currentPage === totalPages"
|
||||
class="relative ml-3 inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium"
|
||||
class="relative ml-3 inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-4 py-2 text-sm font-medium transition-colors"
|
||||
@click="goToPage(currentPage + 1)"
|
||||
>
|
||||
下一页
|
||||
@@ -461,7 +458,7 @@ const toggleExpand = (event, id) => {
|
||||
class="hidden sm:flex sm:flex-1 sm:items-center sm:justify-between"
|
||||
>
|
||||
<div>
|
||||
<p class="text-sm text-gray-700">
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300">
|
||||
显示第
|
||||
<span class="font-medium">{{
|
||||
(currentPage - 1) * pageSize + 1
|
||||
@@ -484,10 +481,10 @@ const toggleExpand = (event, id) => {
|
||||
:class="
|
||||
currentPage === 1
|
||||
? 'cursor-not-allowed'
|
||||
: 'hover:text-gray-500'
|
||||
: 'hover:text-gray-500 dark:hover:text-gray-300'
|
||||
"
|
||||
:disabled="currentPage === 1"
|
||||
class="relative inline-flex items-center rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-20 focus:outline-offset-0"
|
||||
class="relative inline-flex items-center rounded-l-md px-2 py-2 text-gray-400 dark:text-gray-500 ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 focus:z-20 focus:outline-offset-0 transition-colors"
|
||||
@click="goToPage(currentPage - 1)"
|
||||
>
|
||||
<span class="sr-only">上一页</span>
|
||||
@@ -505,10 +502,10 @@ const toggleExpand = (event, id) => {
|
||||
"
|
||||
:class="[
|
||||
page === currentPage
|
||||
? 'z-10 bg-sky-500 text-white focus:z-20 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-500'
|
||||
: 'text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-20 focus:outline-offset-0',
|
||||
? 'z-10 bg-sky-500 dark:bg-sky-600 text-white focus:z-20 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-500 dark:focus-visible:outline-sky-400'
|
||||
: 'text-gray-900 dark:text-gray-200 ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 focus:z-20 focus:outline-offset-0',
|
||||
]"
|
||||
class="relative inline-flex items-center px-4 py-2 text-sm font-semibold"
|
||||
class="relative inline-flex items-center px-4 py-2 text-sm font-semibold transition-colors"
|
||||
@click="goToPage(page)"
|
||||
>
|
||||
{{ page }}
|
||||
@@ -520,7 +517,7 @@ const toggleExpand = (event, id) => {
|
||||
(page === 2 && currentPage > 3) ||
|
||||
(page === totalPages - 1 && currentPage < totalPages - 2)
|
||||
"
|
||||
class="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-gray-700 ring-1 ring-inset ring-gray-300"
|
||||
class="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-gray-700 dark:text-gray-300 ring-1 ring-inset ring-gray-300 dark:ring-gray-600"
|
||||
>
|
||||
...
|
||||
</span>
|
||||
@@ -530,10 +527,10 @@ const toggleExpand = (event, id) => {
|
||||
:class="
|
||||
currentPage === totalPages
|
||||
? 'cursor-not-allowed'
|
||||
: 'hover:text-gray-500'
|
||||
: 'hover:text-gray-500 dark:hover:text-gray-300'
|
||||
"
|
||||
:disabled="currentPage === totalPages"
|
||||
class="relative inline-flex items-center rounded-r-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-20 focus:outline-offset-0"
|
||||
class="relative inline-flex items-center rounded-r-md px-2 py-2 text-gray-400 dark:text-gray-500 ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 focus:z-20 focus:outline-offset-0 transition-colors"
|
||||
@click="goToPage(currentPage + 1)"
|
||||
>
|
||||
<span class="sr-only">下一页</span>
|
||||
@@ -570,17 +567,26 @@ const toggleExpand = (event, id) => {
|
||||
/* 微妙的悬停动画 */
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(14, 165, 233, 0.2);
|
||||
box-shadow: 0 0 0 0 rgba(var(--color-primary-rgb), 0.2);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 6px rgba(14, 165, 233, 0);
|
||||
box-shadow: 0 0 0 6px rgba(var(--color-primary-rgb), 0);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(14, 165, 233, 0);
|
||||
box-shadow: 0 0 0 0 rgba(var(--color-primary-rgb), 0);
|
||||
}
|
||||
}
|
||||
|
||||
.group:hover .pulse-on-hover {
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
/* 暗黑模式下的自定义灰色 */
|
||||
.dark .bg-gray-750 {
|
||||
background-color: #1a202c; /* 介于gray-800和gray-900之间 */
|
||||
}
|
||||
|
||||
.dark .bg-gray-650 {
|
||||
background-color: #2d3748; /* 介于gray-600和gray-700之间 */
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
"dependencies": {
|
||||
"html2canvas": "^1.4.1",
|
||||
"js-base64": "^3.7.7",
|
||||
"lodash-es": "^4.17.21"
|
||||
"lodash-es": "^4.17.21",
|
||||
"lucide-vue-next": "^0.487.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ async function handleSubmit() {
|
||||
if (valid) {
|
||||
localStorage.setItem(
|
||||
REMEMBER_ME_KEY,
|
||||
rememberMe.value ? values?.username : '',
|
||||
rememberMe.value ? values?.nick_name : '',
|
||||
);
|
||||
emit('submit', values);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export const authenticateResponseInterceptor = ({
|
||||
rejected: async (error) => {
|
||||
const { config, response } = error;
|
||||
// 如果不是 401 错误,直接抛出异常
|
||||
if (response?.status !== 401) {
|
||||
if (response?.data.code !== 401) {
|
||||
throw error;
|
||||
}
|
||||
// 判断是否启用了 refreshToken 功能
|
||||
|
||||
26
pnpm-lock.yaml
generated
26
pnpm-lock.yaml
generated
@@ -478,6 +478,9 @@ importers:
|
||||
lodash-es:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
lucide-vue-next:
|
||||
specifier: ^0.487.0
|
||||
version: 0.487.0(vue@3.5.13(typescript@5.6.3))
|
||||
devDependencies:
|
||||
'@changesets/changelog-github':
|
||||
specifier: 'catalog:'
|
||||
@@ -3157,8 +3160,8 @@ packages:
|
||||
resolution: {integrity: sha512-bmsP4L2HqBF6i6uaMqJMcFBONVjKt+siGluRq4Ca4C0q7W2eMaVZr8iCgF9dKbcVXutftkC7D6z2SaSMmLiDyA==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
'@intlify/shared@11.1.2':
|
||||
resolution: {integrity: sha512-dF2iMMy8P9uKVHV/20LA1ulFLL+MKSbfMiixSmn6fpwqzvix38OIc7ebgnFbBqElvghZCW9ACtzKTGKsTGTWGA==}
|
||||
'@intlify/shared@11.1.3':
|
||||
resolution: {integrity: sha512-pTFBgqa/99JRA2H1qfyqv97MKWJrYngXBA/I0elZcYxvJgcCw3mApAoPW3mJ7vx3j+Ti0FyKUFZ4hWxdjKaxvA==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
'@intlify/shared@12.0.0-alpha.2':
|
||||
@@ -6699,6 +6702,11 @@ packages:
|
||||
peerDependencies:
|
||||
vue: ^3.5.13
|
||||
|
||||
lucide-vue-next@0.487.0:
|
||||
resolution: {integrity: sha512-ilVgu9EHkfId7WSjmoPkzp13cuzpSGO5J16AmltjRHFoj6MlCBGY8BzsBU/ISKVlDheUxM+MsIYjNo/1hSEa6w==}
|
||||
peerDependencies:
|
||||
vue: ^3.5.13
|
||||
|
||||
magic-string@0.25.9:
|
||||
resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
|
||||
|
||||
@@ -11234,7 +11242,7 @@ snapshots:
|
||||
|
||||
'@intlify/shared@10.0.5': {}
|
||||
|
||||
'@intlify/shared@11.1.2': {}
|
||||
'@intlify/shared@11.1.3': {}
|
||||
|
||||
'@intlify/shared@12.0.0-alpha.2': {}
|
||||
|
||||
@@ -11242,8 +11250,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2))
|
||||
'@intlify/bundle-utils': 10.0.0(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))
|
||||
'@intlify/shared': 11.1.2
|
||||
'@intlify/vue-i18n-extensions': 7.0.0(@intlify/shared@11.1.2)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))
|
||||
'@intlify/shared': 11.1.3
|
||||
'@intlify/vue-i18n-extensions': 7.0.0(@intlify/shared@11.1.3)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))
|
||||
'@rollup/pluginutils': 5.1.4(rollup@4.28.1)
|
||||
'@typescript-eslint/scope-manager': 8.18.1
|
||||
'@typescript-eslint/typescript-estree': 8.18.1(typescript@5.7.2)
|
||||
@@ -11265,11 +11273,11 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
'@intlify/vue-i18n-extensions@7.0.0(@intlify/shared@11.1.2)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))':
|
||||
'@intlify/vue-i18n-extensions@7.0.0(@intlify/shared@11.1.3)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))':
|
||||
dependencies:
|
||||
'@babel/parser': 7.26.3
|
||||
optionalDependencies:
|
||||
'@intlify/shared': 11.1.2
|
||||
'@intlify/shared': 11.1.3
|
||||
'@vue/compiler-dom': 3.5.13
|
||||
vue: 3.5.13(typescript@5.7.2)
|
||||
vue-i18n: 10.0.5(vue@3.5.13(typescript@5.7.2))
|
||||
@@ -15207,6 +15215,10 @@ snapshots:
|
||||
dependencies:
|
||||
vue: 3.5.13(typescript@5.7.2)
|
||||
|
||||
lucide-vue-next@0.487.0(vue@3.5.13(typescript@5.6.3)):
|
||||
dependencies:
|
||||
vue: 3.5.13(typescript@5.6.3)
|
||||
|
||||
magic-string@0.25.9:
|
||||
dependencies:
|
||||
sourcemap-codec: 1.4.8
|
||||
|
||||
Reference in New Issue
Block a user