This commit is contained in:
guanpeng 2025-10-17 23:11:55 +08:00
parent 0ea10e1b23
commit 62359b7fb2
32 changed files with 20991 additions and 0 deletions

View File

@ -0,0 +1,60 @@
import useUserStore from '@/store/modules/user'
function authPermission(permission) {
const all_permission = "*:*:*";
const permissions = useUserStore().permissions
if (permission && permission.length > 0) {
return permissions.some(v => {
return all_permission === v || v === permission
})
} else {
return false
}
}
function authRole(role) {
const super_admin = "admin";
const roles = useUserStore().roles
if (role && role.length > 0) {
return roles.some(v => {
return super_admin === v || v === role
})
} else {
return false
}
}
export default {
// 验证用户是否具备某权限
hasPermi(permission) {
return authPermission(permission);
},
// 验证用户是否含有指定权限,只需包含其中一个
hasPermiOr(permissions) {
return permissions.some(item => {
return authPermission(item)
})
},
// 验证用户是否含有指定权限,必须全部拥有
hasPermiAnd(permissions) {
return permissions.every(item => {
return authPermission(item)
})
},
// 验证用户是否具备某角色
hasRole(role) {
return authRole(role);
},
// 验证用户是否含有指定角色,只需包含其中一个
hasRoleOr(roles) {
return roles.some(item => {
return authRole(item)
})
},
// 验证用户是否含有指定角色,必须全部拥有
hasRoleAnd(roles) {
return roles.every(item => {
return authRole(item)
})
}
}

View File

@ -0,0 +1,79 @@
const sessionCache = {
set (key, value) {
if (!sessionStorage) {
return
}
if (key != null && value != null) {
sessionStorage.setItem(key, value)
}
},
get (key) {
if (!sessionStorage) {
return null
}
if (key == null) {
return null
}
return sessionStorage.getItem(key)
},
setJSON (key, jsonValue) {
if (jsonValue != null) {
this.set(key, JSON.stringify(jsonValue))
}
},
getJSON (key) {
const value = this.get(key)
if (value != null) {
return JSON.parse(value)
}
return null
},
remove (key) {
sessionStorage.removeItem(key);
}
}
const localCache = {
set (key, value) {
if (!localStorage) {
return
}
if (key != null && value != null) {
localStorage.setItem(key, value)
}
},
get (key) {
if (!localStorage) {
return null
}
if (key == null) {
return null
}
return localStorage.getItem(key)
},
setJSON (key, jsonValue) {
if (jsonValue != null) {
this.set(key, JSON.stringify(jsonValue))
}
},
getJSON (key) {
const value = this.get(key)
if (value != null) {
return JSON.parse(value)
}
return null
},
remove (key) {
localStorage.removeItem(key);
}
}
export default {
/**
* 会话级缓存
*/
session: sessionCache,
/**
* 本地缓存
*/
local: localCache
}

View File

@ -0,0 +1,79 @@
import axios from 'axios'
import { ElLoading, ElMessage } from 'element-plus'
import { saveAs } from 'file-saver'
import { getToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode'
import { blobValidate } from '@/utils/dkl'
const baseURL = import.meta.env.VITE_APP_BASE_API
let downloadLoadingInstance;
export default {
name(name, isDelete = true) {
var url = baseURL + "/common/download?fileName=" + encodeURIComponent(name) + "&delete=" + isDelete
axios({
method: 'get',
url: url,
responseType: 'blob',
headers: { 'Authorization': 'Bearer ' + getToken() }
}).then((res) => {
const isBlob = blobValidate(res.data);
if (isBlob) {
const blob = new Blob([res.data])
this.saveAs(blob, decodeURIComponent(res.headers['download-filename']))
} else {
this.printErrMsg(res.data);
}
})
},
resource(resource) {
var url = baseURL + "/common/download/resource?resource=" + encodeURIComponent(resource);
axios({
method: 'get',
url: url,
responseType: 'blob',
headers: { 'Authorization': 'Bearer ' + getToken() }
}).then((res) => {
const isBlob = blobValidate(res.data);
if (isBlob) {
const blob = new Blob([res.data])
this.saveAs(blob, decodeURIComponent(res.headers['download-filename']))
} else {
this.printErrMsg(res.data);
}
})
},
zip(url, name) {
var url = baseURL + url
downloadLoadingInstance = ElLoading.service({ text: "正在下载数据,请稍候", background: "rgba(0, 0, 0, 0.7)", })
axios({
method: 'get',
url: url,
responseType: 'blob',
headers: { 'Authorization': 'Bearer ' + getToken() }
}).then((res) => {
const isBlob = blobValidate(res.data);
if (isBlob) {
const blob = new Blob([res.data], { type: 'application/zip' })
this.saveAs(blob, name)
} else {
this.printErrMsg(res.data);
}
downloadLoadingInstance.close();
}).catch((r) => {
console.error(r)
ElMessage.error('下载文件出现错误,请联系管理员!')
downloadLoadingInstance.close();
})
},
saveAs(text, name, opts) {
saveAs(text, name, opts);
},
async printErrMsg(data) {
const resText = await data.text();
const rspObj = JSON.parse(resText);
const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
ElMessage.error(errMsg);
}
}

View File

@ -0,0 +1,18 @@
import tab from './tab'
import auth from './auth'
import cache from './cache'
import modal from './modal'
import download from './download'
export default function installPlugins(app){
// 页签操作
app.config.globalProperties.$tab = tab
// 认证对象
app.config.globalProperties.$auth = auth
// 缓存对象
app.config.globalProperties.$cache = cache
// 模态框对象
app.config.globalProperties.$modal = modal
// 下载文件
app.config.globalProperties.$download = download
}

View File

@ -0,0 +1,102 @@
import { ElMessage, ElMessageBox, ElNotification, ElLoading } from 'element-plus'
let loadingInstance;
export default {
// 消息提示
msg(content) {
let options = {
customClass: 'public-message',
message: content,
plain: true,
}
ElMessage.info(options)
},
// 错误消息
msgError(content) {
let options = {
customClass: 'public-message',
message: content,
plain: true,
}
ElMessage.error(options)
},
// 成功消息
msgSuccess(content) {
let options = {
customClass: 'public-message',
message: content,
plain: true,
}
ElMessage.success(options)
},
// 警告消息
msgWarning(content) {
let options = {
customClass: 'public-message',
message: content,
plain: true,
}
ElMessage.warning(options)
},
// 弹出提示
alert(content) {
ElMessageBox.alert(content, "系统提示")
},
// 错误提示
alertError(content) {
ElMessageBox.alert(content, "系统提示", { type: 'error' })
},
// 成功提示
alertSuccess(content) {
ElMessageBox.alert(content, "系统提示", { type: 'success' })
},
// 警告提示
alertWarning(content) {
ElMessageBox.alert(content, "系统提示", { type: 'warning' })
},
// 通知提示
notify(content) {
ElNotification.info(content)
},
// 错误通知
notifyError(content) {
ElNotification.error(content);
},
// 成功通知
notifySuccess(content) {
ElNotification.success(content)
},
// 警告通知
notifyWarning(content) {
ElNotification.warning(content)
},
// 确认窗体
confirm(content) {
return ElMessageBox.confirm(content, "系统提示", {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: "warning",
})
},
// 提交内容
prompt(content) {
return ElMessageBox.prompt(content, "系统提示", {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: "warning",
})
},
// 打开遮罩层
loading(content) {
loadingInstance = ElLoading.service({
lock: true,
text: content,
background: "rgba(0, 0, 0, 0.7)",
})
},
// 关闭遮罩层
closeLoading() {
loadingInstance.close();
}
}

View File

@ -0,0 +1,69 @@
import useTagsViewStore from '@/store/modules/tagsView'
import router from '@/router'
export default {
// 刷新当前tab页签
refreshPage(obj) {
const { path, query, matched } = router.currentRoute.value;
if (obj === undefined) {
matched.forEach((m) => {
if (m.components && m.components.default && m.components.default.name) {
if (!['Layout', 'ParentView'].includes(m.components.default.name)) {
obj = { name: m.components.default.name, path: path, query: query };
}
}
});
}
return useTagsViewStore().delCachedView(obj).then(() => {
const { path, query } = obj
router.replace({
path: '/redirect' + path,
query: query
})
})
},
// 关闭当前tab页签打开新页签
closeOpenPage(obj) {
useTagsViewStore().delView(router.currentRoute.value);
if (obj !== undefined) {
return router.push(obj);
}
},
// 关闭指定tab页签
closePage(obj) {
if (obj === undefined) {
return useTagsViewStore().delView(router.currentRoute.value).then(({ visitedViews }) => {
const latestView = visitedViews.slice(-1)[0]
if (latestView) {
return router.push(latestView.fullPath)
}
return router.push('/');
});
}
return useTagsViewStore().delView(obj);
},
// 关闭所有tab页签
closeAllPage() {
return useTagsViewStore().delAllViews();
},
// 关闭左侧tab页签
closeLeftPage(obj) {
return useTagsViewStore().delLeftTags(obj || router.currentRoute.value);
},
// 关闭右侧tab页签
closeRightPage(obj) {
return useTagsViewStore().delRightTags(obj || router.currentRoute.value);
},
// 关闭其他tab页签
closeOtherPage(obj) {
return useTagsViewStore().delOthersViews(obj || router.currentRoute.value);
},
// 打开tab页签
openPage(url) {
return router.push(url);
},
// 修改tab页签
updatePage(obj) {
return useTagsViewStore().updateVisitedView(obj);
}
}

View File

@ -0,0 +1,395 @@
<template>
<!-- 信息数据分析 -->
<div class="chartBox">
<div class="search largeScreen">
<el-form :model="queryParams" ref="queryRef" :inline="true" label-width="68px">
<el-row gutter="10">
<el-col :span="8">
<el-form-item label="类型" prop="type" class="w100">
<el-select
class="largeScreen__select w100"
popper-class="largeScreen__select"
v-model="queryParams.type"
placeholder="请选择分析类型"
@change="changeTypeHandle"
>
<el-option
v-for="item in typeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8" v-if="queryParams.type == '1'">
<el-form-item label="年度" prop="year" class="w100">
<el-date-picker
v-model="queryParams.year"
type="year"
placeholder="请选择年份"
/>
</el-form-item>
</el-col>
<el-col :span="8" v-if="queryParams.type == '2'">
<el-form-item label="季度" prop="quarter" class="w100">
<el-select
class="largeScreen__select w100"
popper-class="largeScreen__select"
v-model="queryParams.quarter"
placeholder="请选择分析类型"
>
<el-option
v-for="item in quarterOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8" v-if="queryParams.type == '3'">
<el-form-item label="月度" prop="month" class="w100">
<el-date-picker
v-model="queryParams.month"
type="month"
placeholder="请选择年份"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<!-- <el-button icon="Refresh" @click="resetQuery">重置</el-button> -->
</el-col>
</el-row>
</el-form>
</div>
<Echarts class="echarts" :option="initOption"></Echarts>
</div>
</template>
<script setup>
import {ref, reactive, onMounted, nextTick, computed, getCurrentInstance} from "vue";
import moment from "moment";
import {fitChartSize, fitChartSizeVh} from "@/utils/styleUtils";
const {proxy} = getCurrentInstance();
//
const queryParams = reactive({
// pageNum: 1,
// pageSize: 10,
type: '1',
year: '',
quarter: '',
month: '',
})
const typeOptions = reactive([
{
value: "1",
label: "年度",
},
{
value: "2",
label: "季度",
},
{
value: "3",
label: "月度",
},
]);
const quarterOptions = reactive([
{
value: "1",
label: "第一季度",
},
{
value: "2",
label: "第二季度",
},
{
value: "3",
label: "第三季度",
},
{
value: "4",
label: "第四季度",
},
]);
function changeTypeHandle(e) {
console.log("e",e)
if (e === "1") {
queryParams.year = formatDate(Date.now(), "YYYY");
queryParams.quarter = "";
queryParams.month = "";
}else if(e === "2") {
queryParams.year = "";
queryParams.quarter = "1";
queryParams.month = "";
}else if(e === "3") {
queryParams.year = "";
queryParams.quarter = "";
queryParams.month = formatDate(Date.now(), "YYYY-MM");
}
getList();
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
// function resetQuery() {
// dateRange.value = [];
// proxy.resetForm("queryRef");
// handleQuery();
// }
function getList() {
console.log("queryParams", queryParams)
nextTick(() => {
init();
});
}
function formatDate(str,type="yyyy-MM-dd") {
return moment(str).format(type);
}
const dataMap = reactive({
streetData: [],
streetPieData: [],
});
const minYear = ref("2021");
const maxYear = ref(new Date().getFullYear());
const yearList = ref([]);
const fanZui_options = ref([
{
dictLabel: "危险作业罪",
dictVal: "1",
},
{
dictLabel: "重大责任事故罪",
dictVal: "2",
},
{
dictLabel: "危险驾驶罪",
dictVal: "3",
},
{
dictLabel: "信用卡诈骗罪等",
dictVal: "4",
},
]);
let types = ref("西安市");
const dataList = ref([]);
const xData = ref([]);
const initOption = ref({});
const echartsOptions = ref({});
function randomHandle(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
function getValue(arr, key) {
return arr.map((v) => v[key]);
}
function init() {
var option;
let xData = [...new Set(dataList.value.map((v) => v.street))];
let seriesData = dataList.value.map((v) => ({
value: v.amount,
groupId: v.id,
}));
let seriesObj = {
name: "各辖区统计",
type: "bar",
id: "sales",
data: seriesData,
itemStyle: {
//
color: {
x: 0,
y: 0,
x2: 0,
y2: 1,
type: "linear",
global: false,
colorStops: [
{
offset: 0,
color: "#826fe5",
},
{
offset: 1,
color: "#0c1f5699",
},
],
},
},
};
let varColor = "#D1EDFF";
initOption.value = {
// legend: {
// data: [""],
// textStyle: {
// color: varColor,
// },
// },
tooltip: {
trigger: "item",
axisPointer: {
type: "shadow",
},
borderColor: "rgba(255,255,255,.3)",
backgroundColor: "rgba(13,5,30,.6)",
textStyle: {
color: "white", //
},
},
//
grid: {
top: fitChartSizeVh(70),
left: fitChartSizeVh(100),
right: fitChartSizeVh(100),
bottom: fitChartSizeVh(30),
containLabel: true,
},
xAxis: {
name: "区域",
nameTextStyle: {
color: varColor, // Y
},
data: xData,
axisTick: {
//x线
show: false,
},
axisLine: {
//x
show: false,
},
axisLabel: {
color: varColor,
fontSize: fitChartSize(30),
},
},
yAxis: {
name: "数量",
nameTextStyle: {
color: varColor, // Y
},
splitLine: {
show: true,
lineStyle: {
color: ["#00A8FF"],
type: "dashed",
opacity: 0.5,
},
},
axisLabel: {
color: varColor,
},
},
// dataGroupId: "",
animationDurationUpdate: 500,
series: [
{
...seriesObj,
},
{
data: [],
},
],
graphic: [
{
type: "text",
style: {
text: "",
},
},
],
};
}
function getYearList() {
let arr = [];
for (var year = minYear.value; year <= maxYear.value; year++) {
arr.push(year);
}
yearList.value = arr;
}
function testHandle() {
let arr = [];
let list = [
// ,
"未央区",
"新城区",
"碑林区",
"莲湖区",
"灞桥区",
"雁塔区",
"阎良区",
"临潼区",
"长安区",
"高陵区",
"鄠邑区",
"蓝田县",
"周至县",
];
let list2 = [
// ,
"三桥街道",
"建章路街道",
"六村堡街道",
"未央宫街道",
"汉城街道",
"草滩街道",
"未央湖街道",
"徐家湾街道",
"张家堡街道",
"谭家街道",
"大明宫街道",
"辛家庙街道",
];
let lists = [];
if (types.value == "西安市") {
lists = list;
} else {
lists = list2;
}
lists.forEach((v, i) => {
arr.push({
id: "00" + i,
street: v,
age: randomHandle(20, 25),
amount: randomHandle(30, 55),
index: i,
outsideData: randomHandle(10, 20),
picketAmount: randomHandle(10, 25),
});
});
dataList.value = arr;
}
onMounted(() => {
xData.value = getValue(fanZui_options.value, "dictLabel");
testHandle();
getYearList();
changeTypeHandle('1');
});
</script>
<style lang="scss" scoped>
@use "@/assets/styles/computed.scss" as calculate;
.chartBox {
height: 100%;
.echarts {
height: calc(100% - calculate.vh(50px)) !important;
}
}
</style>

View File

@ -0,0 +1,462 @@
<template>
<!-- 只保留一个图表容器 -->
<div ref="chartContainer" class="chartContainer"></div>
<!-- 模态框部分保持不变 -->
<LargeScreenModal
v-model:open="chargeOpen"
:title="modalTitle"
>
<div class="tableWrap largeScreen">
<el-table
class="tableData"
row-class-name="table-row"
:data="tableData"
@row-click="handleRowClick"
>
<el-table-column
label="名称"
show-overflow-tooltip=""
align="center"
prop="pointName"
/>
<el-table-column
label="值班人员"
show-overflow-tooltip=""
align="center"
prop="dutyPeople"
/>
<el-table-column label="值班联系方式" align="center" prop="dutyPhone" />
<el-table-column
label="地址"
show-overflow-tooltip
align="center"
prop="pointAddress"
/>
<el-table-column
label="经度"
show-overflow-tooltip
align="center"
prop="lng"
/>
<el-table-column
label="纬度"
show-overflow-tooltip
align="center"
prop="lat"
/>
<el-table-column
label="最大承载量"
align="center"
prop="loadBearingMax"
/>
</el-table>
<pagination
class="largeScreen__pagination"
popperClass="largeScreen__pagination"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</div>
</LargeScreenModal>
</template>
<script setup>
import {
ref,
reactive,
onMounted,
onBeforeUnmount,
watch,
getCurrentInstance,
} from "vue";
import * as echarts from "echarts";
import LargeScreenModal from "../largeScreenModal.vue";
import { fitChartSize, fitChartSizeVh } from "@/utils/styleUtils";
import { getallList } from "@/api/InfoOverview.js";
import { getzongshuDetail } from "@/api/map.js";
import { useRouter } from "vue-router";
import { useResizeObserver } from "@vueuse/core";
const router = useRouter();
const { proxy } = getCurrentInstance();
const { sys_yes_no } = proxy.useDict("sys_yes_no");
const props = defineProps({
time: {
type: Date,
required: true,
},
});
//
const chartContainer = ref(null);
let chartInstance = null;
//
const chargeOpen = ref(false);
const modalTitle = ref("");
const queryParams = reactive({
pageNum: 1,
pageSize: 10,
regionname: null,
});
const tableData = ref([]);
const total = ref(0);
//
const processStackedBarData = (rawData) => {
const targetTypes = ["重点场所", "安保力量", "交通枢纽", "景区"];
const regionMap = {};
rawData.forEach((item) => {
const region = item.regionname;
const type = item.typename || "无类型";
if (!regionMap[region]) {
regionMap[region] = {
重点场所: 0,
安保力量: 0,
交通枢纽: 0,
景区: 0,
};
}
if (targetTypes.includes(type)) {
const count = item.loadBearingMax ? parseInt(item.loadBearingMax) : 1;
regionMap[region][type] += count;
}
});
const regions = Object.keys(regionMap);
const seriesData = targetTypes.map((type) => ({
name: type,
type: "bar",
stack: "total",
emphasis: { focus: "series" },
data: regions.map((region) => regionMap[region][type]),
itemStyle: {
color: getSeriesColor(targetTypes.indexOf(type)),
},
}));
return { regions, seriesData };
};
//
const getSeriesColor = (index) => {
const colors = [
new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "#409EFF" },
{ offset: 1, color: "#409EFF99" },
]),
new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "#67C23A" },
{ offset: 1, color: "#67C23AAA" },
]),
new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "#2CBF04" },
{ offset: 1, color: "#2CBF04AA" },
]),
new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "#F56C6C" },
{ offset: 1, color: "#F56C6CAA" },
]),
];
return colors[index % colors.length];
};
const paramsseriesName = ref('');
const paramsname = ref('');
const name1 = ref('');
const name2 = ref('');
//
const initChart = () => {
if (!chartContainer.value) return;
//
if (chartInstance) {
chartInstance.dispose();
}
chartInstance = echarts.init(chartContainer.value);
const option = {
title: {
text: "",
left: "center",
textStyle: {
color: "#D1EDFF",
fontSize: fitChartSize(16),
},
},
dataZoom: [
{
type: 'slider', // dataZoom
show: true,
xAxisIndex: [0],
start: 0, // 0%
end: 30, // 30%6-7
height: fitChartSizeVh(40), //
left: 0,
right: 0,
bottom: fitChartSizeVh(20), //
fillerColor: 'rgba(0, 168, 255, 0.5)', //
borderColor: 'transparent',
handleStyle: {
color: '#00A8FF' //
},
textStyle: {
color: '#D1EDFF' //
}
},
{
type: 'inside', // dataZoom
xAxisIndex: [0],
zoomOnMouseWheel: false, //
moveOnMouseWheel: true, //
moveOnMouseMove: true //
}
],
tooltip: {
trigger: "axis",
className: "toolTip",
formatter: function (params) {
console.log(params);
let str = "";
str += `<div class="chartsBox">
<div class="chartsBox__title">${params[0].axisValue}</div>
<div class="chartsBox__container">`;
params.forEach(v => {
str += `
<div class="chartsBox__container-item flex">
<div class="chartsBox__container-item-label flex">
${v.marker}
${v.seriesName}
</div>
<div class="chartsBox__container-item-value">${v.value}</div>
</div>
`;
})
str += `</div>
</div>
`;
return str
},
},
grid: {
top: fitChartSizeVh(20),
left: fitChartSize(20),
right: fitChartSize(20),
bottom: fitChartSizeVh(80),
containLabel: true,
},
xAxis: {
type: "category",
axisLabel: {
color: "#D1EDFF",
rotate: 30,
fontSize: fitChartSize(28),
formatter: (value) =>
value.length > 4 ? value.substring(0, 4) + "..." : value,
},
axisLine: { lineStyle: { color: "#FFF" } },
axisTick: { show: false },
},
yAxis: {
type: "value",
name: "数量",
nameTextStyle: { color: "#D1EDFF" },
splitLine: {
lineStyle: {
color: ["#FFF"],
type: "dashed",
opacity: 0.5,
},
},
axisLabel: { color: "#D1EDFF", fontSize: fitChartSize(26) },
axisLine: { show: false, lineStyle: { color: "#00A8FF" } },
},
series: [],
};
//
chartInstance.on("click", (params) => {
console.log(params);
paramsseriesName.value = params.seriesName;
paramsname.value = params.name;
let par = {
typename: paramsseriesName.value,
regionname: paramsname.value ,
};
getList()
});
chartInstance.setOption(option);
window.addEventListener("resize", () => {
chartInstance.resize();
});
};
//
const updateChart = (data) => {
if (!chartInstance) return;
const { regions, seriesData } = processStackedBarData(data);
chartInstance.setOption({
xAxis: { data: regions },
series: seriesData,
});
};
//
const handleResize = () => {
if (chartInstance) {
chartInstance.resize();
}
};
//
const fetchData = () => {
const params = {
delFlag: 0,
monitoringType: 2,
};
getallList(params).then((res) => {
updateChart(res.rows);
});
};
//
const getList = () => {
let par = {
typename: paramsseriesName.value,
regionname: paramsname.value,
pageSize:queryParams.pageSize,
pageNum:queryParams.pageNum,
};
getzongshuDetail(par).then((res) => {
tableData.value = res.rows;
total.value = res.total;
modalTitle.value = "大客流信息概览" +'('+ paramsname.value + '-'+ paramsseriesName.value + ')';
chargeOpen.value = true;
});
};
const handleRowClick = (row) => {
console.log(row);
// router.push({
// path: "/BasicInformation/DklMonitoringPoints",
// query: { id: row.id },
// });
};
const handleDetail = (row) => {
console.log(row);
// queryParams.regionname = row.data.groupId;
// getList();
};
//
watch(
() => props.time,
(newTime) => {
console.log("时间变化:", newTime);
fetchData();
}
);
//
onMounted(() => {
initChart();
fetchData();
// 使vueuseuseResizeObserver
useResizeObserver(chartContainer, handleResize);
});
//
onBeforeUnmount(() => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
});
</script>
<style scoped lang="scss">
@use "@/assets/styles/computed.scss" as calculate;
// :deep(.el-table__body-wrapper) {
// background: #29689E !important;
// }
:deep(.cell) {
color: #fff !important;
}
/* 图表容器样式 */
// div[ref="chartContainer"] {
// transition: all 0.3s ease;
// }
.chartContainer {
width: 100%;
height: 100%;
}
.tableWrap {
:deep(td.el-table__cell) {
border-bottom: calculate.px2font(1px) solid #888;
}
:deep(.table-row:hover) {
box-shadow: inset 0 0 0 calculate.px2font(3px) #266FFF;
background-color: transparent;
}
// :deep(tr) {
// background-color: transparent !important;
// }
}
.chartContainer {
&:deep(.toolTip) {
padding: 0 !important;
box-shadow: none !important;
border: 0 !important;
border-radius: calculate.px2font(5px) !important;
.chartsBox {
line-height: 1;
padding: calculate.vh(15px) calculate.vw(15px);
.chartsBox__title {
font-size: calculate.px2font(16px);
margin-bottom: calculate.vh(10px);
}
.chartsBox__container {
.flex {
display: flex;
align-items: center;
}
&-item {
display: flex;
gap: calculate.vw(20px);
justify-content: space-between;
font-size: calculate.px2font(14px);
line-height: inherit;
padding: calculate.vh(5px) 0;
&-label {
gap: calculate.vw(5px);
span {
display: block;
width: calculate.vh(10px) !important;
height: calculate.vh(10px) !important;
border-radius: 50%;
}
}
}
}
}
}
}
</style>

