mirror of
https://github.com/continew-org/continew-admin-ui.git
synced 2026-01-12 05:01:39 +08:00
feat: 重构个人消息中心,支持展示个人公告,并优化相关地址
This commit is contained in:
140
src/views/user/message/components/MyMessage.vue
Normal file
140
src/views/user/message/components/MyMessage.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<GiTable
|
||||
row-key="id"
|
||||
:data="dataList"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
|
||||
:pagination="pagination"
|
||||
:disabled-tools="['size', 'setting']"
|
||||
:row-selection="{ type: 'checkbox', showCheckedAll: true }"
|
||||
:selected-keys="selectedKeys"
|
||||
@select-all="selectAll"
|
||||
@select="select"
|
||||
@refresh="search"
|
||||
>
|
||||
<template #toolbar-left>
|
||||
<a-input-search v-model="queryForm.title" placeholder="搜索标题" allow-clear @search="search" />
|
||||
<a-select
|
||||
v-model="queryForm.isRead"
|
||||
placeholder="全部状态"
|
||||
allow-clear
|
||||
style="width: 150px"
|
||||
@change="search"
|
||||
>
|
||||
<a-option :value="false">未读</a-option>
|
||||
<a-option :value="true">已读</a-option>
|
||||
</a-select>
|
||||
<a-button @click="reset">
|
||||
<template #icon><icon-refresh /></template>
|
||||
<template #default>重置</template>
|
||||
</a-button>
|
||||
</template>
|
||||
<template #toolbar-right>
|
||||
<a-button type="primary" status="danger" :disabled="!selectedKeys.length" :title="!selectedKeys.length ? '请选择' : ''" @click="onDelete">
|
||||
<template #icon><icon-delete /></template>
|
||||
<template #default>删除</template>
|
||||
</a-button>
|
||||
<a-button type="primary" :disabled="!selectedKeys.length" :title="!selectedKeys.length ? '请选择' : ''" @click="onRead">
|
||||
<template #default>标记为已读</template>
|
||||
</a-button>
|
||||
<a-button type="primary" :disabled="selectedKeys.length" :title="!selectedKeys.length ? '请选择' : ''" @click="onReadAll">
|
||||
<template #default>全部已读</template>
|
||||
</a-button>
|
||||
</template>
|
||||
<template #title="{ record }">
|
||||
<a-tooltip :content="record.content"><span>{{ record.title }}</span></a-tooltip>
|
||||
</template>
|
||||
<template #isRead="{ record }">
|
||||
<a-tag :color="record.isRead ? '' : 'arcoblue'">
|
||||
{{ record.isRead ? '已读' : '未读' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template #type="{ record }">
|
||||
<GiCellTag :value="record.type" :dict="message_type" />
|
||||
</template>
|
||||
</GiTable>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { TableInstance } from '@arco-design/web-vue'
|
||||
import { Message, Modal } from '@arco-design/web-vue'
|
||||
import { type MessageQuery, deleteMessage, listMessage, readMessage } from '@/apis'
|
||||
import { useTable } from '@/hooks'
|
||||
import { useDict } from '@/hooks/app'
|
||||
|
||||
defineOptions({ name: 'SystemMessage' })
|
||||
|
||||
const { message_type } = useDict('message_type')
|
||||
|
||||
const queryForm = reactive<MessageQuery>({
|
||||
sort: ['createTime,desc'],
|
||||
})
|
||||
|
||||
const {
|
||||
tableData: dataList,
|
||||
loading,
|
||||
pagination,
|
||||
selectedKeys,
|
||||
select,
|
||||
selectAll,
|
||||
search,
|
||||
handleDelete,
|
||||
} = useTable((page) => listMessage({ ...queryForm, ...page }), { immediate: true })
|
||||
|
||||
const columns: TableInstance['collumns'] = [
|
||||
{
|
||||
title: '序号',
|
||||
width: 66,
|
||||
align: 'center',
|
||||
render: ({ rowIndex }) => h('span', {}, rowIndex + 1 + (pagination.current - 1) * pagination.pageSize),
|
||||
},
|
||||
{ title: '标题', dataIndex: 'title', slotName: 'title', minWidth: 100, ellipsis: true, tooltip: true },
|
||||
{ title: '状态', dataIndex: 'isRead', slotName: 'isRead', align: 'center' },
|
||||
{ title: '时间', dataIndex: 'createTime', width: 180 },
|
||||
{ title: '类型', dataIndex: 'type', slotName: 'type', width: 180, ellipsis: true, tooltip: true },
|
||||
]
|
||||
|
||||
// 重置
|
||||
const reset = () => {
|
||||
queryForm.title = undefined
|
||||
queryForm.type = undefined
|
||||
queryForm.isRead = undefined
|
||||
search()
|
||||
}
|
||||
|
||||
// 删除
|
||||
const onDelete = () => {
|
||||
if (!selectedKeys.value.length) {
|
||||
return Message.warning('请选择数据')
|
||||
}
|
||||
return handleDelete(() => deleteMessage(selectedKeys.value), { showModal: false, multiple: true })
|
||||
}
|
||||
|
||||
// 标记为已读
|
||||
const onRead = async () => {
|
||||
if (!selectedKeys.value.length) {
|
||||
return Message.warning('请选择数据')
|
||||
}
|
||||
await readMessage(selectedKeys.value)
|
||||
Message.success('操作成功')
|
||||
search()
|
||||
}
|
||||
|
||||
// 全部已读
|
||||
const onReadAll = async () => {
|
||||
Modal.warning({
|
||||
title: '全部已读',
|
||||
content: '确定要标记全部消息为已读吗?',
|
||||
hideCancel: false,
|
||||
maskClosable: false,
|
||||
onOk: async () => {
|
||||
await readMessage([])
|
||||
Message.success('操作成功')
|
||||
search()
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
95
src/views/user/message/components/MyNotice.vue
Normal file
95
src/views/user/message/components/MyNotice.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<GiTable
|
||||
row-key="id"
|
||||
:data="dataList"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%' }"
|
||||
:pagination="pagination"
|
||||
:disabled-tools="['size', 'setting']"
|
||||
@refresh="search"
|
||||
>
|
||||
<template #toolbar-left>
|
||||
<a-input-search v-model="queryForm.title" placeholder="搜索标题" allow-clear @search="search" />
|
||||
<a-select
|
||||
v-model="queryForm.type"
|
||||
:options="notice_type"
|
||||
placeholder="全部类型"
|
||||
allow-clear
|
||||
style="width: 150px"
|
||||
@change="search"
|
||||
/>
|
||||
<a-button @click="reset">
|
||||
<template #icon><icon-refresh /></template>
|
||||
<template #default>重置</template>
|
||||
</a-button>
|
||||
</template>
|
||||
<template #title="{ record }">
|
||||
<a-link @click="onDetail(record)">
|
||||
<a-typography-paragraph
|
||||
class="link-text"
|
||||
:ellipsis="{
|
||||
rows: 1,
|
||||
showTooltip: true,
|
||||
css: true,
|
||||
}"
|
||||
>
|
||||
{{ record.title }}
|
||||
</a-typography-paragraph>
|
||||
</a-link>
|
||||
</template>
|
||||
<template #type="{ record }">
|
||||
<GiCellTag :value="record.type" :dict="notice_type" />
|
||||
</template>
|
||||
</GiTable>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { TableInstance } from '@arco-design/web-vue'
|
||||
import { type NoticeQuery, type NoticeResp, listUserNotice } from '@/apis/system'
|
||||
import { useTable } from '@/hooks'
|
||||
import { useDict } from '@/hooks/app'
|
||||
|
||||
defineOptions({ name: 'SystemMessage' })
|
||||
|
||||
const { notice_type } = useDict('notice_type')
|
||||
|
||||
const queryForm = reactive<NoticeQuery>({
|
||||
sort: ['createTime,desc'],
|
||||
})
|
||||
|
||||
const {
|
||||
tableData: dataList,
|
||||
loading,
|
||||
pagination,
|
||||
search,
|
||||
} = useTable((page) => listUserNotice({ ...queryForm, ...page }), { immediate: true })
|
||||
|
||||
const columns: TableInstance['columns'] = [
|
||||
{
|
||||
title: '序号',
|
||||
width: 66,
|
||||
align: 'center',
|
||||
render: ({ rowIndex }) => h('span', {}, rowIndex + 1 + (pagination.current - 1) * pagination.pageSize),
|
||||
},
|
||||
{ title: '标题', dataIndex: 'title', slotName: 'title', ellipsis: true, tooltip: true },
|
||||
{ title: '类型', dataIndex: 'type', slotName: 'type', align: 'center' },
|
||||
{ title: '发布人', dataIndex: 'createUserString', ellipsis: true, tooltip: true },
|
||||
{ title: '发布时间', dataIndex: 'createTime', width: 180 },
|
||||
]
|
||||
|
||||
// 重置
|
||||
const reset = () => {
|
||||
queryForm.title = undefined
|
||||
queryForm.type = undefined
|
||||
search()
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
// 详情
|
||||
const onDetail = (record: NoticeResp) => {
|
||||
router.push({ path: '/user/notice', query: { id: record.id } })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
123
src/views/user/message/components/detail/components/index.vue
Normal file
123
src/views/user/message/components/detail/components/index.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<!-- 未完善 -->
|
||||
<template>
|
||||
<div ref="divRef" class="container">
|
||||
<div class="aie-container">
|
||||
<div class="aie-header-panel" style="display: none;">
|
||||
<div class="aie-container-header"></div>
|
||||
</div>
|
||||
<div class="aie-main">
|
||||
<div class="aie-container-panel">
|
||||
<div class="aie-container-main"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aie-container-footer" style="display: none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { AiEditor, type AiEditorOptions } from 'aieditor'
|
||||
import 'aieditor/dist/style.css'
|
||||
import { useAppStore } from '@/stores'
|
||||
|
||||
defineOptions({ name: 'AiEditor' })
|
||||
const props = defineProps<{
|
||||
modelValue: string
|
||||
options?: AiEditorOptions
|
||||
}>()
|
||||
const aieditor = ref<AiEditor | null>(null)
|
||||
const appStore = useAppStore()
|
||||
const divRef = ref<any>()
|
||||
|
||||
const editorConfig = reactive<AiEditorOptions>({
|
||||
element: '',
|
||||
theme: appStore.theme,
|
||||
placeholder: '请输入内容',
|
||||
content: '',
|
||||
editable: false,
|
||||
})
|
||||
const init = () => {
|
||||
aieditor.value?.destroy()
|
||||
aieditor.value = new AiEditor(editorConfig)
|
||||
}
|
||||
watch(() => props.modelValue, (value) => {
|
||||
if (value !== aieditor.value?.getHtml()) {
|
||||
editorConfig.content = value
|
||||
init()
|
||||
}
|
||||
})
|
||||
watch(() => appStore.theme, (value) => {
|
||||
editorConfig.theme = value
|
||||
init()
|
||||
})
|
||||
|
||||
// 挂载阶段
|
||||
onMounted(() => {
|
||||
editorConfig.element = divRef.value
|
||||
init()
|
||||
})
|
||||
// 销毁阶段
|
||||
onUnmounted(() => {
|
||||
aieditor.value?.destroy()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.aie-header-panel {
|
||||
position: sticky;
|
||||
// top: 51px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.aie-header-panel aie-header>div {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.aie-container {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.aie-container-panel {
|
||||
width: calc(100% - 2rem - 2px);
|
||||
max-width: 826.77px;
|
||||
margin: 0rem auto;
|
||||
border: 1px solid var(--color-border-1);
|
||||
background-color: var() rgba($color: var(--color-bg-1), $alpha: 1.0);
|
||||
height: 100%;
|
||||
padding: 1rem;
|
||||
z-index: 99;
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.aie-main {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
box-sizing: border-box;
|
||||
padding: 1rem 0px;
|
||||
background-color: var(--color-bg-1);
|
||||
}
|
||||
|
||||
.aie-directory {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
left: 10px;
|
||||
width: 260px;
|
||||
z-index: 0;
|
||||
|
||||
}
|
||||
|
||||
.aie-title1 {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
94
src/views/user/message/components/detail/index.vue
Normal file
94
src/views/user/message/components/detail/index.vue
Normal file
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<div ref="containerRef" class="detail">
|
||||
<div class="detail_header">
|
||||
<a-affix :target="(containerRef as HTMLElement)">
|
||||
<a-page-header title="通知公告" subtitle="查看" @back="onBack">
|
||||
</a-page-header>
|
||||
</a-affix>
|
||||
</div>
|
||||
<div class="detail_content">
|
||||
<h1 class="title">{{ form?.title }}</h1>
|
||||
<div class="info">
|
||||
<a-space>
|
||||
<span>
|
||||
<icon-user class="icon" />
|
||||
<span class="label">发布人:</span>
|
||||
<span>{{ form?.createUserString }}</span>
|
||||
</span>
|
||||
<a-divider direction="vertical" />
|
||||
<span>
|
||||
<icon-history class="icon" />
|
||||
<span class="label">发布时间:</span>
|
||||
<span>{{ form?.effectiveTime ? form?.effectiveTime : form?.createTime
|
||||
}}</span>
|
||||
</span>
|
||||
<a-divider v-if="form?.updateTime" direction="vertical" />
|
||||
<span v-if="form?.updateTime">
|
||||
<icon-schedule class="icon" />
|
||||
<span>更新时间:</span>
|
||||
<span>{{ form?.updateTime }}</span>
|
||||
</span>
|
||||
</a-space>
|
||||
</div>
|
||||
<div style="flex: 1;">
|
||||
<AiEditor v-model="form.content" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import AiEditor from './components/index.vue'
|
||||
import { getUserNotice } from '@/apis/system/user-message'
|
||||
import { useTabsStore } from '@/stores'
|
||||
import { useResetReactive } from '@/hooks'
|
||||
|
||||
defineOptions({ name: 'UserNotice' })
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const tabsStore = useTabsStore()
|
||||
|
||||
const { id } = route.query
|
||||
const containerRef = ref<HTMLElement | null>()
|
||||
const [form, resetForm] = useResetReactive({
|
||||
title: '',
|
||||
createUserString: '',
|
||||
effectiveTime: '',
|
||||
createTime: '',
|
||||
content: '',
|
||||
})
|
||||
|
||||
// 回退
|
||||
const onBack = () => {
|
||||
router.back()
|
||||
tabsStore.closeCurrent(route.path)
|
||||
}
|
||||
|
||||
// 打开
|
||||
const onOpen = async (id: string) => {
|
||||
resetForm()
|
||||
const { data } = await getUserNotice(id)
|
||||
Object.assign(form, data)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onOpen(id as string)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.detail_content {
|
||||
.title {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.info {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
83
src/views/user/message/index.vue
Normal file
83
src/views/user/message/index.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<GiPageLayout :margin="true" :default-collapsed="false">
|
||||
<template v-if="isDesktop" #left>
|
||||
<a-tabs v-model:active-key="activeKey" position="left" hide-content @change="change">
|
||||
<a-tab-pane v-for="(item) in menuList" :key="item.key" :title="item.name"></a-tab-pane>
|
||||
</a-tabs>
|
||||
</template>
|
||||
<a-tabs v-if="!isDesktop" v-model:active-key="activeKey" type="card-gutter" style="margin-bottom: 10px" position="top" hide-content @change="change">
|
||||
<a-tab-pane v-for="(item) in menuList" :key="item.key" :title="item.name"></a-tab-pane>
|
||||
</a-tabs>
|
||||
<transition name="fade-slide" mode="out-in" appear>
|
||||
<component :is="menuList.find((item) => item.key === activeKey)?.value"></component>
|
||||
</transition>
|
||||
</GiPageLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import MyMessage from './components/MyMessage.vue'
|
||||
import MyNotice from './components/MyNotice.vue'
|
||||
import { useDevice } from '@/hooks'
|
||||
|
||||
defineOptions({ name: 'UserMessage' })
|
||||
|
||||
const { isDesktop } = useDevice()
|
||||
|
||||
const menuList = [
|
||||
{ name: '我的消息', key: 'msg', value: MyMessage },
|
||||
{ name: '我的公告', key: 'notice', value: MyNotice },
|
||||
]
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const activeKey = ref('msg')
|
||||
watch(
|
||||
() => route.query,
|
||||
() => {
|
||||
if (route.query.tab) {
|
||||
activeKey.value = String(route.query.tab)
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
const change = (key: string | number) => {
|
||||
activeKey.value = key as string
|
||||
router.replace({ path: route.path, query: { tab: key } })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.arco-tabs-nav-vertical.arco-tabs-nav-type-line .arco-tabs-tab) {
|
||||
margin: 0;
|
||||
padding: 8px 16px;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-fill-1);
|
||||
|
||||
.arco-tabs-tab-title {
|
||||
&::before {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.arco-tabs-tab-active {
|
||||
background: rgba(var(--primary-6), 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.arco-tabs-nav-vertical::before) {
|
||||
left: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(.arco-tabs-nav-vertical .arco-tabs-nav-ink) {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
:deep(.arco-tabs-nav-vertical) {
|
||||
float: none;
|
||||
flex-direction: row;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user