Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
W
wangxiaolu-sfa-ui
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
sfa
wangxiaolu-sfa-ui
Commits
9c0c3e2a
提交
9c0c3e2a
authored
12月 13, 2025
作者:
lidongxu
浏览文件
操作
浏览文件
下载
差异文件
Merge branch 'ceshi_quickbi'
上级
78c68066
c58c3e87
隐藏空白字符变更
内嵌
并排
正在显示
13 个修改的文件
包含
360 行增加
和
117 行删除
+360
-117
App.vue
src/App.vue
+26
-3
index.js
src/api/index.js
+1
-0
index.js
src/api/quickbi/index.js
+18
-0
index.vue
src/components/CommonMenu/index.vue
+1
-0
Logo.vue
src/layout/components/Sidebar/Logo.vue
+1
-1
ScrollPane.vue
src/layout/components/TagsView/ScrollPane.vue
+7
-1
index.vue
src/layout/components/TagsView/index.vue
+109
-77
index.vue
src/layout/index.vue
+11
-1
index.js
src/router/index.js
+38
-29
index.vue
src/views/mobile/pages/menu/index.vue
+3
-0
index.js
src/views/mobile/router/index.js
+0
-5
index.vue
src/views/quickbi/list/index.vue
+121
-0
index.vue
src/views/quickbi/preview/index.vue
+24
-0
没有找到文件。
src/App.vue
浏览文件 @
9c0c3e2a
<
template
>
<template
v-if=
"
!isInWhiteList
"
>
<template
v-if=
"
shouldShowWatermark
"
>
<el-watermark
:font=
"font"
:content=
"content"
class=
"wm-class"
>
class=
"wm-class"
:class=
"
{'wm-mobile-class': isMobile}">
<router-view
/>
</el-watermark>
</
template
>
...
...
@@ -16,13 +17,30 @@ import useSettingsStore from '@/store/modules/settings'
import
{
handleThemeStyle
}
from
'@/utils/theme'
import
useUserStore
from
'@/store/modules/user'
import
{
isWhiteList
}
from
'@/permission'
import
{
isMobile
}
from
'@/utils'
const
route
=
useRoute
()
/*************** 水印相关 *************** */
const
isInWhiteList
=
computed
(()
=>
isWhiteList
(
route
.
path
))
const
isInFeishu
=
computed
(()
=>
{
const
userAgent
=
navigator
.
userAgent
.
toLowerCase
()
// 检测飞书用户代理标识
return
userAgent
.
includes
(
'lark'
)
||
userAgent
.
includes
(
'feishu'
)
})
const
shouldShowWatermark
=
computed
(()
=>
{
// 既不在飞书也不在白名单则显示水印
return
!
isInWhiteList
.
value
&&
!
isInFeishu
.
value
})
// 水印显示内容
const
content
=
computed
(()
=>
{
return
useUserStore
().
userInfo
.
nickName
+
' '
+
useUserStore
().
userInfo
.
phonenumber
?.
substring
(
7
)
})
// 水印字体,暗黑模式切换水印文字颜色
const
isDark
=
computed
(()
=>
{
return
useSettingsStore
().
isDark
})
...
...
@@ -51,8 +69,13 @@ onMounted(() => {
<
style
scoped
lang=
"scss"
>
.wm-class
{
.wm-class
{
min-height
:
100%
;
height
:
100%
;
}
.wm-mobile-class
{
min-height
:
100%
;
height
:
auto
;
}
</
style
>
src/api/index.js
浏览文件 @
9c0c3e2a
...
...
@@ -30,6 +30,7 @@ export * from './promotion/display_schedule'
export
*
from
'./promotion/display_schedule_dashboard'
export
*
from
'./promotion/plan'
export
*
from
'./promotion/task'
export
*
from
'./quickbi'
export
*
from
'./scm/logistics_receipt'
export
*
from
'./system/dict/data'
export
*
from
'./system/dict/type'
...
...
src/api/quickbi/index.js
0 → 100644
浏览文件 @
9c0c3e2a
import
request
from
'@/utils/request'
// 获取 quickBI 列表
export
const
getQuickbiList
=
(
params
)
=>
{
return
request
({
url
:
'/report/extra/quickbi/embed/list'
,
params
})
}
// 获取 ticketId
export
const
getTicketId
=
(
params
)
=>
{
return
request
({
url
:
'/report/extra/quickbi/embed/accessTicket'
,
params
})
}
\ No newline at end of file
src/components/CommonMenu/index.vue
浏览文件 @
9c0c3e2a
...
...
@@ -101,6 +101,7 @@ const rightChange = (val) => {
height
:
100%
;
.el-card__header
{
font-size
:
18px
;
.custom
{
float
:
right
;
padding
:
3px
0
;
...
...
src/layout/components/Sidebar/Logo.vue
浏览文件 @
9c0c3e2a
...
...
@@ -77,7 +77,7 @@ const getLogoTextColor = computed(() => {
// width: 32px;
// height: 32px;
width
:
120px
;
height
:
6
0
px
;
height
:
6
8
px
;
vertical-align
:
middle
;
// margin-right: 12px;
}
...
...
src/layout/components/TagsView/ScrollPane.vue
浏览文件 @
9c0c3e2a
...
...
@@ -104,7 +104,7 @@ defineExpose({
}
:deep
(
.el-scrollbar__wrap
)
{
height
:
34px
!
important
;
//
height: 34px !important;
/* 与tags-view-container高度保持一致 */
overflow-y
:
hidden
!
important
;
/* 明确禁用垂直滚动 */
...
...
@@ -120,6 +120,11 @@ defineExpose({
:deep
(
.el-scrollbar__wrap
)
{
height
:
39px
;
display
:
flex
;
align-items
:
center
;
.el-scrollbar__view
{
display
:
flex
;
}
}
}
</
style
>
\ No newline at end of file
src/layout/components/TagsView/index.vue
浏览文件 @
9c0c3e2a
<
template
>
<div
id=
"tags-view-container"
class=
"tags-view-container"
>
<scroll-pane
ref=
"scrollPaneRef"
class=
"tags-view-wrapper"
@
scroll=
"handleScroll"
>
<router-link
v-for=
"tag in visitedViews"
<div
id=
"tags-view-container"
class=
"tags-view-container"
>
<scroll-pane
ref=
"scrollPaneRef"
class=
"tags-view-wrapper"
@
scroll=
"handleScroll"
>
<router-link
v-for=
"tag in visitedViews"
:key=
"tag.path"
:data-path=
"tag.path"
:class=
"isActive(tag) ? 'active' : ''"
...
...
@@ -10,28 +12,34 @@
class="tags-view-item"
:style="activeStyle(tag)"
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
@contextmenu.prevent="openMenu(tag, $event)"
>
@contextmenu.prevent="openMenu(tag, $event)">
{{
tag
.
title
}}
<span
v-if=
"!isAffix(tag)"
@
click
.
prevent
.
stop=
"closeSelectedTag(tag)"
>
<close
class=
"el-icon-close"
style=
"width: 1em; height: 1em;vertical-align: middle;"
/>
<span
v-if=
"!isAffix(tag)"
@
click
.
prevent
.
stop=
"closeSelectedTag(tag)"
>
<close
class=
"el-icon-close"
style=
"width: 1em; height: 1em;vertical-align: middle;"
/>
</span>
</router-link>
</scroll-pane>
<ul
v-show=
"visible"
:style=
"
{ left: left + 'px', top: top + 'px' }" class="contextmenu">
<ul
v-show=
"visible"
:style=
"
{ left: left + 'px', top: top + 'px' }"
class="contextmenu">
<li
@
click=
"refreshSelectedTag(selectedTag)"
>
<refresh-right
style=
"width: 1em; height: 1em;"
/>
刷新页面
</li>
<li
v-if=
"!isAffix(selectedTag)"
@
click=
"closeSelectedTag(selectedTag)"
>
<li
v-if=
"!isAffix(selectedTag)"
@
click=
"closeSelectedTag(selectedTag)"
>
<close
style=
"width: 1em; height: 1em;"
/>
关闭当前
</li>
<li
@
click=
"closeOthersTags"
>
<circle-close
style=
"width: 1em; height: 1em;"
/>
关闭其他
</li>
<li
v-if=
"!isFirstView()"
@
click=
"closeLeftTags"
>
<li
v-if=
"!isFirstView()"
@
click=
"closeLeftTags"
>
<back
style=
"width: 1em; height: 1em;"
/>
关闭左侧
</li>
<li
v-if=
"!isLastView()"
@
click=
"closeRightTags"
>
<li
v-if=
"!isLastView()"
@
click=
"closeRightTags"
>
<right
style=
"width: 1em; height: 1em;"
/>
关闭右侧
</li>
<li
@
click=
"closeAllTags(selectedTag)"
>
...
...
@@ -141,7 +149,7 @@ function initTags() {
for
(
const
tag
of
res
)
{
// Must have tag name
if
(
tag
.
name
)
{
useTagsViewStore
().
addVisitedView
(
tag
)
useTagsViewStore
().
addVisitedView
(
tag
)
}
}
}
...
...
@@ -257,81 +265,105 @@ function handleScroll() {
}
</
script
>
<
style
lang=
"scss"
scoped
>
.tags-view-container
{
height
:
34px
;
width
:
100%
;
background
:
var
(
--
tags-bg
,
#fff
);
border-bottom
:
1px
solid
var
(
--
tags-item-border
,
#d8dce5
);
box-shadow
:
0
1px
3px
0
rgba
(
0
,
0
,
0
,
.12
)
,
0
0
3px
0
rgba
(
0
,
0
,
0
,
.04
);
.tags-view-wrapper
{
.tags-view-item
{
display
:
inline-block
;
position
:
relative
;
cursor
:
pointer
;
height
:
26px
;
line-height
:
26px
;
border
:
1px
solid
var
(
--
tags-item-border
,
#d8dce5
);
color
:
var
(
--
tags-item-text
,
#495060
);
background
:
var
(
--
tags-item-bg
,
#fff
);
padding
:
0
8px
;
font-size
:
12px
;
margin-left
:
5px
;
margin-top
:
4px
;
&
:first-of-type
{
margin-left
:
15px
;
<
style
lang=
"scss"
scoped
>
.tags-view-container
{
height
:
34px
;
width
:
100%
;
background
:
var
(
--
tags-bg
,
#fff
);
border-bottom
:
1px
solid
var
(
--
tags-item-border
,
#d8dce5
);
box-shadow
:
0
1px
3px
0
rgba
(
0
,
0
,
0
,
.12
)
,
0
0
3px
0
rgba
(
0
,
0
,
0
,
.04
);
// 添加移动端适配
@media
(
max-width
:
768px
)
{
height
:
40px
;
// 移动端适当增加高度
.tags-view-wrapper
{
.tags-view-item
{
height
:
32px
;
line-height
:
32px
;
font-size
:
13px
;
padding
:
0
6px
;
margin-top
:
4px
;
&
:first-of-type
{
margin-left
:
10px
;
}
&
:last-of-type
{
margin-right
:
10px
;
}
}
}
}
&
:last-of-type
{
margin-right
:
15px
;
}
.tags-view-wrapper
{
.tags-view-item
{
display
:
inline-block
;
position
:
relative
;
cursor
:
pointer
;
height
:
26px
;
line-height
:
26px
;
border
:
1px
solid
var
(
--
tags-item-border
,
#d8dce5
);
color
:
var
(
--
tags-item-text
,
#495060
);
background
:
var
(
--
tags-item-bg
,
#fff
);
padding
:
0
8px
;
font-size
:
12px
;
margin-left
:
5px
;
margin-top
:
4px
;
&
:first-of-type
{
margin-left
:
15px
;
}
&
.active
{
background-color
:
#42b983
;
color
:
#fff
;
border-color
:
#42b983
;
&
:
:
before
{
content
:
''
;
background
:
#fff
;
display
:
inline-block
;
width
:
8px
;
height
:
8px
;
border-radius
:
50%
;
position
:
relative
;
margin-right
:
5px
;
&
:last-of-type
{
margin-right
:
15px
;
}
&
.active
{
background-color
:
#42b983
;
color
:
#fff
;
border-color
:
#42b983
;
&
:
:
before
{
content
:
''
;
background
:
#fff
;
display
:
inline-block
;
width
:
8px
;
height
:
8px
;
border-radius
:
50%
;
position
:
relative
;
margin-right
:
5px
;
}
}
}
}
}
.contextmenu
{
margin
:
0
;
background
:
var
(
--
el-bg-color-overlay
,
#fff
);
z-index
:
3000
;
position
:
absolute
;
list-style-type
:
none
;
padding
:
5px
0
;
border-radius
:
4px
;
font-size
:
12px
;
font-weight
:
400
;
color
:
var
(
--
tags-item-text
,
#333
);
box-shadow
:
2px
2px
3px
0
rgba
(
0
,
0
,
0
,
.3
);
border
:
1px
solid
var
(
--
el-border-color-light
,
#e4e7ed
);
li
{
.contextmenu
{
margin
:
0
;
padding
:
7px
16px
;
cursor
:
pointer
;
&
:hover
{
background
:
var
(
--
tags-item-hover
,
#eee
);
background
:
var
(
--
el-bg-color-overlay
,
#fff
);
z-index
:
3000
;
position
:
absolute
;
list-style-type
:
none
;
padding
:
5px
0
;
border-radius
:
4px
;
font-size
:
12px
;
font-weight
:
400
;
color
:
var
(
--
tags-item-text
,
#333
);
box-shadow
:
2px
2px
3px
0
rgba
(
0
,
0
,
0
,
.3
);
border
:
1px
solid
var
(
--
el-border-color-light
,
#e4e7ed
);
li
{
margin
:
0
;
padding
:
7px
16px
;
cursor
:
pointer
;
&
:hover
{
background
:
var
(
--
tags-item-hover
,
#eee
);
}
}
}
}
}
</
style
>
<
style
lang=
"scss"
>
...
...
src/layout/index.vue
浏览文件 @
9c0c3e2a
...
...
@@ -106,7 +106,7 @@ function setLayout() {
transition
:
width
0
.28s
;
}
.app-main
{
.app-main
{
// 头部固定菜单高度 50px
height
:
calc
(
100vh
-
50px
);
}
...
...
@@ -122,6 +122,11 @@ function setLayout() {
.fixed-header
+
.app-main
{
height
:
100vh
;
padding-top
:
84px
;
// 移动端适配
@media
(
max-width
:
768px
)
{
padding-top
:
90px
;
// 调整为50px导航栏 + 40px TagsView
}
}
}
...
...
@@ -136,6 +141,11 @@ function setLayout() {
.mobile
.fixed-header
{
width
:
100%
;
// 确保移动端TagsView正确显示
.tags-view-container
{
height
:
40px
;
}
}
...
...
src/router/index.js
浏览文件 @
9c0c3e2a
...
...
@@ -2,7 +2,7 @@ import { createWebHashHistory, createRouter, createWebHistory } from 'vue-router
/* Layout */
import
Layout
from
'@/layout'
import
{
isMobile
}
from
'@/utils'
import
{
constantMobileRoutes
}
from
'@/views/mobile/router/index.js'
import
{
constantMobileRoutes
}
from
'@/views/mobile/router/index.js'
// 移动端路由表
/**
* Note: 路由配置项
...
...
@@ -26,21 +26,42 @@ import { constantMobileRoutes } from '@/views/mobile/router/index.js'
}
*/
// 使用的公共静态路由
export
let
constantRoutes
=
[{
path
:
'/report/jmreport/view/:id'
,
component
:
()
=>
import
(
'@/views/jimureport/entry.vue'
),
hidden
:
true
},
{
path
:
'/report/jmreport/index/:id'
,
component
:
()
=>
import
(
'@/views/jimureport/entry.vue'
),
hidden
:
true
},
{
path
:
'/report/jmreport/shareView/:id'
,
component
:
()
=>
import
(
'@/views/jimureport/entry.vue'
),
hidden
:
true
}]
export
let
constantRoutes
=
[
{
path
:
''
,
component
:
Layout
,
redirect
:
'/index'
,
children
:
[
{
path
:
'/index'
,
component
:
()
=>
import
(
'@/views/index'
),
name
:
'Index'
,
meta
:
{
title
:
'首页'
,
icon
:
'dashboard'
,
affix
:
true
}
}
]
},
{
path
:
'/report/jmreport/view/:id'
,
component
:
()
=>
import
(
'@/views/jimureport/entry.vue'
),
hidden
:
true
},
{
path
:
'/report/jmreport/index/:id'
,
component
:
()
=>
import
(
'@/views/jimureport/entry.vue'
),
hidden
:
true
},
{
path
:
'/report/jmreport/shareView/:id'
,
component
:
()
=>
import
(
'@/views/jimureport/entry.vue'
),
hidden
:
true
},
// QuickBI 预览
{
path
:
'/report/quickbi/preview'
,
component
:
()
=>
import
(
'@/views/quickbi/preview/index.vue'
),
hidden
:
true
},
]
// PC端 静态路由
export
const
constantPCRoutes
=
[
...
constantRoutes
,
...
...
@@ -70,19 +91,7 @@ export const constantPCRoutes = [
component
:
()
=>
import
(
'@/views/error/401'
),
hidden
:
true
},
{
path
:
''
,
component
:
Layout
,
redirect
:
'/index'
,
children
:
[
{
path
:
'/index'
,
component
:
()
=>
import
(
'@/views/index'
),
name
:
'Index'
,
meta
:
{
title
:
'首页'
,
icon
:
'dashboard'
,
affix
:
true
}
}
]
},
{
path
:
'/user'
,
component
:
Layout
,
...
...
src/views/mobile/pages/menu/index.vue
浏览文件 @
9c0c3e2a
...
...
@@ -43,6 +43,9 @@ const modules = ref([]); // 替换原有的静态modules
// 观察路由挂载后事件
// 格式化路由为菜单所需结构
const
formatRoutesToModules
=
(
routes
)
=>
{
// 把首页去除掉
// 把首页去除掉
routes
=
routes
.
filter
(
route
=>
route
.
path
!==
''
);
// 根据实际路由结构转换,这里假设需要提取一级路由作为模块
modules
.
value
=
routes
.
map
(
o
=>
{
return
{
...
...
src/views/mobile/router/index.js
浏览文件 @
9c0c3e2a
// 移动端 静态路由
export
const
constantMobileRoutes
=
[
{
path
:
'/index'
,
redirect
:
'/menu'
},
{
path
:
'/login'
,
component
:
()
=>
import
(
'@/views/login'
),
...
...
@@ -18,7 +14,6 @@ export const constantMobileRoutes = [
{
path
:
'/'
,
component
:
()
=>
import
(
'@/views/mobile/index'
),
redirect
:
'/menu'
,
children
:
[
{
path
:
'menu'
,
// 菜单页
...
...
src/views/quickbi/list/index.vue
0 → 100644
浏览文件 @
9c0c3e2a
<
template
>
<div
class=
"app-container"
>
<div
class=
"container"
>
<!-- 表格数据 -->
<el-table
v-loading=
"loading"
:data=
"reportList"
>
<el-table-column
label=
"报表 ID"
key=
"id"
prop=
"id"
align=
"left"
width=
"100"
sortable
/>
<el-table-column
label=
"报表名称"
key=
"name"
prop=
"name"
align=
"left"
sortable
>
<template
#
default=
"
{ row }">
<div
style=
"display: flex; align-items: center"
>
<svg-icon
icon-class=
"bg-document"
></svg-icon>
<span
style=
"margin-left: 10px"
>
{{
row
.
name
}}
</span>
</div>
</
template
>
</el-table-column>
<!-- 操作预览 -->
<el-table-column
label=
"操作"
key=
"operation"
align=
"center"
>
<
template
#
default=
"scope"
>
<el-button
text
type=
"primary"
@
click=
"previewClick(scope)"
>
预览
</el-button>
</
template
>
</el-table-column>
</el-table>
</div>
</div>
</template>
<
script
setup
name=
"Role"
>
import
{
getQuickbiList
}
from
"@/api"
import
useUserStore
from
'@/store/modules/user'
const
{
proxy
}
=
getCurrentInstance
()
const
router
=
useRouter
()
const
userStore
=
useUserStore
()
const
reportBaseDomain
=
import
.
meta
.
env
.
VITE_APP_REPORT_URL
// 基础域名
const
reportViewURL
=
import
.
meta
.
env
.
VITE_APP_REPORT_PREVIEW_URL
// 预览
const
reportShareViewURL
=
import
.
meta
.
env
.
VITE_APP_REPORT_SHARE_PREVIEW_URL
// 分享预览
const
reportEditURL
=
import
.
meta
.
env
.
VITE_APP_REPORT_EDIT_URL
// 新增/编辑
/*************** 报表列表部分 ****************/
// 报表搜索
const
queryParams
=
reactive
({
name
:
undefined
,
// 报表名称
category
:
undefined
,
// 报表类目
pageNum
:
1
,
pageSize
:
10
})
// 获取报表列表
const
loading
=
ref
(
true
)
const
reportList
=
ref
([])
// 查询报表列表
function
getReportList
()
{
loading
.
value
=
true
getQuickbiList
(
queryParams
).
then
(
response
=>
{
console
.
log
(
response
)
reportList
.
value
=
response
.
data
loading
.
value
=
false
})
}
getReportList
()
const
previewClick
=
(
scope
)
=>
{
const
row
=
scope
.
row
router
.
push
({
path
:
`/report/quickbi/preview`
,
query
:
{
id
:
row
.
pageId
}
})
}
</
script
>
<
style
scoped
lang=
"scss"
>
.app-container
{
overflow-x
:
auto
;
}
.container
{
min-width
:
600px
;
/* 设置最小宽度确保表格可以横向滚动 */
}
/* 移动端适配 */
@media
(
max-width
:
768px
)
{
.app-container
{
overflow-x
:
scroll
;
-webkit-overflow-scrolling
:
touch
;
/* iOS 平滑滚动 */
}
.container
{
min-width
:
500px
;
/* 移动端需要更大的最小宽度 */
}
/* 表格样式调整 */
::v-deep
(
.el-table
)
{
.el-table__body-wrapper
{
overflow-x
:
auto
;
}
}
}
</
style
>
\ No newline at end of file
src/views/quickbi/preview/index.vue
0 → 100644
浏览文件 @
9c0c3e2a
<
template
>
<iframe
:src=
"iframeURL"
frameborder=
"0"
width=
"100%"
height=
"800px"
></iframe>
</
template
>
<
script
setup
>
import
{
useRoute
}
from
'vue-router'
import
{
getTicketId
}
from
'@/api'
import
{
onMounted
}
from
'vue'
const
route
=
useRoute
()
const
ticket
=
ref
(
''
)
const
iframeURL
=
ref
(
''
)
onMounted
(()
=>
{
getTicketId
({
pageId
:
route
.
query
.
id
}).
then
(
response
=>
{
ticket
.
value
=
response
.
data
.
accessTicket
iframeURL
.
value
=
`https://bi.aliyun.com/token3rd/dashboard/view/pc.htm?pageId=
${
route
.
query
.
id
}
&embedDisplayParam=%7B%22showTitle%22%3Afalse%7D&accessTicket=
${
ticket
.
value
}
`
})
})
</
script
>
<
style
scoped
></
style
>
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论