1. 新增模块品类穿透
This commit is contained in:
148
AIRBT.html
148
AIRBT.html
@@ -1,148 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>AI 决策通</title>
|
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
||||||
<style>
|
|
||||||
.ai-drawer { transition: all 0.5s cubic-bezier(0.16, 1, 0.3, 1); transform: translateX(110%); }
|
|
||||||
.ai-drawer.active { transform: translateX(0); }
|
|
||||||
.ai-drawer.fullscreen { width: 85vw !important; height: 80vh !important; right: 7.5vw !important; top: 10vh !important; border-radius: 1.5rem !important; transform: translateX(0); }
|
|
||||||
.typing::after { content: '|'; animation: blink 1s infinite; margin-left: 2px; color: #3B82F6; }
|
|
||||||
@keyframes blink { 50% { opacity: 0; } }
|
|
||||||
.custom-scroll::-webkit-scrollbar { width: 5px; }
|
|
||||||
.custom-scroll::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; }
|
|
||||||
#overlay { display: none; position: fixed; inset: 0; background: rgba(15, 23, 42, 0.4); backdrop-filter: blur(4px); z-index: 55; }
|
|
||||||
#overlay.active { display: block; }
|
|
||||||
#fileInput, #imageInput { display: none; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body class="bg-[#F1F5F9]">
|
|
||||||
|
|
||||||
<div id="overlay" onclick="closeAI()"></div>
|
|
||||||
|
|
||||||
<div class="fixed bottom-[85px] right-6 z-50" id="aiFab">
|
|
||||||
<button onclick="openAI()" class="flex items-center gap-2 bg-white border border-blue-100 px-5 py-3 rounded-full shadow-2xl hover:scale-105 transition-all group">
|
|
||||||
<div class="w-8 h-8 bg-blue-600 rounded-full flex items-center justify-center text-white shadow-lg shadow-blue-200">
|
|
||||||
<i class="fas fa-robot text-sm"></i>
|
|
||||||
</div>
|
|
||||||
<span class="text-blue-600 font-bold text-sm tracking-wide">AI 决策助手</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="aiPanel" class="fixed top-0 right-0 h-full w-[420px] bg-white shadow-2xl z-[60] ai-drawer flex flex-col overflow-hidden border-l border-slate-100">
|
|
||||||
|
|
||||||
<div class="px-6 py-4 border-b flex items-center justify-between bg-white shrink-0">
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<div class="w-10 h-10 bg-blue-600 rounded-xl flex items-center justify-center text-white shadow-md shadow-blue-100">
|
|
||||||
<i class="fas fa-robot text-lg"></i>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 class="font-bold text-slate-800 text-sm">AI 决策助手</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-1">
|
|
||||||
<button onclick="toggleFullscreen()" class="w-9 h-9 rounded-lg hover:bg-slate-100 flex items-center justify-center text-slate-500">
|
|
||||||
<i class="fas fa-expand-alt" id="expandIcon"></i>
|
|
||||||
</button>
|
|
||||||
<button onclick="closeAI()" class="w-9 h-9 rounded-lg hover:bg-red-50 flex items-center justify-center text-slate-400 hover:text-red-500">
|
|
||||||
<i class="fas fa-times"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="chatBox" class="flex-1 overflow-y-auto p-6 space-y-6 bg-[#FBFCFE] custom-scroll">
|
|
||||||
<div class="flex gap-4 items-start">
|
|
||||||
<div class="w-8 h-8 bg-blue-50 text-blue-600 rounded-lg flex items-center justify-center shrink-0 mt-1 border border-blue-100">
|
|
||||||
<i class="fas fa-robot text-xs"></i>
|
|
||||||
</div>
|
|
||||||
<div class="bg-white border border-slate-200 p-4 rounded-2xl rounded-tl-none shadow-sm text-sm text-slate-700">
|
|
||||||
您好!请发送数据或输入指令。
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="p-4 bg-white border-t border-slate-100 shrink-0">
|
|
||||||
<div class="max-w-4xl mx-auto flex flex-col">
|
|
||||||
|
|
||||||
<div class="flex gap-1 mb-2">
|
|
||||||
<input type="file" id="fileInput" accept=".xls,.xlsx,.csv" onchange="handleFileUpload(event, 'excel')">
|
|
||||||
<input type="file" id="imageInput" accept="image/*" onchange="handleFileUpload(event, 'image')">
|
|
||||||
|
|
||||||
<button onclick="triggerInput('image')" class="w-8 h-8 flex items-center justify-center text-slate-400 hover:text-blue-600 hover:bg-blue-50 rounded-md transition-all">
|
|
||||||
<i class="fas fa-image"></i>
|
|
||||||
</button>
|
|
||||||
<button onclick="triggerInput('excel')" class="w-8 h-8 flex items-center justify-center text-slate-400 hover:text-emerald-600 hover:bg-emerald-50 rounded-md transition-all">
|
|
||||||
<i class="fas fa-file-excel"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center gap-3 bg-slate-100 rounded-2xl px-5 py-1.5 border border-transparent focus-within:bg-white focus-within:border-blue-500 transition-all">
|
|
||||||
<input id="chatInput" type="text" placeholder="输入指令..."
|
|
||||||
class="flex-1 bg-transparent py-2.5 text-sm outline-none text-slate-700 font-medium"
|
|
||||||
onkeypress="if(event.key==='Enter') sendMsg()">
|
|
||||||
|
|
||||||
<button onclick="sendMsg()" class="w-9 h-9 bg-blue-600 text-white rounded-xl flex items-center justify-center hover:bg-blue-700 shadow-lg shadow-blue-100 active:scale-95 transition-all">
|
|
||||||
<i class="fas fa-paper-plane text-xs"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const panel = document.getElementById('aiPanel');
|
|
||||||
const chatBox = document.getElementById('chatBox');
|
|
||||||
const input = document.getElementById('chatInput');
|
|
||||||
const overlay = document.getElementById('overlay');
|
|
||||||
const fab = document.getElementById('aiFab');
|
|
||||||
const icon = document.getElementById('expandIcon');
|
|
||||||
|
|
||||||
function openAI() { panel.classList.add('active'); overlay.classList.add('active'); fab.style.opacity = '0'; }
|
|
||||||
function closeAI() { panel.classList.remove('active'); panel.classList.remove('fullscreen'); overlay.classList.remove('active'); fab.style.opacity = '1'; icon.className = 'fas fa-expand-alt'; }
|
|
||||||
function toggleFullscreen() { panel.classList.toggle('fullscreen'); icon.className = panel.classList.contains('fullscreen') ? 'fas fa-compress-alt' : 'fas fa-expand-alt'; }
|
|
||||||
function triggerInput(type) { document.getElementById(type === 'image' ? 'imageInput' : 'fileInput').click(); }
|
|
||||||
|
|
||||||
function handleFileUpload(event, type) {
|
|
||||||
const file = event.target.files[0];
|
|
||||||
if (!file) return;
|
|
||||||
const contentHtml = type === 'image' ? `<img src="${URL.createObjectURL(file)}" class="rounded-lg max-w-full">` : `<div class="flex items-center gap-3 bg-white/10 p-3 rounded-xl border border-white/20"><div class="w-8 h-8 bg-emerald-500 rounded flex items-center justify-center text-white shrink-0"><i class="fas fa-file-excel text-xs"></i></div><p class="text-xs font-bold truncate">${file.name}</p></div>`;
|
|
||||||
const msgHtml = `<div class="flex justify-end"><div class="bg-blue-600 text-white p-3 rounded-2xl rounded-tr-none text-sm max-w-[85%] shadow-lg">${contentHtml}</div></div>`;
|
|
||||||
chatBox.insertAdjacentHTML('beforeend', msgHtml);
|
|
||||||
chatBox.scrollTop = chatBox.scrollHeight;
|
|
||||||
setTimeout(() => {
|
|
||||||
const aiId = 'ai-' + Date.now();
|
|
||||||
const aiHtml = `<div class="flex gap-4 items-start"><div class="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center text-white shrink-0 mt-1 shadow-sm"><i class="fas fa-robot text-xs"></i></div><div class="bg-white border border-slate-200 p-4 rounded-2xl rounded-tl-none shadow-sm text-sm text-slate-700 flex-1"><p id="${aiId}" class="typing leading-relaxed"></p></div></div>`;
|
|
||||||
chatBox.insertAdjacentHTML('beforeend', aiHtml);
|
|
||||||
chatBox.scrollTop = chatBox.scrollHeight;
|
|
||||||
typeWriter(`正在分析您的文件数据...`, aiId);
|
|
||||||
}, 800);
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendMsg() {
|
|
||||||
const text = input.value.trim();
|
|
||||||
if(!text) return;
|
|
||||||
const userHtml = `<div class="flex justify-end"><div class="bg-blue-600 text-white p-4 rounded-2xl rounded-tr-none text-sm max-w-[80%] shadow-lg shadow-blue-100/50">${text}</div></div>`;
|
|
||||||
chatBox.insertAdjacentHTML('beforeend', userHtml);
|
|
||||||
input.value = '';
|
|
||||||
chatBox.scrollTop = chatBox.scrollHeight;
|
|
||||||
setTimeout(() => {
|
|
||||||
const aiId = 'ai-' + Date.now();
|
|
||||||
const aiHtml = `<div class="flex gap-4 items-start"><div class="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center text-white shrink-0 mt-1 border border-blue-100"><i class="fas fa-robot text-xs"></i></div><div class="bg-white border border-slate-200 p-4 rounded-2xl rounded-tl-none shadow-sm text-sm text-slate-700 flex-1"><p id="${aiId}" class="typing leading-relaxed"></p></div></div>`;
|
|
||||||
chatBox.insertAdjacentHTML('beforeend', aiHtml);
|
|
||||||
chatBox.scrollTop = chatBox.scrollHeight;
|
|
||||||
typeWriter(`正在处理您的指令...`, aiId);
|
|
||||||
}, 600);
|
|
||||||
}
|
|
||||||
|
|
||||||
function typeWriter(text, elementId) {
|
|
||||||
let i = 0;
|
|
||||||
const el = document.getElementById(elementId);
|
|
||||||
function type() { if (i < text.length) { el.innerHTML += text.charAt(i); i++; chatBox.scrollTop = chatBox.scrollHeight; setTimeout(type, 30); } else { el.classList.remove('typing'); } }
|
|
||||||
type();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -21,6 +21,8 @@ export interface ExecuteProcedureParams {
|
|||||||
* 注意与 axios 的 `params` 配置重名,此处为查询参数字段名
|
* 注意与 axios 的 `params` 配置重名,此处为查询参数字段名
|
||||||
*/
|
*/
|
||||||
params?: string
|
params?: string
|
||||||
|
/** 存储过程入参 @ztj(如 YDY_AI_GET_SPTPQ 正特价维度) */
|
||||||
|
ztj?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 日报表-当日总销售数据 */
|
/** 日报表-当日总销售数据 */
|
||||||
|
|||||||
@@ -56,7 +56,9 @@ const styles = computed(() => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
width,
|
width,
|
||||||
height
|
height,
|
||||||
|
/** 嵌套 flex 時避免高度鏈斷裂導致圖表區變矮、模組下半截空白 */
|
||||||
|
minHeight: height === '100%' ? '100%' : undefined
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -775,7 +775,10 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
hidden: true,
|
hidden: true,
|
||||||
canTo: true
|
canTo: true
|
||||||
},
|
},
|
||||||
component: () => import('@/views/ydoyun/report/lijun/reportpage6/components/categoryCardListComponents.vue')
|
component: () =>
|
||||||
|
import(
|
||||||
|
'@/views/ydoyun/report/lijun/reportpage6/components/categoryCardListComponents.vue'
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'lijun/supplier-ranking',
|
path: 'lijun/supplier-ranking',
|
||||||
@@ -786,7 +789,8 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
hidden: true,
|
hidden: true,
|
||||||
canTo: true
|
canTo: true
|
||||||
},
|
},
|
||||||
component: () => import('@/views/ydoyun/report/lijun/reportpage6/components/SupplierRanking.vue')
|
component: () =>
|
||||||
|
import('@/views/ydoyun/report/lijun/reportpage6/components/SupplierRanking.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'lijun/supplier-performance',
|
path: 'lijun/supplier-performance',
|
||||||
@@ -796,7 +800,8 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
hidden: true,
|
hidden: true,
|
||||||
canTo: true
|
canTo: true
|
||||||
},
|
},
|
||||||
component: () => import('@/views/ydoyun/report/lijun/reportpage6/components/SupplierPerformancePage.vue')
|
component: () =>
|
||||||
|
import('@/views/ydoyun/report/lijun/reportpage6/components/SupplierPerformancePage.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'lijun/middle-class-ranking',
|
path: 'lijun/middle-class-ranking',
|
||||||
@@ -807,7 +812,8 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
hidden: true,
|
hidden: true,
|
||||||
canTo: true
|
canTo: true
|
||||||
},
|
},
|
||||||
component: () => import('@/views/ydoyun/report/lijun/reportpage6/components/MiddleClassRanking.vue')
|
component: () =>
|
||||||
|
import('@/views/ydoyun/report/lijun/reportpage6/components/MiddleClassRanking.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'lijun/product-cards',
|
path: 'lijun/product-cards',
|
||||||
@@ -818,7 +824,8 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
hidden: true,
|
hidden: true,
|
||||||
canTo: true
|
canTo: true
|
||||||
},
|
},
|
||||||
component: () => import('@/views/ydoyun/report/lijun/reportpage6/components/ProductCardsPage.vue')
|
component: () =>
|
||||||
|
import('@/views/ydoyun/report/lijun/reportpage6/components/ProductCardsPage.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'lijun/reportpage6/detail',
|
path: 'lijun/reportpage6/detail',
|
||||||
@@ -841,6 +848,28 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
canTo: true
|
canTo: true
|
||||||
},
|
},
|
||||||
component: () => import('@/views/ydoyun/report/wangbuliao/productSplb/index.vue')
|
component: () => import('@/views/ydoyun/report/wangbuliao/productSplb/index.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'product-daily/top-wall-more',
|
||||||
|
name: 'ProductDailyTopWallMore',
|
||||||
|
meta: {
|
||||||
|
title: 'TOP图片墙 - 更多',
|
||||||
|
noCache: true,
|
||||||
|
hidden: true,
|
||||||
|
canTo: true
|
||||||
|
},
|
||||||
|
component: () => import('@/views/ydoyun/report/productDaily/TopWallMore.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'sales-daily/category-penetration',
|
||||||
|
name: 'SalesDailyCategoryPenetration',
|
||||||
|
meta: {
|
||||||
|
title: '品类穿透看板',
|
||||||
|
noCache: true,
|
||||||
|
hidden: true,
|
||||||
|
canTo: true
|
||||||
|
},
|
||||||
|
component: () => import('@/views/ydoyun/report/salesdaily/CategoryPenetrationBoard.vue')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -860,7 +889,8 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
noCache: true,
|
noCache: true,
|
||||||
canTo: true
|
canTo: true
|
||||||
},
|
},
|
||||||
component: () => import(/* webpackChunkName: "custom-tag" */ '@/views/ydoyun/customtag/index.vue')
|
component: () =>
|
||||||
|
import(/* webpackChunkName: "custom-tag" */ '@/views/ydoyun/customtag/index.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'product-custom-tag',
|
path: 'product-custom-tag',
|
||||||
@@ -870,7 +900,10 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
noCache: true,
|
noCache: true,
|
||||||
canTo: true
|
canTo: true
|
||||||
},
|
},
|
||||||
component: () => import(/* webpackChunkName: "product-custom-tag" */ '@/views/ydoyun/productcustomtag/index.vue')
|
component: () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "product-custom-tag" */ '@/views/ydoyun/productcustomtag/index.vue'
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'store-custom-tag',
|
path: 'store-custom-tag',
|
||||||
@@ -880,7 +913,10 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
noCache: true,
|
noCache: true,
|
||||||
canTo: true
|
canTo: true
|
||||||
},
|
},
|
||||||
component: () => import(/* webpackChunkName: "store-custom-tag" */ '@/views/ydoyun/storecustomtag/index.vue')
|
component: () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "store-custom-tag" */ '@/views/ydoyun/storecustomtag/index.vue'
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'supplier-custom-tag',
|
path: 'supplier-custom-tag',
|
||||||
@@ -890,7 +926,10 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
noCache: true,
|
noCache: true,
|
||||||
canTo: true
|
canTo: true
|
||||||
},
|
},
|
||||||
component: () => import(/* webpackChunkName: "supplier-custom-tag" */ '@/views/ydoyun/suppliercustomtag/index.vue')
|
component: () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "supplier-custom-tag" */ '@/views/ydoyun/suppliercustomtag/index.vue'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -207,7 +207,7 @@
|
|||||||
<div class="value">{{ rawCellValue(summary.ZTJ ?? summary.ztj ?? summary.zjzb) }}</div>
|
<div class="value">{{ rawCellValue(summary.ZTJ ?? summary.ztj ?? summary.zjzb) }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 第 3 行:同比业绩、同比毛利率、库存量 -->
|
<!-- 第 3 行:同比业绩、同比毛利率、库存量、周转天 -->
|
||||||
<div class="stat-box">
|
<div class="stat-box">
|
||||||
<span class="label">同比业绩</span>
|
<span class="label">同比业绩</span>
|
||||||
<div class="value">{{ formatMoney(summary.TBYJ) }}</div>
|
<div class="value">{{ formatMoney(summary.TBYJ) }}</div>
|
||||||
@@ -220,6 +220,10 @@
|
|||||||
<span class="label">库存量</span>
|
<span class="label">库存量</span>
|
||||||
<div class="value">{{ formatNumber(summary.kcl) }}</div>
|
<div class="value">{{ formatNumber(summary.kcl) }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="stat-box">
|
||||||
|
<span class="label">周转天</span>
|
||||||
|
<div class="value">{{ formatNumber(summary.zzt ?? summary.ZZT) }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -230,7 +234,7 @@
|
|||||||
>
|
>
|
||||||
<div class="card-title trend-title">
|
<div class="card-title trend-title">
|
||||||
<span class="trend-title-main">TOP图片墙</span>
|
<span class="trend-title-main">TOP图片墙</span>
|
||||||
<div class="trend-title-sub trend-inline-filters">
|
<div class="trend-title-sub trend-inline-filters trend-image-wall-filters">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="imageFilter.storeValues"
|
v-model="imageFilter.storeValues"
|
||||||
multiple
|
multiple
|
||||||
@@ -293,6 +297,16 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</div>
|
</div>
|
||||||
|
<el-button
|
||||||
|
v-if="isProductDailyReport"
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
class="trend-more-link"
|
||||||
|
@click="goTopWallMorePage"
|
||||||
|
>
|
||||||
|
更多
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="trend-chart-wrap trend-product-wrap"
|
class="trend-chart-wrap trend-product-wrap"
|
||||||
@@ -442,12 +456,33 @@
|
|||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="imagePreviewVisible"
|
v-model="imagePreviewVisible"
|
||||||
title="图片详情"
|
title="图片详情"
|
||||||
width="680px"
|
width="720px"
|
||||||
append-to-body
|
append-to-body
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
|
class="image-detail-dialog"
|
||||||
|
@closed="resetImagePreviewState"
|
||||||
>
|
>
|
||||||
<div class="image-preview-wrap">
|
<div class="image-preview-body">
|
||||||
<img v-if="currentPreviewImage" :src="currentPreviewImage" class="image-preview-main" alt="图片详情" />
|
<div class="image-preview-wrap">
|
||||||
|
<img
|
||||||
|
v-if="currentPreviewImage"
|
||||||
|
:src="currentPreviewImage"
|
||||||
|
class="image-preview-main"
|
||||||
|
alt="商品图片"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="previewImageDetailEntries.length" class="image-preview-detail">
|
||||||
|
<div class="image-preview-detail-title">全部信息</div>
|
||||||
|
<el-descriptions :column="2" border size="small" class="image-preview-descriptions">
|
||||||
|
<el-descriptions-item
|
||||||
|
v-for="ent in previewImageDetailEntries"
|
||||||
|
:key="ent.key"
|
||||||
|
:label="ent.label"
|
||||||
|
>
|
||||||
|
<span class="image-preview-detail-value">{{ ent.value }}</span>
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
@@ -457,7 +492,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { computed, nextTick, onActivated, onDeactivated, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
|
import { computed, nextTick, onActivated, onDeactivated, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { ReportApi, type ExecuteProcedureParams } from '@/api/ydoyun/report/reportpage'
|
import { ReportApi, type ExecuteProcedureParams } from '@/api/ydoyun/report/reportpage'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
import { Icon } from '@/components/Icon'
|
import { Icon } from '@/components/Icon'
|
||||||
@@ -470,6 +505,7 @@ import AiPromptEditDialog from '../lijun/reportpage6/components/AiPromptEditDial
|
|||||||
defineOptions({ name: 'ProductDaily' })
|
defineOptions({ name: 'ProductDaily' })
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
const reportScene = computed(() => String((route.meta as any)?.query?.scene || '').trim().toLowerCase())
|
const reportScene = computed(() => String((route.meta as any)?.query?.scene || '').trim().toLowerCase())
|
||||||
const isProductDailyReport = computed(() => reportScene.value === 'product')
|
const isProductDailyReport = computed(() => reportScene.value === 'product')
|
||||||
/** 与 setPageModuleCode、AiPromptEditDialog 保存逻辑一致:component = moduleKey 冒号前一段 */
|
/** 与 setPageModuleCode、AiPromptEditDialog 保存逻辑一致:component = moduleKey 冒号前一段 */
|
||||||
@@ -593,6 +629,31 @@ const daleiOptions = ref<Array<{ label: string; value: string }>>([])
|
|||||||
const ztjOptions = ref<Array<{ label: string; value: string }>>([])
|
const ztjOptions = ref<Array<{ label: string; value: string }>>([])
|
||||||
const zhongleiOptions = ref<Array<{ label: string; value: string }>>([])
|
const zhongleiOptions = ref<Array<{ label: string; value: string }>>([])
|
||||||
|
|
||||||
|
/** TOP 明细页 ckdm:与图片墙「门店」大图筛选一致(cangku 模式用选中门店,否则主表门店) */
|
||||||
|
const topWallMoreCkdmParam = computed(() => {
|
||||||
|
if (imageFilter.activeType === 'cangku' && imageFilter.storeValues.length) {
|
||||||
|
return imageFilter.storeValues.map((v) => String(v).trim()).filter(Boolean).join(',')
|
||||||
|
}
|
||||||
|
return ckdmQueryParam.value
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 跳转 TOP图片墙明细:携带门店 / 日期 / 品牌 / 正特价 */
|
||||||
|
function goTopWallMorePage() {
|
||||||
|
const rqStr = String(queryParams.rq || '').trim()
|
||||||
|
router.push({
|
||||||
|
path: '/reports/product-daily/top-wall-more',
|
||||||
|
query: {
|
||||||
|
fromProductDaily: '1',
|
||||||
|
pp: String(queryParams.brandCode || ''),
|
||||||
|
ckdm: topWallMoreCkdmParam.value,
|
||||||
|
rq: rqStr,
|
||||||
|
rq_s: rqStr,
|
||||||
|
rq_e: rqStr,
|
||||||
|
ztj: String(imageFilter.ztjValue || '').trim()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const routeSelectOptions = computed(() => {
|
const routeSelectOptions = computed(() => {
|
||||||
const seen = new Set<string>()
|
const seen = new Set<string>()
|
||||||
return userStoreRows.value
|
return userStoreRows.value
|
||||||
@@ -895,11 +956,20 @@ const summary = reactive<any>({})
|
|||||||
const categoryRowsRaw = ref<any[]>([])
|
const categoryRowsRaw = ref<any[]>([])
|
||||||
/** 仓库节点是否展开(默认折叠,点击箭头展开子行) */
|
/** 仓库节点是否展开(默认折叠,点击箭头展开子行) */
|
||||||
const warehouseExpanded = ref<Record<string, boolean>>({})
|
const warehouseExpanded = ref<Record<string, boolean>>({})
|
||||||
const productImageRows = ref<Array<{ spdm: string; pic: string }>>([])
|
/** TOP 图片墙行:含接口完整 raw,便于详情弹窗展示全部字段 */
|
||||||
|
interface ProductImageWallItem {
|
||||||
|
spdm: string
|
||||||
|
pic: string
|
||||||
|
raw: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
const productImageRows = ref<ProductImageWallItem[]>([])
|
||||||
const topProductImageRows = computed(() => productImageRows.value.slice(0, 10))
|
const topProductImageRows = computed(() => productImageRows.value.slice(0, 10))
|
||||||
const imageWallLoading = ref(false)
|
const imageWallLoading = ref(false)
|
||||||
const imagePreviewVisible = ref(false)
|
const imagePreviewVisible = ref(false)
|
||||||
const currentPreviewImage = ref('')
|
const currentPreviewImage = ref('')
|
||||||
|
/** 图片详情:与 SPRB2 返回行一致,用于列出全部键值 */
|
||||||
|
const currentPreviewRow = ref<Record<string, any> | null>(null)
|
||||||
|
|
||||||
function clearReactiveObject(obj: Record<string, any>) {
|
function clearReactiveObject(obj: Record<string, any>) {
|
||||||
Object.keys(obj).forEach((k) => delete obj[k])
|
Object.keys(obj).forEach((k) => delete obj[k])
|
||||||
@@ -997,6 +1067,7 @@ async function loadSummary() {
|
|||||||
if (anySum.PJZ == null && anySum.pjz != null) anySum.PJZ = anySum.pjz
|
if (anySum.PJZ == null && anySum.pjz != null) anySum.PJZ = anySum.pjz
|
||||||
if (anySum.zjzb == null && anySum.ZJZB != null) anySum.zjzb = anySum.ZJZB
|
if (anySum.zjzb == null && anySum.ZJZB != null) anySum.zjzb = anySum.ZJZB
|
||||||
if (anySum.kcl == null && anySum.KCL != null) anySum.kcl = anySum.KCL
|
if (anySum.kcl == null && anySum.KCL != null) anySum.kcl = anySum.KCL
|
||||||
|
if (anySum.zzt == null && anySum.ZZT != null) anySum.zzt = anySum.ZZT
|
||||||
// 毛利率:YDY_AI_GET_SXRB 可能返回 mll / MLL / ML
|
// 毛利率:YDY_AI_GET_SXRB 可能返回 mll / MLL / ML
|
||||||
if (anySum.mll == null && anySum.MLL != null) anySum.mll = anySum.MLL
|
if (anySum.mll == null && anySum.MLL != null) anySum.mll = anySum.MLL
|
||||||
if (anySum.mll == null && anySum.ML != null) anySum.mll = anySum.ML
|
if (anySum.mll == null && anySum.ML != null) anySum.mll = anySum.ML
|
||||||
@@ -1178,10 +1249,14 @@ async function loadProductImageList() {
|
|||||||
p: PROCEDURE_SECRET,
|
p: PROCEDURE_SECRET,
|
||||||
username: username.value
|
username: username.value
|
||||||
} satisfies ExecuteProcedureParams)
|
} satisfies ExecuteProcedureParams)
|
||||||
productImageRows.value = extractProcedureList(res).map((row: any) => ({
|
productImageRows.value = extractProcedureList(res).map((row: any) => {
|
||||||
spdm: String(row.spdm ?? row.SPDM ?? '').trim(),
|
const r = row && typeof row === 'object' ? row : {}
|
||||||
pic: normalizeWeatherIconUrl(String(row.pic ?? row.PIC ?? '').trim())
|
return {
|
||||||
}))
|
spdm: String(r.spdm ?? r.SPDM ?? '').trim(),
|
||||||
|
pic: normalizeWeatherIconUrl(String(r.pic ?? r.PIC ?? '').trim()),
|
||||||
|
raw: r as Record<string, any>
|
||||||
|
}
|
||||||
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('商品图片查询加载失败:', e)
|
console.warn('商品图片查询加载失败:', e)
|
||||||
productImageRows.value = []
|
productImageRows.value = []
|
||||||
@@ -1207,12 +1282,58 @@ function handleImageScopeChange(type: 'cangku' | 'dalei' | 'fjsx4' | 'fjsx1') {
|
|||||||
handleImageFilterChange()
|
handleImageFilterChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
function openImagePreview(item: { pic: string }) {
|
function openImagePreview(item: ProductImageWallItem) {
|
||||||
if (!item?.pic) return
|
if (!item?.pic) return
|
||||||
currentPreviewImage.value = item.pic
|
currentPreviewImage.value = item.pic
|
||||||
|
currentPreviewRow.value = item.raw && typeof item.raw === 'object' ? item.raw : {}
|
||||||
imagePreviewVisible.value = true
|
imagePreviewVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resetImagePreviewState() {
|
||||||
|
currentPreviewImage.value = ''
|
||||||
|
currentPreviewRow.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 图片详情弹窗:字段中文标签(未知字段仍显示键名) */
|
||||||
|
function imageDetailFieldLabel(key: string): string {
|
||||||
|
const k = key.toLowerCase()
|
||||||
|
const map: Record<string, string> = {
|
||||||
|
pic: '图片地址',
|
||||||
|
spdmt: '商品代码(拓展)',
|
||||||
|
spdm: '商品代码',
|
||||||
|
spmc: '商品名称',
|
||||||
|
ppmc: '品牌名称',
|
||||||
|
sl: '数量',
|
||||||
|
ckdm: '仓库/门店代码',
|
||||||
|
ckmc: '仓库/门店名称'
|
||||||
|
}
|
||||||
|
return map[k] ?? key
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatImageDetailValue(v: any): string {
|
||||||
|
if (v == null || v === '') return '—'
|
||||||
|
if (typeof v === 'object') {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(v)
|
||||||
|
} catch {
|
||||||
|
return String(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const s = String(v).trim()
|
||||||
|
return s === '' ? '—' : s
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 弹窗中按接口原字段顺序展示全部信息 */
|
||||||
|
const previewImageDetailEntries = computed(() => {
|
||||||
|
const row = currentPreviewRow.value
|
||||||
|
if (!row || typeof row !== 'object') return [] as { key: string; label: string; value: string }[]
|
||||||
|
return Object.keys(row).map((key) => ({
|
||||||
|
key,
|
||||||
|
label: imageDetailFieldLabel(key),
|
||||||
|
value: formatImageDetailValue(row[key])
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
// ========= 字段归一/展示 =========
|
// ========= 字段归一/展示 =========
|
||||||
function toFiniteNumber(v: any): number {
|
function toFiniteNumber(v: any): number {
|
||||||
if (v == null || v === '') return NaN
|
if (v == null || v === '') return NaN
|
||||||
@@ -1930,8 +2051,22 @@ function formatAmountSlashQuantity(v: any): string {
|
|||||||
.trend-title {
|
.trend-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: baseline;
|
align-items: center;
|
||||||
gap: 4px 10px;
|
gap: 6px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 中部筛选项占满中间区域,「更多」自然靠最右 */
|
||||||
|
.trend-image-wall-filters {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trend-more-link {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
padding: 0 2px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.trend-title-main {
|
.trend-title-main {
|
||||||
@@ -2087,19 +2222,52 @@ function formatAmountSlashQuantity(v: any): string {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.image-preview-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
max-height: 85vh;
|
||||||
|
}
|
||||||
|
|
||||||
.image-preview-wrap {
|
.image-preview-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 260px;
|
min-height: 200px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-preview-main {
|
.image-preview-main {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 70vh;
|
max-height: 46vh;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.image-preview-detail {
|
||||||
|
min-height: 0;
|
||||||
|
max-height: 38vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-preview-detail-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-preview-descriptions {
|
||||||
|
:deep(.el-descriptions__label) {
|
||||||
|
width: 120px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-preview-detail-value {
|
||||||
|
word-break: break-all;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
.trend-chart-inner {
|
.trend-chart-inner {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 160px;
|
min-height: 160px;
|
||||||
|
|||||||
@@ -256,8 +256,13 @@
|
|||||||
element-loading-text="加载中..."
|
element-loading-text="加载中..."
|
||||||
>
|
>
|
||||||
<div class="card-title category-title">
|
<div class="card-title category-title">
|
||||||
<span class="category-title-main">品类全维度分析</span>
|
<div class="category-title-left">
|
||||||
<span class="category-title-sub">实收/标准/正价/特价/对比/目标</span>
|
<span class="category-title-main">品类全维度分析</span>
|
||||||
|
<span class="category-title-sub">实收/标准/正价/特价/对比/目标</span>
|
||||||
|
</div>
|
||||||
|
<el-button type="primary" link class="category-image-wall-link" @click="goProductImageWall">
|
||||||
|
商品图片墙
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-scroll">
|
<div class="table-scroll">
|
||||||
@@ -293,7 +298,9 @@
|
|||||||
<tr v-for="(row, idx) in categoryRows" :key="row.PP || idx">
|
<tr v-for="(row, idx) in categoryRows" :key="row.PP || idx">
|
||||||
<td class="sxrb-td-sticky sxrb-col-brand">
|
<td class="sxrb-td-sticky sxrb-col-brand">
|
||||||
<div class="sxrb-brand-cell">
|
<div class="sxrb-brand-cell">
|
||||||
<span class="sxrb-brand-name">{{ row.PP || '-' }}</span>
|
<el-button link type="primary" class="sxrb-brand-btn" @click="goCategoryPenetration(row)">
|
||||||
|
<span class="sxrb-brand-name">{{ row.PP || '-' }}</span>
|
||||||
|
</el-button>
|
||||||
<span class="sxrb-brand-zb">{{ formatCategoryPpzb(row.PPZB) }}</span>
|
<span class="sxrb-brand-zb">{{ formatCategoryPpzb(row.PPZB) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -354,7 +361,7 @@ import dayjs from 'dayjs'
|
|||||||
import type { EChartsOption } from 'echarts'
|
import type { EChartsOption } from 'echarts'
|
||||||
import { useCssVar } from '@vueuse/core'
|
import { useCssVar } from '@vueuse/core'
|
||||||
import { computed, nextTick, onActivated, onDeactivated, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
|
import { computed, nextTick, onActivated, onDeactivated, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { ReportApi, type ExecuteProcedureParams } from '@/api/ydoyun/report/reportpage'
|
import { ReportApi, type ExecuteProcedureParams } from '@/api/ydoyun/report/reportpage'
|
||||||
import { getWeatherAggregate } from '@/api/ydoyun/weather'
|
import { getWeatherAggregate } from '@/api/ydoyun/weather'
|
||||||
import { useAppStore } from '@/store/modules/app'
|
import { useAppStore } from '@/store/modules/app'
|
||||||
@@ -374,6 +381,69 @@ const SALES_DAILY_COMPONENT = 'SalesDaily'
|
|||||||
const SALES_DAILY_MODULE_KEY = `${SALES_DAILY_COMPONENT}:main`
|
const SALES_DAILY_MODULE_KEY = `${SALES_DAILY_COMPONENT}:main`
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
/** 品类行品牌代号(存储过程 YDY_AI_GET_PLCT1 的 @pp):优先 PPDM */
|
||||||
|
function resolveCategoryRowPpCode(row: Record<string, any>): string {
|
||||||
|
return String(
|
||||||
|
row?.PPDM ??
|
||||||
|
row?.ppdm ??
|
||||||
|
row?.PPBH ??
|
||||||
|
row?.ppbh ??
|
||||||
|
row?.BH ??
|
||||||
|
row?.bh ??
|
||||||
|
row?.CODE ??
|
||||||
|
row?.code ??
|
||||||
|
row?.PP ??
|
||||||
|
row?.pp ??
|
||||||
|
''
|
||||||
|
).trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 跳转品类全维度穿透看板 */
|
||||||
|
function goCategoryPenetration(row: Record<string, any>) {
|
||||||
|
const pp = resolveCategoryRowPpCode(row)
|
||||||
|
if (!pp) {
|
||||||
|
ElMessage.warning('该行缺少品牌代号,无法穿透')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!String(queryParams.ckdm || '').trim() || !String(queryParams.rq || '').trim()) {
|
||||||
|
ElMessage.warning('请先选择门店与日期并完成查询')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
router.push({
|
||||||
|
path: '/reports/sales-daily/category-penetration',
|
||||||
|
query: {
|
||||||
|
rq: String(queryParams.rq).trim(),
|
||||||
|
ckdm: String(queryParams.ckdm).trim(),
|
||||||
|
pp,
|
||||||
|
ppmc: String(row.PP ?? row.pp ?? '').trim()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 跳转商品图片墙明细(与商品日报「更多」同源页,携带门店/日期) */
|
||||||
|
function goProductImageWall() {
|
||||||
|
if (!String(queryParams.ckdm || '').trim()) {
|
||||||
|
ElMessage.warning('请先选择门店')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!String(queryParams.rq || '').trim()) {
|
||||||
|
ElMessage.warning('请先选择日期')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const rqStr = String(queryParams.rq).trim()
|
||||||
|
router.push({
|
||||||
|
path: '/reports/product-daily/top-wall-more',
|
||||||
|
query: {
|
||||||
|
fromSalesDaily: '1',
|
||||||
|
ckdm: String(queryParams.ckdm).trim(),
|
||||||
|
rq: rqStr,
|
||||||
|
rq_s: rqStr,
|
||||||
|
rq_e: rqStr
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
/** 每日汇报模块名称:与后台菜单名称一致(meta.title),缺省为「销售日报」 */
|
/** 每日汇报模块名称:与后台菜单名称一致(meta.title),缺省为「销售日报」 */
|
||||||
const aiMenuModuleName = computed(() => {
|
const aiMenuModuleName = computed(() => {
|
||||||
const t = route.meta?.title
|
const t = route.meta?.title
|
||||||
@@ -688,8 +758,18 @@ watch(weatherPrimaryStackRef, () => nextTick(bindWeatherPrimaryStackResizeObserv
|
|||||||
flush: 'post'
|
flush: 'post'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** 與 onMounted 一致:從 keep-alive 再次進入時重拉報表(避免分頁切回仍為舊數據);首次掛載已由 onMounted 查詢,此處跳過一次 */
|
||||||
|
let skipSalesDailyActivateRefetchOnce = true
|
||||||
|
|
||||||
onActivated(() => {
|
onActivated(() => {
|
||||||
setAiAssistantFabEnabled(true)
|
setAiAssistantFabEnabled(true)
|
||||||
|
if (skipSalesDailyActivateRefetchOnce) {
|
||||||
|
skipSalesDailyActivateRefetchOnce = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (queryParams.ckdm && queryParams.rq) {
|
||||||
|
void handleQuery()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onDeactivated(() => {
|
onDeactivated(() => {
|
||||||
@@ -1901,10 +1981,25 @@ const timeSlotBarChartOptions = computed<EChartsOption>(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.category-title {
|
.category-title {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-title-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
gap: 4px 10px;
|
gap: 4px 10px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-image-wall-link {
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-title-main {
|
.category-title-main {
|
||||||
@@ -2028,9 +2123,24 @@ const timeSlotBarChartOptions = computed<EChartsOption>(() => {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sxrb-brand-btn {
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
padding: 0 !important;
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sxrb-brand-btn :deep(.el-button__text) {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.sxrb-brand-name {
|
.sxrb-brand-name {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--el-text-color-primary);
|
color: inherit;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|||||||
143
日志汇总统计.html
143
日志汇总统计.html
@@ -1,143 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>日志填报明细 - 已填报</title>
|
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
|
||||||
<style>
|
|
||||||
body { background-color: #f0f2f5; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial; }
|
|
||||||
.card { background: white; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
||||||
.sidebar-active { border-right: 3px solid #1890ff; background: #e6f7ff; color: #1890ff; font-weight: bold; }
|
|
||||||
/* 限制内容列宽度并处理长文本溢出 */
|
|
||||||
.content-cell {
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body class="p-6">
|
|
||||||
|
|
||||||
<div class="flex justify-between items-center mb-6">
|
|
||||||
<div>
|
|
||||||
<h1 class="text-xl font-bold text-gray-800">日志填报明细</h1>
|
|
||||||
<p class="text-sm text-gray-500">查看已完成提交的员工日志内容</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-3">
|
|
||||||
<input type="date" value="2026-04-12" class="border rounded px-3 py-1.5 text-sm outline-none focus:border-blue-500 shadow-sm bg-white text-gray-600">
|
|
||||||
<button class="bg-[#1890ff] text-white px-4 py-1.5 rounded text-sm hover:bg-blue-600 transition shadow-sm font-medium">导出已报明细</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex gap-6">
|
|
||||||
<div class="w-64 flex-shrink-0 card p-4 h-fit">
|
|
||||||
<h3 class="font-bold mb-4 text-gray-700 text-sm flex items-center gap-2">
|
|
||||||
<i class="fas fa-sitemap text-blue-500"></i> 部门结构
|
|
||||||
</h3>
|
|
||||||
<ul class="space-y-1 text-sm">
|
|
||||||
<li class="p-3 sidebar-active cursor-pointer rounded flex justify-between">
|
|
||||||
<span>全公司</span> <span class="opacity-60 font-normal">128</span>
|
|
||||||
</li>
|
|
||||||
<li class="p-3 hover:bg-gray-50 cursor-pointer rounded text-gray-600 flex justify-between transition">
|
|
||||||
<span>店铺日报</span> <span class="text-gray-400 font-normal">45</span>
|
|
||||||
</li>
|
|
||||||
<li class="p-3 hover:bg-gray-50 cursor-pointer rounded text-gray-600 flex justify-between transition">
|
|
||||||
<span>采购日报</span> <span class="text-gray-400 font-normal">32</span>
|
|
||||||
</li>
|
|
||||||
<li class="p-3 hover:bg-gray-50 cursor-pointer rounded text-gray-600 flex justify-between transition">
|
|
||||||
<span>商品日报</span> <span class="text-gray-400 font-normal">51</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-1 card overflow-hidden">
|
|
||||||
<div class="border-b px-6 py-4 flex justify-between items-center bg-gray-50/50">
|
|
||||||
<div class="flex gap-8">
|
|
||||||
<button class="text-sm text-gray-400 pb-2 hover:text-gray-600 transition-all font-medium">未填报名单 (16)</button>
|
|
||||||
<button class="text-sm font-bold border-b-2 border-blue-500 pb-2 text-blue-600 transition-all">已填报明细 (112)</button>
|
|
||||||
</div>
|
|
||||||
<div class="relative">
|
|
||||||
<input type="text" placeholder="搜索姓名或内容..." class="text-xs border rounded-full px-4 py-1.5 w-64 outline-none focus:border-blue-500 shadow-sm">
|
|
||||||
<i class="fas fa-search absolute right-3 top-2 text-gray-300"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table class="w-full text-left text-sm table-fixed">
|
|
||||||
<thead class="bg-gray-50 text-gray-500 border-b">
|
|
||||||
<tr>
|
|
||||||
<th class="px-6 py-4 font-bold w-40">姓名</th>
|
|
||||||
<th class="px-6 py-4 font-bold w-56">部门</th>
|
|
||||||
<th class="px-6 py-4 font-bold">填报内容</th>
|
|
||||||
<th class="px-6 py-4 font-bold text-center w-28 uppercase text-[10px] tracking-wider">提交状态</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="divide-y text-gray-700">
|
|
||||||
<tr class="hover:bg-blue-50/30 transition group">
|
|
||||||
<td class="px-6 py-4 flex items-center gap-3">
|
|
||||||
<span class="w-8 h-8 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center text-xs font-bold shadow-sm">王</span>
|
|
||||||
<span class="font-medium text-gray-800">王小明</span>
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4">
|
|
||||||
<div class="text-gray-700 font-medium">零售事业部</div>
|
|
||||||
<div class="text-[10px] text-gray-400 uppercase tracking-tighter mt-0.5">Chenzhou Store · Manager</div>
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4">
|
|
||||||
<div class="bg-gray-50 p-2.5 rounded border border-transparent group-hover:border-gray-200 group-hover:bg-white transition relative">
|
|
||||||
<p class="text-xs text-gray-600 leading-relaxed content-cell">
|
|
||||||
今日郴州店客流量较昨日回升,成交额达到预期目标。主推的春季新款T恤表现优异,转化率约12%。下午对新入职导购进行了系统培训。
|
|
||||||
</p>
|
|
||||||
<div class="absolute right-2 bottom-1 text-[10px] text-blue-500 opacity-0 group-hover:opacity-100 transition font-bold">详情 ></div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 text-center">
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-bold bg-green-50 text-green-600 border border-green-100">
|
|
||||||
<i class="fas fa-check-circle mr-1"></i> 已提交
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr class="hover:bg-blue-50/30 transition group">
|
|
||||||
<td class="px-6 py-4 flex items-center gap-3">
|
|
||||||
<span class="w-8 h-8 rounded-full bg-indigo-100 text-indigo-600 flex items-center justify-center text-xs font-bold shadow-sm">李</span>
|
|
||||||
<span class="font-medium text-gray-800">李华</span>
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4">
|
|
||||||
<div class="text-gray-700 font-medium">采购部</div>
|
|
||||||
<div class="text-[10px] text-gray-400 uppercase tracking-tighter mt-0.5">Supply Chain · Buyer</div>
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4">
|
|
||||||
<div class="bg-gray-50 p-2.5 rounded border border-transparent group-hover:border-gray-200 group-hover:bg-white transition relative">
|
|
||||||
<p class="text-xs text-gray-600 leading-relaxed content-cell">
|
|
||||||
完成了与面料供应商的本周对账工作。确认了下周到货的夏季轻薄款面料数量及颜色。注意到原材料价格有小幅波动,已汇报给商品中心。
|
|
||||||
</p>
|
|
||||||
<div class="absolute right-2 bottom-1 text-[10px] text-blue-500 opacity-0 group-hover:opacity-100 transition font-bold">详情 ></div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 text-center">
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-bold bg-green-50 text-green-600 border border-green-100">
|
|
||||||
<i class="fas fa-check-circle mr-1"></i> 已提交
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<div class="px-6 py-4 flex justify-between items-center border-t bg-gray-50/50 text-xs text-gray-500">
|
|
||||||
<span>显示 1 到 10 条,共 112 条已填报记录</span>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<button class="p-1.5 border rounded bg-white hover:bg-gray-100 disabled:opacity-50" disabled><</button>
|
|
||||||
<button class="p-1.5 border rounded bg-blue-500 text-white min-w-[30px] font-bold shadow-sm">1</button>
|
|
||||||
<button class="p-1.5 border rounded bg-white hover:bg-gray-100 min-w-[30px]">2</button>
|
|
||||||
<button class="p-1.5 border rounded bg-white hover:bg-gray-100 min-w-[30px]">3</button>
|
|
||||||
<button class="p-1.5 border rounded bg-white hover:bg-gray-100">></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Reference in New Issue
Block a user