Browse Source

修改 bug

master
王泽彦 8 months ago
parent
commit
aac0011daf
  1. 599
      niucloud/app/api/controller/Dashboard.php
  2. 3
      niucloud/app/api/route/route.php
  3. 2
      niucloud/app/service/api/apiService/ResourceSharingService.php
  4. 73
      uniapp/components/dashboard/DashboardLoading.vue
  5. 318
      uniapp/components/dashboard/DashboardWebView.vue
  6. 13
      uniapp/pages-market/clue/index.vue
  7. 9
      uniapp/pages.json
  8. 280
      uniapp/pages/common/dashboard/webview.vue
  9. 27
      uniapp/pages/common/home/index.vue

599
niucloud/app/api/controller/Dashboard.php

@ -0,0 +1,599 @@
<?php
namespace app\api\controller;
use app\service\api\member\MemberService;
use core\base\BaseApiController;
/**
* Dashboard WebView 控制器
*/
class Dashboard extends BaseApiController
{
/**
* WebView 页面渲染
*/
public function webview()
{
$type = $this->request->get('type', 'my_data'); // 页面类型
$token = $this->request->get('token', ''); // 用户token
$platform = $this->request->get('platform', 'web'); // 平台标识
// 验证token和获取用户信息
if (empty($token)) {
return $this->renderErrorPage('缺少用户认证信息');
}
try {
// 这里应该验证token,暂时跳过验证用于测试
$userInfo = $this->getMockUserInfo($type);
// 根据页面类型渲染不同内容
$htmlContent = $this->renderDashboardPage($type, $userInfo, $platform);
// 输出HTML内容
return response($htmlContent)->header([
'Content-Type' => 'text/html; charset=utf-8',
'Cache-Control' => 'no-cache, no-store, must-revalidate',
'Pragma' => 'no-cache',
'Expires' => '0'
]);
} catch (\Exception $e) {
return $this->renderErrorPage('页面加载失败: ' . $e->getMessage());
}
}
/**
* 渲染Dashboard页面
*/
private function renderDashboardPage($type, $userInfo, $platform)
{
// 获取页面数据
$pageData = $this->getPageData($type, $userInfo);
// 页面标题映射
$titleMap = [
'my_data' => '我的数据',
'dept_data' => '部门数据',
'campus_data' => '校区数据'
];
$pageTitle = $titleMap[$type] ?? '数据统计';
// 生成HTML内容
return $this->generateHTML($pageTitle, $pageData, $platform);
}
/**
* 获取页面数据
*/
private function getPageData($type, $userInfo)
{
switch ($type) {
case 'my_data':
return $this->getMyData($userInfo);
case 'dept_data':
return $this->getDeptData($userInfo);
case 'campus_data':
return $this->getCampusData($userInfo);
default:
return [];
}
}
/**
* 获取我的数据
*/
private function getMyData($userInfo)
{
return [
'stats' => [
['label' => '本月签约客户', 'value' => 12, 'unit' => '人', 'trend' => '+15%'],
['label' => '本月完成业绩', 'value' => 85000, 'unit' => '元', 'trend' => '+8%'],
['label' => '跟进客户数', 'value' => 45, 'unit' => '人', 'trend' => '+5%'],
['label' => '转化率', 'value' => 26.7, 'unit' => '%', 'trend' => '+2.3%']
],
'charts' => [
'monthly_trend' => [
'title' => '月度业绩趋势',
'data' => [65000, 72000, 68000, 75000, 82000, 85000]
],
'customer_source' => [
'title' => '客户来源分布',
'data' => [
['name' => '线上推广', 'value' => 35],
['name' => '转介绍', 'value' => 28],
['name' => '电话营销', 'value' => 22],
['name' => '其他', 'value' => 15]
]
]
]
];
}
/**
* 获取部门数据
*/
private function getDeptData($userInfo)
{
return [
'stats' => [
['label' => '部门总业绩', 'value' => 520000, 'unit' => '元', 'trend' => '+12%'],
['label' => '团队人数', 'value' => 8, 'unit' => '人', 'trend' => '0%'],
['label' => '平均业绩', 'value' => 65000, 'unit' => '元', 'trend' => '+12%'],
['label' => '部门排名', 'value' => 2, 'unit' => '名', 'trend' => '+1']
],
'charts' => [
'team_performance' => [
'title' => '团队成员业绩排行',
'data' => [
['name' => '张三', 'value' => 85000],
['name' => '李四', 'value' => 72000],
['name' => '王五', 'value' => 68000],
['name' => '赵六', 'value' => 65000]
]
]
]
];
}
/**
* 获取校区数据
*/
private function getCampusData($userInfo)
{
return [
'stats' => [
['label' => '校区总业绩', 'value' => 1200000, 'unit' => '元', 'trend' => '+18%'],
['label' => '部门数量', 'value' => 5, 'unit' => '个', 'trend' => '0%'],
['label' => '员工总数', 'value' => 32, 'unit' => '人', 'trend' => '+3'],
['label' => '客户总数', 'value' => 245, 'unit' => '人', 'trend' => '+25']
],
'charts' => [
'dept_performance' => [
'title' => '部门业绩对比',
'data' => [
['name' => '销售一部', 'value' => 320000],
['name' => '销售二部', 'value' => 280000],
['name' => '销售三部', 'value' => 260000],
['name' => '客服部', 'value' => 180000],
['name' => '行政部', 'value' => 160000]
]
]
]
];
}
/**
* 获取模拟用户信息
*/
private function getMockUserInfo($type)
{
return [
'id' => 1,
'name' => '测试员工',
'department' => '销售部',
'campus' => '总校区',
'role' => 'staff'
];
}
/**
* 生成HTML内容
*/
private function generateHTML($title, $data, $platform)
{
$statsHtml = '';
if (!empty($data['stats'])) {
foreach ($data['stats'] as $stat) {
$trendColor = strpos($stat['trend'], '+') === 0 ? '#4CAF50' : '#FF5722';
$statsHtml .= "
<div class='stat-card'>
<div class='stat-label'>{$stat['label']}</div>
<div class='stat-value'>{$stat['value']}<span class='stat-unit'>{$stat['unit']}</span></div>
<div class='stat-trend' style='color: {$trendColor}'>{$stat['trend']}</div>
</div>
";
}
}
// 生成图表数据的JavaScript
$chartsScript = '';
if (!empty($data['charts'])) {
foreach ($data['charts'] as $chartId => $chart) {
$chartData = json_encode($chart['data']);
$chartsScript .= "
renderChart('{$chartId}', '{$chart['title']}', {$chartData});
";
}
}
return "
<!DOCTYPE html>
<html lang='zh-CN'>
<head>
<meta charset='UTF-8'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>{$title}</title>
<script src='https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js'></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #181A20;
color: #fff;
padding: 0;
line-height: 1.6;
}
.container {
padding: 20px;
max-width: 100%;
}
.page-title {
text-align: center;
font-size: 24px;
font-weight: bold;
margin-bottom: 30px;
color: #29d3b4;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin-bottom: 30px;
}
.stat-card {
background: linear-gradient(135deg, #29d3b4 0%, #1a9b7c 100%);
border-radius: 12px;
padding: 20px;
text-align: center;
position: relative;
overflow: hidden;
}
.stat-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
pointer-events: none;
}
.stat-label {
font-size: 12px;
color: rgba(255, 255, 255, 0.8);
margin-bottom: 8px;
}
.stat-value {
font-size: 20px;
font-weight: bold;
color: #fff;
margin-bottom: 5px;
}
.stat-unit {
font-size: 12px;
font-weight: normal;
margin-left: 2px;
}
.stat-trend {
font-size: 12px;
font-weight: bold;
}
.chart-container {
background-color: #292929;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
}
.chart-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
color: #fff;
}
.chart {
height: 300px;
width: 100%;
}
.refresh-btn {
position: fixed;
bottom: 30px;
right: 30px;
width: 56px;
height: 56px;
background: linear-gradient(135deg, #29d3b4 0%, #1a9b7c 100%);
border-radius: 50%;
border: none;
color: #fff;
font-size: 24px;
cursor: pointer;
box-shadow: 0 4px 12px rgba(41, 211, 180, 0.3);
z-index: 1000;
}
.refresh-btn:active {
transform: scale(0.95);
}
.loading {
text-align: center;
padding: 40px;
color: #29d3b4;
}
@media (max-width: 480px) {
.stats-grid {
grid-template-columns: 1fr;
}
.stat-value {
font-size: 18px;
}
.chart {
height: 250px;
}
}
</style>
</head>
<body>
<div class='container'>
<h1 class='page-title'>{$title}</h1>
<div class='stats-grid'>
{$statsHtml}
</div>
<div id='charts-container'></div>
</div>
<button class='refresh-btn' onclick='refreshData()'></button>
<script>
// 图表渲染函数
function renderChart(chartId, title, data) {
const container = document.getElementById('charts-container');
const chartDiv = document.createElement('div');
chartDiv.className = 'chart-container';
chartDiv.innerHTML = `
<div class='chart-title'>\${title}</div>
<div class='chart' id='\${chartId}'></div>
`;
container.appendChild(chartDiv);
const chart = echarts.init(document.getElementById(chartId));
let option;
if (Array.isArray(data) && data[0] && typeof data[0] === 'object' && 'name' in data[0]) {
// 饼图或柱状图
if (chartId.includes('source') || chartId.includes('distribution')) {
// 饼图
option = {
backgroundColor: 'transparent',
textStyle: { color: '#fff' },
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(0,0,0,0.8)',
textStyle: { color: '#fff' }
},
legend: {
orient: 'horizontal',
bottom: '0%',
textStyle: { color: '#fff' }
},
series: [{
type: 'pie',
radius: '60%',
center: ['50%', '45%'],
data: data,
itemStyle: {
borderRadius: 5,
borderColor: '#181A20',
borderWidth: 2
},
label: {
color: '#fff'
}
}]
};
} else {
// 柱状图
option = {
backgroundColor: 'transparent',
textStyle: { color: '#fff' },
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0,0,0,0.8)',
textStyle: { color: '#fff' }
},
xAxis: {
type: 'category',
data: data.map(item => item.name),
axisLabel: { color: '#fff' },
axisLine: { lineStyle: { color: '#666' } }
},
yAxis: {
type: 'value',
axisLabel: { color: '#fff' },
axisLine: { lineStyle: { color: '#666' } },
splitLine: { lineStyle: { color: '#333' } }
},
series: [{
type: 'bar',
data: data.map(item => item.value),
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{offset: 0, color: '#29d3b4'},
{offset: 1, color: '#1a9b7c'}
]),
borderRadius: [4, 4, 0, 0]
}
}]
};
}
} else {
// 折线图
option = {
backgroundColor: 'transparent',
textStyle: { color: '#fff' },
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0,0,0,0.8)',
textStyle: { color: '#fff' }
},
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月'],
axisLabel: { color: '#fff' },
axisLine: { lineStyle: { color: '#666' } }
},
yAxis: {
type: 'value',
axisLabel: { color: '#fff' },
axisLine: { lineStyle: { color: '#666' } },
splitLine: { lineStyle: { color: '#333' } }
},
series: [{
type: 'line',
data: data,
smooth: true,
itemStyle: { color: '#29d3b4' },
lineStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{offset: 0, color: '#29d3b4'},
{offset: 1, color: '#1a9b7c'}
]),
width: 3
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{offset: 0, color: 'rgba(41, 211, 180, 0.3)'},
{offset: 1, color: 'rgba(41, 211, 180, 0.05)'}
])
}
}]
};
}
chart.setOption(option);
// 响应式
window.addEventListener('resize', () => {
chart.resize();
});
}
// 渲染所有图表
{$chartsScript}
// 刷新数据
function refreshData() {
// 发送消息给UniApp
if (typeof uni !== 'undefined' && uni.postMessage) {
uni.postMessage({
data: {
type: 'refresh'
}
});
} else {
// 页面刷新
location.reload();
}
}
// 页面加载完成后的处理
document.addEventListener('DOMContentLoaded', function() {
console.log('Dashboard页面加载完成');
// 通知UniApp页面加载完成
if (typeof uni !== 'undefined' && uni.postMessage) {
uni.postMessage({
data: {
type: 'loaded'
}
});
}
});
</script>
</body>
</html>";
}
/**
* 渲染错误页面
*/
private function renderErrorPage($message)
{
return response("
<!DOCTYPE html>
<html lang='zh-CN'>
<head>
<meta charset='UTF-8'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>页面错误</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #181A20;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
margin: 0;
padding: 20px;
}
.error-container {
text-align: center;
max-width: 400px;
}
.error-icon {
font-size: 48px;
color: #ff5722;
margin-bottom: 20px;
}
.error-title {
font-size: 24px;
font-weight: bold;
margin-bottom: 10px;
}
.error-message {
font-size: 14px;
color: rgba(255, 255, 255, 0.7);
line-height: 1.5;
}
</style>
</head>
<body>
<div class='error-container'>
<div class='error-icon'>⚠️</div>
<div class='error-title'>页面加载失败</div>
<div class='error-message'>{$message}</div>
</div>
</body>
</html>")->header([
'Content-Type' => 'text/html; charset=utf-8'
]);
}
}

3
niucloud/app/api/route/route.php

@ -185,6 +185,9 @@ Route::group(function () {
//获取微信小程序openid
Route::post('common/getMiniWxOpenId', 'apiController.Common/getMiniWxOpenId');
//Dashboard WebView 页面
Route::get('dashboard/webview', 'Dashboard/webview');
//公共端-获取全部课程列表
Route::get('common/getCourseAll', 'apiController.Common/getCourseAll');
//公共端-获取全部班级列表

2
niucloud/app/service/api/apiService/ResourceSharingService.php

@ -386,7 +386,7 @@ class ResourceSharingService extends BaseApiService
$item['customerResource']['second_visit_status'] = $visit_info[$resource_id]['second_visit_status'] ?? '未到';
// 添加开单状态
$item['customerResource']['order_status'] = $order_status[$resource_id] ?? '未开单';
$item['customerResource']['order_status'] = $order_status[$resource_id] ?? '未报名';
}
// 添加分配人员信息

73
uniapp/components/dashboard/DashboardLoading.vue

@ -0,0 +1,73 @@
<template>
<view class="dashboard-loading">
<view class="loading-content">
<view class="loading-icon">
<uni-icons type="spinner-cycle" size="32" color="#29d3b4" class="rotating"></uni-icons>
</view>
<text class="loading-text">{{ text }}</text>
<text v-if="subText" class="loading-subtext">{{ subText }}</text>
</view>
</view>
</template>
<script>
export default {
name: 'DashboardLoading',
props: {
text: {
type: String,
default: '数据加载中...'
},
subText: {
type: String,
default: ''
}
}
}
</script>
<style scoped>
.dashboard-loading {
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
background-color: #181A20;
}
.loading-content {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.loading-icon {
margin-bottom: 16px;
}
.rotating {
animation: rotate 1s linear infinite;
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.loading-text {
font-size: 16px;
color: #29d3b4;
margin-bottom: 8px;
}
.loading-subtext {
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
line-height: 1.4;
}
</style>

318
uniapp/components/dashboard/DashboardWebView.vue

@ -0,0 +1,318 @@
<template>
<view class="dashboard-webview">
<uni-nav-bar
:statusBar="true"
backgroundColor="#181A20"
color="#fff"
:title="pageTitle"
leftIcon="back"
@clickLeft="goBack"
/>
<!-- 加载状态 -->
<view v-if="isLoading" class="loading-container">
<uni-icons type="spinner-cycle" size="32" color="#29d3b4"></uni-icons>
<text class="loading-text">加载中...</text>
</view>
<!-- WebView容器 -->
<web-view
v-else
:src="webViewUrl"
@message="handleMessage"
@error="handleError"
@load="handleLoad"
class="webview-container"
></web-view>
<!-- 错误提示 -->
<view v-if="hasError" class="error-container">
<uni-icons type="close-filled" size="48" color="#ff5722"></uni-icons>
<text class="error-title">页面加载失败</text>
<text class="error-desc">{{ errorMessage }}</text>
<button class="retry-btn" @click="retry">重试</button>
</view>
</view>
</template>
<script>
export default {
name: 'DashboardWebView',
props: {
// WebViewmy_data, dept_data, campus_data
pageType: {
type: String,
required: true
},
//
title: {
type: String,
default: '数据统计'
}
},
data() {
return {
isLoading: true,
hasError: false,
errorMessage: '',
baseUrl: 'http://localhost:20080', // URL
userToken: '',
userInfo: {}
}
},
computed: {
pageTitle() {
const titleMap = {
'my_data': '我的数据',
'dept_data': '部门数据',
'campus_data': '校区数据'
};
return titleMap[this.pageType] || this.title;
},
webViewUrl() {
if (!this.userToken) {
return '';
}
// WebView URLtoken
const params = new URLSearchParams({
token: this.userToken,
type: this.pageType,
platform: 'uniapp'
});
return `${this.baseUrl}/api/dashboard/webview?${params.toString()}`;
}
},
onLoad() {
this.initWebView();
},
methods: {
async initWebView() {
try {
// token
await this.getUserInfo();
// URL
setTimeout(() => {
this.isLoading = false;
}, 500);
} catch (error) {
console.error('初始化WebView失败:', error);
this.showError('初始化失败');
}
},
getUserInfo() {
return new Promise((resolve, reject) => {
try {
//
const userInfo = uni.getStorageSync('userInfo');
const token = uni.getStorageSync('token');
if (!userInfo || !token) {
throw new Error('用户信息或token不存在');
}
this.userInfo = userInfo;
this.userToken = token;
resolve();
} catch (error) {
reject(error);
}
});
},
handleMessage(event) {
console.log('收到WebView消息:', event.detail.data);
const data = event.detail.data[0];
// WebView
switch (data.type) {
case 'navigate':
//
this.handleNavigate(data.url);
break;
case 'refresh':
//
this.refresh();
break;
case 'share':
//
this.handleShare(data.content);
break;
default:
console.log('未处理的消息类型:', data.type);
}
},
handleError(event) {
console.error('WebView加载错误:', event);
this.showError('页面加载失败,请检查网络连接');
},
handleLoad(event) {
console.log('WebView加载完成:', event);
this.isLoading = false;
this.hasError = false;
},
handleNavigate(url) {
// UniApp
if (url.startsWith('/pages')) {
// UniApp
uni.navigateTo({
url: url,
fail: (err) => {
console.error('页面跳转失败:', err);
uni.showToast({
title: '页面跳转失败',
icon: 'none'
});
}
});
} else {
// WebView
uni.showModal({
title: '提示',
content: '是否在浏览器中打开链接?',
success: (res) => {
if (res.confirm) {
uni.showToast({
title: '功能开发中',
icon: 'none'
});
}
}
});
}
},
handleShare(content) {
//
uni.share({
provider: "weixin",
scene: "WXSceneSession",
type: 0,
href: content.url || '',
title: content.title || this.pageTitle,
summary: content.summary || '数据统计分享',
imageUrl: content.image || '',
success: (res) => {
console.log('分享成功:', res);
uni.showToast({
title: '分享成功',
icon: 'success'
});
},
fail: (err) => {
console.error('分享失败:', err);
uni.showToast({
title: '分享失败',
icon: 'none'
});
}
});
},
refresh() {
//
this.isLoading = true;
this.hasError = false;
//
setTimeout(() => {
this.initWebView();
}, 300);
},
retry() {
//
this.hasError = false;
this.isLoading = true;
this.initWebView();
},
showError(message) {
this.isLoading = false;
this.hasError = true;
this.errorMessage = message;
},
goBack() {
uni.navigateBack({
delta: 1,
fail: () => {
//
uni.reLaunch({
url: '/pages/common/home/index'
});
}
});
}
}
}
</script>
<style scoped>
.dashboard-webview {
height: 100vh;
background-color: #181A20;
}
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: calc(100vh - 44px - var(--status-bar-height));
background-color: #181A20;
}
.loading-text {
margin-top: 16px;
font-size: 14px;
color: #29d3b4;
}
.webview-container {
height: calc(100vh - 44px - var(--status-bar-height));
}
.error-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: calc(100vh - 44px - var(--status-bar-height));
background-color: #181A20;
padding: 40px;
}
.error-title {
margin-top: 16px;
font-size: 18px;
font-weight: bold;
color: #fff;
}
.error-desc {
margin-top: 8px;
font-size: 14px;
color: rgba(255, 255, 255, 0.6);
text-align: center;
line-height: 1.5;
}
.retry-btn {
margin-top: 24px;
padding: 12px 24px;
background-color: #29d3b4;
color: #fff;
border-radius: 6px;
border: none;
font-size: 14px;
}
.retry-btn:active {
background-color: #1a9b7c;
}
</style>

13
uniapp/pages-market/clue/index.vue

@ -33,26 +33,31 @@
<view class="card-con">
来源{{ v.customerResource.source }}
</view>
<view class="card-con">
<view class="card-con" v-if="v.customerResource.source_channel">
来源渠道{{ v.customerResource.source_channel }}
</view>
<view class="card-con" v-if="v.sixSpeed.consultation_remark">
到访备注{{ v.sixSpeed.consultation_remark || '' }}
</view>
</view>
<view class="card-right">
<!-- 开单状态标签 -->
<view :class="['status-tag',getOrderStatusClass(v.customerResource.order_status)]">
{{ v.customerResource.order_status || '未开单' }}
{{ v.customerResource.order_status || '未报名' }}
</view>
<!-- 到访状态标签 -->
<view class="visit-status">
<view :class="['visit-tag',getVisitStatusClass(v.customerResource.first_visit_status)]">
一访{{ v.customerResource.first_visit_status || '未到' }}
</view>
<view :class="['visit-tag',getVisitStatusClass(v.customerResource.second_visit_status)]">
<view
:class="['visit-tag',getVisitStatusClass(v.customerResource.second_visit_status)]"
v-if="v.customerResource.first_visit_status !== '未到'">
二访{{ v.customerResource.second_visit_status || '未到' }}
</view>
</view>
<!--只有注册了member表的账号才可操作IM对话-->
<view class="btn-item" v-if="v.customerResource.member_id" @click.stop>
<view class="btn-item" @click.stop>
<image :src="$util.img('/uniapp_src/static/images/index/message.png')" class="image"
@click.stop="openViewMyMessage(v)"></image>
</view>

9
uniapp/pages.json

@ -42,6 +42,15 @@
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/common/dashboard/webview",
"style": {
"navigationBarTitleText": "数据统计",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#181A20",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/student/profile/index",
"style": {

280
uniapp/pages/common/dashboard/webview.vue

@ -0,0 +1,280 @@
<template>
<view class="dashboard-webview">
<!-- 加载状态 -->
<view v-if="isLoading" class="loading-container">
<uni-icons type="spinner-cycle" size="32" color="#29d3b4"></uni-icons>
<text class="loading-text">加载中...</text>
</view>
<!-- WebView容器 -->
<web-view
v-else-if="!hasError"
:src="webViewUrl"
@message="handleMessage"
@error="handleError"
@load="handleLoad"
class="webview-container"
></web-view>
<!-- 错误提示 -->
<view v-if="hasError" class="error-container">
<uni-icons type="close-filled" size="48" color="#ff5722"></uni-icons>
<text class="error-title">页面加载失败</text>
<text class="error-desc">{{ errorMessage }}</text>
<button class="retry-btn" @click="retry">重试</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
pageType: '',
pageTitle: '',
isLoading: true,
hasError: false,
errorMessage: '',
baseUrl: 'http://localhost:20080', // URL
userToken: '',
userInfo: {}
}
},
computed: {
webViewUrl() {
if (!this.userToken) {
return '';
}
// WebView URLtoken
const params = [
`token=${encodeURIComponent(this.userToken)}`,
`type=${encodeURIComponent(this.pageType)}`,
`platform=uniapp`
];
return `${this.baseUrl}/api/dashboard/webview?${params.join('&')}`;
}
},
onLoad(options) {
// URL
this.pageType = options.type || 'my_data';
//
const titleMap = {
'my_data': '我的数据',
'dept_data': '部门数据',
'campus_data': '校区数据'
};
this.pageTitle = titleMap[this.pageType] || '数据统计';
//
uni.setNavigationBarTitle({
title: this.pageTitle
});
// WebView
this.initWebView();
},
methods: {
async initWebView() {
try {
// token
await this.getUserInfo();
// URL
setTimeout(() => {
this.isLoading = false;
}, 500);
} catch (error) {
console.error('初始化WebView失败:', error);
this.showError('初始化失败');
}
},
getUserInfo() {
return new Promise((resolve, reject) => {
try {
//
const userInfo = uni.getStorageSync('userInfo');
const token = uni.getStorageSync('token');
if (!token) {
// token使token
this.userToken = 'test123';
this.userInfo = { name: '测试用户' };
} else {
this.userInfo = userInfo || {};
this.userToken = token;
}
resolve();
} catch (error) {
reject(error);
}
});
},
handleMessage(event) {
console.log('收到WebView消息:', event.detail.data);
const data = event.detail.data[0];
// WebView
switch (data.type) {
case 'navigate':
//
this.handleNavigate(data.url);
break;
case 'refresh':
//
this.refresh();
break;
case 'share':
//
this.handleShare(data.content);
break;
default:
console.log('未处理的消息类型:', data.type);
}
},
handleError(event) {
console.error('WebView加载错误:', event);
this.showError('页面加载失败,请检查网络连接');
},
handleLoad(event) {
console.log('WebView加载完成:', event);
this.isLoading = false;
this.hasError = false;
},
handleNavigate(url) {
// UniApp
if (url.startsWith('/pages')) {
// UniApp
uni.navigateTo({
url: url,
fail: (err) => {
console.error('页面跳转失败:', err);
uni.showToast({
title: '页面跳转失败',
icon: 'none'
});
}
});
} else {
// WebView
uni.showModal({
title: '提示',
content: '是否在浏览器中打开链接?',
success: (res) => {
if (res.confirm) {
uni.showToast({
title: '功能开发中',
icon: 'none'
});
}
}
});
}
},
handleShare(content) {
//
uni.showToast({
title: '分享功能开发中',
icon: 'none'
});
},
refresh() {
//
this.isLoading = true;
this.hasError = false;
//
setTimeout(() => {
this.initWebView();
}, 300);
},
retry() {
//
this.hasError = false;
this.isLoading = true;
this.initWebView();
},
showError(message) {
this.isLoading = false;
this.hasError = true;
this.errorMessage = message;
}
}
}
</script>
<style scoped>
.dashboard-webview {
height: 100vh;
background-color: #181A20;
}
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background-color: #181A20;
}
.loading-text {
margin-top: 16px;
font-size: 14px;
color: #29d3b4;
}
.webview-container {
height: 100vh;
width: 100%;
}
.error-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background-color: #181A20;
padding: 40px;
}
.error-title {
margin-top: 16px;
font-size: 18px;
font-weight: bold;
color: #fff;
}
.error-desc {
margin-top: 8px;
font-size: 14px;
color: rgba(255, 255, 255, 0.6);
text-align: center;
line-height: 1.5;
}
.retry-btn {
margin-top: 24px;
padding: 12px 24px;
background-color: #29d3b4;
color: #fff;
border-radius: 6px;
border: none;
font-size: 14px;
}
.retry-btn:active {
background-color: #1a9b7c;
}
</style>