View File

@ -0,0 +1,46 @@
<template>
<el-dropdown class="dropdown" popper-class="largeScreen__dropdown" trigger="click" @command="dropDownHandle">
<span class="el-dropdown-link">
<img src="@/assets/images/largeScreenImage/more@2x.png" alt="">
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="v in options" :command="v.value">
{{ v.label }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script setup>
import { ref, reactive, onMounted, computed, getCurrentInstance} from "vue";
const props = defineProps({
options: {
required: true,
default: () => []
},
})
const emit = defineEmits(["command"]);
function dropDownHandle(e) {
emit("command",e)
}
onMounted(() => {});
</script>
<style lang="scss" scoped>
@use "@/assets/styles/computed.scss" as calculate;
.dropdown {
.el-dropdown-link {
img {
width: calculate.vw(15px);
height: calculate.vw(15px);
cursor: pointer;
}
}
}
</style>

View File

@ -0,0 +1,374 @@
<template>
<!-- 信息数据分析 -->
<div class="chartBox">
<div class="search largeScreen">
<el-form :model="queryParams" ref="queryRef" :inline="true" label-width="68px">
<el-row gutter="10">
<el-col :span="8">
<el-form-item label="类型" prop="type" class="w100">
<el-select
class="largeScreen__select w100"
popper-class="largeScreen__select"
v-model="queryParams.type"
placeholder="请选择分析类型"
@change="changeTypeHandle"
>
<el-option
v-for="item in typeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8" v-if="queryParams.type == '1'">
<el-form-item label="年度" prop="year" class="w100">
<el-date-picker
v-model="queryParams.year"
type="year"
placeholder="请选择年份"
/>
</el-form-item>
</el-col>
<el-col :span="8" v-if="queryParams.type == '2'">
<el-form-item label="季度" prop="quarter" class="w100">
<el-select
class="largeScreen__select w100"
popper-class="largeScreen__select"
v-model="queryParams.quarter"
placeholder="请选择分析类型"
>
<el-option
v-for="item in quarterOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8" v-if="queryParams.type == '3'">
<el-form-item label="月度" prop="month" class="w100">
<el-date-picker
v-model="queryParams.month"
type="month"
placeholder="请选择年份"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<!-- <el-button icon="Refresh" @click="resetQuery">重置</el-button> -->
</el-col>
</el-row>
</el-form>
</div>
<Echarts class="echarts" :option="initOption"></Echarts>
</div>
</template>
<script setup>
import {ref, reactive, onMounted, nextTick, computed, getCurrentInstance} from "vue";
import moment from "moment";
import {fitChartSize, fitChartSizeVh} from "@/utils/styleUtils";
const {proxy} = getCurrentInstance();
//
const queryParams = reactive({
// pageNum: 1,
// pageSize: 10,
type: '1',
year: '',
quarter: '',
month: '',
})
const typeOptions = reactive([
{
value: "1",
label: "年度",
},
{
value: "2",
label: "季度",
},
{
value: "3",
label: "月度",
},
]);
const quarterOptions = reactive([
{
value: "1",
label: "第一季度",
},
{
value: "2",
label: "第二季度",
},
{
value: "3",
label: "第三季度",
},
{
value: "4",
label: "第四季度",
},
]);
function changeTypeHandle(e) {
console.log("e",e)
if (e === "1") {
queryParams.year = formatDate(Date.now(), "YYYY");
queryParams.quarter = "";
queryParams.month = "";
}else if(e === "2") {
queryParams.year = "";
queryParams.quarter = "1";
queryParams.month = "";
}else if(e === "3") {
queryParams.year = "";
queryParams.quarter = "";
queryParams.month = formatDate(Date.now(), "YYYY-MM");
}
getList();
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
// function resetQuery() {
// dateRange.value = [];
// proxy.resetForm("queryRef");
// handleQuery();
// }
function getList() {
console.log("queryParams", queryParams)
nextTick(() => {
init();
});
}
function formatDate(str,type="yyyy-MM-dd") {
return moment(str).format(type);
}
const dataMap = reactive({
streetData: [],
streetPieData: [],
});
const minYear = ref("2021");
const maxYear = ref(new Date().getFullYear());
const yearList = ref([]);
const fanZui_options = ref([
{
dictLabel: "危险作业罪",
dictVal: "1",
},
{
dictLabel: "重大责任事故罪",
dictVal: "2",
},
{
dictLabel: "危险驾驶罪",
dictVal: "3",
},
{
dictLabel: "信用卡诈骗罪等",
dictVal: "4",
},
]);
let types = ref("西安市");
const dataList = ref([]);
const xData = ref([]);
const initOption = ref({});
const echartsOptions = ref({});
function randomHandle(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
function getValue(arr, key) {
return arr.map((v) => v[key]);
}
function init() {
var option;
let xData = [...new Set(dataList.value.map((v) => v.street))];
let seriesData = dataList.value.map((v) => ({
value: v.amount,
groupId: v.id,
}));
let seriesObj = {
name: "各辖区统计",
type: "bar",
id: "sales",
data: seriesData,
itemStyle: {
//
color: {
x: 0,
y: 0,
x2: 0,
y2: 1,
type: "linear",
global: false,
colorStops: [
{
offset: 0,
color: "#826fe5",
},
{
offset: 1,
color: "#0c1f5699",
},
],
},
},
};
let varColor = "#D1EDFF";
initOption.value = {
// legend: {
// data: [""],
// textStyle: {
// color: varColor,
// },
// },
tooltip: {
trigger: "item",
axisPointer: {
type: "shadow",
},
borderColor: "rgba(255,255,255,.3)",
backgroundColor: "rgba(13,5,30,.6)",
textStyle: {
color: "white", //
},
},
//
grid: {
top: fitChartSizeVh(70),
left: fitChartSizeVh(100),
right: fitChartSizeVh(100),
bottom: fitChartSizeVh(30),
containLabel: true,
},
xAxis: {
name: "区域",
nameTextStyle: {
color: varColor, // Y
},
data: xData,
axisTick: {
//x线
show: false,
},
axisLine: {
//x
show: false,
},
axisLabel: {
color: varColor,
fontSize: fitChartSize(30),
},
},
yAxis: {
name: "数量",
nameTextStyle: {
color: varColor, // Y
},
splitLine: {
show: true,
lineStyle: {
color: ["#00A8FF"],
type: "dashed",
opacity: 0.5,
},
},
axisLabel: {
color: varColor,
},
},
// dataGroupId: "",
animationDurationUpdate: 500,
series: [
{
...seriesObj,
},
{
data: [],
},
],
graphic: [
{
type: "text",
style: {
text: "",
},
},
],
};
}
function getYearList() {
let arr = [];
for (var year = minYear.value; year <= maxYear.value; year++) {
arr.push(year);
}
yearList.value = arr;
}
function testHandle() {
let arr = [];
let list = [
"未央区",
"新城区",
"碑林区",
"莲湖区",
"灞桥区",
"雁塔区",
"阎良区",
"临潼区",
"长安区",
"高陵区",
"鄠邑区",
"蓝田县",
"周至县",
];
list.forEach((v, i) => {
arr.push({
id: "00" + i,
street: v,
age: randomHandle(20, 25),
amount: randomHandle(30, 55),
index: i,
outsideData: randomHandle(10, 20),
picketAmount: randomHandle(10, 25),
});
});
dataList.value = arr;
}
onMounted(() => {
xData.value = getValue(fanZui_options.value, "dictLabel");
testHandle();
getYearList();
changeTypeHandle('1');
});
</script>
<style lang="scss" scoped>
@use "@/assets/styles/computed.scss" as calculate;
.chartBox {
height: 100%;
.echarts {
height: calc(100% - calculate.vh(50px)) !important;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,12 @@
// utils/date.js
export const formatDate = (date) => {
const d = new Date(date)
return d.toISOString().split('T')[0] // 返回 YYYY-MM-DD 格式
}
export const getLastMonthToday = () => {
const today = new Date()
const lastMonth = new Date(today)
lastMonth.setMonth(lastMonth.getMonth() - 1)
return lastMonth
}

View File

@ -0,0 +1,554 @@
<template>
<div class="largeScreen">
<el-dialog
class="largeScreen__dialog"
v-model="visible"
:width="width"
:show-close="false"
append-to-body
@opened="openedHandle"
@close="closeHandle"
>
<div class="largeScreen__dialog-content" :style="{ '--height--' : height }">
<div class="header">
<div class="header-title">
<!-- <el-icon size="16"><CopyDocument /></el-icon> -->
<span>{{ title }}</span>
</div>
<div class="header-close" @click="closeHandle">
<el-icon size="24"><Close /></el-icon>
</div>
</div>
<!-- v-if="contentShow" -->
<div class="container">
<slot />
</div>
</div>
<template #footer>
<div></div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import {ref, reactive, onMounted, computed, getCurrentInstance} from "vue";
import { fitChartSize, fitChartSizeVh } from "@/utils/styleUtils";
import styleUtil from "@/utils/styleUtils";
const props = defineProps({
open: {
type: Boolean,
default: false,
},
width: {
type: String || Number,
default: styleUtil.px2vw(900),
},
height: {
type: String || Number,
default: styleUtil.px2vh(700),
},
title: {
required: true,
type: String,
default: "",
},
});
const contentShow = ref(false);
const visible = computed(() => props.open);
function openedHandle() {
contentShow.value = true;
}
// emit
const emit = defineEmits(["update:open"]);
//
const closeHandle = () => {
contentShow.value = false;
emit("update:open", false);
};
defineExpose({
contentShow,
});
</script>
<style lang="scss" scoped>
.container {
width: 100%;
}
</style>
<style lang="scss">
@use "@/assets/styles/computed.scss" as calculate;
//
$shadow-color: #127BDC;
$dialog__title-color: #00f4f5;
$dp-primary-color: #11396A;
$text__color: #F4F4F4;
$pager__bg-color: linear-gradient(rgba(0, 240, 255, 0.1), rgba(13, 172, 255, 0.1));
$pager__bg-color-isActive: #127BDC;
$header__height: calculate.vh(40px);
$clip__size: calculate.px2font(15px);
.largeScreen {
//
&__dialog {
background-color: $dp-primary-color;
// padding: calculate.vh(20px) calculate.vh(30px);
padding: calculate.vh(15px);
border-radius: calculate.px2font(0px);
border: calculate.px2font(1px) solid rgba(14, 255, 255, 0.6);
box-shadow: 0px 0px calculate.px2font(100px) 0 #4790f4;
// box-shadow: inset 0 0 calculate.px2font(20px) $shadow-color; /* */
max-height: fit-content;
clip-path: polygon($clip__size 0, calc(100% - $clip__size) 0, 100% $clip__size, 100% 100%, 100% 100%, 0 100%, 0 100%, 0 $clip__size);
&::before,&::after {
content: "";
bottom: 0;
position: absolute;
z-index: 1;
width: calculate.px2font(25px);
height: calculate.px2font(25px);
border-color: #84E0FF;
border-style: solid;
}
&::before {
left: 0;
border-width: 0 0 calculate.px2font(3px) calculate.px2font(3px);
}
&::after {
right: 0;
border-width: 0 calculate.px2font(3px) calculate.px2font(3px) 0;
}
.el-dialog__header,.el-dialog__footer {
display: none;
}
.show-close {
display: none;
}
&-content {
width: 100%;
// height: var(--height--);
.header {
position: relative;
// border-bottom: calculate.px2font(2px) solid #072a52;
margin-bottom: calculate.px2font(15px);
&-title {
height: $header__height;
color: $dialog__title-color;
font-size: calculate.px2font(16px);
display: flex;
align-items: center;
gap: calculate.px2font(10px);
.el-icon {
font-size: calculate.px2font(20px) !important;
line-height: 1.5;
}
}
&-close {
position: absolute;
top: calculate.vh(10px);
right: 0;
z-index: 9;
border-radius: 50%;
// padding: calculate.vh(3px) calculate.vw(5px);
// background-color: #0c3573;
border: calculate.px2font(1px) solid #D8D8D8;
color: #FFF;
width: calculate.vh(30px);
height: calculate.vh(30px);
display: grid;
place-items: center;
cursor: pointer;
.el-icon {
font-size: calculate.px2font(16px) !important;
font-weight: bold;
line-height: 1;
}
}
}
.container {
height: calc(#{var(--height--)} - calculate.vh(60px));
overflow-y: auto;
&::-webkit-scrollbar {
width: 0;
}
.tableWrap {
height: 100%;
}
}
}
.el-dialog__body {
padding: 0;
overflow-y: visible;
&::-webkit-scrollbar {
width: 0;
}
}
.el-scrollbar__bar {
border-radius: calculate.px2font(9999px);
height: calculate.vh(10px);
}
.el-card {
.el-card__body {
--el-font-size-base: #{calculate.px2font(14px)};
padding: calculate.vh(14px) calculate.vw(20px) calculate.vh(20px) calculate.vw(20px) !important;
h4,p {
margin: calculate.vh(14px) 0;
font-size: calculate.px2font(16px);
}
}
}
.el-timeline {
.el-timeline-item {
.el-timeline-item__node--normal {
--el-timeline-node-size-normal: #{calculate.px2font(12px)};
}
.el-timeline-item__tail {
left: calculate.px2font(4px);
border-left-width: calculate.px2font(2px);
}
}
}
}
.el-table {
--el-table-header-bg-color: #1A87D2;
--el-table-bg-color: #005E99;
--el-table-border-color: transparent;
--el-table-border: 0px solid #043576;
--el-table-text-color: #FFF;
// --el-table-header-text-color: #98C8FF;
--el-table-tr-bg-color: #325b81;
--el-table-row-hover-bg-color: #0dacff4d;
$tableBg: #005E99;
height: calc(100% - calculate.vh(70px)) !important;
font-size: calculate.px2font(14px);
border-radius: 0;
.el-table__inner-wrapper {
height: 100% !important;
background: $tableBg !important;
}
.el-table__header-wrapper th,
.el-table__fixed-header-wrapper th {
font-size: calculate.px2font(16px) !important;
height: calculate.vh(30px) !important;
padding: calculate.vh(10px) calculate.vh(12px);
// background-color: var(--el-table-header-bg-color) !important;
background-color: transparent !important;
color: var(--el-table-header-text-color);
}
thead {
tr {
background: linear-gradient(to bottom, #56CCF2, #2F80ED);
pointer-events: none;
}
}
.cell{
padding: 0 calculate.vw(10px);
line-height: calculate.vh(23px);
}
.el-table__cell{
padding: calculate.vh(15px) 0;
}
.el-table__empty-block {
height: 100%;
}
tr {
background-color: $tableBg;
&:hover {
background-color: $tableBg;
}
}
tr:nth-child(2n) {
background-color: #3C93D2;
&:hover {
background-color: #3C93D2;
}
}
}
.pagination-container {
display: flex;
justify-content: flex-end;
margin: calculate.vh(20px) 0 0 0;
.el-pagination {
$buttonWH: calculate.vh(40px);
--el-pagination-font-size: #{calculate.px2font(14px)};
--el-pagination-button-width: #{$buttonWH};
--el-pagination-button-height: #{$buttonWH};
--el-pagination-border-radius: #{calculate.px2font(3px)};
--el-input-border-radius: #{calculate.px2font(5px)};
--el-input-inner-height: calc(#{calculate.vh(32px)} - #{calculate.vh(2px)});
--el-pagination-item-gap: #{calculate.px2font(12px)};
margin-right: calculate.vw(10px);
button {
// border-radius: var(--el-pagination-border-radius) !important;
padding: 0 calculate.vh(4px);
.el-icon {
font-size: calculate.px2font(18px);
}
}
&.is-background .el-pager li.is-active {
border-radius: var(--el-pagination-border-radius);
}
&.is-background {
.btn-next, .btn-prev, .el-pager li {
margin: 0 calculate.vw(4px);
}
}
&__jump {
.el-pagination__editor.el-input {
width: calculate.vw(56px);
}
.el-input__wrapper {
padding: calculate.vh(1px) calculate.vh(11px);
box-shadow: 0 0 0 calculate.px2font(1px) var(--el-input-border-color,var(--el-border-color)) inset;
border-radius: var(--el-pagination-border-radius);
.el-input__inner {
height: calculate.vh(35px);
font-size: calculate.px2font(14px);
border-radius: var(--el-pagination-border-radius);
}
}
}
.el-select {
width: calculate.vw(128px);
.el-select__wrapper {
min-height: calculate.vh(40px);
line-height: calculate.vh(24px);
font-size: calculate.px2font(14px);
padding: calculate.vh(4px) calculate.vh(12px);
border-radius: calculate.px2font(4px);
}
.el-select__suffix {
.el-icon {
width: calculate.vw(20px);
height: calculate.vh(20px);
line-height: calculate.vh(20px);
font-size: calculate.px2font(14px);
}
}
}
}
}
&__select, &__pagination {
//
&[data-popper-placement^=bottom] .el-popper__arrow::before {
background: #254277;
border: calculate.px2font(1px) solid #254277;
}
.el-select__wrapper {
background-color: $dp-primary-color; //
box-shadow: 0 0 0 calculate.px2font(1px) #254277 inset;
&:hover{
box-shadow: 0 0 0 calculate.px2font(1px) $shadow-color inset;
}
}
.el-select__placeholder {
color: $text__color;
&.is-transparent {
// color: rgb(244, 244, 244, .5);
}
}
&.is-light {
border-color: #00a2ff;
background-color: rgba(0, 120, 233, 0.63) ; //
}
.el-select-dropdown {
&__wrap {
border: none;
// background-color: $dp-primary-color; //
background-color: linear-gradient(0deg, #00B4FF 0%, rgba(0,234,255,0.13) 71%, rgba(1,12,16,0) 100%);
}
&__item {
color: #409eff;
&:hover,&.selcted,&.is-hovering {
background-color: rgba(0,234,255,0.13);
color: $text__color;
font-weight: 700;
}
}
}
}
&__pagination {
.el-input__wrapper {
background-color: $dp-primary-color;
.el-input__inner {
color: $text__color;
}
}
.el-pagination.is-background .btn-next,
.el-pagination.is-background .btn-prev,
.el-pagination.is-background .el-pager li {
background: $pager__bg-color;
}
.el-pagination.is-background .btn-next.is-active,
.el-pagination.is-background .btn-prev.is-active,
.el-pagination.is-background .el-pager li.is-active {
background-color: $pager__bg-color-isActive;
}
.btn-prev, .btn-next, .el-pager li {
color: #FFF;
}
.el-pagination {
&__total,&__jump {
color: $text__color;
}
}
}
&__map-select {
//
&[data-popper-placement^=bottom] .el-popper__arrow::before {
background: #00B4FF;
}
.el-select__wrapper {
background: linear-gradient(0deg, rgba(0,234,255,0.13) 0%, rgba(1,12,16,0) 100%);
box-shadow: none;
border-radius: 0;
&:hover{
box-shadow: none;
}
}
.el-select__suffix {
.el-select__caret {
color: #FFF;
}
}
.el-select__placeholder {
color: #fff;
font-weight: bold;
&.is-transparent {
// color: rgb(244, 244, 244, .5);
color: #fff;
}
}
&.is-light {
border-color: #00a2ff;
background-color: rgba(0, 120, 233, 0.63) ; //
}
.el-select-dropdown {
&__wrap {
border: none;
background-color: #024489; //
}
&__item {
color: #409eff;
&:hover,&.selcted,&.is-hovering {
background-color: #0257A9;
color: $text__color;
font-weight: 700;
}
}
}
.el-cascader-menu {
color: #409eff;
}
.el-cascader-node:not(.is-disabled):focus,
.el-cascader-node:not(.is-disabled):hover {
background-color: rgba(0,234,255,0.13);
color: #FFF;
font-weight: 700;
}
}
//
&__map-cascader {
background-color: #024489 !important;
.el-cascader-node.in-active-path, .el-cascader-node.is-active, .el-cascader-node.is-selectable.in-checked-path {
background-color: #0257a9;
color: #FFF;
}
}
&__dropdown {
&.is-light {
border-color: #00a2ff;
background-color: rgba(0, 120, 233, 0.63) ; //
}
.el-dropdown-menu {
background-color: #024489;
&__item {
color: #409eff;
}
}
.el-dropdown-menu__item:not(.is-disabled):focus,
.el-dropdown-menu__item:not(.is-disabled):hover {
background-color: rgba(0,234,255,0.13);
color: #FFF;
font-weight: 700;
}
.el-popper.is-light .el-popper__arrow:before {
background-color: transparent;
}
}
.el-form {
&-item__label {
color: #FFF;
}
.el-input {
&__wrapper {
background-color: $dp-primary-color;
// box-shadow: 0 0 0 1px var(--el-input-border-color,var(--el-border-color)) inset;
input {
color: #FFF;
}
}
&-group__append, &-group__prepend {
background-color: #409EFF;
color: #fff;
}
}
}
&__descriptions{
font-size: calculate.px2font(16px);
color: #FFF;
.el-descriptions__header {
margin-bottom: calculate.vh(15px);
}
.el-descriptions__body {
background-color: #325b81;
.el-descriptions__table.is-bordered .el-descriptions__cell {
padding: calculate.vh(12px) calculate.vw(15px);
line-height: calculate.vh(25px);
}
.el-descriptions__content.el-descriptions__cell.is-bordered-content {
font-size: inherit;
color: inherit;
background-color: #195590;
}
.el-descriptions__label.el-descriptions__cell.is-bordered-label {
width: calculate.vw(150px);
font-size: inherit;
background-color: #155ea2;
color: #98C8FF;
}
}
}
}
.w100 {
width: 100% !important;
}
</style>

View File

@ -0,0 +1,521 @@
<template>
<div class="largeScreen">
<el-dialog
class="largeScreen__dialog"
v-model="visible"
:width="width"
:show-close="false"
append-to-body
@opened="openedHandle"
@close="closeHandle"
>
<div class="largeScreen__dialog-content" :style="{ '--height--' : height }">
<div class="header">
<div class="header-title">
<el-icon size="16"><CopyDocument /></el-icon>
<span>{{ title }}</span>
</div>
<div class="header-close" @click="closeHandle">
<el-icon size="24"><Close /></el-icon>
</div>
</div>
<!-- v-if="contentShow" -->
<div class="container">
<slot />
</div>
</div>
<template #footer>
<div></div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import {ref, reactive, onMounted, computed, getCurrentInstance} from "vue";
import { fitChartSize, fitChartSizeVh } from "@/utils/styleUtils";
import styleUtil from "@/utils/styleUtils";
const props = defineProps({
open: {
type: Boolean,
default: false,
},
width: {
type: String || Number,
default: styleUtil.px2vw(900),
},
height: {
type: String || Number,
default: styleUtil.px2vh(700),
},
title: {
required: true,
type: String,
default: "",
},
});
const contentShow = ref(false);
const visible = computed(() => props.open);
function openedHandle() {
contentShow.value = true;
}
// emit
const emit = defineEmits(["update:open"]);
//
const closeHandle = () => {
contentShow.value = false;
emit("update:open", false);
};
defineExpose({
contentShow,
});
</script>
<style lang="scss" scoped>
.container {
width: 100%;
}
</style>
<style lang="scss">
@use "@/assets/styles/computed.scss" as calculate;
//
$shadow-color: #127BDC;
$dialog__title-color: #00f4f5;
$dp-primary-color: #00214E;
$text__color: #F4F4F4;
$pager__bg-color: linear-gradient(rgba(0, 240, 255, 0.1), rgba(13, 172, 255, 0.1));
$pager__bg-color-isActive: #127BDC;
$header__height: calculate.vh(50px);
.largeScreen {
//
&__dialog {
background-color: $dp-primary-color;
padding: calculate.vh(20px) calculate.vh(30px);
border-radius: calculate.px2font(15px);
box-shadow: inset 0 0 calculate.px2font(20px) $shadow-color; /* 向内阴影,颜色为深红色 */
max-height: fit-content;
.el-dialog__header,.el-dialog__footer {
display: none;
}
.show-close {
display: none;
}
&-content {
width: 100%;
// height: var(--height--);
.header {
position: relative;
border-bottom: calculate.px2font(2px) solid #072a52;
margin-bottom: calculate.px2font(15px);
&-title {
height: $header__height;
color: $dialog__title-color;
font-size: calculate.px2font(16px);
display: flex;
align-items: center;
gap: calculate.px2font(10px);
.el-icon {
font-size: calculate.px2font(20px) !important;
line-height: 1.5;
}
}
&-close {
position: absolute;
top: calculate.vh(10px);
right: 0;
z-index: 9;
border-radius: calculate.px2font(3px);
padding: calculate.vh(3px) calculate.vw(5px);
background-color: #0c3573;
color: #aaa;
width: calculate.vh(30px);
height: calculate.vh(30px);
display: grid;
place-items: center;
cursor: pointer;
.el-icon {
font-size: calculate.px2font(16px) !important;
line-height: 1;
}
}
}
.container {
height: calc(#{var(--height--)} - calculate.vh(60px));
overflow-y: auto;
&::-webkit-scrollbar {
width: 0;
}
.tableWrap {
height: 100%;
}
}
}
.el-dialog__body {
padding: 0;
overflow-y: visible;
&::-webkit-scrollbar {
width: 0;
}
}
.el-scrollbar__bar {
border-radius: calculate.px2font(9999px);
height: calculate.vh(10px);
}
.el-card {
.el-card__body {
--el-font-size-base: #{calculate.px2font(14px)};
padding: calculate.vh(14px) calculate.vw(20px) calculate.vh(20px) calculate.vw(20px) !important;
h4,p {
margin: calculate.vh(14px) 0;
font-size: calculate.px2font(16px);
}
}
}
.el-timeline {
.el-timeline-item {
.el-timeline-item__node--normal {
--el-timeline-node-size-normal: #{calculate.px2font(12px)};
}
.el-timeline-item__tail {
left: calculate.px2font(4px);
border-left-width: calculate.px2font(2px);
}
}
}
}
.el-table {
--el-table-header-bg-color: #1A87D2;
--el-table-bg-color: #29689E;
--el-table-border-color: transparent;
--el-table-border: 0px solid #043576;
--el-table-text-color: #FFF;
// --el-table-header-text-color: #98C8FF;
--el-table-tr-bg-color: #325b81;
--el-table-row-hover-bg-color: #0dacff4d;
$tableBg: #29689E;
height: calc(100% - calculate.vh(70px)) !important;
font-size: calculate.px2font(14px);
border-radius: calculate.px2font(10px) calculate.px2font(10px) 0 0;
.el-table__inner-wrapper {
height: 100% !important;
background: $tableBg !important;
}
.el-table__header-wrapper th,
.el-table__fixed-header-wrapper th {
font-size: calculate.px2font(16px) !important;
height: calculate.vh(30px) !important;
padding: calculate.vh(10px) calculate.vh(12px);
background-color: var(--el-table-header-bg-color) !important;
color: var(--el-table-header-text-color);
}
.cell{
padding: 0 calculate.vw(10px);
line-height: calculate.vh(23px);
}
.el-table__cell{
padding: calculate.vh(15px) 0;
}
.el-table__empty-block {
height: 100%;
}
tr {
background-color: $tableBg;
&:hover {
background-color: $tableBg;
}
}
tr:nth-child(2n) {
background-color: #3C93D2;
&:hover {
background-color: #3C93D2;
}
}
}
.pagination-container {
display: flex;
justify-content: flex-end;
margin: calculate.vh(20px) 0 0 0;
.el-pagination {
$buttonWH: calculate.vh(40px);
--el-pagination-font-size: #{calculate.px2font(14px)};
--el-pagination-button-width: #{$buttonWH};
--el-pagination-button-height: #{$buttonWH};
--el-pagination-border-radius: #{calculate.px2font(3px)};
--el-input-border-radius: #{calculate.px2font(5px)};
--el-input-inner-height: calc(#{calculate.vh(32px)} - #{calculate.vh(2px)});
--el-pagination-item-gap: #{calculate.px2font(12px)};
margin-right: calculate.vw(10px);
button {
// border-radius: var(--el-pagination-border-radius) !important;
padding: 0 calculate.vh(4px);
.el-icon {
font-size: calculate.px2font(18px);
}
}
&.is-background .el-pager li.is-active {
border-radius: var(--el-pagination-border-radius);
}
&.is-background {
.btn-next, .btn-prev, .el-pager li {
margin: 0 calculate.vw(4px);
}
}
&__jump {
.el-pagination__editor.el-input {
width: calculate.vw(56px);
}
.el-input__wrapper {
padding: calculate.vh(1px) calculate.vh(11px);
box-shadow: 0 0 0 calculate.px2font(1px) var(--el-input-border-color,var(--el-border-color)) inset;
border-radius: var(--el-pagination-border-radius);
.el-input__inner {
height: calculate.vh(35px);
font-size: calculate.px2font(14px);
border-radius: var(--el-pagination-border-radius);
}
}
}
.el-select {
width: calculate.vw(128px);
.el-select__wrapper {
min-height: calculate.vh(40px);
line-height: calculate.vh(24px);
font-size: calculate.px2font(14px);
padding: calculate.vh(4px) calculate.vh(12px);
border-radius: calculate.px2font(4px);
}
.el-select__suffix {
.el-icon {
width: calculate.vw(20px);
height: calculate.vh(20px);
line-height: calculate.vh(20px);
font-size: calculate.px2font(14px);
}
}
}
}
}
&__select, &__pagination {
//
&[data-popper-placement^=bottom] .el-popper__arrow::before {
background: #254277;
border: calculate.px2font(1px) solid #254277;
}
.el-select__wrapper {
background-color: $dp-primary-color; //
box-shadow: 0 0 0 calculate.px2font(1px) #254277 inset;
&:hover{
box-shadow: 0 0 0 calculate.px2font(1px) $shadow-color inset;
}
}
.el-select__placeholder {
color: $text__color;
&.is-transparent {
// color: rgb(244, 244, 244, .5);
}
}
&.is-light {
border-color: #00a2ff;
background-color: rgba(0, 120, 233, 0.63) ; //
}
.el-select-dropdown {
&__wrap {
border: none;
// background-color: $dp-primary-color; //
background-color: linear-gradient(0deg, #00B4FF 0%, rgba(0,234,255,0.13) 71%, rgba(1,12,16,0) 100%);
}
&__item {
color: #409eff;
&:hover,&.selcted,&.is-hovering {
background-color: rgba(0,234,255,0.13);
color: $text__color;
font-weight: 700;
}
}
}
}
&__pagination {
.el-input__wrapper {
background-color: $dp-primary-color;
.el-input__inner {
color: $text__color;
}
}
.el-pagination.is-background .btn-next,
.el-pagination.is-background .btn-prev,
.el-pagination.is-background .el-pager li {
background: $pager__bg-color;
}
.el-pagination.is-background .btn-next.is-active,
.el-pagination.is-background .btn-prev.is-active,
.el-pagination.is-background .el-pager li.is-active {
background-color: $pager__bg-color-isActive;
}
.btn-prev, .btn-next, .el-pager li {
color: #FFF;
}
.el-pagination {
&__total,&__jump {
color: $text__color;
}
}
}
&__map-select {
//
&[data-popper-placement^=bottom] .el-popper__arrow::before {
background: #00B4FF;
}
.el-select__wrapper {
background: linear-gradient(0deg, rgba(0,234,255,0.13) 0%, rgba(1,12,16,0) 100%);
box-shadow: none;
border-radius: 0;
&:hover{
box-shadow: none;
}
}
.el-select__suffix {
.el-select__caret {
color: #FFF;
}
}
.el-select__placeholder {
color: #fff;
font-weight: bold;
&.is-transparent {
// color: rgb(244, 244, 244, .5);
color: #fff;
}
}
&.is-light {
border-color: #00a2ff;
background-color: rgba(0, 120, 233, 0.63) ; //
}
.el-select-dropdown {
&__wrap {
border: none;
background-color: #024489; //
}
&__item {
color: #409eff;
&:hover,&.selcted,&.is-hovering {
background-color: #0257A9;
color: $text__color;
font-weight: 700;
}
}
}
.el-cascader-menu {
color: #409eff;
}
.el-cascader-node:not(.is-disabled):focus,
.el-cascader-node:not(.is-disabled):hover {
background-color: rgba(0,234,255,0.13);
color: #FFF;
font-weight: 700;
}
}
//
&__map-cascader {
background-color: #024489 !important;
.el-cascader-node.in-active-path, .el-cascader-node.is-active, .el-cascader-node.is-selectable.in-checked-path {
background-color: #0257a9;
color: #FFF;
}
}
&__dropdown {
&.is-light {
border-color: #00a2ff;
background-color: rgba(0, 120, 233, 0.63) ; //
}
.el-dropdown-menu {
background-color: #024489;
&__item {
color: #409eff;
}
}
.el-dropdown-menu__item:not(.is-disabled):focus,
.el-dropdown-menu__item:not(.is-disabled):hover {
background-color: rgba(0,234,255,0.13);
color: #FFF;
font-weight: 700;
}
.el-popper.is-light .el-popper__arrow:before {
background-color: transparent;
}
}
.el-form {
&-item__label {
color: #FFF;
}
.el-input {
&__wrapper {
background-color: $dp-primary-color;
// box-shadow: 0 0 0 1px var(--el-input-border-color,var(--el-border-color)) inset;
input {
color: #FFF;
}
}
&-group__append, &-group__prepend {
background-color: #409EFF;
color: #fff;
}
}
}
&__descriptions{
font-size: calculate.px2font(16px);
color: #FFF;
.el-descriptions__header {
margin-bottom: calculate.vh(15px);
}
.el-descriptions__body {
background-color: #325b81;
.el-descriptions__table.is-bordered .el-descriptions__cell {
padding: calculate.vh(12px) calculate.vw(15px);
line-height: calculate.vh(25px);
}
.el-descriptions__content.el-descriptions__cell.is-bordered-content {
font-size: inherit;
color: inherit;
background-color: #195590;
}
.el-descriptions__label.el-descriptions__cell.is-bordered-label {
width: calculate.vw(150px);
font-size: inherit;
background-color: #155ea2;
color: #98C8FF;
}
}
}
}
.w100 {
width: 100% !important;
}
</style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
/* src/assets/css/leaflet.css */
@import 'leaflet/dist/leaflet.css';

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,427 @@
<template>
<div class="perfect-time-slider">
<!-- 日期选择器 -->
<div class="date-picker">
<el-date-picker
v-model="selectedDate"
value-format="YYYY-MM-DD"
type="date"
@change="handleDateChange"
:disabled-date="disabledDate"
placeholder="选择日期:"
></el-date-picker>
</div>
<!-- 时间滑块 -->
<div class="slider-container">
<div class="slider-header">
<span class="current-time">{{ formattedTime }}</span>
<button @click="togglePlay" class="play-button">
{{ isPlaying ? "暂停" : "播放" }}
</button>
</div>
<!-- 带刻度的时间滑块 -->
<div class="slider-wrapper">
<div class="slider-track" :style="trackStyle">
<input
type="range"
v-model="sliderValue"
:min="0"
:max="maxSliderMinutes"
class="slider"
@input="handleSliderChange"
step="1"
:disabled="isFutureDate"
/>
<div class="progress" :style="progressStyle"></div>
</div>
<!-- 刻度标记 -->
<div class="ticks" :style="ticksContainerStyle">
<span
v-for="(tick, index) in ticks"
:key="index"
:style="{ left: tick.position + '%' }"
class="tick"
>
<span class="tick-label">{{ tick.label }}</span>
</span>
</div>
</div>
<!-- 提示信息 -->
<div v-if="isFutureDate" class="future-date-message">
已到达当前时间请选择更早的日期
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onBeforeUnmount } from "vue";
import { getTimeLine } from "@/api/map.js";
const props = defineProps({
playSpeed: {
type: Number,
default: 3000,
},
slideHours: {
type: Number,
default: 4,
},
});
const emit = defineEmits(["timeChange"]);
//
const isPlaying = ref(false);
const sliderValue = ref(0);
const playInterval = ref(null);
const selectedDate = ref(getFormattedDate(new Date()));
const currentTime = ref(new Date());
const now = ref(new Date());
const canAutoPlay = ref(false); //
//
const maxSliderMinutes = computed(() => {
if (isToday.value) {
return now.value.getHours() * 60 + now.value.getMinutes();
}
return 24 * 60;
});
const trackStyle = computed(() => ({
width: `${(maxSliderMinutes.value / (24 * 60)) * 100}%`,
}));
const ticksContainerStyle = computed(() => ({
width: `${(maxSliderMinutes.value / (24 * 60)) * 100}%`,
}));
const progressStyle = computed(() => ({
width: `${(sliderValue.value / maxSliderMinutes.value) * 100}%`,
}));
const formattedTime = computed(() => formatDateTime(currentTime.value));
const ticks = computed(() => {
const ticks = [];
const totalMinutes = maxSliderMinutes.value;
const hourSteps = isToday.value ? 2 : 2; // 2
for (let hour = 0; hour <= 24; hour += hourSteps) {
const minutes = hour * 60;
if (minutes > totalMinutes) break;
ticks.push({
position: (minutes / totalMinutes) * 100,
label: `${hour.toString().padStart(2, "0")}:00`,
});
}
return ticks;
});
const isToday = computed(() => {
return selectedDate.value === getFormattedDate(new Date());
});
const isFutureDate = computed(() => {
return new Date(selectedDate.value) > new Date(getFormattedDate(new Date()));
});
const maxSelectableDate = computed(() => {
return getFormattedDate(new Date());
});
//
const handleDateChange = () => {
const [year, month, day] = selectedDate.value.split("-");
const newDate = new Date(year, month - 1, day, 0, 0, 0);
updateTime(newDate);
sliderValue.value = 0;
canAutoPlay.value = true; //
if (isFutureDate.value && isPlaying.value) {
togglePlay();
}
};
// YYYY-MM-DD
function getFormattedDate(date) {
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, "0");
const day = date.getDate().toString().padStart(2, "0");
return `${year}-${month}-${day}`;
}
//
const formatDateTime = (date) => {
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const hours = date.getHours().toString().padStart(2, "0");
const minutes = date.getMinutes().toString().padStart(2, "0");
const seconds = date.getSeconds().toString().padStart(2, "0");
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
//
const handleSliderChange = () => {
const hours = Math.floor(sliderValue.value / 60);
const minutes = sliderValue.value % 60;
const newTime = new Date(selectedDate.value);
newTime.setHours(hours, minutes);
//
if (isToday.value && newTime > now.value) {
newTime.setTime(now.value.getTime());
updateSliderPosition(newTime);
}
updateTime(newTime);
canAutoPlay.value = true; //
};
//
const updateTime = (newTime) => {
currentTime.value = newTime;
console.log(formatDateTime(newTime));
localStorage.setItem("timeValue", formatDateTime(newTime));
emit("timeChange", newTime);
};
//
const slideToNext = () => {
const newTime = new Date(currentTime.value);
newTime.setHours(newTime.getHours() + props.slideHours);
//
if (isToday.value && newTime > now.value) {
//
if (isPlaying.value) {
togglePlay();
}
canAutoPlay.value = false; //
return;
}
// 23:59:59
if (!isToday.value && newTime.getDate() !== currentTime.value.getDate()) {
//
if (isPlaying.value) {
togglePlay();
}
canAutoPlay.value = false; //
return;
}
updateTime(newTime);
updateSliderPosition(newTime);
};
function disabledDate(time) {
//
const today = new Date().setHours(0, 0, 0, 0);
//
const dateTime = time.getTime();
//
return dateTime > today;
}
//
const updateSliderPosition = (time) => {
sliderValue.value = time.getHours() * 60 + time.getMinutes();
};
//
const togglePlay = () => {
// canAutoPlay
isPlaying.value = !isPlaying.value;
if (isPlaying.value) {
playInterval.value = setInterval(slideToNext, props.playSpeed);
} else {
clearInterval(playInterval.value);
playInterval.value = null;
}
};
//
const updateNow = () => {
now.value = new Date();
};
//
onMounted(() => {
const startOfDay = new Date(selectedDate.value);
startOfDay.setHours(0, 0, 0, 0);
updateTime(startOfDay);
updateSliderPosition(startOfDay);
setInterval(updateNow, 60000);
});
onBeforeUnmount(() => {
if (playInterval.value) {
clearInterval(playInterval.value);
}
});
</script>
<style lang="scss" scoped>
.perfect-time-slider {
width: 100%;
margin: 0 auto;
padding: 6%;
font-family: Arial, sans-serif;
background-image: url("../../../assets/images/timeline.png");
background-size: cover;
background-repeat: no-repeat;
background-position: center;
}
.date-picker {
margin-bottom: 4px;
::v-deep .el-input__wrapper {
background: #ebf5ff;
}
}
.date-picker label {
margin-right: 10px;
font-weight: bold;
}
.date-picker input {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1em;
}
.slider-container {
height: 100px;
border-radius: 8px;
position: relative;
}
.slider-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.current-time {
font-size: 1.1em;
font-weight: bold;
color: #ebf5ff;
}
.play-button {
padding: 8px 20px;
background-color: #ebf5ff;
color: #409eff;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1em;
transition: background-color 0.3s;
margin-bottom: 14px;
}
.play-button:hover {
background-color: #ebf5ff;
}
.slider-wrapper {
position: relative;
// margin: 40px 0 20px;
}
.slider-track {
position: relative;
height: 10px;
background: #4a5d79;
border-radius: 5px;
overflow: hidden;
}
.slider {
position: absolute;
width: 100%;
height: 100%;
margin: 0;
-webkit-appearance: none;
appearance: none;
background: transparent;
outline: none;
z-index: 3;
cursor: pointer;
}
.progress {
position: absolute;
left: 0;
top: 0;
height: 100%;
background: #4a5d79;
z-index: 1;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
background: #0b3054;
cursor: pointer;
border-radius: 50%;
border: 2px solid #4a5d79;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
z-index: 4;
position: relative;
}
.slider:disabled::-webkit-slider-thumb {
background: #cccccc;
cursor: not-allowed;
}
.slider::-moz-range-thumb {
width: 20px;
height: 20px;
background: #4caf50;
cursor: pointer;
border-radius: 50%;
border: 2px solid white;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
z-index: 4;
}
.ticks {
position: absolute;
top: 25px;
height: 10px;
}
.tick {
position: absolute;
width: 1px;
height: 10px;
background: #fff;
transform: translateX(-50%);
}
.tick-label {
position: absolute;
top: 15px;
left: 50%;
transform: translateX(-50%);
font-size: 0.8em;
color: #fff;
}
.future-date-message {
margin-top: 10px;
color: #ff5722;
font-weight: bold;
text-align: center;
}
</style>

View File

@ -0,0 +1,473 @@
<template>
<div class="monitor-container">
<!-- 分屏控制 -->
<div class="control-bar">
<el-tag
class="tags"
v-for="item in viewModes"
:key="item.value"
size="large"
:effect="activeMode === item.value ? 'dark' : ''"
:type="activeMode === item.value ? 'primary' : ''"
@click="handleViewModeChange(item.value)"
>
{{ item.label }}
</el-tag>
<!-- <el-select
v-model="selectValue"
filterable
placeholder="请选择部门"
class="dept-select custam-select"
popper-class="custam-select-pupper"
@change="handleDeptChange"
>
<el-option
v-for="item in bmList"
:key="item.deptid"
:label="item.deptname"
:value="item.deptid"
/>
</el-select> -->
<el-select
v-model="selectedCameraCodes"
multiple
filterable
placeholder="请选择监控点"
class="camera-select custam-select"
:disabled="!selectValue"
@change="handleCameraChange"
style="width: 100%"
popper-class="custam-select-pupper"
>
<el-option
v-for="item in availableCameras"
:key="item.code"
:label="item.name"
:value="item.code"
@click.stop="handleOptionClick(item)"
>
<span :class="{ 'no-stream': !item.depturl }">
{{ item.name }}
<span
v-if="!item.depturl && loadingCameras.includes(item.code)"
class="loading-text"
>
(加载中...)
</span>
</span>
</el-option>
</el-select>
</div>
<!-- 播放器容器 -->
<div id="player" :data-mode="activeMode"></div>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, computed, watch } from "vue";
import {
getDeptOfWwsList,
getDeptOfWwsUrlList,
getvideoWws,
} from "@/api/camera.js";
import { ElMessage } from "element-plus";
const viewModes = [
{ value: 4, label: "四组" },
// { value: 9, label: "" },
];
const activeMode = ref(4);
const currentType = ref(0);
let player = null;
const videoUrls = ref([]);
const cameraList = ref([]);
const selectedCameraCodes = ref([]);
const loadingCameras = ref([]);
const bmList = ref([]);
const selectValue = ref("");
//
const availableCameras = computed(() => {
return cameraList.value.filter((item) => item.deptid === selectValue.value);
});
//
const handleOptionClick = async (camera) => {
//
if (camera.depturl || loadingCameras.value.includes(camera.code)) return;
//
const isSelected = selectedCameraCodes.value.includes(camera.code);
//
if (!isSelected && selectedCameraCodes.value.length >= activeMode.value) {
ElMessage.warning(`最多只能选择${activeMode.value}个监控点`);
return;
}
try {
loadingCameras.value.push(camera.code);
const res = await getvideoWws({
typeinfo: camera.filePath || camera.code,
});
const depturl = res.msg;
if (!depturl) {
throw new Error("接口返回的视频流URL为空");
}
//
const index = cameraList.value.findIndex(
(item) => item.code === camera.code
);
if (index !== -1) {
cameraList.value[index].depturl = depturl;
//
if (!isSelected) {
selectedCameraCodes.value = [...selectedCameraCodes.value, camera.code];
}
initPlayer();
}
} catch (error) {
console.error("获取视频流失败:", error);
ElMessage.error(`获取视频流失败: ${error.message}`);
} finally {
loadingCameras.value = loadingCameras.value.filter(
(item) => item !== camera.code
);
}
};
//
const handleCameraChange = (selectedCodes) => {
//
const uniqueCodes = [...new Set(selectedCodes)];
const maxSelect = activeMode.value;
if (uniqueCodes.length > maxSelect) {
selectedCameraCodes.value = uniqueCodes.slice(0, maxSelect);
ElMessage.warning(`最多只能选择${maxSelect}个监控点`);
return;
}
selectedCameraCodes.value = uniqueCodes;
initPlayer();
};
//
const fetchCameraList = async () => {
if (!selectValue.value) return;
const par = {
deptid: selectValue.value,
type: currentType.value,
};
try {
const res = await getDeptOfWwsUrlList(par);
cameraList.value = res.rows.map((item) => ({
...item,
filePath: item.filePath || item.code,
}));
// N
const maxSelect = activeMode.value;
const validCameras = cameraList.value.filter((item) => item.depturl);
selectedCameraCodes.value = validCameras
.slice(0, maxSelect)
.map((item) => item.code);
initPlayer();
} catch (error) {
console.error("获取监控点列表失败:", error);
ElMessage.error("获取监控点列表失败");
}
};
//
const handleDeptChange = () => {
selectedCameraCodes.value = [];
fetchCameraList();
};
//
const handleViewModeChange = async (mode) => {
activeMode.value = mode;
currentType.value = mode === 9 ? 1 : 0;
//
if (selectedCameraCodes.value.length > mode) {
selectedCameraCodes.value = selectedCameraCodes.value.slice(0, mode);
}
await fetchCameraList();
initPlayer();
};
//
const initPlayer = () => {
try {
//
const validCameras = availableCameras.value.filter(
(item) => selectedCameraCodes.value.includes(item.code) && item.depturl
);
const urls = validCameras.map((item) => item.depturl);
if (urls.length === 0) {
console.log("没有可播放的视频流");
if (player) {
player.JS_StopRealPlayAll();
player.JS_Destroy();
player = null;
}
return;
}
if (player) {
player.JS_StopRealPlayAll();
player.JS_Destroy();
}
player = new JSPlugin({
szId: "player",
szBasePath: "js/",
iMaxSplit: activeMode.value,
openDebug: true,
mseWorkerEnable: false,
bSupporDoubleClickFull: true,
supportGMProtocol: true, //
oStyle: {
borderSelect: "#343434",
},
});
player.JS_ArrangeWindow(activeMode.value);
const videosToPlay = Math.min(activeMode.value, urls.length);
for (let i = 0; i < videosToPlay; i++) {
player.JS_Play(
urls[i],
{
playURL: urls[i],
mode: 0,
keepDecoder: 0,
token: "",
},
i
);
}
} catch (error) {
console.error("初始化播放器失败:", error);
ElMessage.error("播放器初始化失败");
}
};
//
const getDeptList = () => {
getDeptOfWwsList()
.then((res) => {
console.log(res, "sdsdasadsa");
bmList.value = res.rows;
if (bmList.value.length > 0) {
selectValue.value = bmList.value[0].deptid;
handleDeptChange();
}
})
.catch((error) => {
console.error("获取部门列表失败:", error);
ElMessage.error("获取部门列表失败");
});
};
//
watch(selectedCameraCodes, (newVal) => {
console.log("当前选中监控点:", newVal);
});
onMounted(() => {
getDeptList();
});
onBeforeUnmount(() => {
if (player) {
player.JS_StopRealPlayAll();
player.JS_Destroy();
}
});
</script>
<style lang="scss" scoped>
@use "@/assets/styles/computed.scss" as calculate;
$inputHeight: calculate.vh(45px);
$selectBg: #16437F;
:deep(.el-select__wrapper) {
width: 100%;
height: $inputHeight;
overflow: hidden;
min-height: $inputHeight;
line-height: $inputHeight;
/* padding: 6%; */
background: transparent;
color: #1890ff;
background-color: $selectBg;
border-radius: calculate.px2font(5px);
}
.tags {
height: $inputHeight;
border-radius: calculate.px2font(5px);
font-size: calculate.px2font(14px);
padding: 0 calculate.vw(10px);
border-width: calculate.px2font(1px);
}
.monitor-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
// padding: 20px;
box-sizing: border-box;
}
.control-bar,
.select-container {
margin-bottom: calculate.vh(15px);
// padding: 10px;
display: flex;
gap: calculate.vw(15px);
// background: #09375b;
border-radius: calculate.px2font(4px);
box-shadow: 0 calculate.px2font(1px) calculate.px2font(4px) rgba(0, 0, 0, 0.1);
}
.select-container {
// padding: 10px;
align-items: center;
:deep(.el-select__wrapper) {
width: 100%;
// height: 60px;
overflow: hidden;
line-height: calculate.vh(40px);
/* padding: 6%; */
background: $selectBg;
color: #1890ff;
}
}
.camera-select {
width: 100%;
}
#player {
flex: 1;
background: #000;
border-radius: 4px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.no-stream {
color: #999;
}
.loading-text {
color: #1890ff;
font-size: calculate.px2font(12px);
margin-left: calculate.vw(4px);
}
/* 四组模式 - 2x2 */
#player[data-mode="4"] :deep(> div > div) {
width: 50% !important;
height: 50% !important;
float: left !important;
}
/* 九组模式 - 3x3 */
#player[data-mode="9"] :deep(> div > div) {
width: 33.333% !important;
height: 33.333% !important;
float: left !important;
}
/* 确保视频填满每个格子 */
#player :deep(> div > div > video) {
width: 100% !important;
height: 100% !important;
object-fit: cover !important;
}
</style>
<style lang="scss">
@use "@/assets/styles/computed.scss" as calculate;
/* 下拉框样式 */
.camera-select-dropdown {
max-height: calculate.vh(400px) !important;
overflow-y: auto !important;
scrollbar-width: thin;
scrollbar-color: #4a6b8b #1e3a5f;
background-color: #1e3a5f !important;
border: 1px solid #09375b !important;
}
.camera-select-dropdown::-webkit-scrollbar {
width: 6px;
}
.camera-select-dropdown::-webkit-scrollbar-track {
background: #1e3a5f;
border-radius: 3px;
}
.camera-select-dropdown::-webkit-scrollbar-thumb {
background-color: #4a6b8b;
border-radius: 3px;
}
.camera-select-dropdown .el-select-dropdown__item {
padding: 0 20px;
height: 36px;
line-height: 36px;
color: #c9d9f3 !important;
background-color: #1e3a5f !important;
}
.camera-select-dropdown .el-select-dropdown__item.hover,
.camera-select-dropdown .el-select-dropdown__item:hover {
background-color: #2c4a6e !important;
}
.camera-select-dropdown .el-select-dropdown__item.selected {
color: #ffffff !important;
background-color: #09375b !important;
font-weight: 500;
}
/* 多选框选中标签样式 */
.el-select__tags {
padding-left: 10px;
}
// .el-tag {
// background-color: #12457C !important;
// border-color: #4a6b8b !important;
// color: #c9d9f3 !important;
// }
.el-tag .el-icon-close {
color: #c9d9f3 !important;
}
.el-tag .el-icon-close:hover {
background-color: #4a6b8b !important;
color: #ffffff !important;
}
</style>

View File

@ -0,0 +1,968 @@
<template>
<div class="wrap">
<div class="header">
<div class="bgtitle">
<!-- <img src="../../assets/logo/dkl.png" alt="logo" /> -->
<img src="../../assets/images/largeScreenImage/dp-logo.png" alt="">
<span>大客流一张图</span>
</div>
</div>
<div class="footer"></div>
<div class="largeScreen__btn">
<div class="rights">
<router-link to="/index">
<img
src="../../assets/images/sy.png"
alt="首页"
class="icon"
/>
</router-link>
<img
src="../../assets/images/qp.png"
alt="全屏"
class="icon hover-effect"
@click="toggleFullscreen"
/>
<router-link to="/index" class="flex">
<span>进入系统</span>
<img
src="../../assets/images/fh.png"
alt="返回"
class="back"
/>
</router-link>
</div>
</div>
<!-- 模态框部分 -->
<LargeScreenModal v-model:open="chargeOpen" :title="modalTitle">
<Analysis v-if="caseTab == '1'"></Analysis>
</LargeScreenModal>
<LargeScreenModal v-model:open="chargeOpen2" :title="modalTitle">
<!-- 内容 -->
</LargeScreenModal>
<LargeScreenModal v-model:open="chargeOpen3" :title="modalTitle">
<RiskAnalysis v-if="caseTab3 == '1'"></RiskAnalysis>
</LargeScreenModal>
<LargeScreenModal v-model:open="chargeOpen4" :title="modalTitle">
<!-- 内容 -->
</LargeScreenModal>
<div class="container">
<div class="container__top">
<div class="left flexcolumns">
<div class="card BarWrap">
<div class="card__title flex-btn">
<div>大客流总数区域展示</div>
</div>
<div class="card__content noBg">
<div class="titles">大客流总数区域展示</div>
<InfoOverview :time="newTime"></InfoOverview>
</div>
</div>
<div class="card BarWrap">
<div class="card__title flex-btn">
<div>大客流风险预警信息</div>
</div>
<div class="card__content">
<div class="titles">待处理预警信息</div>
<!-- 监听EarlyWarning组件的事件 -->
<EarlyWarning :time="newTime" @update-map-params="handleMapUpdate" ></EarlyWarning>
</div>
</div>
</div>
<div class="middle">
<!-- 地图组件参数绑定 - 添加updateTrigger确保更新 -->
<Map
:screenId="SYid"
:latitude="currentLatitude"
:longitude="currentLongitude"
:tabName="tabName"
:monitoringTypeName="currentMonitoringType"
:update-trigger="updateTrigger"
></Map>
</div>
<div class="right flexcolumns">
<div class="card BarWrap">
<div class="card__title flex-btn">
<div>视频监控</div>
</div>
<div class="card__content noBg">
<div class="titles">视频监控</div>
<VideoSurveillance></VideoSurveillance>
</div>
</div>
<div class="card BarWrap">
<div class="card__title flex-btn">
<div>处置状态监控</div>
</div>
<div class="card__content">
<div class="titles">处置状态监控</div>
<MonitoringPoints></MonitoringPoints>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, computed, getCurrentInstance, watch } from "vue";
import Moment from "moment";
import { useRouter, useRoute } from "vue-router";
import EnhancedTimeSlider from "./components/timeline.vue";
const route = useRoute();
const SYid = route.query.screenId;
//
const updateTrigger = ref(0);
//
const routeLatitude = ref(route.query.latitude);
const routeLongitude = ref(route.query.longitude);
const tabName = ref(route.query.tabName);
const routeMonitoringType = ref(route.query.monitoringTypeName);
//
const currentLatitude = ref(routeLatitude.value);
const currentLongitude = ref(routeLongitude.value);
const currentMonitoringType = ref(routeMonitoringType.value);
//
const formatDateTime = (date) => {
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const hours = date.getHours().toString().padStart(2, "0");
const minutes = date.getMinutes().toString().padStart(2, "0");
const seconds = date.getSeconds().toString().padStart(2, "0");
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
const activeNames = ref(["1"]);
const DaystartTime = ref("");
const DayendTime = ref("");
const newTime = ref("");
const handleTimeChange = (timeValue) => {
newTime.value = formatDateTime(timeValue);
const datePart = newTime.value.split(" ")[0];
DaystartTime.value = datePart + " 00:00:00";
DayendTime.value = datePart + " 23:59:59";
};
//
import LargeScreenModal from "./components/largeScreenModal.vue";
import InfoOverview from "./components/InfoOverview/index.vue";
import Analysis from "./components/InfoOverview/components/analysis.vue";
import EarlyWarning from "./components/earlyWarning/index.vue";
import RiskAnalysis from "./components/earlyWarning/components/riskAnalysis.vue";
import VideoSurveillance from "./components/videoSurveillance/index.vue";
import MonitoringPoints from "./components/monitoringPoints/index.vue";
import DropdownItem from "./components/dropdownItem.vue";
import Map from "./components/map/index.vue";
const imgList = ref([
{ src: new URL("../../assets/images/x2.jpg", import.meta.url).href },
{ src: new URL("../../assets/images/x1.jpg", import.meta.url).href },
{ src: new URL("../../assets/images/x3.jpg", import.meta.url).href },
{ src: new URL("../../assets/images/x4.jpg", import.meta.url).href },
{ src: new URL("../../assets/images/x5.jpg", import.meta.url).href },
{ src: new URL("../../assets/images/x6.jpg", import.meta.url).href },
{ src: new URL("../../assets/images/x8.jpg", import.meta.url).href },
]);
const { proxy } = getCurrentInstance();
let router = useRouter();
//
const chargeOpen = ref(false);
const modalTitle = ref("");
const chargeOpen2 = ref(false);
const chargeOpen3 = ref(false);
const chargeOpen4 = ref(false);
const caseTab = ref("1");
const caseTab2 = ref("1");
const caseTab3 = ref("1");
const caseTab4 = ref("1");
const caseTab_options = reactive([{ value: "1", label: "大客流信息数据分析" }]);
const caseTab2_options = reactive([
{ value: "1", label: "日历展示" },
{ value: "2", label: "普法学习" },
]);
const caseTab3_options = reactive([
{ value: "1", label: "大客流风险预警分析" },
]);
const caseTab4_options = reactive([{ value: "1", label: "模型碰撞" }]);
function getDate(time, type = "YYYY-MM-DD") {
let date = time || new Date().getTime();
return Moment(date).format(type);
}
// EarlyWarning
const handleMapUpdate = (params) => {
//
currentLatitude.value = params.latitude;
currentLongitude.value = params.longitude;
currentMonitoringType.value = params.monitoringTypeName;
//
updateTrigger.value = Date.now();
//
router.replace({
query: {
...route.query,
latitude: params.latitude,
longitude: params.longitude,
monitoringTypeName: params.monitoringTypeName
}
});
};
//
watch(
() => [route.query.latitude, route.query.longitude, route.query.monitoringTypeName],
([lat, lng, type]) => {
if (lat && lng) {
currentLatitude.value = lat;
currentLongitude.value = lng;
updateTrigger.value = Date.now(); //
}
if (type) {
currentMonitoringType.value = type;
}
},
{ immediate: true }
);
onMounted(() => {
nowTime.value = getDate();
currYearMonth.value = getDate(null, "YYYY-MM");
});
function toPage() {
router.push("/");
}
function getDropLabel(arr, val) {
let obj = arr.find((v) => v.value === val);
return obj ? obj.label : "";
}
const toggleFullscreen = () => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch((err) => {
console.error(`全屏错误: ${err.message}`);
});
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
}
}
};
function dropDownHandle(e, type) {
switch (type) {
case "caseTab":
caseTab.value = e;
chargeOpen.value = true;
modalTitle.value = getDropLabel(caseTab_options, e);
break;
case "caseTab2":
caseTab2.value = e;
chargeOpen2.value = true;
modalTitle.value = getDropLabel(caseTab2_options, e);
break;
case "caseTab3":
caseTab3.value = e;
chargeOpen3.value = true;
modalTitle.value = getDropLabel(caseTab3_options, e);
break;
case "caseTab4":
caseTab4.value = e;
chargeOpen4.value = true;
modalTitle.value = getDropLabel(caseTab4_options, e);
break;
}
}
//
const initialTime = ref(new Date(2025, 5, 10, 4, 0, 0));
const nowTime = ref("");
const currYearMonth = ref("");
const data = reactive({});
</script>
<style lang="scss" scoped>
@use "@/assets/styles/computed.scss" as calculate;
* {
margin: 0;
padding: 0;
}
//
$leftLineNum: 2;
$headerHeight: calculate.vh(80px);
$statisticsHeight: calculate.vh(0px);
$rightTopHeight: calculate.vh(350px);
$leftContainerHeight: calc(
(100vh - $headerHeight - $statisticsHeight - calculate.vh(50px))
);
$rightContainerBottomHeight: calc($leftContainerHeight - $rightTopHeight);
$top_card_height: 300px;
$bottom_card_height: 330px;
$card_title_height: calculate.vh(45px);
$card_title_color: #FFF;
$cardZIndex: 9;
$bg: #00214e;
$bg2: #00214eEE;
$bg3: #00214eCC;
.wrap {
background-image: url("@/assets/images/BG.png");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
width: 100%;
height: 100vh;
overflow: hidden;
position: relative;
z-index: 1;
&::before {
content: "";
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
z-index: 99999999;
}
&::after {
content: "";
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
box-shadow: inset 0 0 calculate.px2font(500px) #162d4b;
z-index: 0;
}
.header {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: $headerHeight;
line-height: $headerHeight;
font-size: calculate.px2font(28px);
font-weight: bold;
text-align: center;
// background-image: url("@/assets/images/newtopbg.png");
background-image: url("@/assets/images/hbg.png");
background-size: cover;
background-position: center 0;
color: #c8fefd;
letter-spacing: 0.1em;
z-index: 999;
letter-spacing: 0.3em;
display: flex;
justify-content: center;
.bgtitle {
// margin-top: 1%;
display: flex;
align-items: center;
gap: calculate.vw(15px);
font-size: calculate.px2font(32px);
// font-style: italic;
// font-family: YeZiGongChangAoYeHei;
img {
// width: 100%;
// height: 4vh;
width: calculate.vw(55px);
// height: calculate.vw(55px);
}
}
&::after {
content: "";
width: 100vw;
height: calc($headerHeight);
position: absolute;
inset: 0 0 auto 0;
z-index: -1;
background: linear-gradient(to bottom, #0f214ced, #07204b00);
}
}
.footer {
width: 100vw;
height: calculate.vh(50px);
position: fixed;
inset: auto 0 0 0;
z-index: 999999;
background-image: url("../../assets/images/footerBg.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
&::after {
content: "";
position: absolute;
inset: auto 0 0 0;
z-index: 1;
width: 100vw;
height: 100%;
background: linear-gradient(to top,#0f214ced, #07204b00);
}
}
.largeScreen__btn {
display: flex;
align-items: center;
position: fixed;
top: calculate.vh(20px);
right: calculate.vw(60px);
z-index: 9999;
cursor: pointer;
.rights {
display: flex;
align-items: center;
gap: calculate.px2font(10px);
span {
color: #fff;
font-weight: 600;
font-size: calculate.px2font(14px);
white-space: nowrap;
}
.icon,
img {
width: calculate.vh(50px);
height: calculate.vh(50px);
object-fit: contain;
// transition: all 0.3s;
&:hover {
transform: scale(1.1);
opacity: 0.8;
}
}
a {
display: grid;
place-items: center;
gap: calculate.px2font(10px);
}
.flex {
display: flex;
gap: calculate.px2font(10px);
.back {
width: calculate.vh(45px);
height: calculate.vh(45px);
object-fit: contain;
}
}
.right-menu-item {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
&:hover {
background: transparent;
}
}
}
}
.currDate {
position: fixed;
top: calculate.vw(45px);
left: calculate.vw(10px);
z-index: 9;
color: #1da2fa;
}
}
.totality {
width: calculate.vw(700px);
position: fixed;
left: 50%;
top: calculate.vh(120px);
z-index: 9;
display: grid;
grid-template-columns: repeat(5, 1fr);
transform: translateX(-50%);
text-align: center;
&:deep(.el-statistic) {
.el-statistic__head {
font-size: calculate.px2font(18px);
font-weight: bold;
color: #388bdd;
margin-bottom: calculate.vh(10px);
}
.el-statistic__content {
font-size: calculate.px2font(20px);
color: #fff;
}
}
}
.container {
padding: calculate.px2font(10px) 0 0;
box-sizing: border-box;
position: relative;
margin-top: 1%;
&__top {
display: grid;
grid-template-columns: 4fr 6fr 4fr;
gap: calculate.px2font(20px);
margin-bottom: calculate.px2font(10px);
position: relative;
$lrWidth: 23vw;
.left {
position: absolute;
left: 0;
z-index: 9;
width: $lrWidth;
top: calculate.vh(20px);
height: 90vh;
&::before {
content: "";
width: 25vw;
height: 100vh;
position: absolute;
inset: calculate.vh(-90px) auto 0 0;
z-index: 1;
background: linear-gradient(90deg, #0f214ced,#05204fba 49%, #07204b00);
}
.card {
// background: linear-gradient(to right,$bg, $bg2, $bg3);
// border-radius: 0 calculate.px2font(10px) calculate.px2font(10px) 0;
}
}
.right {
position: absolute;
right: 0;
top: calculate.vh(20px);
z-index: 9;
width: $lrWidth;
height: 90vh;
&::before {
content: "";
width: 25vw;
height: 100vh;
position: absolute;
top: calculate.vh(-90px);
bottom: 0;
right: 0;
z-index: 1;
background: linear-gradient(270deg,#0f214ced,#05204fba 49%, #07204b00);
}
.card {
// background: linear-gradient(to left,$bg, $bg2, $bg3);
// border-radius: calculate.px2font(10px) 0 0 calculate.px2font(10px);
}
}
.timeline {
position: fixed;
left: 28%;
bottom: 18%;
width: 44vw;
height: 80px;
z-index: 99;
}
.flexcolumns {
display: flex;
flex-direction: column;
z-index: 2;
}
.middle {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 1;
font-size: calculate.px2font(16px);
// position: relative;
// &::after {
// content: "";
// width: 100vw;
// height: 100vh;
// position: absolute;
// inset: 0;
// padding: calculate.px2font(200px);
// box-shadow: inset 0 0 calculate.px2font(500px) #162d4b;
// z-index: 0;
// }
}
}
}
.flex-btn {
display: flex;
align-items: center;
justify-content: space-between;
}
.card {
height: 43vh;
position: relative;
z-index: 1;
padding: calculate.vh(15px) calculate.vw(15px);
&__title {
// display: none;
padding: 0 calculate.px2font(10px) 0 calculate.px2font(40px);
font-weight: bold;
font-size: calculate.px2font(15px);
font-style: italic;
width: 100%;
// height: $card_title_height;
line-height: $card_title_height;
margin-bottom: calculate.vh(15px);
background: url("@/assets/images/largeScreenImage/topbg.png") no-repeat;
background-size: 100% 100%;
position: relative;
color: $card_title_color;
letter-spacing: 0.1em;
// &::before, &::after {
// content: "";
// width: calculate.vw(15px);
// height: calculate.vw(10px);
// position: absolute;
// top: 65%;
// z-index: 9;
// transform: translateY(-50%);
// clip-path: polygon(50% 0%, 100% 0%, 50% 100%, 0% 100%);
// // background: url("@/assets/images/largeScreenImage/cardTitleIcon@2x.png")
// // no-repeat;
// // background-size: 100% 100%;
// }
// &::before {
// left: calculate.vw(5px);
// background-color: #1A87D2;
// }
// &::after {
// left: calculate.vw(18px);
// background-color: #FFF;
// }
&:deep(.el-tabs) {
.el-tabs__header {
margin: 0;
}
.el-tabs__item {
font-size: calculate.px2font(16px);
font-weight: bold;
color: #000;
}
}
}
&__content {
width: 100%;
height: calc(100% - $card_title_height);
// border-radius: 10px;
background-image: url("@/assets/images/cardBg.png");
background-size: 100% 100%;
background-repeat: no-repeat;
z-index: 2;
overflow: hidden;
&-box {
width: 100%;
height: 100%;
}
.titles {
display: none;
color: #fff;
font-weight: 700;
font-size: 1vw;
width: 100%;
line-height: calculate.vh(45px);
// transform: scale(1, 0.85) skewX(-7deg);
padding: calculate.vh(10px) calculate.vh(15px);
box-sizing: border-box;
background-image: url("../../assets/images/titbg2.png");
// background-image: url("@/assets/images/largeScreenImage/cardHeader.png");
background-size: 100% 100%; /* 或 contain根据需求调整 */
background-size: cover;
background-repeat: no-repeat;
// position: relative;
// &::after {
// content: "";
// position: absolute;
// inset: auto 0 calculate.vh(1px) 0;
// z-index: -1;
// width: 100%;
// height: 30px;
// }
}
}
.noBg {
background-image: none;
}
}
.card-1 {
height: $rightTopHeight;
}
.card-2 {
height: $rightContainerBottomHeight;
}
.card2 {
&__title {
background-image: url("@/assets/images/largeScreenImage/topbg.png");
}
}
.flex-column {
display: flex;
flex-direction: column;
justify-content: space-between;
gap: calculate.px2font(10px);
}
.gap20 {
display: flex;
gap: calculate.vw(20px);
}
:deep(.leaflet-bar) {
display: none;
}
.elegant-collapse-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
background: linear-gradient(
90deg,
rgba(24, 75, 112, 0.6),
rgba(24, 75, 112, 0.3)
);
box-shadow: inset 0 0 14px 20px rgba(36, 55, 87, 0.7),
inset 0 0 20px 100px rgba(36, 55, 87, 0.3);
border-radius: 12px;
box-shadow: 0 4px 20px rgba(91, 143, 249, 0.15);
}
.elegant-collapse {
border: none;
:deep(.el-collapse-item__header) {
height: 20px;
line-height: 20px;
padding: 0 20px;
font-size: 18px;
font-weight: 500;
color: #fff;
background: transparent;
border-radius: 8px;
margin-bottom: 8px;
transition: all 0.3s ease;
border: none;
&:hover {
background: transparent;
}
.el-collapse-item__arrow {
font-size: 25px;
color: #fff;
}
}
:deep(.el-collapse-item__wrap) {
background: transparent;
border: none;
padding: 0 20px;
transition: all 0.3s ease;
}
:deep(.el-collapse-item__content) {
padding: 20px 0;
color: #4a5b6c;
font-size: 15px;
line-height: 1.6;
}
}
.collapse-content {
padding: 15px;
border-radius: 8px;
.imgList {
width: 100%;
height: 20vh;
padding-bottom: 4vh;
display: flex;
align-items: center;
justify-content: start;
flex-wrap: wrap;
overflow: auto;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: rgba(91, 143, 249, 0.5);
border-radius: 3px;
&:hover {
background: rgba(91, 143, 249, 0.8);
}
}
img {
width: 100px;
height: 100px;
}
}
}
.data-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin-top: 20px;
}
.data-item {
background: rgba(235, 245, 255, 0.5);
padding: 12px;
border-radius: 6px;
text-align: center;
}
.data-label {
display: block;
font-size: 13px;
color: #6b7c93;
margin-bottom: 5px;
}
.data-value {
display: block;
font-size: 18px;
font-weight: 600;
color: #2c3e50;
&.positive {
color: #52c41a;
}
}
.progress-label {
display: flex;
justify-content: space-between;
margin-top: 8px;
font-size: 13px;
color: #6b7c93;
}
.metric-cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin-top: 20px;
}
.metric-card {
display: flex;
align-items: center;
background: rgba(235, 245, 255, 0.5);
padding: 15px;
border-radius: 8px;
}
.metric-icon {
font-size: 24px;
margin-right: 12px;
color: #5b8ff9;
}
.metric-value {
font-size: 18px;
font-weight: 600;
color: #2c3e50;
}
.metric-title {
font-size: 13px;
color: #6b7c93;
}
:deep(.custam-select){
$inputHeight: calculate.vh(45px);
$selectBg: #16437F;
.el-select__wrapper {
box-shadow: 0 0 0 calculate.px2font(1px) #2d6691 inset;
padding: calculate.vh(10px) calculate.vw(15px);
font-size: calculate.px2font(18px);
width: 100%;
height: $inputHeight;
overflow: hidden;
min-height: $inputHeight;
line-height: $inputHeight;
/* padding: 6%; */
background: transparent;
color: #1890ff;
background-color: $selectBg;
border-radius: calculate.px2font(5px);
.el-select__input {
height: 100%;
}
.el-select__caret {
font-size: inherit;
}
.el-icon svg {
font-size: inherit;
}
}
.el-select__placeholder {
color: #FFF;
font-size: calculate.px2font(14px);
}
}
</style>
<style lang="scss">
@use "@/assets/styles/computed.scss" as calculate;
.custam-select-pupper {
min-width: calculate.vh(10px);
line-height: calculate.vh(20px);
border-radius: calculate.px2font(5px);
font-size: calculate.px2font(20px);
&.is-light {
border-width: calculate.px2font(2px);
}
&[data-popper-placement^=left]>.el-popper__arrow {
right: calculate.vh(-5px);
}
&[data-popper-placement^=bottom]>.el-popper__arrow {
top: calculate.vh(-5px);
}
.el-popper__arrow:before {
width: calculate.vh(10px);
height: calculate.vh(10px);
}
.el-select-dropdown__wrap {
max-height: calculate.vh(280px);
}
.el-select-dropdown__list {
padding: calculate.vh(10px) 0;
}
.el-select-dropdown__item {
height: calculate.vh(45px);
line-height: calculate.vh(45px);
font-size: calculate.px2font(16px);
padding: 0 calculate.vw(45px) 0 calculate.vw(20px);
}
}
</style>

View File

@ -0,0 +1,25 @@
<template>
  <div id="demo">
<iframe src="https://10.22.245.209:18888/ktCallingSystem/wholeCallPage?agentId= &agentPwd= " id="iframe"
style="width:108px;height:108px;right:20px;bottom:20px;" allow="microphone;camera;midi;encrypted-media">
</iframe>
</div>
</template>
<script>
export default {
data() {
return {
}
},
mounted() {
},
methods: {
}
}
</script>
<style scoped lang="less"></style>

View File

@ -0,0 +1,412 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true">
<el-form-item label="培训机构" prop="noticeTitle">
<el-input
v-model="queryParams.noticeTitle"
placeholder="请输入培训机构"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="联系电话" prop="createBy">
<el-input
v-model="queryParams.createBy"
placeholder="请输入联系电话"
style="width: 200px"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="考试计划通知电话1" prop="createBy">
<el-input
v-model="queryParams.createBy"
placeholder="考试计划通知电话1"
style="width: 200px"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="考试计划通知电话2" prop="createBy">
<el-input
v-model="queryParams.createBy"
placeholder="考试计划通知电话2"
style="width: 200px"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" size="mini" @click="handleQuery"
>搜索</el-button
>
<el-button icon="Refresh" size="mini" @click="resetQuery"
>重置</el-button
>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
size="mini"
@click="handleAdd"
v-hasPermi="['system:notice:add']"
>新增</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['system:notice:edit']"
>编辑</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['system:notice:remove']"
>删除</el-button
>
</el-col>
<right-toolbar
v-model:showSearch="showSearch"
@queryTable="getList"
></right-toolbar>
</el-row>
<el-table
v-loading="loading"
:data="noticeList"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column
label="培训机构名称"
align="center"
prop="noticeTitle"
:show-overflow-tooltip="true"
/>
<el-table-column
label="联系电话"
align="center"
prop="createBy"
width="160"
/>
<el-table-column
label="考试计划通知电话1"
align="center"
prop="createBy"
width="160"
/>
<el-table-column
label="考试计划通知电话2"
align="center"
prop="createBy"
width="160"
/>
<el-table-column
label="地址"
align="center"
prop="createBy"
width="160"
/>
<el-table-column
label="操作"
align="center"
class-name="small-padding fixed-width"
>
<template #default="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['system:notice:edit']"
>编辑</el-button
>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['system:notice:remove']"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改公告对话框 -->
<el-dialog :title="title" v-model="open" width="780px" append-to-body>
<el-form ref="formRef" :model="form" :rules="rules" label-width="160px">
<el-row>
<el-col :span="12">
<el-form-item label="培训机构名称" prop="noticeTitle">
<el-input
v-model="form.noticeTitle"
placeholder="培训机构名称"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系电话" prop="noticeType">
<el-select
v-model="form.noticeType"
placeholder="联系电话"
>
<el-option
v-for="dict in sys_notice_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="考试计划通知电话1" prop="noticeTitle">
<el-input v-model="form.noticeTitle" placeholder="考试计划通知电话1" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="考试计划通知电话2" prop="noticeType">
<el-input v-model="form.noticeTitle" placeholder="考试计划通知电话2" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="地址" prop="noticeTitle">
<el-input type="textarea" v-model="form.noticeTitle" placeholder="地址" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script>
import {
listNotice,
getNotice,
delNotice,
addNotice,
updateNotice,
} from "@/api/system/notice";
import { ref, reactive, onMounted } from "vue";
import { useDict } from "@/utils/dict";
import { ElMessage, ElMessageBox } from "element-plus";
export default {
name: "Notice",
setup() {
const { dict } = useDict(["sys_notice_status", "sys_notice_type"]);
const { proxy } = getCurrentInstance();
//
const loading = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const showSearch = ref(true);
const total = ref(0);
const noticeList = ref([]);
const title = ref("");
const open = ref(false);
const queryParams = reactive({
pageNum: 1,
pageSize: 10,
noticeTitle: undefined,
createBy: undefined,
status: undefined,
});
const form = reactive({
noticeId: undefined,
noticeTitle: undefined,
noticeType: undefined,
noticeContent: undefined,
status: "0",
});
const rules = reactive({
noticeTitle: [
{ required: true, message: "培训机构不能为空", trigger: "blur" },
],
noticeType: [
{ required: true, message: "公告类型不能为空", trigger: "change" },
],
});
//
const formRef = ref(null);
const queryFormRef = ref(null);
//
const getList = () => {
loading.value = true;
listNotice(queryParams).then((response) => {
noticeList.value = response.rows;
total.value = response.total;
loading.value = false;
});
};
const cancel = () => {
open.value = false;
reset();
};
const reset = () => {
Object.assign(form, {
noticeId: undefined,
noticeTitle: undefined,
noticeType: undefined,
noticeContent: undefined,
status: "0",
});
// formRef.value.resetFields();
};
const handleQuery = () => {
queryParams.pageNum = 1;
getList();
};
const resetQuery = () => {
proxy.resetForm("queryRef");
handleQuery();
};
const handleSelectionChange = (selection) => {
ids.value = selection.map((item) => item.noticeId);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
const handleAdd = () => {
reset();
open.value = true;
title.value = "添加培训机构";
};
const handleUpdate = (row) => {
reset();
const noticeId = row.noticeId || ids.value;
getNotice(noticeId).then((response) => {
Object.assign(form, response.data);
open.value = true;
title.value = "修改公告";
});
};
const submitForm = () => {
formRef.value.validate((valid) => {
if (valid) {
if (form.noticeId != undefined) {
updateNotice(form).then((response) => {
ElMessage.success("修改成功");
open.value = false;
getList();
});
} else {
addNotice(form).then((response) => {
ElMessage.success("新增成功");
open.value = false;
getList();
});
}
}
});
};
const handleDelete = (row) => {
const noticeIds = row.noticeId || ids.value;
ElMessageBox.confirm(
'是否确认删除公告编号为"' + noticeIds + '"的数据项?',
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
)
.then(() => {
return delNotice(noticeIds);
})
.then(() => {
getList();
ElMessage.success("删除成功");
})
.catch(() => {});
};
//
onMounted(() => {
getList();
});
return {
dict,
loading,
ids,
single,
multiple,
showSearch,
total,
noticeList,
title,
open,
queryParams,
form,
rules,
formRef,
queryFormRef,
getList,
cancel,
reset,
handleQuery,
resetQuery,
handleSelectionChange,
handleAdd,
handleUpdate,
submitForm,
handleDelete,
};
},
};
</script>

View File

@ -0,0 +1,537 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true">
<el-form-item label="培训班名称" prop="noticeTitle">
<el-input
v-model="queryParams.noticeTitle"
placeholder="培训班名称"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" size="mini" @click="handleQuery"
>搜索</el-button
>
<el-button icon="Refresh" size="mini" @click="resetQuery"
>重置</el-button
>
</el-form-item>
</el-form>
<el-table
v-loading="loading"
:data="noticeList"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column
label="培训班名称"
align="center"
prop="noticeTitle"
:show-overflow-tooltip="true"
/>
<el-table-column
label="理论计划培训时间"
align="center"
prop="createBy"
width="160"
/>
<el-table-column
label="实操计划培训时间"
align="center"
prop="createBy"
width="160"
/>
<el-table-column
label="班级状态"
align="center"
prop="createBy"
width="160"
/>
<el-table-column
label="报名情况"
align="center"
prop="createBy"
width="160"
/>
<el-table-column
label="操作"
align="center"
class-name="small-padding fixed-width"
>
<template #default="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['system:notice:edit']"
>详情</el-button
>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="studentBM(scope.row)"
v-hasPermi="['system:notice:remove']"
>学员报名</el-button
>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改公告对话框 -->
<el-dialog :title="title" v-model="open" width="780px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
<el-row>
<el-col :span="12">
<el-form-item label="报名或考试" prop="noticeTitle">
<el-input
v-model="form.noticeTitle"
placeholder="请输入报名或考试"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="班级或个人" prop="noticeType">
<el-select
v-model="form.noticeType"
placeholder="请选择班级或个人"
>
<el-option
v-for="dict in sys_notice_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="考试类型" prop="noticeTitle">
<el-input v-model="form.noticeTitle" placeholder="考试类型" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="岗位类别" prop="noticeType">
<el-select v-model="form.noticeType" placeholder="岗位类别">
<el-option
v-for="dict in sys_notice_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="资料类型" prop="noticeTitle">
<el-input v-model="form.noticeTitle" placeholder="资料类型" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否必须" prop="noticeType">
<el-select v-model="form.noticeType" placeholder="是否必须">
<el-option
v-for="dict in sys_notice_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="序号" prop="noticeTitle">
<el-input v-model="form.noticeTitle" placeholder="序号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="资料要求描述" prop="noticeType">
<el-input
type="textarea"
v-model="form.noticeTitle"
placeholder="资料要求描述"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<!-- 学生报名 -->
<el-dialog
:title="title"
v-model="studentBMopen"
width="980px"
append-to-body
>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
size="mini"
v-hasPermi="['system:notice:add']"
>添加学员</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
size="mini"
:disabled="single"
v-hasPermi="['system:notice:edit']"
>批量导入</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
size="mini"
:disabled="single"
v-hasPermi="['system:notice:edit']"
>批量导出</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
size="mini"
:disabled="single"
v-hasPermi="['system:notice:edit']"
>资料导入</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
size="mini"
:disabled="single"
v-hasPermi="['system:notice:edit']"
>资料导出</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
size="mini"
:disabled="single"
v-hasPermi="['system:notice:edit']"
>批量审核</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
size="mini"
:disabled="multiple"
v-hasPermi="['system:notice:remove']"
>批量删除</el-button
>
</el-col>
</el-row>
<el-table
v-loading="loading"
:data="noticeList"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column
label="课时"
align="center"
prop="noticeTitle"
:show-overflow-tooltip="true"
/>
<el-table-column
label="身份证复印件"
align="center"
prop="createBy"
width="160"
/>
<el-table-column
label="照片"
align="center"
prop="createBy"
width="160"
/>
<el-table-column
label="健康承诺"
align="center"
prop="createBy"
width="160"
/>
<el-table-column
label="证件集"
align="center"
prop="createBy"
width=""
/>
<el-table-column
label="考核申请表"
align="center"
prop="createBy"
width="160"
/>
<el-table-column
label="无违规证明"
align="center"
prop="createBy"
width="160"
/>
<el-table-column
label="任职文件"
align="center"
prop="createBy"
width="160"
/>
<el-table-column
label="学历证明"
align="center"
prop="createBy"
width="160"
/>
</el-table>
<template #footer>
<div class="dialog-footer">
<el-button @click="studentBMopen = false"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script>
import {
listNotice,
getNotice,
delNotice,
addNotice,
updateNotice,
} from "@/api/system/notice";
import { ref, reactive, onMounted } from "vue";
import { useDict } from "@/utils/dict";
import { ElMessage, ElMessageBox } from "element-plus";
export default {
name: "Notice",
setup() {
const { dict } = useDict(["sys_notice_status", "sys_notice_type"]);
const { proxy } = getCurrentInstance();
//
const loading = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const showSearch = ref(true);
const total = ref(0);
const noticeList = ref([]);
const title = ref("");
const open = ref(false);
const studentBMopen = ref(false);
const queryParams = reactive({
pageNum: 1,
pageSize: 10,
noticeTitle: undefined,
createBy: undefined,
status: undefined,
});
const form = reactive({
noticeId: undefined,
noticeTitle: undefined,
noticeType: undefined,
noticeContent: undefined,
status: "0",
});
const rules = reactive({
noticeTitle: [
{ required: true, message: "公告标题不能为空", trigger: "blur" },
],
noticeType: [
{ required: true, message: "公告类型不能为空", trigger: "change" },
],
});
//
const formRef = ref(null);
const queryFormRef = ref(null);
//
const getList = () => {
loading.value = true;
listNotice(queryParams).then((response) => {
noticeList.value = response.rows;
total.value = response.total;
loading.value = false;
});
};
const cancel = () => {
open.value = false;
reset();
};
const reset = () => {
Object.assign(form, {
noticeId: undefined,
noticeTitle: undefined,
noticeType: undefined,
noticeContent: undefined,
status: "0",
});
// formRef.value.resetFields();
};
const handleQuery = () => {
queryParams.pageNum = 1;
getList();
};
const resetQuery = () => {
proxy.resetForm("queryRef");
handleQuery();
};
const handleSelectionChange = (selection) => {
ids.value = selection.map((item) => item.noticeId);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
const handleAdd = () => {
reset();
open.value = true;
title.value = "添加资料模板";
};
const handleUpdate = (row) => {
reset();
const noticeId = row.noticeId || ids.value;
getNotice(noticeId).then((response) => {
Object.assign(form, response.data);
open.value = true;
title.value = "修改公告";
});
};
const submitForm = () => {
formRef.value.validate((valid) => {
if (valid) {
if (form.noticeId != undefined) {
updateNotice(form).then((response) => {
ElMessage.success("修改成功");
open.value = false;
getList();
});
} else {
addNotice(form).then((response) => {
ElMessage.success("新增成功");
open.value = false;
getList();
});
}
}
});
};
const studentBM = (row) => {
console.log(123);
studentBMopen.value = true;
};
const handleDelete = (row) => {
const noticeIds = row.noticeId || ids.value;
ElMessageBox.confirm(
'是否确认删除公告编号为"' + noticeIds + '"的数据项?',
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
)
.then(() => {
return delNotice(noticeIds);
})
.then(() => {
getList();
ElMessage.success("删除成功");
})
.catch(() => {});
};
//
onMounted(() => {
getList();
});
return {
dict,
loading,
ids,
single,
multiple,
showSearch,
total,
noticeList,
title,
open,
queryParams,
form,
rules,
formRef,
queryFormRef,
studentBMopen,
getList,
cancel,
reset,
handleQuery,
resetQuery,
handleSelectionChange,
handleAdd,
handleUpdate,
submitForm,
handleDelete,
studentBM,
};
},
};
</script>

View File

@ -0,0 +1,181 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" v-show="showSearch" :inline="true">
<el-form-item label="用户名称" prop="userName">
<el-input
v-model="queryParams.userName"
placeholder="请输入用户名称"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input
v-model="queryParams.phonenumber"
placeholder="请输入手机号码"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="openSelectUser"
v-hasPermi="['system:role:add']"
>添加用户</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="CircleClose"
:disabled="multiple"
@click="cancelAuthUserAll"
v-hasPermi="['system:role:remove']"
>批量取消授权</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Close"
@click="handleClose"
>关闭</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<div class="tablebox">
<el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
<el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
<el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" />
<el-table-column label="手机" prop="phonenumber" :show-overflow-tooltip="true" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="CircleClose" @click="cancelAuthUser(scope.row)" v-hasPermi="['system:role:remove']">取消授权</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</div>
<select-user ref="selectRef" :roleId="queryParams.roleId" @ok="handleQuery" />
</div>
</template>
<script setup name="AuthUser">
import selectUser from "./selectUser";
import { allocatedUserList, authUserCancel, authUserCancelAll } from "@/api/system/role";
const route = useRoute();
const { proxy } = getCurrentInstance();
const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
const userList = ref([]);
const loading = ref(true);
const showSearch = ref(true);
const multiple = ref(true);
const total = ref(0);
const userIds = ref([]);
const queryParams = reactive({
pageNum: 1,
pageSize: 10,
roleId: route.params.roleId,
userName: undefined,
phonenumber: undefined,
});
/** 查询授权用户列表 */
function getList() {
loading.value = true;
allocatedUserList(queryParams).then(response => {
userList.value = response.rows;
total.value = response.total;
loading.value = false;
});
}
/** 返回按钮 */
function handleClose() {
const obj = { path: "/system/role" };
proxy.$tab.closeOpenPage(obj);
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm("queryRef");
handleQuery();
}
/** 多选框选中数据 */
function handleSelectionChange(selection) {
userIds.value = selection.map(item => item.userId);
multiple.value = !selection.length;
}
/** 打开授权用户表弹窗 */
function openSelectUser() {
proxy.$refs["selectRef"].show();
}
/** 取消授权按钮操作 */
function cancelAuthUser(row) {
proxy.$modal.confirm('确认要取消该用户"' + row.userName + '"角色吗?').then(function () {
return authUserCancel({ userId: row.userId, roleId: queryParams.roleId });
}).then(() => {
getList();
proxy.$modal.msgSuccess("取消授权成功");
}).catch(() => {});
}
/** 批量取消授权按钮操作 */
function cancelAuthUserAll(row) {
const roleId = queryParams.roleId;
const uIds = userIds.value.join(",");
proxy.$modal.confirm("是否取消选中用户授权数据项?").then(function () {
return authUserCancelAll({ roleId: roleId, userIds: uIds });
}).then(() => {
getList();
proxy.$modal.msgSuccess("取消授权成功");
}).catch(() => {});
}
getList();
</script>

View File

@ -0,0 +1,599 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" v-show="showSearch" :inline="true" label-width="68px">
<el-row>
<el-form-item label="角色名称" prop="roleName">
<el-input
v-model="queryParams.roleName"
placeholder="请输入角色名称"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="权限字符" prop="roleKey">
<el-input
v-model="queryParams.roleKey"
placeholder="请输入权限字符"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="角色状态"
clearable
style="width: 240px"
>
<el-option
v-for="dict in sys_normal_disable"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<!-- <el-form-item label="创建时间" style="width: 308px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item> -->
</el-row>
<el-row class="dfjb">
<el-row :gutter="10">
<el-col :span="1.5">
<el-button
type="primary"
@click="handleAdd"
class="circle-plus-btn"
v-hasPermi="['large:data:addweather']"
>
新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['large:data:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['large:data:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
v-hasPermi="['large:data:export']"
>导出</el-button
>
</el-col>
</el-row>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"
>搜索</el-button
>
<el-button icon="RefreshLeft" class="Refresh" @click="resetQuery"
>重置</el-button
>
</el-form-item>
</el-row>
</el-form>
<div class="tablebox">
<!-- 表格数据 -->
<el-table v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="角色编号" prop="roleId" width="120" />
<el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" />
<el-table-column label="权限字符" prop="roleKey" :show-overflow-tooltip="true" width="150" />
<el-table-column label="显示顺序" prop="roleSort" width="100" />
<el-table-column label="状态" align="center" width="100">
<template #default="scope">
<el-switch
v-model="scope.row.status"
active-value="0"
inactive-value="1"
@change="handleStatusChange(scope.row)"
></el-switch>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top" v-if="scope.row.roleId !== 1">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:role:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top" v-if="scope.row.roleId !== 1">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:role:remove']"></el-button>
</el-tooltip>
<el-tooltip content="数据权限" placement="top" v-if="scope.row.roleId !== 1">
<el-button link type="primary" icon="CircleCheck" @click="handleDataScope(scope.row)" v-hasPermi="['system:role:edit']"></el-button>
</el-tooltip>
<el-tooltip content="分配用户" placement="top" v-if="scope.row.roleId !== 1">
<el-button link type="primary" icon="User" @click="handleAuthUser(scope.row)" v-hasPermi="['system:role:edit']"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</div>
<!-- 添加或修改角色配置对话框 -->
<el-dialog :title="title" :style="{ height: '700px' }" v-model="open" width="500px" append-to-body>
<el-form ref="roleRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="角色名称" prop="roleName">
<el-input v-model="form.roleName" placeholder="请输入角色名称" />
</el-form-item>
<el-form-item prop="roleKey">
<template #label>
<span>
<el-tooltip content="控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasRole('admin')`)" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
权限字符
</span>
</template>
<el-input v-model="form.roleKey" placeholder="请输入权限字符" />
</el-form-item>
<el-form-item label="角色顺序" prop="roleSort">
<el-input-number v-model="form.roleSort" controls-position="right" :min="0" />
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in sys_normal_disable"
:key="dict.value"
:value="dict.value"
>{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="菜单权限">
<el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">展开/折叠</el-checkbox>
<el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">全选/全不选</el-checkbox>
<el-checkbox v-model="form.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')">父子联动</el-checkbox>
<el-tree
class="tree-border"
:data="menuOptions"
show-checkbox
ref="menuRef"
node-key="id"
:check-strictly="!form.menuCheckStrictly"
empty-text="加载中,请稍候"
:props="{ label: 'label', children: 'children' }"
></el-tree>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<!-- 分配角色数据权限对话框 -->
<el-dialog :title="title" :style="{ height: '700px' }" v-model="openDataScope" width="500px" append-to-body>
<el-form :model="form" label-width="80px">
<el-form-item label="角色名称">
<el-input v-model="form.roleName" :disabled="true" />
</el-form-item>
<el-form-item label="权限字符">
<el-input v-model="form.roleKey" :disabled="true" />
</el-form-item>
<el-form-item label="权限范围">
<el-select v-model="form.dataScope" @change="dataScopeSelectChange">
<el-option
v-for="item in dataScopeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="数据权限" v-show="form.dataScope == 2">
<el-checkbox v-model="deptExpand" @change="handleCheckedTreeExpand($event, 'dept')">展开/折叠</el-checkbox>
<el-checkbox v-model="deptNodeAll" @change="handleCheckedTreeNodeAll($event, 'dept')">全选/全不选</el-checkbox>
<el-checkbox v-model="form.deptCheckStrictly" @change="handleCheckedTreeConnect($event, 'dept')">父子联动</el-checkbox>
<el-tree
class="tree-border"
:data="deptOptions"
show-checkbox
default-expand-all
ref="deptRef"
node-key="id"
:check-strictly="!form.deptCheckStrictly"
empty-text="加载中,请稍候"
:props="{ label: 'label', children: 'children' }"
></el-tree>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitDataScope"> </el-button>
<el-button @click="cancelDataScope"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Role">
import { addRole, changeRoleStatus, dataScope, delRole, getRole, listRole, updateRole, deptTreeSelect } from "@/api/system/role";
import { roleMenuTreeselect, treeselect as menuTreeselect } from "@/api/system/menu";
const router = useRouter();
const { proxy } = getCurrentInstance();
const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
const roleList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
const dateRange = ref([]);
const menuOptions = ref([]);
const menuExpand = ref(false);
const menuNodeAll = ref(false);
const deptExpand = ref(true);
const deptNodeAll = ref(false);
const deptOptions = ref([]);
const openDataScope = ref(false);
const menuRef = ref(null);
const deptRef = ref(null);
/** 数据范围选项*/
const dataScopeOptions = ref([
{ value: "1", label: "全部数据权限" },
{ value: "2", label: "自定数据权限" },
{ value: "3", label: "本部门数据权限" },
{ value: "4", label: "本部门及以下数据权限" },
{ value: "5", label: "仅本人数据权限" }
]);
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
roleName: undefined,
roleKey: undefined,
status: undefined
},
rules: {
roleName: [{ required: true, message: "角色名称不能为空", trigger: "blur" }],
roleKey: [{ required: true, message: "权限字符不能为空", trigger: "blur" }],
roleSort: [{ required: true, message: "角色顺序不能为空", trigger: "blur" }]
},
});
const { queryParams, form, rules } = toRefs(data);
/** 查询角色列表 */
function getList() {
loading.value = true;
listRole(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
roleList.value = response.rows;
total.value = response.total;
loading.value = false;
});
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
dateRange.value = [];
proxy.resetForm("queryRef");
handleQuery();
}
/** 删除按钮操作 */
function handleDelete(row) {
const roleIds = row.roleId || ids.value;
proxy.$modal.confirm('是否确认删除角色编号为"' + roleIds + '"的数据项?').then(function () {
return delRole(roleIds);
}).then(() => {
getList();
proxy.$modal.msgSuccess("删除成功");
}).catch(() => {});
}
/** 导出按钮操作 */
function handleExport() {
proxy.download("system/role/export", {
...queryParams.value,
}, `role_${new Date().getTime()}.xlsx`);
}
/** 多选框选中数据 */
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.roleId);
single.value = selection.length != 1;
multiple.value = !selection.length;
}
/** 角色状态修改 */
function handleStatusChange(row) {
let text = row.status === "0" ? "启用" : "停用";
proxy.$modal.confirm('确认要"' + text + '""' + row.roleName + '"角色吗?').then(function () {
return changeRoleStatus(row.roleId, row.status);
}).then(() => {
proxy.$modal.msgSuccess(text + "成功");
}).catch(function () {
row.status = row.status === "0" ? "1" : "0";
});
}
/** 更多操作 */
function handleCommand(command, row) {
switch (command) {
case "handleDataScope":
handleDataScope(row);
break;
case "handleAuthUser":
handleAuthUser(row);
break;
default:
break;
}
}
/** 分配用户 */
function handleAuthUser(row) {
router.push("/system/role-auth/user/" + row.roleId);
}
/** 查询菜单树结构 */
function getMenuTreeselect() {
menuTreeselect().then(response => {
menuOptions.value = response.data;
});
}
/** 所有部门节点数据 */
function getDeptAllCheckedKeys() {
//
let checkedKeys = deptRef.value.getCheckedKeys();
//
let halfCheckedKeys = deptRef.value.getHalfCheckedKeys();
checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys);
return checkedKeys;
}
/** 重置新增的表单以及其他数据 */
function reset() {
if (menuRef.value != undefined) {
menuRef.value.setCheckedKeys([]);
}
menuExpand.value = false;
menuNodeAll.value = false;
deptExpand.value = true;
deptNodeAll.value = false;
form.value = {
roleId: undefined,
roleName: undefined,
roleKey: undefined,
roleSort: 0,
status: "0",
menuIds: [],
deptIds: [],
menuCheckStrictly: true,
deptCheckStrictly: true,
remark: undefined
};
proxy.resetForm("roleRef");
}
/** 添加角色 */
function handleAdd() {
reset();
getMenuTreeselect();
open.value = true;
title.value = "添加角色";
}
/** 修改角色 */
function handleUpdate(row) {
reset();
const roleId = row.roleId || ids.value;
const roleMenu = getRoleMenuTreeselect(roleId);
getRole(roleId).then(response => {
form.value = response.data;
form.value.roleSort = Number(form.value.roleSort);
open.value = true;
nextTick(() => {
roleMenu.then((res) => {
let checkedKeys = res.checkedKeys;
checkedKeys.forEach((v) => {
nextTick(() => {
menuRef.value.setChecked(v, true, false);
});
});
});
});
});
title.value = "修改角色";
}
/** 根据角色ID查询菜单树结构 */
function getRoleMenuTreeselect(roleId) {
return roleMenuTreeselect(roleId).then(response => {
menuOptions.value = response.menus;
return response;
});
}
/** 根据角色ID查询部门树结构 */
function getDeptTree(roleId) {
return deptTreeSelect(roleId).then(response => {
deptOptions.value = response.depts;
return response;
});
}
/** 树权限(展开/折叠)*/
function handleCheckedTreeExpand(value, type) {
if (type == "menu") {
let treeList = menuOptions.value;
for (let i = 0; i < treeList.length; i++) {
menuRef.value.store.nodesMap[treeList[i].id].expanded = value;
}
} else if (type == "dept") {
let treeList = deptOptions.value;
for (let i = 0; i < treeList.length; i++) {
deptRef.value.store.nodesMap[treeList[i].id].expanded = value;
}
}
}
/** 树权限(全选/全不选) */
function handleCheckedTreeNodeAll(value, type) {
if (type == "menu") {
menuRef.value.setCheckedNodes(value ? menuOptions.value : []);
} else if (type == "dept") {
deptRef.value.setCheckedNodes(value ? deptOptions.value : []);
}
}
/** 树权限(父子联动) */
function handleCheckedTreeConnect(value, type) {
if (type == "menu") {
form.value.menuCheckStrictly = value ? true : false;
} else if (type == "dept") {
form.value.deptCheckStrictly = value ? true : false;
}
}
/** 所有菜单节点数据 */
function getMenuAllCheckedKeys() {
//
let checkedKeys = menuRef.value.getCheckedKeys();
//
let halfCheckedKeys = menuRef.value.getHalfCheckedKeys();
checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys);
return checkedKeys;
}
/** 提交按钮 */
function submitForm() {
proxy.$refs["roleRef"].validate(valid => {
if (valid) {
if (form.value.roleId != undefined) {
form.value.menuIds = getMenuAllCheckedKeys();
updateRole(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功");
open.value = false;
getList();
});
} else {
form.value.menuIds = getMenuAllCheckedKeys();
addRole(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功");
open.value = false;
getList();
});
}
}
});
}
/** 取消按钮 */
function cancel() {
open.value = false;
reset();
}
/** 选择角色权限范围触发 */
function dataScopeSelectChange(value) {
if (value !== "2") {
deptRef.value.setCheckedKeys([]);
}
}
/** 分配数据权限操作 */
function handleDataScope(row) {
reset();
const deptTreeSelect = getDeptTree(row.roleId);
getRole(row.roleId).then(response => {
form.value = response.data;
openDataScope.value = true;
nextTick(() => {
deptTreeSelect.then(res => {
nextTick(() => {
if (deptRef.value) {
deptRef.value.setCheckedKeys(res.checkedKeys);
}
});
});
});
});
title.value = "分配数据权限";
}
/** 提交按钮(数据权限) */
function submitDataScope() {
if (form.value.roleId != undefined) {
form.value.deptIds = getDeptAllCheckedKeys();
dataScope(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功");
openDataScope.value = false;
getList();
});
}
}
/** 取消按钮(数据权限)*/
function cancelDataScope() {
openDataScope.value = false;
reset();
}
getList();
</script>

View File

@ -0,0 +1,144 @@
<template>
<!-- 授权用户 -->
<el-dialog title="选择用户" v-model="visible" width="800px" top="5vh" append-to-body>
<el-form :model="queryParams" ref="queryRef" :inline="true">
<el-form-item label="用户名称" prop="userName">
<el-input
v-model="queryParams.userName"
placeholder="请输入用户名称"
clearable
style="width: 180px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input
v-model="queryParams.phonenumber"
placeholder="请输入手机号码"
clearable
style="width: 180px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row>
<el-table @row-click="clickRow" ref="refTable" :data="userList" @selection-change="handleSelectionChange" height="260px">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
<el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
<el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" />
<el-table-column label="手机" prop="phonenumber" :show-overflow-tooltip="true" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</el-row>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleSelectUser"> </el-button>
<el-button @click="visible = false"> </el-button>
</div>
</template>
</el-dialog>
</template>
<script setup name="SelectUser">
import { authUserSelectAll, unallocatedUserList } from "@/api/system/role";
const props = defineProps({
roleId: {
type: [Number, String]
}
});
const { proxy } = getCurrentInstance();
const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
const userList = ref([]);
const visible = ref(false);
const total = ref(0);
const userIds = ref([]);
const queryParams = reactive({
pageNum: 1,
pageSize: 10,
roleId: undefined,
userName: undefined,
phonenumber: undefined
});
//
function show() {
queryParams.roleId = props.roleId;
getList();
visible.value = true;
}
/**选择行 */
function clickRow(row) {
proxy.$refs["refTable"].toggleRowSelection(row);
}
//
function handleSelectionChange(selection) {
userIds.value = selection.map(item => item.userId);
}
//
function getList() {
unallocatedUserList(queryParams).then(res => {
userList.value = res.rows;
total.value = res.total;
});
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm("queryRef");
handleQuery();
}
const emit = defineEmits(["ok"]);
/** 选择授权用户操作 */
function handleSelectUser() {
const roleId = queryParams.roleId;
const uIds = userIds.value.join(",");
if (uIds == "") {
proxy.$modal.msgError("请选择要分配的用户");
return;
}
authUserSelectAll({ roleId: roleId, userIds: uIds }).then(res => {
proxy.$modal.msgSuccess(res.msg);
visible.value = false;
emit("ok");
});
}
defineExpose({
show,
});
</script>

