This commit is contained in:
那些花儿
2025-09-23 11:32:48 +08:00
parent af146d6c9b
commit f8b1aa6398
44 changed files with 4555 additions and 1247 deletions
@@ -17,14 +17,16 @@ layout("/layouts/platform_h5.html"){
<work v-if="homeTabbarActive === 'work'"></work>
<mine v-if="homeTabbarActive === 'mine'"></mine>
<apps v-if="homeTabbarActive === 'apps'"></apps>
<msg v-if="homeTabbarActive === 'msg'"></msg>
</div>
<div id="page-tarbar" class="page-tarbar">
<van-tabbar v-model="homeTabbarActive" @change="homeTabbarChange">
<van-tabbar-item name="home" icon="wap-home-o">首页</van-tabbar-item>
<van-tabbar-item name="apps" icon="gem">应用</van-tabbar-item>
<van-tabbar-item name="work" icon="gem">工作台</van-tabbar-item>
<!-- <van-tabbar-item name="work" icon="gem">工作台</van-tabbar-item>-->
<van-tabbar-item name="todo" icon="gem">待办</van-tabbar-item>
<van-tabbar-item name="msg" icon="gem">消息</van-tabbar-item>
<van-tabbar-item name="mine" icon="user-o">我的</van-tabbar-item>
</van-tabbar>
</div>
@@ -37,6 +39,7 @@ layout("/layouts/platform_h5.html"){
<!--#include("mine.js"){}#-->
<!--#include("todo.js"){}#-->
<!--#include("apps.js"){}#-->
<!--#include("msg.js"){}#-->
new Vue({
el: "#app",
store,
@@ -45,7 +48,8 @@ layout("/layouts/platform_h5.html"){
mine,
work,
todo,
apps
apps,
msg
},
data() {
return {
@@ -0,0 +1,598 @@
const msg= {
template: /*language=HTML*/ `
<div class="message-center">
<van-nav-bar title="消息中心" placeholder fixed></van-nav-bar>
<!-- 筛选区域 -->
<van-sticky offset-top="46px">
<!-- <div class="filter-section">-->
<!-- <van-row gutter="10">-->
<!-- <van-col span="12">-->
<!-- <van-field-->
<!-- v-model="filters.type"-->
<!-- is-link-->
<!-- readonly-->
<!-- label="消息类型"-->
<!-- placeholder="选择消息类型"-->
<!-- @click="showTypePicker = true"-->
<!-- />-->
<!-- </van-col>-->
<!-- <van-col span="12" v-if="pageForm.isRead == 0">-->
<!-- <van-button -->
<!-- type="primary" -->
<!-- size="small" -->
<!-- :loading="loading"-->
<!-- @click="markAllAsRead"-->
<!-- style="margin-top: 6px; width: 100%;"-->
<!-- >-->
<!-- 全部已读-->
<!-- </van-button>-->
<!-- </van-col>-->
<!-- </van-row>-->
<!-- </div>-->
<!-- 标签页 -->
<van-tabs v-model="activeTab" @change="handleTabChange">
<van-tab title="全部消息" name="-1">
<template #title>
<van-icon name="chat-o" />
全部消息 ({{stats.total}})
</template>
</van-tab>
<van-tab title="未读消息" name="0">
<template #title>
<van-icon name="bell" />
未读消息 ({{stats.unread}})
</template>
</van-tab>
<van-tab title="已读消息" name="1">
<template #title>
<van-icon name="passed" />
已读消息 ({{stats.read}})
</template>
</van-tab>
</van-tabs>
</van-sticky>
<!-- 消息列表 -->
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<div
v-for="message in messages"
:key="message.id"
@click="viewMessage(message)"
class="custom-message-item"
:class="{'unread-message': !message.isRead}"
>
<div class="message-header">
<div class="message-title-wrapper">
<van-tag
:type="message.type === 1 ? 'danger' : 'primary'"
size="mini"
style="margin-right: 8px;"
>
{{getMessageTypeName(message.type)}}
</van-tag>
<!-- <span class="message-title">{{message.title}}</span>-->
<van-tag
v-if="!message.isRead"
type="warning"
size="mini"
style="margin-left: 8px;"
>
未读
</van-tag>
</div>
<div class="message-time">{{formatTime(message.createdAt)}}</div>
</div>
<div class="message-content">{{getContentPreview(message.content)}}</div>
<div class="message-arrow">
<van-icon name="arrow" />
</div>
</div>
</van-list>
</van-pull-refresh>
<!-- 消息类型选择器 -->
<van-popup v-model="showTypePicker" position="bottom">
<van-picker
:columns="typeColumns"
@confirm="onTypeConfirm"
@cancel="showTypePicker = false"
/>
</van-popup>
<!-- 消息详情弹窗 -->
<van-popup
v-model="detailVisible"
position="bottom"
:style="{ height: '80%' }"
closeable
close-icon-position="top-right"
>
<div class="message-detail" v-if="currentMessage.id">
<div class="detail-header">
<h3>{{currentMessage.title}}</h3>
<div class="detail-meta">
<van-tag :type="currentMessage?.globalMessage?.type === 1 ? 'danger' : 'primary'">
{{getMessageTypeName(currentMessage?.globalMessage?.type)}}
</van-tag>
<span class="detail-time">{{formatTime(currentMessage.createdAt)}}</span>
</div>
</div>
<div class="detail-content" v-html="currentMessage?.globalMessage?.content"></div>
</div>
</van-popup>
</div>
`,
data(){
return{
activeTab: '-1',
loading: false,
refreshing: false,
finished: false,
messages: [],
stats: {
total: 0,
unread: 0,
read: 0
},
filters: {
type: ''
},
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
isRead: -1
},
detailVisible: false,
currentMessage: {},
showTypePicker: false,
typeColumns: [
{ text: '全部类型', value: '' },
{ text: '系统公告', value: '1' },
{ text: '消息通知', value: '2' }
]
}
},
methods: {
// 加载统计数据
async loadStats() {
try {
const resp = await this.$axios.get('/platform/v4/msg/stats')
if (resp.code === 0) {
this.stats = resp.data
}
} catch (error) {
console.error('加载统计数据失败:', error)
}
},
// 加载消息列表
async loadMessages(isRefresh = false) {
if (isRefresh) {
this.pageForm.pageNumber = 1
this.messages = []
this.finished = false
}
this.loading = true
try {
const resp = await this.$axios.post('/platform/v4/msg/pageData', this.pageForm)
if (resp.code === 0) {
const newMessages = resp.data.list || []
if (isRefresh) {
this.messages = newMessages
} else {
this.messages = [...this.messages, ...newMessages]
}
this.pageForm.totalCount = resp.data.totalCount
// 判断是否还有更多数据
if (this.messages.length >= this.pageForm.totalCount) {
this.finished = true
}
} else {
this.$toast(resp.msg)
}
} catch (error) {
this.$toast('加载消息失败')
} finally {
this.loading = false
this.refreshing = false
}
},
// 查看消息详情
async viewMessage(message) {
try {
const resp = await this.$axios.post(`/platform/v4/msg/detail/` + message.id)
if (resp.code === 0) {
this.currentMessage = resp.data
this.detailVisible = true
// 如果是未读消息,标记为已读
if (!message.isRead) {
await this.markAsRead(message.id)
message.isRead = true
this.loadStats()
}
}
} catch (error) {
this.$toast('加载消息详情失败')
}
},
// 标记单条消息为已读
async markAsRead(messageId) {
try {
await this.$axios.post(`/platform/v4/msg/read/` + messageId)
} catch (error) {
console.error('标记已读失败:', error)
}
},
// 全部标记为已读
async markAllAsRead() {
this.$dialog.confirm({
title: '提示',
message: '确定要将所有未读消息标记为已读吗?'
}).then(async () => {
this.loading = true
try {
const resp = await this.$axios.post('/platform/v4/msg/read/all')
if (resp.code === 0) {
this.$toast.success('操作成功')
this.loadMessages(true)
this.loadStats()
} else {
this.$toast(resp.msg)
}
} catch (error) {
this.$toast('操作失败')
} finally {
this.loading = false
}
}).catch(() => {
// 用户取消
})
},
// 切换标签页
handleTabChange(name) {
this.pageForm.isRead = parseInt(name)
this.loadMessages(true)
},
// 点击统计卡片切换标签
switchTab(tabValue) {
this.activeTab = tabValue.toString()
this.pageForm.isRead = tabValue
this.loadMessages(true)
},
// 下拉刷新
onRefresh() {
this.loadMessages(true)
this.loadStats()
},
// 上拉加载更多
onLoad() {
if (this.finished) {
return
}
this.pageForm.pageNumber++
this.loadMessages()
},
// 消息类型选择确认
onTypeConfirm(value) {
this.filters.type = value.value
this.showTypePicker = false
this.loadMessages(true)
},
// 获取消息类型名称
getMessageTypeName(type) {
return type === 1 ? '系统公告' : '消息通知'
},
// 获取内容预览
getContentPreview(content) {
if (!content) return ''
// 移除HTML标签
const text = content.replace(/<[^>]*>/g, '')
return text.length > 60 ? text.substring(0, 30) + '...' : text
},
// 格式化时间
formatTime(timestamp) {
return this.$moment(timestamp).format('MM-DD HH:mm')
}
},
created() {
this.loadStats()
this.loadMessages(true)
},
style: /*language=CSS*/ `
.message-center {
background-color: #f5f7fa;
min-height: 100vh;
padding-bottom: 20px;
}
/* 自定义消息列表项样式 */
/deep/ .custom-message-item {
background-color: #fff;
padding: 20px;
margin-bottom: 8px;
cursor: pointer;
transition: all 0.2s ease;
position: relative;
border-bottom: 1px solid #f0f0f0;
border-radius: 8px;
}
/deep/ .custom-message-item:hover {
background-color: #f8f9fa;
}
/deep/ .custom-message-item:active {
background-color: #f0f0f0;
}
/deep/ .message-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
}
/deep/ .message-time {
font-size: 12px;
color: #969799;
flex-shrink: 0;
margin-left: 12px;
}
/deep/ .message-content {
font-size: 14px;
color: #646566;
line-height: 1.6;
margin-bottom: 8px;
padding-right: 20px;
}
/deep/ .message-arrow {
position: absolute;
right: 16px;
top: 50%;
transform: translateY(-50%);
color: #c8c9cc;
font-size: 12px;
}
/* 筛选区域样式 */
/deep/ .filter-section {
padding: 12px 16px;
background-color: #fff;
}
/deep/ .message-title-wrapper {
display: flex;
align-items: center;
flex-wrap: wrap;
flex: 1;
}
/deep/ .message-title {
flex: 1;
/*font-weight: 500;*/
color: #323233;
}
/* 消息详情弹窗样式 */
/deep/ .message-detail {
padding: 20px;
height: 100%;
overflow-y: auto;
background-color: #fff;
}
/deep/ .detail-header {
text-align: center;
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 1px solid #eee;
}
/deep/ .detail-header h3 {
font-size: 18px;
font-weight: 500;
margin: 0 0 8px 0;
color: #323233;
}
/deep/ .detail-meta {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
font-size: 14px;
color: #666;
}
/deep/ .detail-time {
color: #969799;
}
/deep/ .detail-content {
line-height: 1.8;
font-size: 15px;
color: #323233;
}
/deep/ .detail-content img {
max-width: 100%;
height: auto;
border-radius: 4px;
margin: 8px 0;
}
/deep/ .detail-content p {
margin: 12px 0;
}
/* 标签页样式优化 */
/deep/ .van-tabs__nav {
background-color: #fff;
}
/deep/ .van-tab {
font-size: 14px;
}
/deep/ .van-tab--active {
color: var(--color-primary, #1989fa);
font-weight: 500;
}
/deep/ .van-tabs__line {
background-color: var(--color-primary, #1989fa);
}
/* 列表样式优化 */
/deep/ .van-cell__title {
font-size: 15px;
}
/deep/ .van-cell__label {
font-size: 13px;
color: #969799;
margin-top: 4px;
}
/deep/ .van-cell__value {
font-size: 12px;
color: #969799;
}
/* 标签样式 */
/deep/ .van-tag--mini {
font-size: 10px;
padding: 2px 6px;
border-radius: 4px;
}
/* 弹窗样式 */
/deep/ .van-popup--bottom {
border-radius: 16px 16px 0 0;
}
/* 下拉刷新和上拉加载样式 */
/deep/ .van-pull-refresh__track {
background-color: #f5f7fa;
}
/deep/ .van-list__finished-text {
color: #969799;
font-size: 12px;
padding: 16px 0;
}
/* 空状态样式 */
/deep/ .van-empty {
padding: 40px 0;
}
/deep/ .van-empty__description {
color: #969799;
font-size: 14px;
}
/* 简约动画效果 */
/* 基础过渡效果 */
/deep/ .oa-message-item,
/deep/ .oa-stat-item,
/deep/ .oa-refresh-btn,
/deep/ .oa-search-input {
transition: all 0.2s ease;
}
/* 页面进入动画 */
/deep/ .page-enter {
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* 简化的响应式动画 */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* 响应式设计 */
@media (max-width: 480px) {
/deep/ .oa-header {
padding: 10px 12px;
}
/deep/ .oa-header-title {
font-size: 15px;
}
/deep/ .oa-stats-panel,
/deep/ .oa-toolbar,
/deep/ .oa-message-list {
margin: 6px 12px;
}
/deep/ .oa-stat-item {
padding: 12px 8px;
}
/deep/ .oa-stat-number {
font-size: 16px;
}
/deep/ .oa-stat-label {
font-size: 11px;
}
/deep/ .oa-message-item {
padding: 12px;
}
/deep/ .oa-message-title {
font-size: 14px;
}
/deep/ .oa-message-content {
font-size: 12px;
}
}
`
}
@@ -5,15 +5,16 @@ const todo = {
<!-- 标签页 -->
<van-sticky offset-top="46px">
<van-tabs v-model="activeTab" @change="handleTabChange" animated swipeable>
<van-tab title="待办" name="todo"></van-tab>
<van-tab title="已办" name="done"></van-tab>
<van-tab title="发起" name="started"></van-tab>
<van-tab :title="'待办(' + todoCount + ')'" name="todo"></van-tab>
<van-tab :title="'已办(' + doneCount + ')'" name="done"></van-tab>
<van-tab :title="'发起(' + startedCount + ')'" name="started"></van-tab>
</van-tabs>
<!-- 搜索筛选区域 -->
<div class="filter-section">
<div>
<div class="search-form">
<van-search v-model="pageForm.searchKeyword" placeholder="请输入搜索关键词"></van-search>
<van-search v-model="pageForm.searchKeyword" placeholder="请输入搜索关键词"
@search="doSearch"></van-search>
</div>
<!-- <van-field-->
@@ -39,63 +40,27 @@ const todo = {
</van-sticky>
<!-- 任务列表区域 -->
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="getTasks"
>
<div v-if="tasks.length > 0" class="task-list">
<div
v-for="task in tasks"
:key="task.taskId"
class="task-item"
>
<div class="task-content">
<div class="task-title">{{task.variable?.instanceName || '无标题流程'}}</div>
<div class="task-info">
<span class="task-info-label">任务节点:</span>
<span class="task-info-value">{{task.taskName}}</span>
</div>
<div class="task-info">
<span class="task-info-label">流程分类:</span>
<span class="task-info-value">
{{categoryOptions.find(item => item.id === task.category)?.name || '未知分类'}}
</span>
</div>
<div class="task-info">
<span class="task-info-label">申请人:</span>
<span class="task-info-value">{{task.variable?.initiatorName || '未知'}}</span>
</div>
<div class="task-info" v-if="activeTab === 'started'">
<span class="task-info-label">状态:</span>
<span class="task-info-value">
{{processStatusMap[task.state]?.text || '未知状态'}}
</span>
</div>
<div class="task-footer">
<van-button
type="primary"
size="mini"
@click="openView(task)"
>查看
</van-button>
</div>
</div>
</div>
<table-list :api="'/flow/todoCenter/'+ activeTab" :page_form.sync="pageForm"
ref="tableListRef"
@ready="doSearch"
title="variable.instanceName">
<template v-slot="{index,row}">
<table-column label="任务节点">{{row.taskName}}</table-column>
<table-column label="流程分类">{{categoryOptions.find(item => item.id === row.category)?.name ||
'未知分类'}}
</table-column>
<table-column label="申请人">{{row.variable?.initiatorName || '未知'}}</table-column>
<table-column label="状态" v-if="activeTab === 'started'">{{processStatusMap[row.state]?.text ||
'未知状态'}}
</table-column>
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<span>查看</span>
</div>
<div v-else class="empty-section">
<van-empty :description="getEmptyText()"></van-empty>
</div>
</van-list>
</van-pull-refresh>
</template>
</table-list>
</div>
`,
data() {
@@ -147,8 +112,6 @@ const todo = {
// 初始化数据
async initData() {
await Promise.all([this.getStatistics(), this.listCategory()]);
// 初始加载任务
// this.getTasks();
},
// 获取统计数据
@@ -181,61 +144,20 @@ const todo = {
}
},
// 获取任务列表
async getTasks() {
if (this.refreshing) {
this.tasks = [];
this.refreshing = false;
}
this.loading = true;
try {
const {code,data,msg} = await this.$axios.post("/flow/todoCenter/" + this.activeTab, this.pageForm);
if (code === 0) {
this.tasks = this.tasks.concat(data.list || []);
this.pageForm.totalCount = data.totalCount;
this.loading = false;
// 数据全部加载完成
if (this.tasks.length >= this.pageForm.totalCount) {
this.finished = true;
} else {
this.pageForm.pageNumber++;
}
}
} catch (error) {
this.$toast("获取任务列表失败");
console.error("获取任务列表失败:", error);
this.loading = false;
this.refreshing = false;
}
},
// 下拉刷新
onRefresh() {
this.pageForm.pageNumber = 1;
this.finished = false;
this.getTasks();
},
// 处理标签页变化
handleTabChange(name) {
this.activeTab = name;
this.pageForm.pageNumber = 1;
this.tasks = [];
this.finished = false;
this.getTasks();
this.doSearch()
this.getStatistics();
},
// 搜索
search() {
this.pageForm.pageNumber = 1;
this.tasks = [];
this.finished = false;
this.getTasks();
this.getStatistics();
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
},
// 重置搜索
@@ -243,7 +165,7 @@ const todo = {
this.pageForm.searchKeyword = "";
this.pageForm.category = "";
this.categoryLabel = "";
this.search();
this.doSearch();
},
// 分类选择确认
@@ -254,7 +176,7 @@ const todo = {
},
// 处理任务
openView(task) {
onView(task) {
const {taskId, taskKey, instanceId, businessNo, formKey} = task;
if (!formKey) {
@@ -278,10 +200,24 @@ const todo = {
}
},
style: /*language=CSS*/ `
.task-center {
margin: 0 auto;
/* 标签页样式优化 */
/deep/ .van-tabs__nav {
background-color: #fff;
}
/deep/ .van-tab {
font-size: 14px;
}
/deep/ .van-tab--active {
color: var(--color-primary, #1989fa);
font-weight: 500;
}
/deep/ .van-tabs__line {
background-color: var(--color-primary, #1989fa);
}
/deep/ .filter-section {
margin-bottom: 16px;
}