车辆管理、站内信消息适配手机端
This commit is contained in:
@@ -1,5 +1,48 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" :max-height="500" :scroll="true" title="消息详情">
|
||||
<!-- 移动端使用抽屉 -->
|
||||
<el-drawer
|
||||
v-if="isMobile"
|
||||
v-model="dialogVisible"
|
||||
title="消息详情"
|
||||
direction="btt"
|
||||
size="70%"
|
||||
>
|
||||
<div class="mobile-detail">
|
||||
<div class="mobile-detail__header">
|
||||
<div class="mobile-detail__sender">{{ detailData.templateNickname }}</div>
|
||||
<div class="mobile-detail__time">{{ formatDate(detailData.createTime) }}</div>
|
||||
</div>
|
||||
|
||||
<div class="mobile-detail__meta">
|
||||
<div class="meta-item">
|
||||
<span class="meta-item__label">消息类型</span>
|
||||
<dict-tag :type="DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE" :value="detailData.templateType" />
|
||||
</div>
|
||||
<div class="meta-item">
|
||||
<span class="meta-item__label">状态</span>
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="detailData.readStatus" />
|
||||
</div>
|
||||
<div class="meta-item" v-if="detailData.readStatus">
|
||||
<span class="meta-item__label">阅读时间</span>
|
||||
<span class="meta-item__value">{{ formatDate(detailData.readTime) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mobile-detail__content">
|
||||
<div class="content-title">消息内容</div>
|
||||
<div class="content-body">{{ detailData.templateContent }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="mobile-drawer-footer">
|
||||
<el-button @click="dialogVisible = false" style="flex:1">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
|
||||
<!-- PC端使用对话框 -->
|
||||
<Dialog v-else v-model="dialogVisible" :max-height="500" :scroll="true" title="消息详情">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="发送人">
|
||||
{{ detailData.templateNickname }}
|
||||
@@ -23,12 +66,17 @@
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import * as NotifyMessageApi from '@/api/system/notify/message'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
|
||||
defineOptions({ name: 'MyNotifyMessageDetailDetail' })
|
||||
|
||||
const { width } = useWindowSize()
|
||||
const isMobile = computed(() => width.value < 768)
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const detailLoading = ref(false) // 表单的加载中
|
||||
const detailData = ref({} as NotifyMessageApi.NotifyMessageVO) // 详情数据
|
||||
@@ -46,3 +94,77 @@ const open = async (data: NotifyMessageApi.NotifyMessageVO) => {
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mobile-detail {
|
||||
&__header {
|
||||
text-align: center;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
&__sender {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
&__time {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
&__meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
&__content {
|
||||
.content-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.content-body {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
line-height: 1.8;
|
||||
padding: 12px;
|
||||
background: #fafafa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
&__label {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
&__value {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-drawer-footer {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,128 +1,199 @@
|
||||
<template>
|
||||
<doc-alert title="站内信配置" url="https://doc.iocoder.cn/notify/" />
|
||||
<!-- 移动端布局 -->
|
||||
<div v-if="isMobile" class="mobile-notify-list">
|
||||
<!-- 顶部操作栏 -->
|
||||
<div class="mobile-header">
|
||||
<div class="mobile-header__title">我的站内信</div>
|
||||
<div class="mobile-header__actions">
|
||||
<el-button size="small" @click="handleUpdateAll">全部已读</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="是否已读" prop="readStatus">
|
||||
<el-select
|
||||
v-model="queryParams.readStatus"
|
||||
placeholder="请选择状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发送时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button @click="handleUpdateList">
|
||||
<Icon icon="ep:reading" class="mr-5px" /> 标记已读
|
||||
</el-button>
|
||||
<el-button @click="handleUpdateAll">
|
||||
<Icon icon="ep:reading" class="mr-5px" /> 全部已读
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
<!-- 快捷筛选 -->
|
||||
<div class="mobile-quick-filter">
|
||||
<div
|
||||
class="quick-filter-item"
|
||||
:class="{ active: queryParams.readStatus === undefined }"
|
||||
@click="handleQuickFilter(undefined)"
|
||||
>全部</div>
|
||||
<div
|
||||
class="quick-filter-item"
|
||||
:class="{ active: queryParams.readStatus === false }"
|
||||
@click="handleQuickFilter(false)"
|
||||
>未读</div>
|
||||
<div
|
||||
class="quick-filter-item"
|
||||
:class="{ active: queryParams.readStatus === true }"
|
||||
@click="handleQuickFilter(true)"
|
||||
>已读</div>
|
||||
</div>
|
||||
|
||||
<!-- 卡片列表 -->
|
||||
<div class="mobile-list" v-loading="loading">
|
||||
<div v-if="list.length === 0 && !loading" class="mobile-empty">
|
||||
<el-empty description="暂无消息" />
|
||||
</div>
|
||||
<div
|
||||
v-for="row in list"
|
||||
:key="row.id"
|
||||
class="mobile-card"
|
||||
:class="{ 'mobile-card--unread': !row.readStatus }"
|
||||
@click="openDetail(row)"
|
||||
>
|
||||
<div class="mobile-card__header">
|
||||
<div class="mobile-card__sender">
|
||||
<span class="mobile-card__dot" v-if="!row.readStatus"></span>
|
||||
{{ row.templateNickname }}
|
||||
</div>
|
||||
<span class="mobile-card__time">{{ formatTime(row.createTime) }}</span>
|
||||
</div>
|
||||
<div class="mobile-card__body">
|
||||
<div class="mobile-card__type">
|
||||
<dict-tag :type="DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE" :value="row.templateType" />
|
||||
</div>
|
||||
<div class="mobile-card__content">{{ row.templateContent }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" :selectable="selectable" :reserve-selection="true" />
|
||||
<el-table-column label="发送人" align="center" prop="templateNickname" width="180" />
|
||||
<el-table-column
|
||||
label="发送时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
width="200"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="类型" align="center" prop="templateType" width="180">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE" :value="scope.row.templateType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="消息内容"
|
||||
align="center"
|
||||
prop="templateContent"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="是否已读" align="center" prop="readStatus" width="160">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.readStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="阅读时间"
|
||||
align="center"
|
||||
prop="readTime"
|
||||
width="200"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" align="center" width="160">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
:type="scope.row.readStatus ? 'primary' : 'warning'"
|
||||
@click="openDetail(scope.row)"
|
||||
>
|
||||
{{ scope.row.readStatus ? '详情' : '已读' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
<div class="mobile-pagination" v-if="total > 0">
|
||||
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" :page-sizes="[10, 20]" layout="total, prev, pager, next" :pager-count="5" @pagination="getList" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PC端布局 -->
|
||||
<template v-else>
|
||||
<doc-alert title="站内信配置" url="https://doc.iocoder.cn/notify/" />
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="是否已读" prop="readStatus">
|
||||
<el-select
|
||||
v-model="queryParams.readStatus"
|
||||
placeholder="请选择状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发送时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button @click="handleUpdateList">
|
||||
<Icon icon="ep:reading" class="mr-5px" /> 标记已读
|
||||
</el-button>
|
||||
<el-button @click="handleUpdateAll">
|
||||
<Icon icon="ep:reading" class="mr-5px" /> 全部已读
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" :selectable="selectable" :reserve-selection="true" />
|
||||
<el-table-column label="发送人" align="center" prop="templateNickname" width="180" />
|
||||
<el-table-column
|
||||
label="发送时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
width="200"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="类型" align="center" prop="templateType" width="180">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE" :value="scope.row.templateType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="消息内容"
|
||||
align="center"
|
||||
prop="templateContent"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="是否已读" align="center" prop="readStatus" width="160">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.readStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="阅读时间"
|
||||
align="center"
|
||||
prop="readTime"
|
||||
width="200"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" align="center" width="160">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
:type="scope.row.readStatus ? 'primary' : 'warning'"
|
||||
@click="openDetail(scope.row)"
|
||||
>
|
||||
{{ scope.row.readStatus ? '详情' : '已读' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<!-- 表单弹窗:详情 -->
|
||||
<MyNotifyMessageDetail ref="detailRef" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { DICT_TYPE, getBoolDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as NotifyMessageApi from '@/api/system/notify/message'
|
||||
import MyNotifyMessageDetail from './MyNotifyMessageDetail.vue'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
|
||||
defineOptions({ name: 'SystemMyNotify' })
|
||||
|
||||
const { width } = useWindowSize()
|
||||
const isMobile = computed(() => width.value < 768)
|
||||
|
||||
const message = useMessage() // 消息
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
@@ -158,11 +229,37 @@ const handleQuery = () => {
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
tableRef.value.clearSelection()
|
||||
queryFormRef.value?.resetFields()
|
||||
tableRef.value?.clearSelection()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 快捷筛选 */
|
||||
const handleQuickFilter = (status: boolean | undefined) => {
|
||||
queryParams.readStatus = status
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 格式化时间 */
|
||||
const formatTime = (time: string) => {
|
||||
if (!time) return ''
|
||||
const date = new Date(time)
|
||||
const now = new Date()
|
||||
const diff = now.getTime() - date.getTime()
|
||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
|
||||
|
||||
if (days === 0) {
|
||||
return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
|
||||
} else if (days === 1) {
|
||||
return '昨天'
|
||||
} else if (days < 7) {
|
||||
return `${days}天前`
|
||||
} else {
|
||||
return date.toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit' })
|
||||
}
|
||||
}
|
||||
|
||||
/** 详情操作 */
|
||||
const detailRef = ref()
|
||||
const openDetail = (data: NotifyMessageApi.NotifyMessageVO) => {
|
||||
@@ -216,3 +313,117 @@ onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mobile-notify-list {
|
||||
padding: 12px;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.mobile-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
|
||||
&__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-quick-filter {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.quick-filter-item {
|
||||
padding: 6px 16px;
|
||||
font-size: 14px;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
color: #909399;
|
||||
background: #fff;
|
||||
transition: all 0.2s;
|
||||
|
||||
&.active {
|
||||
color: #fff;
|
||||
background: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.mobile-empty { padding: 40px 0; }
|
||||
|
||||
.mobile-card {
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
padding: 14px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||
transition: all 0.2s;
|
||||
|
||||
&--unread {
|
||||
border-left: 3px solid #409eff;
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&__sender {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
&__dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #f56c6c;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&__time {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
&__body {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
&__type {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
&__content {
|
||||
color: #606266;
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-pagination {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
:deep(.el-pagination) { flex-wrap: wrap; justify-content: center; }
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user