View File

@ -0,0 +1,307 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<!-- <el-form-item label="预警信息id" prop="warningId">
<el-input
v-model="queryParams.warningId"
placeholder="请输入预警信息id"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item> -->
<!-- <el-form-item label="类型" prop="types">
<el-input
v-model="queryParams.types"
placeholder="请输入类型"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item> -->
<el-form-item label="类型" prop="types">
<el-select
v-model="queryParams.types"
placeholder="类型"
clearable
style="width: 200px"
>
<el-option
v-for="dict in supervise_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<!-- <el-form-item label="内容" prop="contents">
<el-input
v-model="queryParams.contents"
placeholder="请输入内容"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item> -->
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete"
>删除</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="superviseList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<!-- <el-table-column label="id" align="center" prop="id" /> -->
<el-table-column label="预警信息" align="center" prop="warningName" />
<el-table-column label="类型" align="center" prop="types">
<template #default="scope">
<dict-tag :options="supervise_type" :value="scope.row.types" />
</template>
</el-table-column>
<el-table-column label="内容" align="center" prop="contents" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" >修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" >删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改督办信息对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="superviseRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="预警信息" prop="warningId">
<!-- <el-input v-model="form.warningId" placeholder="请输入预警信息id" /> -->
<el-select
v-model="form.warningId"
placeholder="预警信息"
clearable
style="width: 100%"
>
<el-option
v-for="dict in yjList"
:key="dict.id"
:label="dict.warningSigns"
:value="dict.id"
/>
</el-select>
</el-form-item>
<el-form-item label="类型" prop="types">
<el-select
v-model="form.types"
placeholder="类型"
clearable
style="width: 100%"
>
<el-option
v-for="dict in supervise_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="内容" prop="contents">
<el-input v-model="form.contents" placeholder="请输入内容" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Supervise">
import { listSupervise, getSupervise, delSupervise, addSupervise, updateSupervise } from "@/api/supervise";
const { proxy } = getCurrentInstance();
import {
getYujinList,
} from "@/api/map.js";
const { supervise_type } = proxy.useDict(
"supervise_type"
);
const superviseList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
const yjList = ref([]);
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
warningId: null,
types: null,
contents: null
},
rules: {
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询督办信息列表 */
function getList() {
loading.value = true;
listSupervise(queryParams.value).then(response => {
superviseList.value = response.rows;
total.value = response.total;
loading.value = false;
});
}
function getYjList() {
getYujinList().then(response => {
yjList.value = response.rows;
});
}
//
function cancel() {
open.value = false;
reset();
}
//
function reset() {
form.value = {
id: null,
warningId: null,
types: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
delFlag: null,
contents: null
};
proxy.resetForm("superviseRef");
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm("queryRef");
handleQuery();
}
//
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加督办信息";
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _id = row.id || ids.value
getSupervise(_id).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改督办信息";
});
}
/** 提交按钮 */
function submitForm() {
proxy.$refs["superviseRef"].validate(valid => {
if (valid) {
if (form.value.id != null) {
updateSupervise(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功");
open.value = false;
getList();
});
} else {
addSupervise(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功");
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
const _ids = row.id || ids.value;
proxy.$modal.confirm('是否确认删除督办信息?').then(function() {
return delSupervise(_ids);
}).then(() => {
getList();
proxy.$modal.msgSuccess("删除成功");
}).catch(() => {});
}
/** 导出按钮操作 */
function handleExport() {
proxy.download('system/supervise/export', {
...queryParams.value
}, `supervise_${new Date().getTime()}.xlsx`)
}
getList();
getYjList();
</script>