<template>
|
<div class="huanxin-code-manage">
|
<!-- 顶部宏观看板区 -->
|
<el-row :gutter="20" class="dashboard">
|
<el-col :span="8">
|
<el-card shadow="hover" class="dashboard-card green-card" @click="filterByCode('green')">
|
<div class="card-content">
|
<div class="card-title">绿码店铺数</div>
|
<div class="card-value">{{ statistics.greenCount }}</div>
|
<div class="card-percentage">{{ statistics.greenPercentage }}%</div>
|
</div>
|
</el-card>
|
</el-col>
|
<el-col :span="8">
|
<el-card shadow="hover" class="dashboard-card yellow-card" @click="filterByCode('yellow')">
|
<div class="card-content">
|
<div class="card-title">黄码店铺数</div>
|
<div class="card-value">{{ statistics.yellowCount }}</div>
|
<div class="card-percentage">{{ statistics.yellowPercentage }}%</div>
|
</div>
|
</el-card>
|
</el-col>
|
<el-col :span="8">
|
<el-card shadow="hover" class="dashboard-card red-card" @click="filterByCode('red')">
|
<div class="card-content">
|
<div class="card-title">红码店铺数</div>
|
<div class="card-value">{{ statistics.redCount }}</div>
|
<div class="card-percentage">{{ statistics.redPercentage }}%</div>
|
</div>
|
</el-card>
|
</el-col>
|
</el-row>
|
|
<!-- 评分模型配置按钮 -->
|
<!-- <div class="model-config" v-if="isAdmin">
|
<el-button type="primary" @click="openModelConfig">
|
<el-icon><Setting /></el-icon>
|
<span>评分模型配置</span>
|
</el-button>
|
</div> -->
|
|
<!-- 中部视图切换区 -->
|
<el-tabs v-model="activeView" class="view-tabs">
|
<!-- 列表视图 -->
|
<el-tab-pane label="列表视图" name="list">
|
<el-table :data="filteredShopList" style="width: 100%">
|
<el-table-column prop="shopName" label="店铺名称" />
|
<el-table-column prop="district" label="所在区县" width="120" />
|
<el-table-column prop="town" label="所在街镇" width="150" />
|
<el-table-column label="环信码" width="100">
|
<template #default="scope">
|
<el-tag :type="getCodeType(scope.row.code)">{{ getCodeText(scope.row.code) }}</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="score" label="当前评分" width="120" sortable />
|
<el-table-column label="评分变化趋势" width="150">
|
<template #default="scope">
|
<div class="trend">
|
<el-icon :class="['trend-icon', scope.row.trend > 0 ? 'up' : 'down']">
|
<ArrowUp v-if="scope.row.trend > 0" />
|
<ArrowDown v-else />
|
</el-icon>
|
<span :class="scope.row.trend > 0 ? 'up' : 'down'">
|
{{ scope.row.trend > 0 ? '+' : '' }}{{ scope.row.trend }}分
|
</span>
|
</div>
|
</template>
|
</el-table-column>
|
<el-table-column prop="lastUpdate" label="上次更新时间" width="180" />
|
<el-table-column label="操作" width="200" fixed="right">
|
<template #default="scope">
|
<el-button size="small" @click="viewDetails(scope.row)">查看详情</el-button>
|
<!-- <el-button size="small" type="warning" @click="viewRiskWarnings(scope.row)"
|
>风险预警记录</el-button
|
> -->
|
</template>
|
</el-table-column>
|
</el-table>
|
</el-tab-pane>
|
|
<!-- 地图视图 -->
|
<el-tab-pane label="地图视图" name="map">
|
<div class="map-container">
|
<div class="map-placeholder">
|
<el-empty description="地图加载中..." />
|
<!-- 这里应该集成真实的地图组件 -->
|
</div>
|
<div class="map-legend">
|
<div class="legend-item">
|
<div class="legend-dot green"></div>
|
<span>绿码</span>
|
</div>
|
<div class="legend-item">
|
<div class="legend-dot yellow"></div>
|
<span>黄码</span>
|
</div>
|
<div class="legend-item">
|
<div class="legend-dot red"></div>
|
<span>红码</span>
|
</div>
|
</div>
|
</div>
|
</el-tab-pane>
|
</el-tabs>
|
|
<!-- 详情抽屉 -->
|
<el-drawer v-model="drawerVisible" title="店铺详情" direction="rtl" size="70%">
|
<div v-if="selectedShop" class="shop-details">
|
<!-- 环信码大图标及当前评分 -->
|
<div class="code-header">
|
<div class="code-icon" :class="selectedShop.code">
|
{{ getCodeText(selectedShop.code) }}
|
</div>
|
<div class="score-info">
|
<div class="score-label">当前评分</div>
|
<div class="score-value">{{ selectedShop.score }}</div>
|
</div>
|
</div>
|
|
<!-- 评分维度雷达图 -->
|
<div class="chart-section">
|
<h3>评分维度分析</h3>
|
<div class="radar-chart">
|
<!-- 这里应该集成真实的雷达图组件 -->
|
<el-empty description="雷达图加载中..." />
|
</div>
|
</div>
|
|
<!-- 评分历史趋势图 -->
|
<div class="chart-section">
|
<h3>评分历史趋势</h3>
|
<div class="trend-chart">
|
<!-- 这里应该集成真实的趋势图组件 -->
|
<el-empty description="趋势图加载中..." />
|
</div>
|
</div>
|
|
<!-- 风险预警记录 -->
|
<div class="warning-section">
|
<h3>风险预警记录</h3>
|
<el-table :data="selectedShop.warnings" style="width: 100%">
|
<el-table-column prop="time" label="预警时间" width="180" />
|
<el-table-column prop="content" label="预警内容" />
|
<el-table-column prop="score" label="当时评分" width="120" />
|
<el-table-column label="处理状态" width="120">
|
<template #default="scope">
|
<el-tag :type="scope.row.handled ? 'success' : 'danger'">
|
{{ scope.row.handled ? '已处理' : '未处理' }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
</el-table>
|
</div>
|
</div>
|
</el-drawer>
|
|
<!-- 评分模型配置弹窗 -->
|
<el-dialog v-model="modelConfigVisible" title="评分模型配置" width="80%">
|
<div class="model-config-content">
|
<!-- 这里应该集成真实的配置表单 -->
|
<el-empty description="配置表单加载中..." />
|
</div>
|
<template #footer>
|
<span class="dialog-footer">
|
<el-button @click="modelConfigVisible = false">取消</el-button>
|
<el-button type="primary" @click="saveModelConfig">保存配置</el-button>
|
</span>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, computed, onMounted } from 'vue'
|
import { Setting, ArrowUp, ArrowDown } from '@element-plus/icons-vue'
|
|
// 状态
|
const activeView = ref('list')
|
const drawerVisible = ref(false)
|
const modelConfigVisible = ref(false)
|
const selectedShop = ref(null)
|
const isAdmin = ref(true) // 模拟管理员权限
|
const filterCode = ref('all')
|
|
// 统计数据
|
const statistics = reactive({
|
greenCount: 125,
|
greenPercentage: 65.8,
|
yellowCount: 45,
|
yellowPercentage: 23.7,
|
redCount: 20,
|
redPercentage: 10.5,
|
})
|
|
// 店铺名称列表
|
const shopNames = [
|
'付小姐在成都',
|
'吉刻联盟',
|
'家在塔啦',
|
'狼来了',
|
'乐凯撒星游店',
|
'馨远美食小镇(哈尼美食广场)',
|
'棒约翰',
|
'弄堂咪道',
|
'杨记齐齐哈尔烤肉',
|
'上海稔传餐饮管理有限公司(人生一串)',
|
'缘家',
|
'泉盛餐饮(上海)有限公司(食其家)',
|
'丰茂烤串',
|
'上海泰煌餐饮管理有限公司(泰煌鸡)',
|
'徐汇区辰熙餐馆(小铁君串烧居酒屋)',
|
]
|
|
// 徐汇区街镇列表
|
const xuhuiTowns = [
|
'天平路街道',
|
'湖南路街道',
|
'斜土路街道',
|
'枫林路街道',
|
'长桥街道',
|
'田林街道',
|
'虹梅路街道',
|
'康健新村街道',
|
'徐家汇街道',
|
'凌云路街道',
|
'龙华街道',
|
'漕河泾街道',
|
'华泾镇',
|
]
|
|
// 生成2023年8月内的随机时间
|
function generateRandomDate() {
|
const year = 2023
|
const month = 7 // 0-11,8月是7
|
const day = Math.floor(Math.random() * 31) + 1 // 1-31
|
const hour = Math.floor(Math.random() * 24) // 0-23
|
const minute = Math.floor(Math.random() * 60) // 0-59
|
|
const date = new Date(year, month, day, hour, minute)
|
return date.toISOString().slice(0, 16).replace('T', ' ')
|
}
|
|
// 随机选择数组元素
|
function getRandomElement(array) {
|
return array[Math.floor(Math.random() * array.length)]
|
}
|
|
// 生成随机评分趋势
|
function generateRandomTrend() {
|
return Math.floor(Math.random() * 11) - 5 // -5 到 5
|
}
|
|
// 店铺数据
|
const shopList = ref([
|
{
|
id: 1,
|
shopName: getRandomElement(shopNames),
|
district: '徐汇区',
|
town: getRandomElement(xuhuiTowns),
|
code: 'green',
|
score: 95,
|
trend: generateRandomTrend(),
|
lastUpdate: generateRandomDate(),
|
warnings: [
|
{
|
time: generateRandomDate(),
|
content: '净化器运行时长不足',
|
score: 90,
|
handled: true,
|
},
|
],
|
},
|
{
|
id: 2,
|
shopName: getRandomElement(shopNames),
|
district: '徐汇区',
|
town: getRandomElement(xuhuiTowns),
|
code: 'yellow',
|
score: 75,
|
trend: generateRandomTrend(),
|
lastUpdate: generateRandomDate(),
|
warnings: [
|
{
|
time: generateRandomDate(),
|
content: '投诉次数较多',
|
score: 80,
|
handled: false,
|
},
|
],
|
},
|
{
|
id: 3,
|
shopName: getRandomElement(shopNames),
|
district: '徐汇区',
|
town: getRandomElement(xuhuiTowns),
|
code: 'red',
|
score: 60,
|
trend: generateRandomTrend(),
|
lastUpdate: generateRandomDate(),
|
warnings: [
|
{
|
time: generateRandomDate(),
|
content: '排放浓度超标',
|
score: 65,
|
handled: false,
|
},
|
{
|
time: generateRandomDate(),
|
content: '清洗频次不足',
|
score: 62,
|
handled: false,
|
},
|
],
|
},
|
{
|
id: 4,
|
shopName: getRandomElement(shopNames),
|
district: '徐汇区',
|
town: getRandomElement(xuhuiTowns),
|
code: 'green',
|
score: 92,
|
trend: generateRandomTrend(),
|
lastUpdate: generateRandomDate(),
|
warnings: [],
|
},
|
{
|
id: 5,
|
shopName: getRandomElement(shopNames),
|
district: '徐汇区',
|
town: getRandomElement(xuhuiTowns),
|
code: 'yellow',
|
score: 78,
|
trend: generateRandomTrend(),
|
lastUpdate: generateRandomDate(),
|
warnings: [
|
{
|
time: generateRandomDate(),
|
content: '风机联动率低',
|
score: 75,
|
handled: true,
|
},
|
],
|
},
|
{
|
id: 6,
|
shopName: getRandomElement(shopNames),
|
district: '徐汇区',
|
town: getRandomElement(xuhuiTowns),
|
code: 'green',
|
score: 90,
|
trend: generateRandomTrend(),
|
lastUpdate: generateRandomDate(),
|
warnings: [],
|
},
|
{
|
id: 7,
|
shopName: getRandomElement(shopNames),
|
district: '徐汇区',
|
town: getRandomElement(xuhuiTowns),
|
code: 'red',
|
score: 55,
|
trend: generateRandomTrend(),
|
lastUpdate: generateRandomDate(),
|
warnings: [
|
{
|
time: generateRandomDate(),
|
content: '未安装油烟净化设备',
|
score: 60,
|
handled: false,
|
},
|
],
|
},
|
{
|
id: 8,
|
shopName: getRandomElement(shopNames),
|
district: '徐汇区',
|
town: getRandomElement(xuhuiTowns),
|
code: 'yellow',
|
score: 72,
|
trend: generateRandomTrend(),
|
lastUpdate: generateRandomDate(),
|
warnings: [
|
{
|
time: generateRandomDate(),
|
content: '净化器清洗不及时',
|
score: 75,
|
handled: true,
|
},
|
],
|
},
|
{
|
id: 9,
|
shopName: getRandomElement(shopNames),
|
district: '徐汇区',
|
town: getRandomElement(xuhuiTowns),
|
code: 'green',
|
score: 93,
|
trend: generateRandomTrend(),
|
lastUpdate: generateRandomDate(),
|
warnings: [],
|
},
|
{
|
id: 10,
|
shopName: getRandomElement(shopNames),
|
district: '徐汇区',
|
town: getRandomElement(xuhuiTowns),
|
code: 'yellow',
|
score: 76,
|
trend: generateRandomTrend(),
|
lastUpdate: generateRandomDate(),
|
warnings: [
|
{
|
time: generateRandomDate(),
|
content: '排放浓度接近标准限值',
|
score: 78,
|
handled: true,
|
},
|
],
|
},
|
])
|
|
// 过滤后的店铺列表
|
const filteredShopList = computed(() => {
|
if (filterCode.value === 'all') {
|
return shopList.value
|
}
|
return shopList.value.filter((shop) => shop.code === filterCode.value)
|
})
|
|
// 生命周期
|
onMounted(() => {
|
// 这里可以从API获取数据
|
console.log('环信码管理页面加载')
|
})
|
|
// 方法
|
function onBack() {
|
// 回退逻辑
|
console.log('回退')
|
}
|
|
function filterByCode(code) {
|
filterCode.value = code === filterCode.value ? 'all' : code
|
activeView.value = 'list' // 切换到列表视图
|
}
|
|
function getCodeType(code) {
|
switch (code) {
|
case 'green':
|
return 'success'
|
case 'yellow':
|
return 'warning'
|
case 'red':
|
return 'danger'
|
default:
|
return ''
|
}
|
}
|
|
function getCodeText(code) {
|
switch (code) {
|
case 'green':
|
return '绿码'
|
case 'yellow':
|
return '黄码'
|
case 'red':
|
return '红码'
|
default:
|
return ''
|
}
|
}
|
|
function viewDetails(shop) {
|
selectedShop.value = shop
|
drawerVisible.value = true
|
}
|
|
function viewRiskWarnings(shop) {
|
selectedShop.value = shop
|
drawerVisible.value = true
|
// 这里可以滚动到风险预警部分
|
}
|
|
function openModelConfig() {
|
modelConfigVisible.value = true
|
}
|
|
function saveModelConfig() {
|
// 保存配置逻辑
|
modelConfigVisible.value = false
|
console.log('保存评分模型配置')
|
}
|
</script>
|
|
<style scoped>
|
.huanxin-code-manage {
|
padding: 20px;
|
}
|
|
.dashboard {
|
margin-bottom: 30px;
|
}
|
|
.dashboard-card {
|
cursor: pointer;
|
transition: all 0.3s ease;
|
}
|
|
.dashboard-card:hover {
|
transform: translateY(-5px);
|
}
|
|
.card-content {
|
text-align: center;
|
padding: 20px 0;
|
}
|
|
.card-title {
|
font-size: 16px;
|
margin-bottom: 10px;
|
}
|
|
.card-value {
|
font-size: 32px;
|
font-weight: bold;
|
margin-bottom: 5px;
|
}
|
|
.card-percentage {
|
font-size: 14px;
|
opacity: 0.8;
|
}
|
|
.green-card .card-value {
|
color: #67c23a;
|
}
|
|
.yellow-card .card-value {
|
color: #e6a23c;
|
}
|
|
.red-card .card-value {
|
color: #f56c6c;
|
}
|
|
.model-config {
|
text-align: right;
|
margin-bottom: 20px;
|
}
|
|
.view-tabs {
|
margin-bottom: 20px;
|
}
|
|
.trend {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.trend-icon {
|
margin-right: 5px;
|
}
|
|
.trend-icon.up {
|
color: #67c23a;
|
}
|
|
.trend-icon.down {
|
color: #f56c6c;
|
}
|
|
.trend .up {
|
color: #67c23a;
|
}
|
|
.trend .down {
|
color: #f56c6c;
|
}
|
|
.map-container {
|
position: relative;
|
height: 600px;
|
border: 1px solid #e4e7ed;
|
border-radius: 4px;
|
}
|
|
.map-placeholder {
|
height: 100%;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.map-legend {
|
position: absolute;
|
bottom: 20px;
|
right: 20px;
|
background: white;
|
padding: 10px;
|
border-radius: 4px;
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
}
|
|
.legend-item {
|
display: flex;
|
align-items: center;
|
margin-bottom: 5px;
|
}
|
|
.legend-dot {
|
width: 12px;
|
height: 12px;
|
border-radius: 50%;
|
margin-right: 8px;
|
}
|
|
.legend-dot.green {
|
background-color: #67c23a;
|
}
|
|
.legend-dot.yellow {
|
background-color: #e6a23c;
|
}
|
|
.legend-dot.red {
|
background-color: #f56c6c;
|
}
|
|
.shop-details {
|
padding: 20px;
|
}
|
|
.code-header {
|
display: flex;
|
align-items: center;
|
margin-bottom: 30px;
|
}
|
|
.code-icon {
|
width: 100px;
|
height: 100px;
|
border-radius: 50%;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
font-size: 24px;
|
font-weight: bold;
|
color: white;
|
margin-right: 30px;
|
}
|
|
.code-icon.green {
|
background-color: #67c23a;
|
}
|
|
.code-icon.yellow {
|
background-color: #e6a23c;
|
}
|
|
.code-icon.red {
|
background-color: #f56c6c;
|
}
|
|
.score-info {
|
flex: 1;
|
}
|
|
.score-label {
|
font-size: 16px;
|
margin-bottom: 5px;
|
}
|
|
.score-value {
|
font-size: 48px;
|
font-weight: bold;
|
}
|
|
.chart-section {
|
margin-bottom: 30px;
|
}
|
|
.chart-section h3 {
|
margin-bottom: 15px;
|
font-size: 18px;
|
}
|
|
.radar-chart,
|
.trend-chart {
|
height: 300px;
|
border: 1px solid #e4e7ed;
|
border-radius: 4px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.warning-section {
|
margin-top: 30px;
|
}
|
|
.warning-section h3 {
|
margin-bottom: 15px;
|
font-size: 18px;
|
}
|
</style>
|