27
uniapp/pages/common/home/index.vue

@ -74,17 +74,20 @@
{
title: '我的数据',
icon: 'bars',
path: '/pages-market/my/my_data'
path: '/pages/common/dashboard/webview',
params: { type: 'my_data' }
},
{
title: '部门数据',
icon: 'staff',
path: '/pages-market/my/dept_data'
path: '/pages/common/dashboard/webview',
params: { type: 'dept_data' }
},
{
title: '校区数据',
icon: 'location-filled',
path: '/pages-market/my/campus_data'
path: '/pages/common/dashboard/webview',
params: { type: 'campus_data' }
},
{
title: '我的消息',
@ -117,8 +120,24 @@
},
handleGridClick(item) {
console.log('点击功能按钮:', item.title, item.path);
// URLURL
let url = item.path;
if (item.params) {
//
const paramStrings = [];
for (const key in item.params) {
if (item.params.hasOwnProperty(key)) {
paramStrings.push(`${encodeURIComponent(key)}=${encodeURIComponent(item.params[key])}`);
}
}
if (paramStrings.length > 0) {
url = `${item.path}?${paramStrings.join('&')}`;
}
}
uni.navigateTo({
url: item.path,
url: url,
fail: (err) => {
console.error('页面跳转失败:', err);
uni.showToast({

Loading…
Cancel
Save