feat 在线客服

This commit is contained in:
Ironsp
2026-10-10 08:12:18 +08:00
parent 6af8fd7b09
commit 6ecc649a1c
10 changed files with 456 additions and 472 deletions

View File

@@ -1,8 +1,9 @@
<!-- 页面:消息详情(MessageDetail) -->
<script setup lang="ts">
import { computed, onMounted, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { ElButton, ElMessage } from 'element-plus'
import { useRoute, useRouter } from 'vue-router'
import { AppButton } from '@/components'
import { PageHead } from '@/components'
import { useMessageStore } from '@/stores/modules/message'
import { formatFileSize } from '@/utils/format'
@@ -36,181 +37,218 @@ onMounted(load)
<template>
<div class="message-detail">
<nav class="message-detail__breadcrumb text-secondary">
<RouterLink to="/message" class="link">
消息中心
</RouterLink>
<span> &gt; </span>
<span>{{ item ? `${item.type}详情` : '消息详情' }}</span>
</nav>
<PageHead
title="消息详情"
slogan="查看站内消息完整内容与附件资料"
/>
<template v-if="item">
<article class="message-detail__card">
<h1 class="message-detail__title">
{{ item.title }}
</h1>
<p class="message-detail__time text-secondary">
发布时间:{{ item.publishTime }}
</p>
<div class="message-detail__content">
{{ item.content }}
</div>
<div class="message-detail__wrap">
<nav class="message-detail__breadcrumb">
<RouterLink to="/message" class="message-detail__breadcrumb-link">
消息中心
</RouterLink>
<span class="message-detail__breadcrumb-sep">&gt;</span>
<span class="message-detail__breadcrumb-current">{{ item ? `${item.type}详情` : '消息详情' }}</span>
</nav>
<section v-if="item.attachments.length > 0" class="message-detail__attachments">
<h2 class="message-detail__attachments-title">
附件({{ item.attachments.length }})
</h2>
<ul class="message-detail__file-list">
<li
v-for="(file, index) in item.attachments"
:key="`${file.name}-${index}`"
class="message-detail__file"
<template v-if="item">
<article class="message-detail__card">
<h1 class="message-detail__title">
{{ item.title }}
</h1>
<p class="message-detail__time">
发布时间:{{ item.publishTime }}
</p>
<div class="message-detail__content">
{{ item.content }}
</div>
<section v-if="item.attachments.length > 0" class="message-detail__attachments">
<h2 class="message-detail__attachments-title">
附件({{ item.attachments.length }})
</h2>
<ul class="message-detail__file-list">
<li
v-for="(file, index) in item.attachments"
:key="`${file.name}-${index}`"
class="message-detail__file"
>
<div class="message-detail__file-info">
<span class="message-detail__file-name" :title="file.name">{{ file.name }}</span>
<span class="message-detail__file-meta">
{{ formatFileSize(file.size) }} · 上传于{{ file.uploadTime }}
</span>
</div>
<div class="message-detail__file-actions">
<el-button
class="message-detail__file-btn"
link
@click="onPreview"
>
预览
</el-button>
<el-button
class="message-detail__file-btn"
link
@click="onDownload"
>
下载
</el-button>
</div>
</li>
</ul>
</section>
<div class="message-detail__actions">
<el-button
class="message-detail__back"
@click="onBack"
>
<div class="message-detail__file-info">
<span class="message-detail__file-name" :title="file.name">{{ file.name }}</span>
<span class="message-detail__file-meta text-secondary">
{{ formatFileSize(file.size) }} · 上传于{{ file.uploadTime }}
</span>
</div>
<div class="message-detail__file-actions">
<button class="message-detail__file-btn" @click="onPreview">
预览
</button>
<button class="message-detail__file-btn" @click="onDownload">
下载
</button>
</div>
</li>
</ul>
</section>
返回
</el-button>
</div>
</article>
</template>
<div class="message-detail__actions">
<AppButton type="primary" @click="onBack">
返回
</AppButton>
</div>
</article>
</template>
<p v-else class="message-detail__empty text-secondary">
消息不存在
</p>
<p v-else class="message-detail__empty">
消息不存在
</p>
</div>
</div>
</template>
<style lang="less" scoped>
.message-detail {
max-width: @container-width;
margin: 0 auto;
padding: @spacing-lg @spacing-base @spacing-xxl;
.flex(column, flex-start, stretch);
gap: 0;
width: 100%;
padding-bottom: 60px;
background: @message-page-bg;
&__wrap {
width: 100%;
padding: 28px 70px 0;
box-sizing: border-box;
}
&__breadcrumb {
display: flex;
align-items: center;
.flex(row, flex-start, center);
gap: 4px;
margin-bottom: 12px;
font-size: 11px;
color: @text-color-disabled;
height: 49px;
font-size: 12px;
line-height: 17px;
color: @project-muted;
}
.link {
color: @text-color-disabled;
}
&__breadcrumb-link {
color: @project-muted;
span:last-child {
color: @text-color;
font-weight: 500;
&:hover {
color: @primary-strong;
}
}
&__breadcrumb-sep {
color: @project-muted;
}
&__breadcrumb-current {
color: @message-tabs-text;
font-weight: 500;
}
&__card {
padding: 32px 36px 34px;
border-radius: @radius-lg;
padding: 45px 47px 40px;
border-radius: @radius-base;
background: @white;
box-shadow: none;
box-sizing: border-box;
}
&__title {
font-size: 15px;
font-size: 20px;
font-weight: 700;
color: @heading-color;
line-height: 28px;
color: @finance-ink;
text-align: center;
}
&__time {
margin-top: 11px; // 14 × 0.77
font-size: 11px;
color: @text-color-disabled;
margin-top: 14px;
font-size: 12px;
line-height: 18px;
color: @project-muted;
text-align: center;
}
&__content {
margin-top: 24px;
margin-top: 16px;
background: transparent;
color: @text-color;
font-size: 11px;
line-height: 1.9;
color: @message-tabs-text;
font-size: 13px;
line-height: 2.8;
text-align: center;
white-space: pre-wrap;
word-break: break-word;
}
&__attachments {
margin-top: 20px;
margin-top: 24px;
&-title {
margin-bottom: 8px;
font-size: 11px;
margin-bottom: 10px;
font-size: 12px;
font-weight: 400;
color: @text-color-disabled;
color: @project-muted;
}
}
&__file-list {
.flex(column, flex-start, stretch);
gap: @spacing-sm;
gap: 10px;
}
&__file {
.flex(row, space-between);
.flex(row, space-between, center);
gap: @spacing-base;
align-items: center;
width: fit-content;
width: 254px;
max-width: 100%;
padding: 10px 14px;
border: none;
border-radius: 6px;
padding: 15px 12px 15px 19px;
border-radius: @radius-sm;
background: @finance-row-bg;
list-style: none;
box-sizing: border-box;
}
&__file-info {
.flex(column, flex-start, flex-start);
gap: 3px;
gap: 4px;
min-width: 0;
}
&__file-name {
font-size: 11px;
font-size: 12px;
font-weight: 700;
color: @heading-color;
color: @finance-ink;
.ellipsis();
}
&__file-meta {
font-size: 11px;
color: @text-color-disabled;
color: @project-muted;
.ellipsis();
}
&__file-actions {
.flex(row, center);
gap: @spacing-sm;
.flex(column, center, center);
gap: 2px;
flex-shrink: 0;
}
&__file-btn {
font-size: 11px;
color: @text-color-secondary;
transition: color 0.2s;
padding: 0;
font-size: 12px;
font-weight: 400;
color: @message-tabs-text;
&:hover {
color: @primary-strong;
@@ -219,28 +257,32 @@ onMounted(load)
&__actions {
.flex(row, flex-end);
margin-top: 32px;
margin-top: 40px;
}
.app-button {
height: 24px; // 31 × 0.77
padding: 0 16px;
border-radius: 6px;
&__back {
width: 94px;
height: 34px;
padding: 0;
border-color: @primary-strong;
border-radius: @radius-sm;
background: @primary-strong;
color: @white;
font-size: 13px;
font-weight: 500;
&:hover {
border-color: @primary-strong;
background: @primary-strong;
color: @white;
font-size: 11px;
font-weight: 500;
&:hover {
background: @primary-strong;
color: @white;
}
}
}
&__empty {
padding: @spacing-xxl 0;
padding: 80px 0;
text-align: center;
font-size: 11px;
font-size: 13px;
color: @project-muted;
}
}
</style>