Commit e1f34014c8d0fee776b55bd7a8f9efe390614fae
1 parent
fd5f9045
chore: update development environment configuration
- Swapped the VUE_APP_BASE_API value to point to localhost for local development. - Commented out the previous test API URL for clarity.
Showing
3 changed files
with
1598 additions
and
2 deletions
antis-ncc-admin/.env.development
| ... | ... | @@ -2,8 +2,8 @@ |
| 2 | 2 | |
| 3 | 3 | VUE_CLI_BABEL_TRANSPILE_MODULES = true |
| 4 | 4 | # VUE_APP_BASE_API = 'https://erp.lvqianmeiye.com' |
| 5 | -VUE_APP_BASE_API = 'http://erp_test.lvqianmeiye.com' | |
| 6 | -# VUE_APP_BASE_API = 'http://localhost:2011' | |
| 5 | +# VUE_APP_BASE_API = 'http://erp_test.lvqianmeiye.com' | |
| 6 | +VUE_APP_BASE_API = 'http://localhost:2011' | |
| 7 | 7 | # VUE_APP_BASE_API = 'http://localhost:2011' |
| 8 | 8 | VUE_APP_IMG_API = '' |
| 9 | 9 | VUE_APP_BASE_WSS = 'ws://192.168.110.45:2011/websocket' | ... | ... |
antis-ncc-admin/src/views/extend/storeDashboard/index.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <div class="store-dashboard"> | |
| 3 | + <!-- 顶部:门店信息 + 核心指标 --> | |
| 4 | + <div class="dashboard-header"> | |
| 5 | + <div class="header-left"> | |
| 6 | + <div class="store-info"> | |
| 7 | + <div class="store-avatar"> | |
| 8 | + <i class="el-icon-office-building"></i> | |
| 9 | + </div> | |
| 10 | + <div class="store-details"> | |
| 11 | + <div class="store-name-row"> | |
| 12 | + <h2 class="store-name">示例门店名称</h2> | |
| 13 | + <el-tag type="success" size="small">正常营业</el-tag> | |
| 14 | + </div> | |
| 15 | + <div class="store-meta"> | |
| 16 | + <span class="meta-item"><i class="el-icon-tickets"></i> MD2024001</span> | |
| 17 | + <span class="meta-item"><i class="el-icon-location"></i> 北京市朝阳区</span> | |
| 18 | + <span class="meta-item"><i class="el-icon-calendar"></i> 2024-01-15</span> | |
| 19 | + </div> | |
| 20 | + </div> | |
| 21 | + </div> | |
| 22 | + </div> | |
| 23 | + <div class="header-right"> | |
| 24 | + <div class="core-stats"> | |
| 25 | + <div class="core-stat-item primary"> | |
| 26 | + <div class="stat-label">开单业绩</div> | |
| 27 | + <div class="stat-value">¥1,258,680</div> | |
| 28 | + <div class="stat-trend up">+12.5%</div> | |
| 29 | + </div> | |
| 30 | + <div class="core-stat-item success"> | |
| 31 | + <div class="stat-label">消耗业绩</div> | |
| 32 | + <div class="stat-value">¥986,420</div> | |
| 33 | + <div class="stat-trend up">+8.3%</div> | |
| 34 | + </div> | |
| 35 | + <div class="core-stat-item info"> | |
| 36 | + <div class="stat-label">完成率</div> | |
| 37 | + <div class="stat-value">85.6%</div> | |
| 38 | + <div class="stat-trend up">+2.1%</div> | |
| 39 | + </div> | |
| 40 | + <div class="core-stat-item warning"> | |
| 41 | + <div class="stat-label">净业绩</div> | |
| 42 | + <div class="stat-value">¥272,260</div> | |
| 43 | + <div class="stat-trend up">+15.8%</div> | |
| 44 | + </div> | |
| 45 | + </div> | |
| 46 | + </div> | |
| 47 | + </div> | |
| 48 | + | |
| 49 | + <!-- 核心KPI指标 --> | |
| 50 | + <div class="kpi-section"> | |
| 51 | + <div class="kpi-card" v-for="(kpi, index) in kpiList" :key="index" :class="kpi.type"> | |
| 52 | + <div class="kpi-icon"> | |
| 53 | + <i :class="kpi.icon"></i> | |
| 54 | + </div> | |
| 55 | + <div class="kpi-content"> | |
| 56 | + <div class="kpi-label">{{ kpi.label }}</div> | |
| 57 | + <div class="kpi-value"> | |
| 58 | + <span class="unit" v-if="kpi.isMoney">¥</span>{{ kpi.value }} | |
| 59 | + <span class="unit" v-if="kpi.isPercent">%</span> | |
| 60 | + </div> | |
| 61 | + </div> | |
| 62 | + <div class="kpi-trend" v-if="kpi.trend"> | |
| 63 | + <i :class="kpi.trendIcon"></i> | |
| 64 | + <span>{{ kpi.trend }}</span> | |
| 65 | + </div> | |
| 66 | + </div> | |
| 67 | + </div> | |
| 68 | + | |
| 69 | + <!-- 主要内容区域:左右分栏 --> | |
| 70 | + <div class="main-content"> | |
| 71 | + <!-- 左侧:图表区域 --> | |
| 72 | + <div class="content-left"> | |
| 73 | + <!-- 第一行:业绩趋势图 + 品项分类占比 --> | |
| 74 | + <el-row :gutter="16" class="chart-row"> | |
| 75 | + <el-col :span="16"> | |
| 76 | + <el-card class="chart-card" shadow="hover"> | |
| 77 | + <div slot="header" class="card-header"> | |
| 78 | + <i class="el-icon-data-line"></i> | |
| 79 | + <span>近12个月业绩趋势</span> | |
| 80 | + </div> | |
| 81 | + <div ref="trendChart" class="chart-container"></div> | |
| 82 | + </el-card> | |
| 83 | + </el-col> | |
| 84 | + <el-col :span="8"> | |
| 85 | + <el-card class="chart-card" shadow="hover"> | |
| 86 | + <div slot="header" class="card-header"> | |
| 87 | + <i class="el-icon-pie-chart"></i> | |
| 88 | + <span>品项分类占比</span> | |
| 89 | + </div> | |
| 90 | + <div ref="categoryChart" class="chart-container"></div> | |
| 91 | + </el-card> | |
| 92 | + </el-col> | |
| 93 | + </el-row> | |
| 94 | + | |
| 95 | + <!-- 第二行:每日运营数据趋势 + 门店综合能力雷达图 --> | |
| 96 | + <el-row :gutter="16" class="chart-row"> | |
| 97 | + <el-col :span="16"> | |
| 98 | + <el-card class="chart-card" shadow="hover"> | |
| 99 | + <div slot="header" class="card-header"> | |
| 100 | + <i class="el-icon-date"></i> | |
| 101 | + <span>每日运营数据趋势</span> | |
| 102 | + </div> | |
| 103 | + <div ref="dailyChart" class="chart-container"></div> | |
| 104 | + </el-card> | |
| 105 | + </el-col> | |
| 106 | + <el-col :span="8"> | |
| 107 | + <el-card class="chart-card" shadow="hover"> | |
| 108 | + <div slot="header" class="card-header"> | |
| 109 | + <i class="el-icon-aim"></i> | |
| 110 | + <span>门店综合能力分析</span> | |
| 111 | + </div> | |
| 112 | + <div ref="radarChart" class="chart-container"></div> | |
| 113 | + </el-card> | |
| 114 | + </el-col> | |
| 115 | + </el-row> | |
| 116 | + | |
| 117 | + <!-- 第三行:业绩对比分析 + 各分类业绩堆叠对比 --> | |
| 118 | + <el-row :gutter="16" class="chart-row"> | |
| 119 | + <el-col :span="12"> | |
| 120 | + <el-card class="chart-card" shadow="hover"> | |
| 121 | + <div slot="header" class="card-header"> | |
| 122 | + <i class="el-icon-s-marketing"></i> | |
| 123 | + <span>业绩对比分析</span> | |
| 124 | + </div> | |
| 125 | + <div ref="compareChart" class="chart-container"></div> | |
| 126 | + </el-card> | |
| 127 | + </el-col> | |
| 128 | + <el-col :span="12"> | |
| 129 | + <el-card class="chart-card" shadow="hover"> | |
| 130 | + <div slot="header" class="card-header"> | |
| 131 | + <i class="el-icon-s-data"></i> | |
| 132 | + <span>各分类业绩堆叠对比</span> | |
| 133 | + </div> | |
| 134 | + <div ref="stackedChart" class="chart-container"></div> | |
| 135 | + </el-card> | |
| 136 | + </el-col> | |
| 137 | + </el-row> | |
| 138 | + | |
| 139 | + <!-- 第四行:会员转化漏斗 + 客单价与项目数关系 --> | |
| 140 | + <el-row :gutter="16" class="chart-row"> | |
| 141 | + <el-col :span="12"> | |
| 142 | + <el-card class="chart-card" shadow="hover"> | |
| 143 | + <div slot="header" class="card-header"> | |
| 144 | + <i class="el-icon-sort"></i> | |
| 145 | + <span>会员转化漏斗</span> | |
| 146 | + </div> | |
| 147 | + <div ref="funnelChart" class="chart-container"></div> | |
| 148 | + </el-card> | |
| 149 | + </el-col> | |
| 150 | + <el-col :span="12"> | |
| 151 | + <el-card class="chart-card" shadow="hover"> | |
| 152 | + <div slot="header" class="card-header"> | |
| 153 | + <i class="el-icon-s-marketing"></i> | |
| 154 | + <span>客单价与项目数关系分析</span> | |
| 155 | + </div> | |
| 156 | + <div ref="scatterChart" class="chart-container"></div> | |
| 157 | + </el-card> | |
| 158 | + </el-col> | |
| 159 | + </el-row> | |
| 160 | + | |
| 161 | + <!-- 第五行:一周运营热力图 --> | |
| 162 | + <el-row :gutter="16" class="chart-row"> | |
| 163 | + <el-col :span="24"> | |
| 164 | + <el-card class="chart-card" shadow="hover"> | |
| 165 | + <div slot="header" class="card-header"> | |
| 166 | + <i class="el-icon-s-grid"></i> | |
| 167 | + <span>一周运营热力图</span> | |
| 168 | + </div> | |
| 169 | + <div ref="heatmapChart" class="chart-container"></div> | |
| 170 | + </el-card> | |
| 171 | + </el-col> | |
| 172 | + </el-row> | |
| 173 | + | |
| 174 | + <!-- 第六行:品项开单排行 --> | |
| 175 | + <el-row :gutter="16" class="chart-row"> | |
| 176 | + <el-col :span="24"> | |
| 177 | + <el-card class="table-card" shadow="hover"> | |
| 178 | + <div slot="header" class="card-header"> | |
| 179 | + <i class="el-icon-shopping-bag-1"></i> | |
| 180 | + <span>品项开单排行(Top 10)</span> | |
| 181 | + </div> | |
| 182 | + <el-table :data="topBillingItems" size="small" border stripe> | |
| 183 | + <el-table-column type="index" label="排名" width="60" align="center"> | |
| 184 | + <template slot-scope="scope"> | |
| 185 | + <el-tag v-if="scope.$index < 3" :type="['danger', 'warning', 'success'][scope.$index]" size="mini"> | |
| 186 | + {{ scope.$index + 1 }} | |
| 187 | + </el-tag> | |
| 188 | + <span v-else>{{ scope.$index + 1 }}</span> | |
| 189 | + </template> | |
| 190 | + </el-table-column> | |
| 191 | + <el-table-column prop="itemName" label="品项名称" min-width="180" /> | |
| 192 | + <el-table-column prop="billingAmount" label="开单金额" width="140" align="right"> | |
| 193 | + <template slot-scope="scope"> | |
| 194 | + <span style="font-weight: 600; color: #67C23A;">¥{{ formatMoney(scope.row.billingAmount) }}</span> | |
| 195 | + </template> | |
| 196 | + </el-table-column> | |
| 197 | + <el-table-column prop="billingCount" label="开单次数" width="100" align="center" /> | |
| 198 | + <el-table-column prop="category" label="分类" width="80" align="center"> | |
| 199 | + <template slot-scope="scope"> | |
| 200 | + <el-tag size="mini" :type="getCategoryType(scope.row.category)">{{ scope.row.category }}</el-tag> | |
| 201 | + </template> | |
| 202 | + </el-table-column> | |
| 203 | + </el-table> | |
| 204 | + </el-card> | |
| 205 | + </el-col> | |
| 206 | + </el-row> | |
| 207 | + | |
| 208 | + <!-- 第七行:健康师业绩排行 + 消耗品项排行 --> | |
| 209 | + <el-row :gutter="16" class="chart-row"> | |
| 210 | + <el-col :span="12"> | |
| 211 | + <el-card class="table-card" shadow="hover"> | |
| 212 | + <div slot="header" class="card-header"> | |
| 213 | + <i class="el-icon-user-solid"></i> | |
| 214 | + <span>健康师业绩排行(Top 10)</span> | |
| 215 | + </div> | |
| 216 | + <el-table :data="healthCoachRanking" size="small" border stripe> | |
| 217 | + <el-table-column type="index" label="排名" width="60" align="center" /> | |
| 218 | + <el-table-column prop="name" label="健康师姓名" min-width="120" /> | |
| 219 | + <el-table-column prop="billingPerformance" label="开单业绩" width="120" align="right"> | |
| 220 | + <template slot-scope="scope">¥{{ formatMoney(scope.row.billingPerformance) }}</template> | |
| 221 | + </el-table-column> | |
| 222 | + <el-table-column prop="consumePerformance" label="消耗业绩" width="120" align="right"> | |
| 223 | + <template slot-scope="scope">¥{{ formatMoney(scope.row.consumePerformance) }}</template> | |
| 224 | + </el-table-column> | |
| 225 | + <el-table-column prop="totalPerformance" label="总业绩" width="120" align="right"> | |
| 226 | + <template slot-scope="scope">¥{{ formatMoney(scope.row.totalPerformance) }}</template> | |
| 227 | + </el-table-column> | |
| 228 | + </el-table> | |
| 229 | + </el-card> | |
| 230 | + </el-col> | |
| 231 | + <el-col :span="12"> | |
| 232 | + <el-card class="table-card" shadow="hover"> | |
| 233 | + <div slot="header" class="card-header"> | |
| 234 | + <i class="el-icon-goods"></i> | |
| 235 | + <span>消耗品项排行(Top 10)</span> | |
| 236 | + </div> | |
| 237 | + <el-table :data="topConsumeItems" size="small" border stripe> | |
| 238 | + <el-table-column type="index" label="排名" width="60" align="center" /> | |
| 239 | + <el-table-column prop="itemName" label="品项名称" min-width="150" /> | |
| 240 | + <el-table-column prop="consumeAmount" label="消耗金额" width="120" align="right"> | |
| 241 | + <template slot-scope="scope"> | |
| 242 | + <span style="font-weight: 600; color: #409EFF;">¥{{ formatMoney(scope.row.consumeAmount) }}</span> | |
| 243 | + </template> | |
| 244 | + </el-table-column> | |
| 245 | + <el-table-column prop="category" label="分类" width="80" /> | |
| 246 | + </el-table> | |
| 247 | + </el-card> | |
| 248 | + </el-col> | |
| 249 | + </el-row> | |
| 250 | + </div> | |
| 251 | + | |
| 252 | + <!-- 右侧:指标卡片区域 --> | |
| 253 | + <div class="content-right"> | |
| 254 | + <!-- 业绩概览 --> | |
| 255 | + <el-card class="metrics-card" shadow="hover"> | |
| 256 | + <div slot="header" class="card-header"> | |
| 257 | + <i class="el-icon-data-line"></i> | |
| 258 | + <span>业绩概览</span> | |
| 259 | + </div> | |
| 260 | + <div class="metrics-grid"> | |
| 261 | + <div class="metric-item" v-for="(item, index) in performanceList" :key="index"> | |
| 262 | + <div class="metric-icon" :style="{ background: item.iconBg }"> | |
| 263 | + <i :class="item.icon"></i> | |
| 264 | + </div> | |
| 265 | + <div class="metric-info"> | |
| 266 | + <div class="metric-label">{{ item.label }}</div> | |
| 267 | + <div class="metric-value">{{ item.value }}</div> | |
| 268 | + </div> | |
| 269 | + </div> | |
| 270 | + </div> | |
| 271 | + </el-card> | |
| 272 | + | |
| 273 | + <!-- 运营指标 --> | |
| 274 | + <el-card class="metrics-card" shadow="hover"> | |
| 275 | + <div slot="header" class="card-header"> | |
| 276 | + <i class="el-icon-s-data"></i> | |
| 277 | + <span>运营指标</span> | |
| 278 | + </div> | |
| 279 | + <div class="metrics-grid"> | |
| 280 | + <div class="metric-item" v-for="(item, index) in operationList" :key="index"> | |
| 281 | + <div class="metric-icon" :style="{ background: item.iconBg }"> | |
| 282 | + <i :class="item.icon"></i> | |
| 283 | + </div> | |
| 284 | + <div class="metric-info"> | |
| 285 | + <div class="metric-label">{{ item.label }}</div> | |
| 286 | + <div class="metric-value">{{ item.value }}</div> | |
| 287 | + </div> | |
| 288 | + </div> | |
| 289 | + </div> | |
| 290 | + </el-card> | |
| 291 | + | |
| 292 | + <!-- 会员分析 --> | |
| 293 | + <el-card class="metrics-card" shadow="hover"> | |
| 294 | + <div slot="header" class="card-header"> | |
| 295 | + <i class="el-icon-user"></i> | |
| 296 | + <span>会员分析</span> | |
| 297 | + </div> | |
| 298 | + <div class="metrics-grid"> | |
| 299 | + <div class="metric-item" v-for="(item, index) in memberList" :key="index"> | |
| 300 | + <div class="metric-icon" :style="{ background: item.iconBg }"> | |
| 301 | + <i :class="item.icon"></i> | |
| 302 | + </div> | |
| 303 | + <div class="metric-info"> | |
| 304 | + <div class="metric-label">{{ item.label }}</div> | |
| 305 | + <div class="metric-value">{{ item.value }}</div> | |
| 306 | + <div class="metric-rate" v-if="item.rate">{{ item.rate }}</div> | |
| 307 | + </div> | |
| 308 | + </div> | |
| 309 | + </div> | |
| 310 | + </el-card> | |
| 311 | + | |
| 312 | + <!-- 目标完成度仪表盘 --> | |
| 313 | + <el-card class="chart-card-small" shadow="hover"> | |
| 314 | + <div slot="header" class="card-header"> | |
| 315 | + <i class="el-icon-odometer"></i> | |
| 316 | + <span>目标完成度</span> | |
| 317 | + </div> | |
| 318 | + <div ref="gaugeChart" class="chart-container-small"></div> | |
| 319 | + </el-card> | |
| 320 | + | |
| 321 | + <!-- 各分类业绩占比趋势 --> | |
| 322 | + <el-card class="chart-card-small" shadow="hover"> | |
| 323 | + <div slot="header" class="card-header"> | |
| 324 | + <i class="el-icon-data-line"></i> | |
| 325 | + <span>各分类占比趋势</span> | |
| 326 | + </div> | |
| 327 | + <div ref="stackedAreaChart" class="chart-container-small"></div> | |
| 328 | + </el-card> | |
| 329 | + | |
| 330 | + <!-- 门店排名对比 --> | |
| 331 | + <el-card class="metrics-card" shadow="hover"> | |
| 332 | + <div slot="header" class="card-header"> | |
| 333 | + <i class="el-icon-trophy"></i> | |
| 334 | + <span>门店排名对比</span> | |
| 335 | + </div> | |
| 336 | + <div class="ranking-content"> | |
| 337 | + <div class="ranking-item"> | |
| 338 | + <div class="ranking-label">业绩排名</div> | |
| 339 | + <div class="ranking-value"> | |
| 340 | + <span class="rank-number">{{ comparison.performanceRanking }}</span> | |
| 341 | + <span class="rank-total">/ {{ comparison.totalStoreCount }}</span> | |
| 342 | + </div> | |
| 343 | + <div class="ranking-badge" :class="getRankingClass(comparison.performanceRanking, comparison.totalStoreCount)"> | |
| 344 | + {{ getRankingText(comparison.performanceRanking, comparison.totalStoreCount) }} | |
| 345 | + </div> | |
| 346 | + </div> | |
| 347 | + <el-divider></el-divider> | |
| 348 | + <div class="comparison-stats"> | |
| 349 | + <div class="stat-row"> | |
| 350 | + <span class="stat-label">同类型门店平均业绩</span> | |
| 351 | + <span class="stat-value">¥{{ formatMoney(comparison.avgPerformanceSameType) }}</span> | |
| 352 | + </div> | |
| 353 | + <div class="stat-row"> | |
| 354 | + <span class="stat-label">同类型门店数</span> | |
| 355 | + <span class="stat-value">{{ comparison.sameTypeStoreCount }}家</span> | |
| 356 | + </div> | |
| 357 | + <div class="stat-row"> | |
| 358 | + <span class="stat-label">同组织门店平均业绩</span> | |
| 359 | + <span class="stat-value">¥{{ formatMoney(comparison.avgPerformanceSameOrg) }}</span> | |
| 360 | + </div> | |
| 361 | + <div class="stat-row"> | |
| 362 | + <span class="stat-label">同组织门店数</span> | |
| 363 | + <span class="stat-value">{{ comparison.sameOrgStoreCount }}家</span> | |
| 364 | + </div> | |
| 365 | + </div> | |
| 366 | + </div> | |
| 367 | + </el-card> | |
| 368 | + | |
| 369 | + <!-- 本月经营提示 --> | |
| 370 | + <el-card class="tips-card" shadow="hover"> | |
| 371 | + <div slot="header" class="card-header"> | |
| 372 | + <i class="el-icon-warning"></i> | |
| 373 | + <span>本月经营提示</span> | |
| 374 | + </div> | |
| 375 | + <div class="tips-content"> | |
| 376 | + <div class="tip-item" v-for="(tip, index) in operationTips" :key="index" :class="tip.type"> | |
| 377 | + <i :class="tip.icon"></i> | |
| 378 | + <span>{{ tip.text }}</span> | |
| 379 | + </div> | |
| 380 | + </div> | |
| 381 | + </el-card> | |
| 382 | + | |
| 383 | + <!-- 快速数据洞察 --> | |
| 384 | + <el-card class="insight-card" shadow="hover"> | |
| 385 | + <div slot="header" class="card-header"> | |
| 386 | + <i class="el-icon-data-analysis"></i> | |
| 387 | + <span>快速数据洞察</span> | |
| 388 | + </div> | |
| 389 | + <div class="insight-content"> | |
| 390 | + <div class="insight-item" v-for="(insight, index) in dataInsights" :key="index"> | |
| 391 | + <div class="insight-header"> | |
| 392 | + <span class="insight-title">{{ insight.title }}</span> | |
| 393 | + <el-tag :type="insight.tagType" size="mini">{{ insight.tag }}</el-tag> | |
| 394 | + </div> | |
| 395 | + <div class="insight-value">{{ insight.value }}</div> | |
| 396 | + <div class="insight-desc">{{ insight.desc }}</div> | |
| 397 | + </div> | |
| 398 | + </div> | |
| 399 | + </el-card> | |
| 400 | + | |
| 401 | + <!-- 本月关键指标 --> | |
| 402 | + <el-card class="key-metrics-card" shadow="hover"> | |
| 403 | + <div slot="header" class="card-header"> | |
| 404 | + <i class="el-icon-s-flag"></i> | |
| 405 | + <span>本月关键指标</span> | |
| 406 | + </div> | |
| 407 | + <div class="key-metrics-content"> | |
| 408 | + <div class="progress-item" v-for="(metric, index) in keyMetrics" :key="index"> | |
| 409 | + <div class="progress-header"> | |
| 410 | + <span class="progress-label">{{ metric.label }}</span> | |
| 411 | + <span class="progress-value">{{ metric.value }}%</span> | |
| 412 | + </div> | |
| 413 | + <el-progress :percentage="metric.value" :color="metric.color" :stroke-width="8"></el-progress> | |
| 414 | + </div> | |
| 415 | + </div> | |
| 416 | + </el-card> | |
| 417 | + </div> | |
| 418 | + </div> | |
| 419 | + | |
| 420 | + <!-- 表格区域 --> | |
| 421 | + <div class="table-section"> | |
| 422 | + | |
| 423 | + </div> | |
| 424 | + </div> | |
| 425 | +</template> | |
| 426 | + | |
| 427 | +<script> | |
| 428 | +import * as echarts from 'echarts' | |
| 429 | + | |
| 430 | +export default { | |
| 431 | + name: 'StoreDashboard', | |
| 432 | + data() { | |
| 433 | + return { | |
| 434 | + kpiList: [ | |
| 435 | + { label: '开单次数', value: '1,256', icon: 'el-icon-s-order', type: 'primary', trend: '+5.2%', trendIcon: 'el-icon-top' }, | |
| 436 | + { label: '消耗次数', value: '2,458', icon: 'el-icon-s-marketing', type: 'success', trend: '+3.7%', trendIcon: 'el-icon-top' }, | |
| 437 | + { label: '人头数', value: '1,258', icon: 'el-icon-user', type: 'info', trend: '+4.1%', trendIcon: 'el-icon-top' }, | |
| 438 | + { label: '人次', value: '3,456', icon: 'el-icon-user-solid', type: 'warning', trend: '+6.2%', trendIcon: 'el-icon-top' }, | |
| 439 | + { label: '项目数', value: '5,678', icon: 'el-icon-menu', type: 'primary', trend: '+3.5%', trendIcon: 'el-icon-top' }, | |
| 440 | + { label: '客单价', value: '¥285', icon: 'el-icon-coin', type: 'success', isMoney: true, trend: '+2.8%', trendIcon: 'el-icon-top' } | |
| 441 | + ], | |
| 442 | + performanceList: [ | |
| 443 | + { label: '开单次数', value: '1,256', icon: 'el-icon-document', iconBg: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' }, | |
| 444 | + { label: '消耗次数', value: '2,458', icon: 'el-icon-goods', iconBg: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)' }, | |
| 445 | + { label: '退卡次数', value: '23', icon: 'el-icon-refresh-left', iconBg: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)' }, | |
| 446 | + { label: '平均开单金额', value: '¥1,002', icon: 'el-icon-coin', iconBg: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)' }, | |
| 447 | + { label: '平均消耗金额', value: '¥401', icon: 'el-icon-coin', iconBg: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)' }, | |
| 448 | + { label: '剩余权益', value: '¥325.69万', icon: 'el-icon-wallet', iconBg: 'linear-gradient(135deg, #30cfd0 0%, #330867 100%)' }, | |
| 449 | + { label: '目标业绩', value: '¥120万', icon: 'el-icon-aim', iconBg: 'linear-gradient(135deg, #a8edea 0%, #fed6e3 100%)' }, | |
| 450 | + { label: '退卡金额', value: '¥4.57万', icon: 'el-icon-money', iconBg: 'linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%)' } | |
| 451 | + ], | |
| 452 | + operationList: [ | |
| 453 | + { label: '人头数', value: '1,258', icon: 'el-icon-user', iconBg: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' }, | |
| 454 | + { label: '人次', value: '3,456', icon: 'el-icon-user-solid', iconBg: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)' }, | |
| 455 | + { label: '项目数', value: '5,678', icon: 'el-icon-menu', iconBg: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)' }, | |
| 456 | + { label: '客单价', value: '¥285', icon: 'el-icon-coin', iconBg: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)' }, | |
| 457 | + { label: '项目单价', value: '¥174', icon: 'el-icon-coin', iconBg: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)' }, | |
| 458 | + { label: '人均项目数', value: '4.51', icon: 'el-icon-s-grid', iconBg: 'linear-gradient(135deg, #30cfd0 0%, #330867 100%)' } | |
| 459 | + ], | |
| 460 | + memberList: [ | |
| 461 | + { label: '总会员数', value: '3,256', icon: 'el-icon-user', iconBg: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' }, | |
| 462 | + { label: '本月新增', value: '156', icon: 'el-icon-user-solid', iconBg: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)' }, | |
| 463 | + { label: '活跃会员', value: '1,856', icon: 'el-icon-success', iconBg: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)', rate: '57.0%' }, | |
| 464 | + { label: '沉睡会员', value: '856', icon: 'el-icon-warning', iconBg: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)', rate: '26.3%' }, | |
| 465 | + { label: '生美会员', value: '2,156', icon: 'el-icon-star-on', iconBg: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)' }, | |
| 466 | + { label: '医美会员', value: '856', icon: 'el-icon-star-on', iconBg: 'linear-gradient(135deg, #30cfd0 0%, #330867 100%)' }, | |
| 467 | + { label: '科美会员', value: '456', icon: 'el-icon-star-on', iconBg: 'linear-gradient(135deg, #a8edea 0%, #fed6e3 100%)' }, | |
| 468 | + { label: '教育会员', value: '256', icon: 'el-icon-star-on', iconBg: 'linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%)' } | |
| 469 | + ], | |
| 470 | + healthCoachRanking: [ | |
| 471 | + { name: '张健康', billingPerformance: 256800, consumePerformance: 198600, totalPerformance: 455400 }, | |
| 472 | + { name: '李美丽', billingPerformance: 198600, consumePerformance: 156800, totalPerformance: 355400 }, | |
| 473 | + { name: '王优雅', billingPerformance: 186500, consumePerformance: 145600, totalPerformance: 332100 }, | |
| 474 | + { name: '刘青春', billingPerformance: 165800, consumePerformance: 128900, totalPerformance: 294700 }, | |
| 475 | + { name: '陈优雅', billingPerformance: 145600, consumePerformance: 112500, totalPerformance: 258100 }, | |
| 476 | + { name: '杨美丽', billingPerformance: 128900, consumePerformance: 98600, totalPerformance: 227500 }, | |
| 477 | + { name: '赵健康', billingPerformance: 112500, consumePerformance: 85600, totalPerformance: 198100 }, | |
| 478 | + { name: '钱优雅', billingPerformance: 98600, consumePerformance: 75600, totalPerformance: 174200 }, | |
| 479 | + { name: '孙美丽', billingPerformance: 85600, consumePerformance: 65800, totalPerformance: 151400 }, | |
| 480 | + { name: '周健康', billingPerformance: 75600, consumePerformance: 56800, totalPerformance: 132400 } | |
| 481 | + ], | |
| 482 | + topBillingItems: [ | |
| 483 | + { itemName: '高端护理套餐', billingAmount: 156800, billingCount: 45, category: '生美' }, | |
| 484 | + { itemName: '医美美容项目', billingAmount: 128600, billingCount: 38, category: '医美' }, | |
| 485 | + { itemName: 'SPA身体护理', billingAmount: 98500, billingCount: 52, category: '生美' }, | |
| 486 | + { itemName: '激光美容疗程', billingAmount: 89600, billingCount: 28, category: '医美' }, | |
| 487 | + { itemName: '皮肤管理项目', billingAmount: 75800, billingCount: 42, category: '科美' }, | |
| 488 | + { itemName: '面部深度护理', billingAmount: 68900, billingCount: 56, category: '生美' }, | |
| 489 | + { itemName: '抗衰美容项目', billingAmount: 58600, billingCount: 22, category: '医美' }, | |
| 490 | + { itemName: '美白亮肤疗程', billingAmount: 48900, billingCount: 35, category: '科美' }, | |
| 491 | + { itemName: '眼部精华护理', billingAmount: 42800, billingCount: 48, category: '生美' }, | |
| 492 | + { itemName: '补水保湿项目', billingAmount: 38600, billingCount: 62, category: '生美' } | |
| 493 | + ], | |
| 494 | + topConsumeItems: [ | |
| 495 | + { itemName: '面部护理套餐', consumeAmount: 125680, category: '生美' }, | |
| 496 | + { itemName: '身体SPA护理', consumeAmount: 98600, category: '生美' }, | |
| 497 | + { itemName: '激光美容项目', consumeAmount: 85600, category: '医美' }, | |
| 498 | + { itemName: '抗衰老治疗', consumeAmount: 75600, category: '医美' }, | |
| 499 | + { itemName: '皮肤管理项目', consumeAmount: 65800, category: '科美' }, | |
| 500 | + { itemName: '眼部护理', consumeAmount: 56800, category: '生美' }, | |
| 501 | + { itemName: '紧致提升项目', consumeAmount: 45600, category: '医美' }, | |
| 502 | + { itemName: '美白亮肤项目', consumeAmount: 38900, category: '科美' }, | |
| 503 | + { itemName: '深层清洁护理', consumeAmount: 32800, category: '生美' }, | |
| 504 | + { itemName: '补水保湿项目', consumeAmount: 28900, category: '生美' } | |
| 505 | + ], | |
| 506 | + dailyData: [ | |
| 507 | + { date: '2024-12-01', headCount: 45, personCount: 128, projectCount: 256, billingPerformance: 45680, consumePerformance: 32890 }, | |
| 508 | + { date: '2024-12-02', headCount: 52, personCount: 145, projectCount: 289, billingPerformance: 52890, consumePerformance: 38960 }, | |
| 509 | + { date: '2024-12-03', headCount: 48, personCount: 132, projectCount: 268, billingPerformance: 48960, consumePerformance: 35280 }, | |
| 510 | + { date: '2024-12-04', headCount: 56, personCount: 156, projectCount: 312, billingPerformance: 56890, consumePerformance: 41250 }, | |
| 511 | + { date: '2024-12-05', headCount: 49, personCount: 138, projectCount: 278, billingPerformance: 49860, consumePerformance: 36580 }, | |
| 512 | + { date: '2024-12-06', headCount: 58, personCount: 162, projectCount: 325, billingPerformance: 59860, consumePerformance: 43280 }, | |
| 513 | + { date: '2024-12-07', headCount: 62, personCount: 178, projectCount: 356, billingPerformance: 62890, consumePerformance: 45680 }, | |
| 514 | + { date: '2024-12-08', headCount: 55, personCount: 152, projectCount: 304, billingPerformance: 55860, consumePerformance: 40250 }, | |
| 515 | + { date: '2024-12-09', headCount: 51, personCount: 142, projectCount: 284, billingPerformance: 51860, consumePerformance: 37580 }, | |
| 516 | + { date: '2024-12-10', headCount: 59, personCount: 165, projectCount: 330, billingPerformance: 59860, consumePerformance: 43280 } | |
| 517 | + ], | |
| 518 | + trendChart: null, | |
| 519 | + categoryChart: null, | |
| 520 | + dailyChart: null, | |
| 521 | + compareChart: null, | |
| 522 | + stackedChart: null, | |
| 523 | + funnelChart: null, | |
| 524 | + scatterChart: null, | |
| 525 | + heatmapChart: null, | |
| 526 | + radarChart: null, | |
| 527 | + gaugeChart: null, | |
| 528 | + stackedAreaChart: null, | |
| 529 | + comparison: { | |
| 530 | + performanceRanking: 5, | |
| 531 | + totalStoreCount: 28, | |
| 532 | + avgPerformanceSameType: 1156800, | |
| 533 | + sameTypeStoreCount: 12, | |
| 534 | + avgPerformanceSameOrg: 1089600, | |
| 535 | + sameOrgStoreCount: 8 | |
| 536 | + }, | |
| 537 | + operationTips: [ | |
| 538 | + { type: 'success', icon: 'el-icon-success', text: '本月业绩完成度良好,保持当前节奏' }, | |
| 539 | + { type: 'warning', icon: 'el-icon-warning', text: '沉睡会员占比26.3%,建议加强会员唤醒' }, | |
| 540 | + { type: 'info', icon: 'el-icon-info', text: '客单价¥285,可通过项目组合提升' }, | |
| 541 | + { type: 'warning', icon: 'el-icon-warning', text: '退卡金额较上月增长,需关注服务质量' } | |
| 542 | + ], | |
| 543 | + dataInsights: [ | |
| 544 | + { title: '最佳营业时段', tag: '热门', tagType: 'danger', value: '14:00-17:00', desc: '此时段客流量最高,建议配置更多人手' }, | |
| 545 | + { title: '高价值会员', tag: '重点', tagType: 'warning', value: '156人', desc: '单次消费超过¥1000,需重点维护' }, | |
| 546 | + { title: '项目转化率', tag: '优秀', tagType: 'success', value: '68.5%', desc: '体验项目转化为正式开卡的比例' }, | |
| 547 | + { title: '复购周期', tag: '正常', tagType: 'info', value: '28天', desc: '会员平均复购间隔,保持稳定' } | |
| 548 | + ], | |
| 549 | + keyMetrics: [ | |
| 550 | + { label: '目标完成度', value: 85.6, color: '#67C23A' }, | |
| 551 | + { label: '会员活跃度', value: 57.0, color: '#409EFF' }, | |
| 552 | + { label: '项目满意度', value: 92.3, color: '#E6A23C' }, | |
| 553 | + { label: '员工效率', value: 78.5, color: '#F56C6C' } | |
| 554 | + ] | |
| 555 | + } | |
| 556 | + }, | |
| 557 | + mounted() { | |
| 558 | + this.initCharts() | |
| 559 | + window.addEventListener('resize', this.handleResize) | |
| 560 | + }, | |
| 561 | + beforeDestroy() { | |
| 562 | + if (this.trendChart) this.trendChart.dispose() | |
| 563 | + if (this.categoryChart) this.categoryChart.dispose() | |
| 564 | + if (this.dailyChart) this.dailyChart.dispose() | |
| 565 | + if (this.compareChart) this.compareChart.dispose() | |
| 566 | + if (this.stackedChart) this.stackedChart.dispose() | |
| 567 | + if (this.funnelChart) this.funnelChart.dispose() | |
| 568 | + if (this.scatterChart) this.scatterChart.dispose() | |
| 569 | + if (this.heatmapChart) this.heatmapChart.dispose() | |
| 570 | + if (this.radarChart) this.radarChart.dispose() | |
| 571 | + if (this.gaugeChart) this.gaugeChart.dispose() | |
| 572 | + if (this.stackedAreaChart) this.stackedAreaChart.dispose() | |
| 573 | + window.removeEventListener('resize', this.handleResize) | |
| 574 | + }, | |
| 575 | + methods: { | |
| 576 | + initCharts() { | |
| 577 | + this.$nextTick(() => { | |
| 578 | + this.renderTrendChart() | |
| 579 | + this.renderCategoryChart() | |
| 580 | + this.renderDailyChart() | |
| 581 | + this.renderCompareChart() | |
| 582 | + this.renderStackedChart() | |
| 583 | + this.renderFunnelChart() | |
| 584 | + this.renderScatterChart() | |
| 585 | + this.renderHeatmapChart() | |
| 586 | + this.renderRadarChart() | |
| 587 | + this.renderGaugeChart() | |
| 588 | + this.renderStackedAreaChart() | |
| 589 | + }) | |
| 590 | + }, | |
| 591 | + renderTrendChart() { | |
| 592 | + if (!this.$refs.trendChart) return | |
| 593 | + this.trendChart = echarts.init(this.$refs.trendChart) | |
| 594 | + const option = { | |
| 595 | + tooltip: { trigger: 'axis', axisPointer: { type: 'cross' } }, | |
| 596 | + legend: { data: ['开单业绩', '消耗业绩', '净业绩'], top: 10 }, | |
| 597 | + grid: { left: '3%', right: '4%', bottom: '3%', top: '15%', containLabel: true }, | |
| 598 | + xAxis: { type: 'category', data: ['2024-01', '2024-02', '2024-03', '2024-04', '2024-05', '2024-06', '2024-07', '2024-08', '2024-09', '2024-10', '2024-11', '2024-12'] }, | |
| 599 | + yAxis: { type: 'value', axisLabel: { formatter: '¥{value}' } }, | |
| 600 | + series: [ | |
| 601 | + { name: '开单业绩', type: 'line', smooth: true, data: [856000, 928000, 1025000, 1156000, 1089000, 1125000, 1186000, 1258000, 1156000, 1289000, 1356000, 1258680], itemStyle: { color: '#409EFF' }, areaStyle: { color: 'rgba(64, 158, 255, 0.1)' } }, | |
| 602 | + { name: '消耗业绩', type: 'line', smooth: true, data: [658000, 712000, 786000, 856000, 798000, 825000, 868000, 912000, 856000, 936000, 986000, 986420], itemStyle: { color: '#67C23A' }, areaStyle: { color: 'rgba(103, 194, 58, 0.1)' } }, | |
| 603 | + { name: '净业绩', type: 'line', smooth: true, data: [198000, 216000, 239000, 300000, 291000, 300000, 318000, 346000, 300000, 353000, 370000, 272260], itemStyle: { color: '#E6A23C' }, areaStyle: { color: 'rgba(230, 162, 60, 0.1)' } } | |
| 604 | + ] | |
| 605 | + } | |
| 606 | + this.trendChart.setOption(option) | |
| 607 | + }, | |
| 608 | + renderCategoryChart() { | |
| 609 | + if (!this.$refs.categoryChart) return | |
| 610 | + this.categoryChart = echarts.init(this.$refs.categoryChart) | |
| 611 | + const option = { | |
| 612 | + tooltip: { trigger: 'item' }, | |
| 613 | + legend: { show: false }, | |
| 614 | + series: [{ | |
| 615 | + name: '品项分类', | |
| 616 | + type: 'pie', | |
| 617 | + radius: ['40%', '70%'], | |
| 618 | + center: ['50%', '50%'], | |
| 619 | + avoidLabelOverlap: true, | |
| 620 | + itemStyle: { borderRadius: 6, borderColor: '#fff', borderWidth: 2 }, | |
| 621 | + label: { show: true, position: 'outside', formatter: '{b}\n{c}', fontSize: 12 }, | |
| 622 | + labelLine: { show: true, length: 15, length2: 10 }, | |
| 623 | + data: [ | |
| 624 | + { value: 456800, name: '生美', itemStyle: { color: '#A8D5E2' } }, | |
| 625 | + { value: 256800, name: '医美', itemStyle: { color: '#B8E6B8' } }, | |
| 626 | + { value: 198600, name: '科美', itemStyle: { color: '#FFD4A3' } }, | |
| 627 | + { value: 74220, name: '产品', itemStyle: { color: '#E6C1E6' } } | |
| 628 | + ] | |
| 629 | + }] | |
| 630 | + } | |
| 631 | + this.categoryChart.setOption(option) | |
| 632 | + }, | |
| 633 | + renderDailyChart() { | |
| 634 | + if (!this.$refs.dailyChart) return | |
| 635 | + this.dailyChart = echarts.init(this.$refs.dailyChart) | |
| 636 | + const option = { | |
| 637 | + tooltip: { trigger: 'axis', axisPointer: { type: 'cross' } }, | |
| 638 | + legend: { data: ['开单业绩', '消耗业绩', '人头数', '人次', '项目数'], top: 10 }, | |
| 639 | + grid: { left: '60px', right: '80px', top: '50px', bottom: '60px', containLabel: false }, | |
| 640 | + xAxis: { type: 'category', data: ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30'], axisLabel: { rotate: 45, interval: 2, fontSize: 11 } }, | |
| 641 | + yAxis: [ | |
| 642 | + { type: 'value', name: '业绩', position: 'left', axisLabel: { formatter: value => value >= 10000 ? '¥' + (value / 10000).toFixed(1) + '万' : '¥' + value }, splitLine: { lineStyle: { type: 'dashed', color: '#E4E7ED' } } }, | |
| 643 | + { type: 'value', name: '数量', position: 'right', splitLine: { show: false } } | |
| 644 | + ], | |
| 645 | + series: [ | |
| 646 | + { name: '开单业绩', type: 'bar', yAxisIndex: 0, data: [45680, 52890, 48960, 56890, 49860, 59860, 62890, 55860, 51860, 59860, 52890, 48960, 56890, 49860, 59860, 62890, 55860, 51860, 59860, 52890, 48960, 56890, 49860, 59860, 62890, 55860, 51860, 59860, 52890, 48960], itemStyle: { color: '#409EFF' }, barWidth: '30%' }, | |
| 647 | + { name: '消耗业绩', type: 'bar', yAxisIndex: 0, data: [32890, 38960, 35280, 41250, 36580, 43280, 45680, 40250, 37580, 43280, 38960, 35280, 41250, 36580, 43280, 45680, 40250, 37580, 43280, 38960, 35280, 41250, 36580, 43280, 45680, 40250, 37580, 43280, 38960, 35280], itemStyle: { color: '#67C23A' }, barWidth: '30%' }, | |
| 648 | + { name: '人头数', type: 'line', yAxisIndex: 1, data: [45, 52, 48, 56, 49, 58, 62, 55, 51, 59, 52, 48, 56, 49, 58, 62, 55, 51, 59, 52, 48, 56, 49, 58, 62, 55, 51, 59, 52, 48], itemStyle: { color: '#F56C6C' }, lineStyle: { width: 2 }, symbol: 'circle', symbolSize: 6 }, | |
| 649 | + { name: '人次', type: 'line', yAxisIndex: 1, data: [128, 145, 132, 156, 138, 162, 178, 152, 142, 165, 145, 132, 156, 138, 162, 178, 152, 142, 165, 145, 132, 156, 138, 162, 178, 152, 142, 165, 145, 132], itemStyle: { color: '#E6A23C' }, lineStyle: { width: 2 }, symbol: 'circle', symbolSize: 6 }, | |
| 650 | + { name: '项目数', type: 'line', yAxisIndex: 1, data: [256, 289, 268, 312, 278, 325, 356, 304, 284, 330, 289, 268, 312, 278, 325, 356, 304, 284, 330, 289, 268, 312, 278, 325, 356, 304, 284, 330, 289, 268], itemStyle: { color: '#909399' }, lineStyle: { width: 2 }, symbol: 'circle', symbolSize: 6 } | |
| 651 | + ] | |
| 652 | + } | |
| 653 | + this.dailyChart.setOption(option) | |
| 654 | + }, | |
| 655 | + renderCompareChart() { | |
| 656 | + if (!this.$refs.compareChart) return | |
| 657 | + this.compareChart = echarts.init(this.$refs.compareChart) | |
| 658 | + const option = { | |
| 659 | + tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } }, | |
| 660 | + legend: { data: ['开单业绩', '消耗业绩'], top: 10 }, | |
| 661 | + grid: { left: '3%', right: '4%', bottom: '3%', top: '15%', containLabel: true }, | |
| 662 | + xAxis: { type: 'category', data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'] }, | |
| 663 | + yAxis: { type: 'value', axisLabel: { formatter: value => value >= 10000 ? (value / 10000).toFixed(1) + '万' : value } }, | |
| 664 | + series: [ | |
| 665 | + { name: '开单业绩', type: 'bar', data: [856, 928, 1025, 1156, 1089, 1125, 1186, 1258, 1156, 1289, 1356, 1258], itemStyle: { color: '#409EFF' } }, | |
| 666 | + { name: '消耗业绩', type: 'bar', data: [658, 712, 786, 856, 798, 825, 868, 912, 856, 936, 986, 986], itemStyle: { color: '#67C23A' } } | |
| 667 | + ] | |
| 668 | + } | |
| 669 | + this.compareChart.setOption(option) | |
| 670 | + }, | |
| 671 | + renderStackedChart() { | |
| 672 | + if (!this.$refs.stackedChart) return | |
| 673 | + this.stackedChart = echarts.init(this.$refs.stackedChart) | |
| 674 | + const option = { | |
| 675 | + tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } }, | |
| 676 | + legend: { data: ['生美', '医美', '科美', '产品'], top: 10 }, | |
| 677 | + grid: { left: '3%', right: '4%', bottom: '3%', top: '15%', containLabel: true }, | |
| 678 | + xAxis: { type: 'category', data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'] }, | |
| 679 | + yAxis: { type: 'value', axisLabel: { formatter: value => value >= 10000 ? (value / 10000).toFixed(1) + '万' : value } }, | |
| 680 | + series: [ | |
| 681 | + { name: '生美', type: 'bar', stack: 'total', data: [320, 350, 380, 420, 390, 410, 450, 480, 420, 460, 500, 480], itemStyle: { color: '#A8D5E2' } }, | |
| 682 | + { name: '医美', type: 'bar', stack: 'total', data: [180, 200, 220, 250, 230, 240, 260, 280, 250, 270, 290, 280], itemStyle: { color: '#B8E6B8' } }, | |
| 683 | + { name: '科美', type: 'bar', stack: 'total', data: [140, 150, 160, 180, 170, 175, 190, 200, 180, 195, 210, 200], itemStyle: { color: '#FFD4A3' } }, | |
| 684 | + { name: '产品', type: 'bar', stack: 'total', data: [50, 55, 60, 70, 65, 68, 75, 80, 70, 78, 85, 80], itemStyle: { color: '#E6C1E6' } } | |
| 685 | + ] | |
| 686 | + } | |
| 687 | + this.stackedChart.setOption(option) | |
| 688 | + }, | |
| 689 | + renderFunnelChart() { | |
| 690 | + if (!this.$refs.funnelChart) return | |
| 691 | + this.funnelChart = echarts.init(this.$refs.funnelChart) | |
| 692 | + const option = { | |
| 693 | + tooltip: { trigger: 'item', formatter: '{a} <br/>{b}: {c} ({d}%)' }, | |
| 694 | + legend: { data: ['访问', '咨询', '到店', '体验', '开单', '复购'], top: 10 }, | |
| 695 | + series: [{ | |
| 696 | + name: '会员转化', | |
| 697 | + type: 'funnel', | |
| 698 | + left: '10%', | |
| 699 | + top: 60, | |
| 700 | + bottom: 60, | |
| 701 | + width: '80%', | |
| 702 | + min: 0, | |
| 703 | + max: 10000, | |
| 704 | + minSize: '0%', | |
| 705 | + maxSize: '100%', | |
| 706 | + sort: 'descending', | |
| 707 | + gap: 2, | |
| 708 | + label: { show: true, position: 'inside', formatter: '{b}: {c}' }, | |
| 709 | + labelLine: { length: 10, lineStyle: { width: 1, type: 'solid' } }, | |
| 710 | + itemStyle: { borderColor: '#fff', borderWidth: 1 }, | |
| 711 | + emphasis: { label: { fontSize: 20 } }, | |
| 712 | + data: [ | |
| 713 | + { value: 10000, name: '访问', itemStyle: { color: '#409EFF' } }, | |
| 714 | + { value: 8000, name: '咨询', itemStyle: { color: '#67C23A' } }, | |
| 715 | + { value: 6000, name: '到店', itemStyle: { color: '#E6A23C' } }, | |
| 716 | + { value: 4000, name: '体验', itemStyle: { color: '#F56C6C' } }, | |
| 717 | + { value: 2000, name: '开单', itemStyle: { color: '#909399' } }, | |
| 718 | + { value: 1500, name: '复购', itemStyle: { color: '#606266' } } | |
| 719 | + ] | |
| 720 | + }] | |
| 721 | + } | |
| 722 | + this.funnelChart.setOption(option) | |
| 723 | + }, | |
| 724 | + renderScatterChart() { | |
| 725 | + if (!this.$refs.scatterChart) return | |
| 726 | + this.scatterChart = echarts.init(this.$refs.scatterChart) | |
| 727 | + const option = { | |
| 728 | + tooltip: { trigger: 'item', formatter: '客单价: {c[0]}<br/>项目数: {c[1]}<br/>会员数: {c[2]}' }, | |
| 729 | + legend: { data: ['会员分布'], top: 10 }, | |
| 730 | + grid: { left: '3%', right: '7%', bottom: '3%', top: '15%', containLabel: true }, | |
| 731 | + xAxis: { type: 'value', name: '客单价(元)', nameLocation: 'middle', nameGap: 30 }, | |
| 732 | + yAxis: { type: 'value', name: '项目数', nameLocation: 'middle', nameGap: 50 }, | |
| 733 | + series: [{ | |
| 734 | + name: '会员分布', | |
| 735 | + type: 'scatter', | |
| 736 | + symbolSize: data => Math.sqrt(data[2]) * 2, | |
| 737 | + data: [ | |
| 738 | + [285, 4.5, 320], [320, 5.2, 280], [250, 3.8, 350], [380, 6.5, 180], [290, 4.8, 300], | |
| 739 | + [350, 5.8, 220], [280, 4.2, 310], [420, 7.2, 150], [310, 5.0, 260], [360, 6.0, 200], | |
| 740 | + [270, 4.0, 330], [390, 6.8, 170], [300, 4.9, 290], [370, 6.2, 210], [260, 3.9, 340] | |
| 741 | + ], | |
| 742 | + itemStyle: { color: '#409EFF', opacity: 0.6 } | |
| 743 | + }] | |
| 744 | + } | |
| 745 | + this.scatterChart.setOption(option) | |
| 746 | + }, | |
| 747 | + renderHeatmapChart() { | |
| 748 | + if (!this.$refs.heatmapChart) return | |
| 749 | + this.heatmapChart = echarts.init(this.$refs.heatmapChart) | |
| 750 | + const hours = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] | |
| 751 | + const times = ['09:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00', '16:00', '17:00', '18:00', '19:00', '20:00', '21:00'] | |
| 752 | + const data = [] | |
| 753 | + for (let i = 0; i < hours.length; i++) { | |
| 754 | + for (let j = 0; j < times.length; j++) { | |
| 755 | + const value = Math.floor(Math.random() * 100) | |
| 756 | + data.push([j, i, value]) | |
| 757 | + } | |
| 758 | + } | |
| 759 | + const option = { | |
| 760 | + tooltip: { position: 'top', formatter: params => `${hours[params.value[1]]} ${times[params.value[0]]}<br/>客流量: ${params.value[2]}` }, | |
| 761 | + grid: { height: '50%', top: '10%' }, | |
| 762 | + xAxis: { type: 'category', data: times, splitArea: { show: true }, position: 'top' }, | |
| 763 | + yAxis: { type: 'category', data: hours, splitArea: { show: true } }, | |
| 764 | + visualMap: { | |
| 765 | + min: 0, | |
| 766 | + max: 100, | |
| 767 | + calculable: true, | |
| 768 | + orient: 'horizontal', | |
| 769 | + left: 'center', | |
| 770 | + bottom: '5%', | |
| 771 | + inRange: { color: ['#e0f3ff', '#409EFF', '#1d4ed8'] } | |
| 772 | + }, | |
| 773 | + series: [{ | |
| 774 | + name: '客流量', | |
| 775 | + type: 'heatmap', | |
| 776 | + data: data, | |
| 777 | + label: { show: true }, | |
| 778 | + emphasis: { itemStyle: { shadowBlur: 10, shadowColor: 'rgba(0, 0, 0, 0.5)' } } | |
| 779 | + }] | |
| 780 | + } | |
| 781 | + this.heatmapChart.setOption(option) | |
| 782 | + }, | |
| 783 | + renderRadarChart() { | |
| 784 | + if (!this.$refs.radarChart) return | |
| 785 | + this.radarChart = echarts.init(this.$refs.radarChart) | |
| 786 | + const option = { | |
| 787 | + tooltip: {}, | |
| 788 | + radar: { | |
| 789 | + indicator: [ | |
| 790 | + { name: '业绩能力', max: 100 }, | |
| 791 | + { name: '服务能力', max: 100 }, | |
| 792 | + { name: '会员管理', max: 100 }, | |
| 793 | + { name: '运营效率', max: 100 }, | |
| 794 | + { name: '团队协作', max: 100 }, | |
| 795 | + { name: '客户满意度', max: 100 } | |
| 796 | + ], | |
| 797 | + center: ['50%', '55%'], | |
| 798 | + radius: '70%' | |
| 799 | + }, | |
| 800 | + series: [{ | |
| 801 | + name: '门店综合能力', | |
| 802 | + type: 'radar', | |
| 803 | + data: [{ | |
| 804 | + value: [85, 78, 82, 75, 80, 88], | |
| 805 | + name: '当前门店', | |
| 806 | + areaStyle: { color: 'rgba(64, 158, 255, 0.3)' }, | |
| 807 | + itemStyle: { color: '#409EFF' }, | |
| 808 | + lineStyle: { color: '#409EFF', width: 2 } | |
| 809 | + }, { | |
| 810 | + value: [75, 72, 70, 68, 75, 80], | |
| 811 | + name: '行业平均', | |
| 812 | + areaStyle: { color: 'rgba(103, 194, 58, 0.2)' }, | |
| 813 | + itemStyle: { color: '#67C23A' }, | |
| 814 | + lineStyle: { color: '#67C23A', width: 2, type: 'dashed' } | |
| 815 | + }] | |
| 816 | + }] | |
| 817 | + } | |
| 818 | + this.radarChart.setOption(option) | |
| 819 | + }, | |
| 820 | + renderGaugeChart() { | |
| 821 | + if (!this.$refs.gaugeChart) return | |
| 822 | + this.gaugeChart = echarts.init(this.$refs.gaugeChart) | |
| 823 | + const option = { | |
| 824 | + tooltip: { formatter: '{a} <br/>{b}: {c}%' }, | |
| 825 | + series: [{ | |
| 826 | + name: '目标完成度', | |
| 827 | + type: 'gauge', | |
| 828 | + progress: { show: true }, | |
| 829 | + detail: { valueAnimation: true, formatter: '{value}%', fontSize: 20, offsetCenter: [0, '70%'] }, | |
| 830 | + data: [{ value: 85.6, name: '完成率' }], | |
| 831 | + axisLine: { | |
| 832 | + lineStyle: { | |
| 833 | + width: 20, | |
| 834 | + color: [[0.3, '#67C23A'], [0.7, '#E6A23C'], [1, '#F56C6C']] | |
| 835 | + } | |
| 836 | + }, | |
| 837 | + axisTick: { show: false }, | |
| 838 | + splitLine: { show: false }, | |
| 839 | + axisLabel: { show: false }, | |
| 840 | + pointer: { show: false }, | |
| 841 | + title: { show: false } | |
| 842 | + }] | |
| 843 | + } | |
| 844 | + this.gaugeChart.setOption(option) | |
| 845 | + }, | |
| 846 | + renderStackedAreaChart() { | |
| 847 | + if (!this.$refs.stackedAreaChart) return | |
| 848 | + this.stackedAreaChart = echarts.init(this.$refs.stackedAreaChart) | |
| 849 | + const option = { | |
| 850 | + tooltip: { trigger: 'axis', axisPointer: { type: 'cross', label: { backgroundColor: '#6a7985' } } }, | |
| 851 | + legend: { data: ['生美', '医美', '科美', '产品'], top: 10 }, | |
| 852 | + grid: { left: '3%', right: '4%', bottom: '3%', top: '15%', containLabel: true }, | |
| 853 | + xAxis: [{ type: 'category', boundaryGap: false, data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'] }], | |
| 854 | + yAxis: [{ type: 'value', axisLabel: { formatter: '{value}%' } }], | |
| 855 | + series: [ | |
| 856 | + { name: '生美', type: 'line', stack: 'Total', areaStyle: {}, emphasis: { focus: 'series' }, data: [45, 48, 50, 52, 49, 51, 53, 55, 52, 54, 56, 54], itemStyle: { color: '#A8D5E2' } }, | |
| 857 | + { name: '医美', type: 'line', stack: 'Total', areaStyle: {}, emphasis: { focus: 'series' }, data: [25, 26, 27, 28, 27, 28, 29, 30, 28, 29, 30, 29], itemStyle: { color: '#B8E6B8' } }, | |
| 858 | + { name: '科美', type: 'line', stack: 'Total', areaStyle: {}, emphasis: { focus: 'series' }, data: [20, 21, 22, 23, 22, 22, 23, 24, 23, 23, 24, 23], itemStyle: { color: '#FFD4A3' } }, | |
| 859 | + { name: '产品', type: 'line', stack: 'Total', areaStyle: {}, emphasis: { focus: 'series' }, data: [10, 5, 1, -3, 2, -1, -5, -9, -3, -6, -10, -6], itemStyle: { color: '#E6C1E6' } } | |
| 860 | + ] | |
| 861 | + } | |
| 862 | + this.stackedAreaChart.setOption(option) | |
| 863 | + }, | |
| 864 | + handleResize() { | |
| 865 | + if (this.trendChart) this.trendChart.resize() | |
| 866 | + if (this.categoryChart) this.categoryChart.resize() | |
| 867 | + if (this.dailyChart) this.dailyChart.resize() | |
| 868 | + if (this.compareChart) this.compareChart.resize() | |
| 869 | + if (this.stackedChart) this.stackedChart.resize() | |
| 870 | + if (this.funnelChart) this.funnelChart.resize() | |
| 871 | + if (this.scatterChart) this.scatterChart.resize() | |
| 872 | + if (this.heatmapChart) this.heatmapChart.resize() | |
| 873 | + if (this.radarChart) this.radarChart.resize() | |
| 874 | + if (this.gaugeChart) this.gaugeChart.resize() | |
| 875 | + if (this.stackedAreaChart) this.stackedAreaChart.resize() | |
| 876 | + }, | |
| 877 | + formatMoney(value) { | |
| 878 | + if (value === null || value === undefined) return '0.00' | |
| 879 | + return Number(value).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) | |
| 880 | + }, | |
| 881 | + getRankingClass(rank, total) { | |
| 882 | + const percentage = rank / total | |
| 883 | + if (percentage <= 0.2) return 'excellent' | |
| 884 | + if (percentage <= 0.5) return 'good' | |
| 885 | + return 'normal' | |
| 886 | + }, | |
| 887 | + getRankingText(rank, total) { | |
| 888 | + const percentage = rank / total | |
| 889 | + if (percentage <= 0.2) return '优秀' | |
| 890 | + if (percentage <= 0.5) return '良好' | |
| 891 | + return '一般' | |
| 892 | + }, | |
| 893 | + getCategoryType(category) { | |
| 894 | + const typeMap = { | |
| 895 | + '生美': 'primary', | |
| 896 | + '医美': 'success', | |
| 897 | + '科美': 'warning', | |
| 898 | + '产品': 'info' | |
| 899 | + } | |
| 900 | + return typeMap[category] || '' | |
| 901 | + } | |
| 902 | + } | |
| 903 | +} | |
| 904 | +</script> | |
| 905 | + | |
| 906 | +<style lang="scss" scoped> | |
| 907 | +.store-dashboard { | |
| 908 | + padding: 20px; | |
| 909 | + background: #f5f7fa; | |
| 910 | + min-height: calc(100vh - 84px); | |
| 911 | + | |
| 912 | + // 顶部Header | |
| 913 | + .dashboard-header { | |
| 914 | + display: flex; | |
| 915 | + justify-content: space-between; | |
| 916 | + align-items: center; | |
| 917 | + padding: 20px 24px; | |
| 918 | + background: #fff; | |
| 919 | + border-radius: 12px; | |
| 920 | + margin-bottom: 20px; | |
| 921 | + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); | |
| 922 | + | |
| 923 | + .header-left { | |
| 924 | + .store-info { | |
| 925 | + display: flex; | |
| 926 | + align-items: center; | |
| 927 | + gap: 16px; | |
| 928 | + | |
| 929 | + .store-avatar { | |
| 930 | + width: 64px; | |
| 931 | + height: 64px; | |
| 932 | + border-radius: 12px; | |
| 933 | + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| 934 | + display: flex; | |
| 935 | + align-items: center; | |
| 936 | + justify-content: center; | |
| 937 | + color: #fff; | |
| 938 | + font-size: 28px; | |
| 939 | + } | |
| 940 | + | |
| 941 | + .store-details { | |
| 942 | + .store-name-row { | |
| 943 | + display: flex; | |
| 944 | + align-items: center; | |
| 945 | + gap: 12px; | |
| 946 | + margin-bottom: 8px; | |
| 947 | + | |
| 948 | + .store-name { | |
| 949 | + margin: 0; | |
| 950 | + font-size: 24px; | |
| 951 | + font-weight: 600; | |
| 952 | + color: #303133; | |
| 953 | + } | |
| 954 | + } | |
| 955 | + | |
| 956 | + .store-meta { | |
| 957 | + display: flex; | |
| 958 | + gap: 20px; | |
| 959 | + font-size: 14px; | |
| 960 | + color: #606266; | |
| 961 | + | |
| 962 | + .meta-item { | |
| 963 | + display: flex; | |
| 964 | + align-items: center; | |
| 965 | + gap: 6px; | |
| 966 | + | |
| 967 | + i { | |
| 968 | + color: #909399; | |
| 969 | + } | |
| 970 | + } | |
| 971 | + } | |
| 972 | + } | |
| 973 | + } | |
| 974 | + } | |
| 975 | + | |
| 976 | + .header-right { | |
| 977 | + .core-stats { | |
| 978 | + display: flex; | |
| 979 | + gap: 16px; | |
| 980 | + | |
| 981 | + .core-stat-item { | |
| 982 | + padding: 16px 20px; | |
| 983 | + border-radius: 10px; | |
| 984 | + min-width: 140px; | |
| 985 | + text-align: center; | |
| 986 | + transition: all 0.3s; | |
| 987 | + | |
| 988 | + &.primary { | |
| 989 | + background: linear-gradient(135deg, #ecf5ff 0%, #d9ecff 100%); | |
| 990 | + border-left: 4px solid #409EFF; | |
| 991 | + } | |
| 992 | + | |
| 993 | + &.success { | |
| 994 | + background: linear-gradient(135deg, #f0f9ff 0%, #e1f3ff 100%); | |
| 995 | + border-left: 4px solid #67C23A; | |
| 996 | + } | |
| 997 | + | |
| 998 | + &.info { | |
| 999 | + background: linear-gradient(135deg, #f4f4f5 0%, #e9e9eb 100%); | |
| 1000 | + border-left: 4px solid #909399; | |
| 1001 | + } | |
| 1002 | + | |
| 1003 | + &.warning { | |
| 1004 | + background: linear-gradient(135deg, #fdf6ec 0%, #fae6d3 100%); | |
| 1005 | + border-left: 4px solid #E6A23C; | |
| 1006 | + } | |
| 1007 | + | |
| 1008 | + .stat-label { | |
| 1009 | + font-size: 13px; | |
| 1010 | + color: #606266; | |
| 1011 | + margin-bottom: 8px; | |
| 1012 | + } | |
| 1013 | + | |
| 1014 | + .stat-value { | |
| 1015 | + font-size: 22px; | |
| 1016 | + font-weight: 700; | |
| 1017 | + color: #303133; | |
| 1018 | + margin-bottom: 6px; | |
| 1019 | + } | |
| 1020 | + | |
| 1021 | + .stat-trend { | |
| 1022 | + font-size: 12px; | |
| 1023 | + font-weight: 500; | |
| 1024 | + | |
| 1025 | + &.up { | |
| 1026 | + color: #67C23A; | |
| 1027 | + } | |
| 1028 | + | |
| 1029 | + i { | |
| 1030 | + font-size: 10px; | |
| 1031 | + } | |
| 1032 | + } | |
| 1033 | + } | |
| 1034 | + } | |
| 1035 | + } | |
| 1036 | + } | |
| 1037 | + | |
| 1038 | + // KPI指标卡片 | |
| 1039 | + .kpi-section { | |
| 1040 | + display: grid; | |
| 1041 | + grid-template-columns: repeat(6, 1fr); | |
| 1042 | + gap: 12px; | |
| 1043 | + margin-bottom: 20px; | |
| 1044 | + | |
| 1045 | + .kpi-card { | |
| 1046 | + display: flex; | |
| 1047 | + align-items: center; | |
| 1048 | + padding: 16px; | |
| 1049 | + background: #fff; | |
| 1050 | + border-radius: 10px; | |
| 1051 | + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); | |
| 1052 | + transition: all 0.3s; | |
| 1053 | + position: relative; | |
| 1054 | + overflow: hidden; | |
| 1055 | + | |
| 1056 | + &::before { | |
| 1057 | + content: ''; | |
| 1058 | + position: absolute; | |
| 1059 | + top: 0; | |
| 1060 | + left: 0; | |
| 1061 | + width: 4px; | |
| 1062 | + height: 100%; | |
| 1063 | + } | |
| 1064 | + | |
| 1065 | + &.primary::before { | |
| 1066 | + background: #409EFF; | |
| 1067 | + } | |
| 1068 | + | |
| 1069 | + &.success::before { | |
| 1070 | + background: #67C23A; | |
| 1071 | + } | |
| 1072 | + | |
| 1073 | + &.info::before { | |
| 1074 | + background: #909399; | |
| 1075 | + } | |
| 1076 | + | |
| 1077 | + &.warning::before { | |
| 1078 | + background: #E6A23C; | |
| 1079 | + } | |
| 1080 | + | |
| 1081 | + &:hover { | |
| 1082 | + transform: translateY(-2px); | |
| 1083 | + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); | |
| 1084 | + } | |
| 1085 | + | |
| 1086 | + .kpi-icon { | |
| 1087 | + width: 48px; | |
| 1088 | + height: 48px; | |
| 1089 | + border-radius: 10px; | |
| 1090 | + display: flex; | |
| 1091 | + align-items: center; | |
| 1092 | + justify-content: center; | |
| 1093 | + font-size: 22px; | |
| 1094 | + color: #fff; | |
| 1095 | + margin-right: 12px; | |
| 1096 | + flex-shrink: 0; | |
| 1097 | + } | |
| 1098 | + | |
| 1099 | + &.primary .kpi-icon { | |
| 1100 | + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| 1101 | + } | |
| 1102 | + | |
| 1103 | + &.success .kpi-icon { | |
| 1104 | + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); | |
| 1105 | + } | |
| 1106 | + | |
| 1107 | + &.info .kpi-icon { | |
| 1108 | + background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); | |
| 1109 | + } | |
| 1110 | + | |
| 1111 | + &.warning .kpi-icon { | |
| 1112 | + background: linear-gradient(135deg, #fa709a 0%, #fee140 100%); | |
| 1113 | + } | |
| 1114 | + | |
| 1115 | + .kpi-content { | |
| 1116 | + flex: 1; | |
| 1117 | + min-width: 0; | |
| 1118 | + | |
| 1119 | + .kpi-label { | |
| 1120 | + font-size: 13px; | |
| 1121 | + color: #909399; | |
| 1122 | + margin-bottom: 6px; | |
| 1123 | + } | |
| 1124 | + | |
| 1125 | + .kpi-value { | |
| 1126 | + font-size: 20px; | |
| 1127 | + font-weight: 600; | |
| 1128 | + color: #303133; | |
| 1129 | + | |
| 1130 | + .unit { | |
| 1131 | + font-size: 14px; | |
| 1132 | + font-weight: 500; | |
| 1133 | + } | |
| 1134 | + } | |
| 1135 | + } | |
| 1136 | + | |
| 1137 | + .kpi-trend { | |
| 1138 | + font-size: 12px; | |
| 1139 | + color: #67C23A; | |
| 1140 | + font-weight: 500; | |
| 1141 | + margin-left: 8px; | |
| 1142 | + white-space: nowrap; | |
| 1143 | + } | |
| 1144 | + } | |
| 1145 | + } | |
| 1146 | + | |
| 1147 | + // 主要内容区域:左右分栏 | |
| 1148 | + .main-content { | |
| 1149 | + display: grid; | |
| 1150 | + grid-template-columns: 1fr 400px; | |
| 1151 | + gap: 20px; | |
| 1152 | + margin-bottom: 20px; | |
| 1153 | + | |
| 1154 | + .content-left { | |
| 1155 | + display: flex; | |
| 1156 | + flex-direction: column; | |
| 1157 | + gap: 16px; | |
| 1158 | + } | |
| 1159 | + | |
| 1160 | + .content-right { | |
| 1161 | + display: flex; | |
| 1162 | + flex-direction: column; | |
| 1163 | + gap: 16px; | |
| 1164 | + } | |
| 1165 | + | |
| 1166 | + .chart-row { | |
| 1167 | + margin-bottom: 0; | |
| 1168 | + } | |
| 1169 | + } | |
| 1170 | + | |
| 1171 | + // 图表卡片 | |
| 1172 | + .chart-card { | |
| 1173 | + border-radius: 12px; | |
| 1174 | + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); | |
| 1175 | + | |
| 1176 | + ::v-deep .el-card__header { | |
| 1177 | + padding: 16px 20px; | |
| 1178 | + border-bottom: 1px solid #ebeef5; | |
| 1179 | + } | |
| 1180 | + | |
| 1181 | + ::v-deep .el-card__body { | |
| 1182 | + padding: 20px; | |
| 1183 | + } | |
| 1184 | + | |
| 1185 | + .chart-container { | |
| 1186 | + width: 100%; | |
| 1187 | + height: 360px; | |
| 1188 | + min-height: 360px; | |
| 1189 | + } | |
| 1190 | + } | |
| 1191 | + | |
| 1192 | + .chart-card-small { | |
| 1193 | + border-radius: 12px; | |
| 1194 | + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); | |
| 1195 | + | |
| 1196 | + ::v-deep .el-card__header { | |
| 1197 | + padding: 14px 18px; | |
| 1198 | + border-bottom: 1px solid #ebeef5; | |
| 1199 | + } | |
| 1200 | + | |
| 1201 | + ::v-deep .el-card__body { | |
| 1202 | + padding: 16px; | |
| 1203 | + } | |
| 1204 | + | |
| 1205 | + .chart-container-small { | |
| 1206 | + width: 100%; | |
| 1207 | + height: 260px; | |
| 1208 | + min-height: 260px; | |
| 1209 | + } | |
| 1210 | + } | |
| 1211 | + | |
| 1212 | + // 指标卡片 | |
| 1213 | + .metrics-card { | |
| 1214 | + border-radius: 12px; | |
| 1215 | + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); | |
| 1216 | + | |
| 1217 | + ::v-deep .el-card__header { | |
| 1218 | + padding: 14px 18px; | |
| 1219 | + border-bottom: 1px solid #ebeef5; | |
| 1220 | + } | |
| 1221 | + | |
| 1222 | + ::v-deep .el-card__body { | |
| 1223 | + padding: 16px; | |
| 1224 | + } | |
| 1225 | + | |
| 1226 | + .metrics-grid { | |
| 1227 | + display: grid; | |
| 1228 | + grid-template-columns: repeat(2, 1fr); | |
| 1229 | + gap: 10px; | |
| 1230 | + | |
| 1231 | + .metric-item { | |
| 1232 | + display: flex; | |
| 1233 | + align-items: center; | |
| 1234 | + padding: 12px; | |
| 1235 | + background: #f8f9fa; | |
| 1236 | + border-radius: 8px; | |
| 1237 | + transition: all 0.3s; | |
| 1238 | + | |
| 1239 | + &:hover { | |
| 1240 | + background: #f0f2f5; | |
| 1241 | + transform: translateY(-1px); | |
| 1242 | + } | |
| 1243 | + | |
| 1244 | + .metric-icon { | |
| 1245 | + width: 40px; | |
| 1246 | + height: 40px; | |
| 1247 | + border-radius: 8px; | |
| 1248 | + color: #fff; | |
| 1249 | + display: flex; | |
| 1250 | + align-items: center; | |
| 1251 | + justify-content: center; | |
| 1252 | + font-size: 18px; | |
| 1253 | + margin-right: 10px; | |
| 1254 | + flex-shrink: 0; | |
| 1255 | + } | |
| 1256 | + | |
| 1257 | + .metric-info { | |
| 1258 | + flex: 1; | |
| 1259 | + min-width: 0; | |
| 1260 | + | |
| 1261 | + .metric-label { | |
| 1262 | + font-size: 12px; | |
| 1263 | + color: #909399; | |
| 1264 | + margin-bottom: 4px; | |
| 1265 | + white-space: nowrap; | |
| 1266 | + overflow: hidden; | |
| 1267 | + text-overflow: ellipsis; | |
| 1268 | + } | |
| 1269 | + | |
| 1270 | + .metric-value { | |
| 1271 | + font-size: 16px; | |
| 1272 | + font-weight: 600; | |
| 1273 | + color: #303133; | |
| 1274 | + white-space: nowrap; | |
| 1275 | + overflow: hidden; | |
| 1276 | + text-overflow: ellipsis; | |
| 1277 | + } | |
| 1278 | + | |
| 1279 | + .metric-rate { | |
| 1280 | + font-size: 11px; | |
| 1281 | + color: #909399; | |
| 1282 | + margin-top: 2px; | |
| 1283 | + } | |
| 1284 | + } | |
| 1285 | + } | |
| 1286 | + } | |
| 1287 | + } | |
| 1288 | + | |
| 1289 | + // 卡片标题 | |
| 1290 | + .card-header { | |
| 1291 | + display: flex; | |
| 1292 | + align-items: center; | |
| 1293 | + font-size: 15px; | |
| 1294 | + font-weight: 600; | |
| 1295 | + color: #303133; | |
| 1296 | + | |
| 1297 | + i { | |
| 1298 | + margin-right: 8px; | |
| 1299 | + color: #409EFF; | |
| 1300 | + font-size: 16px; | |
| 1301 | + } | |
| 1302 | + } | |
| 1303 | + | |
| 1304 | + // 排名对比卡片 | |
| 1305 | + .ranking-content { | |
| 1306 | + padding: 8px 0; | |
| 1307 | + | |
| 1308 | + .ranking-item { | |
| 1309 | + text-align: center; | |
| 1310 | + padding: 16px 0; | |
| 1311 | + | |
| 1312 | + .ranking-label { | |
| 1313 | + font-size: 13px; | |
| 1314 | + color: #909399; | |
| 1315 | + margin-bottom: 12px; | |
| 1316 | + } | |
| 1317 | + | |
| 1318 | + .ranking-value { | |
| 1319 | + margin-bottom: 12px; | |
| 1320 | + | |
| 1321 | + .rank-number { | |
| 1322 | + font-size: 36px; | |
| 1323 | + font-weight: 700; | |
| 1324 | + color: #409EFF; | |
| 1325 | + } | |
| 1326 | + | |
| 1327 | + .rank-total { | |
| 1328 | + font-size: 18px; | |
| 1329 | + color: #909399; | |
| 1330 | + margin-left: 4px; | |
| 1331 | + } | |
| 1332 | + } | |
| 1333 | + | |
| 1334 | + .ranking-badge { | |
| 1335 | + display: inline-block; | |
| 1336 | + padding: 4px 16px; | |
| 1337 | + border-radius: 12px; | |
| 1338 | + font-size: 13px; | |
| 1339 | + font-weight: 600; | |
| 1340 | + | |
| 1341 | + &.excellent { | |
| 1342 | + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); | |
| 1343 | + color: #fff; | |
| 1344 | + } | |
| 1345 | + | |
| 1346 | + &.good { | |
| 1347 | + background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); | |
| 1348 | + color: #fff; | |
| 1349 | + } | |
| 1350 | + | |
| 1351 | + &.normal { | |
| 1352 | + background: #f4f4f5; | |
| 1353 | + color: #909399; | |
| 1354 | + } | |
| 1355 | + } | |
| 1356 | + } | |
| 1357 | + | |
| 1358 | + .comparison-stats { | |
| 1359 | + .stat-row { | |
| 1360 | + display: flex; | |
| 1361 | + justify-content: space-between; | |
| 1362 | + align-items: center; | |
| 1363 | + padding: 10px 0; | |
| 1364 | + font-size: 13px; | |
| 1365 | + | |
| 1366 | + &:not(:last-child) { | |
| 1367 | + border-bottom: 1px dashed #ebeef5; | |
| 1368 | + } | |
| 1369 | + | |
| 1370 | + .stat-label { | |
| 1371 | + color: #606266; | |
| 1372 | + } | |
| 1373 | + | |
| 1374 | + .stat-value { | |
| 1375 | + font-weight: 600; | |
| 1376 | + color: #303133; | |
| 1377 | + } | |
| 1378 | + } | |
| 1379 | + } | |
| 1380 | + } | |
| 1381 | + | |
| 1382 | + // 经营提示卡片 | |
| 1383 | + .tips-card { | |
| 1384 | + border-radius: 12px; | |
| 1385 | + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); | |
| 1386 | + | |
| 1387 | + ::v-deep .el-card__header { | |
| 1388 | + padding: 14px 18px; | |
| 1389 | + border-bottom: 1px solid #ebeef5; | |
| 1390 | + } | |
| 1391 | + | |
| 1392 | + ::v-deep .el-card__body { | |
| 1393 | + padding: 16px; | |
| 1394 | + } | |
| 1395 | + | |
| 1396 | + .tips-content { | |
| 1397 | + .tip-item { | |
| 1398 | + display: flex; | |
| 1399 | + align-items: flex-start; | |
| 1400 | + padding: 12px; | |
| 1401 | + margin-bottom: 8px; | |
| 1402 | + border-radius: 8px; | |
| 1403 | + font-size: 13px; | |
| 1404 | + line-height: 1.6; | |
| 1405 | + transition: all 0.3s; | |
| 1406 | + | |
| 1407 | + &:last-child { | |
| 1408 | + margin-bottom: 0; | |
| 1409 | + } | |
| 1410 | + | |
| 1411 | + i { | |
| 1412 | + margin-right: 8px; | |
| 1413 | + margin-top: 2px; | |
| 1414 | + font-size: 14px; | |
| 1415 | + flex-shrink: 0; | |
| 1416 | + } | |
| 1417 | + | |
| 1418 | + span { | |
| 1419 | + flex: 1; | |
| 1420 | + } | |
| 1421 | + | |
| 1422 | + &.success { | |
| 1423 | + background: #f0f9ff; | |
| 1424 | + color: #67C23A; | |
| 1425 | + | |
| 1426 | + i { | |
| 1427 | + color: #67C23A; | |
| 1428 | + } | |
| 1429 | + } | |
| 1430 | + | |
| 1431 | + &.warning { | |
| 1432 | + background: #fdf6ec; | |
| 1433 | + color: #E6A23C; | |
| 1434 | + | |
| 1435 | + i { | |
| 1436 | + color: #E6A23C; | |
| 1437 | + } | |
| 1438 | + } | |
| 1439 | + | |
| 1440 | + &.info { | |
| 1441 | + background: #f4f4f5; | |
| 1442 | + color: #909399; | |
| 1443 | + | |
| 1444 | + i { | |
| 1445 | + color: #909399; | |
| 1446 | + } | |
| 1447 | + } | |
| 1448 | + | |
| 1449 | + &:hover { | |
| 1450 | + transform: translateX(4px); | |
| 1451 | + } | |
| 1452 | + } | |
| 1453 | + } | |
| 1454 | + } | |
| 1455 | + | |
| 1456 | + // 快速数据洞察卡片 | |
| 1457 | + .insight-card { | |
| 1458 | + border-radius: 12px; | |
| 1459 | + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); | |
| 1460 | + | |
| 1461 | + ::v-deep .el-card__header { | |
| 1462 | + padding: 14px 18px; | |
| 1463 | + border-bottom: 1px solid #ebeef5; | |
| 1464 | + } | |
| 1465 | + | |
| 1466 | + ::v-deep .el-card__body { | |
| 1467 | + padding: 16px; | |
| 1468 | + } | |
| 1469 | + | |
| 1470 | + .insight-content { | |
| 1471 | + .insight-item { | |
| 1472 | + padding: 14px; | |
| 1473 | + margin-bottom: 12px; | |
| 1474 | + background: #f8f9fa; | |
| 1475 | + border-radius: 8px; | |
| 1476 | + border-left: 3px solid #409EFF; | |
| 1477 | + transition: all 0.3s; | |
| 1478 | + | |
| 1479 | + &:last-child { | |
| 1480 | + margin-bottom: 0; | |
| 1481 | + } | |
| 1482 | + | |
| 1483 | + &:hover { | |
| 1484 | + background: #f0f2f5; | |
| 1485 | + transform: translateX(4px); | |
| 1486 | + } | |
| 1487 | + | |
| 1488 | + .insight-header { | |
| 1489 | + display: flex; | |
| 1490 | + justify-content: space-between; | |
| 1491 | + align-items: center; | |
| 1492 | + margin-bottom: 8px; | |
| 1493 | + | |
| 1494 | + .insight-title { | |
| 1495 | + font-size: 13px; | |
| 1496 | + font-weight: 600; | |
| 1497 | + color: #303133; | |
| 1498 | + } | |
| 1499 | + } | |
| 1500 | + | |
| 1501 | + .insight-value { | |
| 1502 | + font-size: 20px; | |
| 1503 | + font-weight: 700; | |
| 1504 | + color: #409EFF; | |
| 1505 | + margin-bottom: 6px; | |
| 1506 | + } | |
| 1507 | + | |
| 1508 | + .insight-desc { | |
| 1509 | + font-size: 12px; | |
| 1510 | + color: #909399; | |
| 1511 | + line-height: 1.5; | |
| 1512 | + } | |
| 1513 | + } | |
| 1514 | + } | |
| 1515 | + } | |
| 1516 | + | |
| 1517 | + // 关键指标卡片 | |
| 1518 | + .key-metrics-card { | |
| 1519 | + border-radius: 12px; | |
| 1520 | + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); | |
| 1521 | + | |
| 1522 | + ::v-deep .el-card__header { | |
| 1523 | + padding: 14px 18px; | |
| 1524 | + border-bottom: 1px solid #ebeef5; | |
| 1525 | + } | |
| 1526 | + | |
| 1527 | + ::v-deep .el-card__body { | |
| 1528 | + padding: 16px; | |
| 1529 | + } | |
| 1530 | + | |
| 1531 | + .key-metrics-content { | |
| 1532 | + .progress-item { | |
| 1533 | + margin-bottom: 20px; | |
| 1534 | + | |
| 1535 | + &:last-child { | |
| 1536 | + margin-bottom: 0; | |
| 1537 | + } | |
| 1538 | + | |
| 1539 | + .progress-header { | |
| 1540 | + display: flex; | |
| 1541 | + justify-content: space-between; | |
| 1542 | + align-items: center; | |
| 1543 | + margin-bottom: 8px; | |
| 1544 | + | |
| 1545 | + .progress-label { | |
| 1546 | + font-size: 13px; | |
| 1547 | + color: #606266; | |
| 1548 | + font-weight: 500; | |
| 1549 | + } | |
| 1550 | + | |
| 1551 | + .progress-value { | |
| 1552 | + font-size: 16px; | |
| 1553 | + font-weight: 700; | |
| 1554 | + color: #303133; | |
| 1555 | + } | |
| 1556 | + } | |
| 1557 | + } | |
| 1558 | + } | |
| 1559 | + } | |
| 1560 | + | |
| 1561 | + // 表格区域 | |
| 1562 | + .table-section { | |
| 1563 | + .table-card { | |
| 1564 | + border-radius: 12px; | |
| 1565 | + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); | |
| 1566 | + | |
| 1567 | + ::v-deep .el-card__header { | |
| 1568 | + padding: 16px 20px; | |
| 1569 | + border-bottom: 1px solid #ebeef5; | |
| 1570 | + } | |
| 1571 | + | |
| 1572 | + ::v-deep .el-card__body { | |
| 1573 | + padding: 20px; | |
| 1574 | + } | |
| 1575 | + } | |
| 1576 | + } | |
| 1577 | +} | |
| 1578 | +</style> | ... | ... |
sql/门店驾驶舱菜单配置.sql
0 → 100644
| 1 | +-- 门店驾驶舱菜单配置脚本 | |
| 2 | + | |
| 3 | +SET @AdminRoleId = '94e3a9bb0fce4547886972998fddba1c'; -- 系统管理员角色ID | |
| 4 | + | |
| 5 | +-- 1. 清理旧数据 (防止重复执行报错) | |
| 6 | +DELETE FROM BASE_MODULE WHERE F_Id = 'store-dashboard'; | |
| 7 | +DELETE FROM BASE_AUTHORIZE WHERE F_ItemId = 'store-dashboard'; | |
| 8 | + | |
| 9 | +-- 2. 创建菜单: 门店驾驶舱 (父级: 报表中心 725873504657868037) | |
| 10 | +INSERT INTO BASE_MODULE (F_Id, F_ParentId, F_Type, F_FullName, F_EnCode, F_UrlAddress, F_Icon, F_SortCode, F_EnabledMark, F_Category, F_DeleteMark, F_LinkTarget, F_PropertyJson, F_IsButtonAuthorize, F_IsColumnAuthorize, F_IsDataAuthorize, F_IsFormAuthorize, F_CreatorTime) | |
| 11 | +VALUES | |
| 12 | +('store-dashboard', '725873504657868037', 2, '门店驾驶舱', 'storeDashboard', 'extend/storeDashboard', 'icon-ym icon-ym-office-building', 11, 1, 'Web', NULL, '_self', '{"moduleId":"","iconBackgroundColor":"","isTree":0}', 1, 1, 1, 1, NOW()); | |
| 13 | + | |
| 14 | +-- 3. 授权给 系统管理员 角色 | |
| 15 | +INSERT INTO BASE_AUTHORIZE (F_Id, F_ItemType, F_ItemId, F_ObjectType, F_ObjectId, F_SortCode, F_CreatorTime, F_CreatorUserId) | |
| 16 | +VALUES | |
| 17 | +(REPLACE(UUID(), '-', ''), 'module', 'store-dashboard', 'Role', @AdminRoleId, 1, NOW(), 'admin'); | |
| 18 | + | ... | ... |