This commit is contained in:
2025-12-19 11:41:50 +08:00
13 changed files with 878 additions and 132 deletions

View File

@ -109,4 +109,13 @@ export function upImageUploadId(data) {
})
}
/** 发送指令 (网上会议室)*/
export function wshsSendFqzl(data) {
return request({
url: gsxtApi + '/wshs/sendFqzl',
method: 'post',
data
})
}

View File

@ -26,41 +26,6 @@ export const tacticalGet = (params) => {
})
}
// 战术研判-新增
export const tacticalPost = (data, url) => {
return request({
url: api,
method: "post",
data
})
}
// 战术研判-删除
export const tacticalDelete = (data, url) => {
return request({
url: api,
method: "delete",
data
})
}
// 战术研判-根据ID查询详情
export const tacticalGetInfo = (id) => {
return request({
url: api + `/tactical/${id}`,
method: "get"
})
}
// 战术研判-编辑
export const tacticalPut = (data, url) => {
return request({
url: api,
method: "put",
data
})
}
/**
* 开始研判
@ -85,10 +50,79 @@ export const endJudgment = (id) => {
method: "post"
})
}
/**
* 网上会商(新增)
* @param {Object} data 会商数据
* @param {Array<string>} [data.chryList] 参会人员ID列表
* @param {string} [data.czyj] 会商处置意见
* @param {string} [data.glxsid] 关联线索ID(采集ID)
* @param {string} [data.glxsmc] 关联线索名称
* @param {string} [data.hsbt] 会商标题
* @param {string} [data.hsjssj] 会商结束时间 (ISO 8601格式)
* @param {string} [data.hskssj] 会商开始时间 (ISO 8601格式)
* @param {string} [data.hsnr] 会商内容
* @param {string} [data.hybh] 会议编号
* @param {string} [data.id] ID
* @param {string} [data.plnr] 评论内容
* @param {string} [data.tsypid] 态势研判ID
* @returns {Promise} 请求Promise对象
* @returns {Promise} 请求Promise对象
*/
export const wshsAdd = (data) => {
return request({
url: api + `/wshs/add`,
method: "post",
data
})
}
/**
* 新增研判报告
* @param {Object} data 研判报告数据
* @param {string} data.bgmc - 报告名称
* @param {string} data.bgnr - 报告内容
* @param {string} data.bz - 备注
* @param {string} data.cjrsfzh - 创建人身份证号
* @param {string} data.cjrxm - 创建人姓名
* @param {string} data.id - ID
* @param {string} data.scsj - 生成时间
* @param {string} data.ssbm - 所属部门
* @param {string} data.ssbmdm - 所属部门代码
* @param {string} data.sssgaj - 所属市公安局
* @param {string} data.sssgajdm - 所属市公安局代码
* @param {string} data.ssxgaj - 所属县公安局
* @param {string} data.ssxgajdm - 所属县公安局代码
* @param {string} data.ypid - 研判来源ID(数据整理ID)
* @param {string} data.ypmc - 研判名称
* @returns {Promise} 请求Promise对象
*/
export const gsxtYpbgAddEntity = (data) => {
return request({
url: api + `/gsxtYpbg/addEntity`,
method: "post",
data
})
}
/**
* 自建研判报告
* @param {Object} data 自建研判数据
* @param {Array<Object>} data.cyypList 自建研判数据列表
* @param {string} data.cyypList[].fj - 附件
* @param {string} data.cyypList[].id - ID
* @param {string} data.cyypList[].scyq - 素材要求
* @param {string} data.cyypList[].sjzlid - 研判数据整理ID
* @param {string} data.cyypList[].wcqk - 完成情况01 准备中、02 已完成)
* @param {string} data.cyypList[].wcsj - 完成时间
* @param {string} data.cyypList[].ypbmdm - 研判部门代码
* @param {string} data.cyypList[].ypbmmc - 研判部门名称
* @param {Object} data.ypbgDto 研判报告DTO
* @param {string} data.ypbgDto.bgmc 报告名称
* @param {string} data.ypbgDto.bgnr 报告内容
* @param {string} data.ypbgDto.ypmc 研判名称
* @param {string} data.bglx 报告类型01 战术研判 02 战略研判)
* @param {string} data.ypyq 研判要求
* @param {string} data.ypyt 研判议题
* @returns {Promise} 请求Promise对象
*/
export const selfBuildJudgment = (data) => {
@ -102,11 +136,11 @@ export const selfBuildJudgment = (data) => {
/**
* 研判指令列表
* @param {Object} params 查询参数
* @param {number} [params.pageSize] 每页显示数量
* @param {number} [params.pageCurrent] 页码
* @param {string} [params.zlbt] 指令标题
* @param {string} [params.zlnr] 指令内容
* @param {string} [params.fqrxm] 发起人姓名
* @param {number} params.pageSize 每页显示数量
* @param {number} params.pageCurrent 页码
* @param {string} params.zlbt 指令标题
* @param {string} params.zlnr 指令内容
* @param {string} params.fqrxm 发起人姓名
* @returns {Promise} 请求Promise对象
*/
export const getJudgmentCommandList = (params) => {

View File

@ -14,7 +14,7 @@
<script setup>
import { reactive, ref, onMounted, watch } from "vue";
import { sendFqzl, ZdrfjSendFqzl ,qbcjSendFqzl} from '@/api/commit'
import { sendFqzl, ZdrfjSendFqzl ,qbcjSendFqzl, wshsSendFqzl} from '@/api/commit'
import { getItem } from '@/utils/storage'
import * as MOSTY from "@/components/MyComponents/index";
import ChooseUser from "@/components/ChooseList/ChooseUser/index.vue"
@ -86,6 +86,8 @@ const getsendFqzl = () => {
break;
case 'qbcj':
res = await qbcjSendFqzl(promes)
case 'meeting': // 网上会议室
res = await wshsSendFqzl(promes)
break;
}
const str = JSON.parse(res)

View File

@ -46,17 +46,22 @@ import Footer from './footer.vue'
// import useCallModule from '../sdk/call.js'
const { proxy } = getCurrentInstance();
// const Call = useCallModule()
window.lemon.login.login({
username: "linzhigongan1",
password: "linzhigongan1",
realm: "puc.com",
webpucUrl: "https://192.168.0.1:16888",
}).then(res => {
console.log(res,"登录成功");
}).catch(err => {
console.log(err,"登录失败");
try {
window.lemon.login.login({
username: "linzhigongan1",
password: "linzhigongan1",
realm: "puc.com",
webpucUrl: "https://192.168.0.1:16888",
}).then(res => {
console.log(res,"登录成功");
}).catch(err => {
console.log(err,"登录失败");
})
})
} catch (error) {
console.log('error: ', error);
}
const props = defineProps({

View File

@ -0,0 +1,104 @@
<template>
<el-dialog v-model="dialogVisible" title="会商反馈" width="600px" :before-close="handleClose" destroy-on-close>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="反馈内容" prop="fknr">
<el-input v-model="form.fknr" type="textarea" :rows="6" placeholder="请输入反馈内容" maxlength="500" show-word-limit />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" :loading="loading" @click="handleSave">
保存
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import { wshsHsfk } from '@/api/huiShangyp/onlineConsultationRoom.js'
const emit = defineEmits(['success'])
const dialogVisible = ref(false)
const loading = ref(false)
const formRef = ref()
const currentHsid = ref('')
const form = reactive({
fknr: ''
})
const rules = {
fknr: [
{ required: true, message: '请输入反馈内容', trigger: 'blur' },
{ min: 2, message: '反馈内容至少2个字符', trigger: 'blur' }
]
}
// 打开弹框
const open = (hsid) => {
currentHsid.value = hsid
form.fknr = ''
dialogVisible.value = true
}
// 关闭弹框
const handleClose = () => {
dialogVisible.value = false
form.fknr = ''
currentHsid.value = ''
if (formRef.value) {
formRef.value.resetFields()
}
}
// 保存反馈
const handleSave = async () => {
if (!formRef.value) return
try {
const valid = await formRef.value.validate()
if (!valid) return
loading.value = true
const params = {
hsid: currentHsid.value,
fknr: form.fknr
}
await wshsHsfk(params)
ElMessage.success('反馈保存成功')
handleClose()
emit('success') // 通知父组件刷新列表
} catch (error) {
ElMessage.error('保存失败,请重试')
} finally {
loading.value = false
}
}
defineExpose({
open
})
</script>
<style scoped>
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
:deep(.el-textarea__inner) {
resize: vertical;
}
</style>

View File

@ -0,0 +1,197 @@
<template>
<el-dialog
v-model="dialogVisible"
title="查看反馈"
width="800px"
:before-close="handleClose"
>
<div class="feedback-container" v-loading="loading">
<!-- 反馈列表 -->
<div class="feedback-list" v-if="currFkListList.length > 0">
<div
v-for="(feedback, index) in currFkListList"
:key="index"
class="feedback-item"
>
<div class="feedback-header">
<div class="user-info">
<el-avatar :size="32" :src="feedback.avatar">
{{ feedback.fkrxm?.charAt(0) || '用' }}
</el-avatar>
<span class="username">{{ feedback.fkrxm || '匿名用户' }}</span>
</div>
<div class="feedback-time" v-if="feedback.fksj">
{{ formatTime(feedback.fksj) }}
</div>
</div>
<div class="feedback-content">
{{ feedback.fknr }}
</div>
</div>
</div>
<!-- 暂无反馈 -->
<el-empty
v-else
description="暂无反馈信息"
:image-size="120"
/>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">关闭</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref, computed } from 'vue'
const dialogVisible = ref(false)
const loading = ref(false)
const currFkListList = ref([])
// // 计算所有反馈列表
// const currFkListList = computed(() => {
// let allFeedbacks = []
// if (currFkListList.value && Array.isArray(currFkListList.value)) {
// currFkListList.value.forEach(item => {
// if (item.fkList && Array.isArray(item.fkList)) {
// item.fkList.forEach(feedback => {
// allFeedbacks.push({
// ...feedback,
// // 如果有评论内容,也一并显示
// plnr: item.plnr
// })
// })
// }
// })
// }
// // 按时间排序(如果有时间字段)
// return allFeedbacks.sort((a, b) => {
// if (a.fksj && b.fksj) {
// return new Date(b.fksj) - new Date(a.fksj)
// }
// return 0
// })
// })
// 打开弹框
const open = (fkList) => {
currFkListList.value = fkList || []
dialogVisible.value = true
}
// 关闭弹框
const handleClose = () => {
dialogVisible.value = false
currFkListList.value = []
}
// 格式化时间
const formatTime = (timeStr) => {
if (!timeStr) return ''
try {
const date = new Date(timeStr)
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
})
} catch (error) {
return timeStr
}
}
defineExpose({
open
})
</script>
<style scoped>
.feedback-container {
max-height: 500px;
overflow-y: auto;
}
.feedback-list {
display: flex;
flex-direction: column;
gap: 16px;
}
.feedback-item {
border: 1px solid #e4e7ed;
border-radius: 8px;
padding: 16px;
background-color: #fafafa;
transition: all 0.3s ease;
}
.feedback-item:hover {
border-color: #409eff;
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
}
.feedback-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.user-info {
display: flex;
align-items: center;
gap: 8px;
}
.username {
font-weight: 500;
color: #303133;
font-size: 14px;
}
.feedback-time {
font-size: 12px;
color: #909399;
}
.feedback-content {
color: #606266;
line-height: 1.6;
font-size: 14px;
white-space: pre-wrap;
word-break: break-word;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
}
/* 滚动条样式 */
.feedback-container::-webkit-scrollbar {
width: 6px;
}
.feedback-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.feedback-container::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.feedback-container::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
</style>

View File

@ -32,12 +32,18 @@
</div>
<div class="right">
<el-button type="primary" size="small" @click="joinMeeting(item, '会议')">加入会议</el-button>
<el-button type="primary" size="small">反馈情况</el-button>
<el-button type="primary" size="small">处置下发</el-button>
<el-button type="primary" size="small" @click="feedBack(item)">反馈情况</el-button>
<el-button v-if="cityIntelligenceCommand" type="primary" size="small"
@click="disposeIssue(item)">处置下发</el-button>
</div>
</div>
<div class="bottom">
<el-link type="primary" @click="viewFeedback(item)">
<el-icon>
<View />
</el-icon>查看反馈
</el-link>
<el-popover placement="top" :visible="item.visible" :width="400" trigger="click">
<template #reference>
<el-link type="primary"><el-icon>
@ -76,6 +82,17 @@
<!-- 音视频会议窗口 -->
<MeetingView ref="refMeetingView" :update="updateItem"></MeetingView>
<!-- 反馈弹窗 -->
<FeedbackForm ref="feedbackFormRef" @success="getList" />
<!-- 查看反馈弹窗 -->
<ViewFeedback ref="viewFeedbackRef" />
<Information v-model="showDialog" title="发送指令" @submit="seedFqzl" @close='close'>
<SemdFqzl ref="semdFqzlRef" :itemData="itemData" @handleClose="handleClose" identification="meeting"
:tacitly="tacitly" />
</Information>
</template>
<script setup>
@ -87,10 +104,16 @@ import Pages from "@/components/aboutTable/Pages.vue";
import Search from "@/components/aboutTable/Search.vue";
import DetailForm from "./components/detailForm.vue";
import RoomDetail from "./components/roomDetail.vue";
import FeedbackForm from "./components/FeedbackForm.vue";
import ViewFeedback from "./components/ViewFeedback.vue";
import { useRoute, useRouter } from 'vue-router';
import { qcckGet, qcckPost, qcckDelete } from "@/api/qcckApi.js";
import { reactive, ref, onMounted, getCurrentInstance, nextTick } from "vue";
import ConferenceRoom from "./components/Communications/conferenceRoom.vue";
import SemdFqzl from '@/components/instructionHasBeen/sendFqzl.vue'
import Information from "@/views/home/model/information.vue";
import { isShiQingZhi } from "@/utils/auth.js"
const router = useRouter();
const route = useRoute();
const { proxy } = getCurrentInstance();
@ -98,6 +121,19 @@ const detailDiloag = ref();
const searchBox = ref(); //搜索框
const openMeeting = ref(false); // 打开会议窗口
const refMeetingView = ref()
const feedbackFormRef = ref() // 反馈弹窗引用
const viewFeedbackRef = ref() // 查看反馈弹窗引用
const semdFqzlRef = ref()
/** 发送指令Item */
const itemData = ref({})
/** 市情指 */
const cityIntelligenceCommand = ref(isShiQingZhi())
/** 下发指令 */
const showDialog = ref(false)
const tacitly = {
// title:'ryXm'
}
const searchConfiger = ref([
{
label: "会议主题",
@ -123,6 +159,9 @@ const pageData = reactive({
},
});
const jsonData = ref('')
/** 当前行数据 */
const currRow = ref({})
onMounted(() => {
jsonData.value = require('@/components/Consultation/components/zh_CN.json');
getList();
@ -191,6 +230,16 @@ const joinMeeting = (item, type) => {
item.number = item.hybh;
refMeetingView.value.openInit(item, type)
};
// 反馈情况
const feedBack = (item) => {
feedbackFormRef.value.open(item.id);
};
// 查看反馈
const viewFeedback = (item) => {
viewFeedbackRef.value.open(item.fkList || []);
};
// 删除
const delDictItem = (id) => {
@ -219,6 +268,21 @@ const tabHeightFn = () => {
// 会议详情弹窗
const conferenceRoomVisible = ref(false);
const seedFqzl = () => {
semdFqzlRef.value.getsendFqzl()
}
const handleClose = () => {
showDialog.value = false;
}
// 处置下发
const disposeIssue = (item) => {
showDialog.value = true;
itemData.value = item
};
</script>
<style lang="scss" scoped>

View File

@ -46,22 +46,28 @@ import PageTitle from "@/components/aboutTable/PageTitle.vue";
import MyTable from "@/components/aboutTable/MyTable.vue";
import Pages from "@/components/aboutTable/Pages.vue";
import Search from "@/components/aboutTable/Search.vue";
import { useRoute } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import { tacticalGet, strategicDelete } from "@/api/huiShangyp/strategicApi.js";
import { reactive, ref, onMounted, getCurrentInstance, watch,computed } from "vue";
import { reactive, ref, onMounted, getCurrentInstance, watch, computed,nextTick } from "vue";
import AddForm from "./addForm.vue";
const { proxy } = getCurrentInstance();
const { D_BZ_YPFS, D_BZ_YPLX } = proxy.$dict("D_BZ_YPFS", "D_BZ_YPLX")
const detailDiloag = ref();
const searchBox = ref(); //搜索框
const router = useRouter();
const route = useRoute();
onMounted(() => {
tabHeightFn()
if (route.query.id) {
detailDiloag.value.init('edit', {
id: route.query.id
});
return
nextTick(() => {
addForm.value && addForm.value.init('edit', {
id: route.query.id
});
router.replace({ path: '/strategicResearch' })// 移除id 避免刷新一直带参数
})
}
getList()
});
/** 是否市情报指挥中心 */
@ -136,11 +142,10 @@ const getList = () => {
// 表格高度计算
const tabHeightFn = () => {
pageData.tableHeight = window.innerHeight - searchBox.value.offsetHeight - 250;
window.onresize = function () {
window.onresize = function() {
tabHeightFn();
};
};
const route = useRoute()
const addForm = ref(null)
const getDataById = (type, row) => {

View File

@ -8,20 +8,19 @@
</div>
</div>
<div style="padding-bottom: 0px;" class="form_cnt">
<FormMessage v-model="listQuery" :formList="formData" ref="elform" :rules="rules">
<FormMessage v-model="listQuery" :formList="formData" ref="elform" :rules="rules" :disabled="title === '详情'">
<template #bmList>
<div class="table-box">
<el-table :data="tableList" border style="width: 100%">
<el-table-column prop="ypbmmc" label="部门" width="150" align="center" />
<el-table-column label="研判素材" width="380" align="center">
<template #default="{ row }">
<el-input v-model="row.scyq" type="textarea" :rows="3" :disabled="true" placeholder="请输入研判素材" />
<el-input v-model="row.scyq" placeholder="请输入研判素材" />
</template>
</el-table-column>
<el-table-column label="附件" width="200" align="center">
<template #default="{ row }">
<UploadFile v-model="row.fj" :disabled="true" :limit="1" :isImg="false"
:isAll="true" />
<UploadFile v-model="row.fj" :limit="1" :isImg="false" :isAll="true" />
</template>
</el-table-column>
<el-table-column prop="wcqk" label="完成状态" width="120" align="center">
@ -91,16 +90,29 @@
</template>
<script setup>
/**
* @typedef {Object} JudgmentDept 研判部门对象
* @property {string} ypbmdm - 研判部门代码
* @property {string} ypbmmc - 研判部门名称
* @property {string} scyq - 素材要求
* @property {Array} fj - 附件数组
* @property {string} wcqk - 完成情况01 准备中、02 已完成)
*/
import FormMessage from "@/components/aboutTable/FormMessage.vue";
import UploadFile from "@/components/MyComponents/Upload/index.vue";
// import ChooseUser from "@/components/ChooseList/ChooseUser/index.vue"
import { ref, defineExpose, reactive, defineEmits, getCurrentInstance, watch, computed } from "vue";
import { sjzlAddEntity, sjzlEditEntity, sjzlGetInfo, sjzlPerfectlnfo, sjzlFstz, sjzlQryp, sjzlPerfectSorce } from '@//api/yj.js'
import { sjzlGetInfo, sjzlPerfectlnfo, sjzlFstz, sjzlQryp, sjzlPerfectSorce } from '@//api/yj.js'
import { selfBuildJudgment } from "@/api/huiShangyp/tacticalApi.js"
// import { tacticalGet, strategicDelete } from "@/api/huiShangyp/strategicApi.js";
import { getItem } from '@//utils/storage.js'
const emit = defineEmits(["updateDate", "getList"]);
const props = defineProps({
dict: Object
dict: Object,
/** 报告类型 01 战术研判 02战略研判 */
bglx: String,
});
const imgMsg = ref([])
const { proxy } = getCurrentInstance();
@ -146,7 +158,7 @@ watch(() => props.dict, (val) => {
formData.value = [
{ label: "研判议题", prop: "ypyt", type: "input", width: '48%' },
{ label: "研判时间", prop: "ypsj", type: "datetime", width: '48%' },
{ label: "报告类型", prop: "bglx", type: "radio", options: props.dict.D_BZ_YPLX, width: '48%' },
{ label: "报告类型", prop: "bglx", type: "radio", options: props.dict.D_BZ_YPLX, width: '48%', disabled: true },
{ label: "研判方式", prop: "ypfs", type: "radio", options: props.dict.D_BZ_YPFS, width: '48%' },
{ label: "参与研判部门", prop: "jsdxBmDm", type: "department", multiple: true, depMc: 'jsdxBmMc', width: '48%' },
{ label: "研判要求", prop: "ypyq", type: "textarea", width: '100%' },
@ -164,6 +176,8 @@ function updateDis(row) {
const isFinish = row.wcqk === '02'
return isShiQingBaoZhongXin.value || !iscjr || isFinish
}
/** 获取附件数组 */
function getFjArr(fj) {
if (typeof fj !== 'string' || !fj) return []
let fjArr = []
@ -175,18 +189,19 @@ function getFjArr(fj) {
return fjArr
}
watch(() => listQuery.value.jsdxBmDm, (val) => {
/** @type {Array<{ypbmdm: string, ypbmmc: string, scyq: string, fj: Array, wcqk: string}>} 参与研判部门数据数组 */
/** @type {Array<JudgmentDept>} 参与研判部门数据数组 */
const arr = Array.isArray(val) ? val : []
/** @type {Array} 参与研判部门*/
const cyypList = Array.isArray(listQuery.value.cyypList) ? listQuery.value.cyypList : []
tableList.value = arr.map((item, i) => {
/** 找原来的对象 */
const curr = cyypList.find(item2 => item2.ypbmdm == item) || {}
/** 是否是新增 */
const isAddForm = !listQuery.value.id
const isAddForm = !listQuery.value?.id
return {
// id: null,
// sjzlid: null, // 研判战研判ID
// sjzlid: null, // 研判战研判ID
/** 部门代码 */
ypbmdm: item,
ypbmmc: listQuery.value.jsdxBmMc[i],
@ -202,13 +217,24 @@ watch(() => listQuery.value.jsdxBmDm, (val) => {
const init = (type, row, wjlb) => {
dialogForm.value = true;
title.value = type == "add" ? "新增" : type == "edit" ? "编辑" : "详情";
outRow.value = row || {}
if (row) { getDataById(row.id) }
outRow.value = row
if (row) {
getDataById(row.id)
} else {
listQuery.value = {
bglx: props.bglx, // 报告类型 01 战术研判 02战略研判
ypfs: undefined, // 研判方式
ypsj: undefined, // 研判时间
ypyq: undefined, // 研判要求
cyypList: [], // 参与研判部门数据数组
}
}
};
// 根据id查询详情
const getDataById = (id) => {
sjzlGetInfo(id).then((res) => {
listQuery.value = res || {};
/** @type {Array<JudgmentDept>} 参与研判部门数据数组 */
const cyypList = Array.isArray(res.cyypList) ? res.cyypList : []
listQuery.value.jsdxBmDm = cyypList.map(item => {
return item.ypbmdm
@ -218,21 +244,21 @@ const getDataById = (id) => {
})
});
};
/** 获取附件json字符串 */
function getFjString(arr) {
arr = Array.isArray(arr) ? arr : []
return JSON.stringify(arr)
}
/**获取下发部门数据 */
const getXfbmList = () => {
/** @type {Array<JudgmentDept>} 参与研判部门数据数组 */
const cyypList = Array.isArray(listQuery.value.cyypList) ? listQuery.value.cyypList : []
return tableList.value.map((item, i) => {
/** 找原来的对象 */
/** @type {JudgmentDept} 找原来的对象 */
const curr = cyypList.find(item => item.ypbmdm == item.ypbmdm) || {}
return {
id: curr.id || null,
sjzlid: curr.sjzlid || null, // 研判战研判ID
sjzlid: curr.sjzlid || null, // 研判战研判ID
ypbmdm: item.ypbmdm,
ypbmmc: listQuery.value.jsdxBmMc[i],
/** 素材要求 */
@ -251,21 +277,14 @@ const submit = () => {
let params = {
...listQuery.value,
cyypList: xfbmList,
// cyypList: listQuery.value.jsdxBmDm.map((item, index) => {
// return {
// ypbmdm: item,
// ypbmmc: listQuery.value.jsdxBmMc[index],
// ypcylx: '01'
// }
// })
};
try {
loading.value = true;
let res
if (title.value == "新增") {
res = await sjzlAddEntity(params)
res = await selfBuildJudgment(params)
} else {
res = await sjzlEditEntity(params)
// res = await sjzlEditEntity(params)// 目前没有修改
}
if (res && res > 0) {
loading.value = false;
@ -357,7 +376,7 @@ const submitMaterialAction = async () => {
closeMaterialDialog()
if (outRow.value.id) { getDataById(outRow.value.id) }
} catch (error) {
console.log(error)
} finally {
materialLoading.value = false
}
@ -383,7 +402,7 @@ const sendNotice = () => {
proxy.$message({ type: "success", message: "下发通知成功" });
close();
} catch (error) {
console.log(error);
} finally {
noticeLoading.value = false;
}
@ -404,7 +423,7 @@ const confirmJudgment = () => {
proxy.$message({ type: "success", message: "确认研判成功" });
close();
} catch (error) {
console.log(error);
} finally {
confirmLoading.value = false;
}

View File

@ -0,0 +1,254 @@
<template>
<div class="dialog" v-if="dialogForm">
<div class="head_box">
<span class="title">经验分享{{ title }} </span>
<div>
<el-button type="primary" size="small" :loading="loading" @click="submit">保存</el-button>
<el-button size="small" @click="close">关闭</el-button>
</div>
</div>
<div class="form_cnt">
<FormMessage :formList="formData" v-model="listQuery" ref="elform" :rules="rules">
<template #ypnr>
<el-input v-model="listQuery.ypnr" style="width: 100%" placeholder="请输入关键字" class="input-with-select">
<template #append>
<el-button :icon="Search" type="primary">搜索</el-button>
</template>
</el-input>
</template>
<template #fj><el-button type="primary" @click="showText = true">附件上传</el-button></template>
</FormMessage>
<div class="cntBox">
<!-- 工具栏 -->
<Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :defaultConfig="toolbarConfig"
:mode="mode" />
<!-- 编辑器 -->
<Editor :style="`height: 480px; overflow-y: hidden`" v-model="textContent" :defaultConfig="editorConfig"
:mode="mode" @onCreated="handleCreated" @onChange="handChange" />
</div>
<div style="display: flex; justify-content: center;">
<!-- <el-button style="display: block;" type="primary" @click="ConsultationShow = true">网上会商</el-button> -->
<el-button style="display: block;" type="primary" @click="downloadWithStyles(textContent)">下载</el-button>
</div>
</div>
</div>
<!-- 文字解析 -->
<ExtractionText v-model="showText" @change="getText"></ExtractionText>
<!-- 网上会商 -->
<!-- <Consultation v-model="ConsultationShow" /> -->
</template>
<script setup>
import { Search } from '@element-plus/icons-vue'
import { timeValidate } from '@/utils/tools.js'
import ExtractionText from "@/components/ExtractionText/index.vue";
import FormMessage from "@/components/aboutTable/FormMessage.vue";
import { qcckGet, qcckPost, qcckPut } from "@/api/qcckApi.js";
import "@wangeditor/editor/dist/css/style.css";
// import Consultation from './consultation.vue'
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
import { ref, defineExpose, reactive, defineEmits, getCurrentInstance, shallowRef, onBeforeUnmount, watch } from "vue";
import { gsxtYpbgAddEntity } from "@/api/huiShangyp/tacticalApi.js"
const emit = defineEmits(["updateDate"]);
const props = defineProps({
dic: Object,
});
const ConsultationShow = ref(false)
const showText = ref(false);
const textContent = ref()
const { proxy } = getCurrentInstance();
const editorRef = shallowRef();
const dialogForm = ref(false); //弹窗
const mode = "default";
const dataBt = ref(`<p class="html_bt">
<h4 style=\"text-align: center;\"><span style=\"color: rgb(225, 60, 57); font-size: 32px; font-family: 标楷体;\">林芝市公安局情指中心</span></h4>
<h4 style=\"text-align: center;\"><span style=\"color: rgb(225, 60, 57); font-size: 32px; font-family: 标楷体;\">研判专刊(初稿)</span></h4></p>
<p style="text-align: center;"><span style="color: rgb(225, 60, 57); font-size: 22px; font-family: 标楷体;">市公安局情指中心编</span></p>
<p style="text-align: center;"><span style="color: rgb(225, 60, 57); font-size: 22px; font-family: 标楷体;"> ${timeValidate(new Date(), 'td')}</span></p>
<hr/>`)
//编辑器配置
const editorConfig = {
withCredentials: true, //允许跨域
placeholder: props.placeholder, //提示语
MENU_CONF: {
uploadImage: {
// 自定义上传图片
async customUpload(file, insertFn) {
let fileBlob = await compressImage(file);
let fileData = new File([fileBlob], fileBlob.name, { type: fileBlob.type });
if (fileData.size > 2 * 1024 * 1024) {
ElMessage({ message: "图片超过2MB", type: "success" });
} else {
await uploadFn(fileData, insertFn);
}
}
},
uploadVideo: {
// 自定义上传视频
async customUpload(file, insertFn) {
await uploadFn(file, insertFn);
}
}
}
};
//工具配置
const toolbarConfig = {
excludeKeys: ["blockquote", "codeBlock"] //清除不必要的工具,引用和代码块
};
const rules = reactive({
jymc: [
{ required: true, message: "请输入经验名称", trigger: "blur" }
],
fbnr: [
{ required: true, message: "请输入经验内容", trigger: "blur" }
]
});
const formData = ref([
{ label: "研判标题", prop: "ypbt", type: "input", width: "100%" },
{ label: "研判内容", prop: "ypnr", type: "slot", width: "100%" },
{ label: "附件内容", prop: "fj", type: "slot", width: "100%" },
]);
const listQuery = ref({
ypbt: "",
ypnr: "",
fj: ""
}); //表单
const loading = ref(false);
const elform = ref();
const title = ref("");
const outRow = ref({})
// 初始化数据
const init = (type, row) => {
dialogForm.value = true;
title.value = type == "add" ? "新增" : "编辑";
};
const getText = (val, row = {}) => {
outRow.value = { ...row }
listQuery.value.fj = val.text;
}
watch(() => listQuery.value, (val) => {
let html = dataBt.value;
html += `<p style="text-align: center;"><span style="font-size: 22px;">${val.ypbt || ''}</span></p>`
html += `<p>${val.fj || ''}</p>`
textContent.value = html
},
{
deep: true, immediate: true
})
// 提交
const submit = () => {
elform.value.submit((data) => {
// let url = title.value == "新增" ? "/mosty-gsxt/gsxt/jyfx/add" : "/mosty-gsxt/gsxt/jyfx/edit";
let params = {
...data,
ypid: outRow.value.id,
ypmc: outRow.value.ypyt
};
gsxtYpbgAddEntity(params).then(() => {
loading.value = false;
proxy.$message({ type: "success", message: title.value + "成功" });
emit("ok");
close();
}).catch(() => {
loading.value = false;
})
});
};
//编辑器创建成功
const handleCreated = (editor) => {
editorRef.value = editor;
};
//内容发生变化
const handChange = (editor) => {
// 判断是否是一个空段落,是空就传空文本
console.log(editor.getHtml(), 'editor.getHtml()');
};
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor) editor.destroy();
});
// 关闭
const close = () => {
listQuery.value = {};
loading.value = false;
dialogForm.value = false;
listQuery.value = {}
};
// 带样式的下载方法
const downloadWithStyles = (textContent) => {
const wordDocument = `
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" >
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>富文本导出</title>
<style>
/* 应用两端对齐样式 */
body {
text-align: justify;
text-justify: inter-character; /* 中文文本两端对齐 */
font-family: "Microsoft YaHei", Arial, sans-serif;
}
p {
text-align: justify;
text-justify: inter-character;
}
</style>
</head>
<body>
${textContent}
</body>
</html>
`;
const blob = new Blob([wordDocument], {
type: 'application/msword'
});
saveAs(blob, '战略研判报告.doc');
};
defineExpose({ init });
</script>
<style lang="scss" scoped>
@import "~@/assets/css/layout.scss";
@import "~@/assets/css/element-plus.scss";
.boxlist {
width: 99%;
margin-top: 10px;
}
::v-deep .el-textarea__inner {
min-height: 550px !important;
}
.html_bt>>>p {
color: red;
}
.html_bt {
h4 {
margin: 10px 0 !important;
}
}
.cntBox {
width: 80vw;
margin: 0 auto;
}
</style>

View File

@ -2,12 +2,12 @@
<div>
<div class="titleBox">
<PageTitle title="战术研判">
<!-- <el-button type="primary" @click="getDataById('add', '')">
<el-button type="primary" @click="selfCreateResearch('add')">
<el-icon style="vertical-align: middle">
<CirclePlus />
</el-icon>
<span style="vertical-align: middle">新增</span>
</el-button> -->
<span style="vertical-align: middle">自建研判</span>
</el-button>
</PageTitle>
</div>
@ -29,6 +29,7 @@
<template #controls="{ row }">
<!-- <el-link size="small" type="primary" @click="getDataById('edit', row)">修改</el-link> -->
<el-link size="small" type="primary" @click="getDataById('detail', row)">详情</el-link>
<el-link size="small" type="primary" @click="createReport(row)">创建报告</el-link>
<el-link size="small" type="danger" @click="deleteFile(row)">删除</el-link>
</template>
</MyTable>
@ -38,7 +39,10 @@
}"></Pages>
</div>
</div>
<AddForm ref="addForm" @getList="getList" :dict="{ D_BZ_YPFS, D_BZ_YPLX }" />
<AddForm ref="addForm" :bglx="bglx" @getList="getList" :dict="{ D_BZ_YPFS, D_BZ_YPLX }" />
<!-- 创建报告 -->
<addReport ref="reportTc" :row="currRow" :dic="{ D_GS_BQ_LX }" @ok="getList" />
</template>
<script setup>
@ -46,21 +50,29 @@ import PageTitle from "@/components/aboutTable/PageTitle.vue";
import MyTable from "@/components/aboutTable/MyTable.vue";
import Pages from "@/components/aboutTable/Pages.vue";
import Search from "@/components/aboutTable/Search.vue";
import { useRoute } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import { tacticalGet, strategicDelete } from "@/api/huiShangyp/tacticalApi.js";
import { reactive, ref, onMounted, getCurrentInstance, watch, computed } from "vue";
import { reactive, ref, onMounted, getCurrentInstance, watch, computed, nextTick } from "vue";
import addReport from "./addReport.vue";
import AddForm from "./addForm.vue";
const { proxy } = getCurrentInstance();
const { D_BZ_YPFS, D_BZ_YPLX } = proxy.$dict("D_BZ_YPFS", "D_BZ_YPLX")
const detailDiloag = ref();
const { D_BZ_YPFS, D_BZ_YPLX, D_GS_BQ_LX } = proxy.$dict("D_BZ_YPFS", "D_BZ_YPLX", "D_GS_BQ_LX")
/** 报告弹框 */
const reportTc = ref();
const searchBox = ref(); //搜索框
const router = useRouter();
const route = useRoute();
onMounted(() => {
tabHeightFn()
if (route.query.id) {
detailDiloag.value.init('edit', {
id: route.query.id
});
return
nextTick(() => {
addForm.value && addForm.value.init('edit', {
id: route.query.id
});
router.replace({ path: '/tacticalResearch' })// 移除id 避免刷新一直带参数
})
}
getList()
});
@ -98,8 +110,12 @@ const pageData = reactive({
{ label: "发起部门", prop: "ssbm" },
]
});
const queryFrom = ref({});
const isShowReport = ref(false)
const currRow = ref({})
const queryFrom = ref({});
/** 报告类型 01 战术研判 02 战略研判*/
const bglx = ref('01')
// 搜索
const onSearch = (val) => {
const promes = {
@ -123,7 +139,7 @@ const changeSize = (val) => {
// 获取列表
const getList = () => {
pageData.tableConfiger.loading = true;
// bglx 报告类型01 战术研判 02 战略研判)
// bglx 报告类型01 战术研判 02 战略研判)
let data = { ...pageData.pageConfiger, ...queryFrom.value, bglx: '01' };
tacticalGet(data).then(res => {
pageData.tableData = res.records || [];
@ -140,7 +156,7 @@ const tabHeightFn = () => {
tabHeightFn();
};
};
const route = useRoute()
// const route = useRoute()
const addForm = ref(null)
const getDataById = (type, row) => {
@ -163,6 +179,17 @@ const deleteFile = (row) => {
proxy.$message.info('已取消删除');
});
}
const selfCreateResearch = (type = 'add') => {
addForm.value.init(type, null, '01');
}
const createReport = (row) => {
reportTc.value.init('add',row)
// currRow.value = { ...row }
// isShowReport.value = true
}
</script>
<style lang="scss" scoped>

View File

@ -6,14 +6,15 @@
</div>
</div>
<!-- <div class="comom-cnt zdryBox"> -->
<transition name="flip" mode="out-in">
<div :key="'qb'" v-if="reversalShow" class="flip-wrapper">
<TacticalYp></TacticalYp>
</div>
<div :key="'text'" v-else class="flip-wrapper">
<StrategyYp></StrategyYp>
</div>
</transition>
<transition name="flip" mode="out-in">
<div :key="'qb'" v-if="reversalShow" class="flip-wrapper">
<TacticalYp bglx="01"></TacticalYp>
</div>
<div :key="'text'" v-else class="flip-wrapper">
<TacticalYp bglx="02"></TacticalYp>
<!-- <StrategyYp bglx="01"></StrategyYp> -->
</div>
</transition>
<!-- </div> -->
</template>
@ -155,6 +156,7 @@ const reversalShow = ref(true)
font-size: 14px;
color: rgb(255, 146, 4);
}
/* 包装层样式 */
.flip-wrapper {
position: relative;

View File

@ -1,12 +1,12 @@
<template>
<div class="comom-cnt" >
<div class="comom-cnt">
<div class="zdryBox">
<ul class="ryBox" :infinite-scroll-distance="30" ref="carouselList" @mouseenter="stopAutoScroll" @mouseleave="startAutoScroll"
v-loading="loading" v-infinite-scroll="loadList">
<ul class="ryBox" :infinite-scroll-distance="30" ref="carouselList" @mouseenter="stopAutoScroll"
@mouseleave="startAutoScroll" v-loading="loading" v-infinite-scroll="loadList">
<li v-for="item in personList" :key="item.id" @click="chooseItem(item)">
<div>{{ item.title }}</div>
<div class="meta-info">{{ item.time }}{{ item.fbrxm }}</div>
<div>{{ item.content }}</div>
<div>{{ item.ypyq }}</div>
<div class="meta-info">{{ item.ypsj }}{{ item.cjrxm }}</div>
<div>{{ item.ypyt }}</div>
</li>
<MOSTY.Empty :show="!loading && personList.length <= 0" :imgSize="100"></MOSTY.Empty>
</ul>
@ -17,16 +17,33 @@
</template>
<script setup>
import { tbGsxtXxltSelectPage } from '@/api/tbGsxtXxltHf'
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
import {useRouter} from 'vue-router'
import { tacticalGet } from '@/api/huiShangyp/tacticalApi.js'
import { defineProps, ref, reactive, onMounted, onBeforeUnmount } from 'vue';
import { useRouter } from 'vue-router'
import * as MOSTY from "@/components/MyComponents/index";
const router = useRouter()
const emit = defineEmits(["reversalPush"])
const reversalPush = () => {
emit('reversalPush')
}
// 数据相关
const props = defineProps({
/** bglx 报告类型01 战术研判 02 战略研判) */
bglx: {
type: String,
default: '01'
}
})
/**
* @typedef {Object} YpItem
* @property {string} ypsj - 研判时间
* @property {string} ypyt - 研判议题
* @property {string} ypyq - 研判要求
* @property {string} cjrxm - 创建人
*/
/** @type {YpItem[]} 战术,战略研判*/
const personList = ref([]);
const loading = ref(false);
const loadingMore = ref(false);
@ -42,12 +59,13 @@ let scrollTimer = null;
const getList = (type) => {
loading.value = !type ? true : false;
loadingMore.value = !!type;
tbGsxtXxltSelectPage({ pageSize: 10, pageCurrent: pageNum.value }).then(res => {
tacticalGet({ pageSize: 10, pageCurrent: pageNum.value, bglx: props.bglx }).then(res => {
loading.value = false;
loadingMore.value = false;
let arr = res.records || [];
/** @type {YpItem[]} 战术,战略研判*/
let arr = Array.isArray(res?.records) ? res.records : [];
personList.value = pageNum.value == 1 ? arr : personList.value.concat(arr);
total.value = res.total;
total.value = res?.total || 0;
}).catch(() => {
loading.value = false;
loadingMore.value = false;
@ -98,8 +116,10 @@ const stopAutoScroll = () => {
// 点击项
const chooseItem = (item) => {
stopAutoScroll(); // 点击时停止自动滚动
const path = props.bglx === '01' ? '/tacticalResearch' : '/strategicResearch'
router.push({
path: '/forumPost',
// path: '/forumPost',
path: path,
query: { id: item.id }
})
};
@ -107,7 +127,9 @@ const chooseItem = (item) => {
// 添加跳转
const chooseForumPost = () => {
stopAutoScroll(); // 点击时停止自动滚动
router.push({ path: '/forumPost' })
const path = props.bglx === '01' ? '/tacticalResearch' : '/strategicResearch'
router.push({ path: path })
};
// 生命周期
@ -160,8 +182,9 @@ onBeforeUnmount(() => {
&::-webkit-scrollbar {
display: none;
}
-ms-overflow-style: none; // IE和Edge
scrollbar-width: none; // Firefox
-ms-overflow-style: none; // IE和Edge
scrollbar-width: none; // Firefox
li {
padding: 12px;
@ -174,7 +197,7 @@ onBeforeUnmount(() => {
background-color: rgba(20, 107, 190, 0.2);
}
> div:first-child {
>div:first-child {
font-weight: bold;
color: #fff;
margin-bottom: 8px;
@ -192,7 +215,7 @@ onBeforeUnmount(() => {
margin-bottom: 8px;
}
> div:last-child {
>div:last-child {
color: rgba(255, 255, 255, 0.8);
font-size: 13px;
line-height: 1.6;
@ -206,7 +229,8 @@ onBeforeUnmount(() => {
}
}
}
.switchover{
.switchover {
cursor: pointer;
font-size: 14px;
margin-left: 20px;