Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f9997828d | |||
| 4165a9a78f | |||
| 557deaaa84 | |||
| 7feef8e17a | |||
| 95a7deac4c | |||
| 14223c7543 | |||
| 081cbdeaed | |||
| 082ad32437 | |||
| 7624633183 | |||
| 2bb619ddf7 | |||
| 4917f2e01e |
@@ -0,0 +1,72 @@
|
||||
# sales_data_dashboard 变更日志
|
||||
|
||||
> 记录销售数据仪表盘项目的所有重要变更。
|
||||
|
||||
---
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- 项目接入协作框架
|
||||
- 创建 `ai/contracts.yaml` 定义服务契约
|
||||
- 定义数据接口格式(年度数据、排名数据、季度进度)
|
||||
- 定义技术栈(React 18 + Vite 5 / Vue 3 + Vite 5 / 原生 HTML)
|
||||
|
||||
---
|
||||
|
||||
## [1.0.0] - 2026-02-12
|
||||
|
||||
### Added
|
||||
- 初始化销售数据仪表盘项目
|
||||
- 实现三个版本:原生 HTML、React、Vue
|
||||
- 实现年度销售目标和实际销售额展示
|
||||
- 实现销售完成率进度条
|
||||
- 实现年度季度进度显示(Q1-Q4)
|
||||
- 实现小组销售排名(列表 + 柱状图)
|
||||
- 实现个人销售排名(列表 + 柱状图)
|
||||
- 支持响应式设计(PC 端等比例缩放,移动端自适应)
|
||||
- 集成 ECharts 5.4 进行数据可视化
|
||||
|
||||
### Features (React 版本)
|
||||
- React Hooks (useState, useEffect, useRef)
|
||||
- 组件化开发(TopData、GroupRanking、PersonRanking)
|
||||
- 防抖处理窗口 resize
|
||||
- 图表自动销毁重建
|
||||
|
||||
### Features (Vue 版本)
|
||||
- Vue 3 Composition API
|
||||
- Setup script 语法
|
||||
- 响应式数据绑定
|
||||
- 生命周期钩子(onMounted、onUnmounted)
|
||||
|
||||
### Features (原生 HTML 版本)
|
||||
- 原生 JavaScript 实现
|
||||
- DOM 操作
|
||||
- Fetch API 数据获取
|
||||
|
||||
### Technical Details
|
||||
- **技术栈**:React 18 / Vue 3 / Vite 5 / ECharts 5.4
|
||||
- **开发端口**:React (3001)、Vue (3002)
|
||||
- **构建工具**:Vite 5
|
||||
- **样式**:原生 CSS3
|
||||
- **浏览器支持**:Chrome、Firefox、Safari、Edge
|
||||
|
||||
---
|
||||
|
||||
## 变更类型说明
|
||||
|
||||
- **Added**: 新增功能
|
||||
- **Changed**: 功能变更
|
||||
- **Deprecated**: 即将废弃的功能
|
||||
- **Removed**: 已删除的功能
|
||||
- **Fixed**: 问题修复
|
||||
- **Security**: 安全性改进
|
||||
|
||||
---
|
||||
|
||||
## 发布流程
|
||||
|
||||
1. 更新 CHANGELOG_AI.md
|
||||
2. 更新版本号(package.json)
|
||||
3. 创建 Git tag
|
||||
4. 推送到远程仓库
|
||||
@@ -0,0 +1,189 @@
|
||||
# sales_data_dashboard 服务契约
|
||||
# 销售数据统计仪表盘 - 数据可视化大屏
|
||||
|
||||
# API 规范(遵循中控服务定义的规范)
|
||||
api_spec: ../../_coordination/API_SPEC.md
|
||||
|
||||
# 服务描述
|
||||
description: >
|
||||
销售数据统计仪表盘,用于展示年度销售目标完成情况、小组排名和个人排名。
|
||||
提供三个版本实现:原生 HTML、React 18 + Vite 5、Vue 3 + Vite 5。
|
||||
支持响应式设计,适配 PC 端和移动端,使用 ECharts 5 进行数据可视化。
|
||||
|
||||
# 职责
|
||||
# - 展示年度销售目标和实际销售额对比
|
||||
# - 展示销售完成率进度条
|
||||
# - 展示年度季度进度(Q1-Q4)
|
||||
# - 展示小组销售排名(列表 + 柱状图)
|
||||
# - 展示个人销售排名(列表 + 柱状图)
|
||||
# - 支持数据动态加载和实时更新
|
||||
|
||||
# 自己拥有的(owns)
|
||||
owns:
|
||||
# API 接口(无后端 API,纯前端展示)
|
||||
apis: []
|
||||
|
||||
# 数据存储(无数据库,使用 API 获取数据)
|
||||
tables: []
|
||||
|
||||
# 领域事件(无事件发布)
|
||||
events: []
|
||||
|
||||
# 枚举类型
|
||||
enums:
|
||||
- name: "RankingType"
|
||||
description: "排名类型"
|
||||
values: ["group", "person"]
|
||||
module: "ranking"
|
||||
|
||||
- name: "ChartType"
|
||||
description: "图表类型"
|
||||
values: ["bar", "line", "pie"]
|
||||
module: "chart"
|
||||
|
||||
# 业务流程
|
||||
process:
|
||||
- name: "数据加载流程"
|
||||
description: "从后端 API 获取销售数据(年度目标、实际销售额、排名数据)"
|
||||
module: "data"
|
||||
|
||||
- name: "数据可视化流程"
|
||||
description: "使用 ECharts 渲染图表(柱状图、折线图、饼图)"
|
||||
module: "chart"
|
||||
|
||||
- name: "响应式适配流程"
|
||||
description: "根据屏幕尺寸自动调整布局和图表大小"
|
||||
module: "ui"
|
||||
|
||||
# 依赖的外部服务(depends_on)
|
||||
depends_on:
|
||||
# 后端 API(需要对接)
|
||||
apis:
|
||||
- name: "GET /api/sales/year-data"
|
||||
description: "获取年度销售数据(目标、实际)"
|
||||
provider: "backend"
|
||||
critical: true
|
||||
|
||||
- name: "GET /api/sales/group-ranking"
|
||||
description: "获取小组销售排名"
|
||||
provider: "backend"
|
||||
critical: true
|
||||
|
||||
- name: "GET /api/sales/person-ranking"
|
||||
description: "获取个人销售排名"
|
||||
provider: "backend"
|
||||
critical: true
|
||||
|
||||
- name: "GET /api/sales/quarter-progress"
|
||||
description: "获取季度进度数据"
|
||||
provider: "backend"
|
||||
critical: false
|
||||
|
||||
# 外部事件
|
||||
events: []
|
||||
|
||||
# 技术栈(React 版本)
|
||||
tech_stack:
|
||||
type: "web-app"
|
||||
framework: "Vite 5"
|
||||
language: "React 18 + JavaScript"
|
||||
ui_library: "无(原生 CSS)"
|
||||
styling: "CSS3"
|
||||
build_tool: "Vite 5"
|
||||
state_management: "React Hooks"
|
||||
deployment: "Static Site"
|
||||
api_client: "Fetch API"
|
||||
chart_library: "ECharts 5.4"
|
||||
|
||||
# 技术栈(Vue 版本)
|
||||
tech_stack_vue:
|
||||
type: "web-app"
|
||||
framework: "Vite 5"
|
||||
language: "Vue 3 + JavaScript"
|
||||
ui_library: "无(原生 CSS)"
|
||||
styling: "CSS3(scoped)"
|
||||
build_tool: "Vite 5"
|
||||
state_management: "Vue 3 Composition API"
|
||||
deployment: "Static Site"
|
||||
api_client: "Fetch API"
|
||||
chart_library: "ECharts 5.4"
|
||||
|
||||
# 技术栈(原生 HTML 版本)
|
||||
tech_stack_vanilla:
|
||||
type: "web-app"
|
||||
framework: "无(原生 HTML/CSS/JS)"
|
||||
language: "JavaScript ES6+"
|
||||
ui_library: "无"
|
||||
styling: "CSS3"
|
||||
build_tool: "无"
|
||||
state_management: "无(直接 DOM 操作)"
|
||||
deployment: "Static Site"
|
||||
api_client: "Fetch API"
|
||||
chart_library: "ECharts 5.4"
|
||||
|
||||
# 部署配置
|
||||
deployment:
|
||||
type: "Static Site"
|
||||
react_app_command: "cd react-app && npm run dev"
|
||||
vue_app_command: "cd vue-app && npm run dev"
|
||||
vanilla_command: "使用静态服务器(如 nginx)"
|
||||
build_command: "cd react-app && npm run build"
|
||||
output_dir: "react-app/dist"
|
||||
dev_port_react: 3001
|
||||
dev_port_vue: 3002
|
||||
|
||||
# 环境变量
|
||||
env:
|
||||
- name: "VITE_API_BASE_URL"
|
||||
description: "后端 API 地址"
|
||||
default: "http://localhost:8000/api"
|
||||
required: true
|
||||
|
||||
# 数据接口格式(待对接)
|
||||
data_formats:
|
||||
# 年度数据格式
|
||||
year_data:
|
||||
target:
|
||||
type: "number"
|
||||
description: "年度销售目标"
|
||||
example: 3500
|
||||
actual:
|
||||
type: "number"
|
||||
description: "年度实际销售额"
|
||||
example: 2750
|
||||
|
||||
# 排名数据格式
|
||||
ranking_data:
|
||||
type: "array"
|
||||
items:
|
||||
name:
|
||||
type: "string"
|
||||
description: "名称(小组名或个人姓名)"
|
||||
target:
|
||||
type: "number"
|
||||
description: "销售目标"
|
||||
actual:
|
||||
type: "number"
|
||||
description: "实际销售额"
|
||||
example:
|
||||
- name: "华东一组"
|
||||
target: 500
|
||||
actual: 450
|
||||
- name: "华东二组"
|
||||
target: 500
|
||||
actual: 380
|
||||
|
||||
# 季度进度数据格式
|
||||
quarter_progress:
|
||||
type: "object"
|
||||
description: "季度进度信息(可选,前端也可自动计算)"
|
||||
properties:
|
||||
current_quarter:
|
||||
type: "number"
|
||||
description: "当前季度(1-4)"
|
||||
current_week:
|
||||
type: "number"
|
||||
description: "当前季度第几周"
|
||||
total_weeks:
|
||||
type: "number"
|
||||
description: "当前季度总周数"
|
||||
+335
-31
@@ -21,49 +21,33 @@ body {
|
||||
color: #fff;
|
||||
min-width: 320px;
|
||||
overflow-x: hidden;
|
||||
/* PC端缩放相关样式 */
|
||||
transform-origin: left top;
|
||||
transition: transform 0.1s ease-out;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
background: #000;
|
||||
/* 固定最小宽度,避免缩放时内容挤压 */
|
||||
min-width: 1200px;
|
||||
}
|
||||
|
||||
.cont{
|
||||
width: 90%;
|
||||
max-width: 1400px;
|
||||
max-width: 1600px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
/* 确保内容区域最小宽度 */
|
||||
min-width: 1080px;
|
||||
/* padding: clamp(0.5rem, 1.5vw, 2rem); */
|
||||
}
|
||||
|
||||
/* 移动端下调整宽度 */
|
||||
/* 移动端优化 (768px及以下) */
|
||||
@media screen and (max-width: 768px) {
|
||||
body {
|
||||
transform: none !important;
|
||||
width: 100% !important;
|
||||
min-width: 320px !important;
|
||||
height: auto !important;
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
.container {
|
||||
min-width: 320px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cont {
|
||||
width: 95%;
|
||||
max-width: 100%;
|
||||
min-width: auto;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +78,7 @@ body {
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: clamp(6px, 1.5vw, 14px);
|
||||
margin-bottom: clamp(10px, 1.5vw, 20px);
|
||||
min-height: 0; /* 允许grid子项收缩 */
|
||||
}
|
||||
|
||||
.sales-info {
|
||||
@@ -268,6 +253,7 @@ body {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden; /* 防止溢出 */
|
||||
}
|
||||
|
||||
.countdown h3 {
|
||||
@@ -275,6 +261,7 @@ body {
|
||||
color: #e7552f;
|
||||
margin-bottom: clamp(10px, 1.5vw, 15px);
|
||||
font-weight: bold;
|
||||
white-space: nowrap; /* 标题不换行 */
|
||||
}
|
||||
|
||||
/* 季度容器 */
|
||||
@@ -294,6 +281,7 @@ body {
|
||||
min-width: 0;
|
||||
border-bottom: 1px solid #523A14;
|
||||
padding-bottom: 8px;
|
||||
overflow: hidden; /* PC端防止内容溢出 */
|
||||
}
|
||||
.last-quarter{
|
||||
border-bottom: none;
|
||||
@@ -316,26 +304,34 @@ body {
|
||||
/* 周数圆圈容器 */
|
||||
.weeks-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: clamp(3px, 0.5vw, 5px);
|
||||
flex-wrap: nowrap; /* PC端不换行 */
|
||||
gap: clamp(2px, 0.4vw, 5px);
|
||||
flex: 1;
|
||||
overflow-x: hidden; /* 隐藏溢出内容 */
|
||||
overflow-y: hidden;
|
||||
min-width: 0; /* 允许flex子项缩小 */
|
||||
justify-content: flex-start; /* 从左侧开始排列 */
|
||||
}
|
||||
|
||||
/* 单个周数圆圈 */
|
||||
.week-circle {
|
||||
width: clamp(18px, 2.5vw, 24px);
|
||||
height: clamp(18px, 2.5vw, 24px);
|
||||
width: clamp(12px, 2.5vw, 24px);
|
||||
height: clamp(12px, 2.5vw, 24px);
|
||||
border-radius: 50%;
|
||||
border: 2px solid #815B37;
|
||||
background: rgba(129, 91, 55, 0.2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: clamp(0.625rem, 1vw, 0.75rem);
|
||||
font-size: clamp(0.5rem, 1vw, 0.75rem);
|
||||
color: #f5bbab;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
flex-shrink: 1; /* PC端允许周圈缩小以适应容器 */
|
||||
min-width: 10px; /* 最小尺寸 */
|
||||
min-height: 10px;
|
||||
flex-basis: clamp(12px, 2.5vw, 24px); /* 弹性基础尺寸 */
|
||||
}
|
||||
|
||||
.week-circle:hover {
|
||||
@@ -544,6 +540,21 @@ body {
|
||||
font-size: clamp(0.875rem, 2.5vw, 1.125rem);
|
||||
}
|
||||
|
||||
/* 移动端年度进度允许换行 */
|
||||
.countdown {
|
||||
overflow: visible; /* 移动端允许溢出换行 */
|
||||
}
|
||||
|
||||
.quarter-item {
|
||||
overflow: visible; /* 移动端允许内容换行 */
|
||||
}
|
||||
|
||||
.weeks-container {
|
||||
flex-wrap: wrap; /* 移动端允许换行 */
|
||||
overflow-x: visible;
|
||||
overflow-y: visible;
|
||||
}
|
||||
|
||||
.quarter-label {
|
||||
font-size: clamp(0.75rem, 2vw, 0.875rem);
|
||||
padding: clamp(2px, 0.5vw, 4px) clamp(6px, 1.5vw, 10px);
|
||||
@@ -554,6 +565,10 @@ body {
|
||||
width: clamp(16px, 4vw, 20px);
|
||||
height: clamp(16px, 4vw, 20px);
|
||||
font-size: clamp(0.5rem, 1.5vw, 0.625rem);
|
||||
flex-shrink: 0; /* 移动端不缩小 */
|
||||
min-width: auto;
|
||||
min-height: auto;
|
||||
flex-basis: auto;
|
||||
}
|
||||
|
||||
.ranking-item {
|
||||
@@ -639,15 +654,135 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
/* 笔记本小屏幕特别优化 (1024px - 1366px) */
|
||||
@media screen and (min-width: 1024px) and (max-width: 1366px) {
|
||||
/* 年度进度区域紧凑显示 */
|
||||
.countdown {
|
||||
padding: clamp(6px, 1vw, 8px);
|
||||
}
|
||||
|
||||
.countdown h3 {
|
||||
font-size: clamp(0.7rem, 1vw, 0.9rem);
|
||||
margin-bottom: clamp(6px, 1vw, 10px);
|
||||
}
|
||||
|
||||
.quarters-container {
|
||||
gap: clamp(3px, 0.8vw, 4px);
|
||||
}
|
||||
|
||||
.quarter-item {
|
||||
gap: clamp(3px, 0.6vw, 5px);
|
||||
padding-bottom: clamp(5px, 0.7vw, 6px);
|
||||
}
|
||||
|
||||
.quarter-label {
|
||||
font-size: clamp(0.7rem, 1vw, 0.85rem);
|
||||
padding: clamp(2px, 0.3vw, 3px) clamp(5px, 0.8vw, 8px);
|
||||
min-width: clamp(28px, 4vw, 35px);
|
||||
}
|
||||
|
||||
.week-circle {
|
||||
width: clamp(14px, 2vw, 18px);
|
||||
height: clamp(14px, 2vw, 18px);
|
||||
font-size: clamp(0.5rem, 0.8vw, 0.625rem);
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.weeks-container {
|
||||
gap: clamp(1px, 0.3vw, 3px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 高分辨率小笔记本优化 (125%-200% DPI缩放)
|
||||
比如:13寸1920x1080 (150%缩放实际约1280px), 14寸2560x1440 (200%缩放实际约1280px) */
|
||||
@media screen and (min-width: 1280px) and (max-width: 1600px) {
|
||||
/* 当年度进度容器宽度不足时的自动缩放 */
|
||||
.countdown {
|
||||
padding: clamp(6px, 1vw, 10px);
|
||||
/* 使用容器查询的方式,如果内容太多则整体缩小 */
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.countdown h3 {
|
||||
font-size: clamp(0.75rem, 1.1vw, 1.1rem);
|
||||
margin-bottom: clamp(8px, 1.2vw, 12px);
|
||||
}
|
||||
|
||||
.quarters-container {
|
||||
gap: clamp(4px, 0.9vw, 6px);
|
||||
}
|
||||
|
||||
.quarter-item {
|
||||
gap: clamp(4px, 0.7vw, 7px);
|
||||
padding-bottom: clamp(6px, 0.8vw, 8px);
|
||||
}
|
||||
|
||||
.quarter-label {
|
||||
font-size: clamp(0.72rem, 1.05vw, 0.9rem);
|
||||
padding: clamp(2px, 0.35vw, 4px) clamp(6px, 0.85vw, 10px);
|
||||
min-width: clamp(30px, 4.2vw, 38px);
|
||||
}
|
||||
|
||||
.week-circle {
|
||||
width: clamp(15px, 2.1vw, 20px);
|
||||
height: clamp(15px, 2.1vw, 20px);
|
||||
font-size: clamp(0.5rem, 0.85vw, 0.65rem);
|
||||
border-width: 1.5px;
|
||||
}
|
||||
|
||||
.weeks-container {
|
||||
gap: clamp(2px, 0.35vw, 4px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 极致紧凑模式 - 高DPI小屏幕 (13-14寸高分辨率笔记本)
|
||||
检测16:9或16:10宽高比 + 中等分辨率 */
|
||||
@media screen and (min-width: 1200px) and (max-width: 1440px) and (min-aspect-ratio: 16/10) {
|
||||
.countdown {
|
||||
padding: clamp(5px, 0.9vw, 8px);
|
||||
}
|
||||
|
||||
.countdown h3 {
|
||||
font-size: clamp(0.7rem, 1vw, 0.95rem);
|
||||
margin-bottom: clamp(6px, 1vw, 10px);
|
||||
}
|
||||
|
||||
.quarters-container {
|
||||
gap: clamp(3px, 0.7vw, 5px);
|
||||
}
|
||||
|
||||
.quarter-item {
|
||||
gap: clamp(3px, 0.6vw, 6px);
|
||||
padding-bottom: clamp(5px, 0.7vw, 7px);
|
||||
}
|
||||
|
||||
.quarter-label {
|
||||
font-size: clamp(0.68rem, 1vw, 0.82rem);
|
||||
padding: clamp(2px, 0.3vw, 4px) clamp(5px, 0.8vw, 9px);
|
||||
min-width: clamp(28px, 4vw, 35px);
|
||||
}
|
||||
|
||||
.week-circle {
|
||||
width: clamp(14px, 2vw, 18px);
|
||||
height: clamp(14px, 2vw, 18px);
|
||||
font-size: clamp(0.48rem, 0.8vw, 0.6rem);
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.weeks-container {
|
||||
gap: clamp(1.5px, 0.3vw, 3px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 头部和尾部图片 */
|
||||
.header-image {
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: clamp(10px, 1.5vw, 20px);
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer-image {
|
||||
margin-top: 20px;
|
||||
margin-top: clamp(10px, 1.5vw, 20px);
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -665,3 +800,172 @@ body {
|
||||
.ranking-item .rank-number {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/* 窄容器优化 - 适配所有高DPI小笔记本
|
||||
当容器宽度小于400px时(比如高分辨率小笔记本的系统缩放) */
|
||||
@media screen and (min-width: 769px) and (max-width: 1400px) {
|
||||
.top-data {
|
||||
/* 如果年度进度区域太窄,调整比例 */
|
||||
grid-template-columns: 2fr minmax(280px, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* 极窄容器优化 - 当容器实际显示宽度很小 */
|
||||
@media screen and (min-width: 769px) and (max-width: 1200px) {
|
||||
.top-data {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== 响应式布局 - 不同屏幕尺寸优化 ========== */
|
||||
|
||||
/* 小屏幕笔记本 (1366x768, 1440x900) */
|
||||
@media screen and (min-width: 769px) and (max-width: 1600px) {
|
||||
.cont {
|
||||
width: 95%;
|
||||
/* padding: clamp(0.5rem, 1.5vw, 1.5rem); */
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: clamp(0.8rem, 1.1vw, 0.95rem);
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
min-height: clamp(220px, 28vw, 280px);
|
||||
}
|
||||
|
||||
/* 年度进度区域优化 */
|
||||
.countdown {
|
||||
padding: clamp(6px, 1vw, 10px);
|
||||
}
|
||||
|
||||
.countdown h3 {
|
||||
font-size: clamp(0.75rem, 1.1vw, 1.1rem);
|
||||
margin-bottom: clamp(8px, 1.2vw, 12px);
|
||||
}
|
||||
|
||||
.quarter-item {
|
||||
gap: clamp(4px, 0.8vw, 6px);
|
||||
padding-bottom: clamp(6px, 0.8vw, 8px);
|
||||
}
|
||||
|
||||
.quarter-label {
|
||||
font-size: clamp(0.75rem, 1.1vw, 0.95rem);
|
||||
padding: clamp(2px, 0.4vw, 4px) clamp(6px, 0.9vw, 10px);
|
||||
min-width: clamp(32px, 4.5vw, 40px);
|
||||
}
|
||||
|
||||
.week-circle {
|
||||
width: clamp(16px, 2.2vw, 22px);
|
||||
height: clamp(16px, 2.2vw, 22px);
|
||||
font-size: clamp(0.5625rem, 0.9vw, 0.6875rem);
|
||||
border-width: 1.5px;
|
||||
}
|
||||
|
||||
.weeks-container {
|
||||
gap: clamp(2px, 0.4vw, 4px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 标准全高清 (1920x1080) */
|
||||
@media screen and (min-width: 1601px) and (max-width: 2100px) {
|
||||
.cont {
|
||||
width: 90%;
|
||||
max-width: 1700px;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
min-height: clamp(250px, 28vw, 320px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 2K 屏幕 (2560x1440) */
|
||||
@media screen and (min-width: 2101px) and (max-width: 3200px) {
|
||||
.cont {
|
||||
width: 85%;
|
||||
max-width: 2200px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: clamp(0.9rem, 1.2vw, 1.1rem);
|
||||
padding: clamp(6px, 0.9vw, 8px) clamp(12px, 1.6vw, 18px);
|
||||
}
|
||||
|
||||
.top-data {
|
||||
gap: clamp(10px, 1.5vw, 18px);
|
||||
margin-bottom: clamp(15px, 1.8vw, 25px);
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
min-height: clamp(280px, 28vw, 380px);
|
||||
}
|
||||
|
||||
.ranking-item {
|
||||
padding: clamp(10px, 1.3vw, 14px) clamp(12px, 1.6vw, 18px);
|
||||
font-size: clamp(0.8rem, 1.1vw, 0.95rem);
|
||||
}
|
||||
|
||||
.header-image,
|
||||
.footer-image {
|
||||
margin: clamp(12px, 1.8vw, 25px) auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* 4K 屏幕 (3840x2160 及以上) */
|
||||
@media screen and (min-width: 3201px) {
|
||||
.cont {
|
||||
width: 80%;
|
||||
max-width: 3000px;
|
||||
padding: clamp(1.5rem, 2vw, 3rem);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: clamp(1rem, 1.5vw, 1.3rem);
|
||||
padding: clamp(8px, 1vw, 10px) clamp(15px, 2vw, 25px);
|
||||
}
|
||||
|
||||
.top-data {
|
||||
gap: clamp(15px, 2vw, 25px);
|
||||
margin-bottom: clamp(20px, 2.5vw, 30px);
|
||||
}
|
||||
|
||||
.sales-info {
|
||||
padding: clamp(15px, 2vw, 25px);
|
||||
gap: clamp(15px, 2vw, 20px);
|
||||
}
|
||||
|
||||
.countdown {
|
||||
padding: clamp(12px, 1.8vw, 15px);
|
||||
}
|
||||
|
||||
.group-ranking,
|
||||
.person-ranking {
|
||||
padding: clamp(15px, 2vw, 30px);
|
||||
margin-bottom: clamp(15px, 2vw, 25px);
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
min-height: clamp(350px, 30vw, 500px);
|
||||
}
|
||||
|
||||
.ranking-item {
|
||||
padding: clamp(12px, 1.5vw, 18px) clamp(15px, 2vw, 22px);
|
||||
font-size: clamp(0.85rem, 1.2vw, 1rem);
|
||||
}
|
||||
|
||||
.rank-number {
|
||||
width: clamp(22px, 3.5vw, 28px);
|
||||
height: clamp(22px, 3.5vw, 28px);
|
||||
}
|
||||
|
||||
.header-image,
|
||||
.footer-image {
|
||||
margin: clamp(15px, 2.5vw, 30px) auto;
|
||||
}
|
||||
|
||||
/* 4K屏幕下的文字大小优化 */
|
||||
.target-amount,
|
||||
.total-amount {
|
||||
font-size: clamp(1.5rem, 3vw, 2.5rem);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 101 KiB |
+84
-50
@@ -5,7 +5,6 @@ const RESIZE_DEBOUNCE_TIME = 300; // 防抖时间(毫秒)
|
||||
const PROGRESS_STEP = 0.4; // 进度条每帧增量
|
||||
const PROGRESS_INTERVAL = 20; // 进度条更新间隔(毫秒)
|
||||
const MOBILE_BREAKPOINT = 768; // 移动端断点(px)
|
||||
const DESIGN_WIDTH = 1920; // 设计稿宽度(px)
|
||||
|
||||
// 年度销售数据(可通过API动态获取)
|
||||
let yearData = {
|
||||
@@ -13,49 +12,6 @@ let yearData = {
|
||||
actual: 231154000 // 年度实际销售额(万元)
|
||||
};
|
||||
|
||||
// PC端等比例缩放函数
|
||||
function setScale() {
|
||||
const screenWidth = window.innerWidth;
|
||||
|
||||
// 移动端不缩放,保持响应式布局
|
||||
if (screenWidth <= MOBILE_BREAKPOINT) {
|
||||
document.body.style.transform = 'none';
|
||||
document.body.style.transformOrigin = 'left top';
|
||||
document.body.style.marginLeft = '0px';
|
||||
document.body.style.width = '100%';
|
||||
document.body.style.minWidth = '320px';
|
||||
document.body.style.height = 'auto';
|
||||
document.body.style.overflowX = 'hidden';
|
||||
return;
|
||||
}
|
||||
|
||||
// PC端等比例缩放
|
||||
const scale = screenWidth / DESIGN_WIDTH;
|
||||
|
||||
// 只限制最小缩放比例,允许放大
|
||||
// 最小缩放到60%,最大可以放大填满屏幕
|
||||
const finalScale = Math.max(scale, 0.6);
|
||||
|
||||
// 重置样式以获取真实内容高度
|
||||
document.body.style.transform = 'none';
|
||||
document.body.style.height = 'auto';
|
||||
|
||||
// 获取真实内容高度
|
||||
const contentHeight = document.body.scrollHeight;
|
||||
|
||||
// 计算缩放后的高度
|
||||
const scaledHeight = Math.ceil(contentHeight * finalScale);
|
||||
|
||||
// 应用缩放
|
||||
document.body.style.transform = `scale(${finalScale})`;
|
||||
document.body.style.transformOrigin = 'left top';
|
||||
document.body.style.width = `${DESIGN_WIDTH}px`;
|
||||
document.body.style.minWidth = `${DESIGN_WIDTH}px`;
|
||||
document.body.style.height = `${scaledHeight}px`;
|
||||
document.body.style.marginLeft = '0px';
|
||||
document.body.style.overflowX = 'hidden';
|
||||
}
|
||||
|
||||
// 模拟数据 - 小组排名
|
||||
const groupData = [
|
||||
{ name: '华东一组', target: 50000000000, actual: 62000000000 },
|
||||
@@ -556,9 +512,6 @@ function renderPersonChart() {
|
||||
|
||||
// 初始化
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
// PC端等比例缩放
|
||||
setScale();
|
||||
|
||||
// 获取年度数据并更新顶部显示
|
||||
yearData = await fetchYearData();
|
||||
const progressRate = updateTopData();
|
||||
@@ -591,9 +544,6 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
|
||||
// 响应式调整 - 使用防抖优化性能
|
||||
window.addEventListener('resize', () => {
|
||||
// 立即更新缩放(不防抖,保证视觉效果流畅)
|
||||
setScale();
|
||||
|
||||
// 图表重新渲染使用防抖
|
||||
clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(() => {
|
||||
@@ -643,6 +593,90 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
|
||||
initQuarters();
|
||||
|
||||
// 动态调整周圈大小以适应容器宽度
|
||||
function adjustWeekCirclesSize() {
|
||||
const weekContainers = document.querySelectorAll('.weeks-container');
|
||||
|
||||
// 只在PC端(>768px)时执行动态调整,移动端清除内联样式
|
||||
const isMobile = window.innerWidth <= 768;
|
||||
|
||||
weekContainers.forEach(container => {
|
||||
// 获取所有周圈
|
||||
const weekCircles = container.querySelectorAll('.week-circle');
|
||||
if (weekCircles.length === 0) return;
|
||||
|
||||
// 移动端:清除所有内联样式,让 CSS 样式生效
|
||||
if (isMobile) {
|
||||
weekCircles.forEach(circle => {
|
||||
circle.style.width = '';
|
||||
circle.style.height = '';
|
||||
circle.style.fontSize = '';
|
||||
circle.style.borderWidth = '';
|
||||
});
|
||||
container.style.transform = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// PC端:动态调整周圈大小
|
||||
// 获取容器的实际宽度
|
||||
const containerWidth = container.offsetWidth;
|
||||
|
||||
// 计算所有周圈的总宽度(包括间距)
|
||||
const gap = 3; // 默认间距
|
||||
const totalGap = gap * (weekCircles.length - 1);
|
||||
const circleCount = weekCircles.length;
|
||||
|
||||
// 计算理想的周圈大小
|
||||
const idealSize = (containerWidth - totalGap) / circleCount;
|
||||
|
||||
// 设置最小和最大尺寸限制
|
||||
const minSize = 10;
|
||||
const maxSize = 24;
|
||||
|
||||
// 计算最终尺寸
|
||||
let finalSize = Math.max(minSize, Math.min(idealSize, maxSize));
|
||||
|
||||
// 如果内容仍然溢出,计算缩放比例
|
||||
const totalContentWidth = (finalSize * circleCount) + totalGap;
|
||||
let scale = 1;
|
||||
|
||||
if (totalContentWidth > containerWidth) {
|
||||
scale = containerWidth / totalContentWidth;
|
||||
// 限制最小缩放比例,避免过小
|
||||
scale = Math.max(0.5, scale);
|
||||
}
|
||||
|
||||
// 应用样式到所有周圈
|
||||
weekCircles.forEach(circle => {
|
||||
circle.style.width = finalSize + 'px';
|
||||
circle.style.height = finalSize + 'px';
|
||||
circle.style.fontSize = (finalSize * 0.4) + 'px'; // 字体大小随圆圈缩放
|
||||
circle.style.borderWidth = Math.max(1, finalSize / 8) + 'px'; // 边框随圆圈缩放
|
||||
});
|
||||
|
||||
// 如果需要缩放整个容器
|
||||
if (scale < 1) {
|
||||
container.style.transform = `scaleX(${scale})`;
|
||||
container.style.transformOrigin = 'left';
|
||||
} else {
|
||||
container.style.transform = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 初始调整
|
||||
setTimeout(() => {
|
||||
adjustWeekCirclesSize();
|
||||
}, 100);
|
||||
|
||||
// 监听窗口大小变化并重新调整
|
||||
window.addEventListener('resize', () => {
|
||||
clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(() => {
|
||||
adjustWeekCirclesSize();
|
||||
}, RESIZE_DEBOUNCE_TIME);
|
||||
});
|
||||
|
||||
// 延迟启动进度条动画,使用动态计算的完成率
|
||||
setTimeout(() => {
|
||||
startProgressAnimation(progressRate);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
VITE_API_BASE_URL=http://localhost:8000/api
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 101 KiB |
+1
-56
@@ -7,68 +7,13 @@ import footerImage from '/img/foot.png'
|
||||
|
||||
function App() {
|
||||
const containerRef = useRef(null)
|
||||
const MOBILE_BREAKPOINT = 768
|
||||
const DESIGN_WIDTH = 1920
|
||||
|
||||
// PC端等比例缩放函数
|
||||
const setScale = () => {
|
||||
const screenWidth = window.innerWidth
|
||||
|
||||
// 移动端不缩放,保持响应式布局
|
||||
if (screenWidth <= MOBILE_BREAKPOINT) {
|
||||
document.body.style.transform = 'none'
|
||||
document.body.style.transformOrigin = 'left top'
|
||||
document.body.style.marginLeft = '0px'
|
||||
document.body.style.width = '100%'
|
||||
document.body.style.minWidth = '320px'
|
||||
document.body.style.height = 'auto'
|
||||
document.body.style.overflowX = 'hidden'
|
||||
return
|
||||
}
|
||||
|
||||
// PC端等比例缩放
|
||||
const scale = screenWidth / DESIGN_WIDTH
|
||||
const finalScale = Math.max(scale, 0.6)
|
||||
|
||||
// 重置样式以获取真实内容高度
|
||||
document.body.style.transform = 'none'
|
||||
document.body.style.height = 'auto'
|
||||
|
||||
// 获取真实内容高度
|
||||
const contentHeight = document.body.scrollHeight
|
||||
|
||||
// 计算缩放后的高度
|
||||
const scaledHeight = Math.ceil(contentHeight * finalScale)
|
||||
|
||||
// 应用缩放
|
||||
document.body.style.transform = `scale(${finalScale})`
|
||||
document.body.style.transformOrigin = 'left top'
|
||||
document.body.style.width = `${DESIGN_WIDTH}px`
|
||||
document.body.style.minWidth = `${DESIGN_WIDTH}px`
|
||||
document.body.style.height = `${scaledHeight}px`
|
||||
document.body.style.marginLeft = '0px'
|
||||
document.body.style.overflowX = 'hidden'
|
||||
}
|
||||
|
||||
const handleProgressRate = (rate) => {
|
||||
console.log('进度率:', rate)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setScale()
|
||||
|
||||
let resizeTimer = null
|
||||
const handleResize = () => {
|
||||
clearTimeout(resizeTimer)
|
||||
resizeTimer = setTimeout(setScale, 100)
|
||||
}
|
||||
|
||||
window.addEventListener('resize', handleResize)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
clearTimeout(resizeTimer)
|
||||
}
|
||||
// 移除窗口缩放监听,使用纯响应式布局
|
||||
}, [])
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
function GroupRanking() {
|
||||
@@ -7,14 +7,36 @@ function GroupRanking() {
|
||||
|
||||
const MOBILE_BREAKPOINT = 768
|
||||
|
||||
// 小组数据
|
||||
const groupData = [
|
||||
// 小组数据(从 API 获取)
|
||||
const [groupData, setGroupData] = useState([
|
||||
{ name: '华东一组', target: 50000000000, actual: 62000000000 },
|
||||
{ name: '华南二组', target: 45000000000, actual: 51000000000 },
|
||||
{ name: '华北三组', target: 40000000000, actual: 43500000000 },
|
||||
{ name: '西南四组', target: 35000000000, actual: 28000000000 },
|
||||
{ name: '东北五组', target: 30000000000, actual: 26500000000 }
|
||||
]
|
||||
])
|
||||
|
||||
// 获取小组排名数据
|
||||
const fetchGroupRanking = async () => {
|
||||
try {
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000/api'
|
||||
const response = await fetch(`${API_BASE_URL}/sales/group-ranking?year=2024`)
|
||||
const result = await response.json()
|
||||
console.log('小组排名 API 响应:', result)
|
||||
|
||||
// 转换单位:API 返回的是万元,需要转换为元
|
||||
const rankings = result.rankings.map(item => ({
|
||||
name: item.name,
|
||||
target: item.target * 10000, // 万元转元
|
||||
actual: item.actual * 10000
|
||||
}))
|
||||
|
||||
setGroupData(rankings)
|
||||
} catch (error) {
|
||||
console.error('获取小组排名失败:', error)
|
||||
// 保持默认数据
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化数字(万元)
|
||||
const formatNumber = (num) => {
|
||||
@@ -203,6 +225,12 @@ function GroupRanking() {
|
||||
chartInstance.current.setOption(option)
|
||||
}
|
||||
|
||||
// 初始加载数据
|
||||
useEffect(() => {
|
||||
fetchGroupRanking()
|
||||
}, [])
|
||||
|
||||
// 数据变化时重新渲染图表
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
renderChart()
|
||||
@@ -226,7 +254,7 @@ function GroupRanking() {
|
||||
chartInstance.current.dispose()
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
}, [groupData])
|
||||
|
||||
return (
|
||||
<div className="group-ranking">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
function PersonRanking() {
|
||||
@@ -7,8 +7,8 @@ function PersonRanking() {
|
||||
|
||||
const MOBILE_BREAKPOINT = 768
|
||||
|
||||
// 个人数据
|
||||
const personData = [
|
||||
// 个人数据(从 API 获取)
|
||||
const [personData, setPersonData] = useState([
|
||||
{ name: '张伟', target: 5000000000, actual: 7200000000 },
|
||||
{ name: '李娜', target: 4500000000, actual: 5800000000 },
|
||||
{ name: '王强', target: 4000000000, actual: 4900000000 },
|
||||
@@ -16,7 +16,29 @@ function PersonRanking() {
|
||||
{ name: '陈静', target: 3500000000, actual: 3600000000 },
|
||||
{ name: '杨明', target: 3200000000, actual: 2800000000 },
|
||||
{ name: '赵丽', target: 3000000000, actual: 2500000000 }
|
||||
]
|
||||
])
|
||||
|
||||
// 获取个人排名数据
|
||||
const fetchPersonRanking = async () => {
|
||||
try {
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000/api'
|
||||
const response = await fetch(`${API_BASE_URL}/sales/person-ranking?year=2024`)
|
||||
const result = await response.json()
|
||||
console.log('个人排名 API 响应:', result)
|
||||
|
||||
// 转换单位:API 返回的是万元,需要转换为元
|
||||
const rankings = result.rankings.map(item => ({
|
||||
name: item.name,
|
||||
target: item.target * 10000, // 万元转元
|
||||
actual: item.actual * 10000
|
||||
}))
|
||||
|
||||
setPersonData(rankings)
|
||||
} catch (error) {
|
||||
console.error('获取个人排名失败:', error)
|
||||
// 保持默认数据
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化数字(万元)
|
||||
const formatNumber = (num) => {
|
||||
@@ -202,6 +224,12 @@ function PersonRanking() {
|
||||
chartInstance.current.setOption(option)
|
||||
}
|
||||
|
||||
// 初始加载数据
|
||||
useEffect(() => {
|
||||
fetchPersonRanking()
|
||||
}, [])
|
||||
|
||||
// 数据变化时重新渲染图表
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
renderChart()
|
||||
@@ -225,7 +253,7 @@ function PersonRanking() {
|
||||
chartInstance.current.dispose()
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
}, [personData])
|
||||
|
||||
return (
|
||||
<div className="person-ranking">
|
||||
|
||||
@@ -6,6 +6,66 @@ function TopData({ onProgressRate }) {
|
||||
const [progressRate, setProgressRate] = useState('0.00')
|
||||
|
||||
const MOBILE_BREAKPOINT = 768
|
||||
const weekContainersRef = useRef(null)
|
||||
|
||||
// 动态调整周圈大小以适应容器宽度(仅PC端)
|
||||
const adjustWeekCirclesSize = () => {
|
||||
const weekContainers = document.querySelectorAll('.weeks-container')
|
||||
|
||||
// 只在PC端(>768px)时执行动态调整,移动端清除内联样式
|
||||
const isMobile = window.innerWidth <= 768
|
||||
|
||||
weekContainers.forEach(container => {
|
||||
const weekCircles = container.querySelectorAll('.week-circle')
|
||||
if (weekCircles.length === 0) return
|
||||
|
||||
// 移动端:清除所有内联样式,让 CSS 样式生效
|
||||
if (isMobile) {
|
||||
weekCircles.forEach(circle => {
|
||||
circle.style.width = ''
|
||||
circle.style.height = ''
|
||||
circle.style.fontSize = ''
|
||||
circle.style.borderWidth = ''
|
||||
})
|
||||
container.style.transform = ''
|
||||
return
|
||||
}
|
||||
|
||||
// PC端:动态调整周圈大小
|
||||
const containerWidth = container.offsetWidth
|
||||
const gap = 3
|
||||
const totalGap = gap * (weekCircles.length - 1)
|
||||
const circleCount = weekCircles.length
|
||||
|
||||
const idealSize = (containerWidth - totalGap) / circleCount
|
||||
const minSize = 10
|
||||
const maxSize = 24
|
||||
|
||||
let finalSize = Math.max(minSize, Math.min(idealSize, maxSize))
|
||||
|
||||
const totalContentWidth = (finalSize * circleCount) + totalGap
|
||||
let scale = 1
|
||||
|
||||
if (totalContentWidth > containerWidth) {
|
||||
scale = containerWidth / totalContentWidth
|
||||
scale = Math.max(0.5, scale)
|
||||
}
|
||||
|
||||
weekCircles.forEach(circle => {
|
||||
circle.style.width = finalSize + 'px'
|
||||
circle.style.height = finalSize + 'px'
|
||||
circle.style.fontSize = (finalSize * 0.4) + 'px'
|
||||
circle.style.borderWidth = Math.max(1, finalSize / 8) + 'px'
|
||||
})
|
||||
|
||||
if (scale < 1) {
|
||||
container.style.transform = `scaleX(${scale})`
|
||||
container.style.transformOrigin = 'left'
|
||||
} else {
|
||||
container.style.transform = 'none'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取当前周数
|
||||
const getCurrentWeek = () => {
|
||||
@@ -28,10 +88,19 @@ function TopData({ onProgressRate }) {
|
||||
// 获取年度数据
|
||||
const fetchYearData = async () => {
|
||||
console.log('正在获取年度数据...')
|
||||
// TODO: 替换为实际的API调用
|
||||
return {
|
||||
target: 3500,
|
||||
actual: 2750
|
||||
try {
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000/api'
|
||||
const response = await fetch(`${API_BASE_URL}/sales/year-data?year=2024`)
|
||||
const result = await response.json()
|
||||
console.log('API 响应:', result)
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error('获取年度数据失败:', error)
|
||||
// 返回默认数据
|
||||
return {
|
||||
target: 3500,
|
||||
actual: 2750
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +154,27 @@ function TopData({ onProgressRate }) {
|
||||
}
|
||||
|
||||
initData()
|
||||
|
||||
// 初始调整周圈大小 - 延迟到数据渲染后
|
||||
setTimeout(() => {
|
||||
adjustWeekCirclesSize()
|
||||
}, 500) // 增加延迟确保 DOM 已渲染
|
||||
|
||||
// 监听窗口大小变化
|
||||
let resizeTimer
|
||||
const handleResize = () => {
|
||||
clearTimeout(resizeTimer)
|
||||
resizeTimer = setTimeout(() => {
|
||||
adjustWeekCirclesSize()
|
||||
}, 300)
|
||||
}
|
||||
|
||||
window.addEventListener('resize', handleResize)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
clearTimeout(resizeTimer)
|
||||
}
|
||||
}, [onProgressRate])
|
||||
|
||||
return (
|
||||
|
||||
+312
-32
@@ -1,15 +1,3 @@
|
||||
/* PC端等比例缩放,根字号固定 */
|
||||
html {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 移动端基础字号 */
|
||||
@media screen and (max-width: 768px) {
|
||||
html {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -21,9 +9,6 @@ body {
|
||||
color: #fff;
|
||||
min-width: 320px;
|
||||
overflow-x: hidden;
|
||||
/* PC端缩放相关样式 */
|
||||
transform-origin: left top;
|
||||
transition: transform 0.1s ease-out;
|
||||
}
|
||||
|
||||
.container {
|
||||
@@ -32,8 +17,6 @@ body {
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
background: #000;
|
||||
/* 固定最小宽度,避免缩放时内容挤压 */
|
||||
min-width: 1200px;
|
||||
}
|
||||
|
||||
.cont{
|
||||
@@ -41,20 +24,10 @@ body {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
/* 确保内容区域最小宽度 */
|
||||
min-width: 1080px;
|
||||
}
|
||||
|
||||
/* 移动端下调整宽度 */
|
||||
@media screen and (max-width: 768px) {
|
||||
body {
|
||||
transform: none !important;
|
||||
width: 100% !important;
|
||||
min-width: 320px !important;
|
||||
height: auto !important;
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
.container {
|
||||
min-width: 320px;
|
||||
width: 100%;
|
||||
@@ -73,6 +46,7 @@ body {
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: clamp(6px, 1.5vw, 14px);
|
||||
margin-bottom: clamp(10px, 1.5vw, 20px);
|
||||
min-height: 0; /* 允许grid子项收缩 */
|
||||
}
|
||||
|
||||
.sales-info {
|
||||
@@ -237,6 +211,7 @@ body {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden; /* PC端防止溢出 */
|
||||
}
|
||||
|
||||
.countdown h3 {
|
||||
@@ -244,6 +219,7 @@ body {
|
||||
color: #e7552f;
|
||||
margin-bottom: clamp(10px, 1.5vw, 15px);
|
||||
font-weight: bold;
|
||||
white-space: nowrap; /* 标题不换行 */
|
||||
}
|
||||
|
||||
/* 季度容器 */
|
||||
@@ -263,6 +239,7 @@ body {
|
||||
min-width: 0;
|
||||
border-bottom: 1px solid #523A14;
|
||||
padding-bottom: 8px;
|
||||
overflow: hidden; /* PC端防止内容溢出 */
|
||||
}
|
||||
.last-quarter{
|
||||
border-bottom: none;
|
||||
@@ -285,26 +262,34 @@ body {
|
||||
/* 周数圆圈容器 */
|
||||
.weeks-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: clamp(3px, 0.5vw, 5px);
|
||||
flex-wrap: nowrap; /* PC端不换行 */
|
||||
gap: clamp(2px, 0.4vw, 5px);
|
||||
flex: 1;
|
||||
overflow-x: hidden; /* 隐藏溢出内容 */
|
||||
overflow-y: hidden;
|
||||
min-width: 0; /* 允许flex子项缩小 */
|
||||
justify-content: flex-start; /* 从左侧开始排列 */
|
||||
}
|
||||
|
||||
/* 单个周数圆圈 */
|
||||
.week-circle {
|
||||
width: clamp(18px, 2.5vw, 24px);
|
||||
height: clamp(18px, 2.5vw, 24px);
|
||||
width: clamp(12px, 2.5vw, 24px);
|
||||
height: clamp(12px, 2.5vw, 24px);
|
||||
border-radius: 50%;
|
||||
border: 2px solid #815B37;
|
||||
background: rgba(129, 91, 55, 0.2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: clamp(0.625rem, 1vw, 0.75rem);
|
||||
font-size: clamp(0.5rem, 1vw, 0.75rem);
|
||||
color: #f5bbab;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
flex-shrink: 1; /* PC端允许周圈缩小以适应容器 */
|
||||
min-width: 10px; /* 最小尺寸 */
|
||||
min-height: 10px;
|
||||
flex-basis: clamp(12px, 2.5vw, 24px); /* 弹性基础尺寸 */
|
||||
}
|
||||
|
||||
.week-circle:hover {
|
||||
@@ -505,6 +490,21 @@ body {
|
||||
font-size: clamp(0.875rem, 2.5vw, 1.125rem);
|
||||
}
|
||||
|
||||
/* 移动端年度进度允许换行 */
|
||||
.countdown {
|
||||
overflow: visible; /* 移动端允许溢出换行 */
|
||||
}
|
||||
|
||||
.quarter-item {
|
||||
overflow: visible; /* 移动端允许内容换行 */
|
||||
}
|
||||
|
||||
.weeks-container {
|
||||
flex-wrap: wrap; /* 移动端允许换行 */
|
||||
overflow-x: visible;
|
||||
overflow-y: visible;
|
||||
}
|
||||
|
||||
.quarter-label {
|
||||
font-size: clamp(0.75rem, 2vw, 0.875rem);
|
||||
padding: clamp(2px, 0.5vw, 4px) clamp(6px, 1.5vw, 10px);
|
||||
@@ -515,6 +515,10 @@ body {
|
||||
width: clamp(16px, 4vw, 20px);
|
||||
height: clamp(16px, 4vw, 20px);
|
||||
font-size: clamp(0.5rem, 1.5vw, 0.625rem);
|
||||
flex-shrink: 0; /* 移动端不缩小 */
|
||||
min-width: auto;
|
||||
min-height: auto;
|
||||
flex-basis: auto;
|
||||
}
|
||||
|
||||
.ranking-item {
|
||||
@@ -574,6 +578,14 @@ body {
|
||||
|
||||
/* 超小屏幕优化 (480px及以下) */
|
||||
@media (max-width: 480px) {
|
||||
.header h1 {
|
||||
font-size: clamp(1.25rem, 5vw, 1.5rem);
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: clamp(0.75rem, 3vw, 1rem);
|
||||
}
|
||||
|
||||
.quarters-container {
|
||||
gap: clamp(5px, 2vw, 8px);
|
||||
}
|
||||
@@ -592,6 +604,274 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
/* 笔记本小屏幕特别优化 (1024px - 1366px) */
|
||||
@media screen and (min-width: 1024px) and (max-width: 1366px) {
|
||||
/* 年度进度区域紧凑显示 */
|
||||
.countdown {
|
||||
padding: clamp(6px, 1vw, 8px);
|
||||
}
|
||||
|
||||
.countdown h3 {
|
||||
font-size: clamp(0.7rem, 1vw, 0.9rem);
|
||||
margin-bottom: clamp(6px, 1vw, 10px);
|
||||
}
|
||||
|
||||
.quarters-container {
|
||||
gap: clamp(3px, 0.8vw, 4px);
|
||||
}
|
||||
|
||||
.quarter-item {
|
||||
gap: clamp(3px, 0.6vw, 5px);
|
||||
padding-bottom: clamp(5px, 0.7vw, 6px);
|
||||
}
|
||||
|
||||
.quarter-label {
|
||||
font-size: clamp(0.7rem, 1vw, 0.85rem);
|
||||
padding: clamp(2px, 0.3vw, 3px) clamp(5px, 0.8vw, 8px);
|
||||
min-width: clamp(28px, 4vw, 35px);
|
||||
}
|
||||
|
||||
.week-circle {
|
||||
width: clamp(14px, 2vw, 18px);
|
||||
height: clamp(14px, 2vw, 18px);
|
||||
font-size: clamp(0.5rem, 0.8vw, 0.625rem);
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.weeks-container {
|
||||
gap: clamp(1px, 0.3vw, 3px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 高分辨率小笔记本优化 (125%-200% DPI缩放) */
|
||||
@media screen and (min-width: 1280px) and (max-width: 1600px) {
|
||||
.countdown {
|
||||
padding: clamp(6px, 1vw, 10px);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.countdown h3 {
|
||||
font-size: clamp(0.75rem, 1.1vw, 1.1rem);
|
||||
margin-bottom: clamp(8px, 1.2vw, 12px);
|
||||
}
|
||||
|
||||
.quarters-container {
|
||||
gap: clamp(4px, 0.9vw, 6px);
|
||||
}
|
||||
|
||||
.quarter-item {
|
||||
gap: clamp(4px, 0.7vw, 7px);
|
||||
padding-bottom: clamp(6px, 0.8vw, 8px);
|
||||
}
|
||||
|
||||
.quarter-label {
|
||||
font-size: clamp(0.72rem, 1.05vw, 0.9rem);
|
||||
padding: clamp(2px, 0.35vw, 4px) clamp(6px, 0.85vw, 10px);
|
||||
min-width: clamp(30px, 4.2vw, 38px);
|
||||
}
|
||||
|
||||
.week-circle {
|
||||
width: clamp(15px, 2.1vw, 20px);
|
||||
height: clamp(15px, 2.1vw, 20px);
|
||||
font-size: clamp(0.5rem, 0.85vw, 0.65rem);
|
||||
border-width: 1.5px;
|
||||
}
|
||||
|
||||
.weeks-container {
|
||||
gap: clamp(2px, 0.35vw, 4px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 极致紧凑模式 - 高DPI小屏幕 */
|
||||
@media screen and (min-width: 1200px) and (max-width: 1440px) and (min-aspect-ratio: 16/10) {
|
||||
.countdown {
|
||||
padding: clamp(5px, 0.9vw, 8px);
|
||||
}
|
||||
|
||||
.countdown h3 {
|
||||
font-size: clamp(0.7rem, 1vw, 0.95rem);
|
||||
margin-bottom: clamp(6px, 1vw, 10px);
|
||||
}
|
||||
|
||||
.quarters-container {
|
||||
gap: clamp(3px, 0.7vw, 5px);
|
||||
}
|
||||
|
||||
.quarter-item {
|
||||
gap: clamp(3px, 0.6vw, 6px);
|
||||
padding-bottom: clamp(5px, 0.7vw, 7px);
|
||||
}
|
||||
|
||||
.quarter-label {
|
||||
font-size: clamp(0.68rem, 1vw, 0.82rem);
|
||||
padding: clamp(2px, 0.3vw, 4px) clamp(5px, 0.8vw, 9px);
|
||||
min-width: clamp(28px, 4vw, 35px);
|
||||
}
|
||||
|
||||
.week-circle {
|
||||
width: clamp(14px, 2vw, 18px);
|
||||
height: clamp(14px, 2vw, 18px);
|
||||
font-size: clamp(0.48rem, 0.8vw, 0.6rem);
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.weeks-container {
|
||||
gap: clamp(1.5px, 0.3vw, 3px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 窄容器优化 - 适配所有高DPI小笔记本 */
|
||||
@media screen and (min-width: 769px) and (max-width: 1400px) {
|
||||
.top-data {
|
||||
grid-template-columns: 2fr minmax(280px, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* 极窄容器优化 */
|
||||
@media screen and (min-width: 769px) and (max-width: 1200px) {
|
||||
.top-data {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== 响应式布局 - 不同屏幕尺寸优化 ========== */
|
||||
|
||||
/* 小屏幕笔记本 (1366x768, 1440x900) */
|
||||
@media screen and (min-width: 769px) and (max-width: 1600px) {
|
||||
.cont {
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: clamp(0.8rem, 1.1vw, 0.95rem);
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
min-height: clamp(220px, 28vw, 280px);
|
||||
}
|
||||
|
||||
/* 年度进度区域优化 */
|
||||
.countdown {
|
||||
padding: clamp(6px, 1vw, 10px);
|
||||
}
|
||||
|
||||
.countdown h3 {
|
||||
font-size: clamp(0.75rem, 1.1vw, 1.1rem);
|
||||
margin-bottom: clamp(8px, 1.2vw, 12px);
|
||||
}
|
||||
|
||||
.quarter-item {
|
||||
gap: clamp(4px, 0.8vw, 6px);
|
||||
padding-bottom: clamp(6px, 0.8vw, 8px);
|
||||
}
|
||||
|
||||
.quarter-label {
|
||||
font-size: clamp(0.75rem, 1.1vw, 0.95rem);
|
||||
padding: clamp(2px, 0.4vw, 4px) clamp(6px, 0.9vw, 10px);
|
||||
min-width: clamp(32px, 4.5vw, 40px);
|
||||
}
|
||||
|
||||
.week-circle {
|
||||
width: clamp(16px, 2.2vw, 22px);
|
||||
height: clamp(16px, 2.2vw, 22px);
|
||||
font-size: clamp(0.5625rem, 0.9vw, 0.6875rem);
|
||||
border-width: 1.5px;
|
||||
}
|
||||
|
||||
.weeks-container {
|
||||
gap: clamp(2px, 0.4vw, 4px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 标准全高清 (1920x1080) */
|
||||
@media screen and (min-width: 1601px) and (max-width: 2100px) {
|
||||
.cont {
|
||||
width: 90%;
|
||||
max-width: 1700px;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
min-height: clamp(250px, 28vw, 320px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 2K 屏幕 (2560x1440) */
|
||||
@media screen and (min-width: 2101px) and (max-width: 3200px) {
|
||||
.cont {
|
||||
width: 85%;
|
||||
max-width: 2200px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: clamp(0.9rem, 1.2vw, 1.1rem);
|
||||
padding: clamp(6px, 0.9vw, 8px) clamp(12px, 1.6vw, 18px);
|
||||
}
|
||||
|
||||
.top-data {
|
||||
gap: clamp(10px, 1.5vw, 18px);
|
||||
margin-bottom: clamp(15px, 1.8vw, 25px);
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
min-height: clamp(280px, 28vw, 380px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 4K 屏幕 (3840x2160 及以上) */
|
||||
@media screen and (min-width: 3201px) {
|
||||
.cont {
|
||||
width: 80%;
|
||||
max-width: 3000px;
|
||||
padding: clamp(1.5rem, 2vw, 3rem);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: clamp(1rem, 1.5vw, 1.3rem);
|
||||
padding: clamp(8px, 1vw, 10px) clamp(15px, 2vw, 25px);
|
||||
}
|
||||
|
||||
.top-data {
|
||||
gap: clamp(15px, 2vw, 25px);
|
||||
margin-bottom: clamp(20px, 2.5vw, 30px);
|
||||
}
|
||||
|
||||
.sales-info {
|
||||
padding: clamp(15px, 2vw, 25px);
|
||||
gap: clamp(15px, 2vw, 20px);
|
||||
}
|
||||
|
||||
.countdown {
|
||||
padding: clamp(12px, 1.8vw, 15px);
|
||||
}
|
||||
|
||||
.group-ranking,
|
||||
.person-ranking {
|
||||
padding: clamp(15px, 2vw, 30px);
|
||||
margin-bottom: clamp(15px, 2vw, 25px);
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
min-height: clamp(350px, 30vw, 500px);
|
||||
}
|
||||
|
||||
.ranking-item {
|
||||
padding: clamp(12px, 1.5vw, 18px) clamp(15px, 2vw, 22px);
|
||||
font-size: clamp(0.85rem, 1.2vw, 1rem);
|
||||
}
|
||||
|
||||
.rank-number {
|
||||
width: clamp(22px, 3.5vw, 28px);
|
||||
height: clamp(22px, 3.5vw, 28px);
|
||||
}
|
||||
|
||||
/* 4K屏幕下的文字大小优化 */
|
||||
.target-amount,
|
||||
.total-amount {
|
||||
font-size: clamp(1.5rem, 3vw, 2.5rem);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* 头部和尾部图片 */
|
||||
.header-image {
|
||||
margin-bottom: 20px;
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 101 KiB |
+4
-57
@@ -35,71 +35,18 @@ import PersonRanking from './components/PersonRanking.vue'
|
||||
import headerImage from '/img/heard.png'
|
||||
import footerImage from '/img/foot.png'
|
||||
|
||||
const MOBILE_BREAKPOINT = 768
|
||||
const DESIGN_WIDTH = 1920
|
||||
const containerRef = ref(null)
|
||||
|
||||
// PC端等比例缩放函数
|
||||
const setScale = () => {
|
||||
const screenWidth = window.innerWidth
|
||||
|
||||
// 移动端不缩放,保持响应式布局
|
||||
if (screenWidth <= MOBILE_BREAKPOINT) {
|
||||
document.body.style.transform = 'none'
|
||||
document.body.style.transformOrigin = 'left top'
|
||||
document.body.style.marginLeft = '0px'
|
||||
document.body.style.width = '100%'
|
||||
document.body.style.minWidth = '320px'
|
||||
document.body.style.height = 'auto'
|
||||
document.body.style.overflowX = 'hidden'
|
||||
return
|
||||
}
|
||||
|
||||
// PC端等比例缩放
|
||||
const scale = screenWidth / DESIGN_WIDTH
|
||||
const finalScale = Math.max(scale, 0.6)
|
||||
|
||||
// 重置样式以获取真实内容高度
|
||||
document.body.style.transform = 'none'
|
||||
document.body.style.height = 'auto'
|
||||
|
||||
// 获取真实内容高度
|
||||
const contentHeight = document.body.scrollHeight
|
||||
|
||||
// 计算缩放后的高度
|
||||
const scaledHeight = Math.ceil(contentHeight * finalScale)
|
||||
|
||||
// 应用缩放
|
||||
document.body.style.transform = `scale(${finalScale})`
|
||||
document.body.style.transformOrigin = 'left top'
|
||||
document.body.style.width = `${DESIGN_WIDTH}px`
|
||||
document.body.style.minWidth = `${DESIGN_WIDTH}px`
|
||||
document.body.style.height = `${scaledHeight}px`
|
||||
document.body.style.marginLeft = '0px'
|
||||
document.body.style.overflowX = 'hidden'
|
||||
}
|
||||
|
||||
let resizeTimer = null
|
||||
|
||||
const handleResize = () => {
|
||||
setScale()
|
||||
}
|
||||
|
||||
const handleProgressRate = (rate) => {
|
||||
// 可以在这里处理进度率数据
|
||||
console.log('进度率:', rate)
|
||||
// 可以在这里处理进度率数据
|
||||
console.log('进度率:', rate)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setScale()
|
||||
window.addEventListener('resize', () => {
|
||||
clearTimeout(resizeTimer)
|
||||
resizeTimer = setTimeout(setScale, 100)
|
||||
})
|
||||
// 移除窗口缩放监听,使用纯响应式布局
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
clearTimeout(resizeTimer)
|
||||
// 清理工作
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
const emit = defineEmits(['progress-rate'])
|
||||
|
||||
@@ -79,6 +79,65 @@ const getCurrentWeek = () => {
|
||||
|
||||
const currentWeek = ref(getCurrentWeek())
|
||||
|
||||
// 动态调整周圈大小以适应容器宽度
|
||||
const adjustWeekCirclesSize = () => {
|
||||
const weekContainers = document.querySelectorAll('.weeks-container')
|
||||
|
||||
// 只在PC端(>768px)时执行动态调整,移动端清除内联样式
|
||||
const isMobile = window.innerWidth <= 768
|
||||
|
||||
weekContainers.forEach(container => {
|
||||
const weekCircles = container.querySelectorAll('.week-circle')
|
||||
if (weekCircles.length === 0) return
|
||||
|
||||
// 移动端:清除所有内联样式,让 CSS 样式生效
|
||||
if (isMobile) {
|
||||
weekCircles.forEach(circle => {
|
||||
circle.style.width = ''
|
||||
circle.style.height = ''
|
||||
circle.style.fontSize = ''
|
||||
circle.style.borderWidth = ''
|
||||
})
|
||||
container.style.transform = ''
|
||||
return
|
||||
}
|
||||
|
||||
// PC端:动态调整周圈大小
|
||||
const containerWidth = container.offsetWidth
|
||||
const gap = 3
|
||||
const totalGap = gap * (weekCircles.length - 1)
|
||||
const circleCount = weekCircles.length
|
||||
|
||||
const idealSize = (containerWidth - totalGap) / circleCount
|
||||
const minSize = 10
|
||||
const maxSize = 24
|
||||
|
||||
let finalSize = Math.max(minSize, Math.min(idealSize, maxSize))
|
||||
|
||||
const totalContentWidth = (finalSize * circleCount) + totalGap
|
||||
let scale = 1
|
||||
|
||||
if (totalContentWidth > containerWidth) {
|
||||
scale = containerWidth / totalContentWidth
|
||||
scale = Math.max(0.5, scale)
|
||||
}
|
||||
|
||||
weekCircles.forEach(circle => {
|
||||
circle.style.width = finalSize + 'px'
|
||||
circle.style.height = finalSize + 'px'
|
||||
circle.style.fontSize = (finalSize * 0.4) + 'px'
|
||||
circle.style.borderWidth = Math.max(1, finalSize / 8) + 'px'
|
||||
})
|
||||
|
||||
if (scale < 1) {
|
||||
container.style.transform = `scaleX(${scale})`
|
||||
container.style.transformOrigin = 'left'
|
||||
} else {
|
||||
container.style.transform = 'none'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 季度数据
|
||||
const quarters = ref([
|
||||
{ id: 'q1', label: 'Q1', start: 1, end: 13, weeks: [] },
|
||||
@@ -162,5 +221,27 @@ onMounted(async () => {
|
||||
setTimeout(() => {
|
||||
startProgressAnimation(parseFloat(rate))
|
||||
}, 800)
|
||||
|
||||
// 初始调整周圈大小 - 延迟到数据渲染后
|
||||
setTimeout(() => {
|
||||
adjustWeekCirclesSize()
|
||||
}, 500) // 增加延迟确保 DOM 已渲染
|
||||
|
||||
// 监听窗口大小变化
|
||||
let resizeTimer
|
||||
const handleResize = () => {
|
||||
clearTimeout(resizeTimer)
|
||||
resizeTimer = setTimeout(() => {
|
||||
adjustWeekCirclesSize()
|
||||
}, 300)
|
||||
}
|
||||
|
||||
window.addEventListener('resize', handleResize)
|
||||
|
||||
// 组件卸载时清理
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
clearTimeout(resizeTimer)
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
+312
-32
@@ -1,15 +1,3 @@
|
||||
/* PC端等比例缩放,根字号固定 */
|
||||
html {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 移动端基础字号 */
|
||||
@media screen and (max-width: 768px) {
|
||||
html {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -21,9 +9,6 @@ body {
|
||||
color: #fff;
|
||||
min-width: 320px;
|
||||
overflow-x: hidden;
|
||||
/* PC端缩放相关样式 */
|
||||
transform-origin: left top;
|
||||
transition: transform 0.1s ease-out;
|
||||
}
|
||||
|
||||
.container {
|
||||
@@ -32,8 +17,6 @@ body {
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
background: #000;
|
||||
/* 固定最小宽度,避免缩放时内容挤压 */
|
||||
min-width: 1200px;
|
||||
}
|
||||
|
||||
.cont{
|
||||
@@ -41,20 +24,10 @@ body {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
/* 确保内容区域最小宽度 */
|
||||
min-width: 1080px;
|
||||
}
|
||||
|
||||
/* 移动端下调整宽度 */
|
||||
@media screen and (max-width: 768px) {
|
||||
body {
|
||||
transform: none !important;
|
||||
width: 100% !important;
|
||||
min-width: 320px !important;
|
||||
height: auto !important;
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
.container {
|
||||
min-width: 320px;
|
||||
width: 100%;
|
||||
@@ -73,6 +46,7 @@ body {
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: clamp(6px, 1.5vw, 14px);
|
||||
margin-bottom: clamp(10px, 1.5vw, 20px);
|
||||
min-height: 0; /* 允许grid子项收缩 */
|
||||
}
|
||||
|
||||
.sales-info {
|
||||
@@ -237,6 +211,7 @@ body {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden; /* PC端防止溢出 */
|
||||
}
|
||||
|
||||
.countdown h3 {
|
||||
@@ -244,6 +219,7 @@ body {
|
||||
color: #e7552f;
|
||||
margin-bottom: clamp(10px, 1.5vw, 15px);
|
||||
font-weight: bold;
|
||||
white-space: nowrap; /* 标题不换行 */
|
||||
}
|
||||
|
||||
/* 季度容器 */
|
||||
@@ -263,6 +239,7 @@ body {
|
||||
min-width: 0;
|
||||
border-bottom: 1px solid #523A14;
|
||||
padding-bottom: 8px;
|
||||
overflow: hidden; /* PC端防止内容溢出 */
|
||||
}
|
||||
.last-quarter{
|
||||
border-bottom: none;
|
||||
@@ -285,26 +262,34 @@ body {
|
||||
/* 周数圆圈容器 */
|
||||
.weeks-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: clamp(3px, 0.5vw, 5px);
|
||||
flex-wrap: nowrap; /* PC端不换行 */
|
||||
gap: clamp(2px, 0.4vw, 5px);
|
||||
flex: 1;
|
||||
overflow-x: hidden; /* 隐藏溢出内容 */
|
||||
overflow-y: hidden;
|
||||
min-width: 0; /* 允许flex子项缩小 */
|
||||
justify-content: flex-start; /* 从左侧开始排列 */
|
||||
}
|
||||
|
||||
/* 单个周数圆圈 */
|
||||
.week-circle {
|
||||
width: clamp(18px, 2.5vw, 24px);
|
||||
height: clamp(18px, 2.5vw, 24px);
|
||||
width: clamp(12px, 2.5vw, 24px);
|
||||
height: clamp(12px, 2.5vw, 24px);
|
||||
border-radius: 50%;
|
||||
border: 2px solid #815B37;
|
||||
background: rgba(129, 91, 55, 0.2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: clamp(0.625rem, 1vw, 0.75rem);
|
||||
font-size: clamp(0.5rem, 1vw, 0.75rem);
|
||||
color: #f5bbab;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
flex-shrink: 1; /* PC端允许周圈缩小以适应容器 */
|
||||
min-width: 10px; /* 最小尺寸 */
|
||||
min-height: 10px;
|
||||
flex-basis: clamp(12px, 2.5vw, 24px); /* 弹性基础尺寸 */
|
||||
}
|
||||
|
||||
.week-circle:hover {
|
||||
@@ -505,6 +490,21 @@ body {
|
||||
font-size: clamp(0.875rem, 2.5vw, 1.125rem);
|
||||
}
|
||||
|
||||
/* 移动端年度进度允许换行 */
|
||||
.countdown {
|
||||
overflow: visible; /* 移动端允许溢出换行 */
|
||||
}
|
||||
|
||||
.quarter-item {
|
||||
overflow: visible; /* 移动端允许内容换行 */
|
||||
}
|
||||
|
||||
.weeks-container {
|
||||
flex-wrap: wrap; /* 移动端允许换行 */
|
||||
overflow-x: visible;
|
||||
overflow-y: visible;
|
||||
}
|
||||
|
||||
.quarter-label {
|
||||
font-size: clamp(0.75rem, 2vw, 0.875rem);
|
||||
padding: clamp(2px, 0.5vw, 4px) clamp(6px, 1.5vw, 10px);
|
||||
@@ -515,6 +515,10 @@ body {
|
||||
width: clamp(16px, 4vw, 20px);
|
||||
height: clamp(16px, 4vw, 20px);
|
||||
font-size: clamp(0.5rem, 1.5vw, 0.625rem);
|
||||
flex-shrink: 0; /* 移动端不缩小 */
|
||||
min-width: auto;
|
||||
min-height: auto;
|
||||
flex-basis: auto;
|
||||
}
|
||||
|
||||
.ranking-item {
|
||||
@@ -574,6 +578,14 @@ body {
|
||||
|
||||
/* 超小屏幕优化 (480px及以下) */
|
||||
@media (max-width: 480px) {
|
||||
.header h1 {
|
||||
font-size: clamp(1.25rem, 5vw, 1.5rem);
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: clamp(0.75rem, 3vw, 1rem);
|
||||
}
|
||||
|
||||
.quarters-container {
|
||||
gap: clamp(5px, 2vw, 8px);
|
||||
}
|
||||
@@ -592,6 +604,274 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
/* 笔记本小屏幕特别优化 (1024px - 1366px) */
|
||||
@media screen and (min-width: 1024px) and (max-width: 1366px) {
|
||||
/* 年度进度区域紧凑显示 */
|
||||
.countdown {
|
||||
padding: clamp(6px, 1vw, 8px);
|
||||
}
|
||||
|
||||
.countdown h3 {
|
||||
font-size: clamp(0.7rem, 1vw, 0.9rem);
|
||||
margin-bottom: clamp(6px, 1vw, 10px);
|
||||
}
|
||||
|
||||
.quarters-container {
|
||||
gap: clamp(3px, 0.8vw, 4px);
|
||||
}
|
||||
|
||||
.quarter-item {
|
||||
gap: clamp(3px, 0.6vw, 5px);
|
||||
padding-bottom: clamp(5px, 0.7vw, 6px);
|
||||
}
|
||||
|
||||
.quarter-label {
|
||||
font-size: clamp(0.7rem, 1vw, 0.85rem);
|
||||
padding: clamp(2px, 0.3vw, 3px) clamp(5px, 0.8vw, 8px);
|
||||
min-width: clamp(28px, 4vw, 35px);
|
||||
}
|
||||
|
||||
.week-circle {
|
||||
width: clamp(14px, 2vw, 18px);
|
||||
height: clamp(14px, 2vw, 18px);
|
||||
font-size: clamp(0.5rem, 0.8vw, 0.625rem);
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.weeks-container {
|
||||
gap: clamp(1px, 0.3vw, 3px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 高分辨率小笔记本优化 (125%-200% DPI缩放) */
|
||||
@media screen and (min-width: 1280px) and (max-width: 1600px) {
|
||||
.countdown {
|
||||
padding: clamp(6px, 1vw, 10px);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.countdown h3 {
|
||||
font-size: clamp(0.75rem, 1.1vw, 1.1rem);
|
||||
margin-bottom: clamp(8px, 1.2vw, 12px);
|
||||
}
|
||||
|
||||
.quarters-container {
|
||||
gap: clamp(4px, 0.9vw, 6px);
|
||||
}
|
||||
|
||||
.quarter-item {
|
||||
gap: clamp(4px, 0.7vw, 7px);
|
||||
padding-bottom: clamp(6px, 0.8vw, 8px);
|
||||
}
|
||||
|
||||
.quarter-label {
|
||||
font-size: clamp(0.72rem, 1.05vw, 0.9rem);
|
||||
padding: clamp(2px, 0.35vw, 4px) clamp(6px, 0.85vw, 10px);
|
||||
min-width: clamp(30px, 4.2vw, 38px);
|
||||
}
|
||||
|
||||
.week-circle {
|
||||
width: clamp(15px, 2.1vw, 20px);
|
||||
height: clamp(15px, 2.1vw, 20px);
|
||||
font-size: clamp(0.5rem, 0.85vw, 0.65rem);
|
||||
border-width: 1.5px;
|
||||
}
|
||||
|
||||
.weeks-container {
|
||||
gap: clamp(2px, 0.35vw, 4px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 极致紧凑模式 - 高DPI小屏幕 */
|
||||
@media screen and (min-width: 1200px) and (max-width: 1440px) and (min-aspect-ratio: 16/10) {
|
||||
.countdown {
|
||||
padding: clamp(5px, 0.9vw, 8px);
|
||||
}
|
||||
|
||||
.countdown h3 {
|
||||
font-size: clamp(0.7rem, 1vw, 0.95rem);
|
||||
margin-bottom: clamp(6px, 1vw, 10px);
|
||||
}
|
||||
|
||||
.quarters-container {
|
||||
gap: clamp(3px, 0.7vw, 5px);
|
||||
}
|
||||
|
||||
.quarter-item {
|
||||
gap: clamp(3px, 0.6vw, 6px);
|
||||
padding-bottom: clamp(5px, 0.7vw, 7px);
|
||||
}
|
||||
|
||||
.quarter-label {
|
||||
font-size: clamp(0.68rem, 1vw, 0.82rem);
|
||||
padding: clamp(2px, 0.3vw, 4px) clamp(5px, 0.8vw, 9px);
|
||||
min-width: clamp(28px, 4vw, 35px);
|
||||
}
|
||||
|
||||
.week-circle {
|
||||
width: clamp(14px, 2vw, 18px);
|
||||
height: clamp(14px, 2vw, 18px);
|
||||
font-size: clamp(0.48rem, 0.8vw, 0.6rem);
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.weeks-container {
|
||||
gap: clamp(1.5px, 0.3vw, 3px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 窄容器优化 - 适配所有高DPI小笔记本 */
|
||||
@media screen and (min-width: 769px) and (max-width: 1400px) {
|
||||
.top-data {
|
||||
grid-template-columns: 2fr minmax(280px, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* 极窄容器优化 */
|
||||
@media screen and (min-width: 769px) and (max-width: 1200px) {
|
||||
.top-data {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== 响应式布局 - 不同屏幕尺寸优化 ========== */
|
||||
|
||||
/* 小屏幕笔记本 (1366x768, 1440x900) */
|
||||
@media screen and (min-width: 769px) and (max-width: 1600px) {
|
||||
.cont {
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: clamp(0.8rem, 1.1vw, 0.95rem);
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
min-height: clamp(220px, 28vw, 280px);
|
||||
}
|
||||
|
||||
/* 年度进度区域优化 */
|
||||
.countdown {
|
||||
padding: clamp(6px, 1vw, 10px);
|
||||
}
|
||||
|
||||
.countdown h3 {
|
||||
font-size: clamp(0.75rem, 1.1vw, 1.1rem);
|
||||
margin-bottom: clamp(8px, 1.2vw, 12px);
|
||||
}
|
||||
|
||||
.quarter-item {
|
||||
gap: clamp(4px, 0.8vw, 6px);
|
||||
padding-bottom: clamp(6px, 0.8vw, 8px);
|
||||
}
|
||||
|
||||
.quarter-label {
|
||||
font-size: clamp(0.75rem, 1.1vw, 0.95rem);
|
||||
padding: clamp(2px, 0.4vw, 4px) clamp(6px, 0.9vw, 10px);
|
||||
min-width: clamp(32px, 4.5vw, 40px);
|
||||
}
|
||||
|
||||
.week-circle {
|
||||
width: clamp(16px, 2.2vw, 22px);
|
||||
height: clamp(16px, 2.2vw, 22px);
|
||||
font-size: clamp(0.5625rem, 0.9vw, 0.6875rem);
|
||||
border-width: 1.5px;
|
||||
}
|
||||
|
||||
.weeks-container {
|
||||
gap: clamp(2px, 0.4vw, 4px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 标准全高清 (1920x1080) */
|
||||
@media screen and (min-width: 1601px) and (max-width: 2100px) {
|
||||
.cont {
|
||||
width: 90%;
|
||||
max-width: 1700px;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
min-height: clamp(250px, 28vw, 320px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 2K 屏幕 (2560x1440) */
|
||||
@media screen and (min-width: 2101px) and (max-width: 3200px) {
|
||||
.cont {
|
||||
width: 85%;
|
||||
max-width: 2200px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: clamp(0.9rem, 1.2vw, 1.1rem);
|
||||
padding: clamp(6px, 0.9vw, 8px) clamp(12px, 1.6vw, 18px);
|
||||
}
|
||||
|
||||
.top-data {
|
||||
gap: clamp(10px, 1.5vw, 18px);
|
||||
margin-bottom: clamp(15px, 1.8vw, 25px);
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
min-height: clamp(280px, 28vw, 380px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 4K 屏幕 (3840x2160 及以上) */
|
||||
@media screen and (min-width: 3201px) {
|
||||
.cont {
|
||||
width: 80%;
|
||||
max-width: 3000px;
|
||||
padding: clamp(1.5rem, 2vw, 3rem);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: clamp(1rem, 1.5vw, 1.3rem);
|
||||
padding: clamp(8px, 1vw, 10px) clamp(15px, 2vw, 25px);
|
||||
}
|
||||
|
||||
.top-data {
|
||||
gap: clamp(15px, 2vw, 25px);
|
||||
margin-bottom: clamp(20px, 2.5vw, 30px);
|
||||
}
|
||||
|
||||
.sales-info {
|
||||
padding: clamp(15px, 2vw, 25px);
|
||||
gap: clamp(15px, 2vw, 20px);
|
||||
}
|
||||
|
||||
.countdown {
|
||||
padding: clamp(12px, 1.8vw, 15px);
|
||||
}
|
||||
|
||||
.group-ranking,
|
||||
.person-ranking {
|
||||
padding: clamp(15px, 2vw, 30px);
|
||||
margin-bottom: clamp(15px, 2vw, 25px);
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
min-height: clamp(350px, 30vw, 500px);
|
||||
}
|
||||
|
||||
.ranking-item {
|
||||
padding: clamp(12px, 1.5vw, 18px) clamp(15px, 2vw, 22px);
|
||||
font-size: clamp(0.85rem, 1.2vw, 1rem);
|
||||
}
|
||||
|
||||
.rank-number {
|
||||
width: clamp(22px, 3.5vw, 28px);
|
||||
height: clamp(22px, 3.5vw, 28px);
|
||||
}
|
||||
|
||||
/* 4K屏幕下的文字大小优化 */
|
||||
.target-amount,
|
||||
.total-amount {
|
||||
font-size: clamp(1.5rem, 3vw, 2.5rem);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* 头部和尾部图片 */
|
||||
.header-image {
|
||||
margin-bottom: 20px;
|
||||
|
||||
Reference in New Issue
Block a user