Browse Source

fix: 样式修改

master
buaixuexideshitongxue 2 weeks ago
parent
commit
d4238aa4f7
  1. 664
      src/components/negativeInfo/depart-dialog.vue
  2. 559
      src/components/negativeInfo/police-dialog.vue

664
src/components/negativeInfo/depart-dialog.vue

@ -1,287 +1,153 @@
<template>
<el-dialog
title="单位问题详情"
class="profile-dialog depart-profile"
:title="departInfo.name || '单位问题详情'"
:model-value="modelValue"
@update:model-value="emit('update:modelValue', $event)"
width="85vw"
top="1vh"
style="margin: 1vh auto"
>
<header class="flex center v-center gap">
<template #header>
<div class="profile-heading">
<h2>{{ departInfo.name || "单位问题详情" }}</h2>
<p>单位问题详情</p>
</div>
</template>
<header class="profile-toolbar">
<label>统计范围</label>
<div style="width: 320px">
<div class="profile-date-field">
<date-time-range-picker-ext
v-model="time"
format="YYYY-MM-DD"
range-separator="至"
@change="getProfileData"
style="width: 300px"
/>
</div>
<el-button type="primary" @click="getProfileData">查询</el-button>
<el-button class="profile-query-button" type="primary" @click="getProfileData">查询</el-button>
</header>
<el-scrollbar
max-height="calc(98vh - 120px)"
v-loading="loading"
element-loading-text="单位问题详情加载中..."
>
<main>
<el-row class="mb-20" :gutter="20">
<el-col :span="8">
<el-scrollbar max-height="calc(98vh - 180px)" v-loading="loading" element-loading-text="单位问题详情加载中...">
<main class="profile-content">
<div class="profile-grid profile-overview">
<section class="profile-panel">
<h5>单位情况</h5>
<div class="card-item h-220">
<el-row class="mb-10">
<el-col :span="16">
<div class="row">
<div class="col col-24">
<label>单位名称</label>
<span>{{ departInfo.name }}</span>
</div>
<div class="col col-24">
<label>所长</label>
<span>{{ departInfo.mainRole }}</span>
</div>
<div class="col col-24">
<label>副所长</label>
<span>
<span
v-for="item in departInfo.deputyRole"
:key="item"
class="mr-10"
>{{ item }}</span
>
</span>
</div>
</div>
</el-col>
<el-col :span="8">
<div class="flex center v-center">
<img
:src="`${BASE_PATH}/file/stream/${departInfo.departImg}`"
style="max-height: 96px"
v-if="departInfo.departImg1"
/>
</div>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<description-pair
label1="民警总人数"
label2="问题涉及民警数"
:value1="departInfo.policeSize"
:value2="departInfo.negativePoliceSize"
size="large"
/>
</el-col>
<el-col :span="12">
<description-pair
label1="协辅警人数"
label2="问题涉及协辅警数"
:value1="departInfo.auxSize"
:value2="departInfo.negativeAuxSize"
size="large"
/>
</el-col>
</el-row>
</div>
</el-col>
<el-col :span="8">
<dl class="profile-fields depart-fields">
<div><dt>所长</dt><dd>{{ departInfo.mainRole || "/" }}</dd></div>
<div><dt>副所长</dt><dd>{{ departInfo.deputyRole?.join("、") || "/" }}</dd></div>
<div><dt>民警总人数</dt><dd>{{ departInfo.policeSize ?? 0 }}</dd></div>
<div><dt>问题涉及民警数</dt><dd>{{ departInfo.negativePoliceSize ?? 0 }}</dd></div>
<div><dt>协辅警人数</dt><dd>{{ departInfo.auxSize ?? 0 }}</dd></div>
<div><dt>问题涉及协辅警数</dt><dd>{{ departInfo.negativeAuxSize ?? 0 }}</dd></div>
</dl>
</section>
<section class="profile-panel problem-panel">
<h5>问题情况</h5>
<div class="card-item h-220">
<div class="flex v-center h100">
<div style="width: 25%" class="text-center">
<div class="text-primary" style="font-size: 34px">
{{ negativeInfo.size }}
<div class="problem-summary">
<div class="problem-total">
<span>问题总数</span>
<strong>{{ negativeInfo.size ?? 0 }}</strong>
</div>
<div>问题总数</div>
</div>
<div style="width: 75%">
<div
class="mb-20"
v-if="negativeInfo.jcj110BusinessSize || negativeInfo.jcj110Size"
>
<description-pair
label1="110接处警量"
label2="110接处警问题数"
:value1="negativeInfo.jcj110BusinessSize"
:value2="negativeInfo.jcj110Size"
/>
</div>
<div
v-if="negativeInfo.jcj122BusinessSize || negativeInfo.jcj122Size"
class="mb-20"
>
<description-pair
label1="122接处警量"
label2="122接处警问题数"
:value1="negativeInfo.jcj122BusinessSize"
:value2="negativeInfo.jcj122Size"
/>
</div>
<div>
<description-pair
label1="执法办案"
label2="执法办案问题数"
:value1="negativeInfo.zfbaBusinessSize"
:value2="negativeInfo.zfbaSize"
/>
</div>
</div>
</div>
</div>
</el-col>
<el-col :span="8">
<h5>问题累计分值</h5>
<div class="flex center card-item wrap h-220">
<el-progress
type="dashboard"
:percentage="100"
:stroke-width="16"
:width="200"
color="#5B8FF9"
>
<div class="score-progress-body">
<div>
<div style="font-size: 16px">分数</div>
<div>
<span
class="score-progress_score score-theme text-bold"
style="color: #19257d"
>{{ score }}</span
>
<table class="business-table">
<thead><tr><th>业务类别</th><th>业务量</th><th>问题数</th></tr></thead>
<tbody>
<tr><td>110接处警</td><td>{{ negativeInfo.jcj110BusinessSize ?? 0 }}</td><td>{{ negativeInfo.jcj110Size ?? 0 }}</td></tr>
<tr><td>122接处警</td><td>{{ negativeInfo.jcj122BusinessSize ?? 0 }}</td><td>{{ negativeInfo.jcj122Size ?? 0 }}</td></tr>
<tr><td>执法办案</td><td>{{ negativeInfo.zfbaBusinessSize ?? 0 }}</td><td>{{ negativeInfo.zfbaSize ?? 0 }}</td></tr>
</tbody>
</table>
</div>
</section>
<section class="profile-panel">
<h5>投诉问题</h5>
<div class="complaint-summary">
<svg class="complaint-icon" viewBox="0 0 48 48" fill="none" aria-hidden="true">
<path d="M10 8h28a4 4 0 0 1 4 4v20a4 4 0 0 1-4 4H23L12 44v-8h-2a4 4 0 0 1-4-4V12a4 4 0 0 1 4-4Z" stroke="currentColor" stroke-width="2.5" stroke-linejoin="round" />
<circle cx="16" cy="22" r="2" fill="currentColor" /><circle cx="24" cy="22" r="2" fill="currentColor" /><circle cx="32" cy="22" r="2" fill="currentColor" />
</svg>
<strong>{{ complaintCount }}</strong>
<span>投诉数量(件)</span>
</div>
</section>
</div>
</el-progress>
<div style="width: 100%" class="text-center">累计{{ days }}天分值</div>
</div>
</el-col>
</el-row>
<el-row class="mb-20" :gutter="20">
<el-col :span="8">
<div class="profile-grid profile-analysis">
<section class="profile-panel">
<h5>问题来源占比</h5>
<div class="card-item">
<v-charts
style="height: 320px"
:option="problemSourcesPieOptions"
autoresize
/>
</div>
</el-col>
<el-col :span="8">
<v-charts class="profile-chart" :option="problemSourcesPieOptions" autoresize />
</section>
<section class="profile-panel">
<h5>业务类型占比</h5>
<div class="card-item">
<v-charts
style="height: 320px"
:option="businessTypePieOptions"
autoresize
/>
</div>
</el-col>
<el-col :span="8">
<v-charts class="profile-chart" :option="businessTypePieOptions" autoresize />
</section>
<section class="profile-panel">
<h5>问题涉及方面</h5>
<div class="card-item">
<v-charts
style="height: 320px"
:option="problemTypePieOptions"
autoresize
/>
<v-charts class="profile-chart" :option="problemTypePieOptions" autoresize />
</section>
</div>
</el-col>
</el-row>
<el-row class="mb-20" :gutter="20">
<el-col :span="8">
<div class="profile-grid profile-analysis">
<section class="profile-panel">
<h5>个人问题排名</h5>
<div class="card-item h-300">
<el-scrollbar max-height="300px">
<datav-chart-bar
size="small"
:data="policeBarList"
:color="colors"
:labelWidth="60"
/>
<el-empty v-if="policeBarList.length === 0" />
<el-scrollbar class="ranking-list" height="260px">
<datav-chart-bar v-if="policeBarList.length" size="small" :data="policeBarList" :color="colors" :label-width="100" label-position="top" />
<el-empty v-else description="暂无数据" :image-size="70" />
</el-scrollbar>
</div>
</el-col>
<el-col :span="8">
</section>
<section class="profile-panel">
<h5>问题涉及领导</h5>
<div class="card-item h-300">
<el-scrollbar max-height="300px">
<datav-chart-bar
size="small"
:data="policeLeadBarList"
:color="colors"
:labelWidth="60"
/>
<el-empty v-if="policeLeadBarList.length === 0" />
<el-scrollbar class="ranking-list" height="260px">
<datav-chart-bar v-if="policeLeadBarList.length" size="small" :data="policeLeadBarList" :color="colors" :label-width="100" label-position="top" />
<el-empty v-else description="暂无数据" :image-size="70" />
</el-scrollbar>
</div>
</el-col>
<el-col :span="8">
</section>
<section class="profile-panel">
<h5>突出问题排名</h5>
<div class="card-item h-300">
<el-scrollbar max-height="300px">
<datav-chart-bar1
size="small"
:data="problemTypeBarList"
:color="colors"
labelPosition="top"
/>
<el-empty v-if="problemTypeBarList.length === 0" />
<el-scrollbar class="ranking-list" height="260px">
<datav-chart-bar1 v-if="problemTypeBarList.length" size="small" :data="problemTypeBarList" :color="colors" :label-width="100" label-position="top" />
<el-empty v-else description="暂无数据" :image-size="70" />
</el-scrollbar>
</section>
</div>
</el-col>
</el-row>
<div>
<section class="profile-panel">
<h5>问题变化趋势</h5>
<div class="card-item">
<v-charts style="height: 320px" :option="barOption" autoresize />
</div>
</div>
<h5>问题清单 {{ negativeList.length }}</h5>
<el-table :data="negativeList" height="500">
<v-charts class="trend-chart" :option="barOption" autoresize />
</section>
<section class="profile-panel">
<h5>问题清单 <span class="list-count">共 {{ negativeList.length }} 条</span></h5>
<el-table :data="negativeList" max-height="500" class="profile-table">
<el-table-column label="发现时间" prop="discoveryTime" width="160" />
<el-table-column label="问题来源" prop="problemSources" width="150" />
<el-table-column label="业务类别" prop="businessTypeName" width="150" />
<el-table-column label="问题详情" prop="thingDesc" show-overflow-tooltip />
<el-table-column
label="核查情况"
prop="checkStatusDesc"
show-overflow-tooltip
/>
<el-table-column label="问题类型" prop="problemType" show-overflow-tooltip />
<el-table-column label="分值" width="100" align="center">
<el-table-column label="问题来源" prop="problemSources" width="140" />
<el-table-column label="业务类别" prop="businessTypeName" width="140" />
<el-table-column label="问题详情" prop="thingDesc" min-width="220" show-overflow-tooltip />
<el-table-column label="核查情况" prop="checkStatusDesc" min-width="130" show-overflow-tooltip />
<el-table-column label="问题类型" prop="problemType" min-width="130" show-overflow-tooltip />
<el-table-column label="操作" width="110" align="center">
<template #default="{ row }">
<span class="text-danger">{{ row.score }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="160">
<template #default="{ row }">
<el-button type="primary" link @click="handleAction(row)"
>查看详情
</el-button>
<el-button type="primary" link @click="handleAction(row)">查看详情</el-button>
</template>
</el-table-column>
</el-table>
</section>
</main>
</el-scrollbar>
<negative-dialog
v-model="negativeShow"
:id="activeNegativeId"
@close="negativeShow = false"
/>
</el-dialog>
<negative-dialog v-model="negativeShow" :id="activeNegativeId" @close="negativeShow = false" />
</template>
<script setup>
import vCharts from "vue-echarts";
import { BASE_PATH } from "@/api/request";
import { InspectCase } from "@/enums/dictEnums";
import {
getDepartProfile,
listNegativeMonthly,
} from "@/api/sensitivePerception/profileDepart";
import { listNegative } from "@/api/work/negative";
import moment from "moment";
const props = defineProps({
@ -297,28 +163,18 @@ const time = ref([
moment().startOf("year").format("YYYY-MM-DD HH:mm:ss"),
moment().format("YYYY-MM-DD HH:mm:ss"),
]);
const colors = [
{ color: "#5AD8A6", percentage: 40 },
{ color: "#F6BD16", percentage: 60 },
{ color: "#E8684A ", percentage: 80 },
];
const colors2 = [
{ color: "var(--success-color)", percentage: 60 },
{ color: "#DC6231", percentage: 80 },
{ color: "var(--danger-color)", percentage: 100 },
];
const colors = [{ color: "#5B8FF9", percentage: 0 }];
// TODO: 投诉数量暂用假数据,后续替换为接口返回值。
const complaintCount = ref(6);
const loading = ref(false);
const departInfo = ref({});
const negativeInfo = ref({});
const negativeList = ref([]);
const negativeTotal = ref(0);
const problemTypeBarList = ref([]);
const score = ref(0);
const days = ref(0);
const problemTypeBarList = ref([]);
async function getProfileData() {
loading.value = true;
@ -327,40 +183,50 @@ async function getProfileData() {
beginTime: time.value.length ? time.value[0] : "",
endTime: time.value.length ? time.value[1] : "",
});
score.value = Math.round(data.score);
days.value = data.days;
negativeList.value = data.negatives;
departInfo.value = data.departInfo;
negativeInfo.value = data.negativeInfo;
problemSourcesPieOptions.value.series[0].data = data.problemSourcesList;
problemSourcesPieOptions.value.title.text = String(data.negativeInfo.size ?? 0);
businessTypePieOptions.value.series[0].data = data.businessTypeList;
businessTypePieOptions.value.title.text = String(data.negativeInfo.size ?? 0);
problemTypePieOptions.value.series[0].data = data.problemTypeList;
problemTypePieOptions.value.title.text = String(data.negativeInfo.size ?? 0);
policeBarList.value = data.policeBarList;
policeLeadBarList.value = data.policeLeadBarList;
radarOption.value.radar.indicator = data.problemTypeRadarIndicator;
radarOption.value.series[0].data[0].value = data.problemTypeRadarData;
problemTypeBarList.value = data.problemTypeBarList;
loading.value = false;
}
const barOption = ref({
grid: { left: 40, right: 20, top: 30, bottom: 35, containLabel: true },
tooltip: { trigger: "axis" },
xAxis: {
type: "category",
data: [],
boundaryGap: false,
axisLabel: { color: "#333" },
axisLine: { lineStyle: { color: "#dfe6f5" } },
},
yAxis: {
type: "value",
name: "问题数",
minInterval: 1,
nameTextStyle: { color: "#333" },
axisLabel: { color: "#333" },
splitLine: { lineStyle: { color: "#e8edf6", type: "dashed" } },
},
series: [
{
series: [{
data: [],
type: "bar",
color: "#5B8FF9 ",
},
],
type: "line",
symbolSize: 7,
itemStyle: { color: "#5B8FF9" },
lineStyle: { width: 2 },
areaStyle: { color: "rgba(91, 143, 249, 0.12)" },
}],
});
watch(
@ -381,27 +247,6 @@ watch(
}
);
const radarOption = ref({
radar: {
indicator: [],
},
series: [
{
type: "radar",
data: [
{
value: [],
areaStyle: {
color: "rgba(255, 145, 124, 0.9)",
},
},
],
label: {
show: true,
},
},
],
});
const problemSourcesPieOptions = ref({
tooltip: {
trigger: "item",
@ -442,27 +287,35 @@ const problemTypePieOptions = ref({
const policeBarList = ref([]);
const policeLeadBarList = ref([]);
function getType(val) {
if (val < 60) {
return "success";
}
if (val < 80) {
return "warning";
}
return "danger";
}
function getScoreLabel() {
if (score.value === 0) {
return "无法预测";
}
if (score.value < 60) {
return "低风险";
}
if (score.value < 80) {
return "中风险";
}
return "高风险";
for (const pieOption of [problemSourcesPieOptions, businessTypePieOptions, problemTypePieOptions]) {
Object.assign(pieOption.value, {
color: ["#5B8FF9", "#5AD8A6", "#F6BD16", "#F6903D", "#6DC8EC"],
title: {
text: "0",
subtext: "问题总数",
left: "30%",
top: "39%",
textAlign: "center",
textStyle: { color: "#162582", fontSize: 22, fontWeight: 700 },
subtextStyle: { color: "#333", fontSize: 12 },
},
legend: {
type: "scroll",
orient: "vertical",
right: 0,
top: "middle",
width: "38%",
textStyle: { color: "#333", fontSize: 13, width: 90, overflow: "truncate" },
itemWidth: 10,
itemHeight: 10,
pageTextStyle: { color: "#333" },
},
});
Object.assign(pieOption.value.series[0], {
center: ["30%", "50%"],
radius: ["45%", "65%"],
label: { show: false },
});
}
const negativeShow = ref(false);
@ -474,54 +327,181 @@ function handleAction(row) {
}
</script>
<style lang="scss" scoped>
main {
:global(.el-dialog.depart-profile) {
--profile-primary: #162582;
--profile-border: #dfe6f5;
padding: 0;
border: 3px solid #243d9b;
border-radius: 8px;
overflow: hidden;
color: #333;
background: #f9faff;
}
.col {
--label-width: 60px;
:global(.depart-profile .el-dialog__header) {
margin: 0;
padding: 20px 48px 20px 24px;
background: linear-gradient(100deg, #162582, #1c369f);
border-bottom: 2px solid #77bde9;
}
.score-progress-body {
text-align: center;
font-size: 18px;
.score-progress_score {
font-size: 50px;
line-height: 70px;
}
.score-progress_title {
font-size: 26px;
line-height: 22px;
}
:global(.depart-profile .el-dialog__body) { padding: 0; color: #333; }
:global(.depart-profile .el-dialog__headerbtn) { top: 10px; right: 10px; }
:global(.depart-profile .el-dialog__close) { color: #fff; }
.profile-heading {
h2 { margin: 0; font-size: 26px; font-weight: 700; color: #fff; line-height: 1.4; }
p { margin: 4px 0 0; font-size: 14px; color: #fff; }
}
.score-theme {
&[type="success"] {
color: var(--success-color);
}
&[type="warning"] {
color: #e87749;
.police-profile .profile-heading { text-align: center; }
.profile-toolbar {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 16px;
padding: 16px 22px 18px;
color: #333;
background: #f9faff;
label { font-size: 16px; font-weight: 500; }
.profile-date-field { width: 340px; max-width: 100%; min-width: 0; }
:deep(.el-date-editor.el-input__wrapper) {
width: 100%;
height: 40px;
box-sizing: border-box;
padding: 0 12px;
border-radius: 4px;
background: #fff;
box-shadow: 0 0 0 1px #dce2ef inset;
}
&[type="danger"] {
color: var(--danger-color);
:deep(.el-date-editor.is-active) { box-shadow: 0 0 0 1px #162582 inset; }
:deep(.el-range-input) { min-width: 0; color: #333; font-size: 15px; }
:deep(.el-range-separator) { flex: 0 0 24px; padding: 0; color: #333; font-size: 15px; }
:deep(.el-range__icon) { order: 1; margin: 0 0 0 8px; color: #a5b2ce; }
:deep(.el-range__close-icon) { order: 2; margin-left: 4px; }
.profile-query-button { min-width: 88px; height: 40px; font-size: 15px; border-radius: 4px; }
}
.profile-content {
/* 颜色变量定义在真实内容节点,保证弹窗传送后的内容样式生效。 */
--profile-primary: #162582;
--profile-border: #e2e8f5;
padding: 0 18px 18px;
background: #f9faff;
color: #333;
}
.profile-grid { display: grid; gap: 16px; margin-bottom: 16px; }
.police-profile .profile-overview { grid-template-columns: 1fr 1fr; }
.depart-profile .profile-overview { grid-template-columns: 1.1fr 1fr .8fr; }
.profile-analysis { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.profile-panel {
min-width: 0;
padding: 14px 16px 16px;
border: 1px solid var(--profile-border, #e2e8f5);
border-radius: 6px;
background: linear-gradient(135deg, #f8faff, #fff);
color: #333;
> h5 {
display: flex;
align-items: center;
gap: 10px;
margin: -14px -16px 16px;
padding: 8px 16px;
background: linear-gradient(90deg, #eef3fc, #f6f9ff);
border-radius: 6px 6px 0 0;
font-size: 18px;
font-weight: 700;
line-height: 1.5;
color: var(--profile-primary, #162582);
&::before { content: ""; width: 4px; height: 20px; border-radius: 2px; background: var(--profile-primary, #162582); flex-shrink: 0; }
}
:deep(.bar-item), :deep(.bar-item-label) { color: #333; font-size: 14px; }
:deep(.bar-item_content) { background: #eaf0fc; }
:deep(.el-empty__description p) { color: #666; }
}
h5 {
margin-bottom: 12px;
.profile-content > .profile-panel { margin-bottom: 16px; }
.profile-content > .profile-panel:last-child { margin-bottom: 0; }
.police-basic { display: flex; align-items: center; gap: 20px; min-height: 240px; }
.police-photo {
width: 140px;
height: 190px;
flex-shrink: 0;
border-radius: 4px;
background: #e5edfc;
border: 1px solid #dbe4f7;
}
.card-item {
background: #f9faff;
padding: 16px;
min-height: 200px;
.photo-placeholder { height: 100%; display: flex; justify-content: center; align-items: center; }
.profile-fields {
flex: 1;
min-width: 0;
margin: 0;
font-size: 15px;
> div { display: flex; gap: 14px; padding: 7px 0; line-height: 1.5; }
dt { width: 72px; flex-shrink: 0; color: #555; }
dd { margin: 0; color: #333; overflow-wrap: anywhere; }
}
.depart-fields {
> div { padding: 9px 0; }
> div:nth-child(2) { margin-bottom: 8px; padding-bottom: 16px; border-bottom: 1px solid #e2e8f5; }
dt { width: auto; flex: 1; }
dd { font-weight: 600; text-align: right; }
}
.problem-summary { display: flex; gap: 16px; align-items: center; min-height: 240px; }
.problem-total {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
align-self: stretch;
flex: 0 0 24%;
gap: 12px;
background: #f3f6fd;
strong { font-size: 54px; line-height: 1.1; color: var(--profile-primary, #162582); }
span { font-size: 15px; color: #333; }
}
.business-table {
width: 100%;
border-collapse: collapse;
font-size: 15px;
text-align: center;
th, td { border-bottom: 1px solid var(--profile-border, #e2e8f5); padding: 13px 8px; }
th { background: #ebeefc; color: var(--profile-primary, #162582); font-weight: 600; white-space: nowrap; }
td { color: #333; }
}
.depart-profile {
.problem-summary { flex-direction: column; align-items: stretch; gap: 14px; }
.problem-total { flex-direction: row; flex-basis: auto; justify-content: flex-start; padding: 8px 0; background: transparent; strong { font-size: 36px; } }
.business-table { th, td { padding: 12px 6px; } }
}
.complaint-summary {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
min-height: 260px;
strong { font-size: 56px; line-height: 1; color: var(--profile-primary, #162582); }
span { font-size: 16px; color: #333; }
}
.complaint-icon { width: 64px; height: 64px; padding: 10px; box-sizing: border-box; border-radius: 50%; background: #edf3ff; color: #5b8ff9; }
.profile-chart { height: 260px; }
.trend-chart { height: 280px; }
.ranking-list { padding: 4px 0; }
.list-count { font-size: 14px; font-weight: 400; color: #333; }
.profile-table {
--el-table-header-bg-color: #ebeefc;
--el-table-header-text-color: #162582;
--el-table-text-color: #333;
--el-table-border-color: #e2e8f5;
:deep(.el-table__header .el-table__cell) { padding: 12px 0; color: #162582; background: #ebeefc; font-weight: 600; }
:deep(.el-table__cell) { padding: 10px 0; font-size: 14px; color: #333; }
}
.h-220 {
height: 220px;
@media (max-width: 1280px) {
.police-basic { gap: 12px; }
.police-photo { width: 110px; height: 150px; }
.profile-fields { font-size: 14px; > div { gap: 8px; } }
.problem-summary { gap: 10px; }
.problem-total strong { font-size: 42px; }
.business-table { font-size: 14px; th, td { padding: 12px 5px; } }
}
.h-300 {
height: 300px;
@media (max-width: 1000px) {
.police-profile .profile-overview, .depart-profile .profile-overview, .profile-analysis { grid-template-columns: 1fr; }
}
</style>

559
src/components/negativeInfo/police-dialog.vue

@ -1,5 +1,6 @@
<template>
<el-dialog
class="profile-dialog police-profile"
title="个人问题详情"
:model-value="modelValue"
@update:model-value="emit('update:modelValue', $event)"
@ -7,230 +8,115 @@
top="1vh"
style="margin: 1vh auto"
>
<header class="flex center v-center gap">
<template #header>
<div class="profile-heading">
<h2>个人问题详情</h2>
</div>
</template>
<header class="profile-toolbar">
<label>统计范围</label>
<div style="width: 320px">
<div class="profile-date-field">
<date-time-range-picker-ext
v-model="time"
style="width: 300px"
format="YYYY-MM-DD"
range-separator="至"
@change="getProfileData"
/>
</div>
<el-button type="primary" @click="getProfileData">查询</el-button>
<el-button class="profile-query-button" type="primary" @click="getProfileData">查询</el-button>
</header>
<el-scrollbar
max-height="calc(98vh - 120px)"
v-loading="loading"
element-loading-text="个人问题详情加载中..."
>
<main>
<el-row class="mb-20" :gutter="20">
<el-col :span="8">
<el-scrollbar max-height="calc(98vh - 160px)" v-loading="loading" element-loading-text="个人问题详情加载中...">
<main class="profile-content">
<div class="profile-grid profile-overview">
<section class="profile-panel">
<h5>民警基本情况</h5>
<div class="card-item h-220">
<el-row>
<el-col :span="6">
<div class="flex v-center" style="height: 100%">
<img
v-if="policeInfo.avatarUrl"
:src="`${BASE_PATH}/file/stream/${policeInfo.avatarUrl}`"
class="police-avatar"
style="width: 94px"
/>
<icon name="local-icon-police" :size="120" v-else />
</div>
</el-col>
<el-col :span="18">
<div class="row">
<div class="col col-12">
<label>姓名</label>
<span>{{ policeInfo.name }}</span>
</div>
<div class="col col-12">
<label>性别</label>
<span>{{ getGenderFromIdCode(policeInfo.idCode) }}</span>
</div>
<div class="col col-24">
<label>所属单位</label>
<span>
<span
>{{
activeRow.parentDepartName
? activeRow.parentDepartName + "/"
: ""
}}{{ activeRow.departName || "/" }}</span
<div class="police-basic">
<el-image
class="police-photo"
:src="policeInfo.avatarUrl ? `${BASE_PATH}/file/stream/${policeInfo.avatarUrl}` : ''"
fit="cover"
alt="人员照片"
>
</span>
</div>
<div class="col col-24">
<label>警号</label>
<span>{{ policeInfo.empNo }}</span>
</div>
<div class="col col-24">
<label>身份证号</label>
<span>{{ policeInfo.idCode }}</span>
</div>
<div class="col col-24">
<label>入职时间</label>
<span>{{ policeInfo.employmentDate || "/" }}</span>
</div>
<div class="col col-24">
<label>手机号</label>
<span>{{ policeInfo.mobile || "/" }}</span>
</div>
</div>
</el-col>
</el-row>
<template #error>
<div class="photo-placeholder"><icon name="local-icon-police" :size="110" /></div>
</template>
</el-image>
<dl class="profile-fields">
<div><dt>姓名</dt><dd>{{ policeInfo.name || "/" }}</dd></div>
<div><dt>性别</dt><dd>{{ policeInfo.idCode ? getGenderFromIdCode(policeInfo.idCode) : "/" }}</dd></div>
<div><dt>所属单位</dt><dd>{{ activeRow.parentDepartName ? activeRow.parentDepartName + "/" : "" }}{{ activeRow.departName || "/" }}</dd></div>
<div><dt>警号</dt><dd>{{ policeInfo.empNo || "/" }}</dd></div>
<div><dt>身份证号</dt><dd>{{ policeInfo.idCode || "/" }}</dd></div>
<div><dt>入职时间</dt><dd>{{ policeInfo.employmentDate || "/" }}</dd></div>
<div><dt>手机号</dt><dd>{{ policeInfo.mobile || "/" }}</dd></div>
</dl>
</div>
</el-col>
<el-col :span="8">
</section>
<section class="profile-panel problem-panel">
<h5>问题情况</h5>
<div class="card-item h-220">
<div class="flex v-center h100">
<div style="width: 25%" class="text-center">
<div class="text-primary" style="font-size: 34px">
{{ negativeInfo.size }}
<div class="problem-summary">
<div class="problem-total">
<span>问题总数</span>
<strong>{{ negativeInfo.size ?? 0 }}</strong>
</div>
<div class="mb-10">问题总数</div>
<table class="business-table">
<thead><tr><th>业务类别</th><th>业务量</th><th>问题数</th></tr></thead>
<tbody>
<tr><td>110接处警</td><td>{{ negativeInfo.jcj110BusinessSize ?? 0 }}</td><td>{{ negativeInfo.jcj110Size ?? 0 }}</td></tr>
<tr><td>122接处警</td><td>{{ negativeInfo.jcj122BusinessSize ?? 0 }}</td><td>{{ negativeInfo.jcj122Size ?? 0 }}</td></tr>
<tr><td>执法办案</td><td>{{ negativeInfo.zfbaBusinessSize ?? 0 }}</td><td>{{ negativeInfo.zfbaSize ?? 0 }}</td></tr>
</tbody>
</table>
</div>
<div style="width: 75%" class="text-center">
<div
v-if="negativeInfo.jcj110BusinessSize || negativeInfo.jcj110Size"
class="mb-20"
>
<description-pair
label="110接处警"
label1="量"
label2="问题数"
:value1="negativeInfo.jcj110BusinessSize"
:value2="negativeInfo.jcj110Size"
/>
</div>
<div
v-if="negativeInfo.jcj122BusinessSize || negativeInfo.jcj122Size"
class="mb-20"
>
<description-pair
label="122接处警"
label1="量"
label2="问题数"
:value1="negativeInfo.jcj122BusinessSize"
:value2="negativeInfo.jcj122Size"
/>
</section>
</div>
<div>
<description-pair
label="执法办案"
label2="问题数"
:value1="negativeInfo.zfbaBusinessSize"
:value2="negativeInfo.zfbaSize"
/>
</div>
</div>
</div>
</div>
</el-col>
<el-col :span="8">
<h5>问题累计分值</h5>
<div class="flex center card-item wrap h-220">
<el-progress
type="dashboard"
:percentage="100"
:stroke-width="16"
:width="200"
color="#5B8FF9"
>
<div class="score-progress-body">
<div>
<div style="font-size: 16px">分数</div>
<div>
<span
class="score-progress_score score-theme text-bold"
style="color: #19257d"
>{{ score }}</span
>
</div>
</div>
</div>
</el-progress>
<div style="width: 100%" class="text-center">累计{{ days }}天分值</div>
</div>
</el-col>
</el-row>
<el-row class="mb-20" :gutter="20">
<el-col :span="8">
<div class="profile-grid profile-analysis">
<section class="profile-panel">
<h5>问题来源占比</h5>
<div class="card-item">
<v-charts
style="height: 320px"
:option="problemSourcesPieOptions"
autoresize
/>
</div>
</el-col>
<el-col :span="8">
<v-charts class="profile-chart" :option="problemSourcesPieOptions" autoresize />
</section>
<section class="profile-panel">
<h5>问题涉及方面</h5>
<div class="card-item">
<v-charts
style="height: 320px"
:option="problemTypePieOptions"
autoresize
/>
</div>
</el-col>
<el-col :span="8">
<v-charts class="profile-chart" :option="problemTypePieOptions" autoresize />
</section>
<section class="profile-panel">
<h5>突出问题排名</h5>
<div class="flex center card-item h-320">
<datav-chart-bar1
size="small"
:data="problemTypeBarList"
:color="colors"
labelPosition="top"
/>
<el-empty v-if="problemTypeBarList.length === 0" />
<el-scrollbar class="ranking-list" height="260px">
<datav-chart-bar1 v-if="problemTypeBarList.length" size="small" :data="problemTypeBarList" :color="colors" :label-width="100" label-position="top" />
<el-empty v-else description="暂无数据" :image-size="70" />
</el-scrollbar>
</section>
</div>
</el-col>
</el-row>
<section class="profile-panel">
<h5>问题变化趋势</h5>
<div class="card-item">
<v-charts style="height: 320px" :option="barOption" autoresize />
</div>
<h5>问题清单 {{ negativeList.length }}</h5>
<el-table :data="negativeList" height="500">
<v-charts class="trend-chart" :option="barOption" autoresize />
</section>
<section class="profile-panel">
<h5>问题清单 <span class="list-count">共 {{ negativeList.length }} 条</span></h5>
<el-table :data="negativeList" max-height="500" class="profile-table">
<el-table-column label="发现时间" prop="discoveryTime" width="160" />
<el-table-column label="问题来源" prop="problemSources" width="150" />
<el-table-column label="业务类别" prop="businessTypeName" width="150" />
<el-table-column label="问题详情" prop="thingDesc" show-overflow-tooltip />
<el-table-column
label="核查情况"
prop="checkStatusDesc"
show-overflow-tooltip
/>
<el-table-column label="问题类型" prop="problemType" show-overflow-tooltip />
<el-table-column label="分值" width="100" align="center">
<template #default="{ row }">
<span class="text-danger">{{ row.score }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="160">
<el-table-column label="问题来源" prop="problemSources" width="140" />
<el-table-column label="业务类别" prop="businessTypeName" width="140" />
<el-table-column label="问题详情" prop="thingDesc" min-width="220" show-overflow-tooltip />
<el-table-column label="核查情况" prop="checkStatusDesc" min-width="130" show-overflow-tooltip />
<el-table-column label="问题类型" prop="problemType" min-width="130" show-overflow-tooltip />
<el-table-column label="操作" width="110" align="center">
<template #default="{ row }">
<el-button type="primary" link @click="handleAction(row)"
>查看详情
</el-button>
<el-button type="primary" link @click="handleAction(row)">查看详情</el-button>
</template>
</el-table-column>
</el-table>
</section>
</main>
</el-scrollbar>
</el-dialog>
<negative-dialog
v-model="negativeShow"
:id="activeNegativeId"
@close="negativeShow = false"
/>
<negative-dialog v-model="negativeShow" :id="activeNegativeId" @close="negativeShow = false" />
</template>
<script setup>
import { BASE_PATH } from "@/api/request";
@ -253,8 +139,7 @@ const negativeInfo = ref({});
const loading = ref(false);
const negativeList = ref([]);
const problemTypeBarList = ref([]);
const score = ref(0);
const days = ref(0);
const problemSourcesPieOptions = ref({
tooltip: { trigger: "item" },
series: [{ type: "pie", radius: ["40%", "70%"], data: [] }],
@ -264,15 +149,33 @@ const problemTypePieOptions = ref({
series: [{ type: "pie", radius: ["40%", "70%"], data: [] }],
});
const barOption = ref({
xAxis: { type: "category", data: [] },
yAxis: { type: "value" },
series: [{ data: [], type: "bar", color: "#5B8FF9 " }],
grid: { left: 40, right: 20, top: 30, bottom: 35, containLabel: true },
tooltip: { trigger: "axis" },
xAxis: {
type: "category",
data: [],
boundaryGap: false,
axisLabel: { color: "#333" },
axisLine: { lineStyle: { color: "#dfe6f5" } },
},
yAxis: {
type: "value",
name: "问题数",
minInterval: 1,
nameTextStyle: { color: "#333" },
axisLabel: { color: "#333" },
splitLine: { lineStyle: { color: "#e8edf6", type: "dashed" } },
},
series: [{
data: [],
type: "line",
symbolSize: 7,
itemStyle: { color: "#5B8FF9" },
lineStyle: { width: 2 },
areaStyle: { color: "rgba(91, 143, 249, 0.12)" },
}],
});
const colors = [
{ color: "var(--success-color)", percentage: 60 },
{ color: "#DC6231", percentage: 80 },
{ color: "var(--danger-color)", percentage: 100 },
];
const colors = [{ color: "#5B8FF9", percentage: 0 }];
watch(
() => [props.idCode, props.modelValue],
@ -300,19 +203,51 @@ async function getProfileData() {
beginTime: time.value.length ? time.value[0] : "",
endTime: time.value.length ? time.value[1] : "",
});
score.value = Math.round(data.score);
days.value = data.days;
negativeList.value = data.negatives;
policeInfo.value = data.policeInfo;
negativeInfo.value = data.negativeInfo;
problemSourcesPieOptions.value.series[0].data = data.problemSourcesList;
problemSourcesPieOptions.value.title.text = String(data.negativeInfo.size ?? 0);
problemTypePieOptions.value.series[0].data = data.problemTypeList;
problemTypePieOptions.value.title.text = String(data.negativeInfo.size ?? 0);
problemTypeBarList.value = data.problemTypeBarList;
} finally {
loading.value = false;
}
}
for (const pieOption of [problemSourcesPieOptions, problemTypePieOptions]) {
Object.assign(pieOption.value, {
color: ["#5B8FF9", "#5AD8A6", "#F6BD16", "#F6903D", "#6DC8EC"],
title: {
text: "0",
subtext: "问题总数",
left: "30%",
top: "39%",
textAlign: "center",
textStyle: { color: "#162582", fontSize: 22, fontWeight: 700 },
subtextStyle: { color: "#333", fontSize: 12 },
},
legend: {
type: "scroll",
orient: "vertical",
right: 0,
top: "middle",
width: "38%",
textStyle: { color: "#333", fontSize: 13, width: 90, overflow: "truncate" },
itemWidth: 10,
itemHeight: 10,
pageTextStyle: { color: "#333" },
},
});
Object.assign(pieOption.value.series[0], {
center: ["30%", "50%"],
radius: ["45%", "65%"],
label: { show: false },
});
}
const negativeShow = ref(false);
const activeNegativeId = ref("");
function handleAction(row) {
@ -321,50 +256,184 @@ function handleAction(row) {
}
</script>
<style lang="scss" scoped>
.col {
--label-width: 60px;
}
.score-progress-body {
text-align: center;
font-size: 18px;
.score-progress_score {
font-size: 50px;
line-height: 70px;
}
.score-progress_title {
font-size: 26px;
line-height: 22px;
}
:global(.el-dialog.police-profile) {
--profile-primary: #162582;
--profile-border: #dfe6f5;
padding: 0;
border: 3px solid #243d9b;
border-radius: 8px;
overflow: hidden;
color: #333;
background: #f9faff;
}
.score-theme {
&[type="success"] {
color: var(--success-color);
}
&[type="warning"] {
color: #e87749;
:global(.police-profile .el-dialog__header) {
margin: 0;
padding: 20px 48px 20px 24px;
background: linear-gradient(100deg, #162582, #1c369f);
border-bottom: 2px solid #77bde9;
}
:global(.police-profile .el-dialog__body) { padding: 0; color: #333; }
:global(.police-profile .el-dialog__headerbtn) { top: 10px; right: 10px; }
:global(.police-profile .el-dialog__close) { color: #fff; }
.profile-heading {
h2 { margin: 0; font-size: 26px; font-weight: 700; color: #fff; line-height: 1.4; }
p { margin: 4px 0 0; font-size: 14px; color: #fff; }
}
.police-profile .profile-heading { text-align: center; }
.profile-toolbar {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 16px;
padding: 16px 22px 18px;
color: #333;
background: #f9faff;
label { font-size: 16px; font-weight: 500; }
.profile-date-field { width: 340px; max-width: 100%; min-width: 0; }
:deep(.el-date-editor.el-input__wrapper) {
width: 100%;
height: 40px;
box-sizing: border-box;
padding: 0 12px;
border-radius: 4px;
background: #fff;
box-shadow: 0 0 0 1px #dce2ef inset;
}
&[type="danger"] {
color: var(--danger-color);
:deep(.el-date-editor.is-active) { box-shadow: 0 0 0 1px #162582 inset; }
:deep(.el-range-input) { min-width: 0; color: #333; font-size: 15px; }
:deep(.el-range-separator) { flex: 0 0 24px; padding: 0; color: #333; font-size: 15px; }
:deep(.el-range__icon) { order: 1; margin: 0 0 0 8px; color: #a5b2ce; }
:deep(.el-range__close-icon) { order: 2; margin-left: 4px; }
.profile-query-button { min-width: 88px; height: 40px; font-size: 15px; border-radius: 4px; }
}
.profile-content {
/* 定义在真实内容节点,避免弹窗根节点的 scoped 样式未命中时丢失颜色。 */
--profile-primary: #162582;
--profile-border: #e2e8f5;
padding: 0 18px 18px;
background: #f9faff;
color: #333;
}
.profile-grid { display: grid; gap: 16px; margin-bottom: 16px; }
.police-profile .profile-overview { grid-template-columns: 1fr 1fr; }
.depart-profile .profile-overview { grid-template-columns: 1.1fr 1fr .8fr; }
.profile-analysis { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.profile-panel {
min-width: 0;
padding: 14px 16px 16px;
border: 1px solid var(--profile-border, #e2e8f5);
border-radius: 6px;
background: linear-gradient(135deg, #f8faff, #fff);
color: #333;
> h5 {
display: flex;
align-items: center;
gap: 10px;
margin: 0 0 16px;
font-size: 18px;
font-weight: 700;
line-height: 1.5;
color: var(--profile-primary, #162582);
&::before { content: ""; width: 4px; height: 20px; border-radius: 2px; background: var(--profile-primary, #162582); flex-shrink: 0; }
}
:deep(.bar-item), :deep(.bar-item-label) { color: #333; font-size: 14px; }
:deep(.bar-item_content) { background: #eaf0fc; }
:deep(.el-empty__description p) { color: #666; }
}
h5 {
margin-bottom: 12px;
.profile-content > .profile-panel { margin-bottom: 16px; }
.profile-content > .profile-panel:last-child { margin-bottom: 0; }
.police-basic { display: flex; align-items: center; gap: 20px; min-height: 240px; }
.police-photo {
width: 140px;
height: 190px;
flex-shrink: 0;
border-radius: 4px;
background: #e5edfc;
border: 1px solid #dbe4f7;
}
.card-item {
background: #f9faff;
padding: 16px;
min-height: 200px;
.photo-placeholder { height: 100%; display: flex; justify-content: center; align-items: center; }
.profile-fields {
flex: 1;
min-width: 0;
margin: 0;
font-size: 15px;
> div { display: flex; gap: 14px; padding: 7px 0; line-height: 1.5; }
dt { width: 72px; flex-shrink: 0; color: #555; }
dd { margin: 0; color: #333; font-weight: 500; overflow-wrap: anywhere; }
}
.depart-fields {
> div { padding: 9px 0; border-bottom: 1px solid #edf0f7; }
> div:last-child { border-bottom: 0; }
dt { width: auto; flex: 1; }
dd { font-weight: 600; text-align: right; }
}
.problem-summary { display: flex; gap: 16px; align-items: center; min-height: 240px; }
.problem-total {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
align-self: stretch;
flex: 0 0 24%;
gap: 12px;
background: linear-gradient(180deg, #f4f6fd, #fafbff);
border-radius: 4px;
strong { font-size: 54px; line-height: 1.1; color: var(--profile-primary, #162582); }
span { font-size: 15px; color: #333; }
}
.business-table {
width: 100%;
border-collapse: collapse;
font-size: 15px;
text-align: center;
background: #fff;
border: 1px solid var(--profile-border, #e2e8f5);
th, td { border: 1px solid var(--profile-border, #e2e8f5); padding: 13px 8px; }
th { background: #ebeefc; color: var(--profile-primary, #162582); font-weight: 600; white-space: nowrap; }
td { color: #333; background: #fff; }
}
.depart-profile {
.problem-summary { flex-direction: column; align-items: stretch; gap: 14px; }
.problem-total { flex-direction: row; flex-basis: auto; justify-content: flex-start; padding: 8px 12px; strong { font-size: 36px; } }
.business-table { th, td { padding: 12px 6px; } }
}
.complaint-summary {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
min-height: 260px;
strong { font-size: 56px; line-height: 1; color: var(--profile-primary); }
span { font-size: 16px; color: #333; }
}
.complaint-icon { width: 46px; height: 46px; color: #5b8ff9; }
.profile-chart { height: 260px; }
.trend-chart { height: 280px; }
.ranking-list { padding: 4px 0; }
.list-count { font-size: 14px; font-weight: 400; color: #333; }
.profile-table {
--el-table-header-bg-color: #ebeefc;
--el-table-header-text-color: #162582;
--el-table-text-color: #333;
--el-table-border-color: #e2e8f5;
border: 1px solid #e2e8f5;
border-radius: 4px;
:deep(.el-table__header .el-table__cell) { padding: 12px 0; color: #162582; background: #ebeefc; font-weight: 600; }
:deep(.el-table__cell) { padding: 10px 0; font-size: 14px; color: #333; border-right: 1px solid #e2e8f5; }
:deep(.el-table__cell:last-child) { border-right: 0; }
}
.h-220 {
height: 220px;
@media (max-width: 1280px) {
.police-basic { gap: 12px; }
.police-photo { width: 110px; height: 150px; }
.profile-fields { font-size: 14px; > div { gap: 8px; } }
.problem-summary { gap: 10px; }
.problem-total strong { font-size: 42px; }
.business-table { font-size: 14px; th, td { padding: 12px 5px; } }
}
.h-320 {
height: 320px;
@media (max-width: 1000px) {
.police-profile .profile-overview, .depart-profile .profile-overview, .profile-analysis { grid-template-columns: 1fr; }
}
</style>

Loading…
Cancel
Save