1. 新增模块品类穿透
This commit is contained in:
2
.env.dev
2
.env.dev
@@ -5,7 +5,7 @@ VITE_DEV=true
|
||||
|
||||
# 请求路径
|
||||
#VITE_BASE_URL='http://localhost:48080'
|
||||
VITE_BASE_URL='http://118.253.178.8:48080'
|
||||
VITE_BASE_URL='http://118.253.178.8:58080'
|
||||
|
||||
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
|
||||
VITE_UPLOAD_TYPE=server
|
||||
|
||||
@@ -4,7 +4,7 @@ NODE_ENV=development
|
||||
VITE_DEV=true
|
||||
|
||||
# 请求路径
|
||||
VITE_BASE_URL='http://118.253.178.8:48080'
|
||||
VITE_BASE_URL='http://118.253.178.8:58080'
|
||||
#VITE_BASE_URL='http://localhost:48080'
|
||||
|
||||
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务
|
||||
|
||||
@@ -4,7 +4,7 @@ NODE_ENV=production
|
||||
VITE_DEV=true
|
||||
|
||||
# 请求路径
|
||||
VITE_BASE_URL='http://118.253.178.8:48080'
|
||||
VITE_BASE_URL='http://118.253.178.8:58080'
|
||||
|
||||
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
|
||||
VITE_UPLOAD_TYPE=server
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<slot></slot>
|
||||
<!-- 全局右下角悬浮按钮(参考 AIRBT.html 样式);登录页不展示 -->
|
||||
<!-- Teleport 到 body:避免父級 transform/overflow 讓 position:fixed 參考錯容器而被裁切 -->
|
||||
<Teleport to="body">
|
||||
<div
|
||||
v-if="showAssistantFab"
|
||||
ref="fabRef"
|
||||
@@ -15,6 +16,7 @@
|
||||
</div>
|
||||
<span class="fab-text">AI 决策助手</span>
|
||||
</div>
|
||||
</Teleport>
|
||||
|
||||
<AiImageChatDialog
|
||||
v-model="dialogVisible"
|
||||
@@ -34,6 +36,7 @@ import {
|
||||
} from './useAiAssistant'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { routeShouldShowAiAssistantFab } from './aiFabRoute'
|
||||
|
||||
defineOptions({ name: 'AiAssistantProvider' })
|
||||
|
||||
@@ -212,6 +215,20 @@ function setAiAssistantFabEnabled(enabled: boolean) {
|
||||
}
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
/** 依當前路由同步浮層(後台動態路由 name 常與組件 defineOptions 不一致,僅靠業務頁 setFab 易失效) */
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
() => {
|
||||
if (isHideAssistantFabPath(route.path)) {
|
||||
fabEnabled.value = false
|
||||
return
|
||||
}
|
||||
fabEnabled.value = routeShouldShowAiAssistantFab(route)
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const showAssistantFab = computed(
|
||||
() => !isHideAssistantFabPath(route.path) && fabEnabled.value
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Layout } from '@/utils/routerHelper'
|
||||
import { Layout } from '@/utils/routerHelper'
|
||||
|
||||
const { t } = useI18n()
|
||||
/**
|
||||
@@ -865,7 +865,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
||||
name: 'SalesDailyCategoryPenetration',
|
||||
meta: {
|
||||
title: '品类穿透看板',
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
canTo: true
|
||||
},
|
||||
|
||||
@@ -884,6 +884,7 @@ import { Icon } from '@/components/Icon'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import type { CategoryDiagnosticData } from './components/categoryCardListComponents.vue'
|
||||
import { useAiAssistant } from '@/components/AiAssistant/useAiAssistant'
|
||||
import { routeKeepsAiAssistantFab } from '@/components/AiAssistant/aiFabRoute'
|
||||
|
||||
defineOptions({ name: 'ProductDashboard' })
|
||||
|
||||
@@ -2876,7 +2877,11 @@ onActivated(() => {
|
||||
setAiAssistantFabEnabled(true)
|
||||
})
|
||||
onDeactivated(() => {
|
||||
void nextTick(() => {
|
||||
if (!routeKeepsAiAssistantFab(router.currentRoute.value)) {
|
||||
setAiAssistantFabEnabled(false)
|
||||
}
|
||||
})
|
||||
saveDataCache()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -501,6 +501,7 @@ import { useAiModulePromptEditor } from '@/hooks/web/useAiModulePromptEditor'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { AiModulePromptApi } from '@/api/ydoyun/aiModulePrompt'
|
||||
import AiPromptEditDialog from '../lijun/reportpage6/components/AiPromptEditDialog.vue'
|
||||
import { routeKeepsAiAssistantFab } from '@/components/AiAssistant/aiFabRoute'
|
||||
|
||||
defineOptions({ name: 'ProductDaily' })
|
||||
|
||||
@@ -925,7 +926,11 @@ onActivated(() => {
|
||||
})
|
||||
|
||||
onDeactivated(() => {
|
||||
void nextTick(() => {
|
||||
if (!routeKeepsAiAssistantFab(router.currentRoute.value)) {
|
||||
setAiAssistantFabEnabled(false)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="plct-page">
|
||||
<div ref="plctPageRootRef" class="plct-page">
|
||||
<div class="plct-toolbar card">
|
||||
<el-form :model="queryForm" class="plct-query-form" inline @submit.prevent="handleQuery">
|
||||
<el-form-item label="日期">
|
||||
@@ -209,15 +209,6 @@
|
||||
min-width="68"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
label="销量"
|
||||
align="right"
|
||||
width="64"
|
||||
label-class-name="plct-jj-hd-sl"
|
||||
class-name="plct-jj-col-sl"
|
||||
>
|
||||
<template #default="{ row }">{{ formatJjInt(row.sl) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="库存"
|
||||
align="right"
|
||||
@@ -227,6 +218,15 @@
|
||||
>
|
||||
<template #default="{ row }">{{ formatJjInt(row.kc) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="销量"
|
||||
align="right"
|
||||
width="64"
|
||||
label-class-name="plct-jj-hd-sl"
|
||||
class-name="plct-jj-col-sl"
|
||||
>
|
||||
<template #default="{ row }">{{ formatJjInt(row.sl) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
@@ -276,14 +276,14 @@
|
||||
<span class="plct3-size-dot plct3-size-dot--muted" aria-hidden="true"></span>
|
||||
尺码
|
||||
</span>
|
||||
<span class="plct3-size-legend-i">
|
||||
<span class="plct3-size-dot plct3-size-dot--kc" aria-hidden="true"></span>
|
||||
库存
|
||||
</span>
|
||||
<span class="plct3-size-legend-i">
|
||||
<span class="plct3-size-dot plct3-size-dot--ls" aria-hidden="true"></span>
|
||||
零售
|
||||
</span>
|
||||
<span class="plct3-size-legend-i">
|
||||
<span class="plct3-size-dot plct3-size-dot--kc" aria-hidden="true"></span>
|
||||
库存
|
||||
</span>
|
||||
<span class="plct3-size-legend-i">
|
||||
<span class="plct3-size-dot plct3-size-dot--zt" aria-hidden="true"></span>
|
||||
状态
|
||||
@@ -294,8 +294,8 @@
|
||||
<template #default="{ row }">
|
||||
<template v-if="col.type === 'sku'">
|
||||
<div class="plct3-sku-merged">
|
||||
<div class="plct3-sku-code">{{ plct3FormatCell(row[col.spdmKey]) }}</div>
|
||||
<div class="plct3-sku-name">{{ plct3FormatCell(row[col.spmcKey]) }}</div>
|
||||
<div class="plct3-sku-code">{{ plct3FormatCell(row[col.spdmKey]) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="col.type === 'sizeSku'">
|
||||
@@ -319,9 +319,9 @@
|
||||
</div>
|
||||
<div class="plct3-size-chip-vals">
|
||||
<template v-if="t.kc != null || t.ls != null">
|
||||
<span v-if="t.kc != null" class="plct3-size-kc">{{ t.kc }}</span>
|
||||
<span v-if="t.kc != null && t.ls != null" class="plct3-size-sep">|</span>
|
||||
<span v-if="t.ls != null" class="plct3-size-ls">{{ t.ls }}</span>
|
||||
<span v-if="t.kc != null && t.ls != null" class="plct3-size-sep">|</span>
|
||||
<span v-if="t.kc != null" class="plct3-size-kc">{{ t.kc }}</span>
|
||||
</template>
|
||||
<span v-else class="plct3-size-dash">—</span>
|
||||
</div>
|
||||
@@ -369,16 +369,42 @@
|
||||
import dayjs from 'dayjs'
|
||||
import type { EChartsOption } from 'echarts'
|
||||
import { useCssVar } from '@vueuse/core'
|
||||
import { computed, onActivated, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import {
|
||||
computed,
|
||||
nextTick,
|
||||
onActivated,
|
||||
onBeforeUnmount,
|
||||
onDeactivated,
|
||||
onMounted,
|
||||
reactive,
|
||||
ref,
|
||||
watch
|
||||
} from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ReportApi, type ExecuteProcedureParams } from '@/api/ydoyun/report/reportpage'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import Echart from '@/components/Echart/src/Echart.vue'
|
||||
import { useAiAssistant } from '@/components/AiAssistant/useAiAssistant'
|
||||
import { routeKeepsAiAssistantFab } from '@/components/AiAssistant/aiFabRoute'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'SalesDailyCategoryPenetration' })
|
||||
|
||||
/** 與 ProductDashboard 一致:SPA 內是否已做過初次初始化;配合 window 快取,不依賴 keep-alive */
|
||||
const INITIAL_QUERY_FLAG_KEY = '__SalesDailyPlct_initialQueryDone'
|
||||
const DATA_CACHE_KEY = '__SalesDailyPlct_dataCache'
|
||||
|
||||
/** 關閉分頁或刷新時清除,下次打開重新完整查詢(SPA 內切路由不會觸發 beforeunload) */
|
||||
function clearPlctFlagsOnLeave() {
|
||||
const win = window as unknown as Record<string, unknown>
|
||||
try {
|
||||
delete win[INITIAL_QUERY_FLAG_KEY]
|
||||
delete win[DATA_CACHE_KEY]
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
const REPORT_ID = 1
|
||||
const PROCEDURE_SECRET = '123'
|
||||
const PROC_QX_KEHU = 'YDY_AI_GET_QX_KEHU'
|
||||
@@ -390,6 +416,27 @@ const PROC_PLCT3 = 'YDY_AI_GET_PLCT3'
|
||||
const PLCT3_HIDDEN_KEYS = /^(ysdm)$/i
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
/** AI 決策助手:須在本頁顯式開啟;從銷售日報跳入時上一頁 onDeactivated 會關閉懸浮按鈕,此處需重新打開 */
|
||||
const PLCT_AI_MODULE_KEY = 'SalesDailyCategoryPenetration:main'
|
||||
const aiAssistant = useAiAssistant()
|
||||
const { setAiAssistantFabEnabled } = aiAssistant
|
||||
const plctPageRootRef = ref<HTMLElement | null>(null)
|
||||
|
||||
const aiMenuModuleName = computed(() => {
|
||||
const t = route.meta?.title
|
||||
return typeof t === 'string' && t.trim() ? t.trim() : '品类穿透看板'
|
||||
})
|
||||
|
||||
watch(
|
||||
aiMenuModuleName,
|
||||
(name) => {
|
||||
aiAssistant.setPageModuleName(name)
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const userStore = useUserStore()
|
||||
const appStore = useAppStore()
|
||||
const username = computed(() => userStore.user.username || '')
|
||||
@@ -488,6 +535,77 @@ function syncFormFromRoute() {
|
||||
}
|
||||
}
|
||||
|
||||
/** 路由穿透條件快照:用於與上次成功拉數後對比,避免 keep-alive 切回無條件重查(對齊 ProductDashboard 思路) */
|
||||
function snapshotRouteQueryKey(): string {
|
||||
return [
|
||||
pickRouteQuery(route.query.rq),
|
||||
pickRouteQuery(route.query.ckdm),
|
||||
pickRouteQuery(route.query.pp),
|
||||
pickRouteQuery(route.query.ppmc)
|
||||
].join('|')
|
||||
}
|
||||
|
||||
/** 最近一次 loadDetail 完成後對應的路由 query 快照(切回分頁時若與當前路由一致則不重查) */
|
||||
const lastPlctRouteKeyAfterFetch = ref('')
|
||||
|
||||
const plctBootstrapDone = ref(false)
|
||||
|
||||
function isPlctCacheRestorable(data: unknown): data is Record<string, any> {
|
||||
if (!data || typeof data !== 'object') return false
|
||||
const d = data as Record<string, any>
|
||||
if (String(d.routeQueryKey || '') !== snapshotRouteQueryKey()) return false
|
||||
return d.queried === true
|
||||
}
|
||||
|
||||
function savePlctDataCache() {
|
||||
const win = window as unknown as Record<string, any>
|
||||
try {
|
||||
win[DATA_CACHE_KEY] = {
|
||||
routeQueryKey: snapshotRouteQueryKey(),
|
||||
queried: queried.value,
|
||||
detail: detail.value ? JSON.parse(JSON.stringify(detail.value)) : null,
|
||||
plct2Rows: JSON.parse(JSON.stringify(plct2Rows.value)),
|
||||
plct3Rows: JSON.parse(JSON.stringify(plct3Rows.value)),
|
||||
plct3SkuSizeLines: JSON.parse(JSON.stringify(plct3SkuSizeLines.value)),
|
||||
queryForm: JSON.parse(JSON.stringify(queryForm)),
|
||||
brandOptions: JSON.parse(JSON.stringify(brandOptions.value)),
|
||||
userStoreRows: JSON.parse(JSON.stringify(userStoreRows.value))
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
function restorePlctFromWindowCache(data: Record<string, any>) {
|
||||
if (Array.isArray(data.userStoreRows)) userStoreRows.value = data.userStoreRows
|
||||
if (Array.isArray(data.brandOptions)) brandOptions.value = data.brandOptions
|
||||
if (data.queryForm && typeof data.queryForm === 'object') {
|
||||
Object.assign(queryForm, data.queryForm)
|
||||
}
|
||||
queried.value = !!data.queried
|
||||
detail.value = data.detail && typeof data.detail === 'object' ? data.detail : null
|
||||
plct2Rows.value = Array.isArray(data.plct2Rows) ? data.plct2Rows : []
|
||||
plct3Rows.value = Array.isArray(data.plct3Rows) ? data.plct3Rows : []
|
||||
plct3SkuSizeLines.value = Array.isArray(data.plct3SkuSizeLines) ? data.plct3SkuSizeLines : []
|
||||
lastPlctRouteKeyAfterFetch.value = String(data.routeQueryKey || snapshotRouteQueryKey())
|
||||
}
|
||||
|
||||
function maybeRefetchPlctFromRouteIfChanged() {
|
||||
const cur = snapshotRouteQueryKey()
|
||||
const rqQ = pickRouteQuery(route.query.rq)
|
||||
const ckdmQ = pickRouteQuery(route.query.ckdm)
|
||||
const ppQ = pickRouteQuery(route.query.pp)
|
||||
const ppmcQ = pickRouteQuery(route.query.ppmc)
|
||||
if (!rqQ || !ckdmQ || !(ppQ || ppmcQ)) return
|
||||
if (cur === lastPlctRouteKeyAfterFetch.value) return
|
||||
syncFormFromRoute()
|
||||
ensureSelectionsAfterSync()
|
||||
const rq = String(queryForm.rq || '').trim()
|
||||
const ckdm = String(queryForm.ckdm || '').trim()
|
||||
const pp = String(queryForm.pp || '').trim()
|
||||
if (rq && ckdm && pp) {
|
||||
void handleQuery()
|
||||
}
|
||||
}
|
||||
|
||||
function normalizePercent(v: any): number {
|
||||
const n = toFiniteNumber(v)
|
||||
if (!Number.isFinite(n)) return NaN as unknown as number
|
||||
@@ -2084,12 +2202,15 @@ async function loadDetail() {
|
||||
plct3SkuSizeLines.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
if (rq && ckdm && pp) {
|
||||
lastPlctRouteKeyAfterFetch.value = snapshotRouteQueryKey()
|
||||
savePlctDataCache()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 初次 / 路由同步後:路由帶齊 rq+ckdm+品牌(pp 或 ppmc)且表單已解析則自動查詢 */
|
||||
async function bootstrapPenetrationFromRoute() {
|
||||
await Promise.all([loadUserStores(), loadBrandOptions()])
|
||||
/** 同步路由條件到表單;路徑參數齊全且表單可解析時自動查詢(僅在需要打接口時調用) */
|
||||
async function syncFormAndAutoQueryIfRouteReady() {
|
||||
syncFormFromRoute()
|
||||
ensureSelectionsAfterSync()
|
||||
|
||||
@@ -2107,30 +2228,73 @@ async function bootstrapPenetrationFromRoute() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 與 SalesDailyAiReport 一致:從 keep-alive 再次進入時有完整條件則重拉穿透數據。
|
||||
* 首次掛載已由 onMounted 走 bootstrap,此處跳過一次避免雙次請求。
|
||||
*/
|
||||
let skipPlctActivateRefetchOnce = true
|
||||
|
||||
/** 與 ProductDashboard 相同套路:首次進入完整拉選項+可能自動查;再次進入實例時若有 window 快取且與當前 URL 一致則還原不查 */
|
||||
onMounted(async () => {
|
||||
await bootstrapPenetrationFromRoute()
|
||||
const win = window as unknown as Record<string, any>
|
||||
const isFirstEnter = !win[INITIAL_QUERY_FLAG_KEY]
|
||||
window.addEventListener('beforeunload', clearPlctFlagsOnLeave)
|
||||
|
||||
if (isFirstEnter) {
|
||||
await Promise.all([loadUserStores(), loadBrandOptions()])
|
||||
win[INITIAL_QUERY_FLAG_KEY] = true
|
||||
await syncFormAndAutoQueryIfRouteReady()
|
||||
} else {
|
||||
const dataCache = win[DATA_CACHE_KEY]
|
||||
if (isPlctCacheRestorable(dataCache)) {
|
||||
restorePlctFromWindowCache(dataCache)
|
||||
} else {
|
||||
await Promise.all([loadUserStores(), loadBrandOptions()])
|
||||
await syncFormAndAutoQueryIfRouteReady()
|
||||
}
|
||||
}
|
||||
|
||||
plctBootstrapDone.value = true
|
||||
|
||||
setAiAssistantFabEnabled(true)
|
||||
aiAssistant.setPageModuleCode(PLCT_AI_MODULE_KEY)
|
||||
await nextTick()
|
||||
if (plctPageRootRef.value) aiAssistant.setScreenshotTarget(plctPageRootRef.value)
|
||||
})
|
||||
|
||||
onActivated(() => {
|
||||
if (skipPlctActivateRefetchOnce) {
|
||||
skipPlctActivateRefetchOnce = false
|
||||
return
|
||||
}
|
||||
syncFormFromRoute()
|
||||
ensureSelectionsAfterSync()
|
||||
const rq = String(queryForm.rq || '').trim()
|
||||
const ckdm = String(queryForm.ckdm || '').trim()
|
||||
const pp = String(queryForm.pp || '').trim()
|
||||
if (rq && ckdm && pp) {
|
||||
void handleQuery()
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('beforeunload', clearPlctFlagsOnLeave)
|
||||
savePlctDataCache()
|
||||
setAiAssistantFabEnabled(false)
|
||||
aiAssistant.setPageModuleName(null)
|
||||
aiAssistant.setPageModuleCode(null)
|
||||
aiAssistant.setScreenshotTarget(null)
|
||||
const tagsViewStore = useTagsViewStore()
|
||||
if (tagsViewStore.getVisitedViews.length <= 1) {
|
||||
clearPlctFlagsOnLeave()
|
||||
}
|
||||
})
|
||||
|
||||
onDeactivated(() => {
|
||||
savePlctDataCache()
|
||||
void nextTick(() => {
|
||||
if (!routeKeepsAiAssistantFab(router.currentRoute.value)) {
|
||||
setAiAssistantFabEnabled(false)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
/** keep-alive 切回:僅當 URL 與上次拉數不一致時再同步並查詢(與 ProductDashboard 的 onActivated 不重查相輔) */
|
||||
onActivated(() => {
|
||||
setAiAssistantFabEnabled(true)
|
||||
void nextTick(() => {
|
||||
if (plctPageRootRef.value) aiAssistant.setScreenshotTarget(plctPageRootRef.value)
|
||||
})
|
||||
maybeRefetchPlctFromRouteIfChanged()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => snapshotRouteQueryKey(),
|
||||
(_key, oldKey) => {
|
||||
if (!plctBootstrapDone.value) return
|
||||
if (oldKey === undefined || oldKey === _key) return
|
||||
maybeRefetchPlctFromRouteIfChanged()
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -2250,6 +2414,9 @@ onActivated(() => {
|
||||
/** 與左欄「當日指標」外框等高 */
|
||||
align-items: stretch;
|
||||
gap: var(--plct-section-gap);
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.plct-kpi-col,
|
||||
@@ -2263,6 +2430,8 @@ onActivated(() => {
|
||||
|
||||
.plct-charts-col {
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.plct-kpi-col > .plct-upper-pane {
|
||||
@@ -2510,15 +2679,32 @@ onActivated(() => {
|
||||
@media (max-width: 1180px) {
|
||||
.plct-content-split {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
/**
|
||||
* 直向堆疊時勿再對兩塊使用 flex:1 1 0 均分父級高度,否則在矮視窗下會把「當日指標」與
|
||||
* 圖表區壓扁並被 overflow:hidden 裁掉,窄屏看起來像「兩塊都沒了」。
|
||||
*/
|
||||
.plct-kpi-col,
|
||||
.plct-charts-col {
|
||||
flex: 0 0 auto;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.plct-charts-col {
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.plct-charts-shell.plct-upper-pane {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.plct-kpi-col .plct-upper-pane--kpi {
|
||||
min-height: 340px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.plct-chart-grid {
|
||||
@@ -2527,6 +2713,18 @@ onActivated(() => {
|
||||
}
|
||||
}
|
||||
|
||||
/** 平板寬度:四宮格改單欄,避免兩列餅圖擠在過窄寬度內被裁切 */
|
||||
@media (max-width: 900px) {
|
||||
.plct-chart-grid {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: repeat(4, minmax(160px, auto));
|
||||
height: auto;
|
||||
max-height: none;
|
||||
min-height: 0;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.plct-chart-grid {
|
||||
grid-template-columns: 1fr;
|
||||
@@ -2536,6 +2734,43 @@ onActivated(() => {
|
||||
min-height: 0;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.plct-page {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.plct-field-rq {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.plct-field-ckdm,
|
||||
.plct-field-pp {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.plct-query-form {
|
||||
flex: 1 1 100%;
|
||||
|
||||
:deep(.el-form--inline .el-form-item) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
:deep(.el-form--inline .el-form-item__label) {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
:deep(.el-form--inline .el-form-item__content) {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plct-bullet-row {
|
||||
|
||||
@@ -373,6 +373,7 @@ import { useAiModulePromptEditor } from '@/hooks/web/useAiModulePromptEditor'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { AiModulePromptApi } from '@/api/ydoyun/aiModulePrompt'
|
||||
import AiPromptEditDialog from '../lijun/reportpage6/components/AiPromptEditDialog.vue'
|
||||
import { routeKeepsAiAssistantFab } from '@/components/AiAssistant/aiFabRoute'
|
||||
|
||||
defineOptions({ name: 'SalesDailyAiReport' })
|
||||
|
||||
@@ -758,22 +759,17 @@ watch(weatherPrimaryStackRef, () => nextTick(bindWeatherPrimaryStackResizeObserv
|
||||
flush: 'post'
|
||||
})
|
||||
|
||||
/** 與 onMounted 一致:從 keep-alive 再次進入時重拉報表(避免分頁切回仍為舊數據);首次掛載已由 onMounted 查詢,此處跳過一次 */
|
||||
let skipSalesDailyActivateRefetchOnce = true
|
||||
|
||||
/** 與 ProductDashboard 一致:keep-alive 切回本頁不重拉接口,由緩存實例保留表單與報表數據 */
|
||||
onActivated(() => {
|
||||
setAiAssistantFabEnabled(true)
|
||||
if (skipSalesDailyActivateRefetchOnce) {
|
||||
skipSalesDailyActivateRefetchOnce = false
|
||||
return
|
||||
}
|
||||
if (queryParams.ckdm && queryParams.rq) {
|
||||
void handleQuery()
|
||||
}
|
||||
})
|
||||
|
||||
onDeactivated(() => {
|
||||
void nextTick(() => {
|
||||
if (!routeKeepsAiAssistantFab(router.currentRoute.value)) {
|
||||
setAiAssistantFabEnabled(false)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
Reference in New Issue
Block a user