1. 提交代码
This commit is contained in:
@@ -14,6 +14,13 @@ export interface ExecuteProcedureParams {
|
|||||||
rq_e?: string // 结束日期,格式:YYYY-MM-DD(可选)
|
rq_e?: string // 结束日期,格式:YYYY-MM-DD(可选)
|
||||||
p?: string // 密码(可选)
|
p?: string // 密码(可选)
|
||||||
username?: string // 用户名(可选)
|
username?: string // 用户名(可选)
|
||||||
|
/** 存储过程入参 @auth(VARCHAR(MAX),如 YDY_GET_TAG 第一个参数) */
|
||||||
|
auth?: string
|
||||||
|
/**
|
||||||
|
* 存储过程入参 @params(VARCHAR(MAX),JSON 字符串,如 YDY_GET_TAG 第二个参数)
|
||||||
|
* 注意与 axios 的 `params` 配置重名,此处为查询参数字段名
|
||||||
|
*/
|
||||||
|
params?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 日报表-当日总销售数据 */
|
/** 日报表-当日总销售数据 */
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-drawer
|
<el-drawer
|
||||||
v-model="visible"
|
v-model="visible"
|
||||||
:size="isFullscreen ? '100%' : dialogWidth"
|
:size="drawerSize"
|
||||||
direction="rtl"
|
direction="rtl"
|
||||||
:class="['ai-image-chat-dialog', 'ai-chat-drawer', { 'is-fullscreen': isFullscreen }]"
|
:class="[
|
||||||
|
'ai-image-chat-dialog',
|
||||||
|
'ai-chat-drawer',
|
||||||
|
{ 'is-fullscreen': isFullscreen, 'is-with-daily-report': reportDocVisible }
|
||||||
|
]"
|
||||||
:modal-class="'ai-chat-overlay'"
|
:modal-class="'ai-chat-overlay'"
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
:close-on-click-modal="true"
|
:close-on-click-modal="true"
|
||||||
@@ -62,7 +66,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="ai-chat-dialog-body">
|
<div class="ai-chat-dialog-body" :class="{ 'ai-chat-dialog-body--split': reportDocVisible }">
|
||||||
|
<div
|
||||||
|
class="ai-chat-panel ai-chat-panel--convo"
|
||||||
|
:style="reportDocVisible ? splitConvoStyle : undefined"
|
||||||
|
>
|
||||||
<div class="chat-main">
|
<div class="chat-main">
|
||||||
<div
|
<div
|
||||||
ref="messageListRef"
|
ref="messageListRef"
|
||||||
@@ -153,7 +161,7 @@
|
|||||||
<button
|
<button
|
||||||
class="daily-report-entry"
|
class="daily-report-entry"
|
||||||
type="button"
|
type="button"
|
||||||
:class="{ 'is-module-required': !hasReportModuleContext }"
|
:class="{ 'is-module-required': !hasReportModuleContext, 'is-open': reportDocVisible }"
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
@click="onDailyReportClick"
|
@click="onDailyReportClick"
|
||||||
>
|
>
|
||||||
@@ -235,12 +243,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 报告单据弹窗 -->
|
<div v-if="reportDocVisible" class="ai-chat-panel ai-chat-panel--report">
|
||||||
<ReportDocumentModal
|
<ReportDocumentModal
|
||||||
v-model="reportDocVisible"
|
v-model="reportDocVisible"
|
||||||
:module-name="effectiveReportModuleName"
|
:module-name="effectiveReportModuleName"
|
||||||
:module-code="effectiveReportModuleCode"
|
:module-code="effectiveReportModuleCode"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<!-- Markdown 内图片大图预览 -->
|
<!-- Markdown 内图片大图预览 -->
|
||||||
<ElImageViewer
|
<ElImageViewer
|
||||||
v-if="imageViewerVisible"
|
v-if="imageViewerVisible"
|
||||||
@@ -325,7 +335,10 @@ function onDailyReportClick() {
|
|||||||
ElMessage.warning('请进入一个模块进行每日汇报')
|
ElMessage.warning('请进入一个模块进行每日汇报')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
reportDocVisible.value = true
|
const open = !reportDocVisible.value
|
||||||
|
reportDocVisible.value = open
|
||||||
|
// 打开每日汇报时直接全屏抽屉,收起时恢复窄条宽度
|
||||||
|
isFullscreen.value = open
|
||||||
}
|
}
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -376,8 +389,22 @@ const streamAbortController = ref<AbortController | null>(null)
|
|||||||
const streamingText = ref('')
|
const streamingText = ref('')
|
||||||
/** 多轮对话会话 ID,由 Dify 返回,后续请求携带以保持上下文 */
|
/** 多轮对话会话 ID,由 Dify 返回,后续请求携带以保持上下文 */
|
||||||
const conversationId = ref<string>('')
|
const conversationId = ref<string>('')
|
||||||
const dialogWidth = ref('420px')
|
|
||||||
const isFullscreen = ref(false)
|
const isFullscreen = ref(false)
|
||||||
|
/** 仅聊天时抽屉宽度 = 分栏时左侧对话区宽度(与全屏/宽屏无联动) */
|
||||||
|
const NARROW_DRAWER_PX = 380
|
||||||
|
const splitConvoStyle = computed(() => ({
|
||||||
|
flex: `0 1 ${NARROW_DRAWER_PX}px`,
|
||||||
|
width: `${NARROW_DRAWER_PX}px`,
|
||||||
|
maxWidth: `${NARROW_DRAWER_PX}px`,
|
||||||
|
minWidth: '240px',
|
||||||
|
boxSizing: 'border-box' as const
|
||||||
|
}))
|
||||||
|
/** 全屏或每日汇报为宽分栏时调整宽度;每日汇报打开时会同步 isFullscreen=100% */
|
||||||
|
const drawerSize = computed(() => {
|
||||||
|
if (isFullscreen.value) return '100%'
|
||||||
|
if (reportDocVisible.value) return 'min(100%, 1280px)'
|
||||||
|
return `${NARROW_DRAWER_PX}px`
|
||||||
|
})
|
||||||
const displayStreamingHtml = computed(() => renderMarkdown(streamingText.value))
|
const displayStreamingHtml = computed(() => renderMarkdown(streamingText.value))
|
||||||
const imageViewerVisible = ref(false)
|
const imageViewerVisible = ref(false)
|
||||||
const imageViewerList = ref<string[]>([])
|
const imageViewerList = ref<string[]>([])
|
||||||
@@ -593,6 +620,7 @@ const handleClosed = () => {
|
|||||||
streamAbortController.value?.abort()
|
streamAbortController.value?.abort()
|
||||||
}
|
}
|
||||||
isFullscreen.value = false
|
isFullscreen.value = false
|
||||||
|
reportDocVisible.value = false
|
||||||
if (picPreviewUrl.value) {
|
if (picPreviewUrl.value) {
|
||||||
URL.revokeObjectURL(picPreviewUrl.value)
|
URL.revokeObjectURL(picPreviewUrl.value)
|
||||||
picPreviewUrl.value = ''
|
picPreviewUrl.value = ''
|
||||||
@@ -760,6 +788,31 @@ function getGreeting(): string {
|
|||||||
height: max(calc(100vh - 120px), calc(100vh - var(--left-menu-max-width, 200px) - 80px));
|
height: max(calc(100vh - 120px), calc(100vh - var(--left-menu-max-width, 200px) - 80px));
|
||||||
max-height: max(calc(100vh - 120px), calc(100vh - var(--left-menu-max-width, 200px) - 80px));
|
max-height: max(calc(100vh - 120px), calc(100vh - var(--left-menu-max-width, 200px) - 80px));
|
||||||
}
|
}
|
||||||
|
.ai-image-chat-dialog .ai-chat-dialog-body--split {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
.ai-image-chat-dialog .ai-chat-panel--convo {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.ai-image-chat-dialog .ai-chat-panel--report {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-left: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
.ai-image-chat-dialog .ai-chat-panel--report :deep(.el-dialog) {
|
||||||
|
--el-dialog-margin-top: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
.ai-image-chat-dialog.is-fullscreen .ai-chat-dialog-body {
|
.ai-image-chat-dialog.is-fullscreen .ai-chat-dialog-body {
|
||||||
height: calc(100vh - 120px) !important;
|
height: calc(100vh - 120px) !important;
|
||||||
max-height: calc(100vh - 120px) !important;
|
max-height: calc(100vh - 120px) !important;
|
||||||
@@ -1208,6 +1261,11 @@ function getGreeting(): string {
|
|||||||
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.16);
|
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.16);
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
.daily-report-entry.is-open {
|
||||||
|
border-color: var(--el-color-primary);
|
||||||
|
background: linear-gradient(135deg, #ecf5ff 0%, #d9ecff 100%);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(64, 158, 255, 0.25);
|
||||||
|
}
|
||||||
.daily-report-entry:disabled {
|
.daily-report-entry:disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
|||||||
@@ -1,41 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<!-- 与 AI 决策助手同屏时不用 el-dialog(fixed/teleport 会浮出侧栏外),用普通块级容器填满父级 -->
|
||||||
v-model="modalVisible"
|
<div
|
||||||
width="92vw"
|
v-show="modalVisible"
|
||||||
class="report-document-modal report-document-modal--no-title"
|
class="report-document-surface"
|
||||||
body-class="report-document-modal-body"
|
|
||||||
align-center
|
|
||||||
:show-close="false"
|
|
||||||
:fullscreen="reportModalFullscreen"
|
|
||||||
:close-on-click-modal="true"
|
|
||||||
destroy-on-close
|
|
||||||
@closed="handleClosed"
|
|
||||||
@opened="loadAllData"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="modal-body"
|
class="modal-body"
|
||||||
style="min-height: min(70vh, 760px); box-sizing: border-box"
|
:style="surfaceBodyStyle"
|
||||||
>
|
>
|
||||||
<div class="report-modal-topbar">
|
|
||||||
<h2 class="report-modal-title">每日汇报</h2>
|
|
||||||
<div class="report-modal-header-actions" @click.stop>
|
|
||||||
<el-tooltip :content="reportModalFullscreen ? '退出全屏' : '全屏查看'" placement="bottom">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="report-modal-fs"
|
|
||||||
:aria-label="reportModalFullscreen ? '退出全屏' : '全屏查看'"
|
|
||||||
@click="reportModalFullscreen = !reportModalFullscreen"
|
|
||||||
>
|
|
||||||
<Icon :icon="reportModalFullscreen ? 'ep:copy-document' : 'ep:full-screen'" />
|
|
||||||
</button>
|
|
||||||
</el-tooltip>
|
|
||||||
<el-tooltip content="关闭" placement="bottom">
|
|
||||||
<button type="button" class="report-modal-close" aria-label="关闭" @click="modalVisible = false">
|
|
||||||
<el-icon><Close /></el-icon>
|
|
||||||
</button>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<el-tabs v-model="activeTab" class="main-tabs" :lazy="false">
|
<el-tabs v-model="activeTab" class="main-tabs" :lazy="false">
|
||||||
<!-- Tab1:仅报告内容 -->
|
<!-- Tab1:仅报告内容 -->
|
||||||
<el-tab-pane label="撰写报告" name="write">
|
<el-tab-pane label="撰写报告" name="write">
|
||||||
@@ -318,7 +290,7 @@
|
|||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</div>
|
||||||
|
|
||||||
<!-- 日历:当日多条汇报时先选一条,再打开与历史列表相同的全屏报告详情 -->
|
<!-- 日历:当日多条汇报时先选一条,再打开与历史列表相同的全屏报告详情 -->
|
||||||
<el-dialog
|
<el-dialog
|
||||||
@@ -443,8 +415,7 @@ import {
|
|||||||
} from '@/api/ydoyun/aiAssistantReport'
|
} from '@/api/ydoyun/aiAssistantReport'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
import { ElMessage, ElMessageBox, ElLoading, ElNotification } from 'element-plus'
|
import { ElMessage, ElMessageBox, ElLoading, ElNotification } from 'element-plus'
|
||||||
import { ArrowLeft, ArrowRight, Close, Plus } from '@element-plus/icons-vue'
|
import { ArrowLeft, ArrowRight, Plus } from '@element-plus/icons-vue'
|
||||||
import { Icon } from '@/components/Icon'
|
|
||||||
import { AI_ASSISTANT_REPORT_CONTEXT_KEY } from './useAiAssistant'
|
import { AI_ASSISTANT_REPORT_CONTEXT_KEY } from './useAiAssistant'
|
||||||
import * as FileApi from '@/api/infra/file'
|
import * as FileApi from '@/api/infra/file'
|
||||||
import Echart from '@/components/Echart/src/Echart.vue'
|
import Echart from '@/components/Echart/src/Echart.vue'
|
||||||
@@ -461,6 +432,18 @@ const props = defineProps<{
|
|||||||
moduleCode?: string
|
moduleCode?: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
/** 与 AI 助手侧栏同屏时占满父级,内部 tabs 可滚动 */
|
||||||
|
const surfaceBodyStyle = {
|
||||||
|
minHeight: 0,
|
||||||
|
flex: 1,
|
||||||
|
height: '100%',
|
||||||
|
maxHeight: '100%',
|
||||||
|
boxSizing: 'border-box' as const,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column' as const,
|
||||||
|
overflow: 'hidden' as const
|
||||||
|
}
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'update:modelValue': [value: boolean]
|
'update:modelValue': [value: boolean]
|
||||||
}>()
|
}>()
|
||||||
@@ -558,7 +541,6 @@ async function loadDetailRows(reportId: number | null) {
|
|||||||
resetDetailRows()
|
resetDetailRows()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const reportModalFullscreen = ref(false)
|
|
||||||
const optimizing = ref(false)
|
const optimizing = ref(false)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
/** 历史列表中单条详情弹窗(全屏:正文 + AI 详表 + 截图) */
|
/** 历史列表中单条详情弹窗(全屏:正文 + AI 详表 + 截图) */
|
||||||
@@ -668,14 +650,23 @@ function filterReportRowsByScope<T extends AiAssistantReportItem>(rows: T[]): T[
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(modalVisible, (v) => {
|
watch(
|
||||||
if (v && !hasModuleScope.value) {
|
() => props.modelValue,
|
||||||
ElMessage.warning('请进入一个模块进行每日汇报')
|
(open, wasOpen) => {
|
||||||
|
if (open) {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
|
if (!hasModuleScope.value) {
|
||||||
|
ElMessage.warning('请进入一个模块进行每日汇报')
|
||||||
modalVisible.value = false
|
modalVisible.value = false
|
||||||
})
|
return
|
||||||
}
|
}
|
||||||
})
|
loadAllData()
|
||||||
|
})
|
||||||
|
} else if (wasOpen) {
|
||||||
|
handleClosed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
/** 截图地址补全(相对路径拼 API 根) */
|
/** 截图地址补全(相对路径拼 API 根) */
|
||||||
function resolveMediaUrl(url?: string | null): string {
|
function resolveMediaUrl(url?: string | null): string {
|
||||||
@@ -1204,7 +1195,6 @@ function handleClosed() {
|
|||||||
reportContent.value = ''
|
reportContent.value = ''
|
||||||
currentReportId.value = null
|
currentReportId.value = null
|
||||||
resetDetailRows()
|
resetDetailRows()
|
||||||
reportModalFullscreen.value = false
|
|
||||||
historyRecordDetailVisible.value = false
|
historyRecordDetailVisible.value = false
|
||||||
historyRecordDetail.value = null
|
historyRecordDetail.value = null
|
||||||
historyList.value = []
|
historyList.value = []
|
||||||
@@ -1330,30 +1320,11 @@ function truncateContent(text?: string, maxLen = 60): string {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/*
|
/* 与 AI 助手同屏:无 el-dialog,侧栏内边距由本类控制 */
|
||||||
* 默认弹窗 margin-top: 15vh 会在视口里留出很大空白;align-center 改为垂直居中(margin: auto)。
|
.report-document-surface {
|
||||||
* 同时收紧 .el-dialog 与 body 的上内边距,避免「每日汇报」上方堆叠过多 padding。
|
|
||||||
*/
|
|
||||||
.report-document-modal.report-document-modal--no-title {
|
|
||||||
padding: 8px 16px 16px;
|
padding: 8px 16px 16px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
* 弹窗 teleport 到 body 后,scoped 无法稳定命中 EP 渲染的 `.el-dialog__body`;
|
|
||||||
* 固定高度与非 scoped 块配合,见文末 `.el-dialog__body.report-document-modal-body`。
|
|
||||||
*/
|
|
||||||
.report-document-modal.el-dialog:not(.is-fullscreen) {
|
|
||||||
width: min(1000px, 92vw) !important;
|
|
||||||
max-width: min(1000px, calc(100vw - 24px));
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
.report-document-modal.el-dialog.is-fullscreen {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin: 0 !important;
|
|
||||||
height: 100vh;
|
|
||||||
max-height: 100vh;
|
|
||||||
}
|
|
||||||
.modal-body {
|
.modal-body {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -1364,50 +1335,6 @@ function truncateContent(text?: string, maxLen = 60): string {
|
|||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.report-modal-topbar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
|
||||||
}
|
|
||||||
.report-modal-title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
line-height: 1.35;
|
|
||||||
letter-spacing: 0.02em;
|
|
||||||
}
|
|
||||||
.report-modal-header-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 2px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
.report-modal-fs,
|
|
||||||
.report-modal-close {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: transparent;
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.report-modal-fs:hover,
|
|
||||||
.report-modal-close:hover {
|
|
||||||
color: var(--el-color-primary);
|
|
||||||
background: var(--el-fill-color-light);
|
|
||||||
}
|
|
||||||
.section-title-hint {
|
.section-title-hint {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -2521,45 +2448,24 @@ function truncateContent(text?: string, maxLen = 60): string {
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!--
|
<!-- 与 AI 助手同屏时主内容已非 el-dialog,用 .report-document-surface 填满侧栏,避免主区域高度为 0 -->
|
||||||
Teleport 到 body 后,scoped 无法命中 EP 渲染的 .el-dialog / .el-dialog__body(无父组件 data-v 链),
|
|
||||||
此处用唯一类名做全局样式,避免主内容区高度为 0、只剩标题栏。
|
|
||||||
-->
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.el-dialog.report-document-modal.report-document-modal--no-title > .el-dialog__header {
|
.report-document-surface {
|
||||||
display: none !important;
|
min-height: 0;
|
||||||
height: 0 !important;
|
flex: 1 1 0;
|
||||||
min-height: 0 !important;
|
width: 100%;
|
||||||
padding: 0 !important;
|
height: 100%;
|
||||||
margin: 0 !important;
|
max-height: 100%;
|
||||||
border: none !important;
|
display: flex;
|
||||||
overflow: hidden !important;
|
flex-direction: column;
|
||||||
line-height: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 主弹窗内容区:固定高度 + flex,供内部 .modal-body / .main-tabs 分配剩余空间 */
|
|
||||||
.el-dialog__body.report-document-modal-body {
|
|
||||||
min-height: min(800px, calc(100vh - 72px)) !important;
|
|
||||||
height: min(800px, calc(100vh - 72px)) !important;
|
|
||||||
max-height: min(800px, calc(100vh - 72px)) !important;
|
|
||||||
padding: 0 0 4px !important;
|
|
||||||
box-sizing: border-box;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex !important;
|
box-sizing: border-box;
|
||||||
flex-direction: column !important;
|
background: var(--el-bg-color);
|
||||||
|
border-radius: 8px;
|
||||||
scrollbar-gutter: stable;
|
scrollbar-gutter: stable;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-dialog.is-fullscreen .el-dialog__body.report-document-modal-body {
|
.report-document-surface .modal-body {
|
||||||
flex: 1 1 0 !important;
|
|
||||||
min-height: 0 !important;
|
|
||||||
height: auto !important;
|
|
||||||
max-height: none !important;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 槽位内容铺满 body(避免用 > 以防 EP 插入包裹层) */
|
|
||||||
.el-dialog__body.report-document-modal-body .modal-body {
|
|
||||||
flex: 1 1 0;
|
flex: 1 1 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -832,15 +832,15 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
component: () => import('@/views/ydoyun/report/lijun/reportpage6/detail.vue')
|
component: () => import('@/views/ydoyun/report/lijun/reportpage6/detail.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'product-splb',
|
path: 'wbl/product-splb',
|
||||||
name: 'ProductSplbReport',
|
name: 'ProductSplbReport',
|
||||||
meta: {
|
meta: {
|
||||||
title: '商品报表',
|
title: '商品列表报表',
|
||||||
noCache: true,
|
noCache: true,
|
||||||
hidden: true,
|
hidden: true,
|
||||||
canTo: true
|
canTo: true
|
||||||
},
|
},
|
||||||
component: () => import('@/views/ydoyun/report/productSplb/index.vue')
|
component: () => import('@/views/ydoyun/report/wangbuliao/productSplb/index.vue')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -198,26 +198,49 @@
|
|||||||
|
|
||||||
<!-- KPI 卡片区域 -->
|
<!-- KPI 卡片区域 -->
|
||||||
<div class="kpi-section">
|
<div class="kpi-section">
|
||||||
<div class="module-header">
|
<div class="kb-card-header kb-card-header--title-style">
|
||||||
<span class="module-title">KPI 指标</span>
|
<div class="kb-title-wrapper">
|
||||||
|
<el-icon class="kb-title-icon"><Odometer /></el-icon>
|
||||||
|
<h3 class="kb-card-title">KPI 指标</h3>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="kpi-section-body">
|
||||||
<div
|
<div
|
||||||
class="kpi-grid"
|
class="kpi-grid"
|
||||||
v-loading="loadingKpi"
|
v-loading="loadingKpi"
|
||||||
element-loading-text="加载中..."
|
element-loading-text="加载中..."
|
||||||
element-loading-custom-class="kpi-loading"
|
element-loading-custom-class="kpi-loading"
|
||||||
>
|
>
|
||||||
<el-card v-for="(kpi, index) in kpiList" :key="index" class="kpi-card" shadow="never">
|
<el-empty
|
||||||
|
v-if="!loadingKpi && kpiList.length === 0"
|
||||||
|
class="kpi-grid-empty"
|
||||||
|
description="暂无数据"
|
||||||
|
:image-size="120"
|
||||||
|
/>
|
||||||
|
<template v-else>
|
||||||
|
<el-card
|
||||||
|
v-for="(kpi, index) in kpiList"
|
||||||
|
:key="'kpi-' + index"
|
||||||
|
class="kpi-card"
|
||||||
|
shadow="never"
|
||||||
|
>
|
||||||
<div class="kpi-header">
|
<div class="kpi-header">
|
||||||
<span class="kpi-title">{{ kpi.title }}</span>
|
<span class="kpi-title">{{ kpi.title }}</span>
|
||||||
<span v-if="kpi.unit != null && kpi.unit !== ''" class="kpi-unit">{{ kpi.unit }}</span>
|
<span class="kpi-unit">{{ kpi.unit }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="kpi.value != null && kpi.value !== ''" class="kpi-value">{{ kpi.value }}</div>
|
<div class="kpi-value">{{ kpi.value }}</div>
|
||||||
<div v-if="(formatTrendText(kpi) || (kpi.descs != null && kpi.descs !== ''))" class="kpi-footer">
|
<div class="kpi-footer">
|
||||||
<span v-if="formatTrendText(kpi)" class="kpi-trend" :class="kpi.trend">{{ formatTrendText(kpi) }}</span>
|
<span class="kpi-trend" :class="kpi.trend">{{ formatTrendText(kpi) || '-' }}</span>
|
||||||
<el-tooltip v-if="kpi.descs != null && kpi.descs !== ''" :content="kpi.descs" placement="top" :show-after="300" class="kpi-desc-tooltip">
|
<el-tooltip
|
||||||
|
v-if="kpi.descs != null && String(kpi.descs).trim() !== ''"
|
||||||
|
:content="kpi.descs"
|
||||||
|
placement="top"
|
||||||
|
:show-after="300"
|
||||||
|
class="kpi-desc-tooltip"
|
||||||
|
>
|
||||||
<span class="kpi-desc">{{ kpi.descs }}</span>
|
<span class="kpi-desc">{{ kpi.descs }}</span>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
<span v-else class="kpi-desc kpi-desc--empty">-</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- 迷你柱状图 -->
|
<!-- 迷你柱状图 -->
|
||||||
<div v-if="kpi.miniBars" class="mini-bars">
|
<div v-if="kpi.miniBars" class="mini-bars">
|
||||||
@@ -243,6 +266,8 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -845,6 +870,7 @@ import type { EChartsOption } from 'echarts'
|
|||||||
import Echart from '@/components/Echart/src/Echart.vue'
|
import Echart from '@/components/Echart/src/Echart.vue'
|
||||||
import {
|
import {
|
||||||
StarFilled,
|
StarFilled,
|
||||||
|
Odometer,
|
||||||
Search,
|
Search,
|
||||||
Document,
|
Document,
|
||||||
Picture,
|
Picture,
|
||||||
@@ -2263,13 +2289,16 @@ const handleQuery = async () => {
|
|||||||
raw = Array.isArray(kpiRes.data) ? kpiRes.data : null
|
raw = Array.isArray(kpiRes.data) ? kpiRes.data : null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 前几行常为列配置(含 keys/orders、无 code);数据行含供货商 code
|
// 前几行常为列配置(含 keys/orders、无 code);优先用含 code 的数据行
|
||||||
const dataRows = (raw || []).filter(
|
const rawArr = raw || []
|
||||||
|
let rowsToMap = rawArr.filter(
|
||||||
(row) => row != null && row.code != null && String(row.code).trim() !== ''
|
(row) => row != null && row.code != null && String(row.code).trim() !== ''
|
||||||
)
|
)
|
||||||
if (dataRows.length > 0) {
|
if (rowsToMap.length === 0) {
|
||||||
kpiList.value = dataRows.map((item: any) => mapApiRowToKpi(item))
|
// 无 code 时仍可能返回 KPI 行(仅有 title/指标等),避免整块区域无卡片
|
||||||
|
rowsToMap = rawArr.filter((row) => isLikelyKpiDataRow(row))
|
||||||
}
|
}
|
||||||
|
kpiList.value = rowsToMap.map((item: any) => mapApiRowToKpi(item))
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -2383,6 +2412,18 @@ function trendFromColorField(c: unknown): 'up' | 'down' | 'flat' {
|
|||||||
return 'flat'
|
return 'flat'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 排除明显为列配置的行;其余有标题或主值或 code 的视为 KPI 数据行 */
|
||||||
|
function isLikelyKpiDataRow(row: any): boolean {
|
||||||
|
if (row == null || typeof row !== 'object') return false
|
||||||
|
if (row.keys != null || row.orders != null) return false
|
||||||
|
const hasCode = row.code != null && String(row.code).trim() !== ''
|
||||||
|
const hasTitle =
|
||||||
|
String(row.title ?? row.mainTitle ?? row.name ?? '').trim() !== ''
|
||||||
|
const hasMainValue =
|
||||||
|
String(row.value ?? row.mainValue ?? row.val ?? row.value1 ?? '').trim() !== ''
|
||||||
|
return hasCode || hasTitle || hasMainValue
|
||||||
|
}
|
||||||
|
|
||||||
// 将接口返回的一行数据映射为 KPI 卡片格式(按后端字段名适配,无则保留默认)
|
// 将接口返回的一行数据映射为 KPI 卡片格式(按后端字段名适配,无则保留默认)
|
||||||
function mapApiRowToKpi(row: any): KPIData {
|
function mapApiRowToKpi(row: any): KPIData {
|
||||||
const trend =
|
const trend =
|
||||||
@@ -2950,6 +2991,60 @@ onDeactivated(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KPI 指标模块:白底容器(与整页灰底区分;标题栏与「供应商表现」同一样式)
|
||||||
|
.kpi-section {
|
||||||
|
background-color: var(--el-bg-color);
|
||||||
|
border: 1px solid var(--el-border-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.kpi-section-body {
|
||||||
|
padding: 0 12px 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模块标题栏(KPI、供应商表现等共用,与 kb-card-header--title-style 一致)
|
||||||
|
.kb-card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.more-button {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.kb-card-header--title-style {
|
||||||
|
padding: 16px;
|
||||||
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||||
|
background: var(--el-fill-color-lighter);
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kb-title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding-left: 12px;
|
||||||
|
border-left: 4px solid var(--el-color-primary);
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kb-title-icon {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kb-card-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
margin: 0;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// KPI 网格(含加载中特效)
|
// KPI 网格(含加载中特效)
|
||||||
.kpi-grid {
|
.kpi-grid {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -2957,7 +3052,14 @@ onDeactivated(() => {
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
.kpi-grid-empty {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
justify-self: center;
|
||||||
|
width: 100%;
|
||||||
|
padding: 16px 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.kpi-card {
|
.kpi-card {
|
||||||
height: 160px;
|
height: 160px;
|
||||||
@@ -3134,46 +3236,6 @@ onDeactivated(() => {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kb-card-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
.more-button {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 与中类销售排名 Top 5 标题样式一致 */
|
|
||||||
&.kb-card-header--title-style {
|
|
||||||
padding: 16px;
|
|
||||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
|
||||||
background: var(--el-fill-color-lighter);
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.kb-title-wrapper {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding-left: 12px;
|
|
||||||
border-left: 4px solid var(--el-color-primary);
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.kb-title-icon {
|
|
||||||
color: var(--el-color-primary);
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.kb-card-title {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
margin: 0;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.kb-card-content {
|
.kb-card-content {
|
||||||
padding: 0 12px 12px;
|
padding: 0 12px 12px;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user