feat:增加主包(tabbar)、system(系统管理)、infra(基础设施)、bpm(工作流程)的页面
This commit is contained in:
82
src/pages/message/components/detail-popup.vue
Normal file
82
src/pages/message/components/detail-popup.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<wd-popup
|
||||
v-model="visible"
|
||||
position="bottom"
|
||||
custom-style="border-radius: 24rpx 24rpx 0 0; max-height: 80vh;"
|
||||
safe-area-inset-bottom
|
||||
@close="visible = false"
|
||||
>
|
||||
<view class="p-32rpx">
|
||||
<!-- 标题 -->
|
||||
<view class="mb-32rpx flex items-center justify-between">
|
||||
<view class="text-36rpx text-[#333] font-semibold">
|
||||
消息详情
|
||||
</view>
|
||||
<view class="p-8rpx" @click="visible = false">
|
||||
<wd-icon name="close" size="20px" color="#999" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 详情内容 -->
|
||||
<view v-if="formData" class="space-y-24rpx">
|
||||
<view class="flex items-start">
|
||||
<text class="w-160rpx shrink-0 text-28rpx text-[#999]">发送人</text>
|
||||
<text class="text-28rpx text-[#333]">{{ formData.templateNickname || '-' }}</text>
|
||||
</view>
|
||||
<view class="flex items-start">
|
||||
<text class="w-160rpx shrink-0 text-28rpx text-[#999]">发送时间</text>
|
||||
<text class="text-28rpx text-[#333]">{{ formatDateTime(formData.createTime) || '-' }}</text>
|
||||
</view>
|
||||
<view class="flex items-start">
|
||||
<text class="w-160rpx shrink-0 text-28rpx text-[#999]">消息类型</text>
|
||||
<text class="text-28rpx text-[#333]">
|
||||
{{ getDictLabel(DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE, formData.templateType) }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="flex items-start">
|
||||
<text class="w-160rpx shrink-0 text-28rpx text-[#999]">是否已读</text>
|
||||
<wd-tag v-if="formData.readStatus" type="success" plain>
|
||||
已读
|
||||
</wd-tag>
|
||||
<wd-tag v-else type="warning" plain>
|
||||
未读
|
||||
</wd-tag>
|
||||
</view>
|
||||
<view v-if="formData.readStatus" class="flex items-start">
|
||||
<text class="w-160rpx shrink-0 text-28rpx text-[#999]">阅读时间</text>
|
||||
<text class="text-28rpx text-[#333]">{{ formatDateTime(formData.readTime) || '-' }}</text>
|
||||
</view>
|
||||
<view class="flex flex-col">
|
||||
<text class="mb-12rpx w-160rpx shrink-0 text-28rpx text-[#999]">消息内容</text>
|
||||
<view class="rounded-12rpx bg-[#f5f5f5] p-24rpx">
|
||||
<text class="text-28rpx text-[#333]">{{ formData.templateContent || '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { NotifyMessage } from '@/api/system/notify'
|
||||
import { ref } from 'vue'
|
||||
import { getDictLabel } from '@/hooks/useDict'
|
||||
import { DICT_TYPE } from '@/utils/constants'
|
||||
import { formatDateTime } from '@/utils/date'
|
||||
|
||||
const visible = ref(false)
|
||||
const formData = ref<NotifyMessage>()
|
||||
|
||||
/** 打开弹窗 */
|
||||
function open(data: NotifyMessage) {
|
||||
formData.value = data
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
/** 关闭弹窗 */
|
||||
function close() {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
defineExpose({ open, close })
|
||||
</script>
|
||||
172
src/pages/message/components/search-form.vue
Normal file
172
src/pages/message/components/search-form.vue
Normal file
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<wd-popup
|
||||
v-model="visible"
|
||||
position="top"
|
||||
custom-style="border-radius: 0 0 24rpx 24rpx;"
|
||||
safe-area-inset-top
|
||||
@close="visible = false"
|
||||
>
|
||||
<view class="p-32rpx">
|
||||
<view class="mb-24rpx text-32rpx text-[#333] font-semibold">
|
||||
搜索消息
|
||||
</view>
|
||||
<view class="mb-24rpx">
|
||||
<view class="mb-12rpx text-28rpx text-[#666]">
|
||||
已读状态
|
||||
</view>
|
||||
<wd-radio-group v-model="formData.readStatus" shape="button" size="medium">
|
||||
<wd-radio :value="-1">
|
||||
全部
|
||||
</wd-radio>
|
||||
<wd-radio :value="1">
|
||||
已读
|
||||
</wd-radio>
|
||||
<wd-radio :value="0">
|
||||
未读
|
||||
</wd-radio>
|
||||
</wd-radio-group>
|
||||
</view>
|
||||
<view class="mb-32rpx">
|
||||
<view class="mb-12rpx text-28rpx text-[#666]">
|
||||
发送时间
|
||||
</view>
|
||||
<view class="flex items-center gap-16rpx">
|
||||
<view class="flex-1" @click="showStartPicker = true">
|
||||
<view
|
||||
class="h-72rpx flex items-center justify-center rounded-8rpx bg-[#f5f5f5] px-24rpx text-28rpx"
|
||||
>
|
||||
{{ formatDate(formData.createTime?.[0]) || '开始日期' }}
|
||||
</view>
|
||||
</view>
|
||||
<text class="text-28rpx text-[#999]">至</text>
|
||||
<view class="flex-1" @click="showEndPicker = true">
|
||||
<view
|
||||
class="h-72rpx flex items-center justify-center rounded-8rpx bg-[#f5f5f5] px-24rpx text-28rpx"
|
||||
>
|
||||
{{ formatDate(formData.createTime?.[1]) || '结束日期' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 开始时间选择器 -->
|
||||
<wd-datetime-picker-view
|
||||
v-if="showStartPicker"
|
||||
v-model="tempCreateTime[0]"
|
||||
type="date"
|
||||
:columns-height="200"
|
||||
/>
|
||||
<view v-if="showStartPicker" class="mt-16rpx flex justify-end gap-16rpx">
|
||||
<wd-button size="small" plain @click="handleStartCancel">
|
||||
取消
|
||||
</wd-button>
|
||||
<wd-button size="small" type="primary" @click="handleStartConfirm">
|
||||
确定
|
||||
</wd-button>
|
||||
</view>
|
||||
<!-- 结束时间选择器 -->
|
||||
<wd-datetime-picker-view
|
||||
v-if="showEndPicker"
|
||||
v-model="tempCreateTime[1]"
|
||||
type="date"
|
||||
:columns-height="200"
|
||||
/>
|
||||
<view v-if="showEndPicker" class="mt-16rpx flex justify-end gap-16rpx">
|
||||
<wd-button size="small" plain @click="handleEndCancel">
|
||||
取消
|
||||
</wd-button>
|
||||
<wd-button size="small" type="primary" @click="handleEndConfirm">
|
||||
确定
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
<view class="w-full flex justify-center gap-24rpx">
|
||||
<wd-button class="flex-1" plain @click="handleReset">
|
||||
重置
|
||||
</wd-button>
|
||||
<wd-button class="flex-1" type="primary" @click="handleSearch">
|
||||
搜索
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import { formatDate } from '@/utils/date'
|
||||
|
||||
/** 搜索表单数据 */
|
||||
export interface SearchFormData {
|
||||
readStatus: number // -1 表示全部, 0 未读, 1 已读
|
||||
createTime?: [number | undefined, number | undefined]
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
searchParams?: Partial<SearchFormData>
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: boolean]
|
||||
'search': [data: SearchFormData]
|
||||
'reset': []
|
||||
}>()
|
||||
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val: boolean) => emit('update:modelValue', val),
|
||||
})
|
||||
|
||||
const formData = reactive<SearchFormData>({
|
||||
readStatus: -1,
|
||||
createTime: [undefined, undefined],
|
||||
})
|
||||
|
||||
// 时间选择器状态
|
||||
const showStartPicker = ref(false)
|
||||
const showEndPicker = ref(false)
|
||||
const tempCreateTime = ref<[number, number]>([Date.now(), Date.now()])
|
||||
|
||||
/** 开始时间确认 */
|
||||
function handleStartConfirm() {
|
||||
formData.createTime = [tempCreateTime.value[0], formData.createTime?.[1]]
|
||||
showStartPicker.value = false
|
||||
}
|
||||
|
||||
/** 开始时间取消 */
|
||||
function handleStartCancel() {
|
||||
showStartPicker.value = false
|
||||
}
|
||||
|
||||
/** 结束时间确认 */
|
||||
function handleEndConfirm() {
|
||||
formData.createTime = [formData.createTime?.[0], tempCreateTime.value[1]]
|
||||
showEndPicker.value = false
|
||||
}
|
||||
|
||||
/** 结束时间取消 */
|
||||
function handleEndCancel() {
|
||||
showEndPicker.value = false
|
||||
}
|
||||
|
||||
/** 监听弹窗打开,同步外部参数 */
|
||||
watch(() => props.modelValue, (val) => {
|
||||
if (val && props.searchParams) {
|
||||
formData.readStatus = props.searchParams.readStatus ?? -1
|
||||
formData.createTime = props.searchParams.createTime ?? [undefined, undefined]
|
||||
}
|
||||
})
|
||||
|
||||
/** 搜索 */
|
||||
function handleSearch() {
|
||||
visible.value = false
|
||||
emit('search', { ...formData })
|
||||
}
|
||||
|
||||
/** 重置 */
|
||||
function handleReset() {
|
||||
formData.readStatus = -1
|
||||
formData.createTime = [undefined, undefined]
|
||||
visible.value = false
|
||||
emit('reset')
|
||||
}
|
||||
</script>
|
||||
237
src/pages/message/index.vue
Normal file
237
src/pages/message/index.vue
Normal file
@@ -0,0 +1,237 @@
|
||||
<template>
|
||||
<view class="min-h-screen bg-[#f5f5f5]">
|
||||
<!-- 顶部导航栏 -->
|
||||
<wd-navbar
|
||||
title="我的消息"
|
||||
left-arrow placeholder safe-area-inset-top fixed
|
||||
@click-left="handleBack"
|
||||
>
|
||||
<template #right>
|
||||
<view class="flex items-center gap-24rpx">
|
||||
<view @click="handleReadAll">
|
||||
<wd-icon name="check-circle" size="20px" />
|
||||
</view>
|
||||
<view @click="searchVisible = !searchVisible">
|
||||
<wd-icon name="search" size="20px" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</wd-navbar>
|
||||
|
||||
<!-- 消息列表 -->
|
||||
<view class="p-24rpx">
|
||||
<view
|
||||
v-for="item in list"
|
||||
:key="item.id"
|
||||
class="mb-24rpx overflow-hidden rounded-12rpx bg-white shadow-sm"
|
||||
@click="handleDetail(item)"
|
||||
>
|
||||
<view class="p-24rpx">
|
||||
<!-- 消息头部 -->
|
||||
<view class="mb-16rpx flex items-center justify-between">
|
||||
<view class="flex items-center">
|
||||
<view
|
||||
v-if="!item.readStatus"
|
||||
class="mr-12rpx h-16rpx w-16rpx rounded-full bg-red-500"
|
||||
/>
|
||||
<view class="text-32rpx text-[#333] font-semibold">
|
||||
{{ item.templateNickname }}
|
||||
</view>
|
||||
</view>
|
||||
<wd-tag v-if="item.readStatus" type="success" plain>
|
||||
已读
|
||||
</wd-tag>
|
||||
<wd-tag v-else type="warning" plain>
|
||||
未读
|
||||
</wd-tag>
|
||||
</view>
|
||||
<!-- 消息内容 -->
|
||||
<view class="mb-12rpx rounded-8rpx bg-[#f7f8f9] p-20rpx">
|
||||
<view class="line-clamp-1 mb-8rpx text-30rpx text-[#323333] font-bold">
|
||||
{{ getDictLabel(DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE, item.templateType) }}
|
||||
</view>
|
||||
<view class="line-clamp-2 text-28rpx text-[#777]">
|
||||
{{ item.templateContent }}
|
||||
</view>
|
||||
</view>
|
||||
<!-- 消息时间 -->
|
||||
<view class="flex items-center justify-between text-26rpx text-[#999]">
|
||||
<text>{{ formatDateTime(item.createTime) }}</text>
|
||||
<view
|
||||
v-if="!item.readStatus"
|
||||
class="text-[#1890ff]"
|
||||
@click.stop="handleReadOne(item)"
|
||||
>
|
||||
标记已读
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view v-if="loadMoreState !== 'loading' && list.length === 0" class="py-100rpx text-center">
|
||||
<wd-status-tip image="content" tip="暂无消息" />
|
||||
</view>
|
||||
<wd-loadmore
|
||||
v-if="list.length > 0"
|
||||
:state="loadMoreState"
|
||||
@reload="loadMore"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 搜索弹窗 -->
|
||||
<SearchForm
|
||||
v-model="searchVisible"
|
||||
:search-params="queryParams"
|
||||
@search="handleQuery"
|
||||
@reset="handleReset"
|
||||
/>
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<DetailPopup ref="detailPopupRef" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { SearchFormData } from './components/search-form.vue'
|
||||
import type { NotifyMessage } from '@/api/system/notify'
|
||||
import type { LoadMoreState } from '@/http/types'
|
||||
import { onReachBottom } from '@dcloudio/uni-app'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { useToast } from 'wot-design-uni'
|
||||
import {
|
||||
getMyNotifyMessagePage,
|
||||
updateAllNotifyMessageRead,
|
||||
updateNotifyMessageRead,
|
||||
} from '@/api/system/notify'
|
||||
import { getDictLabel } from '@/hooks/useDict'
|
||||
import { DICT_TYPE } from '@/utils/constants'
|
||||
import { formatDateRange, formatDateTime } from '@/utils/date'
|
||||
import DetailPopup from './components/detail-popup.vue'
|
||||
import SearchForm from './components/search-form.vue'
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '',
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
|
||||
const toast = useToast()
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref<NotifyMessage[]>([]) // 列表的数据
|
||||
const loadMoreState = ref<LoadMoreState>('loading') // 加载更多状态
|
||||
const searchVisible = ref(false) // 搜索弹窗
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
readStatus: -1 as number, // -1 表示全部
|
||||
createTime: [undefined, undefined] as [number | undefined, number | undefined],
|
||||
})
|
||||
const detailPopupRef = ref<InstanceType<typeof DetailPopup>>() // 详情弹窗
|
||||
|
||||
/** 返回上一页 */
|
||||
function handleBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
/** 查询消息列表 */
|
||||
async function getList() {
|
||||
loadMoreState.value = 'loading'
|
||||
try {
|
||||
// 构建参数
|
||||
const params = { ...queryParams } as any
|
||||
if (queryParams.readStatus !== -1) {
|
||||
params.readStatus = queryParams.readStatus === 1
|
||||
} else {
|
||||
delete params.readStatus
|
||||
}
|
||||
params.createTime = formatDateRange(queryParams.createTime)
|
||||
|
||||
// 执行查询
|
||||
const data = await getMyNotifyMessagePage(params)
|
||||
list.value = [...list.value, ...data.list]
|
||||
total.value = data.total
|
||||
loadMoreState.value = list.value.length >= total.value ? 'finished' : 'loading'
|
||||
} catch {
|
||||
queryParams.pageNo = queryParams.pageNo > 1 ? queryParams.pageNo - 1 : 1
|
||||
loadMoreState.value = 'error'
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
function handleQuery(data?: SearchFormData) {
|
||||
queryParams.readStatus = data?.readStatus ?? -1
|
||||
queryParams.createTime = data?.createTime ?? [undefined, undefined]
|
||||
queryParams.pageNo = 1
|
||||
list.value = [] // 清空列表
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
function handleReset() {
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 加载更多 */
|
||||
function loadMore() {
|
||||
if (loadMoreState.value === 'finished') {
|
||||
return
|
||||
}
|
||||
queryParams.pageNo++
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 查看详情 */
|
||||
function handleDetail(item: NotifyMessage) {
|
||||
// 如果未读,先标记已读
|
||||
if (!item.readStatus) {
|
||||
handleReadOne(item, false)
|
||||
}
|
||||
// 打开详情弹窗
|
||||
detailPopupRef.value?.open(item)
|
||||
}
|
||||
|
||||
/** 标记单条已读 */
|
||||
async function handleReadOne(item: NotifyMessage, showToast = true) {
|
||||
await updateNotifyMessageRead(item.id)
|
||||
// 更新本地状态
|
||||
item.readStatus = true
|
||||
item.readTime = new Date().toISOString()
|
||||
if (showToast) {
|
||||
toast.success('已标记为已读')
|
||||
}
|
||||
}
|
||||
|
||||
/** 标记全部已读 */
|
||||
function handleReadAll() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要将所有消息标记为已读吗?',
|
||||
success: async (res) => {
|
||||
if (!res.confirm) {
|
||||
return
|
||||
}
|
||||
await updateAllNotifyMessageRead()
|
||||
toast.success('全部已读成功')
|
||||
// 刷新列表
|
||||
queryParams.pageNo = 1
|
||||
list.value = []
|
||||
await getList()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/** 触底加载更多 */
|
||||
onReachBottom(() => {
|
||||
loadMore()
|
||||
})
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
Reference in New Issue
Block a user