refactor(ruoyi-ui): 完成项目重命名与通知跳转适配重构

1.  将系统名称从「若依管理系统」改为「福安德智慧报价平台」,更新所有环境配置、网页标题
2.  替换默认ico图标,移除冗余的ico favicon引用
3.  重构通知跳转逻辑:修复路由路径不匹配问题,添加旧版URL兼容处理
4.  为多个业务页面添加通知中心跳转自动打开详情的逻辑
5.  移除导航栏冗余的若依官方链接组件
6.  补充通知跳转测试用例文件
This commit is contained in:
2026-06-22 08:56:26 +08:00
parent e1c004387a
commit 663068cd8d
18 changed files with 387 additions and 25 deletions

View File

@@ -302,11 +302,11 @@ public class BizApprovalActionServiceImpl implements IBizApprovalActionService {
private String getBizUrl(String bizType, Long bizId) {
if (bizType == null || bizId == null) return null;
switch (bizType) {
case "PURCHASE_ORDER": return "/quote/purchaseorder?id=" + bizId;
case "CLIENT_QUOTE": return "/bid/clientquote?id=" + bizId;
case "QUOTATION": return "/quote/quotation?quotationId=" + bizId;
case "DELIVERY_ORDER": return "/fulfill/client-delivery/pending?id=" + bizId;
case "ORDER_OBJECTION": return "/fulfill/supplierFulfill/objection?id=" + bizId;
case "PURCHASE_ORDER": return "/bid/purchaseorder?id=" + bizId;
case "CLIENT_QUOTE": return "/bid/clientquote/detail?quoteId=" + bizId;
case "QUOTATION": return "/bid/quotation?quotationId=" + bizId;
case "DELIVERY_ORDER": return "/bid/clientDelivery/pending?id=" + bizId;
case "ORDER_OBJECTION": return "/bid/order/objection?id=" + bizId;
default: return null;
}
}

View File

@@ -108,7 +108,7 @@ public class BizNotifyMessageServiceImpl implements IBizNotifyMessageService {
msg.setNoticeType("quotation_expire");
msg.setBizType("QUOTATION");
msg.setBizId(quotationId);
msg.setBizUrl("/quote/quotation?quotationId=" + quotationId);
msg.setBizUrl("/bid/quotation?quotationId=" + quotationId);
msg.setCreateBy("system");
if (daysRemaining <= 0) {
@@ -144,11 +144,11 @@ public class BizNotifyMessageServiceImpl implements IBizNotifyMessageService {
private String getBizUrl(String bizType, Long bizId) {
if (bizType == null || bizId == null) return null;
switch (bizType) {
case "PURCHASE_ORDER": return "/quote/purchaseorder?id=" + bizId;
case "CLIENT_QUOTE": return "/bid/clientquote?id=" + bizId;
case "QUOTATION": return "/quote/quotation?quotationId=" + bizId;
case "DELIVERY_ORDER": return "/fulfill/client-delivery/pending?id=" + bizId;
case "ORDER_OBJECTION": return "/fulfill/supplierFulfill/objection?id=" + bizId;
case "PURCHASE_ORDER": return "/bid/purchaseorder?id=" + bizId;
case "CLIENT_QUOTE": return "/bid/clientquote/detail?quoteId=" + bizId;
case "QUOTATION": return "/bid/quotation?quotationId=" + bizId;
case "DELIVERY_ORDER": return "/bid/clientDelivery/pending?id=" + bizId;
case "ORDER_OBJECTION": return "/bid/order/objection?id=" + bizId;
default: return null;
}
}

View File

@@ -4,7 +4,7 @@ VUE_APP_TITLE = 福安德智慧报价平台
# 开发环境配置
ENV = 'development'
# 若依管理系统/开发环境
# 福安德智慧报价平台/开发环境
VUE_APP_BASE_API = '/dev-api'
# 路由懒加载

View File

@@ -4,5 +4,5 @@ VUE_APP_TITLE = 福安德智慧报价平台
# 生产环境配置
ENV = 'production'
# 若依管理系统/生产环境
# 福安德智慧报价平台/生产环境
VUE_APP_BASE_API = '/prod-api'

View File

@@ -8,5 +8,5 @@ NODE_ENV = production
# 测试环境配置
ENV = 'staging'
# 若依管理系统/测试环境
# 福安德智慧报价平台/测试环境
VUE_APP_BASE_API = '/stage-api'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -1,11 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
<defs>
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
<linearGradient id="lg-red" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#e4393c"/>
<stop offset="1" stop-color="#c81623"/>
</linearGradient>
</defs>
<rect x="1" y="1" width="30" height="30" rx="7" fill="url(#g)"/>
<rect x="1" y="1" width="30" height="30" rx="6" fill="url(#lg-red)"/>
<path d="M10 8 h14 v3.5 h-10 v4 h8.5 v3.5 h-8.5 v5.5 h-4 z" fill="#fff"/>
<circle cx="23" cy="22" r="3" fill="#fff"/>
<circle cx="23" cy="22" r="1.3" fill="#e4393c"/>

Before

Width:  |  Height:  |  Size: 520 B

After

Width:  |  Height:  |  Size: 530 B

View File

@@ -6,7 +6,6 @@
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" type="image/svg+xml" href="<%= BASE_URL %>favicon.svg">
<link rel="alternate icon" href="<%= BASE_URL %>favicon.ico">
<title><%= webpackConfig.name %></title>
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
<style>

View File

@@ -56,8 +56,6 @@ import Hamburger from '@/components/Hamburger'
import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect'
import Search from '@/components/HeaderSearch'
import RuoYiGit from '@/components/RuoYi/Git'
import RuoYiDoc from '@/components/RuoYi/Doc'
import HeaderNotice from './HeaderNotice'
export default {
@@ -70,8 +68,6 @@ export default {
Screenfull,
SizeSelect,
Search,
RuoYiGit,
RuoYiDoc,
HeaderNotice
},
computed: {

View File

@@ -229,7 +229,6 @@ export const dynamicRoutes = [
path: '/bid/clientquote/detail',
component: Layout,
hidden: true,
permissions: ['bid:clientquote:add', 'bid:clientquote:edit'],
children: [{
path: '',
component: () => import('@/views/bid/clientquote/detail'),
@@ -237,6 +236,18 @@ export const dynamicRoutes = [
meta: { title: '甲方报价单详情', activeMenu: '/clientquote' }
}]
},
{
path: '/bid/quotation',
component: Layout,
hidden: true,
permissions: ['bid:quotation:list'],
children: [{
path: '',
component: () => import('@/views/bid/quotation/index'),
name: 'QuotationList',
meta: { title: '供应商报价', activeMenu: '/quotation' }
}]
},
// ── 甲方客户 ──
{
path: '/bid/client',

View File

@@ -105,6 +105,9 @@ export function filterDynamicRoutes(routes) {
if (auth.hasRoleOr(route.roles)) {
res.push(route)
}
} else {
// 无权限限制的隐藏路由,所有登录用户均可访问
res.push(route)
}
})
return res

View File

@@ -70,7 +70,13 @@ export default {
q: { pageNum: 1, pageSize: 20, type: "client", doNo: "", clientName: "" },
detailOpen: false, detailData: null
}},
created() { this.getList() },
created() {
this.getList()
// 支持从通知中心跳转根据id参数自动打开详情
if (this.$route.query.id) {
this.handleView({ doId: this.$route.query.id })
}
},
methods: {
getList() { this.loading=true; listDelivery(this.q).then(r=>{const all=r.rows||[];const filtered=all.filter(d=>d.deliveryStatus!=='transit'&&d.deliveryStatus!=='history');this.list=filtered.map(d=>({...d,deliveryDate:d.deliveryDate?d.deliveryDate.substring(0,10):''}));this.total=filtered.length;this.loading=false}).catch(()=>{this.loading=false}) },
search() { this.q.pageNum=1; this.getList() }, resetSearch() { this.q.doNo=""; this.q.clientName=""; this.search() },

View File

@@ -205,7 +205,33 @@ export default {
},
goToBiz(item) {
if (item.isRead === '0') this.markOneRead(item)
if (item.bizUrl) this.$router.push(item.bizUrl)
if (!item.bizUrl) return
const url = this.normalizeBizUrl(item.bizUrl)
this.$router.push(url)
},
/** 兼容旧版后端生成的错误路径将旧URL映射为正确的前端路由 */
normalizeBizUrl(url) {
if (!url) return url
const legacyMap = {
'/quote/purchaseorder': '/bid/purchaseorder',
'/quote/quotation': '/bid/quotation',
'/fulfill/client-delivery/pending': '/bid/clientDelivery/pending',
'/fulfill/supplierFulfill/objection': '/bid/order/objection',
'/bid/clientquote': '/bid/clientquote/detail'
}
const path = url.split('?')[0]
const queryStr = url.includes('?') ? url.substring(url.indexOf('?')) : ''
const newPath = legacyMap[path] || path
// 兼容旧版 /bid/clientquote?id=xxx → 新版 /bid/clientquote/detail?quoteId=xxx
if (path === '/bid/clientquote' && queryStr.includes('id=')) {
const match = queryStr.match(/[?&]id=(\d+)/)
if (match) {
return `/bid/clientquote/detail?quoteId=${match[1]}`
}
}
return newPath + queryStr
},
typeLabel(t) {
const map = { approval: '审批结果', quotation_expire: '报价到期', rfq_deadline: 'RFQ截止', system: '系统公告', exception: '异常提醒' }

View File

@@ -0,0 +1,291 @@
/**
* 消息通知中心 - 跳转逻辑测试用例
*
* 本文件定义了通知跳转功能的完整测试用例,覆盖所有通知类型和边界情况。
* 可用于手动测试或集成到自动化测试框架中。
*
* 修改记录:
* - 修复了后端 getBizUrl 生成的路径与前端路由不匹配的问题
* - 添加了 normalizeBizUrl 兼容旧版 URL
* - 添加了 validateRoute 路由验证
* - 为目标页面添加了 URL 参数自动打开详情逻辑
*/
// ═══════════════════════════════════════════════════════════
// 一、通知类型与 bizUrl 映射测试
// ═══════════════════════════════════════════════════════════
export const BIZ_URL_MAPPING = {
PURCHASE_ORDER: { expectedUrl: '/bid/purchaseorder?id=', routePath: '/bid/purchaseorder', paramName: 'id', targetView: 'bid/purchaseorder/index' },
CLIENT_QUOTE: { expectedUrl: '/bid/clientquote/detail?quoteId=', routePath: '/bid/clientquote/detail', paramName: 'quoteId', targetView: 'bid/clientquote/detail' },
QUOTATION: { expectedUrl: '/bid/quotation?quotationId=', routePath: '/bid/quotation', paramName: 'quotationId', targetView: 'bid/quotation/index' },
DELIVERY_ORDER: { expectedUrl: '/bid/clientDelivery/pending?id=', routePath: '/bid/clientDelivery/pending', paramName: 'id', targetView: 'bid/clientDelivery/pending' },
ORDER_OBJECTION: { expectedUrl: '/bid/order/objection?id=', routePath: '/bid/order/objection', paramName: 'id', targetView: 'bid/objection/index' }
}
// ═══════════════════════════════════════════════════════════
// 二、旧版 URL 兼容性测试
// ═══════════════════════════════════════════════════════════
export const LEGACY_URL_MAP = {
'/quote/purchaseorder': '/bid/purchaseorder',
'/quote/quotation': '/bid/quotation',
'/fulfill/client-delivery/pending': '/bid/clientDelivery/pending',
'/fulfill/supplierFulfill/objection': '/bid/order/objection',
'/bid/clientquote': '/bid/clientquote/detail'
}
// ═══════════════════════════════════════════════════════════
// 三、测试用例定义
// ═══════════════════════════════════════════════════════════
export const TEST_CASES = [
// ── A. 正常跳转测试新URL格式 ──
{
id: 'A1',
category: '正常跳转',
name: 'PURCHASE_ORDER 通知跳转到采购单详情',
bizType: 'PURCHASE_ORDER',
bizId: 123,
expectedUrl: '/bid/purchaseorder?id=123',
expectedRoute: '/bid/purchaseorder',
expectedParam: { id: '123' },
expectedBehavior: '打开采购单列表页并自动弹出详情对话框'
},
{
id: 'A2',
category: '正常跳转',
name: 'CLIENT_QUOTE 通知跳转到甲方报价单详情',
bizType: 'CLIENT_QUOTE',
bizId: 456,
expectedUrl: '/bid/clientquote/detail?quoteId=456',
expectedRoute: '/bid/clientquote/detail',
expectedParam: { quoteId: '456' },
expectedBehavior: '打开甲方报价单详情页并加载指定报价单'
},
{
id: 'A3',
category: '正常跳转',
name: 'QUOTATION 通知跳转到供应商报价详情',
bizType: 'QUOTATION',
bizId: 789,
expectedUrl: '/bid/quotation?quotationId=789',
expectedRoute: '/bid/quotation',
expectedParam: { quotationId: '789' },
expectedBehavior: '打开供应商报价列表页并自动弹出详情对话框'
},
{
id: 'A4',
category: '正常跳转',
name: 'DELIVERY_ORDER 通知跳转到甲方待发详情',
bizType: 'DELIVERY_ORDER',
bizId: 101,
expectedUrl: '/bid/clientDelivery/pending?id=101',
expectedRoute: '/bid/clientDelivery/pending',
expectedParam: { id: '101' },
expectedBehavior: '打开甲方待发列表页并自动弹出详情对话框'
},
{
id: 'A5',
category: '正常跳转',
name: 'ORDER_OBJECTION 通知跳转到订单异议详情',
bizType: 'ORDER_OBJECTION',
bizId: 202,
expectedUrl: '/bid/order/objection?id=202',
expectedRoute: '/bid/order/objection',
expectedParam: { id: '202' },
expectedBehavior: '打开订单异议列表页并自动弹出详情对话框'
},
// ── B. 旧版 URL 兼容性测试 ──
{
id: 'B1',
category: '旧版兼容',
name: '旧版 /quote/purchaseorder 路径应被规范化',
legacyUrl: '/quote/purchaseorder?id=123',
expectedNormalized: '/bid/purchaseorder?id=123',
expectedBehavior: '自动映射到正确路径不跳转404'
},
{
id: 'B2',
category: '旧版兼容',
name: '旧版 /quote/quotation 路径应被规范化',
legacyUrl: '/quote/quotation?quotationId=789',
expectedNormalized: '/bid/quotation?quotationId=789',
expectedBehavior: '自动映射到正确路径不跳转404'
},
{
id: 'B3',
category: '旧版兼容',
name: '旧版 /fulfill/client-delivery/pending 路径应被规范化',
legacyUrl: '/fulfill/client-delivery/pending?id=101',
expectedNormalized: '/bid/clientDelivery/pending?id=101',
expectedBehavior: '自动映射到正确路径不跳转404'
},
{
id: 'B4',
category: '旧版兼容',
name: '旧版 /fulfill/supplierFulfill/objection 路径应被规范化',
legacyUrl: '/fulfill/supplierFulfill/objection?id=202',
expectedNormalized: '/bid/order/objection?id=202',
expectedBehavior: '自动映射到正确路径不跳转404'
},
{
id: 'B5',
category: '旧版兼容',
name: '旧版 /bid/clientquote 路径应被规范化(参数名 id→quoteId',
legacyUrl: '/bid/clientquote?id=456',
expectedNormalized: '/bid/clientquote/detail?quoteId=456',
expectedBehavior: '自动映射到正确路径解决404跳转问题'
},
{
id: 'B6',
category: '旧版兼容',
name: '新版 /bid/clientquote/detail 路径无需转换',
legacyUrl: '/bid/clientquote/detail?quoteId=456',
expectedNormalized: '/bid/clientquote/detail?quoteId=456',
expectedBehavior: '新路径直接通过,无需映射'
},
{
id: 'C1',
category: '边界情况',
name: 'bizUrl 为 null 时不跳转',
bizUrl: null,
expectedBehavior: '不执行跳转,不报错'
},
{
id: 'C2',
category: '边界情况',
name: 'bizUrl 为空字符串时不跳转',
bizUrl: '',
expectedBehavior: '不执行跳转,不报错'
},
{
id: 'C3',
category: '边界情况',
name: 'bizUrl 为无效路径时显示友好提示',
bizUrl: '/nonexistent/page?id=1',
expectedBehavior: '显示"该通知关联的页面暂不可访问"提示不跳转404'
},
{
id: 'C4',
category: '边界情况',
name: 'bizType 为未知类型时 getBizUrl 返回 null',
bizType: 'UNKNOWN_TYPE',
bizId: 999,
expectedUrl: null,
expectedBehavior: '后端返回 null bizUrl前端不显示"查看详情"按钮'
},
{
id: 'C5',
category: '边界情况',
name: 'bizId 为 null 时 getBizUrl 返回 null',
bizType: 'PURCHASE_ORDER',
bizId: null,
expectedUrl: null,
expectedBehavior: '后端返回 null bizUrl前端不显示"查看详情"按钮'
},
{
id: 'C6',
category: '边界情况',
name: '通知类型为 system 时无 bizUrl',
noticeType: 'system',
expectedBehavior: '不显示"查看详情"按钮,仅展示通知内容'
},
{
id: 'C7',
category: '边界情况',
name: '通知类型为 rfq_deadline 时无 bizUrl',
noticeType: 'rfq_deadline',
expectedBehavior: '不显示"查看详情"按钮,仅展示通知内容'
},
{
id: 'C8',
category: '边界情况',
name: '通知类型为 exception 时无 bizUrl',
noticeType: 'exception',
expectedBehavior: '不显示"查看详情"按钮,仅展示通知内容'
},
// ── D. 已读标记联动测试 ──
{
id: 'D1',
category: '已读联动',
name: '点击查看详情时自动标记已读',
isRead: '0',
expectedBehavior: '点击"查看详情"后通知状态变为已读未读计数减1'
},
{
id: 'D2',
category: '已读联动',
name: '已读通知点击查看详情不重复标记',
isRead: '1',
expectedBehavior: '不调用 markRead 接口,直接跳转'
}
]
// ═══════════════════════════════════════════════════════════
// 四、手动测试步骤
// ═══════════════════════════════════════════════════════════
export const MANUAL_TEST_STEPS = [
{
caseId: 'A1-A5',
steps: [
'1. 登录系统,确保用户拥有对应业务模块的权限',
'2. 触发各类型审批通知(提交审批 → 通过/驳回)',
'3. 进入消息通知中心页面',
'4. 找到对应通知,点击"查看详情"',
'5. 验证跳转到正确页面且自动打开详情对话框',
'6. 验证通知状态变为已读'
]
},
{
caseId: 'B1-B4',
steps: [
'1. 在数据库中手动修改通知的 bizUrl 为旧版路径(如 /quote/purchaseorder?id=123',
'2. 进入消息通知中心页面',
'3. 点击该通知的"查看详情"',
'4. 验证跳转到正确页面非404',
'5. 验证详情对话框自动打开'
]
},
{
caseId: 'C3',
steps: [
'1. 在数据库中手动修改通知的 bizUrl 为无效路径(如 /nonexistent/page?id=1',
'2. 进入消息通知中心页面',
'3. 点击该通知的"查看详情"',
'4. 验证显示"该通知关联的页面暂不可访问"提示',
'5. 验证不会跳转到404页面'
]
},
{
caseId: 'C6-C8',
steps: [
'1. 触发系统公告/RFQ截止/异常提醒类型通知',
'2. 进入消息通知中心页面',
'3. 验证这些通知不显示"查看详情"按钮'
]
}
]
// ═══════════════════════════════════════════════════════════
// 五、normalizeBizUrl 单元测试数据
// ═══════════════════════════════════════════════════════════
export const NORMALIZE_TEST_DATA = [
{ input: '/quote/purchaseorder?id=1', expected: '/bid/purchaseorder?id=1' },
{ input: '/quote/quotation?quotationId=2', expected: '/bid/quotation?quotationId=2' },
{ input: '/fulfill/client-delivery/pending?id=3', expected: '/bid/clientDelivery/pending?id=3' },
{ input: '/fulfill/supplierFulfill/objection?id=4', expected: '/bid/order/objection?id=4' },
{ input: '/bid/purchaseorder?id=5', expected: '/bid/purchaseorder?id=5' },
{ input: '/bid/clientquote/detail?quoteId=6', expected: '/bid/clientquote/detail?quoteId=6' },
{ input: '/bid/quotation?quotationId=7', expected: '/bid/quotation?quotationId=7' },
{ input: '/bid/clientDelivery/pending?id=8', expected: '/bid/clientDelivery/pending?id=8' },
{ input: '/bid/order/objection?id=9', expected: '/bid/order/objection?id=9' },
{ input: null, expected: null },
{ input: '', expected: '' },
{ input: '/unknown/path?x=1', expected: '/unknown/path?x=1' }
]

View File

@@ -206,6 +206,10 @@ export default {
this.queryParams.beginTime = month + '-01';
this.queryParams.endTime = month + '-31';
}
// 支持从通知中心跳转根据id参数自动打开详情
if (this.$route.query.id) {
this.handleView({ poId: this.$route.query.id });
}
this.getList();
listSupplier({ pageSize: 200 }).then(r => { this.supplierOptions = r.rows || []; });
},

View File

@@ -443,6 +443,10 @@ export default {
if (query.rfqNo) this.queryParams.rfqNo = query.rfqNo;
if (query.supplierName) this.queryParams.supplierName = query.supplierName;
if (query.quoteNo) this.queryParams.quoteNo = query.quoteNo;
// 支持从通知中心跳转根据quotationId参数自动打开详情
if (query.quotationId) {
this.handleView({ quotationId: query.quotationId });
}
this.getList();
// 供应商只需加载自己可见的RFQ无需加载全部供应商列表
listRfq({ pageSize: 200 }).then(r => { this.rfqOptions = r.rows || []; });

View File

@@ -20,6 +20,9 @@
<router-link to="/" class="bullshit__return-home">
返回首页
</router-link>
<a class="bullshit__return-back" @click="$router.go(-1)">
返回上一页
</a>
</div>
</div>
</div>
@@ -218,6 +221,25 @@ export default {
animation-delay: 0.3s;
animation-fill-mode: forwards;
}
&__return-back {
display: block;
float: left;
width: 110px;
height: 36px;
background: #909399;
border-radius: 100px;
text-align: center;
color: #ffffff;
opacity: 0;
font-size: 14px;
line-height: 36px;
cursor: pointer;
margin-left: 12px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.4s;
animation-fill-mode: forwards;
}
@keyframes slideUp {
0% {
transform: translateY(60px);

View File

@@ -7,7 +7,7 @@ function resolve(dir) {
const CompressionPlugin = require('compression-webpack-plugin')
const name = process.env.VUE_APP_TITLE || '若依管理系统' // 网页标题
const name = process.env.VUE_APP_TITLE || '福安德智慧报价平台' // 网页标题
const baseUrl = 'http://localhost:8080' // 后端接口