fix: 🧩 消息页面待完善

This commit is contained in:
Bunny 2024-11-01 00:08:57 +08:00
parent 2bda0940cf
commit a9e06b151a
5 changed files with 181 additions and 127 deletions

View File

@ -34,10 +34,12 @@ export const getAllMessageList = async () => {
.filter(message => message.messageType === 'notifications') .filter(message => message.messageType === 'notifications')
.map(message => ({ .map(message => ({
cover: message.cover, cover: message.cover,
description: message.summary,
title: message.title, title: message.title,
datetime: message.createTime, datetime: message.createTime,
description: message.summary,
type: '1', type: '1',
status: message.statusType,
extra: message.extra,
})) as ListItem[]; })) as ListItem[];
// 消息 // 消息
@ -49,6 +51,8 @@ export const getAllMessageList = async () => {
title: message.title, title: message.title,
datetime: message.createTime, datetime: message.createTime,
type: '2', type: '2',
status: message.statusType,
extra: message.extra,
})) as ListItem[]; })) as ListItem[];
// 系统消息 // 系统消息
@ -60,6 +64,8 @@ export const getAllMessageList = async () => {
title: message.title, title: message.title,
datetime: message.createTime, datetime: message.createTime,
type: '3', type: '3',
status: message.statusType,
extra: message.extra,
})) as ListItem[]; })) as ListItem[];
noticesData.value = [ noticesData.value = [

View File

@ -4,23 +4,33 @@ import { computed, onMounted, ref } from 'vue';
import { getAllMessageList, noticesData } from './data'; import { getAllMessageList, noticesData } 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 noticesNum = ref(0);
const notices = ref(noticesData); const notices = ref(noticesData);
// //
const activeKey = ref(noticesData.value[0]?.key); const activeKey = ref(noticesData.value[0]?.key);
const getLabel = computed(() => item => item.name + (item.list.length > 0 ? `(${item.list.length})` : '')); const getLabel = computed(() => item => item.name + (item.list.length > 0 ? `(${item.list.length})` : ''));
onMounted(async () => { /** 计算消息数量 */
const computedNoticesNum = async () => {
// //
await getAllMessageList(); await getAllMessageList();
// 0
noticesNum.value = 0;
// //
notices.value.map(v => (noticesNum.value += v.list.length)); notices.value.map(v => (noticesNum.value += v.list.length));
// //
activeKey.value = noticesData.value[0]?.key; activeKey.value = noticesData.value[0]?.key;
//
};
onMounted(() => {
computedNoticesNum();
//
useIntervalFn(() => computedNoticesNum(), 1000 * 30);
}); });
</script> </script>

View File

@ -1,28 +1,28 @@
<script setup lang="ts"> <script lang="ts" setup>
import { useI18n } from "vue-i18n"; import { useI18n } from 'vue-i18n';
import { emitter } from "@/utils/mitt"; import { emitter } from '@/utils/mitt';
import { onClickOutside } from "@vueuse/core"; import { onClickOutside } from '@vueuse/core';
import { ref, computed, onMounted, onBeforeUnmount } from "vue"; import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange"; import { useDataThemeChange } from '@/layout/hooks/useDataThemeChange';
import CloseIcon from "@iconify-icons/ep/close"; import CloseIcon from '@iconify-icons/ep/close';
const target = ref(null); const target = ref(null);
const show = ref<Boolean>(false); const show = ref<Boolean>(false);
const iconClass = computed(() => { const iconClass = computed(() => {
return [ return [
"w-[22px]", 'w-[22px]',
"h-[22px]", 'h-[22px]',
"flex", 'flex',
"justify-center", 'justify-center',
"items-center", 'items-center',
"outline-none", 'outline-none',
"rounded-[4px]", 'rounded-[4px]',
"cursor-pointer", 'cursor-pointer',
"transition-colors", 'transition-colors',
"hover:bg-[#0000000f]", 'hover:bg-[#0000000f]',
"dark:hover:bg-[#ffffff1f]", 'dark:hover:bg-[#ffffff1f]',
"dark:hover:text-[#ffffffd9]" 'dark:hover:text-[#ffffffd9]',
]; ];
}); });
@ -35,14 +35,14 @@ onClickOutside(target, (event: any) => {
}); });
onMounted(() => { onMounted(() => {
emitter.on("openPanel", () => { emitter.on('openPanel', () => {
show.value = true; show.value = true;
}); });
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
// `openPanel` // `openPanel`
emitter.off("openPanel"); emitter.off('openPanel');
}); });
</script> </script>
@ -50,48 +50,38 @@ onBeforeUnmount(() => {
<div :class="{ show }"> <div :class="{ show }">
<div class="right-panel-background" /> <div class="right-panel-background" />
<div ref="target" class="right-panel bg-bg_color"> <div ref="target" class="right-panel bg-bg_color">
<div <div class="project-configuration border-b-[1px] border-solid border-[var(--pure-border-color)]">
class="project-configuration border-b-[1px] border-solid border-[var(--pure-border-color)]"
>
<h4 class="dark:text-white"> <h4 class="dark:text-white">
{{ t("panel.pureSystemSet") }} {{ t('panel.pureSystemSet') }}
</h4> </h4>
<span <span
v-tippy="{ v-tippy="{
content: t('panel.pureCloseSystemSet'), content: t('panel.pureCloseSystemSet'),
placement: 'bottom-start', placement: 'bottom-start',
zIndex: 41000 zIndex: 41000,
}" }"
:class="iconClass" :class="iconClass"
> >
<IconifyIconOffline <IconifyIconOffline :icon="CloseIcon" class="dark:text-white" height="18px" width="18px" @click="show = !show" />
class="dark:text-white"
width="18px"
height="18px"
:icon="CloseIcon"
@click="show = !show"
/>
</span> </span>
</div> </div>
<el-scrollbar> <el-scrollbar>
<slot /> <slot />
</el-scrollbar> </el-scrollbar>
<div <div class="flex justify-end p-3 border-t-[1px] border-solid border-[var(--pure-border-color)]">
class="flex justify-end p-3 border-t-[1px] border-solid border-[var(--pure-border-color)]"
>
<el-button <el-button
v-tippy="{ v-tippy="{
content: t('panel.pureClearCacheAndToLogin'), content: t('panel.pureClearCacheAndToLogin'),
placement: 'left-start', placement: 'left-start',
zIndex: 41000 zIndex: 41000,
}" }"
type="danger"
text
bg bg
text
type="danger"
@click="onReset" @click="onReset"
> >
{{ t("panel.pureClearCache") }} {{ t('panel.pureClearCache') }}
</el-button> </el-button>
</div> </div>
</div> </div>

View File

@ -1,36 +1,24 @@
<script lang="ts" setup> <script lang="ts" setup>
import { formState } from '@/views/messageManagement/messageEditing/utils/hooks'; import { beforeUpload, coverUrl, formState, loading, onSearchUserinfo, onUpload, userDataList } from '@/views/messageManagement/messageEditing/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 { rules } from '@/views/messageManagement/messageEditing/utils/columns';
import { FormInstance } from 'element-plus'; import { FormInstance } from 'element-plus';
import { editorTypeList } from '@/views/messageManagement/message/utils/columns'; import { editorTypeList } from '@/views/messageManagement/message/utils/columns';
import LoadingSvg from '@/assets/svg/loading.svg'; import LoadingSvg from '@/assets/svg/loading.svg';
import { useAdminUserStore } from '@/store/system/adminUser';
import { useMessageTypeStore } from '@/store/message/messageType'; import { useMessageTypeStore } from '@/store/message/messageType';
import { encode } from 'js-base64'; import { encode } from 'js-base64';
import { message } from '@/utils/message'; import { message } from '@/utils/message';
import { useMessageStore } from '@/store/message/message'; import { useMessageStore } from '@/store/message/message';
import { usePublicHooks } from '@/views/hooks'; import { usePublicHooks } from '@/views/hooks';
import { Plus } from '@element-plus/icons-vue';
const formRef = ref(); const formRef = ref();
//
const userDataList = ref();
//
const loading = ref(false);
// //
const { switchStyle } = usePublicHooks(); const { switchStyle } = usePublicHooks();
const adminUserStore = useAdminUserStore();
const messageTypeStore = useMessageTypeStore(); const messageTypeStore = useMessageTypeStore();
const messageStore = useMessageStore(); const messageStore = useMessageStore();
/** 搜索 */
const onSearchUserinfo = async (keyword: string) => {
loading.value = true;
userDataList.value = await adminUserStore.queryUser({ keyword });
loading.value = false;
};
/** 提交消息 */ /** 提交消息 */
const submitForm = (formEl: FormInstance | undefined) => { const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return; if (!formEl) return;
@ -60,9 +48,9 @@ const submitForm = (formEl: FormInstance | undefined) => {
/** 重置消息 */ /** 重置消息 */
const resetForm = (formEl: FormInstance | undefined) => { const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return; if (!formEl) return;
const data = toRaw(formState);
formEl.resetFields(); formEl.resetFields();
formState.content = ''; formState.content = '';
coverUrl.value = '';
}; };
onMounted(() => { onMounted(() => {
@ -109,6 +97,21 @@ onMounted(() => {
<el-switch v-model="formState.status" :active-text="$t('readAlready')" :active-value="true" :inactive-text="$t('unread')" :inactive-value="false" :style="switchStyle" inline-prompt /> <el-switch v-model="formState.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>
<!-- 封面内容 -->
<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-form-item :label="$t('summary')" prop="summary">
<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" />
@ -121,3 +124,13 @@ onMounted(() => {
</el-form-item> </el-form-item>
</el-form> </el-form>
</template> </template>
<style lang="scss" scoped>
:deep(.el-upload-dragger) {
display: flex;
align-items: center;
justify-content: center;
width: 90px;
height: 90px;
}
</style>

View File

@ -1,5 +1,16 @@
import { reactive } from 'vue'; import { reactive, ref } from 'vue';
import type { UploadRawFile, UploadRequestOptions } from 'element-plus';
import { SystemEnum } from '@/enums/upload';
import { message } from '@/utils/message';
import { fetchUploadFile } from '@/api/v1/system';
import { useAdminUserStore } from '@/store/system/adminUser';
// 用户信息列表
export const userDataList = ref();
// 搜索用户加载
export const loading = ref(false);
// 封面url
export const coverUrl = ref('');
// 提交表单信息 // 提交表单信息
export const formState = reactive({ export const formState = reactive({
title: '', title: '',
@ -11,3 +22,27 @@ export const formState = reactive({
summary: '', summary: '',
cover: '', cover: '',
}); });
const adminUserStore = useAdminUserStore();
/** 搜索 */
export const onSearchUserinfo = async (keyword: string) => {
loading.value = true;
userDataList.value = await adminUserStore.queryUser({ keyword });
loading.value = false;
};
/** 上传时 */
export const onUpload = async (options: UploadRequestOptions) => {
const data = { file: options.file, type: 'message' };
const result: any = await fetchUploadFile(data);
coverUrl.value = result.data.url;
formState.cover = result.data.filepath;
};
/** 上传之前 */
export const beforeUpload = (file: UploadRawFile) => {
if (file.size > SystemEnum.IMAGE_SIZE) {
message(SystemEnum.IMAGE_MESSAGE, { type: 'error' });
return false;
}
};