Selaa lähdekoodia

场景分析

master
suyanyan 1 kuukausi sitten
vanhempi
commit
718545e9ac
7 muutettua tiedostoa jossa 968 lisäystä ja 3 poistoa
  1. +9
    -1
      SafeCampus.WEB/src/routers/modules/staticRouter.ts
  2. +1
    -1
      SafeCampus.WEB/src/views/attendance/roolcall/index.vue
  3. +1
    -1
      SafeCampus.WEB/src/views/userManage/clothing/index.vue
  4. +75
    -0
      SafeCampus.WEB/src/views/violation/analysis/index.vue
  5. +250
    -0
      SafeCampus.WEB/src/views/violation/portrait/detail.vue
  6. +123
    -0
      SafeCampus.WEB/src/views/violation/portrait/index.vue
  7. +509
    -0
      SafeCampus.WEB/src/views/violation/portraitSummary/index.vue

+ 9
- 1
SafeCampus.WEB/src/routers/modules/staticRouter.ts Näytä tiedosto

@@ -43,8 +43,16 @@ export const staticRouter: RouteRecordRaw[] = [
title: "学生点名详情"
},
name: "学生点名详情",
path: "/roolcall/detail",
path: "/attendance/roolcall/detail",
component: () => import("@/views/attendance/roolcall/detail.vue")
},
{
meta: {
title: "学生画像详情"
},
name: "学生画像详情",
path: "/violation/portrait/detail",
component: () => import("@/views/violation/portrait/detail.vue")
}
]
}


+ 1
- 1
SafeCampus.WEB/src/views/attendance/roolcall/index.vue Näytä tiedosto

@@ -91,7 +91,7 @@ const columns: ColumnProps[] = [
];
const onView = (row: any) => {
router.push( {
path:'/roolcall/detail',
path:'/attendance/roolcall/detail',
query: {
taskId: row.taskId,
personSetId:row.personSetId


+ 1
- 1
SafeCampus.WEB/src/views/userManage/clothing/index.vue Näytä tiedosto

@@ -10,7 +10,7 @@
label="clothSetName"
id="clothSetId"
width="300px"
:show-all="true"
:show-all="false"
:request-api="userManageClothApi.getList"
@change="changeTreeFilter"
>


+ 75
- 0
SafeCampus.WEB/src/views/violation/analysis/index.vue Näytä tiedosto

@@ -0,0 +1,75 @@
<!--
* @Description: 学生画像
* @Author: syy
* @Date: 2024-7-24
-->
<template>
<div class="main-box">
<div class="table-box">
<ProTable ref="proTable" title="预警分析" :columns="columns" rowKey="id" :request-api="userManagePersonnelApi.page">
<!-- 表格 header 按钮 -->
<template #tableHeader="scope">
<s-button :opt="FormOptEnums.EXPORT" plain @click="onView" :disabled="!scope.isSelected" />
</template>
</ProTable>
</div>
</div>
</template>
<script setup lang="tsx" name="SysUserPersonnel">
import { userManagePersonnelApi} from "@/api";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import { useRouter } from "vue-router";
const router = useRouter();
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
const proTable = ref<ProTableInstance>();
/** 表单操作类型枚举 */
enum FormOptEnums {
/** 导出 */
EXPORT = "导出",
}
// 表格配置项
const columns: ColumnProps[] = [
{ type: "selection", fixed: "left", width: 50 },
{ type: "index", label: "序号", width: 60, fixed: "left" },
{
prop: "name",
label: "姓名"
},
{
prop: "personId",
label: "人员ID"
},
{
prop: "phone",
label: "手机号"
},
{
prop: "personSetName",
label: "所属班级",
},
];

const onView = (row: any) => {
router.push( {
path:'/violation/portrait/detail',
query: {
personId: row.personId
}
});
};

</script>
<style scoped lang="scss">
.table-box {
width: 100%;
height: 100%;
}
.custom-tree-node {
display: flex;
flex: 1;
align-items: center;
justify-content: space-between;
padding-right: 8px;
font-size: 14px;
}
</style>

+ 250
- 0
SafeCampus.WEB/src/views/violation/portrait/detail.vue Näytä tiedosto

@@ -0,0 +1,250 @@
<template>
<div class="roolcallDetail">
<div class="header">
<div class="left">
<el-image
style="width: 100px; height: 100px"
src="https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg"
:zoom-rate="1.2"
:max-scale="7"
:min-scale="0.2"
:preview-src-list="['https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg']"
:initial-index="4"
fit="cover"
/>
</div>
<div class="right">
<el-row>
<el-col :span="6">
<span>人员编号:{{ formData.personId }}</span></el-col
>
<el-col :span="6"
><span>姓名:{{ formData.name }}</span></el-col
>
<el-col :span="6"
><span>性别:{{ genderOptions[formData.gender] }}</span></el-col
>
<el-col :span="6"
><span>年龄:{{ formData.age }}</span></el-col
>
<el-col :span="6"
><span>联系方式:{{ formData.phone }}</span>
</el-col>
<el-col :span="6"><span>班级:演示一班</span> </el-col>
</el-row>
</div>
</div>
<div class="content">
<div ref="chartEl" style="width: 100%; height: 100%"></div>
</div>
</div>
</template>
<script setup lang="ts" name="ViolationPortraitDetail">
import { userManagePersonnelApi } from "@/api";
const route = useRoute();
const formData = ref<any>({}); //班级人员列表
import * as echarts from "echarts";
const chartEl = ref(null);
const genderOptions = ref<any>({
GENDER_UNKNOWN: "未知",
GENDER_MALE: "男",
GENDER_FEMALE: "女"
});
const getDetail = () => {
userManagePersonnelApi.detail({ id: route.query.personId }).then((res: any) => {
formData.value = res.data;
});
};
const getCharts = (data: any) => {
const chart = echarts.init(chartEl.value);
const nameArr = ["爱好运动", "爱好学习", "比较调皮(爱打闹)", "上课不认真听讲"];
const valueArr = [150, 60, 80, 180];
const option = {
title: {
text: "人员属性标签",
left: "center",
top: "5%",
textStyle: {
color: "#000",
fontSize: 25
}
},
grid: {
top: "18%",
left: "5%",
right: "5%",
bottom: "12%"
},
tooltip: {
trigger: "axis",
backgroundColor: "#3A4667",
borderColor: "#3A4667",
textStyle: {
color: "#fff"
},
formatter: "{b} : {c}",
axisPointer: {
type: "cross",
crossStyle: {
color: "#999"
}
}
},
legend: {
icon: "rect",
top: 10,
right: 5,
itemWidth: 10,
itemHeight: 10,
textStyle: {
fontSize: 12, // 字体大小
// color: '#B3CFFF', // 字体颜色
color: "#FFEB3B"
}
},
xAxis: {
type: "category",
axisPointer: {
type: "shadow"
},
axisLine: {
lineStyle: {
color: "rgba(112, 138, 198, 1)"
}
},
axisLabel: {
textStyle: {
color: "#333", // x轴文本颜色
fontSize: 12
}
// rotate:30,
},
axisTick: {
show: false
},
data: nameArr
},
yAxis: {
type: "value",
splitLine: {
show: true,
lineStyle: {
color: "#333",
type: "dashed"
}
},
axisLabel: {
formatter: "{value}",
textStyle: {
color: "#333", // x轴文本颜色
fontSize: 12
}
},
name: "",
nameTextStyle: {
color: "#333",
fontSize: 12
}
},
series: [
{
yAxisIndex: 0,
color: "rgba(12, 245, 229, 1)",
lineStyle: {
color: "rgba(12, 245, 229, 1)"
},
type: "line",
data: valueArr
},
{
type: "bar",
name: "",
barWidth: 27,
emphasis: {
itemStyle: {
color: "#7fb7e9"
}
},
itemStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 1,
color: "rgba(12, 245, 229, 1)"
// opacity: 0.6
},
{
offset: 0,
color: "rgba(12, 245, 229, 0)"
// opacity: 1
}
])
}
},
data: valueArr
},
{
name: "",
type: "pictorialBar",
itemStyle: {
normal: {
color: "rgba(12, 245, 229, 1)"
}
},
symbol: "rect", // 图形类型,这里是矩形
symbolRotate: 0,
symbolSize: ["27", "3"],
symbolPosition: "end",
data: valueArr,
z: 3
}
]
};

chart.setOption(option);
window.addEventListener("resize", function () {
chart.resize();
});
};
onMounted(() => {
getDetail();
getCharts({});
});
</script>
<style lang="scss" scoped>
.roolcallDetail {
.header {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 180px;
padding: 10px 20px;
background: #ffffff;
.left {
display: flex;
align-items: center;
justify-content: flex-end;
width: 200px;
height: 100%;
}
.right {
width: calc(100% - 200px);
.el-row {
height: 100%;
.el-col {
height: 50px;
line-height: 50px;
text-align: center;
}
}
}
}
.content {
height: 550px;
margin-top: 20px;
background: #ffffff;
}
}
</style>

+ 123
- 0
SafeCampus.WEB/src/views/violation/portrait/index.vue Näytä tiedosto

@@ -0,0 +1,123 @@
<!--
* @Description: 学生画像
* @Author: syy
* @Date: 2024-7-24
-->
<template>
<div class="main-box">
<TreeFilter
ref="treeFilter"
label="personSetName"
id="personSetId"
width="300px"
title="班级管理"
:show-all="true"
:request-api="userManageClassManageApi.page"
@change="changeTreeFilter"
>
<template v-slot:label="{ row }">
<span class="custom-tree-node">
<span>{{ row.node.label }} {{ row.node.userName ? `(${row.node.userName})` : "" }}</span>
</span>
</template>
</TreeFilter>
<div class="table-box">
<ProTable ref="proTable" title="人员列表" :columns="columns" rowKey="id" :request-api="userManagePersonnelApi.page">
<!-- 表格操作栏 -->
<template #operation="scope">
<el-space>
<s-button link :opt="FormOptEnum.VIEW" @click="onView(scope.row)" />
</el-space>
</template>
</ProTable>
</div>
<!-- 预览头像 -->
<el-dialog v-model="visible" title="查看头像" width="830px" :before-close="handleClose">
<div style="display: flex; align-items: center; justify-content: center">
<img style="max-width: 100%; max-height: 600px" class="detailpic" :src="faceUrl" alt="" />
</div>
</el-dialog>
</div>
</template>
<script setup lang="tsx" name="ViolationPortrait">
import { userManagePersonnelApi,userManageClassManageApi} from "@/api";
import { FormOptEnum } from "@/enums";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import TreeFilter from "@/components/TreeFilter/index.vue";
import { useRouter } from "vue-router";
const visible = ref(false); //是否显示人员表单
const faceUrl = ref('');
const router = useRouter();
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
const proTable = ref<ProTableInstance>();
const treeFilter = ref<any>();
// 表格配置项
const columns: ColumnProps[] = [
{
prop: "faceUrl",
label: "人脸",
render: scope => {
return (
<img src={scope.row.faces.length > 0 ? scope.row.faces[0].faceUrl : ''} onClick={() => viewHeadImage(scope)} style='width:50px;height:50px;' alt=''/>
);
}
},
{
prop: "name",
label: "姓名"
},
{
prop: "personId",
label: "人员ID"
},
{
prop: "phone",
label: "手机号"
},
{
prop: "personSetName",
label: "所属班级",
},
{ prop: "operation", label: "操作", width: 250, fixed: "right" }
];
const viewHeadImage = (scope: any) => {
faceUrl.value = scope.row.faces[0].faceUrl;
visible.value = true
};
const handleClose = () => {
visible.value = false;
};

const onView = (row: any) => {
router.push({
path:'/violation/portrait/detail',
query: {
personId: row.personId
}
});
};

/** 部门切换 */
const personSetId = ref<number | string>()
function changeTreeFilter(val: number | string) {
personSetId.value = val
proTable.value!.pageable.pageNum = 1;
proTable.value!.searchParam.personSetId = val;
proTable.value!.search();
}

</script>
<style scoped lang="scss">
.table-box {
width: 100%;
height: 100%;
}
.custom-tree-node {
display: flex;
flex: 1;
align-items: center;
justify-content: space-between;
padding-right: 8px;
font-size: 14px;
}
</style>

+ 509
- 0
SafeCampus.WEB/src/views/violation/portraitSummary/index.vue Näytä tiedosto

@@ -0,0 +1,509 @@
<template>
<div class="home">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="home-bg card">
<div class="home-bg-title">
<div></div>
<div>学生性别</div>
</div>
<div class="home-bg-content">
<div ref="chart1" style="width: 100%; height: 100%"></div>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="home-bg card">
<div class="home-bg-title">
<div></div>
<div>学生年龄</div>
</div>
<div class="home-bg-content">
<div ref="chart2" style="width: 100%; height: 100%"></div>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="home-bg card">
<div class="home-bg-title">
<div></div>
<div>地理位置</div>
</div>
<div class="home-bg-content">
<div ref="chart5" style="width: 100%; height: 100%"></div>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="home-bg card">
<div class="home-bg-title">
<div></div>
<div>标签属性</div>
</div>
<div class="home-bg-content handleBox">
<div ref="chart4" style="width: 100%; height: 100%"></div>
</div>
</div>
</el-col>
</el-row>
</div>
</template>

