consumer模块完成90%,前端完成supabase对接
This commit is contained in:
691
mall/layouts/admin/utils/echarts-config.uts
Normal file
691
mall/layouts/admin/utils/echarts-config.uts
Normal file
@@ -0,0 +1,691 @@
|
||||
// ECharts 配置工具 - CRMEB 风格图表配置
|
||||
// 订单统计图表配置(柱状图 + 折线图)
|
||||
export const getOrderChartOption = (period: string = '30days') => {
|
||||
const periods = {
|
||||
'30days': { label: '30天', days: 30 },
|
||||
'week': { label: '本周', days: 7 },
|
||||
'month': { label: '本月', days: 30 },
|
||||
'year': { label: '本年', days: 365 }
|
||||
}
|
||||
|
||||
const periodConfig = periods[period as keyof typeof periods] || periods['30days']
|
||||
|
||||
return {
|
||||
title: {
|
||||
text: `订单统计 (${periodConfig.label})`,
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
fontSize: 16,
|
||||
fontWeight: 600,
|
||||
color: '#262626'
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
borderColor: 'transparent',
|
||||
textStyle: {
|
||||
color: '#ffffff',
|
||||
fontSize: 12
|
||||
},
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
crossStyle: {
|
||||
color: '#999'
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['订单金额', '订单数量'],
|
||||
top: 30,
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
color: '#666666'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '10%',
|
||||
top: '15%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: generateDateLabels(periodConfig.days),
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#e8e8e8'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#999999',
|
||||
fontSize: 12
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '订单金额',
|
||||
position: 'left',
|
||||
axisLabel: {
|
||||
formatter: '¥{value}',
|
||||
color: '#999999',
|
||||
fontSize: 12
|
||||
},
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#f0f0f0',
|
||||
type: 'dashed'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '订单数量',
|
||||
position: 'right',
|
||||
axisLabel: {
|
||||
color: '#999999',
|
||||
fontSize: 12
|
||||
},
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '订单金额',
|
||||
type: 'bar',
|
||||
data: generateAmountData(periodConfig.days),
|
||||
barWidth: '40%',
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#1890ff' },
|
||||
{ offset: 1, color: '#36cfc9' }
|
||||
]),
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
},
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#40a9ff' },
|
||||
{ offset: 1, color: '#5cdbd3' }
|
||||
])
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '订单数量',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
data: generateCountData(periodConfig.days),
|
||||
symbol: 'circle',
|
||||
symbolSize: 8,
|
||||
lineStyle: {
|
||||
color: '#52c41a',
|
||||
width: 3
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#52c41a',
|
||||
borderColor: '#ffffff',
|
||||
borderWidth: 2
|
||||
},
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
|
||||
{ offset: 0, color: 'rgba(82, 196, 26, 0.1)' },
|
||||
{ offset: 1, color: 'rgba(82, 196, 26, 0.3)' }
|
||||
])
|
||||
}
|
||||
}
|
||||
],
|
||||
animationDuration: 1000,
|
||||
animationEasing: 'cubicOut'
|
||||
}
|
||||
}
|
||||
|
||||
// 用户趋势图表配置
|
||||
export const getUserTrendOption = () => {
|
||||
return {
|
||||
title: {
|
||||
text: '用户增长趋势',
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
fontSize: 16,
|
||||
fontWeight: 600,
|
||||
color: '#262626'
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
borderColor: 'transparent',
|
||||
textStyle: {
|
||||
color: '#ffffff',
|
||||
fontSize: 12
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['新增用户'],
|
||||
top: 30,
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
color: '#666666'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '10%',
|
||||
top: '15%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: generateDateLabels(30),
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#e8e8e8'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#999999',
|
||||
fontSize: 12
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '用户数量',
|
||||
axisLabel: {
|
||||
color: '#999999',
|
||||
fontSize: 12
|
||||
},
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#f0f0f0',
|
||||
type: 'dashed'
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '新增用户',
|
||||
type: 'line',
|
||||
data: generateUserTrendData(30),
|
||||
symbol: 'circle',
|
||||
symbolSize: 8,
|
||||
lineStyle: {
|
||||
color: '#1890ff',
|
||||
width: 3
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#1890ff',
|
||||
borderColor: '#ffffff',
|
||||
borderWidth: 2
|
||||
},
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
|
||||
{ offset: 0, color: 'rgba(24, 144, 255, 0.1)' },
|
||||
{ offset: 1, color: 'rgba(24, 144, 255, 0.3)' }
|
||||
])
|
||||
}
|
||||
}
|
||||
],
|
||||
animationDuration: 1000,
|
||||
animationEasing: 'cubicOut'
|
||||
}
|
||||
}
|
||||
|
||||
// 用户构成饼图配置
|
||||
export const getUserCompositionOption = () => {
|
||||
return {
|
||||
title: {
|
||||
text: '用户来源构成',
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
fontSize: 16,
|
||||
fontWeight: 600,
|
||||
color: '#262626'
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b}: {c}% ({d}%)',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
borderColor: 'transparent',
|
||||
textStyle: {
|
||||
color: '#ffffff',
|
||||
fontSize: 12
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
top: 'center',
|
||||
itemGap: 16,
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
color: '#666666'
|
||||
},
|
||||
data: ['自然流量', '搜索引擎', '社交媒体', '广告投放', '其他']
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '用户来源',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
center: ['60%', '50%'],
|
||||
avoidLabelOverlap: false,
|
||||
label: {
|
||||
show: false
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
formatter: '{b}\n{c}%'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: 35,
|
||||
name: '自然流量',
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#1890ff' },
|
||||
{ offset: 1, color: '#36cfc9' }
|
||||
])
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 28,
|
||||
name: '搜索引擎',
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#52c41a' },
|
||||
{ offset: 1, color: '#73d13d' }
|
||||
])
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 20,
|
||||
name: '社交媒体',
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#faad14' },
|
||||
{ offset: 1, color: '#ffc53d' }
|
||||
])
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 12,
|
||||
name: '广告投放',
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#f5222d' },
|
||||
{ offset: 1, color: '#ff7875' }
|
||||
])
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 5,
|
||||
name: '其他',
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#722ed1' },
|
||||
{ offset: 1, color: '#b37feb' }
|
||||
])
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
animationDuration: 1000,
|
||||
animationEasing: 'cubicOut'
|
||||
}
|
||||
}
|
||||
|
||||
// 用户统计多折线图配置
|
||||
export const getUserStatisticsOption = () => {
|
||||
return {
|
||||
title: {
|
||||
text: '用户数据趋势分析',
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
fontSize: 16,
|
||||
fontWeight: 600,
|
||||
color: '#262626'
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
borderColor: 'transparent',
|
||||
textStyle: {
|
||||
color: '#ffffff',
|
||||
fontSize: 12
|
||||
},
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
crossStyle: {
|
||||
color: '#999'
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['新增用户', '访客数', '浏览量', '成交用户', '付费会员'],
|
||||
top: 30,
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
color: '#666666'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '10%',
|
||||
top: '15%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: generateDateLabels(30, 7), // 30天的数据,每7天一个标签
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#e8e8e8'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#999999',
|
||||
fontSize: 12
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '数量',
|
||||
axisLabel: {
|
||||
color: '#999999',
|
||||
fontSize: 12
|
||||
},
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#f0f0f0',
|
||||
type: 'dashed'
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '新增用户',
|
||||
type: 'line',
|
||||
data: generateUserStatisticsData('newUsers', 7),
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
lineStyle: {
|
||||
color: '#1890ff',
|
||||
width: 2
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#1890ff',
|
||||
borderColor: '#ffffff',
|
||||
borderWidth: 2
|
||||
},
|
||||
smooth: true
|
||||
},
|
||||
{
|
||||
name: '访客数',
|
||||
type: 'line',
|
||||
data: generateUserStatisticsData('visitors', 7),
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
lineStyle: {
|
||||
color: '#52c41a',
|
||||
width: 2
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#52c41a',
|
||||
borderColor: '#ffffff',
|
||||
borderWidth: 2
|
||||
},
|
||||
smooth: true
|
||||
},
|
||||
{
|
||||
name: '浏览量',
|
||||
type: 'line',
|
||||
data: generateUserStatisticsData('pageViews', 7),
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
lineStyle: {
|
||||
color: '#faad14',
|
||||
width: 2
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#faad14',
|
||||
borderColor: '#ffffff',
|
||||
borderWidth: 2
|
||||
},
|
||||
smooth: true
|
||||
},
|
||||
{
|
||||
name: '成交用户',
|
||||
type: 'line',
|
||||
data: generateUserStatisticsData('conversions', 7),
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
lineStyle: {
|
||||
color: '#f5222d',
|
||||
width: 2
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#f5222d',
|
||||
borderColor: '#ffffff',
|
||||
borderWidth: 2
|
||||
},
|
||||
smooth: true
|
||||
},
|
||||
{
|
||||
name: '付费会员',
|
||||
type: 'line',
|
||||
data: generateUserStatisticsData('vipUsers', 7),
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
lineStyle: {
|
||||
color: '#722ed1',
|
||||
width: 2
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#722ed1',
|
||||
borderColor: '#ffffff',
|
||||
borderWidth: 2
|
||||
},
|
||||
smooth: true
|
||||
}
|
||||
],
|
||||
animationDuration: 1000,
|
||||
animationEasing: 'cubicOut'
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:生成日期标签
|
||||
function generateDateLabels(days: number, step: number = 1): string[] {
|
||||
const labels = []
|
||||
const today = new Date()
|
||||
|
||||
for (let i = days - 1; i >= 0; i -= step) {
|
||||
const date = new Date(today)
|
||||
date.setDate(date.getDate() - i)
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||||
const day = date.getDate().toString().padStart(2, '0')
|
||||
labels.push(`${month}-${day}`)
|
||||
}
|
||||
|
||||
return labels
|
||||
}
|
||||
|
||||
// 辅助函数:生成订单金额数据
|
||||
function generateAmountData(days: number): number[] {
|
||||
const data = []
|
||||
for (let i = 0; i < days; i++) {
|
||||
// 生成12000-25000之间的随机金额
|
||||
data.push(Math.floor(Math.random() * 13000) + 12000)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// 辅助函数:生成订单数量数据
|
||||
function generateCountData(days: number): number[] {
|
||||
const data = []
|
||||
for (let i = 0; i < days; i++) {
|
||||
// 生成50-150之间的随机数量
|
||||
data.push(Math.floor(Math.random() * 100) + 50)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// 辅助函数:生成用户趋势数据
|
||||
function generateUserTrendData(days: number): number[] {
|
||||
const data = []
|
||||
let base = 100
|
||||
|
||||
for (let i = 0; i < days; i++) {
|
||||
base += Math.floor(Math.random() * 20) - 5 // -5到+15的随机变化
|
||||
base = Math.max(50, base) // 最低50
|
||||
data.push(base)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// 辅助函数:生成用户统计数据
|
||||
function generateUserStatisticsData(type: string, points: number): number[] {
|
||||
const data = []
|
||||
const baseValues = {
|
||||
newUsers: 120,
|
||||
visitors: 450,
|
||||
pageViews: 680,
|
||||
conversions: 45,
|
||||
vipUsers: 12
|
||||
}
|
||||
|
||||
let base = baseValues[type as keyof typeof baseValues] || 100
|
||||
|
||||
for (let i = 0; i < points; i++) {
|
||||
const variation = type === 'vipUsers' ? 0.3 : 0.2 // 付费会员变化小一些
|
||||
base += Math.floor(Math.random() * (base * variation * 2)) - (base * variation)
|
||||
base = Math.max(0, base)
|
||||
data.push(Math.floor(base))
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// Mock API 数据接口
|
||||
export const mockApi = {
|
||||
// 获取订单统计数据
|
||||
getOrderStats: (period: string = '30days') => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
period,
|
||||
amountData: generateAmountData(30),
|
||||
countData: generateCountData(30),
|
||||
dateLabels: generateDateLabels(30)
|
||||
})
|
||||
}, 500)
|
||||
})
|
||||
},
|
||||
|
||||
// 获取用户趋势数据
|
||||
getUserTrend: () => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
data: generateUserTrendData(30),
|
||||
dateLabels: generateDateLabels(30)
|
||||
})
|
||||
}, 500)
|
||||
})
|
||||
},
|
||||
|
||||
// 获取用户构成数据
|
||||
getUserComposition: () => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve([
|
||||
{ name: '自然流量', value: 35, color: '#1890ff' },
|
||||
{ name: '搜索引擎', value: 28, color: '#52c41a' },
|
||||
{ name: '社交媒体', value: 20, color: '#faad14' },
|
||||
{ name: '广告投放', value: 12, color: '#f5222d' },
|
||||
{ name: '其他', value: 5, color: '#722ed1' }
|
||||
])
|
||||
}, 500)
|
||||
})
|
||||
},
|
||||
|
||||
// 获取用户统计数据
|
||||
getUserStatistics: () => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
newUsers: generateUserStatisticsData('newUsers', 7),
|
||||
visitors: generateUserStatisticsData('visitors', 7),
|
||||
pageViews: generateUserStatisticsData('pageViews', 7),
|
||||
conversions: generateUserStatisticsData('conversions', 7),
|
||||
vipUsers: generateUserStatisticsData('vipUsers', 7),
|
||||
dateLabels: generateDateLabels(30, 7)
|
||||
})
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 导出所有配置
|
||||
export const chartConfigs = {
|
||||
orderChart: getOrderChartOption,
|
||||
userTrendChart: getUserTrendOption,
|
||||
userCompositionChart: getUserCompositionOption,
|
||||
userStatisticsChart: getUserStatisticsOption,
|
||||
mockApi
|
||||
}
|
||||
95
mall/layouts/admin/utils/nav.uts
Normal file
95
mall/layouts/admin/utils/nav.uts
Normal file
@@ -0,0 +1,95 @@
|
||||
// utils/nav.uts
|
||||
import type { MenuItem, MenuGroup, MenuChild } from '../types.uts'
|
||||
|
||||
export function findActiveByCurrentPage(
|
||||
menuList: MenuItem[],
|
||||
currentPage: string
|
||||
): { activeMenuId: string, activeSubId: string } {
|
||||
|
||||
const page = currentPage || ''
|
||||
|
||||
// 1) currentPage 直接是一级 menu id
|
||||
const mById = menuList.find(m => m.id === page)
|
||||
if (mById) {
|
||||
const leaf = firstLeafOfMenu(mById)
|
||||
return { activeMenuId: mById.id, activeSubId: leaf?.id ?? '' }
|
||||
}
|
||||
|
||||
// 2) currentPage 是 path(/pages/xxx 或 pages/xxx)
|
||||
const pageNorm = normalize(page)
|
||||
|
||||
for (const m of menuList) {
|
||||
const groups = m.groups ?? []
|
||||
|
||||
// group / child / 四级 全部扫描
|
||||
for (const g of groups) {
|
||||
// group 叶子(可选)
|
||||
if (g.path && normalize(g.path) === pageNorm) {
|
||||
return { activeMenuId: m.id, activeSubId: '' }
|
||||
}
|
||||
|
||||
const cs = g.children ?? []
|
||||
for (const c of cs) {
|
||||
// 用 id 命中
|
||||
if (c.id === page) return { activeMenuId: m.id, activeSubId: c.id }
|
||||
// 用 path 命中
|
||||
if (c.path && normalize(c.path) === pageNorm) return { activeMenuId: m.id, activeSubId: c.id }
|
||||
|
||||
// 四级
|
||||
const ds = c.children ?? []
|
||||
const hit = findInChildren(ds, page, pageNorm)
|
||||
if (hit) return { activeMenuId: m.id, activeSubId: hit.id }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3) 找不到:兜底 home
|
||||
return { activeMenuId: 'home', activeSubId: '' }
|
||||
}
|
||||
|
||||
function findInChildren(list: MenuChild[], targetId: string, targetPathNorm: string): MenuChild | null {
|
||||
for (const n of list) {
|
||||
if (n.id === targetId) return n
|
||||
if (n.path && normalize(n.path) === targetPathNorm) return n
|
||||
const deep = n.children ?? []
|
||||
if (deep.length > 0) {
|
||||
const hit = findInChildren(deep, targetId, targetPathNorm)
|
||||
if (hit) return hit
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function normalize(p: string): string {
|
||||
if (!p) return ''
|
||||
const s = p.startsWith('/') ? p.slice(1) : p
|
||||
const q = s.indexOf('?')
|
||||
return q >= 0 ? s.slice(0, q) : s
|
||||
}
|
||||
|
||||
// 你可以把 index.uvue 里的 firstLeafOfMenu 移到这里复用(保持逻辑一致)
|
||||
function firstLeafOfMenu(m: MenuItem): MenuChild | null {
|
||||
const gs = m.groups ?? []
|
||||
if (gs.length === 0) return null
|
||||
const g0 = gs[0]
|
||||
const cs = g0.children ?? []
|
||||
if (cs.length === 0) return null
|
||||
let n = cs[0]
|
||||
while (n.children && n.children.length > 0) n = n.children[0]
|
||||
return n
|
||||
}
|
||||
|
||||
|
||||
export function getCurrentRoutePath(): string {
|
||||
// 使用页面栈获取当前路由(uni-app标准能力)
|
||||
// getCurrentPages 用于获取当前页面栈实例 :contentReference[oaicite:2]{index=2}
|
||||
const pages = getCurrentPages()
|
||||
const last: any = pages[pages.length - 1]
|
||||
// #ifdef H5
|
||||
return last?.route ? `/${last.route}` : ''
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
// 小程序/App 可能是 route / $page?.fullPath 形式,按你项目实际字段微调
|
||||
return last?.route ? `/${last.route}` : (last?.$page?.fullPath || '')
|
||||
// #endif
|
||||
}
|
||||
33
mall/layouts/admin/utils/tabs.uts
Normal file
33
mall/layouts/admin/utils/tabs.uts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { TabItem, MenuItem } from '../types.uts'
|
||||
|
||||
export function makeTabFromPath(menuList: MenuItem[], path: string): TabItem {
|
||||
// path 可能带 query;用于 tab 的 id 也要稳定
|
||||
const pure = path.split('?')[0]
|
||||
|
||||
// 先找子页面
|
||||
for (const m of menuList) {
|
||||
const groups = m.groups || []
|
||||
for (const g of groups) {
|
||||
for (const c of g.children) {
|
||||
if (c.path.split('?')[0] === pure) {
|
||||
return { id: c.id, title: c.title, path: c.path }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m.path.split('?')[0] === pure) {
|
||||
return { id: m.id, title: m.title, path: m.path }
|
||||
}
|
||||
}
|
||||
// 找不到就兜底
|
||||
return { id: pure, title: '页面', path }
|
||||
}
|
||||
|
||||
export function upsertTab(tabs: TabItem[], tab: TabItem): TabItem[] {
|
||||
const idx = tabs.findIndex(t => t.id === tab.id)
|
||||
if (idx >= 0) return tabs
|
||||
return [...tabs, tab]
|
||||
}
|
||||
|
||||
export function removeTab(tabs: TabItem[], tabId: string): TabItem[] {
|
||||
return tabs.filter(t => t.id !== tabId)
|
||||
}
|
||||
Reference in New Issue
Block a user