This commit is contained in:
lcw
2025-11-22 21:59:58 +08:00
parent ea3022c3f6
commit 93c49dff27
661 changed files with 195357 additions and 2160 deletions

View File

@ -0,0 +1,133 @@
<!--
* @Date: 2025-08-06 14:49:49
* @Description: 系统切换窗口
-->
<template>
<!-- append-to-body -->
<div class="a">
<el-dialog
:model-value="modelValue"
class="switch-sys-dialog"
modal-class="switch-sys-dialog-modal"
:show-close="false"
width="75%"
align-center
destroy-on-close
@close="handleModalClick"
>
<div class="switch-sys-dialog__content">
<div class="carousel">
<div
:class="['card-item']"
v-for="(item, index) in list"
:key="item.value"
@click="goPage(item, index)"
>
<img :src="item.icon" class="card-item__img" />
<div class="card-item__label">{{ item.label }}</div>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import fk from '@/assets/images/fk.png'
import ty from '@/assets/images/ty.png'
import pcs from '@/assets/images/pcs.png'
const props = defineProps({
modelValue: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['update:modelValue'])
const list = [
{ label: '俯瞰系统', value: 1, url: `https://tyyy.lz.dsj.xz/overlooking/home`, icon: fk },
{ label: '统一门户', value: 2, url: 'https://tyyy.lz.dsj.xz/portal/home', icon: ty },
{ label: '智慧派出所', value: 3, url: 'https://pcs.lz.dsj.xz:9020/index.html', icon: pcs },
]
const goPage = (item) => {
if (item.url) {
window.open(item.url, '_self')
}
}
// 处理遮罩点击事件
const handleModalClick = () => {
emit('update:modelValue', false)
}
</script>
<style lang="scss">
</style>
<style lang="scss" scoped>
.a{
::v-deep(.el-dialog){
background-color: transparent !important;
}
}
body .el-overlay .el-overlay-dialog .el-dialog{
background-color: transparent !important;
}
.switch-sys-dialog {
&__content {
display: grid;
grid-template-columns: auto 1fr auto;
width: inherit;
// height: 335px;
align-items: center;
.carousel {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 40px;
width: 100%;
height: 100%;
.card-item {
border-radius: 10px;
overflow: hidden;
cursor: pointer;
transition: all 0.25s ease;
&:hover {
transform: scale(1.05);
.card-item__label {
height: 30px;
line-height: 30px;
font-size: 16px;
color: var(--theme-text-color);
font-weight: bold;
}
}
&__img {
width: 100%;
height: auto;
object-fit: cover;
}
&__label {
height: 25px;
line-height: 25px;
background-color: #fff;
text-align: center;
color: var(--text-color-black);
transition: all 0.5s ease;
}
}
}
}
}
</style>

188
src/components/fzq/fxq.vue Normal file
View File

@ -0,0 +1,188 @@
<template>
<div class="floating-ball" :style="ballStyle" @mousedown="startDrag" @touchstart="startDrag" @click="handleClick">
<slot>
</slot>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const props = defineProps({
// 初始位置
initialPosition: {
type: Object,
default: () => ({ x: 0, y: 0 })
},
// 是否可拖动
draggable: {
type: Boolean,
default: true
},
// 自动吸附边缘的阈值
snapThreshold: {
type: Number,
default: 0
}
});
// watch(() => props.initialPosition, (newVal) => {
// position.value = { x: newVal.x, y: newVal.y };
// },{deep:true})
const emit = defineEmits(['click']);
const position = ref({ x: props.initialPosition.x, y: props.initialPosition.y });
const isDragging = ref(false);
const startPos = ref({ x: 0, y: 0 });
const startMousePos = ref({ x: 0, y: 0 });
const ballStyle = ref({
left: `${position.value.x}px`,
top: `${position.value.y}px`,
cursor: props.draggable ? 'move' : 'pointer'
});
// 开始拖动
const startDrag = (e) => {
if (!props.draggable) return;
isDragging.value = true;
startPos.value = { ...position.value };
// 处理鼠标和触摸事件
if (e.type === 'mousedown') {
startMousePos.value = { x: e.clientX, y: e.clientY };
} else if (e.type === 'touchstart') {
startMousePos.value = { x: e.touches[0].clientX, y: e.touches[0].clientY };
}
// 阻止默认行为和冒泡
e.preventDefault();
e.stopPropagation();
};
// 处理移动
const handleMove = (e) => {
if (!isDragging.value) return;
let clientX, clientY;
if (e.type === 'mousemove') {
clientX = e.clientX;
clientY = e.clientY;
} else if (e.type === 'touchmove') {
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
}
const dx = clientX - startMousePos.value.x;
const dy = clientY - startMousePos.value.y;
position.value = {
x: startPos.value.x + dx,
y: startPos.value.y + dy
};
updatePosition();
};
// 结束拖动
const endDrag = () => {
if (!isDragging.value) return;
isDragging.value = false;
snapToEdge();
};
// 自动吸附到边缘
const snapToEdge = () => {
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
// 检查是否靠近左右边缘
if (position.value.x < props.snapThreshold) {
position.value.x = 0;
} else if (position.value.x > windowWidth - props.snapThreshold) {
position.value.x = windowWidth;
}
// 检查是否靠近上下边缘
if (position.value.y < props.snapThreshold) {
position.value.y = 0;
} else if (position.value.y > windowHeight - props.snapThreshold) {
position.value.y = windowHeight;
}
updatePosition();
};
// 更新位置样式
const updatePosition = () => {
ballStyle.value = {
...ballStyle.value,
left: `${position.value.x}px`,
top: `${position.value.y}px`
};
};
// 点击事件
const handleClick = (e) => {
if (isDragging.value) {
// 如果是拖动结束的点击,不触发点击事件
isDragging.value = false;
return;
}
emit('click', e);
};
// 添加事件监听
onMounted(() => {
window.addEventListener('mousemove', handleMove);
window.addEventListener('touchmove', handleMove);
window.addEventListener('mouseup', endDrag);
window.addEventListener('touchend', endDrag);
});
// 移除事件监听
onUnmounted(() => {
window.removeEventListener('mousemove', handleMove);
window.removeEventListener('touchmove', handleMove);
window.removeEventListener('mouseup', endDrag);
window.removeEventListener('touchend', endDrag);
});
</script>
<style scoped>
.floating-ball {
position: fixed;
cursor: pointer;
width: 50px;
padding: 10px;
/* height: 50px; */
/* border-radius: 50%; */
/* background-color: #409eff; */
color: white;
/* display: flex;
justify-content: center;
align-items: center; */
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
z-index: 9999;
user-select: none;
/* transition: all 0.3s ease; */
transform: translate(-50%, -50%);
}
.ball-content {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}
.icon {
font-size: 24px;
}
.floating-ball:active {
opacity: 0.8;
}
</style>

View File

@ -0,0 +1,57 @@
<template>
<div class="iframe-container">
<el-dialog class="dialog-container" :model-value="modelValue" width="75%" :show-close="false" @close="close">
<div style="height: 75vh;">
<div class="close" @click="close"><el-icon :size="30"><CircleClose /></el-icon></div>
<iframe :src="src" frameborder="0" width="100%" height="100%"></iframe>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps({
modelValue: {
type: Boolean,
required: true
}, title: {
type: String,
default: '提示'
}, showFooter: {
type: Boolean,
default: true
}, src: {
type: String,
default: ''
}
})
const emit = defineEmits(['update:modelValue', 'submit', 'close'])
const close = () => {
emit('update:modelValue', false)
emit('close')
}
const submit = () => {
emit('submit')
}
</script>
<style lang="scss" scoped>
.iframe-container {
::v-deep .el-dialog__header {
display: none;
}
.close{
position: absolute;
top: 0px;
right: -35px;
cursor: pointer;
border-radius: 50%;
color: #fff;
}
::v-deep .el-dialog__body {
padding: 0 !important;
}
}
</style>

View File

@ -0,0 +1,256 @@
<template>
<Fxq :initial-position="{ x: position.x, y: position.y }" v-if="showFxq" :snapThreshold="50">
<div class="badge-container">
<div>
<div>
<el-tooltip effect="dark" content="林小警" placement="left-start">
<img style="width: 34px;height: 34px;"
@click.stop="skipIframe(`https://tyyy.lz.dsj.xz/embed/home?userId=${userId}&clientKey=${clientKey}&avatar=''`)"
class="box-item" src="@/assets/images/streetBi/lxj.png" />
</el-tooltip>
<el-tooltip effect="dark" content="切换门户" placement="left-start">
<img style="width: 34px;height: 34px;" @click.stop="SwitchSysDialogShow = true" class="box-item"
src="@/assets/images/streetBi/sst.png" />
</el-tooltip>
<el-tooltip effect="dark" content="蜂群消息" placement="left-start">
<img style="width: 34px;height: 34px; margin-bottom: 14px;"
@click.stop="skipIframe('https://fqxt.lz.dsj.xz:9020/fqxt/?source=other')" class="box-item"
src="@/assets/images/streetBi/fq.png" />
</el-tooltip>
</div>
<el-badge :value="xxListData.xtxxNumber" class="item badge-top-left">
<div class='fxq fxq1' @click.stop="opneMsg('xtxx')">
<div class="title">
<img src="@/assets/images/streetBi/xtxx.png" />
<span>系统消息</span>
</div>
</div>
</el-badge>
<el-badge :value="xxListData.tztgNumber" class="item badge-top-left">
<div class='fxq fxq2' @click.stop="opneMsg('tztg')">
<div class="title">
<img src="@/assets/images/streetBi/tztg.png" />
<span>通知通报</span>
</div>
</div>
</el-badge>
</div>
</div>
</Fxq>
<Iframe v-model='showIframe' :src='src' />
<SwitchSysDialog v-model="SwitchSysDialogShow" />
<Information v-model='showDialog' :title='title'>
<systemMessages :dict="{ BD_D_XXLX, BD_D_XXLY }" :idEntityCard='idEntityCard' :xxlx="showMsgLx" />
</Information>
</template>
<script setup>
import { ref, nextTick, provide, onMounted, reactive, getCurrentInstance, onUnmounted } from "vue";
import { queryWdxxPageList, queryWdxxDetail } from '@/api/commit.js'
import SwitchSysDialog from '@/components/fzq/SwitchSysDialog.vue'
import systemMessages from '@/components/fzq/systemMessages.vue'
import Information from '@/components/fzq/information.vue'
import Fxq from '@/components/fzq/fxq.vue'
import { getItem } from '@/utils/storage.js'
import { getCookie } from '@/utils/cookie'
import Iframe from '@/components/fzq/iframe.vue'
import emitter from "@/utils/eventBus.js";
const { proxy } = getCurrentInstance();
const { BD_D_XXLX, BD_D_XXLY } = proxy.$dict('BD_D_XXLX', 'BD_D_XXLY'); //获取字典
const position = reactive({
x: window.innerWidth - 30,
y: window.innerHeight - 160
})
const idEntityCard = ref('')
const xxListData = reactive({
xtxxNumber: 0,
tztgNumber: 0
})
//请求数据
const handleClick = () => {
let promes = {
page: 1,
rows: 1,
jsrid: idEntityCard.value,
xxlx: ""
}
queryWdxxPageList({ ...promes, xxlx: 100 }).then((res) => {
xxListData.xtxxNumber = res.total
});
queryWdxxPageList({ ...promes, xxlx: 200 }).then((res) => {
xxListData.tztgNumber = res.total
});
}
const userId = getItem('USERID')
const clientKey = getCookie('clientKey')
const SwitchSysDialogShow = ref(false)
const src = ref()
const showIframe = ref(false)
const skipIframe = (val) => {
src.value = val
showIframe.value = true
}
const title = ref('系统消息')
const showDialog = ref(false)
const showMsgLx = ref('')
const showFxq = ref(true)
const opneMsg = (val) => {
showDialog.value = true
showMsgLx.value = val
switch (val) {
case 'xtxx':
title.value = '系统消息'
break;
case 'tztg':
title.value = '通知通告'
break;
}
}
const intTime = ref(null)
onMounted(() => {
if (window.parent !== window.self) {
showFxq.value = false
} else {
showFxq.value = true
}
emitter.on("handleClick", () => {
idEntityCard.value = getItem('idEntityCard')
handleClick()
intTime.value = setInterval(() => {
handleClick()
}, 60000)
});
})
onUnmounted(() => {
clearInterval(intTime.value)
emitter.off("handleClick")
})
</script>
<style lang="scss" scoped>
// 蜂群组件样式
.fxqx {
border-radius: 34px;
width: 34px;
background-color: rgb(1, 127, 245);
margin-bottom: 18px;
display: flex;
align-items: center;
position: relative;
.title {
height: 34px;
line-height: 34px;
display: flex;
align-items: center;
white-space: nowrap;
img {
margin-left: 9.5px;
width: 16px;
margin-right: 10px;
vertical-align: middle;
height: 16px;
flex-shrink: 0;
}
span {
opacity: 0;
transition: opacity 0.2s ease 0.1s;
padding-right: 15px;
}
}
}
.fxq {
border-radius: 34px;
width: 34px;
transition: width 0.3s ease;
background-color: rgb(1, 127, 245);
// overflow: hidden;
margin-bottom: 10px;
display: flex;
align-items: center;
position: relative;
.title {
height: 34px;
line-height: 34px;
display: flex;
align-items: center;
white-space: nowrap;
img {
margin-left: 9.5px;
width: 16px;
margin-right: 10px;
vertical-align: middle;
height: 16px;
flex-shrink: 0;
}
span {
opacity: 0;
transition: opacity 0.2s ease 0.1s;
padding-right: 15px;
}
}
}
.fxq2 {
background-color: #9d88f9;
}
.fxq3 {
background-color: #00c07f;
}
.fxq:hover {
width: 120px;
}
.fxq:hover .title span {
opacity: 1;
}
.item {
margin-bottom: 10px;
}
.box-item {
margin-bottom: 10px;
}
.badge-content {
display: flex;
flex-direction: column;
overflow: hidden;
transition: all 0.3s ease;
max-height: 200px;
/* 默认展开的最大高度 */
min-height: 0;
/* 确保收缩时有足够空间显示第一个图标 */
}
.badge-content:not(.expanded) {
max-height: 0;
}
/* 收缩时只显示第一个图标,隐藏其他内容 */
.badge-content:not(.expanded)> :not(:first-child) {
opacity: 0;
max-height: 0;
margin: 0;
padding: 0;
overflow: hidden;
}
::v-deep .el-badge__content.is-fixed {
right: calc(100% + 6px);
}
</style>

View File

@ -0,0 +1,53 @@
<template>
<el-dialog class="dialog-container"
:model-value="modelValue"
:title="title"
:before-close="close" :destroy-on-close="true"
>
<slot></slot>
<template #footer v-if="showFooter">
<div class="dialog-footer" >
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="submit">
确认
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import {ref} from 'vue'
const props=defineProps({
modelValue: {
type: Boolean,
required: true
},title:{
type:String,
default:'提示'
},showFooter:{
type:Boolean,
default:true
}
})
const emit=defineEmits(['update:modelValue','submit','close'])
const close = () => {
emit('update:modelValue',false)
emit('close')
}
const submit=()=>{
emit('submit')
}
</script>
<style lang="scss" scoped>
// @import "@/assets/css/homeScreen.scss";
::v-deep .el-dialog__body{
padding-top: 0 !important;
padding-bottom: 0 !important;
}
</style>

View File

@ -0,0 +1,184 @@
<template>
<!-- <el-button type="success" style='position: absolute;right:30px;'>一键忽略</el-button> -->
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="chageHandle">
<el-tab-pane label="未查看" name="first">
<MyTable customClass="zdy_peo_table" :tableData="pageData.tableData" :tableColumn="pageData.tableColumn"
:tableHeight="pageData.tableHeight" :key="pageData.keyCount" :tableConfiger="pageData.tableConfiger"
:controlsWidth="pageData.controlsWidth">
<template #xxly="{ row }">
<DictTag :tag="false" :value="row.xxly" :options="dict.BD_D_XXLY" />
</template>
<template #xxlx="{ row }">
<DictTag :tag="false" :value="row.xxlx" :options="dict.BD_D_XXLX" />
</template>
<template #controls="{ row }">
<el-button size="small" type="primary" @click="handleDetail(row)">查看</el-button>
</template>
</MyTable>
<Pages @changeNo="changeNo" @changeSize="changeSize" :tableHeight="pageData.tableHeight" :pageConfiger="{
...pageData.pageConfiger,
total: pageData.total
}"></Pages>
</el-tab-pane>
<el-tab-pane label="已查看" name="second">
<MyTable customClass="zdy_peo_table" :tableData="pageData.tableData" :tableColumn="pageData.tableColumn"
:tableHeight="pageData.tableHeight" :key="pageData.keyCount" :tableConfiger="pageData.tableConfiger"
:controlsWidth="pageData.controlsWidth">
<template #xxly="{ row }">
<DictTag :tag="false" :value="row.xxly" :options="dict.BD_D_XXLY" />
</template>
<template #xxlx="{ row }">
<DictTag :tag="false" :value="row.xxlx" :options="dict.BD_D_XXLX" />
</template>
<template #controls="{ row }">
<el-button size="small" type="primary" @click="handleDetail(row)">查看</el-button>
</template>
</MyTable>
<Pages @changeNo="changeNo" @changeSize="changeSize" :tableHeight="pageData.tableHeight" :pageConfiger="{
...pageData.pageConfiger,
total: pageData.total
}"></Pages>
</el-tab-pane>
</el-tabs>
<Information v-model='showDialog' title='消息详情' :showFooter="false">
<Xtxi :item="msgDetail" v-if="xxlx == 'xtxx'" :dict="dict" />
</Information>
</template>
<script setup>
import { ref, reactive } from 'vue'
import MyTable from "@/components/aboutTable/MyTable.vue";
import Pages from "@/components/aboutTable/Pages.vue";
import Information from "./information.vue";
import { queryYdxxPageList, queryWdxxPageList, queryWdxxDetail, queryYdxxDetail, qsXx } from '@/api/commit.js'
import Xtxi from './xtxi.vue'
const props = defineProps({
dict: {
type: Object,
default: () => {
}
}, idEntityCard: {
type: String,
default: ''
}, xxlx: {
type: String,
default: 'xtxx'
}
})
const activeName = ref('first')
const pageData = reactive({
tableData: [],
keyCount: 0,
tableConfiger: {
loading: false,
rowHieght: 40,
haveControls: true,
},
controlsWidth: 160, //操作栏宽度
total: 0,
pageConfiger: {
pageSize: 20,
pageCurrent: 1
}, //分页
tableColumn: [
{ label: "消息标题", prop: "xxbt", showOverflowTooltip: true },
{ label: "消息来源", prop: "xxly", showOverflowTooltip: true, showSolt: true },
{ label: "消息描述", prop: "xxms", showOverflowTooltip: true },
{ label: "消息类型", prop: "xxlx", showOverflowTooltip: true, showSolt: true },
], tableHeight: "calc(80vh - 350px)",
});
const chageHandle = () => {
pageData.pageConfiger.pageCurrent = 1
pageData.pageConfiger.pageSize = 20
handleClick()
}
//请求数据
const handleClick = async () => {
let promes = {
page: pageData.pageConfiger.pageCurrent,
rows: pageData.pageConfiger.pageSize,
jsrid: props.idEntityCard,
xxlx: ""
}
switch (props.xxlx) {
case 'xtxx':
promes.xxlx = 100
const res = activeName.value == 'first' ? await queryWdxxPageList(promes) : await queryYdxxPageList(promes)
pageData.tableData = res.rows
pageData.total = res.total
break;
case 'tztg':
promes.xxlx = 200
const tztgRes = activeName.value == 'first' ? await queryWdxxPageList(promes) : await queryYdxxPageList(promes)
pageData.tableData = tztgRes.rows
pageData.total = tztgRes.total
break;
default:
break;
}
}
const changeNo = (val) => {
pageData.pageConfiger.pageCurrent = val
handleClick()
}
const changeSize = (val) => {
pageData.pageConfiger.pageSize = val
handleClick()
}
handleClick()
// 查看详情
const showDialog = ref(false)
const msgDetail = ref({})
const disposition = (item) => {
let arrId = ''
if (Array.isArray(item)) {
const itemMap = item.map(it => {
return it.id
})
arrId = itemMap.join(',')
} else {
arrId = item.id
}
const promes = {
xxlx: '100',
id: arrId
}
qsXx(promes).then((result) => {
handleClick()
}).catch((err) => {
console.log(err);
});
}
const handleDetail = async (item) => {
showDialog.value = true
const res = activeName.value == 'first' ? await queryWdxxDetail({ id: item.id }) : await queryYdxxDetail({ id: item.id })
if (res) {
msgDetail.value = res[0]
if (msgDetail.value.qszt == '0') {
disposition(item)
}
}
}
</script>
<style lang="scss" scoped>
// @import "@/assets/css/homeScreen.scss";
.zdy_peo_table td.el-table__cell {
color: #ffffff !important;
}
.zdy_peo_table th.el-table__cell {
color: #ffffff !important;
font-size: 15px;
}
.zdy_peo_table .el-table__body tr.el-table__row--striped td.el-table__cell {
background: transparent !important;
}
.zdy_peo_table .table_blue_row {
background: linear-gradient(to right, #001D4B 0%, rgba(0, 29, 75, 0.1) 100%) !important;
}
</style>

View File

@ -0,0 +1,53 @@
<template>
<div style="height: 300px;overflow: auto;font-size: 16px;">
<div>通知标题{{ item.xxbt }}</div>
<div class="mt">通知内容{{ item.xxms }}</div>
<div class="flex align-center just-between mt">
<div class="flex align-center">接收类型
<DictTag :tag="false" :value="item.xxlx" :options="dict.BD_D_XXLX" />
</div>
<div class="flex align-center">消息来源
<DictTag :tag="false" :value="item.xxly" :options="dict.BD_D_XXLY" />
</div>
</div>
</div>
</template>
<script setup>
import {ref} from 'vue'
const props = defineProps({
item: {
type: Object,
default: () => {
}
}, dict: {
type: Object,
default: () => {
}
},
})
</script>
<style lang="scss" scoped>
// @import "@/assets/css/homeScreen.scss";
.zdy_peo_table td.el-table__cell {
color: #ffffff !important;
}
.zdy_peo_table th.el-table__cell {
color: #ffffff !important;
font-size: 15px;
}
.zdy_peo_table .el-table__body tr.el-table__row--striped td.el-table__cell {
background: transparent !important;
}
.zdy_peo_table .table_blue_row {
background: linear-gradient(to right, #001D4B 0%, rgba(0, 29, 75, 0.1) 100%) !important;
}
.mt {
margin-top: 20px;
}
</style>