<script setup lang="ts" name="home">
import * as echarts from "echarts";
import china from "echarts/map/json/china.json"; // 导入china包
const chinaJson = ref<any>(china);
echarts.registerMap("china", chinaJson.value);
const chart1 = ref(null);
const chart2 = ref(null);
const chart3 = ref(null);
const chart4 = ref(null);
const chart5 = ref(null);
onMounted(() => {
getDataChart();
});
function getDataChart() {
getCharts1();
getCharts2();
getCharts4();
getCharts5();
}
/* 性別 */
function getCharts1() {
const chart = echarts.init(chart1.value);
const option = {
tooltip: {
confine: true
},
legend: {
show: true,
right: "0%",
top: "center",
orient: "vertical",
data: ["男", "女"]
},
series: [
{
type: "pie",
radius: "80%",
center: ["50%", "50%"],
roseType: "area",
label: {
normal: {
show: true
},
emphasis: {
show: true
}
},
data: [
{
value: 62,
name: "男"
},
{
value: 100,
name: "女"
}
]
}
]
};

chart.setOption(option);
window.addEventListener("resize", function () {
chart.resize();
});
}
/* 年齡 */
function getCharts2() {
const chartstation = echarts.init(chart2.value);
const option = {
tooltip: {
trigger: "item",
formatter: "{a} <br/>{b} : {c} ({d}%)"
},
calculable: true,
series: [
{
name: "年龄分布",
type: "pie",
radius: [50, 140],

roseType: "area",
data: [
{
value: 10,
name: "<20"
},
{
value: 15,
name: ">60岁"
},
{
value: 25,
name: "50-60岁"
},
{
value: 20,
name: "40-50岁"
},
{
value: 35,
name: "30-40岁"
},
{
value: 30,
name: "20-30岁"
}
]
}
]
};

chartstation.setOption(option);
window.addEventListener("resize", function () {
chartstation.resize();
});
}
/* 兴趣爱好 */
function getCharts4() {
const chartstation = echarts.init(chart4.value);
var dataArr = [100, 200, 300, 400, 500];
var spaceLength = 5,
fixedData = [],
end = 0,
max = 300;
// var categoryData = ys.超市品牌;
var categoryData = ["调皮/爱打闹", "上课不认真听讲", "爱好运动", "爱好画画", "爱好学习"];
var xhao = [1, 2, 3, 4, 5];
var data1 = dataArr.map(item => {
fixedData.push(spaceLength);
return item - spaceLength;
});
if (categoryData.length < 5) {
end = categoryData.length - 1;
} else {
end = 5;
}
const option = {
xAxis: [{ show: false }],

yAxis: [
{
splitLine: {
show: false
},
axisLine: {
//y轴
show: false
},
type: "category",
inverse: true,
axisTick: {
show: false
},

data: categoryData,
axisLabel: {
align: "left",
margin: 170,
formatter: function (value: any, index: any) {
const num = ref<any>("");
var str = "";
num.value = xhao[categoryData.indexOf(value)];
if (index === 0) {
str = "{no1|" + "} {num1|" + num.value + "} {title| " + value + "}";
} else if (index === 1) {
str = "{no2|" + "} {num2|" + num.value + "} {title| " + value + "}";
} else if (index === 2) {
str = "{no3|" + "} {num3|" + num.value + "} {title| " + value + "}";
} else {
str = " {num|" + num.value + "} {title| " + value + "}";
}

return str;
},
rich: {
num: {
color: "#387ec1",
backgroundColor: "#112b67",
width: 10,
height: 10,
fontSize: 14,
padding: [6, 5, 3, 5],
align: "center",
shadowColor: "#3374ba",
borderColor: "#3374ba",
borderWidth: 1
},
num1: {
color: "#51aff8",
backgroundColor: "#112b67",
width: 10,
height: 10,
fontSize: 14,
padding: [7, 5, 3, 5],
align: "center",
shadowColor: "#4db2ff",
borderColor: "#4db2ff",
borderWidth: 1
},
num2: {
color: "#51aff8",
backgroundColor: "#112b67",
width: 10,
height: 10,
fontSize: 14,
padding: [7, 5, 3, 5],
align: "center",
shadowColor: "#4db2ff",
borderColor: "#4db2ff",
borderWidth: 1
},
num3: {
color: "#51aff8",
backgroundColor: "#112b67",
width: 10,
height: 10,
fontSize: 14,
padding: [7, 5, 3, 5],
align: "center",
shadowColor: "#4db2ff",
borderColor: "#4db2ff",
borderWidth: 1
},
title: {
color: "#000"
}
}
}
}
],
dataZoom: [
{
show: false,
type: "slider",
yAxisIndex: 0,
zoomLock: false,
width: 8,
showDetail: false,
startValue: 0, // 从头开始。
endValue: end, // 一次性展示五个。
borderWidth: 0,
borderColor: "transparent",
backgroundColor: "#343F4B",
fillerColor: "#4291CE",
showDataShadow: false,
brushSelect: false,
height: "88%",
handleStyle: {
color: "#4291CE"
},
handleIcon: "path://M512 512m-320 0a320 320 0 1 0 640 0 320 320 0 1 0-640 0Z"
}
],
grid: {
right: "14%",
left: "35%",
top: "4%",
bottom: "1%"
},
series: [
{
name: "",
type: "bar",
barWidth: 12, // 柱子宽度
itemStyle: {
normal: {
color: function (params: any) {
// var colorList = [
// '#7711AF', '#CF77FF', '#AE004F', '#F35872', '#FA7729',
// '#FFC526', '#F8E71C', '#34ADAE', '#3DDFD2', '#A0FFFF'
// ];
var colorListr = [
"#8A64B8",
"#7575D3",
"#5F85DD",
"#6FABE8",
"#7ED3F4",
"#8CD8C0",
"#9CDF8D",
"#BFED6B",
"#EAFE4F",
"#FFFD47",
"#FFEA55",
"#FFCF63",
"#FFA069",
"#fce38a",
"#eaffd0",
"#95e1d3",
"#e3fdfd",
"#749f83",
"#ca8622"
];
return colorListr[params.dataIndex];
},

shadowBlur: 20,
shadowColor: "rgba(40, 40, 40, 0.5)"
}
},
label: {
formatter: function (parms: any) {
return parms.value + spaceLength;
},
show: true,
position: "right", // 位置
color: "#1CD8A8",
fontSize: 14,
fontWeight: "bold", // 加粗
distance: 10 // 距离
},

data: data1
}
]
};

chartstation.setOption(option);
window.addEventListener("resize", function () {
chartstation.resize();
});
}

/* 标签 */
function getCharts5() {
const chartstation5 = echarts.init(chart5.value);
const nameArr = ["杭州", "宁波", "金华", "台州", "嘉兴", "丽水", "温州", "潮州", "绍兴", "衢州"];
const valueArr = [150, 60, 80, 180, 120, 160, 80, 40, 20, 10];
const option = {
grid: {
top: "10%",
left: "8%",
right: "5%",
bottom: "20%"
},
tooltip: {
trigger: "axis",
backgroundColor: "#3A4667",
borderColor: "#3A4667",
textStyle: {
color: "#fff"
},
formatter: "{b} : {c}",
axisPointer: {
type: "cross",
crossStyle: {
color: "#999"
}
}
},
legend: {
icon: "rect",
top: 10,
right: 5,
itemWidth: 10,
itemHeight: 10,
textStyle: {
fontSize: 12, // 字体大小
color: "#B3CFFF" // 字体颜色
}
},
xAxis: {
type: "category",
axisPointer: {
type: "shadow"
},
axisLine: {
lineStyle: {
color: "rgba(112, 138, 198, 1)"
}
},
axisLabel: {
textStyle: {
color: "#B3CFFF", // x轴文本颜色
fontSize: 12
}
// rotate:30,
},
axisTick: {
show: false
},
data: nameArr
},
yAxis: {
type: "value",
splitLine: {
show: true,
lineStyle: {
color: "#162647",
type: "solid"
}
},
axisLabel: {
formatter: "{value}",
textStyle: {
color: "#B3CFFF", // x轴文本颜色
fontSize: 12
}
},
name: "",
nameTextStyle: {
color: "#B3CFFF",
fontSize: 12
}
},
series: [
{
type: "bar",
name: "",
barWidth: 16,
emphasis: {
itemStyle: {
color: "#7fb7e9"
}
},
itemStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 1,
color: "rgba(147, 157, 223, 1)"
},
{
offset: 0,
color: "rgba(147, 157, 223, 0)"
}
])
}
},
data: valueArr
},
{
name: "",
type: "pictorialBar",
itemStyle: {
normal: {
color: "rgba(147, 157, 223, 1)"
}
},
symbolRotate: 0,
symbolSize: ["16", "3"],
symbolPosition: "end",
data: valueArr,
z: 3
}
]
};
chartstation5.setOption(option);
window.addEventListener("resize", function () {
chartstation5.resize();
});
}
</script>


<style scoped lang="scss">
@import "../../home/index.scss";
</style>

Ladataan…
Peruuta
Tallenna