企业级门户网站设计与实现:基于SpringBoot + Vue3的全栈解决方案(Day 8)

0 阅读6分钟

内容安全与广告位完善

前言

第七天完成了横幅海报模块动态化和一键启动脚本。第八天的开发工作将重点关注内容安全防护(文章防复制功能)、数据字典优化(横幅类型下拉选择)、各广告位的完整实现以及数据库字段优化,进一步完善门户系统的功能和安全性。

​编辑

一、富文本内容存储优化

1.1 问题分析

文章内容使用富文本编辑器录入,但数据库 yucms_article 表的 content 字段使用 TEXT 类型,最大存储 64KB,无法满足长文章的存储需求。

1.2 解决方案

content 字段类型从 TEXT 改为 LONGTEXT(最大支持 4GB):

ALTER TABLE yucms_article MODIFY COLUMN content LONGTEXT;

1.3 字段类型对比

字段类型最大容量适用场景
TEXT64KB短文本、摘要
LONGTEXT4GB长文章、富文本内容

二、文章正文防复制功能

2.1 需求分析

为保护原创内容,需要实现文章正文的防复制功能,并在用户尝试复制时弹出提示框。

2.2 实现方案

ArticleDetail.vue 中添加防复制事件监听:

<div 
  ref="articleContentRef" 
  class="anti-copy" 
  @copy="preventCopy" 
  @contextmenu="preventContextMenu" 
  @selectstart="preventSelectStart"
>
  <div v-html="article.content"></div>
</div>

2.3 事件处理函数

// 阻止复制
function preventCopy(e) {
  e.preventDefault();
  showCopyAlert();
}

// 阻止右键菜单
function preventContextMenu(e) {
  e.preventDefault();
}

// 阻止文字选中
function preventSelectStart(e) {
  e.preventDefault();
}

2.4 自定义提示弹窗

创建美观的提示弹窗组件,与网站整体风格协调:

<Transition name="fade">
  <div v-if="showAlert" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
    <div class="bg-white rounded-2xl shadow-2xl p-6 max-w-md w-full mx-4 transform">
      <div class="text-center">
        <div class="w-16 h-16 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-full flex items-center justify-center mx-auto mb-4">
          <svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
          </svg>
        </div>
        <h3 class="text-lg font-semibold text-slate-800 mb-2">关注公众号获取资料</h3>
        <p class="text-slate-600 mb-4">为保护原创内容,文章正文无法直接复制</p>
        <div class="bg-slate-50 rounded-lg p-4 mb-4">
          <p class="text-sm text-slate-500 mb-1">公众号名称</p>
          <p class="text-lg font-bold text-bank-primary">Fintech.Ren</p>
        </div>
        <button 
          @click="showAlert = false" 
          class="w-full py-3 bg-bank-primary text-white font-medium rounded-xl hover:bg-bank-primary/90 transition-colors"
        >
          我知道了
        </button>
      </div>
    </div>
  </div>
</Transition>

2.5 CSS 样式

.anti-copy {
  user-select: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
}

.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

三、横幅类型下拉选择优化

3.1 问题分析

横幅管理页面中,横幅类型使用单选按钮展示,不够美观且占用空间较大。需要改为下拉菜单形式。

3.2 修改 YucmsBanner.data.ts

将横幅类型字段从 radio 改为 select

{
  label: '横幅类型',
  field: 'type',
  defaultValue: "1",
  component: 'JDictSelectTag',
  componentProps:{
      dictCode:"yucms_banner_type",
      type: "select",
      refresh: true,
  },
  dynamicRules: ({model,schema}) => {
        return [{ required: true, message: '请选择横幅类型!' }];
  },
}

3.3 数据字典刷新问题

3.3.1 问题描述

数据字典修改后,前端不会自动更新,因为 initDictOptions 函数优先读取缓存。

3.3.2 解决方案

jeecgboot-vue3/src/utils/dict/index.ts 中添加 refresh 参数:

export const initDictOptions = (code, refresh = false) => {
  if (!refresh && getDictItemsByCode(code)) {
    return new Promise((resolve) => resolve(getDictItemsByCode(code)));
  }
  // 从后端获取最新数据...
};

JDictSelectTag.vue 组件中传递 refresh 属性:

props: {
  refresh: propTypes.bool.def(false),
},
async function initDictData() {
  const dictData = await initDictOptions(dictCode, refresh);
  // 处理数据逻辑
}

四、各广告位动态获取实现

4.1 广告位类型定义

Type 值广告位名称尺寸位置
1首页轮播-首页顶部
2首页横条728×90轮播下方
3首页960×670960×670中间区域
4首页320×790320×790侧边栏
5首页300×250300×250侧边栏下方
6List页640×360640×360文章列表页

4.2 Banner API 封装

src/api/banner.ts 中创建各广告位的 API 函数:

// 获取首页轮播(type=1,取3条)
export const getBannerList = (params?: any) => {
  const now = new Date().toISOString().slice(0, 19).replace('T', ' ');
  return defHttp.get({
    url: Api.GetBannerList,
    params: {
      ...params,
      bannerStatus: '1',
      type: '1',
      pageSize: 3,
      column: 'updateTime',
      order: 'desc',
      startTime_lte: now,
      endTime_gte: now
    }
  });
};

// 获取顶部横幅(type=2,取1条)
export const getTopBanner = () => { /* ... */ };

// 获取中间横幅(type=3,取1条)
export const getMiddleBanner = () => { /* ... */ };

// 获取侧边栏横幅(type=4,取1条)
export const getSideBanner = () => { /* ... */ };

// 获取小横幅(type=5,取1条)
export const getSmallBanner = () => { /* ... */ };

// 获取List页横幅(type=6,取1条)
export const getListBanner = () => { /* ... */ };

4.3 数据获取逻辑

各广告位的数据获取逻辑统一遵循以下规则:

  1. 状态过滤bannerStatus = 1(启用状态)
  2. 时间区间:当前时间在 start_timeend_time 之间
  3. 类型过滤:根据广告位类型设置 type 参数
  4. 排序规则:按 update_time 降序排列
  5. 数量限制:轮播取前3条,其他广告位取前1条

4.4 Home.vue 集成

在首页中集成各广告位组件:

// 响应式变量
const topBanner = ref(null);
const sideBanner = ref(null);
const middleBanner = ref(null);
const smallBanner = ref(null);

// 数据获取函数
async function fetchTopBanner() { /* ... */ }
async function fetchSideBanner() { /* ... */ }
async function fetchMiddleBanner() { /* ... */ }
async function fetchSmallBanner() { /* ... */ }

// 组件挂载时执行
onMounted(() => {
  fetchTopBanner();
  fetchSideBanner();
  fetchMiddleBanner();
  fetchSmallBanner();
});

4.5 模板渲染

使用 v-if/v-else 实现广告位的条件渲染:

<!-- 中间横幅 -->
<div v-if="middleBanner" class="ad-container overflow-hidden">
  <a :href="middleBanner.linkUrl" :target="middleBanner.izBlank === '1' ? '_blank' : '_self'">
    <img :src="getImageUrl(middleBanner.imageUrl)" :alt="middleBanner.title" />
  </a>
</div>
<div v-else class="ad-container flex items-center justify-center">
  <div class="text-center">
    <p class="text-sm text-slate-400">广告位 960 × 670</p>
    <p class="text-xs text-slate-300 mt-1">联盟广告位</p>
  </div>
</div>

五、图片路径问题修复

5.1 问题描述

ArticleList.vue 中的 getImageUrl 函数使用了错误的路径前缀 /jeecg-boot,导致图片无法加载。

5.2 统一修复

确保所有页面的 getImageUrl 函数使用正确的前缀 /jeecgboot(与 Vite 代理配置一致):

function getImageUrl(imagePath) {
  if (!imagePath) return null;
  
  // 完整URL直接返回
  if (imagePath.startsWith('http://') || imagePath.startsWith('https://')) {
    return imagePath;
  }
  
  // 添加正确的代理前缀
  if (imagePath.startsWith('/')) {
    return `/jeecgboot${imagePath}`;
  } else {
    return `/jeecgboot/${imagePath}`;
  }
}

5.3 Vite 代理配置

server: {
  port: 3001,
  proxy: {
    '/jeecgboot': {
      target: 'http://localhost:8080/jeecg-boot',
      changeOrigin: true,
      rewrite: (path) => path.replace(/^/jeecgboot/, '')
    }
  }
}

六、广告位尺寸规范

6.1 各广告位实际尺寸

广告位容器宽度推荐图片尺寸比例
首页轮播全屏1920×60016:5
首页横条960px728×90~8:1
首页960×670960px960×670~1.43:1
首页320×790300px320×790~0.41:1
首页300×250300px300×2506:5
List页640×360自适应640×36016:9

6.2 图片适配策略

  • 使用 object-cover 确保图片填满容器
  • 设置固定高度,宽度自适应
  • 添加 loading="lazy" 实现懒加载

七、总结

第八天的开发工作主要完成了以下内容:

  1. 富文本内容存储优化:将 content 字段类型从 TEXT 改为 LONGTEXT,支持大文本存储
  2. 文章防复制功能:实现文章正文的防复制保护,自定义美观的提示弹窗
  3. 横幅类型下拉选择:将单选按钮改为下拉菜单,优化用户体验
  4. 数据字典刷新机制:添加 refresh 参数支持强制刷新字典数据
  5. 各广告位完整实现:首页轮播、横条、中间横幅、侧边栏横幅、小横幅及 List 页横幅的动态获取
  6. 图片路径问题修复:统一所有页面的 getImageUrl 函数路径前缀

这些改进完善了门户系统的内容安全防护和广告位管理功能,为后续的功能扩展和性能优化奠定了基础。