fix: 🧩 消息页面待完善
This commit is contained in:
parent
2bda0940cf
commit
a9e06b151a
|
@ -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 = [
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -1,149 +1,139 @@
|
||||||
<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]',
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { onReset } = useDataThemeChange();
|
const { onReset } = useDataThemeChange();
|
||||||
|
|
||||||
onClickOutside(target, (event: any) => {
|
onClickOutside(target, (event: any) => {
|
||||||
if (event.clientX > target.value.offsetLeft) return;
|
if (event.clientX > target.value.offsetLeft) return;
|
||||||
show.value = false;
|
show.value = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
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>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<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">
|
||||||
>
|
{{ t('panel.pureSystemSet') }}
|
||||||
<h4 class="dark:text-white">
|
</h4>
|
||||||
{{ t("panel.pureSystemSet") }}
|
<span
|
||||||
</h4>
|
v-tippy="{
|
||||||
<span
|
content: t('panel.pureCloseSystemSet'),
|
||||||
v-tippy="{
|
placement: 'bottom-start',
|
||||||
content: t('panel.pureCloseSystemSet'),
|
zIndex: 41000,
|
||||||
placement: 'bottom-start',
|
}"
|
||||||
zIndex: 41000
|
:class="iconClass"
|
||||||
}"
|
>
|
||||||
:class="iconClass"
|
<IconifyIconOffline :icon="CloseIcon" class="dark:text-white" height="18px" width="18px" @click="show = !show" />
|
||||||
>
|
</span>
|
||||||
<IconifyIconOffline
|
</div>
|
||||||
class="dark:text-white"
|
<el-scrollbar>
|
||||||
width="18px"
|
<slot />
|
||||||
height="18px"
|
</el-scrollbar>
|
||||||
:icon="CloseIcon"
|
|
||||||
@click="show = !show"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<el-scrollbar>
|
|
||||||
<slot />
|
|
||||||
</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
|
||||||
>
|
v-tippy="{
|
||||||
<el-button
|
content: t('panel.pureClearCacheAndToLogin'),
|
||||||
v-tippy="{
|
placement: 'left-start',
|
||||||
content: t('panel.pureClearCacheAndToLogin'),
|
zIndex: 41000,
|
||||||
placement: 'left-start',
|
}"
|
||||||
zIndex: 41000
|
bg
|
||||||
}"
|
text
|
||||||
type="danger"
|
type="danger"
|
||||||
text
|
@click="onReset"
|
||||||
bg
|
>
|
||||||
@click="onReset"
|
{{ t('panel.pureClearCache') }}
|
||||||
>
|
</el-button>
|
||||||
{{ t("panel.pureClearCache") }}
|
</div>
|
||||||
</el-button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
:deep(.el-scrollbar) {
|
:deep(.el-scrollbar) {
|
||||||
height: calc(100vh - 110px);
|
height: calc(100vh - 110px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-panel-background {
|
.right-panel-background {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
background: rgb(0 0 0 / 20%);
|
background: rgb(0 0 0 / 20%);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
|
transition: opacity 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-panel {
|
.right-panel {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 40000;
|
z-index: 40000;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 280px;
|
max-width: 280px;
|
||||||
box-shadow: 0 0 15px 0 rgb(0 0 0 / 5%);
|
box-shadow: 0 0 15px 0 rgb(0 0 0 / 5%);
|
||||||
transition: all 0.25s cubic-bezier(0.7, 0.3, 0.1, 1);
|
transition: all 0.25s cubic-bezier(0.7, 0.3, 0.1, 1);
|
||||||
transform: translate(100%);
|
transform: translate(100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.show {
|
.show {
|
||||||
transition: all 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
|
transition: all 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
|
||||||
|
|
||||||
.right-panel-background {
|
.right-panel-background {
|
||||||
z-index: 20000;
|
z-index: 20000;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-panel {
|
.right-panel {
|
||||||
transform: translate(0);
|
transform: translate(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-configuration {
|
.project-configuration {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 14px 20px;
|
padding: 14px 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue