首页细节调整
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<template>
|
||||
<view class="page" @click="closeMoreMenu">
|
||||
<!-- 鍥哄畾椤堕儴瀵艰埅鏍?-->
|
||||
<!-- 固定顶部导航栏 -->
|
||||
<AnalyticsTopBar
|
||||
:title="'鑷畾涔夋姤琛?"
|
||||
:lastUpdateTime="'鍒涘缓鍜岀鐞嗘偍鐨勪笓灞炴姤琛?"
|
||||
:title="'自定义报表'"
|
||||
:lastUpdateTime="'创建和管理您的专属报表'"
|
||||
:sidebarVisible="showSidebarMenu"
|
||||
@menu-click="handleMenu"
|
||||
@refresh="refreshData"
|
||||
@@ -16,90 +16,90 @@
|
||||
/>
|
||||
|
||||
<view class="page-layout">
|
||||
<!-- 渚ц竟鏍忚彍鍗曠粍浠?-->
|
||||
<!-- 侧边栏菜单组件 -->
|
||||
<AnalyticsSidebarMenu
|
||||
:visible="showSidebarMenu"
|
||||
:currentPath="currentPath"
|
||||
@visible-change="handleSidebarUpdate"
|
||||
/>
|
||||
|
||||
<!-- 涓诲唴瀹瑰尯鍩?-->
|
||||
<!-- 主内容区域 -->
|
||||
<view class="main-content">
|
||||
<view class="container">
|
||||
|
||||
<!-- 椤堕儴鎿嶄綔鍖猴細鏂板缓鎶ヨ〃 -->
|
||||
<!-- 顶部操作区:新建报表 -->
|
||||
<view class="toolbar">
|
||||
<view class="toolbar-left">
|
||||
<text class="toolbar-title">鎴戠殑鑷畾涔夋姤琛?/text>
|
||||
<text class="toolbar-subtitle">鎸夐渶缁勫悎鎸囨爣鍜屾椂闂磋寖鍥达紝鐢熸垚涓撳睘鎶ヨ〃</text>
|
||||
<text class="toolbar-title">我的自定义报表</text>
|
||||
<text class="toolbar-subtitle">按需组合指标和时间范围,生成专属报表</text>
|
||||
</view>
|
||||
<view class="toolbar-right">
|
||||
<button class="btn-primary" @click.stop="createReport">锛?鏂板缓鎶ヨ〃</button>
|
||||
<button class="btn-primary" @click.stop="createReport">+ 新建报表</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 鎶ヨ〃鍒楄〃 / 绌虹姸鎬?-->
|
||||
<!-- 报表列表 / 空状态 -->
|
||||
<view v-if="reports.length > 0" class="report-list">
|
||||
<view v-for="report in reports" :key="report.id" class="report-card" @click="openReport(report)">
|
||||
<view class="report-header">
|
||||
<text class="report-title">{{ report.name }}</text>
|
||||
<view class="report-actions">
|
||||
<view class="action-btn" @click.stop="editReport(report)">
|
||||
<text class="icon">鉁忥笍</text>
|
||||
<text class="icon">✏️</text>
|
||||
</view>
|
||||
<view class="action-btn" @click.stop="deleteReport(report)">
|
||||
<text class="icon">馃棏锔?/text>
|
||||
<text class="icon">🗑️</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<text class="report-desc">{{ report.description || '鐐瑰嚮杩涘叆鎶ヨ〃璇︽儏鏌ョ湅鏁版嵁' }}</text>
|
||||
<text class="report-desc">{{ report.description || '点击进入报表详情查看数据' }}</text>
|
||||
<view class="report-meta">
|
||||
<text class="meta-item">鍥捐〃鍛ㄦ湡锛歿{ report.period || '鑷畾涔? }}</text>
|
||||
<text class="meta-item">鏈€杩戞洿鏂帮細{{ report.updated_at || '-' }}</text>
|
||||
<text class="meta-item">图表周期:{{ report.period || '自定义' }}</text>
|
||||
<text class="meta-item">最近更新:{{ report.updated_at || '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="empty-state">
|
||||
<text v-if="isLoggedIn" class="empty-title">鏆傛棤鑷畾涔夋姤琛?/text>
|
||||
<text v-else class="empty-title">璇峰厛鐧诲綍</text>
|
||||
<text v-if="isLoggedIn" class="empty-desc">鐐瑰嚮涓嬫柟鎸夐挳鍒涘缓绗竴浠芥姤琛紝鐢ㄤ簬澶嶇敤甯哥湅鐨勬寚鏍囩粍鍚堛€?/text>
|
||||
<text v-else class="empty-desc">鍒涘缓鑷畾涔夋姤琛ㄩ渶瑕佺櫥褰曡处鍙凤紝璇峰厛鐧诲綍鍚庡啀浣跨敤姝ゅ姛鑳姐€?/text>
|
||||
<button v-if="isLoggedIn" class="btn-primary" @click.stop="createReport">锛?鏂板缓鎶ヨ〃</button>
|
||||
<button v-else class="btn-primary" @click.stop="goToLogin">鍓嶅線鐧诲綍</button>
|
||||
<text v-if="isLoggedIn" class="empty-title">暂无自定义报表</text>
|
||||
<text v-else class="empty-title">请先登录</text>
|
||||
<text v-if="isLoggedIn" class="empty-desc">点击下方按钮创建第一份报表,用于复用常看的指标组合。</text>
|
||||
<text v-else class="empty-desc">创建自定义报表需要登录账号,请先登录后再使用此功能。</text>
|
||||
<button v-if="isLoggedIn" class="btn-primary" @click.stop="createReport">+ 新建报表</button>
|
||||
<button v-else class="btn-primary" @click.stop="goToLogin">前往登录</button>
|
||||
</view>
|
||||
|
||||
<!-- 鏂板缓鎶ヨ〃瀵硅瘽妗?-->
|
||||
<!-- 新建报表对话框 -->
|
||||
<view class="modal" v-if="showCreateModal" @click.stop>
|
||||
<view class="modal-content" @click.stop>
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">{{ editingReport ? '缂栬緫鎶ヨ〃' : '鏂板缓鎶ヨ〃' }}</text>
|
||||
<text class="modal-title">{{ editingReport ? '编辑报表' : '新建报表' }}</text>
|
||||
<view class="modal-close" @click="closeModal">
|
||||
<text class="icon">鉁?/text>
|
||||
<text class="icon">✕</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="modal-body">
|
||||
<view class="form-item">
|
||||
<text class="form-label">鎶ヨ〃鍚嶇О</text>
|
||||
<text class="form-label">报表名称</text>
|
||||
<input
|
||||
class="form-input"
|
||||
v-model="reportForm.name"
|
||||
placeholder="璇疯緭鍏ユ姤琛ㄥ悕绉帮紙1-50涓瓧绗︼級"
|
||||
placeholder="请输入报表名称(1-50个字符)"
|
||||
@input="onNameInput"
|
||||
/>
|
||||
<text v-if="formErrors.name" class="form-error">{{ formErrors.name }}</text>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">鎶ヨ〃鎻忚堪</text>
|
||||
<text class="form-label">报表描述</text>
|
||||
<textarea
|
||||
class="form-textarea"
|
||||
v-model="reportForm.description"
|
||||
placeholder="閫夊~锛屾渶澶?00涓瓧绗?
|
||||
placeholder="选填,最多200个字符"
|
||||
@input="onDescriptionInput"
|
||||
></textarea>
|
||||
<text v-if="formErrors.description" class="form-error">{{ formErrors.description }}</text>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">閫夋嫨鎸囨爣</text>
|
||||
<text class="form-label">选择指标</text>
|
||||
<view class="metric-list">
|
||||
<view
|
||||
v-for="m in availableMetrics"
|
||||
@@ -114,7 +114,7 @@
|
||||
<text v-if="formErrors.metrics" class="form-error">{{ formErrors.metrics }}</text>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">鏃堕棿缁村害</text>
|
||||
<text class="form-label">时间维度</text>
|
||||
<view class="period-list">
|
||||
<view
|
||||
v-for="p in timePeriods"
|
||||
@@ -129,7 +129,7 @@
|
||||
<text v-if="formErrors.period" class="form-error">{{ formErrors.period }}</text>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">鍥捐〃绫诲瀷</text>
|
||||
<text class="form-label">图表类型</text>
|
||||
<view class="chart-type-list">
|
||||
<view
|
||||
v-for="t in chartTypes"
|
||||
@@ -145,13 +145,13 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="modal-footer">
|
||||
<view class="btn btn-cancel" @click="closeModal">鍙栨秷</view>
|
||||
<view class="btn btn-primary" @click="saveReport">淇濆瓨</view>
|
||||
<view class="btn btn-cancel" @click="closeModal">取消</view>
|
||||
<view class="btn btn-primary" @click="saveReport">保存</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 鐣欑櫧 -->
|
||||
<!-- 留白 -->
|
||||
<view style="height: 24px;"></view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -202,31 +202,31 @@ const formErrors = reactive<ReportFormErrors>({
|
||||
|
||||
const availableMetrics = ref<Array<Metric>>([
|
||||
{ key: 'gmv', label: 'GMV' },
|
||||
{ key: 'orders', label: '璁㈠崟鏁? },
|
||||
{ key: 'users', label: '鐢ㄦ埛鏁? },
|
||||
{ key: 'conversion', label: '杞寲鐜? },
|
||||
{ key: 'avg_order', label: '瀹㈠崟浠? },
|
||||
{ key: 'repurchase', label: '澶嶈喘鐜? }
|
||||
{ key: 'orders', label: '订单数' },
|
||||
{ key: 'users', label: '用户数' },
|
||||
{ key: 'conversion', label: '转化率' },
|
||||
{ key: 'avg_order', label: '客单价' },
|
||||
{ key: 'repurchase', label: '复购率' }
|
||||
])
|
||||
|
||||
const timePeriods = ref<Array<TimePeriod>>([
|
||||
{ value: '7d', label: '7澶? },
|
||||
{ value: '30d', label: '30澶? },
|
||||
{ value: '90d', label: '90澶? },
|
||||
{ value: '1y', label: '1骞? }
|
||||
{ value: '7d', label: '7天' },
|
||||
{ value: '30d', label: '30天' },
|
||||
{ value: '90d', label: '90天' },
|
||||
{ value: '1y', label: '1年' }
|
||||
])
|
||||
|
||||
const chartTypes = ref<Array<ChartType>>([
|
||||
{ value: 'line', label: '鎶樼嚎鍥? },
|
||||
{ value: 'bar', label: '鏌辩姸鍥? },
|
||||
{ value: 'pie', label: '楗煎浘' },
|
||||
{ value: 'area', label: '闈㈢Н鍥? },
|
||||
{ value: 'combo', label: '缁勫悎鍥? }
|
||||
{ value: 'line', label: '折线图' },
|
||||
{ value: 'bar', label: '柱状图' },
|
||||
{ value: 'pie', label: '饼图' },
|
||||
{ value: 'area', label: '面积图' },
|
||||
{ value: 'combo', label: '组合图' }
|
||||
])
|
||||
|
||||
onLoad(() => {
|
||||
currentPath.value = '/pages/mall/analytics/custom-report'
|
||||
if (!ensureAnalyticsLogin({ toastTitle: '璇峰厛鐧诲綍鍚庝娇鐢ㄨ嚜瀹氫箟鎶ヨ〃' })) return
|
||||
if (!ensureAnalyticsLogin({ toastTitle: '请先登录后使用自定义报表' })) return
|
||||
loadReports()
|
||||
})
|
||||
|
||||
@@ -264,7 +264,7 @@ async function loadReports() {
|
||||
reports.splice(0, reports.length, ...list)
|
||||
} catch (e) {
|
||||
console.error('loadReports failed', e)
|
||||
uni.showToast({ title: '鎶ヨ〃鍔犺浇澶辫触', icon: 'none' })
|
||||
uni.showToast({ title: '报表加载失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,8 +306,8 @@ function editReport(report: CustomReport) {
|
||||
|
||||
function deleteReport(report: CustomReport) {
|
||||
uni.showModal({
|
||||
title: '纭鍒犻櫎',
|
||||
content: `纭畾瑕佸垹闄ゆ姤琛?${report.name}"鍚楋紵`,
|
||||
title: '确认删除',
|
||||
content: `确定要删除报表"${report.name}"吗?`,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
doDeleteReport(report)
|
||||
@@ -321,11 +321,11 @@ async function doDeleteReport(report: CustomReport) {
|
||||
await ensureSupabaseReady()
|
||||
|
||||
await deleteCustomReport(report.id)
|
||||
uni.showToast({ title: '鍒犻櫎鎴愬姛', icon: 'success' })
|
||||
uni.showToast({ title: '删除成功', icon: 'success' })
|
||||
loadReports()
|
||||
} catch (e: any) {
|
||||
console.error('doDeleteReport failed', e)
|
||||
const errorMsg = e?.message || '鍒犻櫎澶辫触'
|
||||
const errorMsg = e?.message || '删除失败'
|
||||
uni.showToast({ title: errorMsg, icon: 'none' })
|
||||
}
|
||||
}
|
||||
@@ -345,9 +345,9 @@ function toggleMetric(key: string) {
|
||||
function onNameInput() {
|
||||
const name = reportForm.name.trim()
|
||||
if (name.length === 0) {
|
||||
formErrors.name = '鎶ヨ〃鍚嶇О涓嶈兘涓虹┖'
|
||||
formErrors.name = '报表名称不能为空'
|
||||
} else if (name.length > 50) {
|
||||
formErrors.name = '鎶ヨ〃鍚嶇О涓嶈兘瓒呰繃50涓瓧绗?
|
||||
formErrors.name = '报表名称不能超过50个字符'
|
||||
} else {
|
||||
formErrors.name = ''
|
||||
}
|
||||
@@ -356,7 +356,7 @@ function onNameInput() {
|
||||
function onDescriptionInput() {
|
||||
const desc = reportForm.description
|
||||
if (desc.length > 200) {
|
||||
formErrors.description = '鎶ヨ〃鎻忚堪涓嶈兘瓒呰繃200涓瓧绗?
|
||||
formErrors.description = '报表描述不能超过200个字符'
|
||||
} else {
|
||||
formErrors.description = ''
|
||||
}
|
||||
@@ -377,20 +377,20 @@ function validateReportForm(): boolean {
|
||||
onDescriptionInput()
|
||||
|
||||
if (reportForm.metrics.length === 0) {
|
||||
formErrors.metrics = '璇疯嚦灏戦€夋嫨涓€涓寚鏍?
|
||||
formErrors.metrics = '请至少选择一个指标'
|
||||
} else {
|
||||
formErrors.metrics = ''
|
||||
}
|
||||
|
||||
if (!reportForm.period) {
|
||||
formErrors.period = '璇烽€夋嫨鏃堕棿缁村害'
|
||||
formErrors.period = '请选择时间维度'
|
||||
}
|
||||
if (!reportForm.chartType) {
|
||||
formErrors.chartType = '璇烽€夋嫨鍥捐〃绫诲瀷'
|
||||
formErrors.chartType = '请选择图表类型'
|
||||
}
|
||||
|
||||
if (formErrors.name || formErrors.description || formErrors.metrics || formErrors.period || formErrors.chartType) {
|
||||
uni.showToast({ title: '璇峰厛淇琛ㄥ崟涓殑閿欒鎻愮ず', icon: 'none' })
|
||||
uni.showToast({ title: '请先修正表单中的错误提示', icon: 'none' })
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -402,15 +402,15 @@ async function saveReport() {
|
||||
}
|
||||
|
||||
try {
|
||||
uni.showLoading({ title: '淇濆瓨涓?..' })
|
||||
uni.showLoading({ title: '保存中...' })
|
||||
await ensureSupabaseReady()
|
||||
|
||||
const uid = getUserIdOrNull()
|
||||
if (!uid || uid.length === 0) {
|
||||
uni.hideLoading()
|
||||
uni.showModal({
|
||||
title: '闇€瑕佺櫥褰?,
|
||||
content: '鍒涘缓鑷畾涔夋姤琛ㄩ渶瑕佸厛鐧诲綍锛屾槸鍚﹀墠寰€鐧诲綍椤甸潰锛?,
|
||||
title: '需要登录',
|
||||
content: '创建自定义报表需要先登录,是否前往登录页面?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
goToLogin('/pages/mall/analytics/custom-report')
|
||||
@@ -442,17 +442,17 @@ async function saveReport() {
|
||||
|
||||
uni.hideLoading()
|
||||
|
||||
// 妫€鏌?newReportId 鏄惁鏈夋晥锛屾棤鏁堝垯璁や负鍒涘缓澶辫触
|
||||
// 检查 newReportId 是否有效,无效则认为创建失败
|
||||
if (newReportId == null || newReportId.length === 0) {
|
||||
uni.showToast({
|
||||
title: '鍒涘缓澶辫触锛氭湭杩斿洖鎶ヨ〃ID',
|
||||
title: '创建失败:未返回报表ID',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uni.showToast({ title: '淇濆瓨鎴愬姛', icon: 'success' })
|
||||
uni.showToast({ title: '保存成功', icon: 'success' })
|
||||
closeModal()
|
||||
loadReports()
|
||||
|
||||
@@ -466,7 +466,7 @@ async function saveReport() {
|
||||
uni.hideLoading()
|
||||
console.error('saveReport exception:', e)
|
||||
uni.showToast({
|
||||
title: mapAnalyticsError(e, { fallbackMessage: '淇濆瓨澶辫触' }),
|
||||
title: mapAnalyticsError(e, { fallbackMessage: '保存失败' }),
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
})
|
||||
@@ -486,7 +486,7 @@ function closeModal() {
|
||||
|
||||
function refreshData() {
|
||||
loadReports()
|
||||
uni.showToast({ title: '宸插埛鏂?, icon: 'success' })
|
||||
uni.showToast({ title: '已刷新', icon: 'success' })
|
||||
}
|
||||
|
||||
function handleMenu() {
|
||||
@@ -506,31 +506,31 @@ function closeMoreMenu() {
|
||||
}
|
||||
|
||||
function goToLogin() {
|
||||
ensureAnalyticsLogin({ toastTitle: '璇峰厛鐧诲綍鍚庝娇鐢ㄨ嚜瀹氫箟鎶ヨ〃' })
|
||||
ensureAnalyticsLogin({ toastTitle: '请先登录后使用自定义报表' })
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
uni.showToast({ title: '鎼滅储', icon: 'none' })
|
||||
uni.showToast({ title: '搜索', icon: 'none' })
|
||||
}
|
||||
|
||||
function handleNotification() {
|
||||
uni.showToast({ title: '閫氱煡', icon: 'none' })
|
||||
uni.showToast({ title: '通知', icon: 'none' })
|
||||
}
|
||||
|
||||
function handleFullscreen() {
|
||||
uni.showToast({ title: '鍏ㄥ睆', icon: 'none' })
|
||||
uni.showToast({ title: '全屏', icon: 'none' })
|
||||
}
|
||||
|
||||
function handleMobile() {
|
||||
uni.showToast({ title: '绉诲姩绔?, icon: 'none' })
|
||||
uni.showToast({ title: '移动端', icon: 'none' })
|
||||
}
|
||||
|
||||
function handleDropdown() {
|
||||
uni.showToast({ title: '涓嬫媺鑿滃崟', icon: 'none' })
|
||||
uni.showToast({ title: '下拉菜单', icon: 'none' })
|
||||
}
|
||||
|
||||
function handleSettings() {
|
||||
uni.showToast({ title: '璁剧疆', icon: 'none' })
|
||||
uni.showToast({ title: '设置', icon: 'none' })
|
||||
}
|
||||
|
||||
function handleGoToLogin() {
|
||||
@@ -545,7 +545,7 @@ function handleGoToLogin() {
|
||||
background: #f6f7fb;
|
||||
}
|
||||
|
||||
/* 椤甸潰甯冨眬锛氬灞忔椂渚ц竟鏍?鍐呭锛岀獎灞忔椂鍏ㄥ睆鍐呭 */
|
||||
/* 页面布局:宽屏时侧边栏+内容,窄屏时全屏内容 */
|
||||
.page-layout {
|
||||
display: flex;
|
||||
flex-direction: row !important;
|
||||
@@ -557,18 +557,18 @@ function handleGoToLogin() {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 64px; /* 涓哄浐瀹氶《閮ㄥ鑸爮鐣欏嚭绌洪棿 */
|
||||
padding-top: 64px; /* 为固定顶部导航栏留出空间 */
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
/* padding removed */ 16px 28px;
|
||||
padding: 16px 16px 28px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 椤堕儴鏍?*/
|
||||
/* 顶部栏 */
|
||||
.topbar {
|
||||
display: flex;
|
||||
flex-direction: row !important;
|
||||
@@ -701,7 +701,7 @@ function handleGoToLogin() {
|
||||
color: #111;
|
||||
}
|
||||
|
||||
/* 宸ュ叿鏍?*/
|
||||
/* 工具栏 */
|
||||
.toolbar {
|
||||
margin-top: 12px;
|
||||
padding: 12px 16px;
|
||||
@@ -752,7 +752,7 @@ function handleGoToLogin() {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* 鎶ヨ〃鍒楄〃 */
|
||||
/* 报表列表 */
|
||||
.report-list {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
@@ -858,7 +858,7 @@ function handleGoToLogin() {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 妯℃€佹 */
|
||||
/* 模态框 */
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -1009,7 +1009,7 @@ function handleGoToLogin() {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 鍝嶅簲寮?*/
|
||||
/* 响应式 */
|
||||
@media screen and (max-width: 960px) {
|
||||
.title,
|
||||
.subtitle {
|
||||
@@ -1030,7 +1030,7 @@ function handleGoToLogin() {
|
||||
}
|
||||
}
|
||||
|
||||
/* 鍝嶅簲寮忥細绐勫睆鏃跺叏灞忔樉绀?*/
|
||||
/* 响应式:窄屏时全屏显示 */
|
||||
@media screen and (max-width: 959px) {
|
||||
.page-layout {
|
||||
flex-direction: column !important;
|
||||
@@ -1041,4 +1041,3 @@ function handleGoToLogin() {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user