1045 lines
24 KiB
Plaintext
1045 lines
24 KiB
Plaintext
<template>
|
||
<view class="page" @click="closeMoreMenu">
|
||
<!-- 鍥哄畾椤堕儴瀵艰埅鏍?-->
|
||
<AnalyticsTopBar
|
||
:title="'鑷畾涔夋姤琛?"
|
||
:lastUpdateTime="'鍒涘缓鍜岀鐞嗘偍鐨勪笓灞炴姤琛?"
|
||
:sidebarVisible="showSidebarMenu"
|
||
@menu-click="handleMenu"
|
||
@refresh="refreshData"
|
||
@search="handleSearch"
|
||
@notification="handleNotification"
|
||
@fullscreen="handleFullscreen"
|
||
@mobile="handleMobile"
|
||
@dropdown="handleDropdown"
|
||
@settings="handleSettings"
|
||
/>
|
||
|
||
<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>
|
||
</view>
|
||
<view class="toolbar-right">
|
||
<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>
|
||
</view>
|
||
<view class="action-btn" @click.stop="deleteReport(report)">
|
||
<text class="icon">馃棏锔?/text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<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>
|
||
</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>
|
||
</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>
|
||
<view class="modal-close" @click="closeModal">
|
||
<text class="icon">鉁?/text>
|
||
</view>
|
||
</view>
|
||
<view class="modal-body">
|
||
<view class="form-item">
|
||
<text class="form-label">鎶ヨ〃鍚嶇О</text>
|
||
<input
|
||
class="form-input"
|
||
v-model="reportForm.name"
|
||
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>
|
||
<textarea
|
||
class="form-textarea"
|
||
v-model="reportForm.description"
|
||
placeholder="閫夊~锛屾渶澶?00涓瓧绗?
|
||
@input="onDescriptionInput"
|
||
></textarea>
|
||
<text v-if="formErrors.description" class="form-error">{{ formErrors.description }}</text>
|
||
</view>
|
||
<view class="form-item">
|
||
<text class="form-label">閫夋嫨鎸囨爣</text>
|
||
<view class="metric-list">
|
||
<view
|
||
v-for="m in availableMetrics"
|
||
:key="m.key"
|
||
class="metric-item"
|
||
:class="{ selected: reportForm.metrics.includes(m.key) }"
|
||
@click="toggleMetric(m.key)"
|
||
>
|
||
<text>{{ m.label }}</text>
|
||
</view>
|
||
</view>
|
||
<text v-if="formErrors.metrics" class="form-error">{{ formErrors.metrics }}</text>
|
||
</view>
|
||
<view class="form-item">
|
||
<text class="form-label">鏃堕棿缁村害</text>
|
||
<view class="period-list">
|
||
<view
|
||
v-for="p in timePeriods"
|
||
:key="p.value"
|
||
class="period-item"
|
||
:class="{ selected: reportForm.period === p.value }"
|
||
@click="selectPeriod(p.value)"
|
||
>
|
||
<text>{{ p.label }}</text>
|
||
</view>
|
||
</view>
|
||
<text v-if="formErrors.period" class="form-error">{{ formErrors.period }}</text>
|
||
</view>
|
||
<view class="form-item">
|
||
<text class="form-label">鍥捐〃绫诲瀷</text>
|
||
<view class="chart-type-list">
|
||
<view
|
||
v-for="t in chartTypes"
|
||
:key="t.value"
|
||
class="chart-type-item"
|
||
:class="{ selected: reportForm.chartType === t.value }"
|
||
@click="selectChartType(t.value)"
|
||
>
|
||
<text>{{ t.label }}</text>
|
||
</view>
|
||
</view>
|
||
<text v-if="formErrors.chartType" class="form-error">{{ formErrors.chartType }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="modal-footer">
|
||
<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>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import supa, { ensureSupabaseReady } from '@/components/supadb/aksupainstance.uts'
|
||
import AnalyticsSidebarMenu from '@/components/analytics/AnalyticsSidebarMenu.uvue'
|
||
import AnalyticsTopBar from '@/components/analytics/AnalyticsTopBar.uvue'
|
||
import { goToLogin as goToLoginPage } from '@/utils/utils.uts'
|
||
import { getUserIdOrNull } from '@/services/analytics/auth.uts'
|
||
import { ensureAnalyticsLogin } from '@/services/analytics/authGuard.uts'
|
||
import { listCustomReports, createCustomReport, updateCustomReport, deleteCustomReport } from '@/services/analytics/customReportService.uts'
|
||
import { mapAnalyticsError } from '@/services/analytics/errorMapper.uts'
|
||
|
||
import { reactive, ref } from 'vue'
|
||
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||
|
||
import type { CustomReport, ReportForm, ReportFormErrors } from '@/types/analytics/custom-report.uts'
|
||
import type { Metric, TimePeriod, ChartType } from '@/types/analytics/common.uts'
|
||
|
||
const showMoreMenu = ref(false)
|
||
const showSidebarMenu = ref(false)
|
||
const currentPath = ref('/pages/mall/analytics/custom-report')
|
||
const showCreateModal = ref(false)
|
||
const editingReport = ref<CustomReport | null>(null)
|
||
|
||
const reports = reactive<Array<CustomReport>>([])
|
||
const isLoggedIn = ref(false)
|
||
|
||
const reportForm = reactive<ReportForm>({
|
||
name: '',
|
||
description: '',
|
||
metrics: [] as Array<string>,
|
||
period: '7d',
|
||
chartType: 'line'
|
||
})
|
||
|
||
const formErrors = reactive<ReportFormErrors>({
|
||
name: '',
|
||
description: '',
|
||
metrics: '',
|
||
period: '',
|
||
chartType: ''
|
||
})
|
||
|
||
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: '澶嶈喘鐜? }
|
||
])
|
||
|
||
const timePeriods = ref<Array<TimePeriod>>([
|
||
{ 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: '缁勫悎鍥? }
|
||
])
|
||
|
||
onLoad(() => {
|
||
currentPath.value = '/pages/mall/analytics/custom-report'
|
||
if (!ensureAnalyticsLogin({ toastTitle: '璇峰厛鐧诲綍鍚庝娇鐢ㄨ嚜瀹氫箟鎶ヨ〃' })) return
|
||
loadReports()
|
||
})
|
||
|
||
onShow(() => {
|
||
currentPath.value = '/pages/mall/analytics/custom-report'
|
||
})
|
||
|
||
async function loadReports() {
|
||
try {
|
||
await ensureSupabaseReady()
|
||
|
||
const uid = getUserIdOrNull()
|
||
if (!uid || uid.length === 0) {
|
||
isLoggedIn.value = false
|
||
reports.splice(0, reports.length)
|
||
return
|
||
}
|
||
|
||
isLoggedIn.value = true
|
||
|
||
const items = await listCustomReports(uid)
|
||
const list: Array<CustomReport> = []
|
||
for (let i = 0; i < items.length; i++) {
|
||
const r = items[i]
|
||
list.push({
|
||
id: `${r.id}`,
|
||
name: `${r.title}`,
|
||
description: `${r.description || ''}`,
|
||
metrics: [] as Array<string>,
|
||
charts: [] as Array<string>,
|
||
updated_at: `${r.updated_at || ''}`
|
||
} as CustomReport)
|
||
}
|
||
|
||
reports.splice(0, reports.length, ...list)
|
||
} catch (e) {
|
||
console.error('loadReports failed', e)
|
||
uni.showToast({ title: '鎶ヨ〃鍔犺浇澶辫触', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
function createReport() {
|
||
editingReport.value = null
|
||
|
||
reportForm.name = ''
|
||
reportForm.description = ''
|
||
reportForm.metrics = [] as Array<string>
|
||
reportForm.period = '7d'
|
||
reportForm.chartType = 'line'
|
||
|
||
formErrors.name = ''
|
||
formErrors.description = ''
|
||
formErrors.metrics = ''
|
||
formErrors.period = ''
|
||
formErrors.chartType = ''
|
||
|
||
showCreateModal.value = true
|
||
}
|
||
|
||
function editReport(report: CustomReport) {
|
||
editingReport.value = report
|
||
|
||
reportForm.name = report.name
|
||
reportForm.description = report.description
|
||
reportForm.metrics = report.metrics
|
||
reportForm.period = '7d'
|
||
reportForm.chartType = 'line'
|
||
|
||
formErrors.name = ''
|
||
formErrors.description = ''
|
||
formErrors.metrics = ''
|
||
formErrors.period = ''
|
||
formErrors.chartType = ''
|
||
|
||
showCreateModal.value = true
|
||
}
|
||
|
||
function deleteReport(report: CustomReport) {
|
||
uni.showModal({
|
||
title: '纭鍒犻櫎',
|
||
content: `纭畾瑕佸垹闄ゆ姤琛?${report.name}"鍚楋紵`,
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
doDeleteReport(report)
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
async function doDeleteReport(report: CustomReport) {
|
||
try {
|
||
await ensureSupabaseReady()
|
||
|
||
await deleteCustomReport(report.id)
|
||
uni.showToast({ title: '鍒犻櫎鎴愬姛', icon: 'success' })
|
||
loadReports()
|
||
} catch (e: any) {
|
||
console.error('doDeleteReport failed', e)
|
||
const errorMsg = e?.message || '鍒犻櫎澶辫触'
|
||
uni.showToast({ title: errorMsg, icon: 'none' })
|
||
}
|
||
}
|
||
|
||
function toggleMetric(key: string) {
|
||
const index = reportForm.metrics.indexOf(key)
|
||
if (index >= 0) {
|
||
reportForm.metrics.splice(index, 1)
|
||
} else {
|
||
reportForm.metrics.push(key)
|
||
}
|
||
if (reportForm.metrics.length > 0) {
|
||
formErrors.metrics = ''
|
||
}
|
||
}
|
||
|
||
function onNameInput() {
|
||
const name = reportForm.name.trim()
|
||
if (name.length === 0) {
|
||
formErrors.name = '鎶ヨ〃鍚嶇О涓嶈兘涓虹┖'
|
||
} else if (name.length > 50) {
|
||
formErrors.name = '鎶ヨ〃鍚嶇О涓嶈兘瓒呰繃50涓瓧绗?
|
||
} else {
|
||
formErrors.name = ''
|
||
}
|
||
}
|
||
|
||
function onDescriptionInput() {
|
||
const desc = reportForm.description
|
||
if (desc.length > 200) {
|
||
formErrors.description = '鎶ヨ〃鎻忚堪涓嶈兘瓒呰繃200涓瓧绗?
|
||
} else {
|
||
formErrors.description = ''
|
||
}
|
||
}
|
||
|
||
function selectPeriod(value: string) {
|
||
reportForm.period = value
|
||
formErrors.period = ''
|
||
}
|
||
|
||
function selectChartType(value: string) {
|
||
reportForm.chartType = value
|
||
formErrors.chartType = ''
|
||
}
|
||
|
||
function validateReportForm(): boolean {
|
||
onNameInput()
|
||
onDescriptionInput()
|
||
|
||
if (reportForm.metrics.length === 0) {
|
||
formErrors.metrics = '璇疯嚦灏戦€夋嫨涓€涓寚鏍?
|
||
} else {
|
||
formErrors.metrics = ''
|
||
}
|
||
|
||
if (!reportForm.period) {
|
||
formErrors.period = '璇烽€夋嫨鏃堕棿缁村害'
|
||
}
|
||
if (!reportForm.chartType) {
|
||
formErrors.chartType = '璇烽€夋嫨鍥捐〃绫诲瀷'
|
||
}
|
||
|
||
if (formErrors.name || formErrors.description || formErrors.metrics || formErrors.period || formErrors.chartType) {
|
||
uni.showToast({ title: '璇峰厛淇琛ㄥ崟涓殑閿欒鎻愮ず', icon: 'none' })
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
|
||
async function saveReport() {
|
||
if (!validateReportForm()) {
|
||
return
|
||
}
|
||
|
||
try {
|
||
uni.showLoading({ title: '淇濆瓨涓?..' })
|
||
await ensureSupabaseReady()
|
||
|
||
const uid = getUserIdOrNull()
|
||
if (!uid || uid.length === 0) {
|
||
uni.hideLoading()
|
||
uni.showModal({
|
||
title: '闇€瑕佺櫥褰?,
|
||
content: '鍒涘缓鑷畾涔夋姤琛ㄩ渶瑕佸厛鐧诲綍锛屾槸鍚﹀墠寰€鐧诲綍椤甸潰锛?,
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
goToLogin('/pages/mall/analytics/custom-report')
|
||
}
|
||
}
|
||
})
|
||
return
|
||
}
|
||
|
||
let newReportId = ''
|
||
|
||
if (editingReport.value == null) {
|
||
newReportId = await createCustomReport({
|
||
title: reportForm.name,
|
||
description: reportForm.description || '',
|
||
period: reportForm.period,
|
||
metrics: reportForm.metrics,
|
||
chartType: reportForm.chartType || 'line'
|
||
})
|
||
} else {
|
||
await updateCustomReport({
|
||
reportId: editingReport.value.id,
|
||
title: reportForm.name,
|
||
description: reportForm.description || null,
|
||
period: reportForm.period || null
|
||
})
|
||
newReportId = editingReport.value.id
|
||
}
|
||
|
||
uni.hideLoading()
|
||
|
||
// 妫€鏌?newReportId 鏄惁鏈夋晥锛屾棤鏁堝垯璁や负鍒涘缓澶辫触
|
||
if (newReportId == null || newReportId.length === 0) {
|
||
uni.showToast({
|
||
title: '鍒涘缓澶辫触锛氭湭杩斿洖鎶ヨ〃ID',
|
||
icon: 'none',
|
||
duration: 3000
|
||
})
|
||
return
|
||
}
|
||
|
||
uni.showToast({ title: '淇濆瓨鎴愬姛', icon: 'success' })
|
||
closeModal()
|
||
loadReports()
|
||
|
||
setTimeout(() => {
|
||
uni.navigateTo({
|
||
url: `/pages/mall/analytics/report-detail?reportId=${newReportId}`
|
||
})
|
||
}, 400)
|
||
|
||
} catch (e: any) {
|
||
uni.hideLoading()
|
||
console.error('saveReport exception:', e)
|
||
uni.showToast({
|
||
title: mapAnalyticsError(e, { fallbackMessage: '淇濆瓨澶辫触' }),
|
||
icon: 'none',
|
||
duration: 3000
|
||
})
|
||
}
|
||
}
|
||
|
||
function openReport(report: CustomReport) {
|
||
uni.navigateTo({
|
||
url: `/pages/mall/analytics/report-detail?reportId=${report.id}`
|
||
})
|
||
}
|
||
|
||
function closeModal() {
|
||
showCreateModal.value = false
|
||
editingReport.value = null
|
||
}
|
||
|
||
function refreshData() {
|
||
loadReports()
|
||
uni.showToast({ title: '宸插埛鏂?, icon: 'success' })
|
||
}
|
||
|
||
function handleMenu() {
|
||
showSidebarMenu.value = true
|
||
}
|
||
|
||
function handleSidebarUpdate(visible: boolean) {
|
||
showSidebarMenu.value = visible
|
||
}
|
||
|
||
function toggleMoreMenu() {
|
||
showMoreMenu.value = !showMoreMenu.value
|
||
}
|
||
|
||
function closeMoreMenu() {
|
||
showMoreMenu.value = false
|
||
}
|
||
|
||
function goToLogin() {
|
||
ensureAnalyticsLogin({ toastTitle: '璇峰厛鐧诲綍鍚庝娇鐢ㄨ嚜瀹氫箟鎶ヨ〃' })
|
||
}
|
||
|
||
function handleSearch() {
|
||
uni.showToast({ title: '鎼滅储', icon: 'none' })
|
||
}
|
||
|
||
function handleNotification() {
|
||
uni.showToast({ title: '閫氱煡', icon: 'none' })
|
||
}
|
||
|
||
function handleFullscreen() {
|
||
uni.showToast({ title: '鍏ㄥ睆', icon: 'none' })
|
||
}
|
||
|
||
function handleMobile() {
|
||
uni.showToast({ title: '绉诲姩绔?, icon: 'none' })
|
||
}
|
||
|
||
function handleDropdown() {
|
||
uni.showToast({ title: '涓嬫媺鑿滃崟', icon: 'none' })
|
||
}
|
||
|
||
function handleSettings() {
|
||
uni.showToast({ title: '璁剧疆', icon: 'none' })
|
||
}
|
||
|
||
function handleGoToLogin() {
|
||
goToLoginPage('/pages/mall/analytics/custom-report')
|
||
}
|
||
|
||
</script>
|
||
|
||
<style>
|
||
.page {
|
||
min-height: 100vh;
|
||
background: #f6f7fb;
|
||
}
|
||
|
||
/* 椤甸潰甯冨眬锛氬灞忔椂渚ц竟鏍?鍐呭锛岀獎灞忔椂鍏ㄥ睆鍐呭 */
|
||
.page-layout {
|
||
display: flex;
|
||
flex-direction: row !important;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
.main-content {
|
||
flex: 1;
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding-top: 64px; /* 涓哄浐瀹氶《閮ㄥ鑸爮鐣欏嚭绌洪棿 */
|
||
}
|
||
|
||
.container {
|
||
width: 100%;
|
||
max-width: 1280px;
|
||
margin: 0 auto;
|
||
/* padding removed */ 16px 28px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
/* 椤堕儴鏍?*/
|
||
.topbar {
|
||
display: flex;
|
||
flex-direction: row !important;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 14px 16px;
|
||
background: #fff;
|
||
border-radius: 14px;
|
||
border: 1px solid rgba(0,0,0,0.06);
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.topbar-left {
|
||
display: flex;
|
||
flex-direction: row !important;
|
||
align-items: center;
|
||
gap: 12px;
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.menu-icon {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 8px;
|
||
background: #f3f4f6;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.menu-icon:active {
|
||
background: #e5e7eb;
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
.menu-icon .icon {
|
||
font-size: 18px;
|
||
color: #111;
|
||
line-height: 1;
|
||
}
|
||
|
||
.title-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 4px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.title {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: #111;
|
||
max-width: 420px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.subtitle {
|
||
font-size: 12px;
|
||
color: rgba(0,0,0,0.55);
|
||
max-width: 420px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.topbar-right {
|
||
display: flex;
|
||
flex-direction: row !important;
|
||
gap: 8px;
|
||
align-items: center;
|
||
flex-wrap: nowrap;
|
||
flex-shrink: 0;
|
||
position: relative;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.icon-btn-icon {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 8px;
|
||
background: #f3f4f6;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.icon-btn-icon:active {
|
||
background: #e5e7eb;
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
.icon-btn-icon .icon {
|
||
font-size: 16px;
|
||
line-height: 1;
|
||
}
|
||
|
||
.more-btn {
|
||
display: none;
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 8px;
|
||
background: #f3f4f6;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
position: relative;
|
||
transition: all 0.2s;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.more-btn.active {
|
||
background: #e5e7eb;
|
||
}
|
||
|
||
.more-btn .icon {
|
||
font-size: 18px;
|
||
line-height: 1;
|
||
color: #111;
|
||
}
|
||
|
||
/* 宸ュ叿鏍?*/
|
||
.toolbar {
|
||
margin-top: 12px;
|
||
padding: 12px 16px;
|
||
background: #fff;
|
||
border-radius: 14px;
|
||
border: 1px solid rgba(0,0,0,0.06);
|
||
display: flex;
|
||
flex-direction: row !important;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
}
|
||
|
||
.toolbar-left {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.toolbar-title {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #111;
|
||
}
|
||
|
||
.toolbar-subtitle {
|
||
font-size: 12px;
|
||
color: rgba(0,0,0,0.55);
|
||
}
|
||
|
||
.toolbar-right {
|
||
display: flex;
|
||
flex-direction: row !important;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.btn-primary {
|
||
padding: 8px 16px;
|
||
border-radius: 999px;
|
||
border: none;
|
||
background: #111827;
|
||
color: #fff;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.btn-primary:active {
|
||
opacity: 0.9;
|
||
}
|
||
|
||
/* 鎶ヨ〃鍒楄〃 */
|
||
.report-list {
|
||
margin-top: 12px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.report-card {
|
||
background: #fff;
|
||
border-radius: 14px;
|
||
border: 1px solid rgba(0,0,0,0.06);
|
||
padding: 16px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.report-card:active {
|
||
background: #f9fafb;
|
||
transform: scale(0.98);
|
||
}
|
||
|
||
.report-header {
|
||
display: flex;
|
||
flex-direction: row !important;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.report-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #111;
|
||
}
|
||
|
||
.report-actions {
|
||
display: flex;
|
||
flex-direction: row !important;
|
||
gap: 8px;
|
||
}
|
||
|
||
.action-btn {
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 6px;
|
||
background: #f3f4f6;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.action-btn:active {
|
||
background: #e5e7eb;
|
||
}
|
||
|
||
.action-btn .icon {
|
||
font-size: 14px;
|
||
}
|
||
|
||
.report-desc {
|
||
font-size: 13px;
|
||
color: rgba(0,0,0,0.65);
|
||
margin-bottom: 12px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.report-meta {
|
||
display: flex;
|
||
flex-direction: row !important;
|
||
gap: 16px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.meta-item {
|
||
font-size: 12px;
|
||
color: rgba(0,0,0,0.45);
|
||
}
|
||
|
||
.empty-state {
|
||
margin-top: 24px;
|
||
padding: 32px 16px;
|
||
background: #fff;
|
||
border-radius: 14px;
|
||
border: 1px solid rgba(0,0,0,0.06);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.empty-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #111;
|
||
}
|
||
|
||
.empty-desc {
|
||
font-size: 13px;
|
||
color: rgba(0,0,0,0.55);
|
||
text-align: center;
|
||
}
|
||
|
||
/* 妯℃€佹 */
|
||
.modal {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0,0,0,0.5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.modal-content {
|
||
width: 90%;
|
||
max-width: 600px;
|
||
max-height: 80vh;
|
||
background: #fff;
|
||
border-radius: 16px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.modal-header {
|
||
display: flex;
|
||
flex-direction: row !important;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 16px;
|
||
border-bottom: 1px solid rgba(0,0,0,0.06);
|
||
}
|
||
|
||
.modal-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #111;
|
||
}
|
||
|
||
.modal-close {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 8px;
|
||
background: #f3f4f6;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.modal-close .icon {
|
||
font-size: 18px;
|
||
color: #111;
|
||
}
|
||
|
||
.modal-body {
|
||
flex: 1;
|
||
padding: 16px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.form-item {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.form-label {
|
||
display: block;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: #111;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.form-error {
|
||
margin-top: 4px;
|
||
font-size: 12px;
|
||
color: #dc2626;
|
||
}
|
||
|
||
.form-input,
|
||
.form-textarea {
|
||
width: 100%;
|
||
padding: 10px 12px;
|
||
border: 1px solid rgba(0,0,0,0.1);
|
||
border-radius: 8px;
|
||
font-size: 13px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.form-textarea {
|
||
min-height: 80px;
|
||
resize: none;
|
||
}
|
||
|
||
.metric-list,
|
||
.period-list,
|
||
.chart-type-list {
|
||
display: flex;
|
||
flex-direction: row !important;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
.metric-item,
|
||
.period-item,
|
||
.chart-type-item {
|
||
padding: 8px 12px;
|
||
border: 1px solid rgba(0,0,0,0.1);
|
||
border-radius: 8px;
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.metric-item.selected,
|
||
.period-item.selected,
|
||
.chart-type-item.selected {
|
||
background: #111;
|
||
color: #fff;
|
||
border-color: #111;
|
||
}
|
||
|
||
.modal-footer {
|
||
display: flex;
|
||
flex-direction: row !important;
|
||
gap: 12px;
|
||
padding: 16px;
|
||
border-top: 1px solid rgba(0,0,0,0.06);
|
||
}
|
||
|
||
.btn {
|
||
flex: 1;
|
||
padding: 10px;
|
||
border-radius: 8px;
|
||
text-align: center;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.btn-cancel {
|
||
background: #f3f4f6;
|
||
color: #111;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: #111;
|
||
color: #fff;
|
||
}
|
||
|
||
/* 鍝嶅簲寮?*/
|
||
@media screen and (max-width: 960px) {
|
||
.title,
|
||
.subtitle {
|
||
max-width: 200px;
|
||
}
|
||
|
||
.topbar-right .btn-hidden {
|
||
display: none !important;
|
||
}
|
||
|
||
.more-btn {
|
||
display: flex !important;
|
||
}
|
||
|
||
.modal-content {
|
||
width: 95%;
|
||
max-height: 90vh;
|
||
}
|
||
}
|
||
|
||
/* 鍝嶅簲寮忥細绐勫睆鏃跺叏灞忔樉绀?*/
|
||
@media screen and (max-width: 959px) {
|
||
.page-layout {
|
||
flex-direction: column !important;
|
||
}
|
||
|
||
.main-content {
|
||
width: 100%;
|
||
}
|
||
}
|
||
</style>
|
||
|