修复定位功能封禁
This commit is contained in:
parent
d832cfa05d
commit
01b824233c
736
pages/DigitalIntelligenceDashboard/components/AIAnalysis.vue
Normal file
736
pages/DigitalIntelligenceDashboard/components/AIAnalysis.vue
Normal file
@ -0,0 +1,736 @@
|
||||
<template>
|
||||
<view class="ai-analysis-container">
|
||||
<!-- AI分析按钮 -->
|
||||
<view class="ai-analysis-btn" @click="toggleAnalysis" :class="{ active: showAnalysis }">
|
||||
<text class="ai-btn-text">AI分析</text>
|
||||
<view class="ai-btn-icon" v-if="!showAnalysis">
|
||||
<text class="icon-text">🤖</text>
|
||||
</view>
|
||||
<view class="ai-btn-close" v-else>
|
||||
<text class="close-text">✕</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- AI分析悬浮框 -->
|
||||
<view class="ai-analysis-modal" v-if="showAnalysis" @click.stop>
|
||||
<!-- 悬浮框头部 -->
|
||||
<view class="modal-header">
|
||||
<view class="modal-title">
|
||||
<text class="title-text">🤖 AI智能分析</text>
|
||||
</view>
|
||||
<view class="close-btn" @click="closeAnalysis">
|
||||
<text class="close-icon">✕</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 悬浮框内容 -->
|
||||
<view class="modal-content">
|
||||
<!-- 加载动画 -->
|
||||
<view class="loading-container" v-if="isLoading">
|
||||
<view class="loading-dots">
|
||||
<view class="dot" v-for="n in 3" :key="n"></view>
|
||||
</view>
|
||||
<text class="loading-text">AI分析中</text>
|
||||
<view class="loading-progress">
|
||||
<view class="progress-bar" :style="{ width: loadingProgress + '%' }"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分析结果 - 打字机效果 -->
|
||||
<view class="analysis-result" v-if="!isLoading && analysisResult">
|
||||
<view class="result-text-container" ref="resultContainer">
|
||||
<text class="result-text">{{ displayText }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 滚动指示器 -->
|
||||
<view class="scroll-indicator" v-if="showScrollIndicator">
|
||||
<text class="scroll-text">↓ 继续滚动查看更多</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 遮罩层 -->
|
||||
<view class="modal-mask" v-if="showAnalysis" @click="closeAnalysis"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AIAnalysis',
|
||||
props: {
|
||||
// 图表容器信息
|
||||
chartInfo: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
width: 0,
|
||||
height: 0
|
||||
})
|
||||
},
|
||||
questionText: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
questionData: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showAnalysis: false,
|
||||
isLoading: false,
|
||||
analysisResult: '',
|
||||
displayText: '',
|
||||
loadingProgress: 0,
|
||||
showScrollIndicator: false,
|
||||
typingTimer: null,
|
||||
loadingTimer: null,
|
||||
scrollCheckTimer: null,
|
||||
originalPageStyle: '' // 保存原始页面样式
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.clearTimers()
|
||||
// 确保页面滚动功能恢复
|
||||
if (this.showAnalysis) {
|
||||
this.enablePageScroll()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 切换分析显示
|
||||
toggleAnalysis() {
|
||||
if (this.showAnalysis) {
|
||||
this.closeAnalysis()
|
||||
} else {
|
||||
this.openAnalysis()
|
||||
}
|
||||
},
|
||||
|
||||
// 打开分析
|
||||
openAnalysis() {
|
||||
this.showAnalysis = true
|
||||
this.disablePageScroll()
|
||||
this.startAnalysis()
|
||||
},
|
||||
|
||||
// 关闭分析
|
||||
closeAnalysis() {
|
||||
this.showAnalysis = false
|
||||
this.enablePageScroll()
|
||||
this.clearTimers()
|
||||
// 重置状态
|
||||
setTimeout(() => {
|
||||
this.isLoading = false
|
||||
this.analysisResult = ''
|
||||
this.displayText = ''
|
||||
this.loadingProgress = 0
|
||||
this.showScrollIndicator = false
|
||||
}, 300)
|
||||
},
|
||||
|
||||
// 禁用页面滚动
|
||||
disablePageScroll() {
|
||||
// 通知父组件禁用滚动
|
||||
this.$emit('disableScroll')
|
||||
},
|
||||
|
||||
// 启用页面滚动
|
||||
enablePageScroll() {
|
||||
// 通知父组件启用滚动
|
||||
this.$emit('enableScroll')
|
||||
},
|
||||
|
||||
// 开始分析
|
||||
async startAnalysis() {
|
||||
this.isLoading = true
|
||||
this.loadingProgress = 0
|
||||
|
||||
// 模拟加载进度
|
||||
this.simulateLoadingProgress()
|
||||
|
||||
try {
|
||||
// 固定3秒加载时间
|
||||
await new Promise(resolve => setTimeout(resolve, 3000))
|
||||
|
||||
// 直接使用模拟数据
|
||||
const result = this.generateMockAnalysis()
|
||||
|
||||
// 分析完成,开始打字机效果
|
||||
this.analysisComplete(result)
|
||||
|
||||
} catch (error) {
|
||||
console.error('AI分析失败:', error)
|
||||
this.analysisComplete('抱歉,AI分析过程中出现了问题,请稍后重试。')
|
||||
}
|
||||
},
|
||||
|
||||
// 模拟加载进度
|
||||
simulateLoadingProgress() {
|
||||
this.loadingTimer = setInterval(() => {
|
||||
if (this.loadingProgress < 90) {
|
||||
this.loadingProgress += Math.random() * 15
|
||||
} else {
|
||||
clearInterval(this.loadingTimer)
|
||||
}
|
||||
}, 200)
|
||||
},
|
||||
|
||||
// 生成模拟分析结果
|
||||
async generateMockAnalysis() {
|
||||
const req = {
|
||||
model: "qwen-plus",
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: `#云南大屏直接进行路由角色说明解析#<Br/>
|
||||
#路由角色解析#<Br/>
|
||||
云南高速·智慧大屏数据洞察官 Prompt · V3(决策层友好版)
|
||||
你是“云南高速服务区智慧大屏”的数据洞察官,服务对象是集团高层管理者。你的职责是通过实时和历史结构化数据,提供有深度、节奏感、判断力、正负并重的运营播报,支持科学决策、运营预判与策略部署。
|
||||
【一、输出结构要求】(保留七段结构,优化语气和排序)
|
||||
每次播报需包含以下内容,建议1200字以内,语言自然流畅、避免堆砌数字:
|
||||
今日亮点速览
|
||||
优先输出关键经营表现中的积极变化、业务高点、优秀片区、数据新突破,树立正向感知。
|
||||
波动与关注项
|
||||
概括今日出现的波动或风险行为,简明归因,不夸张渲染,强调“可控+建议”。
|
||||
月度趋势观察
|
||||
判断当前月是否延续预期节奏,有无提前透支或滞后,指出需持续关注的变化项。
|
||||
历史节奏对标
|
||||
结合去年同期与上一季度,判断当前处于哪种阶段(爬坡、收缩、冲刺等),支持节奏判断。
|
||||
策略建议精要
|
||||
给出少而精、能落地的策略建议,适度点出片区/业务优先级,避免泛泛空话。
|
||||
关键节点提醒
|
||||
提前预警即将到来的节假日、季度切换或特殊事件,并建议提前部署内容。
|
||||
运营协同建议
|
||||
强调系统保障、权限设置、数据补录、行为闭环等底层机制,保障整体运行质量。
|
||||
【二、语气与风格要求】
|
||||
基调务实积极:正面 + 中性 + 预警并重,杜绝全是问题或消极分析;
|
||||
语言自然有温度:像是一个懂业务、有判断力的“懂行人”在向高层“说话”;
|
||||
判断优于数据堆砌:数据仅作为支撑,应以“趋势/节奏/建议”为主角;
|
||||
避免模板腔:不使用模板化句式如“建议加强…”,鼓励定制化表达;
|
||||
视角结构化:从“数据→发现→判断→建议”建立因果链条;
|
||||
【三、字段兼容(自动适配缺失)】
|
||||
你可以处理的数据字段包括但不限于:
|
||||
实时类:今日车流、营收、加水、油品、充电、门店收入等;
|
||||
趋势类:月度营收、季度效益、区域表现;
|
||||
异常类:特情行为、抽查异常、交易波动;
|
||||
客群类:年龄性别结构、消费偏好、消费时段、品牌偏好;
|
||||
结构类:片区营收占比、业态结构占比、节假日结构等;
|
||||
缺失字段时请智能跳过,整体不出错。
|
||||
【四、命令支持】
|
||||
用户提问 你要执行的任务
|
||||
“请生成今日运营播报” 启动七段式“数据洞察型运营简报”输出
|
||||
“请简明汇报今日情况” 输出带正向亮点的简要摘要
|
||||
“聚焦滇中片区” 输出滇中片区的趋势、亮点、风险、建议
|
||||
“暑期节奏提醒” 输出未来高峰节点 + 重点片区策略部署
|
||||
【启动规则】
|
||||
一旦接收到结构化数据,立即进入“数据洞察官”角色,生成自然语言播报内容。字段缺失不报错,内容必须整体通顺、具判断力、可执行。<Br/>
|
||||
#用户提问#<Br/>
|
||||
${this.questionText}
|
||||
<Br/>
|
||||
#接口数据说明#<Br/>
|
||||
${this.questionData}`
|
||||
}
|
||||
],
|
||||
max_tokens: 4096,
|
||||
stop: [null],
|
||||
temperatur: 0.4,
|
||||
top_p: 0.4,
|
||||
top_k: 40,
|
||||
frequency_penalty: 0,
|
||||
n: 1,
|
||||
response_format: { type: "text" }
|
||||
}
|
||||
const options = {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: "Bearer sk-1aa2c1c672034c6d826ce62d3aebcb47",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(req),
|
||||
// signal: controller.signal, // 关键:绑定控制器信号
|
||||
};
|
||||
const response = await fetch(
|
||||
"https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
|
||||
options
|
||||
);
|
||||
// 创建流式读取器
|
||||
const reader = response.body.getReader();
|
||||
console.log('readerreaderreaderreader', reader);
|
||||
|
||||
|
||||
|
||||
return `🚗 断面流量智能分析报告
|
||||
|
||||
📊 数据概览
|
||||
断面流量图表分析,当前数据展示了近12个月的交通流量变化趋势。图表采用了柱状图对比形式,直观展现了不同时期的流量差异。
|
||||
|
||||
🔍 关键发现
|
||||
• 流量分布相对均匀,月度波动幅度在合理范围内
|
||||
• 从图表可以看出,上半年流量略高于下半年
|
||||
• 年度整体流量呈现稳定态势,无异常峰值
|
||||
• 同比数据分析显示,交通需求保持稳定增长
|
||||
|
||||
📈 趋势分析
|
||||
• 稳定性:流量数据表现出良好的稳定性,说明服务区运营状况良好
|
||||
• 季节性:呈现一定的季节性特征,符合交通流量的普遍规律
|
||||
• 增长性:整体趋势稳中有升,反映了区域经济的发展活力
|
||||
|
||||
💡 运营建议
|
||||
1. 维持当前的服务标准和运营策略
|
||||
2. 在流量高峰期适当增加服务人员配置
|
||||
3. 持续优化服务区的交通组织和管理
|
||||
4. 建议在低峰期进行设施维护和升级工作
|
||||
|
||||
⚠️ 风险提示
|
||||
• 需要关注节假日等特殊时期的流量激增
|
||||
• 建议建立完善的应急预案和疏导机制
|
||||
• 持续监测流量变化,及时发现异常情况
|
||||
|
||||
📅 数据质量评估
|
||||
• 数据完整性:良好,覆盖完整的统计周期
|
||||
• 数据准确性:高,符合实际的交通流量特征
|
||||
• 数据时效性:实时更新,满足运营决策需求
|
||||
|
||||
这份分析基于当前断面流量图表数据生成,为服务区的运营管理提供了数据支撑和决策参考。通过智能化的数据分析,能够帮助管理人员更好地理解流量变化规律,制定科学的运营策略。`
|
||||
},
|
||||
|
||||
// 分析完成
|
||||
analysisComplete(result) {
|
||||
this.isLoading = false
|
||||
this.analysisResult = result
|
||||
this.loadingProgress = 100
|
||||
|
||||
setTimeout(() => {
|
||||
this.startTypingEffect()
|
||||
}, 500)
|
||||
},
|
||||
|
||||
// 开始打字机效果
|
||||
startTypingEffect() {
|
||||
this.displayText = ''
|
||||
let currentIndex = 0
|
||||
|
||||
this.typingTimer = setInterval(() => {
|
||||
if (currentIndex < this.analysisResult.length) {
|
||||
this.displayText += this.analysisResult[currentIndex]
|
||||
currentIndex++
|
||||
|
||||
// 检查是否需要滚动
|
||||
this.checkScrollNeed()
|
||||
} else {
|
||||
clearInterval(this.typingTimer)
|
||||
this.typingTimer = null
|
||||
}
|
||||
}, 30) // 每30ms显示一个字符
|
||||
},
|
||||
|
||||
// 检查是否需要滚动
|
||||
checkScrollNeed() {
|
||||
// 延迟检查,确保DOM更新完成
|
||||
this.$nextTick(() => {
|
||||
const query = uni.createSelectorQuery().in(this)
|
||||
query.select('.result-text-container').boundingClientRect()
|
||||
query.select('.result-text').boundingClientRect()
|
||||
query.exec((res) => {
|
||||
if (res[0] && res[1]) {
|
||||
this.showScrollIndicator = res[1].height > res[0].height
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 获取状态文本
|
||||
getStatusText() {
|
||||
if (this.isLoading) return '分析中...'
|
||||
if (this.analysisResult) return '分析完成'
|
||||
return '准备分析'
|
||||
},
|
||||
|
||||
// 清除定时器
|
||||
clearTimers() {
|
||||
if (this.typingTimer) {
|
||||
clearInterval(this.typingTimer)
|
||||
this.typingTimer = null
|
||||
}
|
||||
if (this.loadingTimer) {
|
||||
clearInterval(this.loadingTimer)
|
||||
this.loadingTimer = null
|
||||
}
|
||||
if (this.scrollCheckTimer) {
|
||||
clearTimeout(this.scrollCheckTimer)
|
||||
this.scrollCheckTimer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@primary-color: #667eea;
|
||||
@secondary-color: #764ba2;
|
||||
@success-color: #52c41a;
|
||||
@text-primary: #333;
|
||||
@text-secondary: #666;
|
||||
@bg-white: #ffffff;
|
||||
@border-radius: 12rpx;
|
||||
@shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
|
||||
|
||||
.ai-analysis-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* AI分析按钮 */
|
||||
.ai-analysis-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12rpx 20rpx;
|
||||
background: linear-gradient(135deg, @primary-color, @secondary-color);
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4px 16px rgba(102, 126, 234, 0.3);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||
transition: left 0.6s;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.4);
|
||||
|
||||
&::before {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: linear-gradient(135deg, @success-color, #389e0d);
|
||||
box-shadow: 0 4px 16px rgba(82, 196, 26, 0.3);
|
||||
}
|
||||
|
||||
.ai-btn-text {
|
||||
font-size: 24rpx;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.ai-btn-icon,
|
||||
.ai-btn-close {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
|
||||
.icon-text,
|
||||
.close-text {
|
||||
font-size: 20rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 悬浮框 */
|
||||
.ai-analysis-modal {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 640rpx;
|
||||
max-width: 90vw;
|
||||
max-height: 80vh;
|
||||
background: @bg-white;
|
||||
border-radius: 24rpx;
|
||||
box-shadow: @shadow;
|
||||
z-index: 1001;
|
||||
/* 高于右侧导航栏的1000 */
|
||||
overflow: hidden;
|
||||
animation: modalFadeIn 0.3s ease-out;
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx;
|
||||
background: linear-gradient(135deg, @primary-color, @secondary-color);
|
||||
color: white;
|
||||
|
||||
.modal-title {
|
||||
.title-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.9);
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
font-size: 28rpx;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
padding: 32rpx;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
|
||||
/* 隐藏滚动条但保持滚动功能 */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 图表信息 */
|
||||
.chart-info {
|
||||
margin-bottom: 32rpx;
|
||||
padding: 24rpx;
|
||||
background: #f8f9fb;
|
||||
border-radius: 16rpx;
|
||||
border-left: 4rpx solid @primary-color;
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 26rpx;
|
||||
color: @text-secondary;
|
||||
margin-right: 16rpx;
|
||||
min-width: 120rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 26rpx;
|
||||
color: @text-primary;
|
||||
font-weight: 500;
|
||||
|
||||
&.loading {
|
||||
color: @primary-color;
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
&.completed {
|
||||
color: @success-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 48rpx 0;
|
||||
|
||||
.loading-dots {
|
||||
display: flex;
|
||||
gap: 8rpx;
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
.dot {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
background: @primary-color;
|
||||
animation: dotBounce 1.4s infinite ease-in-out both;
|
||||
|
||||
&:nth-child(1) {
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
animation-delay: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: @primary-color;
|
||||
font-weight: 500;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.loading-progress {
|
||||
width: 200rpx;
|
||||
height: 6rpx;
|
||||
background: #f0f0f0;
|
||||
border-radius: 3rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, @primary-color, @secondary-color);
|
||||
border-radius: 3rpx;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 分析结果 */
|
||||
.analysis-result {
|
||||
position: relative;
|
||||
|
||||
.result-text-container {
|
||||
max-height: 400rpx;
|
||||
overflow-y: auto;
|
||||
padding: 24rpx;
|
||||
background: #f8f9fb;
|
||||
border-radius: 16rpx;
|
||||
border: 1rpx solid #e8e8e8;
|
||||
|
||||
/* 隐藏滚动条 */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.result-text {
|
||||
font-size: 28rpx;
|
||||
line-height: 1.8;
|
||||
color: @text-primary;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-indicator {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 16rpx;
|
||||
animation: bounce 2s infinite;
|
||||
|
||||
.scroll-text {
|
||||
font-size: 24rpx;
|
||||
color: @text-secondary;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 遮罩层 */
|
||||
.modal-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1000;
|
||||
/* 高于右侧导航栏的1000,但低于悬浮框 */
|
||||
animation: maskFadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
/* 动画 */
|
||||
@keyframes modalFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -45%);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes maskFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dotBounce {
|
||||
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
40% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
|
||||
0%,
|
||||
20%,
|
||||
50%,
|
||||
80%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
40% {
|
||||
transform: translateY(-10rpx);
|
||||
}
|
||||
|
||||
60% {
|
||||
transform: translateY(-5rpx);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -3,10 +3,14 @@
|
||||
|
||||
<view class="chart-header">
|
||||
<text class="chart-title">断面流量</text>
|
||||
<!-- AI分析按钮 -->
|
||||
<view class="ai-analysis-wrapper">
|
||||
<AIAnalysis :chartInfo="chartInfo" @disableScroll="handleDisableScroll" @enableScroll="handleEnableScroll" />
|
||||
</view>
|
||||
</view>
|
||||
<!-- 断面流量趋势图 -->
|
||||
<view class="chart-card">
|
||||
<view class="chart-content">
|
||||
<view class="chart-content" id="traffic-chart">
|
||||
<view class="bar-chart-container">
|
||||
<!-- 图表加载效果 -->
|
||||
<ChartLoading v-if="isLoading" text="数据加载中..." />
|
||||
@ -23,13 +27,15 @@
|
||||
<script>
|
||||
import QiunDataCharts from './qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue'
|
||||
import ChartLoading from './ChartLoading.vue'
|
||||
import AIAnalysis from './AIAnalysis.vue'
|
||||
import request from "@/util/index.js";
|
||||
import moment from 'moment'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
QiunDataCharts,
|
||||
ChartLoading
|
||||
ChartLoading,
|
||||
AIAnalysis
|
||||
},
|
||||
|
||||
data() {
|
||||
@ -48,7 +54,13 @@ export default {
|
||||
// 统计数据
|
||||
currentYearTotal: 0,
|
||||
previousYearTotal: 0,
|
||||
averageGrowthRate: '0.00'
|
||||
averageGrowthRate: '0.00',
|
||||
|
||||
// AI分析相关
|
||||
chartInfo: {
|
||||
width: 0,
|
||||
height: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -129,6 +141,8 @@ export default {
|
||||
onReady() {
|
||||
// 获取数据
|
||||
this.handleGetTrafficFlowData()
|
||||
// 获取图表尺寸
|
||||
this.getChartDimensions()
|
||||
},
|
||||
|
||||
methods: {
|
||||
@ -263,6 +277,34 @@ export default {
|
||||
// 格式化数字
|
||||
formatNumber(num) {
|
||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
},
|
||||
|
||||
// 获取图表尺寸
|
||||
getChartDimensions() {
|
||||
this.$nextTick(() => {
|
||||
const query = uni.createSelectorQuery().in(this)
|
||||
query.select('#traffic-chart').boundingClientRect()
|
||||
query.exec((res) => {
|
||||
if (res[0]) {
|
||||
this.chartInfo = {
|
||||
width: Math.round(res[0].width),
|
||||
height: Math.round(res[0].height)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 处理禁用滚动
|
||||
handleDisableScroll() {
|
||||
// 通过uni全局事件通知主页面禁用滚动
|
||||
uni.$emit('disableScroll')
|
||||
},
|
||||
|
||||
// 处理启用滚动
|
||||
handleEnableScroll() {
|
||||
// 通过uni全局事件通知主页面启用滚动
|
||||
uni.$emit('enableScroll')
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -417,8 +459,10 @@ export default {
|
||||
|
||||
.chart-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
position: relative;
|
||||
|
||||
.chart-title {
|
||||
font-size: 30rpx;
|
||||
@ -431,6 +475,13 @@ export default {
|
||||
color: @text-light;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.ai-analysis-wrapper {
|
||||
position: absolute;
|
||||
top: -8rpx;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-card {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<page-meta :page-style="'overflow-x:hidden'"></page-meta>
|
||||
<page-meta :page-style="pageStyle"></page-meta>
|
||||
<scroll-view scroll-y @scroll="handleScroll" class="digital-dashboard" :scroll-into-view="scrollIntoView"
|
||||
:scroll-with-animation="true">
|
||||
:scroll-with-animation="true" :scroll-y="allowScroll">
|
||||
<!-- Tab切换区域 -->
|
||||
<scroll-view scroll-x class="tab-container" :scroll-with-animation="true" :scroll-left="tabScrollPosition"
|
||||
show-scrollbar="false">
|
||||
@ -222,6 +222,8 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
activeTab: 0,
|
||||
allowScroll: true, // 控制页面滚动
|
||||
pageStyle: 'overflow-x:hidden', // 页面样式
|
||||
tabList: [
|
||||
{ name: '运营中心', key: 'business' },
|
||||
{ name: '客群画像', key: 'customerProfile' },
|
||||
@ -285,7 +287,9 @@ export default {
|
||||
},
|
||||
onLoad() {
|
||||
this.fetchTabData(this.activeTab);
|
||||
|
||||
// 监听滚动控制事件
|
||||
uni.$on('disableScroll', this.handleDisableScroll);
|
||||
uni.$on('enableScroll', this.handleEnableScroll);
|
||||
},
|
||||
|
||||
onShow() {
|
||||
@ -342,6 +346,18 @@ export default {
|
||||
|
||||
|
||||
|
||||
// 处理禁用滚动
|
||||
handleDisableScroll() {
|
||||
this.allowScroll = false;
|
||||
this.pageStyle = 'overflow-x:hidden; overflow-y:hidden; position:fixed; width:100%; height:100%;';
|
||||
},
|
||||
|
||||
// 处理启用滚动
|
||||
handleEnableScroll() {
|
||||
this.allowScroll = true;
|
||||
this.pageStyle = 'overflow-x:hidden';
|
||||
},
|
||||
|
||||
// 获取各个tab的数据
|
||||
async fetchTabData(tabIndex) {
|
||||
// 切换Tab时重置活动导航项
|
||||
|
||||
@ -379,9 +379,10 @@ export default {
|
||||
isFirst: true,
|
||||
emergencyListLength: 0,
|
||||
dailyListLength: 0,
|
||||
seatInfo: {}
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
async onLoad() {
|
||||
this.menu = uni.getMenuButtonBoundingClientRect()
|
||||
let currentService = uni.getStorageSync('currentService')
|
||||
console.log('currentServicecurrentServicecurrentService', currentService);
|
||||
@ -394,8 +395,94 @@ export default {
|
||||
this.handleGetCurrentServiceAttendanceData(currentService.SAName)
|
||||
this.handleGetEventsData(currentService.SAName)
|
||||
} else {
|
||||
console.warn('onLoad: currentService 为空或无效')
|
||||
// 可以考虑跳转回服务区选择页面或显示错误提示
|
||||
|
||||
this.$util.toNextRoute("navigateTo", "/pages/map/index?type=attendanceStatistics");
|
||||
|
||||
|
||||
|
||||
// console.log('onLoad: currentService 为空或无效')
|
||||
// // 可以考虑跳转回服务区选择页面或显示错误提示
|
||||
// // 因为没有拿到最近的服务区 那么就去 拿定位 去请求最近的服务区
|
||||
// let seatInfo = uni.getStorageSync('seatInfo')
|
||||
|
||||
// if (seatInfo) {
|
||||
// this.seatInfo = JSON.parse(seatInfo)
|
||||
// let req = {
|
||||
// longitude: this.seatInfo.longitude,
|
||||
// Province_Code: '530000',
|
||||
// latitude: this.seatInfo.latitude,
|
||||
// PageIndex: 1,
|
||||
// PageSize: 1
|
||||
// }
|
||||
// const data = await request.$webGet('CommercialApi/BaseInfo/GetServerpartList', req)
|
||||
// let list = data.Result_Data.List
|
||||
// let currentService = list && list.length > 0 ? list[0] : {}
|
||||
// this.serviceInfo = {
|
||||
// ...currentService,
|
||||
// SAName: currentService.SERVERPART_NAME
|
||||
// }
|
||||
// this.handleGetCurrentServiceAttendanceData(this.serviceInfo.SAName)
|
||||
// this.handleGetEventsData(this.serviceInfo.SAName)
|
||||
// this.$forceUpdate()
|
||||
// } else {
|
||||
// let _this = this
|
||||
// uni.getLocation({
|
||||
// type: "gcj02",
|
||||
// altitude: true,
|
||||
// success: async (res) => {
|
||||
// let seatInfo = {
|
||||
// latitude: res.latitude,
|
||||
// longitude: res.longitude,
|
||||
// };
|
||||
// _this.seatInfo = seatInfo
|
||||
// uni.setStorageSync("seatInfo", JSON.stringify(seatInfo));
|
||||
|
||||
// let req = {
|
||||
// longitude: seatInfo.longitude,
|
||||
// Province_Code: '530000',
|
||||
// latitude: seatInfo.latitude,
|
||||
// PageIndex: 1,
|
||||
// PageSize: 1
|
||||
// }
|
||||
// const data = await request.$webGet('CommercialApi/BaseInfo/GetServerpartList', req)
|
||||
// let list = data.Result_Data.List
|
||||
// let currentService = list && list.length > 0 ? list[0] : {}
|
||||
// _this.serviceInfo = {
|
||||
// ...currentService,
|
||||
// SAName: currentService.SERVERPART_NAME
|
||||
// }
|
||||
// _this.handleGetCurrentServiceAttendanceData(_this.serviceInfo.SAName)
|
||||
// _this.handleGetEventsData(_this.serviceInfo.SAName)
|
||||
|
||||
// },
|
||||
// fail: async (err) => {
|
||||
// // 因为错误 所以默认给读书铺的位置
|
||||
// let seatInfo = {
|
||||
// latitude: 24.95152,
|
||||
// longitude: 102.553311,
|
||||
// };
|
||||
// _this.seatInfo = seatInfo
|
||||
// uni.setStorageSync("seatInfo", JSON.stringify(seatInfo));
|
||||
// let req = {
|
||||
// longitude: seatInfo.longitude,
|
||||
// Province_Code: '530000',
|
||||
// latitude: seatInfo.latitude,
|
||||
// PageIndex: 1,
|
||||
// PageSize: 1
|
||||
// }
|
||||
// const data = await request.$webGet('CommercialApi/BaseInfo/GetServerpartList', req)
|
||||
// let list = data.Result_Data.List
|
||||
// let currentService = list && list.length > 0 ? list[0] : {}
|
||||
// _this.serviceInfo = {
|
||||
// ...currentService,
|
||||
// SAName: currentService.SERVERPART_NAME
|
||||
// }
|
||||
// _this.handleGetCurrentServiceAttendanceData(_this.serviceInfo.SAName)
|
||||
// _this.handleGetEventsData(_this.serviceInfo.SAName)
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
@ -404,7 +491,7 @@ export default {
|
||||
|
||||
// 防护:检查 currentService 是否有效
|
||||
if (!currentService || (!currentService.SACode)) {
|
||||
console.warn('currentService 为空或无效,跳过更新');
|
||||
console.log('currentService 为空或无效,跳过更新');
|
||||
this.isFirst = false;
|
||||
return;
|
||||
}
|
||||
@ -482,6 +569,8 @@ export default {
|
||||
},
|
||||
// 拿到当前服务区的考勤数据
|
||||
async handleGetCurrentServiceAttendanceData(SERVERPART_NAME) {
|
||||
console.log('SERVERPART_NAMESERVERPART_NAME', SERVERPART_NAME);
|
||||
|
||||
// 防护:如果服务区名称为空,则不执行请求
|
||||
if (!SERVERPART_NAME) {
|
||||
return;
|
||||
|
||||
@ -354,21 +354,37 @@ export default {
|
||||
onLoad() {
|
||||
let _this = this
|
||||
this.menu = uni.getMenuButtonBoundingClientRect();
|
||||
uni.getLocation({
|
||||
type: "gcj02",
|
||||
altitude: true,
|
||||
success: (res) => {
|
||||
let seatInfo = {
|
||||
latitude: res.latitude,
|
||||
longitude: res.longitude,
|
||||
};
|
||||
uni.getSetting({
|
||||
success(res) {
|
||||
console.log('resresresres', res);
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
// uni.getLocation({
|
||||
// type: "gcj02",
|
||||
// altitude: true,
|
||||
// success: (res) => {
|
||||
// let seatInfo = {
|
||||
// latitude: res.latitude,
|
||||
// longitude: res.longitude,
|
||||
// };
|
||||
|
||||
// uni.setStorageSync("seatInfo", JSON.stringify(seatInfo));
|
||||
// _this.handleGetNearService(seatInfo.latitude, seatInfo.longitude)
|
||||
// },
|
||||
// fail: (err) => {
|
||||
// // 因为错误 所以默认给读书铺的位置
|
||||
// let seatInfo = {
|
||||
// latitude: 24.95152,
|
||||
// longitude: 102.553311,
|
||||
// };
|
||||
|
||||
// uni.setStorageSync("seatInfo", JSON.stringify(seatInfo));
|
||||
// _this.handleGetNearService(seatInfo.latitude, seatInfo.longitude)
|
||||
// },
|
||||
// });
|
||||
|
||||
uni.setStorageSync("seatInfo", JSON.stringify(seatInfo));
|
||||
_this.handleGetNearService(seatInfo.latitude, seatInfo.longitude)
|
||||
},
|
||||
fail: (err) => {
|
||||
},
|
||||
});
|
||||
this.single = timestampToTimeMonth(new Date(this.lastDay).getTime());
|
||||
uni.setStorageSync("lastDay", this.lastDay);
|
||||
|
||||
|
||||
@ -108,21 +108,51 @@ export default {
|
||||
this.menu = uni.getMenuButtonBoundingClientRect()
|
||||
|
||||
let seatInfo = uni.getStorageSync('seatInfo')
|
||||
if (seatInfo) {
|
||||
this.seatInfo = JSON.parse(seatInfo)
|
||||
this.longitude = this.seatInfo.longitude
|
||||
this.latitude = this.seatInfo.latitude
|
||||
}
|
||||
|
||||
if (option.chartType) {
|
||||
this.chartType = true
|
||||
}
|
||||
this.page = option.page
|
||||
this.type = option.type
|
||||
console.log('type', this.type)
|
||||
this.getListData()
|
||||
|
||||
if (seatInfo) {
|
||||
this.seatInfo = JSON.parse(seatInfo)
|
||||
this.longitude = this.seatInfo.longitude
|
||||
this.latitude = this.seatInfo.latitude
|
||||
|
||||
this.getListData()
|
||||
} else {
|
||||
let _this = this
|
||||
uni.getLocation({
|
||||
type: "gcj02",
|
||||
altitude: true,
|
||||
success: (res) => {
|
||||
let seatInfo = {
|
||||
latitude: res.latitude,
|
||||
longitude: res.longitude,
|
||||
};
|
||||
_this.seatInfo = seatInfo
|
||||
uni.setStorageSync("seatInfo", JSON.stringify(seatInfo));
|
||||
_this.getListData()
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log('errerrerr', err);
|
||||
|
||||
// 因为错误 所以默认给读书铺的位置
|
||||
let seatInfo = {
|
||||
latitude: 24.95152,
|
||||
longitude: 102.553311,
|
||||
};
|
||||
_this.seatInfo = seatInfo
|
||||
uni.setStorageSync("seatInfo", JSON.stringify(seatInfo));
|
||||
_this.getListData()
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
this.isShowMap = true
|
||||
|
||||
|
||||
// 地图初始化
|
||||
this.mapCtx = uni.createMapContext('myMap')
|
||||
},
|
||||
@ -431,17 +461,20 @@ export default {
|
||||
|
||||
// 这个是云南那边给来的数据 根据 云南的数据 把我们算的距离补给他们的数据里面
|
||||
let YNList = data.data
|
||||
|
||||
if (YNList && YNList.length > 0) {
|
||||
YNList.forEach((item, index) => {
|
||||
item.SERVERPART_DISTANCE = this.haversineDistance({ lat: this.seatInfo.latitude, lng: this.seatInfo.longitude }, { lat: item.latitude, lng: item.longitude })
|
||||
item.SERVERPART_DISTANCE = item.latitude && item.longitude ? this.haversineDistance({ lat: this.seatInfo.latitude, lng: this.seatInfo.longitude }, { lat: item.latitude, lng: item.longitude }) : "-"
|
||||
item.fileId = item.fileId ? JSON.parse(item.fileId) : []
|
||||
item.businessModel = item.businessModel ? JSON.parse(item.businessModel) : []
|
||||
// fileId: obj.fileId ? JSON.parse(obj.fileId) : [],
|
||||
// businessModel: obj.businessModel ? JSON.parse(obj.businessModel) : [],
|
||||
})
|
||||
uni.setStorageSync('YNList', YNList)
|
||||
|
||||
this.serviceList = YNList
|
||||
this.allServiceList = YNList
|
||||
// uni.setStorageSync('YNList', YNList)
|
||||
|
||||
}
|
||||
// let serverpartList = uni.getStorageSync('ServerpartList')
|
||||
// console.log('serverpartListserverpartListserverpartList', serverpartList);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user