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

内容安全与广告位完善

前言

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

一、富文本内容存储优化

1.1 问题分析

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

1.2 解决方案

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

复制代码
ALTER TABLE yucms_article MODIFY COLUMN content LONGTEXT;

1.3 字段类型对比

字段类型 最大容量 适用场景
TEXT 64KB 短文本、摘要
LONGTEXT 4GB 长文章、富文本内容

二、文章正文防复制功能

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×670 960×670 中间区域
4 首页320×790 320×790 侧边栏
5 首页300×250 300×250 侧边栏下方
6 List页640×360 640×360 文章列表页

在 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_time 和 end_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×600 16:5
首页横条 960px 728×90 ~8:1
首页960×670 960px 960×670 ~1.43:1
首页320×790 300px 320×790 ~0.41:1
首页300×250 300px 300×250 6:5
List页640×360 自适应 640×360 16:9

6.2 图片适配策略

  • 使用 object-cover 确保图片填满容器

  • 设置固定高度,宽度自适应

  • 添加 loading="lazy" 实现懒加载

七、总结

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

  1. 富文本内容存储优化 :将 content 字段类型从 TEXT 改为 LONGTEXT,支持大文本存储

  2. 文章防复制功能:实现文章正文的防复制保护,自定义美观的提示弹窗

  3. 横幅类型下拉选择:将单选按钮改为下拉菜单,优化用户体验

  4. 数据字典刷新机制 :添加 refresh 参数支持强制刷新字典数据

  5. 各广告位完整实现:首页轮播、横条、中间横幅、侧边栏横幅、小横幅及 List 页横幅的动态获取

  6. 图片路径问题修复 :统一所有页面的 getImageUrl 函数路径前缀

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

相关推荐
估值探索者9 分钟前
【Python量化技术指标实战 04】布林带通道突破,怎么知道是真突破还是假突破?
java·开发语言·python·接口·股票数据api接口
未秃头的程序猿9 分钟前
同一个商户号,V2 免密代扣和 V3 分账的证书怎么共存?
java·后端·微信
captain37623 分钟前
Spring Boot的配置文件
java·spring boot·后端
captain37625 分钟前
Spring Boot日志
java·数据库·spring boot
Rabitebla1 小时前
【Linux】 权限篇(三):深入理解 umask 位运算与粘滞位的设计思想
java·linux·服务器·c++·笔记·学习
Pin_Qing1 小时前
缓存能活多久,现在是个要算的参数
java·大数据·开发语言·人工智能·程序人生
jyOverQ1 小时前
从零开始学 RocketMQ(四):集群部署、消息存储与高可用【保姆级教程】
java·rocketmq·java-rocketmq
小裕哥略帅1 小时前
行级数据权限控制@DataPermission
java·开发语言
ManageEngineITSM1 小时前
ITSM系统如何与企业微信、钉钉、飞书集成?提升工单处理效率的方法
java·大数据·开发语言·钉钉·企业微信·变更管理
凤凰院凶涛QAQ12 小时前
《Java版数据结构 & 集合类剖析》Map & Set & 反射:“从哈希碰撞到红黑树转换,反射帮你扒开 HashMap 的桶数组看个究竟”
java·数据结构·算法