page: 📄 消息详情和部分缺陷修复

This commit is contained in:
bunny 2024-11-01 16:42:06 +08:00
parent a9e06b151a
commit a6eaa41226
31 changed files with 712 additions and 256 deletions

View File

@ -6,6 +6,21 @@ export const fetchGetMessageList = (data: any) => {
return http.request<BaseResult<ResultTable>>('get', `message/getMessageList/${data.currentPage}/${data.pageSize}`, { params: data }); return http.request<BaseResult<ResultTable>>('get', `message/getMessageList/${data.currentPage}/${data.pageSize}`, { params: data });
}; };
/** 系统消息---获取系统消息列表 */
export const fetchGetUserMessageList = (data: any) => {
return http.request<BaseResult<ResultTable>>('get', `message/noManage/getUserMessageList/${data.currentPage}/${data.pageSize}`, { params: data });
};
/** 系统消息---根据消息id获取接收人信息 */
export const fetchGetReceivedUserinfoByMessageId = (data: any) => {
return http.request<BaseResult<any>>('get', `messageReceived/noManage/getReceivedUserinfoByMessageId`, { params: data });
};
/** 系统消息---根据消息id查询消息详情 */
export const fetchGetMessageDetailById = (data: any) => {
return http.request<BaseResult<any>>('get', `message/noManage/getMessageDetailById`, { params: data });
};
/** 系统消息---添加系统消息 */ /** 系统消息---添加系统消息 */
export const fetchAddMessage = (data: any) => { export const fetchAddMessage = (data: any) => {
return http.request<BaseResult<object>>('post', 'message/addMessage', { data }); return http.request<BaseResult<object>>('post', 'message/addMessage', { data });

View File

@ -1,4 +1,5 @@
import { computed } from 'vue'; import { computed } from 'vue';
import { $t } from '@/plugins/i18n';
/** /**
* * * *
@ -32,6 +33,12 @@ export const enabledOrNotStatus = [
{ value: false, label: '禁用' }, { value: false, label: '禁用' },
]; ];
/** 是否已读 */
export const isReadStatus = [
{ value: true, label: $t('readAlready') },
{ value: false, label: $t('unread') },
];
/** /**
* * * *
*/ */

View File

@ -3,6 +3,7 @@ import { ListItem } from '../data';
import { nextTick, PropType, ref } from 'vue'; import { nextTick, PropType, ref } from 'vue';
import { useNav } from '@/layout/hooks/useNav'; import { useNav } from '@/layout/hooks/useNav';
import { deviceDetection } from '@pureadmin/utils'; import { deviceDetection } from '@pureadmin/utils';
import { useRouter } from 'vue-router';
defineProps({ defineProps({
noticeItem: { noticeItem: {
@ -17,6 +18,7 @@ const descriptionRef = ref(null);
const descriptionTooltip = ref(false); const descriptionTooltip = ref(false);
const { tooltipEffect } = useNav(); const { tooltipEffect } = useNav();
const isMobile = deviceDetection(); const isMobile = deviceDetection();
const router = useRouter();
function hoverTitle() { function hoverTitle() {
nextTick(() => { nextTick(() => {
@ -39,10 +41,14 @@ function hoverDescription(event, description) {
// //
currentWidth > 2 * cellWidth ? (descriptionTooltip.value = true) : (descriptionTooltip.value = false); currentWidth > 2 * cellWidth ? (descriptionTooltip.value = true) : (descriptionTooltip.value = false);
} }
function goMessageDetail(message: ListItem) {
router.push({ path: `/message-detail/${message.messageType}/${message.messageId}` });
}
</script> </script>
<template> <template>
<div class="notice-container border-b-[1px] border-solid border-[#f0f0f0] dark:border-[#303030]"> <div class="notice-container border-b-[1px] border-solid border-[#f0f0f0] dark:border-[#303030]" @click="goMessageDetail(noticeItem)">
<el-avatar v-if="noticeItem.cover" :size="30" :src="noticeItem.cover" class="notice-container-avatar" /> <el-avatar v-if="noticeItem.cover" :size="30" :src="noticeItem.cover" class="notice-container-avatar" />
<div class="notice-container-text"> <div class="notice-container-text">
<div class="notice-text-title text-[#000000d9] dark:text-white"> <div class="notice-text-title text-[#000000d9] dark:text-white">

View File

@ -1,13 +1,16 @@
import { $t } from '@/plugins/i18n'; import { $t } from '@/plugins/i18n';
import { ref } from 'vue'; import { computed, ref } from 'vue';
import { fetchGetMessageList } from '@/api/v1/message'; import { fetchGetUserMessageList } from '@/api/v1/message';
import { throttle } from '@pureadmin/utils';
export interface ListItem { export interface ListItem {
messageId: string;
cover: string; cover: string;
title: string; title: string;
datetime: string; datetime: string;
type: string; type: string;
description: string; description: string;
messageType: string;
status?: 'primary' | 'success' | 'warning' | 'info' | 'danger'; status?: 'primary' | 'success' | 'warning' | 'info' | 'danger';
extra?: string; extra?: string;
} }
@ -26,19 +29,21 @@ export const noticesData = ref<TabItem[]>([]);
/** 获取所有消息 */ /** 获取所有消息 */
export const getAllMessageList = async () => { export const getAllMessageList = async () => {
const baseResult = await fetchGetMessageList(form); const baseResult = await fetchGetUserMessageList(form);
const datalist = baseResult.data.list; const datalist = baseResult?.data?.list;
// 通知消息 // 通知消息
const notifications = datalist const notifications = datalist
.filter(message => message.messageType === 'notifications') .filter(message => message.messageType === 'notifications')
.map(message => ({ .map(message => ({
messageId: message.id,
cover: message.cover, cover: message.cover,
title: message.title, title: message.title,
datetime: message.createTime, datetime: message.createTime,
description: message.summary, description: message.summary,
messageType: message.messageType,
type: '1', type: '1',
status: message.statusType, status: message.level,
extra: message.extra, extra: message.extra,
})) as ListItem[]; })) as ListItem[];
@ -46,12 +51,14 @@ export const getAllMessageList = async () => {
const notify = datalist const notify = datalist
.filter(message => message.messageType !== 'notifications' && message.messageType !== 'sys') .filter(message => message.messageType !== 'notifications' && message.messageType !== 'sys')
.map(message => ({ .map(message => ({
messageId: message.id,
cover: message.cover, cover: message.cover,
description: message.summary, description: message.summary,
messageType: message.messageType,
title: message.title, title: message.title,
datetime: message.createTime, datetime: message.createTime,
type: '2', type: '2',
status: message.statusType, status: message.level,
extra: message.extra, extra: message.extra,
})) as ListItem[]; })) as ListItem[];
@ -59,12 +66,14 @@ export const getAllMessageList = async () => {
const system = datalist const system = datalist
.filter(message => message.messageType === 'sys') .filter(message => message.messageType === 'sys')
.map(message => ({ .map(message => ({
messageId: message.id,
cover: message.cover, cover: message.cover,
description: message.summary, description: message.summary,
messageType: message.messageType,
title: message.title, title: message.title,
datetime: message.createTime, datetime: message.createTime,
type: '3', type: '3',
status: message.statusType, status: message.level,
extra: message.extra, extra: message.extra,
})) as ListItem[]; })) as ListItem[];
@ -74,3 +83,23 @@ export const getAllMessageList = async () => {
{ key: '3', name: $t('status.systemMessage'), list: system, emptyText: $t('status.systemMessage') }, { key: '3', name: $t('status.systemMessage'), list: system, emptyText: $t('status.systemMessage') },
]; ];
}; };
// 通知消息数据
export const noticesNum = ref(0);
export const notices = ref(noticesData);
// 选择的消息栏目
export const activeKey = ref(noticesData.value[0]?.key);
export const getLabel = computed(() => item => item.name + (item.list.length > 0 ? `(${item.list.length})` : ''));
/** 计算消息数量 */
export const computedNoticesNum = throttle(async () => {
// 获取所有的消息
await getAllMessageList();
// 请求成功后将原本条数置为0
noticesNum.value = 0;
// 整合消息一共多少条
notices.value.map(v => (noticesNum.value += v.list.length));
// 默认选中的消息类别
activeKey.value = noticesData.value[0]?.key;
// 定时刷新
}, 666);

View File

@ -1,42 +1,20 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { computed, onMounted, ref } from 'vue'; import { onMounted } from 'vue';
import { getAllMessageList, noticesData } from './data'; import { activeKey, computedNoticesNum, getLabel, notices, noticesNum } from './data';
import NoticeList from './components/NoticeList.vue'; import NoticeList from './components/NoticeList.vue';
import BellIcon from '@iconify-icons/ep/bell'; import BellIcon from '@iconify-icons/ep/bell';
import { useIntervalFn } from '@vueuse/core';
const { t } = useI18n(); const { t } = useI18n();
//
const noticesNum = ref(0);
const notices = ref(noticesData);
//
const activeKey = ref(noticesData.value[0]?.key);
const getLabel = computed(() => item => item.name + (item.list.length > 0 ? `(${item.list.length})` : ''));
/** 计算消息数量 */
const computedNoticesNum = async () => {
//
await getAllMessageList();
// 0
noticesNum.value = 0;
//
notices.value.map(v => (noticesNum.value += v.list.length));
//
activeKey.value = noticesData.value[0]?.key;
//
};
onMounted(() => { onMounted(() => {
computedNoticesNum(); computedNoticesNum();
//
useIntervalFn(() => computedNoticesNum(), 1000 * 30);
}); });
</script> </script>
<template> <template>
<el-dropdown placement="bottom-end" trigger="click"> <el-dropdown placement="bottom-end" trigger="click">
<span :class="['dropdown-badge', 'navbar-bg-hover', 'select-none', Number(noticesNum) !== 0 && 'mr-[10px]']"> <span :class="['dropdown-badge', 'navbar-bg-hover', 'select-none', Number(noticesNum) !== 0 && 'mr-[10px]']" @click="computedNoticesNum">
<el-badge :max="99" :value="Number(noticesNum) === 0 ? '' : noticesNum"> <el-badge :max="99" :value="Number(noticesNum) === 0 ? '' : noticesNum">
<span class="header-notice-icon"> <span class="header-notice-icon">
<IconifyIconOffline :icon="BellIcon" /> <IconifyIconOffline :icon="BellIcon" />

View File

@ -1,25 +1,25 @@
const { VITE_HIDE_HOME } = import.meta.env; const { VITE_HIDE_HOME } = import.meta.env;
const Layout = () => import("@/layout/index.vue"); const Layout = () => import('@/layout/index.vue');
export default { export default {
path: "/", path: '/',
name: "Home", name: 'Home',
component: Layout, component: Layout,
redirect: "/welcome", redirect: '/welcome',
meta: { meta: {
icon: "ep:home-filled", icon: 'ep:home-filled',
title: "menus.home", title: 'menus.home',
rank: 0 rank: 0,
}, },
children: [ children: [
{ {
path: "/welcome", path: '/welcome',
name: "Welcome", name: 'Welcome',
component: () => import("@/views/welcome/index.vue"), component: () => import('@/views/welcome/index.vue'),
meta: { meta: {
title: "menus.home", title: 'menus.home',
showLink: VITE_HIDE_HOME !== "true" showLink: VITE_HIDE_HOME !== 'true',
} },
} },
] ],
} satisfies RouteConfigsTable; } satisfies RouteConfigsTable;

View File

@ -34,7 +34,27 @@ export default [
meta: { meta: {
title: 'buttons.accountSettings', title: 'buttons.accountSettings',
showLink: false, showLink: false,
rank: 104, rank: 103,
}, },
}, },
{
path: '/message-detail',
name: 'MessageDetail',
component: () => import('@/views/message-management/message-detail/index.vue'),
meta: { title: 'message', showLink: false, rank: 104 },
children: [
{
path: '/message-detail/:messageType',
name: 'MessageDetailByMessageType',
component: () => import('@/views/message-management/message-detail/detail-type.vue'),
meta: { title: 'message', showLink: false },
},
{
path: '/message-detail/:messageType/:messageId',
name: 'MessageDetailByMessageId',
component: () => import('@/views/message-management/message-detail/detail-message-id.vue'),
meta: { title: 'message', showLink: false },
},
],
},
] satisfies Array<RouteConfigsTable>; ] satisfies Array<RouteConfigsTable>;

View File

@ -28,6 +28,10 @@ export const useMessageStore = defineStore('messageStore', {
content: undefined, content: undefined,
// 编辑器类型 // 编辑器类型
editorType: undefined, editorType: undefined,
// 消息等级
level: undefined,
// 消息等级详情
extra: undefined,
// 0:未读 1:已读 // 0:未读 1:已读
status: undefined, status: undefined,
}, },

View File

@ -0,0 +1,53 @@
<script lang="ts" setup>
import { useRoute } from 'vue-router';
import { onMounted, ref } from 'vue';
import 'plus-pro-components/es/components/check-card-group/style/css';
import MarkdownPreview from '@/components/Editor/MarkdownPreview.vue';
import { fetchGetMessageDetailById } from '@/api/v1/message';
import { decode } from 'js-base64';
const route = useRoute();
const messageDetail = ref<any>({});
/** 获取消息详情 */
const getMessageDetail = async () => {
//
const messageId = route.params.messageId;
//
const result = await fetchGetMessageDetailById({ id: messageId });
if (result.code !== 200) return;
//
messageDetail.value = result.data;
//
messageDetail.value.content = decode(messageDetail.value.content);
};
onMounted(() => {
getMessageDetail();
});
</script>
<template>
<div class="flex flex-col">
<header class="flex items-center h-[80px]">
<el-avatar :src="messageDetail.cover" size="large" />
<div class="content ms-3">
<h3>{{ messageDetail.title }}</h3>
<p>{{ messageDetail.summary }}</p>
</div>
</header>
<main>
<span>
<el-text>{{ messageDetail.updateTime }}</el-text>
<el-text type="primary">&nbsp;&nbsp; By{{ messageDetail.sendNickname }}</el-text>
</span>
<markdown-preview v-if="messageDetail.editorType === 'markdown'" id="message-detail-markdown" :text="messageDetail.content" />
<div v-else v-html="messageDetail.content" />
</main>
</div>
</template>

View File

@ -0,0 +1,37 @@
<script lang="ts" setup>
import { useRoute } from 'vue-router';
import { onMounted, ref } from 'vue';
import { PlusCheckCardGroup } from 'plus-pro-components';
import 'plus-pro-components/es/components/check-card-group/style/css';
const route = useRoute();
/** 获取消息详情 */
const getMessageDetail = () => {
const messageId = route.params.messageId;
};
const options = [
{
title: '标题一',
value: '0',
description: '坚持梦想,成就不凡的自己',
avatar: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg',
},
{
title: '标题二',
value: '1',
description: '每一次努力,都是成长的契机',
avatar: 'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
},
];
const size = ref('default');
const dynamicSize = ref();
const list = ref('0');
onMounted(() => {
getMessageDetail();
});
</script>
<template>
<PlusCheckCardGroup v-model="list" :disabled="size === 'disabled'" :options="options" :size="dynamicSize" />
</template>

View File

@ -0,0 +1,112 @@
<script lang="ts" setup>
import { useRoute, useRouter } from 'vue-router';
import { onBeforeMount, ref } from 'vue';
import { deviceDetection, useGlobal } from '@pureadmin/utils';
import { useDataThemeChange } from '@/layout/hooks/useDataThemeChange';
import leftLine from '@iconify-icons/ri/arrow-left-s-line';
import { $t } from '@/plugins/i18n';
import { useMessageTypeStore } from '@/store/message/messageType';
const router = useRouter();
const isOpen = ref(!deviceDetection());
const { $storage } = useGlobal<GlobalPropertiesApi>();
const witchPane = ref('profile');
const messageTypeStore = useMessageTypeStore();
const route = useRoute();
const routerMessageType = ref('');
/** 点击菜单时 */
const onMenuClick = (item: any) => {
const messageType = item.messageType;
const messageId = item.messageId;
routerMessageType.value = messageType;
router.push({ path: `/message-detail/${item.messageType}` });
};
onBeforeMount(() => {
messageTypeStore.getAllMessageTypeList();
routerMessageType.value = route.params.messageType as string;
useDataThemeChange().dataThemeChange($storage.layout?.overallStyle);
});
</script>
<template>
<el-container class="h-full">
<el-aside
v-if="isOpen"
:width="deviceDetection() ? '180px' : '240px'"
class="pure-account-settings overflow-hidden px-2 dark:!bg-[var(--el-bg-color)] border-r-[1px] border-[var(--pure-border-color)]"
>
<el-menu :default-active="witchPane" class="pure-account-settings-menu">
<!--<el-menu-item class="hover:!transition-all hover:!duration-200 hover:!text-base !h-[50px]" @click="router.go(-1)">-->
<el-menu-item class="hover:!transition-all hover:!duration-200 hover:!text-base !h-[50px]" @click="router.push('/')">
<div class="flex items-center">
<IconifyIconOffline :icon="leftLine" />
<span class="ml-2">{{ $t('back') }}</span>
</div>
</el-menu-item>
<el-menu-item v-for="item in messageTypeStore.allMessageTypeList" :key="item.id" :class="routerMessageType === item.messageType ? 'is-active' : ''" :index="item.id" @click="onMenuClick(item)">
<div class="flex items-center z-10">
<span>{{ item.messageName }}</span>
</div>
</el-menu-item>
</el-menu>
</el-aside>
<el-main>
<RouterView />
</el-main>
</el-container>
</template>
<style lang="scss">
.pure-account-settings {
background: $menuBg;
}
.pure-account-settings-menu {
background-color: transparent;
border: none;
.el-menu-item {
height: 48px !important;
color: $menuText;
background-color: transparent !important;
transition: color 0.2s;
&:hover {
color: $menuTitleHover !important;
}
&.is-active {
color: #fff !important;
&:hover {
color: #fff !important;
}
&::before {
position: absolute;
inset: 0 8px;
margin: 4px 0;
clear: both;
content: '';
background: var(--el-color-primary);
border-radius: 3px;
}
}
}
}
</style>
<style lang="scss" scoped>
body[layout] {
.el-menu--vertical .is-active {
color: #fff !important;
transition: color 0.2s;
&:hover {
color: #fff !important;
}
}
}
</style>

View File

@ -0,0 +1,32 @@
<script lang="ts" setup>
import SplitPane from '@/components/SplitPane';
import MessageEditorFrom from '@/views/message-management/message-editing/message-editor-from.vue';
import RichEditor from '@/views/message-management/message-editing/rich-editor.vue';
import { formState } from '@/views/message-management/message-editing/utils/hooks';
import MarkdownEditor from '@/views/message-management/message-editing/markdown-editor.vue';
import { settingLR } from '@/views/message-management/message-editing/utils/columns';
</script>
<template>
<el-card shadow="never">
<div class="split-pane">
<SplitPane :splitSet="settingLR">
<template #paneL>
<rich-editor v-if="formState.editorType === 'rich'" />
<markdown-editor v-else />
</template>
<template #paneR>
<message-editor-from />
</template>
</SplitPane>
</div>
</el-card>
</template>
<style lang="scss" scoped>
.split-pane {
width: 100%;
height: calc(100vh - 120px);
}
</style>

View File

@ -2,7 +2,7 @@
import { MdEditor } from 'md-editor-v3'; import { MdEditor } from 'md-editor-v3';
import 'md-editor-v3/lib/style.css'; import 'md-editor-v3/lib/style.css';
import { fetchUploadFile } from '@/api/v1/system'; import { fetchUploadFile } from '@/api/v1/system';
import { formState } from '@/views/messageManagement/messageEditing/utils/hooks'; import { formState } from '@/views/message-management/message-editing/utils/hooks';
/** /**
* * 上传图片 * * 上传图片

View File

@ -1,10 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import { beforeUpload, coverUrl, formState, loading, onSearchUserinfo, onUpload, userDataList } from '@/views/messageManagement/messageEditing/utils/hooks'; import { beforeUpload, coverUrl, formState, loading, onSearchUserinfo, onUpload, userDataList } from '@/views/message-management/message-editing/utils/hooks';
import { onMounted, ref, toRaw } from 'vue'; import { onMounted, ref, toRaw } from 'vue';
import { $t } from '@/plugins/i18n'; import { $t } from '@/plugins/i18n';
import { rules } from '@/views/messageManagement/messageEditing/utils/columns'; import { messageLevel, rules } from '@/views/message-management/message-editing/utils/columns';
import { FormInstance } from 'element-plus'; import { FormInstance } from 'element-plus';
import { editorTypeList } from '@/views/messageManagement/message/utils/columns'; import { editorTypeList } from '@/views/message-management/message/utils/columns';
import LoadingSvg from '@/assets/svg/loading.svg'; import LoadingSvg from '@/assets/svg/loading.svg';
import { useMessageTypeStore } from '@/store/message/messageType'; import { useMessageTypeStore } from '@/store/message/messageType';
import { encode } from 'js-base64'; import { encode } from 'js-base64';
@ -57,6 +57,7 @@ onMounted(() => {
messageTypeStore.getAllMessageTypeList(); messageTypeStore.getAllMessageTypeList();
}); });
</script> </script>
<template> <template>
<el-form ref="formRef" :model="formState" :rules="rules" class="w-[100%] p-5" label-width="auto"> <el-form ref="formRef" :model="formState" :rules="rules" class="w-[100%] p-5" label-width="auto">
<!-- 标题 --> <!-- 标题 -->
@ -117,6 +118,18 @@ onMounted(() => {
<el-input v-model="formState.summary" :autosize="{ minRows: 3, maxRows: 6 }" maxlength="200" minlength="10" show-word-limit type="textarea" /> <el-input v-model="formState.summary" :autosize="{ minRows: 3, maxRows: 6 }" maxlength="200" minlength="10" show-word-limit type="textarea" />
</el-form-item> </el-form-item>
<!-- 消息等级 -->
<el-form-item :label="$t('level')" prop="level">
<el-select v-model="formState.level" :placeholder="$t('level')" clearable filterable remote remote-show-suffix>
<el-option v-for="item in messageLevel" :key="item" :label="$t(item)" :value="item" />
</el-select>
</el-form-item>
<!-- 消息等级简介 -->
<el-form-item :label="$t('extra')" prop="extra">
<el-input v-model="formState.extra" maxlength="20" minlength="10" show-word-limit type="text" />
</el-form-item>
<!-- 提交 --> <!-- 提交 -->
<el-form-item> <el-form-item>
<el-button type="primary" @click="submitForm(formRef)">{{ $t('submit') }}</el-button> <el-button type="primary" @click="submitForm(formRef)">{{ $t('submit') }}</el-button>

View File

@ -2,7 +2,7 @@
import { onBeforeUnmount, ref, shallowRef } from 'vue'; import { onBeforeUnmount, ref, shallowRef } from 'vue';
import '@wangeditor/editor/dist/css/style.css'; import '@wangeditor/editor/dist/css/style.css';
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'; import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
import { formState } from '@/views/messageManagement/messageEditing/utils/hooks'; import { formState } from '@/views/message-management/message-editing/utils/hooks';
import { getToken } from '@/utils/auth'; import { getToken } from '@/utils/auth';
const mode = 'default'; const mode = 'default';

View File

@ -1,4 +1,6 @@
import { $t } from '@/plugins/i18n'; import { $t } from '@/plugins/i18n';
import type { ContextProps } from '@/components/SplitPane';
import { reactive } from 'vue';
export const rules = { export const rules = {
// 消息标题 // 消息标题
@ -12,3 +14,11 @@ export const rules = {
// 消息简介 // 消息简介
summary: [{ required: true, message: `${$t('input')}${$t('summary')}`, trigger: 'blur' }], summary: [{ required: true, message: `${$t('input')}${$t('summary')}`, trigger: 'blur' }],
}; };
export const messageLevel = ['primary', 'success', 'warning', 'info', 'danger'];
export const settingLR: ContextProps = reactive({
minPercent: 20,
defaultPercent: 80,
split: 'vertical',
});

View File

@ -20,6 +20,8 @@ export const formState = reactive({
editorType: 'markdown', editorType: 'markdown',
status: false, status: false,
summary: '', summary: '',
level: '',
extra: '',
cover: '', cover: '',
}); });
const adminUserStore = useAdminUserStore(); const adminUserStore = useAdminUserStore();

View File

@ -1,10 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { columns } from '@/views/messageManagement/messageType/utils/columns'; import { columns } from '@/views/message-management/message-type/utils/columns';
import PureTableBar from '@/components/TableBar/src/bar'; import PureTableBar from '@/components/TableBar/src/bar';
import AddFill from '@iconify-icons/ri/add-circle-line'; import AddFill from '@iconify-icons/ri/add-circle-line';
import PureTable from '@pureadmin/table'; import PureTable from '@pureadmin/table';
import { deleteIds, onAdd, onDelete, onDeleteBatch, onSearch, onUpdate } from '@/views/messageManagement/messageType/utils/hooks'; import { deleteIds, onAdd, onDelete, onDeleteBatch, onSearch, onUpdate } from '@/views/message-management/message-type/utils/hooks';
import Delete from '@iconify-icons/ep/delete'; import Delete from '@iconify-icons/ep/delete';
import EditPen from '@iconify-icons/ep/edit-pen'; import EditPen from '@iconify-icons/ep/edit-pen';
import Refresh from '@iconify-icons/ep/refresh'; import Refresh from '@iconify-icons/ep/refresh';

View File

@ -1,8 +1,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { FormInstance } from 'element-plus'; import { FormInstance } from 'element-plus';
import { rules } from '@/views/messageManagement/messageType/utils/columns'; import { rules } from '@/views/message-management/message-type/utils/columns';
import { FormProps } from '@/views/messageManagement/messageType/utils/types'; import { FormProps } from '@/views/message-management/message-type/utils/types';
import { $t } from '@/plugins/i18n'; import { $t } from '@/plugins/i18n';
import { usePublicHooks } from '@/views/hooks'; import { usePublicHooks } from '@/views/hooks';

View File

@ -1,9 +1,9 @@
import { addDialog } from '@/components/BaseDialog/index'; import { addDialog } from '@/components/BaseDialog/index';
import MessageTypeDialog from '@/views/messageManagement/messageType/message-type-dialog.vue'; import MessageTypeDialog from '@/views/message-management/message-type/message-type-dialog.vue';
import { useMessageTypeStore } from '@/store/message/messageType'; import { useMessageTypeStore } from '@/store/message/messageType';
import { h, ref } from 'vue'; import { h, ref } from 'vue';
import { message, messageBox } from '@/utils/message'; import { message, messageBox } from '@/utils/message';
import type { FormItemProps } from '@/views/messageManagement/messageType/utils/types'; import type { FormItemProps } from '@/views/message-management/message-type/utils/types';
import { $t } from '@/plugins/i18n'; import { $t } from '@/plugins/i18n';
import DeleteBatchDialog from '@/components/Table/DeleteBatchDialog.vue'; import DeleteBatchDialog from '@/components/Table/DeleteBatchDialog.vue';

View File

@ -1,9 +1,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { columns, editorTypeList } from '@/views/messageManagement/message/utils/columns'; import { columns } from '@/views/message-management/message/utils/columns';
import PureTableBar from '@/components/TableBar/src/bar'; import PureTableBar from '@/components/TableBar/src/bar';
import PureTable from '@pureadmin/table'; import PureTable from '@pureadmin/table';
import { deleteIds, onDelete, onDeleteBatch, onSearch, onUpdate } from '@/views/messageManagement/message/utils/hooks'; import { deleteIds, onDelete, onDeleteBatch, onSearch, onUpdate } from '@/views/message-management/message/utils/hooks';
import Delete from '@iconify-icons/ep/delete'; import Delete from '@iconify-icons/ep/delete';
import EditPen from '@iconify-icons/ep/edit-pen'; import EditPen from '@iconify-icons/ep/edit-pen';
import Refresh from '@iconify-icons/ep/refresh'; import Refresh from '@iconify-icons/ep/refresh';
@ -12,6 +12,8 @@ import { $t } from '@/plugins/i18n';
import { useMessageStore } from '@/store/message/message'; import { useMessageStore } from '@/store/message/message';
import { useRenderIcon } from '@/components/CommonIcon/src/hooks'; import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
import { FormInstance } from 'element-plus'; import { FormInstance } from 'element-plus';
import { messageLevel } from '@/views/message-management/message-editing/utils/columns';
import { isReadStatus } from '@/enums/baseConstant';
const tableRef = ref(); const tableRef = ref();
const formRef = ref(); const formRef = ref();
@ -63,31 +65,28 @@ onMounted(() => {
<el-input v-model="messageStore.form.title" :placeholder="`${$t('input')}${$t('title')}`" class="!w-[180px]" clearable /> <el-input v-model="messageStore.form.title" :placeholder="`${$t('input')}${$t('title')}`" class="!w-[180px]" clearable />
</el-form-item> </el-form-item>
<!-- 发送人昵称 -->
<el-form-item :label="$t('sendNickname')" prop="sendNickname">
<el-input v-model="messageStore.form.sendNickname" :placeholder="`${$t('input')}${$t('sendNickname')}`" class="!w-[180px]" clearable />
</el-form-item>
<!-- 消息类型 --> <!-- 消息类型 -->
<el-form-item :label="$t('messageType')" prop="messageType"> <el-form-item :label="$t('messageType')" prop="messageType">
<el-input v-model="messageStore.form.messageType" :placeholder="`${$t('input')}${$t('messageType')}`" class="!w-[180px]" clearable /> <el-input v-model="messageStore.form.messageType" :placeholder="`${$t('input')}${$t('messageType')}`" class="!w-[180px]" clearable />
</el-form-item> </el-form-item>
<!-- 消息内容 --> <!-- 消息等级 -->
<el-form-item :label="$t('content')" prop="content"> <el-form-item :label="$t('level')" prop="level">
<el-input v-model="messageStore.form.content" :placeholder="`${$t('input')}${$t('content')}`" class="!w-[180px]" clearable /> <el-select v-model="messageStore.form.level" :placeholder="$t('level')" class="!w-[180px]" clearable filterable remote remote-show-suffix>
<el-option v-for="item in messageLevel" :key="item" :label="$t(item)" :value="item" />
</el-select>
</el-form-item> </el-form-item>
<!-- 编辑器类型 --> <!-- 消息等级简介 -->
<el-form-item :label="$t('editorType')" prop="editorType"> <el-form-item :label="$t('extra')" prop="extra">
<el-select v-model="messageStore.form.editorType" :placeholder="`${$t('select')}${$t('editorType')}`" class="!w-[180px]" clearable filterable> <el-input v-model="messageStore.form.extra" class="!w-[180px]" maxlength="20" minlength="10" show-word-limit type="text" />
<el-option v-for="(item, index) in editorTypeList" :key="index" :label="item.label" :navigationBar="false" :value="item.value" />
</el-select>
</el-form-item> </el-form-item>
<!-- 0:未读 1:已读 --> <!-- 0:未读 1:已读 -->
<el-form-item :label="$t('status')" prop="status"> <el-form-item :label="$t('status')" prop="status">
<el-input v-model="messageStore.form.status" :placeholder="`${$t('input')}${$t('status')}`" class="!w-[180px]" clearable /> <el-select v-model="messageStore.form.status" :placeholder="$t('status')" class="!w-[180px]" clearable filterable remote remote-show-suffix>
<el-option v-for="(item, index) in isReadStatus" :key="index" :label="item.label" :value="item.value" />
</el-select>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>

View File

@ -0,0 +1,32 @@
<script lang="ts" setup>
import { MdEditor } from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';
import { fetchUploadFile } from '@/api/v1/system';
import { updateMessage } from '@/views/message-management/message/utils/hooks';
/**
* * 上传图片
* @param files
* @param callback
*/
const onUploadImg = async (files: any, callback: any) => {
//
const res = await Promise.all(
files.map((file: any) => {
return new Promise(async resolve => {
const form = new FormData();
form.append('file', file);
form.append('type', 'message');
resolve(await fetchUploadFile(form));
});
}),
);
//
callback(res.map(item => item.data.url));
};
</script>
<template>
<MdEditor v-model="updateMessage.content" :showToolbarName="true" style="height: calc(100vh - 120px)" @onUploadImg="onUploadImg" />
</template>

View File

@ -0,0 +1,134 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { FormInstance } from 'element-plus';
import { editorTypeList, rules } from '@/views/message-management/message/utils/columns';
import { $t } from '@/plugins/i18n';
import { usePublicHooks } from '@/views/hooks';
import { useMessageTypeStore } from '@/store/message/messageType';
import LoadingSvg from '@/assets/svg/loading.svg';
import { coverUrl, loading, onSearchUserinfo, onUpload, updateMessage, userDataList } from '@/views/message-management/message/utils/hooks';
import { messageLevel, settingLR } from '@/views/message-management/message-editing/utils/columns';
import { Plus } from '@element-plus/icons-vue';
import { beforeUpload } from '@/views/message-management/message-editing/utils/hooks';
import SplitPane from '@/components/SplitPane';
import RichEditor from '@/views/message-management/message/rich-editor.vue';
import MarkdownEditor from '@/views/message-management/message/markdown-editor.vue';
//
const { switchStyle } = usePublicHooks();
const formRef = ref<FormInstance>();
const messageTypeStore = useMessageTypeStore();
onMounted(() => {
messageTypeStore.getAllMessageTypeList();
});
defineExpose({ formRef });
</script>
<template>
<el-card shadow="never" style="height: calc(100vh - 200px); overflow: auto">
<div class="split-pane">
<SplitPane :splitSet="settingLR">
<template #paneL>
<rich-editor v-if="updateMessage.editorType === 'rich'" />
<markdown-editor v-else />
</template>
<template #paneR>
<el-form ref="formRef" :model="updateMessage" :rules="rules" class="w-[100%] p-5" label-width="auto">
<!-- 标题 -->
<el-form-item :label="$t('title')" prop="title">
<el-input v-model="updateMessage.title" />
</el-form-item>
<!-- 消息类型 -->
<el-form-item :label="$t('messageType')" prop="messageType">
<el-select v-model="updateMessage.messageType" :placeholder="`${$t('select')}${$t('messageType')}`" clearable filterable>
<el-option v-for="(item, index) in messageTypeStore.allMessageTypeList" :key="index" :label="item.messageName" :navigationBar="false" :value="item.messageType" />
</el-select>
</el-form-item>
<!-- 接收人ID -->
<el-form-item :label="$t('receivedUserIds')" prop="receivedUserIds">
<el-select
v-model="updateMessage.receivedUserIds"
:loading="loading"
:placeholder="$t('receivedUserIdTip')"
:remote-method="onSearchUserinfo"
clearable
filterable
multiple
remote
remote-show-suffix
>
<el-option v-for="item in userDataList" :key="item.id" :label="item.username" :value="item.id" />
<template #loading>
<el-icon class="is-loading">
<LoadingSvg />
</el-icon>
</template>
</el-select>
</el-form-item>
<!-- 编辑器类型 -->
<el-form-item :label="$t('editorType')" prop="editorType">
<el-radio-group v-model="updateMessage.editorType">
<el-radio v-for="(item, index) in editorTypeList" :key="index" :navigationBar="false" :value="item.value">
{{ item.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<!-- 是否已读 -->
<el-form-item :label="$t('isRead')" prop="status">
<el-switch v-model="updateMessage.status" :active-text="$t('readAlready')" :active-value="true" :inactive-text="$t('unread')" :inactive-value="false" :style="switchStyle" inline-prompt />
</el-form-item>
<!-- 封面内容 -->
<el-form-item :label="$t('cover')" prop="cover">
<el-upload :auto-upload="true" :before-upload="beforeUpload" :http-request="onUpload" :show-file-list="false" accept="image/*" drag>
<el-image v-if="coverUrl" :src="coverUrl" fit="cover" lazy>
<template #placeholder>
<img alt="" src="@/assets/images/tip/loading.gif" />
</template>
</el-image>
<el-icon v-else size="36">
<Plus />
</el-icon>
</el-upload>
<el-button v-show="coverUrl" link type="primary" @click="coverUrl = ''"> 删除图片</el-button>
</el-form-item>
<!-- 简介 -->
<el-form-item :label="$t('summary')" prop="summary">
<el-input v-model="updateMessage.summary" :autosize="{ minRows: 3, maxRows: 6 }" maxlength="200" minlength="10" show-word-limit type="textarea" />
</el-form-item>
<!-- 消息等级 -->
<el-form-item :label="$t('level')" prop="level">
<el-select v-model="updateMessage.level" :placeholder="$t('level')" clearable filterable remote remote-show-suffix>
<el-option v-for="item in messageLevel" :key="item" :label="$t(item)" :value="item" />
</el-select>
</el-form-item>
<!-- 消息等级简介 -->
<el-form-item :label="$t('extra')" prop="extra">
<el-input v-model="updateMessage.extra" maxlength="20" minlength="10" show-word-limit type="text" />
</el-form-item>
</el-form>
</template>
</SplitPane>
</div>
</el-card>
</template>
<style lang="scss" scoped>
:deep(.el-upload-dragger) {
display: flex;
align-items: center;
justify-content: center;
width: 90px;
height: 90px;
}
</style>

View File

@ -0,0 +1,58 @@
<script lang="ts" setup>
import { onBeforeUnmount, ref, shallowRef } from 'vue';
import '@wangeditor/editor/dist/css/style.css';
import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
import { getToken } from '@/utils/auth';
import { updateMessage } from '@/views/message-management/message/utils/hooks';
const mode = 'default';
// shallowRef
const editorRef = shallowRef();
const toolbarConfig: any = { excludeKeys: 'fullScreen' };
const editorConfig = { placeholder: '请输入内容...', MENU_CONF: {} };
const token = ref(getToken().token);
editorConfig.MENU_CONF['uploadImage'] = {
//
server: '/api/files/upload',
// form-data fieldName
fieldName: 'file',
//
allowedFileTypes: ['image/png', 'image/jpg', 'image/jpeg'],
meta: { type: 'message' },
headers: { token: token.value },
//
customInsert(res: any, insertFn) {
// res.data.url
if (res.data.url) {
// const form = new FormData();
// form.append('file', file);
// form.append('type', 'message');
// await fetchUploadFile(form)
setTimeout(() => {
// insertFn
insertFn(res.data.url);
}, 2000);
}
},
};
const handleCreated = editor => {
// editor
editorRef.value = editor;
};
//
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor == null) return;
editor.destroy();
});
</script>
<template>
<div class="wangeditor">
<Toolbar :defaultConfig="toolbarConfig" :editor="editorRef" :mode="mode" style="border-bottom: 1px solid #ccc" />
<Editor v-model="updateMessage.content" :defaultConfig="editorConfig" :mode="mode" style="height: 500px; overflow-y: hidden" @onCreated="handleCreated" />
</div>
</template>

View File

@ -1,6 +1,7 @@
import { reactive } from 'vue'; import { reactive } from 'vue';
import { $t } from '@/plugins/i18n'; import { $t } from '@/plugins/i18n';
import type { FormRules } from 'element-plus'; import type { FormRules } from 'element-plus';
import { ElTag } from 'element-plus';
// 表格列 // 表格列
export const columns: TableColumnList = [ export const columns: TableColumnList = [
@ -8,28 +9,28 @@ export const columns: TableColumnList = [
{ type: 'index', index: (index: number) => index + 1, label: '序号', width: 60 }, { type: 'index', index: (index: number) => index + 1, label: '序号', width: 60 },
// 消息标题 // 消息标题
{ label: $t('title'), prop: 'title' }, { label: $t('title'), prop: 'title' },
// 接收人用户ID
{
label: $t('receivedUserIds'),
prop: 'receivedUserIds',
formatter({ receivedUserIds }) {
return !receivedUserIds ? $t('all') : $t('portion');
},
},
// 发送人昵称 // 发送人昵称
{ label: $t('sendNickname'), prop: 'sendNickname' }, { label: $t('sendNickname'), prop: 'sendNickname' },
// 消息类型 // 消息类型
{ label: $t('messageType'), prop: 'messageType' }, { label: $t('messageType'), prop: 'messageType' },
// 消息内容
{ label: $t('content'), prop: 'content' },
// 编辑器类型 // 编辑器类型
{ label: $t('editorType'), prop: 'editorType' }, { label: $t('editorType'), prop: 'editorType' },
// 编辑器类型
{
label: $t('level'),
prop: 'level',
formatter({ level }) {
return <ElTag type={level}>{$t(level)}</ElTag>;
},
},
// 编辑器类型
{ label: $t('extra'), prop: 'extra' },
// 0:未读 1:已读 // 0:未读 1:已读
{ {
label: $t('status'), label: $t('status'),
prop: 'status', prop: 'status',
formatter({ status }) { formatter({ status }) {
return status ? $t('readAlready') : $t('unread'); return status ? <ElTag type={'info'}>{$t('readAlready')}</ElTag> : <ElTag type={'primary'}>{$t('unread')}</ElTag>;
}, },
}, },
{ label: $t('table.updateTime'), prop: 'updateTime', sortable: true, width: 160 }, { label: $t('table.updateTime'), prop: 'updateTime', sortable: true, width: 160 },

View File

@ -1,20 +1,51 @@
import { addDialog } from '@/components/BaseDialog/index'; import { addDialog } from '@/components/BaseDialog/index';
import MessageDialog from '@/views/messageManagement/message/message-dialog.vue'; import MessageDialog from '@/views/message-management/message/message-dialog.vue';
import { useMessageStore } from '@/store/message/message'; import { useMessageStore } from '@/store/message/message';
import { h, ref } from 'vue'; import { h, reactive, ref, toRaw } from 'vue';
import { message, messageBox } from '@/utils/message'; import { message, messageBox } from '@/utils/message';
import type { FormItemProps } from '@/views/messageManagement/message/utils/types';
import { $t } from '@/plugins/i18n'; import { $t } from '@/plugins/i18n';
import DeleteBatchDialog from '@/components/Table/DeleteBatchDialog.vue'; import DeleteBatchDialog from '@/components/Table/DeleteBatchDialog.vue';
import { useAdminUserStore } from '@/store/system/adminUser'; import { useAdminUserStore } from '@/store/system/adminUser';
import { fetchGetReceivedUserinfoByMessageId } from '@/api/v1/message';
import { decode, encode } from 'js-base64';
import type { UploadRequestOptions } from 'element-plus';
import { fetchUploadFile } from '@/api/v1/system';
export const formRef = ref(); export const formRef = ref();
// 删除ids // 删除ids
export const deleteIds = ref([]); export const deleteIds = ref([]);
// 用户信息列表 // 用户信息列表
export const userDataList = ref(); export const userDataList = ref();
// 更新时封面图片
export const coverUrl = ref('');
// 搜索用户加载 // 搜索用户加载
export const loading = ref(false); export const loading = ref(false);
export const updateMessage = reactive({
// 消息标题
title: undefined,
// 封面
cover: undefined,
// 接收人用户ID
receivedUserIds: undefined,
// 发送人用户ID
sendUserId: undefined,
// 发送人昵称
sendNickName: undefined,
// 消息类型
messageType: undefined,
// 消息内容
content: undefined,
// 简介
summary: undefined,
// 消息等级
level: undefined,
// 消息等级类型
extra: undefined,
// 编辑器类型
editorType: undefined,
// 0:未读 1:已读
status: undefined,
});
const messageStore = useMessageStore(); const messageStore = useMessageStore();
const adminUserStore = useAdminUserStore(); const adminUserStore = useAdminUserStore();
@ -25,39 +56,55 @@ export async function onSearch() {
messageStore.loading = false; messageStore.loading = false;
} }
/** 搜索 */
export const onSearchUserinfo = async (keyword: string) => {
loading.value = true;
userDataList.value = await adminUserStore.queryUser({ keyword });
loading.value = false;
};
/** /**
* * * *
* @param row * @param row
*/ */
export async function onUpdate(row: any) { export async function onUpdate(row: any) {
await onSearchUserinfo(''); // 将表格数据合并到更新数据中
Object.assign(updateMessage, row);
// 解码内容
updateMessage.content = decode(row.content);
// 获取当前消息内容和接收者信息
const result = await fetchGetReceivedUserinfoByMessageId({ messageId: row.id });
userDataList.value = result.data.map((item: any) => ({
id: item.receivedUserId,
nickname: item.nickname,
username: item.username,
}));
updateMessage.receivedUserIds = result.data.map((item: any) => item.receivedUserId);
// 设置封面图片
coverUrl.value = row.cover;
addDialog({ addDialog({
title: `${$t('modify')}${$t('message')}`, title: `${$t('modify')}${$t('message')}`,
width: '30%', width: '85%',
props: {
formInline: {
title: row.title,
receivedUserIds: row.receivedUserIds ? row.receivedUserIds.split(',') : row.receivedUserIds,
sendUserId: row.sendUserId,
sendNickName: row.sendNickName,
messageType: row.messageType,
content: row.content,
editorType: row.editorType,
status: row.status,
},
},
draggable: true, draggable: true,
fullscreenIcon: true, fullscreenIcon: true,
closeOnClickModal: false, closeOnClickModal: false,
contentRenderer: () => h(MessageDialog, { ref: formRef }), contentRenderer: () => h(MessageDialog, { ref: formRef }),
beforeSure: (done, { options }) => { beforeSure: done => {
const form = options.props.formInline as FormItemProps;
formRef.value.formRef.validate(async (valid: any) => { formRef.value.formRef.validate(async (valid: any) => {
if (!valid) return; if (!valid) return;
const result = await messageStore.updateMessage({ ...form, id: row.id }); // 将更新的信息进行编码
const data = toRaw(updateMessage);
data.content = encode(data.content);
// 更新消息内容
const result = await messageStore.updateMessage({ ...data, id: row.id });
if (!result) return; if (!result) return;
Object.assign(updateMessage, {});
done(); done();
await onSearch(); await onSearch();
}); });
@ -113,9 +160,10 @@ export const onDeleteBatch = async () => {
}); });
}; };
/** 搜索 */ /** 上传时 */
export const onSearchUserinfo = async (keyword: string) => { export const onUpload = async (options: UploadRequestOptions) => {
loading.value = true; const data = { file: options.file, type: 'message' };
userDataList.value = await adminUserStore.queryUser({ keyword }); const result: any = await fetchUploadFile(data);
loading.value = false; coverUrl.value = result.data.url;
updateMessage.cover = result.data.filepath;
}; };

View File

@ -10,6 +10,10 @@ export interface FormItemProps {
messageType: string; messageType: string;
// 消息内容 // 消息内容
content: string; content: string;
// 消息等级
level: string;
// 消息等级类型
extra: string;
// 编辑器类型 // 编辑器类型
editorType: string; editorType: string;
// 0:未读 1:已读 // 0:未读 1:已读

View File

@ -1,100 +0,0 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { FormInstance } from 'element-plus';
import { editorTypeList, rules } from '@/views/messageManagement/message/utils/columns';
import { FormProps } from '@/views/messageManagement/message/utils/types';
import { $t } from '@/plugins/i18n';
import { usePublicHooks } from '@/views/hooks';
import { useMessageTypeStore } from '@/store/message/messageType';
import LoadingSvg from '@/assets/svg/loading.svg';
import { loading, onSearchUserinfo, userDataList } from '@/views/messageManagement/message/utils/hooks';
const props = withDefaults(defineProps<FormProps>(), {
formInline: () => ({
//
title: undefined,
// ID
receivedUserIds: undefined,
// ID
sendUserId: undefined,
//
sendNickName: undefined,
//
messageType: undefined,
//
content: undefined,
//
editorType: undefined,
// 0: 1:
status: undefined,
}),
});
//
const { switchStyle } = usePublicHooks();
const formRef = ref<FormInstance>();
const form = ref(props.formInline);
const messageTypeStore = useMessageTypeStore();
onMounted(() => {
messageTypeStore.getAllMessageTypeList();
});
defineExpose({ formRef });
</script>
<template>
<el-form ref="formRef" :model="form" :rules="rules" label-width="auto">
<!-- 消息标题 -->
<el-form-item :label="$t('title')" prop="title">
<el-input v-model="form.title" :placeholder="$t('input') + $t('title')" autocomplete="off" maxlength="100" type="text" />
</el-form-item>
<!-- 接收人用户ID -->
<el-form-item :label="$t('receivedUserIds')" prop="receivedUserIds">
<el-select v-model="form.receivedUserIds" :loading="loading" :placeholder="$t('receivedUserIdTip')" :remote-method="onSearchUserinfo" clearable filterable multiple remote remote-show-suffix>
<el-option v-for="item in userDataList" :key="item.id" :label="item.username" :value="item.id" />
<template #loading>
<el-icon class="is-loading">
<LoadingSvg />
</el-icon>
</template>
</el-select>
</el-form-item>
<!-- 发送人用户ID -->
<el-form-item :label="$t('sendUserId')" prop="sendUserId">
<el-select v-model="form.sendUserId" :loading="loading" :placeholder="$t('select') + $t('sendUserId')" :remote-method="onSearchUserinfo" clearable filterable remote remote-show-suffix>
<el-option v-for="item in userDataList" :key="item.id" :label="item.username" :value="item.id" />
<template #loading>
<el-icon class="is-loading">
<LoadingSvg />
</el-icon>
</template>
</el-select>
</el-form-item>
<!-- 消息类型 -->
<el-form-item :label="$t('messageType')" prop="messageType">
<el-select v-model="form.messageType" :placeholder="`${$t('select')}${$t('messageType')}`" clearable filterable>
<el-option v-for="(item, index) in messageTypeStore.allMessageTypeList" :key="index" :label="item.messageName" :navigationBar="false" :value="item.messageType" />
</el-select>
</el-form-item>
<!-- 编辑器类型 -->
<el-form-item :label="$t('editorType')" prop="editorType">
<el-select v-model="form.editorType" :placeholder="`${$t('select')}${$t('editorType')}`" clearable filterable>
<el-option v-for="(item, index) in editorTypeList" :key="index" :label="item.label" :navigationBar="false" :value="item.value" />
</el-select>
</el-form-item>
<!-- 消息内容 -->
<el-form-item :label="$t('content')" prop="content">
<el-input v-model="form.content" :placeholder="$t('input') + $t('content')" autocomplete="off" type="textarea" />
</el-form-item>
<!-- 0:未读 1:已读 -->
<el-form-item :label="$t('status')" prop="status">
<el-switch v-model="form.status" :active-text="$t('readAlready')" :active-value="true" :inactive-text="$t('unread')" :inactive-value="false" :style="switchStyle" inline-prompt />
</el-form-item>
</el-form>
</template>

View File

@ -1,38 +0,0 @@
<script lang="ts" setup>
import splitpane, { ContextProps } from '@/components/SplitPane';
import { reactive } from 'vue';
import MessageEditorFrom from '@/views/messageManagement/messageEditing/message-editor-from.vue';
import RichEditor from '@/views/messageManagement/messageEditing/rich-editor.vue';
import { formState } from '@/views/messageManagement/messageEditing/utils/hooks';
import MarkdownEditor from '@/views/messageManagement/messageEditing/markdown-editor.vue';
const settingLR: ContextProps = reactive({
minPercent: 20,
defaultPercent: 80,
split: 'vertical',
});
</script>
<template>
<el-card shadow="never">
<div class="split-pane">
<splitpane :splitSet="settingLR">
<template #paneL>
<RichEditor v-if="formState.editorType === 'rich'" />
<markdown-editor v-else />
</template>
<template #paneR>
<message-editor-from />
</template>
</splitpane>
</div>
</el-card>
</template>
<style lang="scss" scoped>
.split-pane {
width: 100%;
height: calc(100vh - 120px);
}
</style>