提交 9c0c3e2a authored 作者: lidongxu's avatar lidongxu

Merge branch 'ceshi_quickbi'

<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>
......@@ -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'
......
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
......@@ -101,6 +101,7 @@ const rightChange = (val) => {
height: 100%;
.el-card__header {
font-size: 18px;
.custom {
float: right;
padding: 3px 0;
......
......@@ -77,7 +77,7 @@ const getLogoTextColor = computed(() => {
// width: 32px;
// height: 32px;
width: 120px;
height: 60px;
height: 68px;
vertical-align: middle;
// margin-right: 12px;
}
......
......@@ -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
<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">
......
......@@ -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;
}
}
......
......@@ -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,
......
......@@ -43,6 +43,9 @@ const modules = ref([]); // 替换原有的静态modules
// 观察路由挂载后事件
// 格式化路由为菜单所需结构
const formatRoutesToModules = (routes) => {
// 把首页去除掉
// 把首页去除掉
routes = routes.filter(route => route.path !== '');
// 根据实际路由结构转换,这里假设需要提取一级路由作为模块
modules.value = routes.map(o => {
return {
......
// 移动端 静态路由
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',// 菜单页
......
<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
<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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论