init: 数字员工平台初始代码
包含前端(Vue3 + VueFlow 画布)、后端(Go)、文档体系。 - 工作台画布:节点拖放、连线模式、右键菜单、AI 助手 - 后端:连接器 API、专员种子数据 - 导航:左侧导航、工坊、市场、控制台
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<div class="page-title">消息通知</div>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="card-head">
|
||||
<span>共 {{ items.length }} 条通知</span>
|
||||
<el-button type="primary" size="small" plain @click="readAll">全部标记已读</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-empty v-if="!loading && items.length === 0" description="暂无通知" />
|
||||
|
||||
<div v-else v-loading="loading">
|
||||
<div
|
||||
v-for="n in items"
|
||||
:key="n.id"
|
||||
class="notice-item"
|
||||
:class="{ unread: !n.read }"
|
||||
@click="openNotice(n)"
|
||||
>
|
||||
<div class="notice-dot" :class="{ dot: !n.read }" />
|
||||
<div class="notice-main">
|
||||
<div class="notice-title">
|
||||
<span class="title">{{ n.title }}</span>
|
||||
<el-tag size="small" effect="plain" :type="typeTag(n.type)">{{ typeLabel(n.type) }}</el-tag>
|
||||
<span class="time">{{ fmtTime(n.created_at) }}</span>
|
||||
</div>
|
||||
<div class="content">{{ n.content }}</div>
|
||||
</div>
|
||||
<el-button v-if="!n.read" type="primary" link size="small" @click.stop="markOne(n)">标为已读</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { myNotifications, markRead, markAllRead } from '@/api/notification'
|
||||
import { useNotificationStore } from '@/store/notification'
|
||||
|
||||
const router = useRouter()
|
||||
const notifStore = useNotificationStore()
|
||||
const loading = ref(false)
|
||||
const items = ref([])
|
||||
|
||||
function fmtTime(t) {
|
||||
if (!t) return '—'
|
||||
return String(t).replace('T', ' ').slice(0, 16)
|
||||
}
|
||||
|
||||
function typeLabel(t) {
|
||||
return { exam_publish: '考试发布', exam_pass: '考试通过', position_set: '岗位设置' }[t] || '通知'
|
||||
}
|
||||
|
||||
function typeTag(t) {
|
||||
return { exam_publish: 'warning', exam_pass: 'success', position_set: 'primary' }[t] || 'info'
|
||||
}
|
||||
|
||||
function openNotice(n) {
|
||||
if (!n.read) markOne(n)
|
||||
if (n.link) router.push(n.link)
|
||||
}
|
||||
|
||||
async function markOne(n) {
|
||||
try {
|
||||
await markRead(n.id)
|
||||
n.read = true
|
||||
notifStore.refresh()
|
||||
} catch (e) {
|
||||
// 忽略
|
||||
}
|
||||
}
|
||||
|
||||
async function readAll() {
|
||||
try {
|
||||
await markAllRead()
|
||||
items.value.forEach((n) => (n.read = true))
|
||||
notifStore.setUnread(0)
|
||||
} catch (e) {
|
||||
// 拦截器已提示
|
||||
}
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await myNotifications()
|
||||
items.value = res.data || []
|
||||
} catch (e) {
|
||||
items.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.notice-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding: 14px 8px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.notice-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.notice-item:hover {
|
||||
background: #f8f9fb;
|
||||
}
|
||||
.notice-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
margin-top: 6px;
|
||||
flex: none;
|
||||
}
|
||||
.notice-dot.dot {
|
||||
background: #f56c6c;
|
||||
}
|
||||
.notice-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.notice-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.title {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
.notice-item.unread .title {
|
||||
color: #303133;
|
||||
}
|
||||
.time {
|
||||
margin-left: auto;
|
||||
font-size: 12px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
.content {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user